diff --git a/apache-poi/README.md b/apache-poi/README.md
index 599b21e063..d500787536 100644
--- a/apache-poi/README.md
+++ b/apache-poi/README.md
@@ -11,3 +11,4 @@ This module contains articles about Apache POI
- [Get String Value of Excel Cell with Apache POI](https://www.baeldung.com/java-apache-poi-cell-string-value)
- [Read Excel Cell Value Rather Than Formula With Apache POI](https://www.baeldung.com/apache-poi-read-cell-value-formula)
- [Setting Formulas in Excel with Apache POI](https://www.baeldung.com/java-apache-poi-set-formulas)
+- [Insert a Row in Excel Using Apache POI](https://www.baeldung.com/apache-poi-insert-excel-row)
diff --git a/core-java-modules/core-java-collections-4/README.md b/core-java-modules/core-java-collections-4/README.md
index 6e117c98b1..4fd77473d4 100644
--- a/core-java-modules/core-java-collections-4/README.md
+++ b/core-java-modules/core-java-collections-4/README.md
@@ -5,3 +5,4 @@
### Relevant Articles:
- [ArrayList vs. LinkedList vs. HashMap in Java](https://www.baeldung.com/java-arraylist-vs-linkedlist-vs-hashmap)
+- [Java Deque vs. Stack](https://www.baeldung.com/java-deque-vs-stack)
diff --git a/core-java-modules/core-java-collections-maps-3/README.md b/core-java-modules/core-java-collections-maps-3/README.md
index 8b84ecbf81..530a9310c2 100644
--- a/core-java-modules/core-java-collections-maps-3/README.md
+++ b/core-java-modules/core-java-collections-maps-3/README.md
@@ -8,4 +8,5 @@ This module contains articles about Map data structures in Java.
- [The Map.computeIfAbsent() Method](https://www.baeldung.com/java-map-computeifabsent)
- [Collections.synchronizedMap vs. ConcurrentHashMap](https://www.baeldung.com/java-synchronizedmap-vs-concurrenthashmap)
- [Java HashMap Load Factor](https://www.baeldung.com/java-hashmap-load-factor)
+- [Converting java.util.Properties to HashMap](https://www.baeldung.com/java-convert-properties-to-hashmap)
- More articles: [[<-- prev]](/core-java-modules/core-java-collections-maps-2)
diff --git a/core-java-modules/core-java-io-conversions/src/test/java/com/baeldung/filetoinputstream/JavaXToInputStreamUnitTest.java b/core-java-modules/core-java-io-conversions/src/test/java/com/baeldung/filetoinputstream/JavaXToInputStreamUnitTest.java
index caedd1747b..c20752639f 100644
--- a/core-java-modules/core-java-io-conversions/src/test/java/com/baeldung/filetoinputstream/JavaXToInputStreamUnitTest.java
+++ b/core-java-modules/core-java-io-conversions/src/test/java/com/baeldung/filetoinputstream/JavaXToInputStreamUnitTest.java
@@ -5,12 +5,12 @@ import com.google.common.io.CharSource;
import com.google.common.io.Files;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
-import org.apache.commons.io.input.ReaderInputStream;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
+import java.nio.charset.StandardCharsets;
public class JavaXToInputStreamUnitTest {
protected final Logger logger = LoggerFactory.getLogger(getClass());
@@ -28,7 +28,7 @@ public class JavaXToInputStreamUnitTest {
@Test
public final void givenUsingGuava_whenConvertingStringToInputStream_thenCorrect() throws IOException {
final String initialString = "text";
- final InputStream targetStream = new ReaderInputStream(CharSource.wrap(initialString).openStream());
+ final InputStream targetStream = CharSource.wrap(initialString).asByteSource(StandardCharsets.UTF_8).openStream();
IOUtils.closeQuietly(targetStream);
}
diff --git a/core-java-modules/core-java-reflection-2/pom.xml b/core-java-modules/core-java-reflection-2/pom.xml
index ea76ee8c98..02a806f87c 100644
--- a/core-java-modules/core-java-reflection-2/pom.xml
+++ b/core-java-modules/core-java-reflection-2/pom.xml
@@ -14,6 +14,15 @@
../
+
+
+ org.springframework
+ spring-test
+ ${spring.version}
+ test
+
+
+
core-java-reflection-2
@@ -40,5 +49,6 @@
3.8.0
1.8
1.8
+ 5.3.4
\ No newline at end of file
diff --git a/core-java-modules/core-java-reflection-2/src/main/java/com/baeldung/reflection/access/privatemethods/LongArrayUtil.java b/core-java-modules/core-java-reflection-2/src/main/java/com/baeldung/reflection/access/privatemethods/LongArrayUtil.java
new file mode 100644
index 0000000000..03d8daabbf
--- /dev/null
+++ b/core-java-modules/core-java-reflection-2/src/main/java/com/baeldung/reflection/access/privatemethods/LongArrayUtil.java
@@ -0,0 +1,18 @@
+package com.baeldung.reflection.access.privatemethods;
+
+public class LongArrayUtil {
+
+ public static int indexOf(long[] array, long target) {
+ return indexOf(array, target, 0, array.length);
+ }
+
+ private static int indexOf(long[] array, long target, int start, int end) {
+ for (int i = start; i < end; i++) {
+ if (array[i] == target) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+}
diff --git a/core-java-modules/core-java-reflection-2/src/test/java/com/baeldung/reflection/access/privatemethods/InvokePrivateMethodsUnitTest.java b/core-java-modules/core-java-reflection-2/src/test/java/com/baeldung/reflection/access/privatemethods/InvokePrivateMethodsUnitTest.java
new file mode 100644
index 0000000000..e98eb9ec7f
--- /dev/null
+++ b/core-java-modules/core-java-reflection-2/src/test/java/com/baeldung/reflection/access/privatemethods/InvokePrivateMethodsUnitTest.java
@@ -0,0 +1,28 @@
+package com.baeldung.reflection.access.privatemethods;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.lang.reflect.Method;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class InvokePrivateMethodsUnitTest {
+
+ private final long[] someLongArray = new long[] { 1L, 2L, 1L, 4L, 2L };
+
+ @Test
+ void whenSearchingForLongValueInSubsequenceUsingReflection_thenTheCorrectIndexOfTheValueIsReturned() throws Exception {
+ Method indexOfMethod = LongArrayUtil.class.getDeclaredMethod("indexOf", long[].class, long.class, int.class, int.class);
+ indexOfMethod.setAccessible(true);
+
+ assertEquals(2, indexOfMethod.invoke(LongArrayUtil.class, someLongArray, 1L, 1, someLongArray.length), "The index should be 2.");
+ }
+
+ @Test
+ void whenSearchingForLongValueInSubsequenceUsingSpring_thenTheCorrectIndexOfTheValueIsReturned() throws Exception {
+ int indexOfSearchTarget = ReflectionTestUtils.invokeMethod(LongArrayUtil.class, "indexOf", someLongArray, 1L, 1, someLongArray.length);
+ assertEquals(2, indexOfSearchTarget, "The index should be 2.");
+ }
+
+}
diff --git a/core-java-modules/core-java-security-2/README.md b/core-java-modules/core-java-security-2/README.md
index 9b99d624c9..3f9520b888 100644
--- a/core-java-modules/core-java-security-2/README.md
+++ b/core-java-modules/core-java-security-2/README.md
@@ -15,4 +15,5 @@ This module contains articles about core Java Security
- [Security Context Basics: User, Subject and Principal](https://www.baeldung.com/security-context-basics)
- [Java AES Encryption and Decryption](https://www.baeldung.com/java-aes-encryption-decryption)
- [InvalidAlgorithmParameterException: Wrong IV Length](https://www.baeldung.com/java-invalidalgorithmparameter-exception)
+- [The java.security.egd JVM Option](https://www.baeldung.com/java-security-egd)
- More articles: [[<-- prev]](/core-java-modules/core-java-security)
diff --git a/data-structures/src/main/java/com/baeldung/circularlinkedlist/CircularLinkedList.java b/data-structures/src/main/java/com/baeldung/circularlinkedlist/CircularLinkedList.java
index 47368d7f15..cf18f0783e 100644
--- a/data-structures/src/main/java/com/baeldung/circularlinkedlist/CircularLinkedList.java
+++ b/data-structures/src/main/java/com/baeldung/circularlinkedlist/CircularLinkedList.java
@@ -5,7 +5,7 @@ import org.slf4j.LoggerFactory;
public class CircularLinkedList {
- final Logger LOGGER = LoggerFactory.getLogger(CircularLinkedList.class);
+ final Logger logger = LoggerFactory.getLogger(CircularLinkedList.class);
private Node head = null;
private Node tail = null;
@@ -42,24 +42,29 @@ public class CircularLinkedList {
}
public void deleteNode(int valueToDelete) {
-
Node currentNode = head;
-
- if (head != null) {
- if (currentNode.value == valueToDelete) {
- head = head.nextNode;
- tail.nextNode = head;
- } else {
- do {
- Node nextNode = currentNode.nextNode;
- if (nextNode.value == valueToDelete) {
- currentNode.nextNode = nextNode.nextNode;
- break;
- }
- currentNode = currentNode.nextNode;
- } while (currentNode != head);
- }
+ if (head == null) {
+ return;
}
+ do {
+ Node nextNode = currentNode.nextNode;
+ if (nextNode.value == valueToDelete) {
+ if (tail == head) {
+ head = null;
+ tail = null;
+ } else {
+ currentNode.nextNode = nextNode.nextNode;
+ if (head == nextNode) {
+ head = head.nextNode;
+ }
+ if (tail == nextNode) {
+ tail = currentNode;
+ }
+ }
+ break;
+ }
+ currentNode = nextNode;
+ } while (currentNode != head);
}
public void traverseList() {
@@ -68,7 +73,7 @@ public class CircularLinkedList {
if (head != null) {
do {
- LOGGER.info(currentNode.value + " ");
+ logger.info(currentNode.value + " ");
currentNode = currentNode.nextNode;
} while (currentNode != head);
}
diff --git a/data-structures/src/test/java/com/baeldung/circularlinkedlist/CircularLinkedListUnitTest.java b/data-structures/src/test/java/com/baeldung/circularlinkedlist/CircularLinkedListUnitTest.java
index 5b0573a1ce..23829df7e9 100644
--- a/data-structures/src/test/java/com/baeldung/circularlinkedlist/CircularLinkedListUnitTest.java
+++ b/data-structures/src/test/java/com/baeldung/circularlinkedlist/CircularLinkedListUnitTest.java
@@ -1,10 +1,10 @@
package com.baeldung.circularlinkedlist;
+import org.junit.Test;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import org.junit.Test;
-
public class CircularLinkedListUnitTest {
@Test
@@ -23,7 +23,7 @@ public class CircularLinkedListUnitTest {
}
@Test
- public void givenACircularLinkedList_WhenDeletingElements_ThenListDoesNotContainThoseElements() {
+ public void givenACircularLinkedList_WhenDeletingInOrderHeadMiddleTail_ThenListDoesNotContainThoseElements() {
CircularLinkedList cll = createCircularLinkedList();
assertTrue(cll.containsNode(13));
@@ -39,6 +39,32 @@ public class CircularLinkedListUnitTest {
assertFalse(cll.containsNode(46));
}
+ @Test
+ public void givenACircularLinkedList_WhenDeletingInOrderTailMiddleHead_ThenListDoesNotContainThoseElements() {
+ CircularLinkedList cll = createCircularLinkedList();
+
+ assertTrue(cll.containsNode(46));
+ cll.deleteNode(46);
+ assertFalse(cll.containsNode(46));
+
+ assertTrue(cll.containsNode(1));
+ cll.deleteNode(1);
+ assertFalse(cll.containsNode(1));
+
+ assertTrue(cll.containsNode(13));
+ cll.deleteNode(13);
+ assertFalse(cll.containsNode(13));
+ }
+
+ @Test
+ public void givenACircularLinkedListWithOneNode_WhenDeletingElement_ThenListDoesNotContainTheElement() {
+ CircularLinkedList cll = new CircularLinkedList();
+ cll.addNode(1);
+ cll.deleteNode(1);
+ assertFalse(cll.containsNode(1));
+ }
+
+
private CircularLinkedList createCircularLinkedList() {
CircularLinkedList cll = new CircularLinkedList();
diff --git a/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/bidirection/ItemWithRef.java b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/bidirection/ItemWithRef.java
index 0ca8d721e8..1bd2451c6c 100644
--- a/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/bidirection/ItemWithRef.java
+++ b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/bidirection/ItemWithRef.java
@@ -1,12 +1,12 @@
package com.baeldung.jackson.bidirection;
-import com.fasterxml.jackson.annotation.JsonManagedReference;
+import com.fasterxml.jackson.annotation.JsonBackReference;
public class ItemWithRef {
public int id;
public String itemName;
- @JsonManagedReference
+ @JsonBackReference
public UserWithRef owner;
public ItemWithRef() {
diff --git a/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/bidirection/UserWithRef.java b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/bidirection/UserWithRef.java
index 3de03fc651..cc7b2b52ca 100644
--- a/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/bidirection/UserWithRef.java
+++ b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/bidirection/UserWithRef.java
@@ -3,13 +3,13 @@ package com.baeldung.jackson.bidirection;
import java.util.ArrayList;
import java.util.List;
-import com.fasterxml.jackson.annotation.JsonBackReference;
+import com.fasterxml.jackson.annotation.JsonManagedReference;
public class UserWithRef {
public int id;
public String name;
- @JsonBackReference
+ @JsonManagedReference
public List userItems;
public UserWithRef() {
@@ -19,7 +19,7 @@ public class UserWithRef {
public UserWithRef(final int id, final String name) {
this.id = id;
this.name = name;
- userItems = new ArrayList();
+ userItems = new ArrayList<>();
}
public void addItem(final ItemWithRef item) {
diff --git a/jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/bidirection/JacksonBidirectionRelationUnitTest.java b/jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/bidirection/JacksonBidirectionRelationUnitTest.java
index d0c5209891..b3ce27ad94 100644
--- a/jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/bidirection/JacksonBidirectionRelationUnitTest.java
+++ b/jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/bidirection/JacksonBidirectionRelationUnitTest.java
@@ -1,7 +1,9 @@
package com.baeldung.jackson.bidirection;
import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
@@ -16,7 +18,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
public class JacksonBidirectionRelationUnitTest {
- @Test(expected = JsonMappingException.class)
+ @Test (expected = JsonMappingException.class)
public void givenBidirectionRelation_whenSerializing_thenException() throws JsonProcessingException {
final User user = new User(1, "John");
final Item item = new Item(2, "book", user);
@@ -26,16 +28,39 @@ public class JacksonBidirectionRelationUnitTest {
}
@Test
- public void givenBidirectionRelation_whenUsingJacksonReferenceAnnotation_thenCorrect() throws JsonProcessingException {
+ public void givenBidirectionRelation_whenUsingJacksonReferenceAnnotationWithSerialization_thenCorrect() throws JsonProcessingException {
final UserWithRef user = new UserWithRef(1, "John");
final ItemWithRef item = new ItemWithRef(2, "book", user);
user.addItem(item);
- final String result = new ObjectMapper().writeValueAsString(item);
+ final String itemJson = new ObjectMapper().writeValueAsString(item);
+ final String userJson = new ObjectMapper().writeValueAsString(user);
- assertThat(result, containsString("book"));
- assertThat(result, containsString("John"));
- assertThat(result, not(containsString("userItems")));
+ assertThat(itemJson, containsString("book"));
+ assertThat(itemJson, not(containsString("John")));
+
+ assertThat(userJson, containsString("John"));
+ assertThat(userJson, containsString("userItems"));
+ assertThat(userJson, containsString("book"));
+ }
+
+ @Test
+ public void givenBidirectionRelation_whenUsingJacksonReferenceAnnotationWithDeserialization_thenCorrect() throws JsonProcessingException {
+ final UserWithRef user = new UserWithRef(1, "John");
+ final ItemWithRef item = new ItemWithRef(2, "book", user);
+ user.addItem(item);
+
+ final String itemJson = new ObjectMapper().writeValueAsString(item);
+ final String userJson = new ObjectMapper().writeValueAsString(user);
+
+ final ItemWithRef itemRead = new ObjectMapper().readValue(itemJson, ItemWithRef.class);
+ final UserWithRef userRead = new ObjectMapper().readValue(userJson, UserWithRef.class);
+
+ assertThat(itemRead.itemName, is("book"));
+ assertThat(itemRead.owner, nullValue());
+
+ assertThat(userRead.name, is("John"));
+ assertThat(userRead.userItems.get(0).itemName, is("book"));
}
@Test
diff --git a/java-collections-maps-3/README.md b/java-collections-maps-3/README.md
index bd1029c9cf..9b1d8510c2 100644
--- a/java-collections-maps-3/README.md
+++ b/java-collections-maps-3/README.md
@@ -3,3 +3,4 @@
- [Java Map With Case-Insensitive Keys](https://www.baeldung.com/java-map-with-case-insensitive-keys)
- [Using a Byte Array as Map Key in Java](https://www.baeldung.com/java-map-key-byte-array)
- [Using the Map.Entry Java Class](https://www.baeldung.com/java-map-entry)
+- [Optimizing HashMap’s Performance](https://www.baeldung.com/java-hashmap-optimize-performance)
diff --git a/jmeter/README.md b/jmeter/README.md
index 11351ffdda..53aca8d34b 100644
--- a/jmeter/README.md
+++ b/jmeter/README.md
@@ -52,3 +52,4 @@ Enjoy it :)
- [Intro to Performance Testing using JMeter](https://www.baeldung.com/jmeter)
- [Configure Jenkins to Run and Show JMeter Tests](https://www.baeldung.com/jenkins-and-jmeter)
+- [Write Extracted Data to a File Using JMeter](https://www.baeldung.com/jmeter-write-to-file)
diff --git a/libraries-http-2/pom.xml b/libraries-http-2/pom.xml
index d0bdb26bd4..855008521f 100644
--- a/libraries-http-2/pom.xml
+++ b/libraries-http-2/pom.xml
@@ -1,7 +1,7 @@
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
libraries-http-2
libraries-http-2
@@ -13,12 +13,17 @@
-
+
com.squareup.okhttp3
okhttp
${okhttp.version}
+
+ com.squareup.okhttp3
+ logging-interceptor
+ ${okhttp.version}
+
com.fasterxml.jackson.core
jackson-databind
@@ -81,9 +86,9 @@
- 3.14.2
+ 4.9.1
2.8.5
- 3.14.2
+ 4.9.1
1.0.3
9.4.19.v20190610
2.2.11
diff --git a/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/CacheControlResponeInterceptor.java b/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/CacheControlResponeInterceptor.java
new file mode 100644
index 0000000000..e9f8db19be
--- /dev/null
+++ b/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/CacheControlResponeInterceptor.java
@@ -0,0 +1,18 @@
+package com.baeldung.okhttp.interceptors;
+
+import java.io.IOException;
+
+import okhttp3.Interceptor;
+import okhttp3.Response;
+
+public class CacheControlResponeInterceptor implements Interceptor {
+
+ @Override
+ public Response intercept(Chain chain) throws IOException {
+ Response response = chain.proceed(chain.request());
+ return response.newBuilder()
+ .header("Cache-Control", "no-store")
+ .build();
+ }
+
+}
diff --git a/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/ErrorMessage.java b/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/ErrorMessage.java
new file mode 100644
index 0000000000..73411d947f
--- /dev/null
+++ b/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/ErrorMessage.java
@@ -0,0 +1,21 @@
+package com.baeldung.okhttp.interceptors;
+
+public class ErrorMessage {
+
+ private final int status;
+ private final String detail;
+
+ public ErrorMessage(int status, String detail) {
+ this.status = status;
+ this.detail = detail;
+ }
+
+ public int getStatus() {
+ return status;
+ }
+
+ public String getDetail() {
+ return detail;
+ }
+
+}
diff --git a/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/ErrorResponseInterceptor.java b/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/ErrorResponseInterceptor.java
new file mode 100644
index 0000000000..f6c6673705
--- /dev/null
+++ b/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/ErrorResponseInterceptor.java
@@ -0,0 +1,32 @@
+package com.baeldung.okhttp.interceptors;
+
+import java.io.IOException;
+
+import com.google.gson.Gson;
+
+import okhttp3.Interceptor;
+import okhttp3.MediaType;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+
+public class ErrorResponseInterceptor implements Interceptor {
+
+ public static final MediaType APPLICATION_JSON = MediaType.get("application/json; charset=utf-8");
+
+ @Override
+ public Response intercept(Chain chain) throws IOException {
+ Response response = chain.proceed(chain.request());
+
+ if(!response.isSuccessful()) {
+ Gson gson = new Gson();
+ String body = gson.toJson(new ErrorMessage(response.code(), "The response from the server was not OK"));
+ ResponseBody responseBody = ResponseBody.create(body, APPLICATION_JSON);
+
+ return response.newBuilder()
+ .body(responseBody)
+ .build();
+ }
+ return response;
+ }
+
+}
diff --git a/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/SimpleLoggingInterceptor.java b/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/SimpleLoggingInterceptor.java
new file mode 100644
index 0000000000..6d08546eea
--- /dev/null
+++ b/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/SimpleLoggingInterceptor.java
@@ -0,0 +1,25 @@
+package com.baeldung.okhttp.interceptors;
+
+import java.io.IOException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import okhttp3.Interceptor;
+import okhttp3.Request;
+import okhttp3.Response;
+
+public class SimpleLoggingInterceptor implements Interceptor {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(SimpleLoggingInterceptor.class);
+
+ @Override
+ public Response intercept(Chain chain) throws IOException {
+ Request request = chain.request();
+
+ LOGGER.info("Intercepted headers: {} from URL: {}", request.headers(), request.url());
+
+ return chain.proceed(request);
+ }
+
+}
diff --git a/libraries-http-2/src/test/java/com/baeldung/okhttp/interceptors/InterceptorIntegrationTest.java b/libraries-http-2/src/test/java/com/baeldung/okhttp/interceptors/InterceptorIntegrationTest.java
new file mode 100644
index 0000000000..d15b67ad3b
--- /dev/null
+++ b/libraries-http-2/src/test/java/com/baeldung/okhttp/interceptors/InterceptorIntegrationTest.java
@@ -0,0 +1,90 @@
+package com.baeldung.okhttp.interceptors;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.logging.HttpLoggingInterceptor;
+import okhttp3.logging.HttpLoggingInterceptor.Level;
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+
+public class InterceptorIntegrationTest {
+
+ @Rule
+ public MockWebServer server = new MockWebServer();
+
+ @Test
+ public void givenSimpleLogginInterceptor_whenRequestSent_thenHeadersLogged() throws IOException {
+ server.enqueue(new MockResponse().setBody("Hello Baeldung Readers!"));
+
+ OkHttpClient client = new OkHttpClient.Builder()
+ .addNetworkInterceptor(new SimpleLoggingInterceptor())
+ .build();
+
+ Request request = new Request.Builder()
+ .url(server.url("/greeting"))
+ .header("User-Agent", "A Baeldung Reader")
+ .build();
+
+ try (Response response = client.newCall(request).execute()) {
+ assertEquals("Response code should be: ", 200, response.code());
+ assertEquals("Body should be: ", "Hello Baeldung Readers!", response.body().string());
+ }
+ }
+
+ @Test
+ public void givenResponseInterceptor_whenRequestSent_thenCacheControlSetToNoStore() throws IOException {
+ server.enqueue(new MockResponse().setBody("Hello Baeldung Readers!"));
+
+ OkHttpClient client = new OkHttpClient.Builder()
+ .addInterceptor(getHttpLogger())
+ .addInterceptor(new CacheControlResponeInterceptor())
+ .build();
+
+ Request request = new Request.Builder()
+ .url(server.url("/greeting"))
+ .header("User-Agent", "A Baeldung Reader")
+ .build();
+
+ try (Response response = client.newCall(request).execute()) {
+ assertEquals("Response code should be: ", 200, response.code());
+ assertEquals("Body should be: ", "Hello Baeldung Readers!", response.body().string());
+ assertEquals("Response cache-control should be", "no-store", response.header("Cache-Control"));
+ }
+ }
+
+ @Test
+ public void givenErrorResponseInterceptor_whenResponseIs500_thenBodyIsJsonWithStatus() throws IOException {
+ server.enqueue(new MockResponse().setResponseCode(500).setBody("Hello Baeldung Readers!"));
+
+ OkHttpClient client = new OkHttpClient.Builder()
+ .addInterceptor(getHttpLogger())
+ .addInterceptor(new ErrorResponseInterceptor())
+ .build();
+
+ Request request = new Request.Builder()
+ .url(server.url("/greeting"))
+ .header("User-Agent", "A Baeldung Reader")
+ .build();
+
+ try (Response response = client.newCall(request).execute()) {
+ assertEquals("Response code should be: ", 500, response.code());
+ assertEquals("Body should be: ", "{\"status\":500,\"detail\":\"The response from the server was not OK\"}",
+ response.body().string());
+ }
+ }
+
+ private HttpLoggingInterceptor getHttpLogger() {
+ HttpLoggingInterceptor logger = new HttpLoggingInterceptor();
+ logger.setLevel(Level.HEADERS);
+ return logger;
+ }
+
+}
diff --git a/persistence-modules/spring-data-jpa-annotations/README.md b/persistence-modules/spring-data-jpa-annotations/README.md
index 1ee579cf6c..3892e75733 100644
--- a/persistence-modules/spring-data-jpa-annotations/README.md
+++ b/persistence-modules/spring-data-jpa-annotations/README.md
@@ -6,7 +6,6 @@ This module contains articles about annotations used in Spring Data JPA
- [DDD Aggregates and @DomainEvents](https://www.baeldung.com/spring-data-ddd)
- [JPA @Embedded And @Embeddable](https://www.baeldung.com/jpa-embedded-embeddable)
-- [Spring Data JPA @Modifying Annotation](https://www.baeldung.com/spring-data-jpa-modifying-annotation)
- [Spring JPA @Embedded and @EmbeddedId](https://www.baeldung.com/spring-jpa-embedded-method-parameters)
- [Programmatic Transaction Management in Spring](https://www.baeldung.com/spring-programmatic-transaction-management)
- [JPA Entity Lifecycle Events](https://www.baeldung.com/jpa-entity-lifecycle-events)
diff --git a/persistence-modules/spring-data-jpa-enterprise/README.md b/persistence-modules/spring-data-jpa-enterprise/README.md
index 81398b1f00..42fbecc880 100644
--- a/persistence-modules/spring-data-jpa-enterprise/README.md
+++ b/persistence-modules/spring-data-jpa-enterprise/README.md
@@ -12,6 +12,7 @@ This module contains articles about Spring Data JPA used in enterprise applicati
- [Working with Lazy Element Collections in JPA](https://www.baeldung.com/java-jpa-lazy-collections)
- [Custom Naming Convention with Spring Data JPA](https://www.baeldung.com/spring-data-jpa-custom-naming)
- [Partial Data Update with Spring Data](https://www.baeldung.com/spring-data-partial-update)
+- [Spring Data JPA @Modifying Annotation](https://www.baeldung.com/spring-data-jpa-modifying-annotation)
### Eclipse Config
After importing the project into Eclipse, you may see the following error:
diff --git a/persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/boot/daos/user/UserRepository.java b/persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/boot/daos/user/UserRepository.java
index 53f692ff28..cfa99414c6 100644
--- a/persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/boot/daos/user/UserRepository.java
+++ b/persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/boot/daos/user/UserRepository.java
@@ -93,6 +93,9 @@ public interface UserRepository extends JpaRepository , UserRepos
@Query("delete User u where u.active = false")
int deleteDeactivatedUsers();
+ @Query("delete User u where u.active = false")
+ int deleteDeactivatedUsersWithNoModifyingAnnotation();
+
@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query(value = "alter table USERS add column deleted int(1) not null default 0", nativeQuery = true)
void addDeletedColumn();
diff --git a/persistence-modules/spring-data-jpa-enterprise/src/test/java/com/baeldung/boot/daos/UserRepositoryCommon.java b/persistence-modules/spring-data-jpa-enterprise/src/test/java/com/baeldung/boot/daos/UserRepositoryCommon.java
index b2581b8034..e8841d921c 100644
--- a/persistence-modules/spring-data-jpa-enterprise/src/test/java/com/baeldung/boot/daos/UserRepositoryCommon.java
+++ b/persistence-modules/spring-data-jpa-enterprise/src/test/java/com/baeldung/boot/daos/UserRepositoryCommon.java
@@ -3,6 +3,7 @@ package com.baeldung.boot.daos;
import org.junit.After;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
@@ -21,6 +22,7 @@ import java.util.function.Predicate;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.*;
public class UserRepositoryCommon {
@@ -520,6 +522,22 @@ public class UserRepositoryCommon {
assertEquals(1, deletedUsersCount);
}
+ @Test
+ @Transactional
+ public void givenTwoUsers_whenDeleteDeactivatedUsersWithNoModifyingAnnotation_ThenException() {
+ User usr01 = new User("usr01", LocalDate.of(2018, 1, 1), "usr01@baeldung.com", 1);
+ usr01.setLastLoginDate(LocalDate.now());
+ User usr02 = new User("usr02", LocalDate.of(2018, 6, 1), "usr02@baeldung.com", 0);
+ usr02.setLastLoginDate(LocalDate.of(2018, 7, 20));
+ usr02.setActive(false);
+
+ userRepository.save(usr01);
+ userRepository.save(usr02);
+
+ assertThatThrownBy(() -> userRepository.deleteDeactivatedUsersWithNoModifyingAnnotation())
+ .isInstanceOf(InvalidDataAccessApiUsageException.class);
+ }
+
@Test
@Transactional
public void givenTwoUsers_whenAddDeletedColumn_ThenUsersHaveDeletedColumn() {
diff --git a/pom.xml b/pom.xml
index ef1e33cfe8..52ebabb727 100644
--- a/pom.xml
+++ b/pom.xml
@@ -531,7 +531,7 @@
protobuffer
quarkus
- quarkus-extension
+
rabbitmq
@@ -992,7 +992,7 @@
protobuffer
quarkus
- quarkus-extension
+
rabbitmq
diff --git a/spring-boot-modules/spring-boot-mvc-jersey/README.md b/spring-boot-modules/spring-boot-mvc-jersey/README.md
index 07f9e78ea6..192658c4a5 100644
--- a/spring-boot-modules/spring-boot-mvc-jersey/README.md
+++ b/spring-boot-modules/spring-boot-mvc-jersey/README.md
@@ -5,4 +5,4 @@ This module contains articles about Spring Boot: JAX-RS vs Spring
### Relevant Articles:
-- [REST API: JAX-RS vs Spring](https://www.baeldung.com/TBD)
+- [REST API: JAX-RS vs Spring](https://www.baeldung.com/rest-api-jax-rs-vs-spring)
diff --git a/spring-cloud/spring-cloud-docker/docker-message-server/src/main/resources/application.properties b/spring-cloud/spring-cloud-docker/docker-message-server/src/main/resources/application.properties
index 7e41f5877f..c4e658f9e2 100644
--- a/spring-cloud/spring-cloud-docker/docker-message-server/src/main/resources/application.properties
+++ b/spring-cloud/spring-cloud-docker/docker-message-server/src/main/resources/application.properties
@@ -1,3 +1 @@
server.port=8888
-spring.security.user.name=root
-spring.security.user.password=docker!
diff --git a/spring-cloud/spring-cloud-docker/docker-product-server/src/main/resources/application.properties b/spring-cloud/spring-cloud-docker/docker-product-server/src/main/resources/application.properties
index f62c8d0bab..2c5fdc3545 100644
--- a/spring-cloud/spring-cloud-docker/docker-product-server/src/main/resources/application.properties
+++ b/spring-cloud/spring-cloud-docker/docker-product-server/src/main/resources/application.properties
@@ -1,3 +1 @@
server.port=9999
-spring.security.user.name=root
-spring.security.user.password=docker!
diff --git a/spring-security-modules/pom.xml b/spring-security-modules/pom.xml
index 99dea4bc67..096ffb9c3f 100644
--- a/spring-security-modules/pom.xml
+++ b/spring-security-modules/pom.xml
@@ -35,6 +35,7 @@
spring-security-legacy-oidc
spring-security-oidc
spring-security-okta
+ spring-security-saml
spring-security-web-react
spring-security-web-rest
spring-security-web-rest-basic-auth
diff --git a/spring-security-modules/spring-security-saml/pom.xml b/spring-security-modules/spring-security-saml/pom.xml
new file mode 100644
index 0000000000..561582045a
--- /dev/null
+++ b/spring-security-modules/spring-security-saml/pom.xml
@@ -0,0 +1,73 @@
+
+
+ 4.0.0
+ spring-security-saml
+ 1.0-SNAPSHOT
+ spring-security-saml
+ war
+
+
+ com.baeldung
+ parent-boot-2
+ 0.0.1-SNAPSHOT
+ ../../parent-boot-2
+
+
+
+ Shibboleth
+ Shibboleth
+ https://build.shibboleth.net/nexus/content/repositories/releases/
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+
+
+ org.springframework.security.extensions
+ spring-security-saml2-core
+ ${saml2-core.spring.version}
+
+
+
+
+ spring-security-saml
+
+
+ src/main/resources
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ true
+
+
+
+
+ repackage
+
+
+
+
+
+
+
+
+ 1.0.10.RELEASE
+
+
diff --git a/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/Application.java b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/Application.java
new file mode 100644
index 0000000000..39eaa46424
--- /dev/null
+++ b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/Application.java
@@ -0,0 +1,11 @@
+package com.baeldung.saml;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Application {
+ public static void main(String... args) {
+ SpringApplication.run(Application.class, args);
+ }
+}
diff --git a/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/authentication/CustomSAMLAuthenticationProvider.java b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/authentication/CustomSAMLAuthenticationProvider.java
new file mode 100644
index 0000000000..b35a72763d
--- /dev/null
+++ b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/authentication/CustomSAMLAuthenticationProvider.java
@@ -0,0 +1,28 @@
+package com.baeldung.saml.authentication;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.providers.ExpiringUsernameAuthenticationToken;
+import org.springframework.security.saml.SAMLAuthenticationProvider;
+import org.springframework.security.saml.SAMLCredential;
+
+public class CustomSAMLAuthenticationProvider extends SAMLAuthenticationProvider {
+
+ @Override
+ public Collection extends GrantedAuthority> getEntitlements(SAMLCredential credential, Object userDetail) {
+
+ if(userDetail instanceof ExpiringUsernameAuthenticationToken) {
+ List authorities = new ArrayList();
+ authorities.addAll(((ExpiringUsernameAuthenticationToken) userDetail).getAuthorities());
+ return authorities;
+
+ } else {
+ return Collections.emptyList();
+ }
+ }
+
+}
diff --git a/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/config/SamlSecurityConfig.java b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/config/SamlSecurityConfig.java
new file mode 100644
index 0000000000..378db478cf
--- /dev/null
+++ b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/config/SamlSecurityConfig.java
@@ -0,0 +1,226 @@
+package com.baeldung.saml.config;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.opensaml.saml2.metadata.provider.FilesystemMetadataProvider;
+import org.opensaml.saml2.metadata.provider.MetadataProvider;
+import org.opensaml.saml2.metadata.provider.MetadataProviderException;
+import org.opensaml.util.resource.ResourceException;
+import org.opensaml.xml.parse.StaticBasicParserPool;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.DefaultResourceLoader;
+import org.springframework.core.io.Resource;
+import org.springframework.security.saml.*;
+import org.springframework.security.saml.context.SAMLContextProviderImpl;
+import org.springframework.security.saml.key.JKSKeyManager;
+import org.springframework.security.saml.key.KeyManager;
+import org.springframework.security.saml.log.SAMLDefaultLogger;
+import org.springframework.security.saml.metadata.CachingMetadataManager;
+import org.springframework.security.saml.metadata.ExtendedMetadata;
+import org.springframework.security.saml.metadata.ExtendedMetadataDelegate;
+import org.springframework.security.saml.processor.*;
+import org.springframework.security.saml.util.VelocityFactory;
+import org.springframework.security.saml.websso.*;
+import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
+import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
+import org.springframework.security.web.authentication.logout.LogoutHandler;
+import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
+import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
+
+import com.baeldung.saml.authentication.CustomSAMLAuthenticationProvider;
+
+@Configuration
+public class SamlSecurityConfig {
+
+ @Value("${saml.keystore.location}")
+ private String samlKeystoreLocation;
+
+ @Value("${saml.keystore.password}")
+ private String samlKeystorePassword;
+
+ @Value("${saml.keystore.alias}")
+ private String samlKeystoreAlias;
+
+ @Value("${saml.idp}")
+ private String defaultIdp;
+
+ @Bean(initMethod = "initialize")
+ public StaticBasicParserPool parserPool() {
+ return new StaticBasicParserPool();
+ }
+
+ @Bean
+ public SAMLAuthenticationProvider samlAuthenticationProvider() {
+ return new CustomSAMLAuthenticationProvider();
+ }
+
+ @Bean
+ public SAMLContextProviderImpl contextProvider() {
+ return new SAMLContextProviderImpl();
+ }
+
+ @Bean
+ public static SAMLBootstrap samlBootstrap() {
+ return new SAMLBootstrap();
+ }
+
+ @Bean
+ public SAMLDefaultLogger samlLogger() {
+ return new SAMLDefaultLogger();
+ }
+
+ @Bean
+ public WebSSOProfileConsumer webSSOprofileConsumer() {
+ return new WebSSOProfileConsumerImpl();
+ }
+
+ @Bean
+ @Qualifier("hokWebSSOprofileConsumer")
+ public WebSSOProfileConsumerHoKImpl hokWebSSOProfileConsumer() {
+ return new WebSSOProfileConsumerHoKImpl();
+ }
+
+ @Bean
+ public WebSSOProfile webSSOprofile() {
+ return new WebSSOProfileImpl();
+ }
+
+ @Bean
+ public WebSSOProfileConsumerHoKImpl hokWebSSOProfile() {
+ return new WebSSOProfileConsumerHoKImpl();
+ }
+
+ @Bean
+ public WebSSOProfileECPImpl ecpProfile() {
+ return new WebSSOProfileECPImpl();
+ }
+
+ @Bean
+ public SingleLogoutProfile logoutProfile() {
+ return new SingleLogoutProfileImpl();
+ }
+
+ @Bean
+ public KeyManager keyManager() {
+ DefaultResourceLoader loader = new DefaultResourceLoader();
+ Resource storeFile = loader.getResource(samlKeystoreLocation);
+ Map passwords = new HashMap<>();
+ passwords.put(samlKeystoreAlias, samlKeystorePassword);
+ return new JKSKeyManager(storeFile, samlKeystorePassword, passwords, samlKeystoreAlias);
+ }
+
+ @Bean
+ public WebSSOProfileOptions defaultWebSSOProfileOptions() {
+ WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions();
+ webSSOProfileOptions.setIncludeScoping(false);
+ return webSSOProfileOptions;
+ }
+
+ @Bean
+ public SAMLEntryPoint samlEntryPoint() {
+ SAMLEntryPoint samlEntryPoint = new SAMLEntryPoint();
+ samlEntryPoint.setDefaultProfileOptions(defaultWebSSOProfileOptions());
+ return samlEntryPoint;
+ }
+
+ @Bean
+ public ExtendedMetadata extendedMetadata() {
+ ExtendedMetadata extendedMetadata = new ExtendedMetadata();
+ extendedMetadata.setIdpDiscoveryEnabled(false);
+ extendedMetadata.setSignMetadata(false);
+ return extendedMetadata;
+ }
+
+ @Bean
+ @Qualifier("okta")
+ public ExtendedMetadataDelegate oktaExtendedMetadataProvider() throws MetadataProviderException {
+ File metadata = null;
+ try {
+ metadata = new File("./src/main/resources/saml/metadata/sso.xml");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ FilesystemMetadataProvider provider = new FilesystemMetadataProvider(metadata);
+ provider.setParserPool(parserPool());
+ return new ExtendedMetadataDelegate(provider, extendedMetadata());
+ }
+
+ @Bean
+ @Qualifier("metadata")
+ public CachingMetadataManager metadata() throws MetadataProviderException, ResourceException {
+ List providers = new ArrayList<>();
+ providers.add(oktaExtendedMetadataProvider());
+ CachingMetadataManager metadataManager = new CachingMetadataManager(providers);
+ metadataManager.setDefaultIDP(defaultIdp);
+ return metadataManager;
+ }
+
+ @Bean
+ @Qualifier("saml")
+ public SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler() {
+ SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler = new SavedRequestAwareAuthenticationSuccessHandler();
+ successRedirectHandler.setDefaultTargetUrl("/home");
+ return successRedirectHandler;
+ }
+
+ @Bean
+ @Qualifier("saml")
+ public SimpleUrlAuthenticationFailureHandler authenticationFailureHandler() {
+ SimpleUrlAuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
+ failureHandler.setUseForward(true);
+ failureHandler.setDefaultFailureUrl("/error");
+ return failureHandler;
+ }
+
+ @Bean
+ public SimpleUrlLogoutSuccessHandler successLogoutHandler() {
+ SimpleUrlLogoutSuccessHandler successLogoutHandler = new SimpleUrlLogoutSuccessHandler();
+ successLogoutHandler.setDefaultTargetUrl("/");
+ return successLogoutHandler;
+ }
+
+ @Bean
+ public SecurityContextLogoutHandler logoutHandler() {
+ SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
+ logoutHandler.setInvalidateHttpSession(true);
+ logoutHandler.setClearAuthentication(true);
+ return logoutHandler;
+ }
+
+ @Bean
+ public SAMLLogoutProcessingFilter samlLogoutProcessingFilter() {
+ return new SAMLLogoutProcessingFilter(successLogoutHandler(), logoutHandler());
+ }
+
+ @Bean
+ public SAMLLogoutFilter samlLogoutFilter() {
+ return new SAMLLogoutFilter(successLogoutHandler(),
+ new LogoutHandler[] { logoutHandler() },
+ new LogoutHandler[] { logoutHandler() });
+ }
+
+ @Bean
+ public HTTPPostBinding httpPostBinding() {
+ return new HTTPPostBinding(parserPool(), VelocityFactory.getEngine());
+ }
+
+ @Bean
+ public HTTPRedirectDeflateBinding httpRedirectDeflateBinding() {
+ return new HTTPRedirectDeflateBinding(parserPool());
+ }
+
+ @Bean
+ public SAMLProcessorImpl processor() {
+ ArrayList bindings = new ArrayList<>();
+ bindings.add(httpRedirectDeflateBinding());
+ bindings.add(httpPostBinding());
+ return new SAMLProcessorImpl(bindings);
+ }
+}
diff --git a/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/config/WebSecurityConfig.java b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/config/WebSecurityConfig.java
new file mode 100644
index 0000000000..297c391823
--- /dev/null
+++ b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/config/WebSecurityConfig.java
@@ -0,0 +1,152 @@
+package com.baeldung.saml.config;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.saml.*;
+import org.springframework.security.saml.key.KeyManager;
+import org.springframework.security.saml.metadata.*;
+import org.springframework.security.web.DefaultSecurityFilterChain;
+import org.springframework.security.web.FilterChainProxy;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.access.channel.ChannelProcessingFilter;
+import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
+import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
+import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
+import org.springframework.security.web.csrf.CsrfFilter;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+
+@Configuration
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(securedEnabled = true)
+public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
+
+ @Value("${saml.sp}")
+ private String samlAudience;
+
+ @Autowired
+ @Qualifier("saml")
+ private SavedRequestAwareAuthenticationSuccessHandler samlAuthSuccessHandler;
+
+ @Autowired
+ @Qualifier("saml")
+ private SimpleUrlAuthenticationFailureHandler samlAuthFailureHandler;
+
+ @Autowired
+ private SAMLEntryPoint samlEntryPoint;
+
+ @Autowired
+ private SAMLLogoutFilter samlLogoutFilter;
+
+ @Autowired
+ private SAMLLogoutProcessingFilter samlLogoutProcessingFilter;
+
+ @Bean
+ public SAMLDiscovery samlDiscovery() {
+ SAMLDiscovery idpDiscovery = new SAMLDiscovery();
+ return idpDiscovery;
+ }
+
+ @Autowired
+ private SAMLAuthenticationProvider samlAuthenticationProvider;
+
+ @Autowired
+ private ExtendedMetadata extendedMetadata;
+
+ @Autowired
+ private KeyManager keyManager;
+
+ public MetadataGenerator metadataGenerator() {
+ MetadataGenerator metadataGenerator = new MetadataGenerator();
+ metadataGenerator.setEntityId(samlAudience);
+ metadataGenerator.setExtendedMetadata(extendedMetadata);
+ metadataGenerator.setIncludeDiscoveryExtension(false);
+ metadataGenerator.setKeyManager(keyManager);
+ return metadataGenerator;
+ }
+
+ @Bean
+ public SAMLProcessingFilter samlWebSSOProcessingFilter() throws Exception {
+ SAMLProcessingFilter samlWebSSOProcessingFilter = new SAMLProcessingFilter();
+ samlWebSSOProcessingFilter.setAuthenticationManager(authenticationManager());
+ samlWebSSOProcessingFilter.setAuthenticationSuccessHandler(samlAuthSuccessHandler);
+ samlWebSSOProcessingFilter.setAuthenticationFailureHandler(samlAuthFailureHandler);
+ return samlWebSSOProcessingFilter;
+ }
+
+ @Bean
+ public FilterChainProxy samlFilter() throws Exception {
+ List chains = new ArrayList<>();
+ chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSO/**"),
+ samlWebSSOProcessingFilter()));
+ chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/discovery/**"),
+ samlDiscovery()));
+ chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/login/**"),
+ samlEntryPoint));
+ chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/logout/**"),
+ samlLogoutFilter));
+ chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SingleLogout/**"),
+ samlLogoutProcessingFilter));
+ return new FilterChainProxy(chains);
+ }
+
+ @Bean
+ @Override
+ public AuthenticationManager authenticationManagerBean() throws Exception {
+ return super.authenticationManagerBean();
+ }
+
+ @Bean
+ public MetadataGeneratorFilter metadataGeneratorFilter() {
+ return new MetadataGeneratorFilter(metadataGenerator());
+ }
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http
+ .csrf()
+ .disable();
+
+ http
+ .httpBasic()
+ .authenticationEntryPoint(samlEntryPoint);
+
+ http
+ .addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class)
+ .addFilterAfter(samlFilter(), BasicAuthenticationFilter.class)
+ .addFilterBefore(samlFilter(), CsrfFilter.class);
+
+ http
+ .authorizeRequests()
+ .antMatchers("/").permitAll()
+ .anyRequest().authenticated();
+
+ http
+ .logout()
+ .addLogoutHandler((request, response, authentication) -> {
+ try {
+ response.sendRedirect("/saml/logout");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ });
+ }
+
+ @Override
+ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+ auth.authenticationProvider(samlAuthenticationProvider);
+ }
+
+}
diff --git a/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/controller/HomeController.java b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/controller/HomeController.java
new file mode 100644
index 0000000000..e77933b8f3
--- /dev/null
+++ b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/controller/HomeController.java
@@ -0,0 +1,35 @@
+package com.baeldung.saml.controller;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+@Controller
+public class HomeController {
+
+ @RequestMapping("/")
+ public String index() {
+ return "index";
+ }
+
+ @GetMapping(value = "/auth")
+ public String handleSamlAuth() {
+ Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+ if (auth != null) {
+ return "redirect:/home";
+ } else {
+ return "/";
+ }
+ }
+
+ @RequestMapping("/home")
+ public String home(Model model) {
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+ model.addAttribute("username", authentication.getPrincipal());
+ return "home";
+ }
+
+}
diff --git a/spring-security-modules/spring-security-saml/src/main/resources/application.properties b/spring-security-modules/spring-security-saml/src/main/resources/application.properties
new file mode 100644
index 0000000000..f9d6a5df3c
--- /dev/null
+++ b/spring-security-modules/spring-security-saml/src/main/resources/application.properties
@@ -0,0 +1,6 @@
+saml.keystore.location=classpath:/saml/samlKeystore.jks
+saml.keystore.password=
+saml.keystore.alias=
+
+saml.idp=
+saml.sp=http://localhost:8080/saml/metadata
\ No newline at end of file
diff --git a/spring-security-modules/spring-security-saml/src/main/resources/saml/metadata/sso.xml b/spring-security-modules/spring-security-saml/src/main/resources/saml/metadata/sso.xml
new file mode 100644
index 0000000000..2d3258de12
--- /dev/null
+++ b/spring-security-modules/spring-security-saml/src/main/resources/saml/metadata/sso.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+ MIIDpDCCAoygAwIBAgIGAXGiSQ7ZMA0GCSqGSIb3DQEBCwUAMIGSMQswCQYDVQQGEwJVUzETMBEG
+ A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
+ MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi05MjY2NjYxHDAaBgkqhkiG9w0BCQEW
+ DWluZm9Ab2t0YS5jb20wHhcNMjAwNDIyMTQyNjA5WhcNMzAwNDIyMTQyNzA5WjCBkjELMAkGA1UE
+ BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
+ BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtOTI2NjY2MRwwGgYJ
+ KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+ g1rQYYqeVx2gl/UUnLJzp5hrm06VOILJB9hIUmNqXgWV3UjzDq/zX0KW8MENjsO7+S8a+LLnYRkb
+ N5egH9FSt8AHtB1pmfXDtpUQmWe9yJbNxbCISoc6XzCmaRw3HRv9pK5SciIutciz9lvFaHMWAWtP
+ MmQSKdhMet52tuf6sTy4ODeXjyMnD9q5QOKww1SJ678wjHbGRRhNvCxvTSAH33sa4oNCf2RvP9hp
+ NiJRcYW9yLZXmZArPQOuAx5PIXfHhK2e4ac39YO4fgO7gwU5TZ+vL7o6iEmd9tk44PrND0ZV5yzZ
+ +Y33Hiun3fIiZu/nZZGUjm4k4exl8JJpwrVTHQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBcfHcL
+ 2DjTjZGoANF4dPpGXTYdVnL/XzGiLS+3LR/HDrEz/EqsHouF40RnzdZ7Ax7RReKBYCUUqHpSE+LU
+ ductz2ANguzyseGEn72I4Ym4ytQWnFyTXeW+xI9CoCLGfOUhT1hlKjsu/qNM8qwKFPWkzQp7mDN8
+ S9MGhsnbiyeD/lceAEKw16Os73/sX2j7F+43WVCYRDCRB8pRIPfcqYLXUIUSstQlwEvCF7HyeO4+
+ jxKHA1tp9Cpmj7/VD9TE3fyvrbVmfjTbKjF7/0wYQNfbHDDko0ratDMAizG5/d3i9wk9KbGCHSxT
+ ph5nl1pdjKgAYPK0iNDnGCZbGKzXOrqV
+
+
+
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-security-modules/spring-security-saml/src/main/resources/saml/samlKeystore.jks b/spring-security-modules/spring-security-saml/src/main/resources/saml/samlKeystore.jks
new file mode 100644
index 0000000000..7f3a5850d9
Binary files /dev/null and b/spring-security-modules/spring-security-saml/src/main/resources/saml/samlKeystore.jks differ
diff --git a/spring-security-modules/spring-security-saml/src/main/resources/templates/error.html b/spring-security-modules/spring-security-saml/src/main/resources/templates/error.html
new file mode 100644
index 0000000000..7223ee43fd
--- /dev/null
+++ b/spring-security-modules/spring-security-saml/src/main/resources/templates/error.html
@@ -0,0 +1,13 @@
+
+
+
+ Something went wrong
+
+
+
+ An error occurred
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-security-modules/spring-security-saml/src/main/resources/templates/home.html b/spring-security-modules/spring-security-saml/src/main/resources/templates/home.html
new file mode 100644
index 0000000000..c66e92c1f0
--- /dev/null
+++ b/spring-security-modules/spring-security-saml/src/main/resources/templates/home.html
@@ -0,0 +1,13 @@
+
+
+
+Baeldung Spring Security SAML: Home
+
+
+ Welcome!
You are successfully logged in!
+ You are logged as null.
+
+ Logout
+
+
+
\ No newline at end of file
diff --git a/spring-security-modules/spring-security-saml/src/main/resources/templates/index.html b/spring-security-modules/spring-security-saml/src/main/resources/templates/index.html
new file mode 100644
index 0000000000..7999c2fded
--- /dev/null
+++ b/spring-security-modules/spring-security-saml/src/main/resources/templates/index.html
@@ -0,0 +1,10 @@
+
+
+
+Baeldung Spring Security SAML
+
+
+ Welcome to Baeldung Spring Security SAML
+ Login
+
+
\ No newline at end of file
diff --git a/testing-modules/zerocode/README.md b/testing-modules/zerocode/README.md
new file mode 100644
index 0000000000..a0a844c63d
--- /dev/null
+++ b/testing-modules/zerocode/README.md
@@ -0,0 +1,3 @@
+### Relevant Articles:
+
+- [Introduction to ZeroCode](https://www.baeldung.com/zerocode-intro)