diff --git a/apache-httpclient-2/src/test/java/com/baeldung/httpclient/cookies/HttpClientGettingCookieValueUnitTest.java b/apache-httpclient-2/src/test/java/com/baeldung/httpclient/cookies/HttpClientGettingCookieValueUnitTest.java index 404acb3098..265fa39816 100644 --- a/apache-httpclient-2/src/test/java/com/baeldung/httpclient/cookies/HttpClientGettingCookieValueUnitTest.java +++ b/apache-httpclient-2/src/test/java/com/baeldung/httpclient/cookies/HttpClientGettingCookieValueUnitTest.java @@ -1,46 +1,50 @@ package com.baeldung.httpclient.cookies; -import org.apache.http.client.CookieStore; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.protocol.HttpClientContext; -import org.apache.http.cookie.ClientCookie; -import org.apache.http.cookie.Cookie; -import org.apache.http.impl.client.BasicCookieStore; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.impl.cookie.BasicClientCookie; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.cookie.BasicCookieStore; +import org.apache.hc.client5.http.cookie.Cookie; +import org.apache.hc.client5.http.cookie.CookieStore; + +import org.apache.hc.client5.http.impl.classic.BasicHttpClientResponseHandler; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; + +import org.apache.hc.client5.http.impl.cookie.BasicClientCookie; +import org.apache.hc.client5.http.protocol.HttpClientContext; + import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; -import static org.junit.Assert.assertEquals; - - -public class HttpClientGettingCookieValueUnitTest { +class HttpClientGettingCookieValueUnitTest { private static Logger log = LoggerFactory.getLogger(HttpClientGettingCookieValueUnitTest.class); private static final String SAMPLE_URL = "http://www.baeldung.com/"; @Test - public final void whenSettingCustomCookieOnTheRequest_thenGettingTheSameCookieFromTheResponse() throws IOException { + void whenSettingCustomCookieOnTheRequest_thenGettingTheSameCookieFromTheResponse() throws IOException { + HttpClientContext context = HttpClientContext.create(); context.setAttribute(HttpClientContext.COOKIE_STORE, createCustomCookieStore()); - try (CloseableHttpClient httpClient = HttpClients.createDefault()) { - try (CloseableHttpResponse response = httpClient.execute(new HttpGet(SAMPLE_URL), context)) { - CookieStore cookieStore = context.getCookieStore(); - Cookie customCookie = cookieStore.getCookies() - .stream() - .peek(cookie -> log.info("cookie name:{}", cookie.getName())) - .filter(cookie -> "custom_cookie".equals(cookie.getName())) - .findFirst() - .orElseThrow(IllegalStateException::new); + final HttpGet request = new HttpGet(SAMPLE_URL); - assertEquals("test_value", customCookie.getValue()); - } + try (CloseableHttpClient client = HttpClientBuilder.create() + .build()) { + client.execute(request, context, new BasicHttpClientResponseHandler()); + CookieStore cookieStore = context.getCookieStore(); + Cookie customCookie = cookieStore.getCookies() + .stream() + .peek(cookie -> log.info("cookie name:{}", cookie.getName())) + .filter(cookie -> "custom_cookie".equals(cookie.getName())) + .findFirst() + .orElseThrow(IllegalStateException::new); + + assertEquals("test_value", customCookie.getValue()); } } @@ -48,7 +52,7 @@ public class HttpClientGettingCookieValueUnitTest { BasicCookieStore cookieStore = new BasicCookieStore(); BasicClientCookie cookie = new BasicClientCookie("custom_cookie", "test_value"); cookie.setDomain("baeldung.com"); - cookie.setAttribute(ClientCookie.DOMAIN_ATTR, "true"); + cookie.setAttribute("domain", "true"); cookie.setPath("/"); cookieStore.addCookie(cookie); return cookieStore; diff --git a/core-java-modules/core-java-19/README.md b/core-java-modules/core-java-19/README.md new file mode 100644 index 0000000000..9663296da0 --- /dev/null +++ b/core-java-modules/core-java-19/README.md @@ -0,0 +1,2 @@ +## Relevant Articles +- [Record Patterns in Java 19](https://www.baeldung.com/java-19-record-patterns) diff --git a/core-java-modules/core-java-8/src/main/java/com/baeldung/monad/MonadSamples.java b/core-java-modules/core-java-8/src/main/java/com/baeldung/monad/MonadSamples.java new file mode 100644 index 0000000000..87b7fe697e --- /dev/null +++ b/core-java-modules/core-java-8/src/main/java/com/baeldung/monad/MonadSamples.java @@ -0,0 +1,82 @@ +package com.baeldung.monad; + +import java.util.Optional; +import java.util.function.Function; + +class MonadBaseExample { + + public double multiplyBy2(double n) { + return n * 2; + } + + public double divideBy2(double n) { + return n / 2; + } + + public double add3(double n) { + return n + 3; + } + + public double subtract1(double n) { + return n - 1; + } + +} + +class MonadSample1 extends MonadBaseExample { + + public double apply(double n) { + return subtract1(add3(divideBy2(multiplyBy2(multiplyBy2(2))))); + } +} + +class MonadSample2 extends MonadBaseExample { + public double apply(double n) { + double n1 = multiplyBy2(n); + double n2 = multiplyBy2(n1); + double n3 = divideBy2(n2); + double n4 = add3(n3); + return subtract1(n4); + } +} + +class MonadSample3 extends MonadBaseExample { + + public double apply(double n) { + return Optional.of(n) + .flatMap(value -> Optional.of(multiplyBy2(value))) + .flatMap(value -> Optional.of(multiplyBy2(value))) + .flatMap(value -> Optional.of(divideBy2(value))) + .flatMap(value -> Optional.of(add3(value))) + .flatMap(value -> Optional.of(subtract1(value))) + .get(); + } + +} + + class MonadSample4 extends MonadBaseExample { + public boolean leftIdentity() { + Function> mapping = value -> Optional.of(value + 1); + return Optional.of(3).flatMap(mapping).equals(mapping.apply(3)); + } + + public boolean rightIdentity() { + return Optional.of(3).flatMap(Optional::of).equals(Optional.of(3)); + } + + public boolean associativity() { + Function> mapping = value -> Optional.of(value + 1); + Optional leftSide = Optional.of(3).flatMap(mapping).flatMap(Optional::of); + Optional rightSide = Optional.of(3).flatMap(v -> mapping.apply(v).flatMap(Optional::of)); + return leftSide.equals(rightSide); + } + + } + +class MonadSample5 extends MonadBaseExample { + public boolean fail() { + Function> mapping = value -> Optional.of(value == null ? -1 : value + 1); + return Optional.ofNullable((Integer) null).flatMap(mapping).equals(mapping.apply(null)); + } +} + diff --git a/core-java-modules/core-java-8/src/test/java/com/baeldung/monad/MonadSampleUnitTest.java b/core-java-modules/core-java-8/src/test/java/com/baeldung/monad/MonadSampleUnitTest.java new file mode 100644 index 0000000000..c851f5f750 --- /dev/null +++ b/core-java-modules/core-java-8/src/test/java/com/baeldung/monad/MonadSampleUnitTest.java @@ -0,0 +1,39 @@ +package com.baeldung.monad; + +import org.junit.Assert; +import org.junit.Test; + +public class MonadSampleUnitTest { + + @Test + public void whenNotUsingMonad_shouldBeOk() { + MonadSample1 test = new MonadSample1(); + Assert.assertEquals(6.0, test.apply(2), 0.000); + } + + @Test + public void whenNotUsingMonadButUsingTempVars_shouldBeOk() { + MonadSample2 test = new MonadSample2(); + Assert.assertEquals(6.0, test.apply(2), 0.000); + } + + @Test + public void whenUsingMonad_shouldBeOk() { + MonadSample3 test = new MonadSample3(); + Assert.assertEquals(6.0, test.apply(2), 0.000); + } + + @Test + public void whenTestingMonadProperties_shouldBeOk() { + MonadSample4 test = new MonadSample4(); + Assert.assertEquals(true, test.leftIdentity()); + Assert.assertEquals(true, test.rightIdentity()); + Assert.assertEquals(true, test.associativity()); + } + + @Test + public void whenBreakingMonadProperties_shouldBeFalse() { + MonadSample5 test = new MonadSample5(); + Assert.assertEquals(false, test.fail()); + } +} diff --git a/core-java-modules/core-java-arrays-convert/README.md b/core-java-modules/core-java-arrays-convert/README.md index b28b97cb09..4365fd12f9 100644 --- a/core-java-modules/core-java-arrays-convert/README.md +++ b/core-java-modules/core-java-arrays-convert/README.md @@ -6,3 +6,4 @@ This module contains articles about arrays conversion in Java - [Convert a Float to a Byte Array in Java](https://www.baeldung.com/java-convert-float-to-byte-array) - [Converting Between Stream and Array in Java](https://www.baeldung.com/java-stream-to-array) - [Convert a Byte Array to a Numeric Representation in Java](https://www.baeldung.com/java-byte-array-to-number) +- [Converting a String Array Into an int Array in Java](https://www.baeldung.com/java-convert-string-array-to-int-array) diff --git a/core-java-modules/core-java-collections-2/README.md b/core-java-modules/core-java-collections-2/README.md index d482ed7773..644cc93be7 100644 --- a/core-java-modules/core-java-collections-2/README.md +++ b/core-java-modules/core-java-collections-2/README.md @@ -13,3 +13,4 @@ - [Getting the Size of an Iterable in Java](https://www.baeldung.com/java-iterable-size) - [Java Null-Safe Streams from Collections](https://www.baeldung.com/java-null-safe-streams-from-collections) - [Differences Between Iterator and Iterable and How to Use Them?](https://www.baeldung.com/java-iterator-vs-iterable) +- More articles: [[<-- prev]](/core-java-modules/core-java-collections-1) [[next -->]](/core-java-modules/core-java-collections-3) \ No newline at end of file diff --git a/core-java-modules/core-java-collections-4/README.md b/core-java-modules/core-java-collections-4/README.md index f3a7d87403..460af21179 100644 --- a/core-java-modules/core-java-collections-4/README.md +++ b/core-java-modules/core-java-collections-4/README.md @@ -7,7 +7,6 @@ - [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) - [Collection.toArray(new T[0]) or .toArray(new T[size])](https://www.baeldung.com/java-collection-toarray-methods) -- [Create an Empty Map in Java](https://www.baeldung.com/java-create-empty-map) - [Sorting Objects in a List by Date](https://www.baeldung.com/java-sort-list-by-date) - [Fixed Size Queue Implementations in Java](https://www.baeldung.com/java-fixed-size-queue) - [Difference Between Java Enumeration and Iterator](https://www.baeldung.com/java-enumeration-vs-iterator) @@ -15,4 +14,4 @@ - [Guide to Java PriorityQueue](https://www.baeldung.com/java-priorityqueue) - [Java Generics PECS – Producer Extends Consumer Super](https://www.baeldung.com/java-generics-pecs) - [Reversing a Stack in Java](https://www.baeldung.com/java-reversing-a-stack) -- [Sorting a HashSet in Java](https://www.baeldung.com/java-sort-hashset) +- More articles: [[<-- prev]](/core-java-modules/core-java-collections-3) \ No newline at end of file diff --git a/core-java-modules/core-java-collections-4/pom.xml b/core-java-modules/core-java-collections-4/pom.xml index 9f1439b850..e88d5a6740 100644 --- a/core-java-modules/core-java-collections-4/pom.xml +++ b/core-java-modules/core-java-collections-4/pom.xml @@ -25,6 +25,12 @@ commons-lang3 ${commons-lang3.version} + + org.junit.platform + junit-platform-runner + ${junit-platform.version} + test + diff --git a/core-java-modules/core-java-collections-4/src/test/java/com/baeldung/collections/comparation/HashMapUnitTest.java b/core-java-modules/core-java-collections-4/src/test/java/com/baeldung/collections/comparation/HashMapUnitTest.java index 3b595472e0..7d4ec8249c 100644 --- a/core-java-modules/core-java-collections-4/src/test/java/com/baeldung/collections/comparation/HashMapUnitTest.java +++ b/core-java-modules/core-java-collections-4/src/test/java/com/baeldung/collections/comparation/HashMapUnitTest.java @@ -2,9 +2,7 @@ package com.baeldung.collections.comparation; import org.junit.jupiter.api.Test; -import java.util.Arrays; import java.util.HashMap; -import java.util.LinkedList; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; diff --git a/core-java-modules/core-java-collections-4/src/test/java/com/baeldung/collections/comparation/LinkedListUnitTest.java b/core-java-modules/core-java-collections-4/src/test/java/com/baeldung/collections/comparation/LinkedListUnitTest.java index aa6b7fa923..00a188737d 100644 --- a/core-java-modules/core-java-collections-4/src/test/java/com/baeldung/collections/comparation/LinkedListUnitTest.java +++ b/core-java-modules/core-java-collections-4/src/test/java/com/baeldung/collections/comparation/LinkedListUnitTest.java @@ -2,10 +2,8 @@ package com.baeldung.collections.comparation; import org.junit.jupiter.api.Test; -import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; -import java.util.List; import static org.assertj.core.api.Assertions.assertThat; diff --git a/core-java-modules/core-java-collections-4/src/test/java/com/baeldung/collections/sorting/EmployeeSortingByDateUnitTest.java b/core-java-modules/core-java-collections-4/src/test/java/com/baeldung/collections/sorting/EmployeeSortingByDateUnitTest.java index 250fb7b62b..de2f3d8ca7 100644 --- a/core-java-modules/core-java-collections-4/src/test/java/com/baeldung/collections/sorting/EmployeeSortingByDateUnitTest.java +++ b/core-java-modules/core-java-collections-4/src/test/java/com/baeldung/collections/sorting/EmployeeSortingByDateUnitTest.java @@ -10,7 +10,7 @@ import java.util.Date; import java.util.List; import org.apache.commons.lang.time.DateUtils; import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class EmployeeSortingByDateUnitTest { diff --git a/core-java-modules/core-java-collections-4/src/test/java/com/baeldung/collections/sorting/multiple/ComparatorsUnitTest.java b/core-java-modules/core-java-collections-4/src/test/java/com/baeldung/collections/sorting/multiple/ComparatorsUnitTest.java index 4608730567..e1297456be 100644 --- a/core-java-modules/core-java-collections-4/src/test/java/com/baeldung/collections/sorting/multiple/ComparatorsUnitTest.java +++ b/core-java-modules/core-java-collections-4/src/test/java/com/baeldung/collections/sorting/multiple/ComparatorsUnitTest.java @@ -5,8 +5,9 @@ import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; -import org.junit.Test; + import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; public class ComparatorsUnitTest { @Test diff --git a/core-java-modules/core-java-collections-4/src/test/java/com/baeldung/stackreversal/StackReversalUnitTest.java b/core-java-modules/core-java-collections-4/src/test/java/com/baeldung/stackreversal/StackReversalUnitTest.java index 7ad0b71028..ccadc4e7d3 100644 --- a/core-java-modules/core-java-collections-4/src/test/java/com/baeldung/stackreversal/StackReversalUnitTest.java +++ b/core-java-modules/core-java-collections-4/src/test/java/com/baeldung/stackreversal/StackReversalUnitTest.java @@ -1,10 +1,12 @@ package com.baeldung.stackreversal; +import static org.junit.jupiter.api.Assertions.assertEquals; + import com.baeldung.collections.sorting.Employee; import com.baeldung.collections.stackreversal.ReverseStackUsingQueue; import com.baeldung.collections.stackreversal.ReverseStackUsingRecursion; -import org.junit.Assert; -import org.junit.Test; + +import org.junit.jupiter.api.Test; import java.util.*; import java.util.stream.Collectors; @@ -15,7 +17,7 @@ public class StackReversalUnitTest { ReverseStackUsingQueue reverseStack = new ReverseStackUsingQueue(); Stack originalStack = generateStackFromGivenList(Arrays.stream(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}).boxed().collect(Collectors.toList()), new Stack()); Stack reverseList = generateStackFromGivenList(Arrays.stream(new int[]{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}).boxed().collect(Collectors.toList()), new Stack()); - Assert.assertEquals(reverseStack.reverseIntegerStack(originalStack), reverseList); + assertEquals(reverseStack.reverseIntegerStack(originalStack), reverseList); } @Test @@ -26,7 +28,7 @@ public class StackReversalUnitTest { Collections.reverse(listOfWordsReversed); Stack originalStack = generateStackFromGivenList(listOfWords, new Stack()); Stack reversedStack = generateStackFromGivenList(listOfWordsReversed, new Stack()); - Assert.assertEquals(stackReversal.reverseStringStack(originalStack), reversedStack); + assertEquals(stackReversal.reverseStringStack(originalStack), reversedStack); } @Test @@ -43,7 +45,7 @@ public class StackReversalUnitTest { Collections.reverse(employeeReversed); Stack originalStack = generateStackFromGivenList(employeeList, new Stack()); Stack reverseStack = generateStackFromGivenList(employeeReversed, new Stack()); - Assert.assertEquals(stackReversal.reverseEmployeeStack(originalStack), reverseStack); + assertEquals(stackReversal.reverseEmployeeStack(originalStack), reverseStack); } @Test @@ -51,7 +53,7 @@ public class StackReversalUnitTest { ReverseStackUsingRecursion reverseStack = new ReverseStackUsingRecursion(); Stack originalStack = generateStackFromGivenList(Arrays.stream(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}).boxed().collect(Collectors.toList()), new Stack()); Stack reversedStack = generateStackFromGivenList(Arrays.stream(new int[]{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}).boxed().collect(Collectors.toList()), new Stack()); - Assert.assertEquals(reverseStack.reverseIntegerStack(originalStack), reversedStack); + assertEquals(reverseStack.reverseIntegerStack(originalStack), reversedStack); } private Stack generateStackFromGivenList(List elements, Stack stack){ diff --git a/core-java-modules/core-java-collections-array-list/pom.xml b/core-java-modules/core-java-collections-array-list/pom.xml index 6b040739e8..e3a115854c 100644 --- a/core-java-modules/core-java-collections-array-list/pom.xml +++ b/core-java-modules/core-java-collections-array-list/pom.xml @@ -5,6 +5,33 @@ 4.0.0 core-java-collections-array-list 0.1.0-SNAPSHOT + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${maven-compiler-plugin.source} + ${maven-compiler-plugin.target} + + + + org.apache.maven.plugins + maven-surefire-plugin + ${surefire.plugin.version} + + + --add-opens java.base/java.util=ALL-UNNAMED + + + + + + + 16 + 16 + 3.0.0-M3 + core-java-collections-array-list jar @@ -20,6 +47,12 @@ commons-collections4 ${commons-collections4.version} + + com.google.guava + guava + 31.1-jre + test + \ No newline at end of file diff --git a/core-java-modules/core-java-collections-array-list/src/main/java/com/baeldung/list/ignorecase/IgnoreCaseSearchUtil.java b/core-java-modules/core-java-collections-array-list/src/main/java/com/baeldung/list/ignorecase/IgnoreCaseSearchUtil.java new file mode 100644 index 0000000000..38532ef031 --- /dev/null +++ b/core-java-modules/core-java-collections-array-list/src/main/java/com/baeldung/list/ignorecase/IgnoreCaseSearchUtil.java @@ -0,0 +1,14 @@ +package com.baeldung.list.ignorecase; + +import java.util.List; + +public class IgnoreCaseSearchUtil { + public static boolean ignoreCaseContains(List theList, String searchStr) { + for (String s : theList) { + if (searchStr.equalsIgnoreCase(s)) { + return true; + } + } + return false; + } +} diff --git a/core-java-modules/core-java-collections-array-list/src/main/java/com/baeldung/list/ignorecase/IgnoreCaseStringList.java b/core-java-modules/core-java-collections-array-list/src/main/java/com/baeldung/list/ignorecase/IgnoreCaseStringList.java new file mode 100644 index 0000000000..a27c2650ed --- /dev/null +++ b/core-java-modules/core-java-collections-array-list/src/main/java/com/baeldung/list/ignorecase/IgnoreCaseStringList.java @@ -0,0 +1,29 @@ +package com.baeldung.list.ignorecase; + +import java.util.ArrayList; +import java.util.Collection; + +public class IgnoreCaseStringList extends ArrayList { + + public IgnoreCaseStringList() { + + } + + public IgnoreCaseStringList(Collection c) { + super(c); + } + + @Override + public boolean contains(Object o) { + String searchStr = (String) o; + // Using Stream API: + // return this.stream().anyMatch(searchStr::equalsIgnoreCase); + for (String s : this) { + if (searchStr.equalsIgnoreCase(s)) { + return true; + } + } + return false; + } + +} diff --git a/core-java-modules/core-java-collections-array-list/src/main/java/com/baeldung/listofobjectstolistofstring/Node.java b/core-java-modules/core-java-collections-array-list/src/main/java/com/baeldung/listofobjectstolistofstring/Node.java new file mode 100644 index 0000000000..3e2c5693de --- /dev/null +++ b/core-java-modules/core-java-collections-array-list/src/main/java/com/baeldung/listofobjectstolistofstring/Node.java @@ -0,0 +1,17 @@ +package com.baeldung.listofobjectstolistofstring; + +public class Node { + + private final int x; + private final int y; + + public Node(int x, int y) { + this.x = x; + this.y = y; + } + + @Override + public String toString() { + return "Node (" + "x=" + x + ", y=" + y + ')'; + } +} diff --git a/core-java-modules/core-java-collections-array-list/src/main/java/com/baeldung/listofobjectstolistofstring/User.java b/core-java-modules/core-java-collections-array-list/src/main/java/com/baeldung/listofobjectstolistofstring/User.java new file mode 100644 index 0000000000..eb9298bce0 --- /dev/null +++ b/core-java-modules/core-java-collections-array-list/src/main/java/com/baeldung/listofobjectstolistofstring/User.java @@ -0,0 +1,14 @@ +package com.baeldung.listofobjectstolistofstring; + +public class User { + private final String fullName; + + public User(String fullName) { + this.fullName = fullName; + } + + @Override + public String toString() { + return "User (" + "full name='" + fullName + ')'; + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-collections-array-list/src/main/java/com/baeldung/triple/Triple.java b/core-java-modules/core-java-collections-array-list/src/main/java/com/baeldung/triple/Triple.java new file mode 100644 index 0000000000..b19266ad21 --- /dev/null +++ b/core-java-modules/core-java-collections-array-list/src/main/java/com/baeldung/triple/Triple.java @@ -0,0 +1,26 @@ +package com.baeldung.triple; + +public class Triple { + + private final L left; + private final M middle; + private final R right; + + public Triple(L left, M middle, R right) { + this.left = left; + this.middle = middle; + this.right = right; + } + + public L getLeft() { + return left; + } + + public M getMiddle() { + return middle; + } + + public R getRight() { + return right; + } +} diff --git a/core-java-modules/core-java-collections-array-list/src/test/java/com/baeldung/list/ignorecase/IgnoreCaseContainsUnitTest.java b/core-java-modules/core-java-collections-array-list/src/test/java/com/baeldung/list/ignorecase/IgnoreCaseContainsUnitTest.java new file mode 100644 index 0000000000..5ca9bb1028 --- /dev/null +++ b/core-java-modules/core-java-collections-array-list/src/test/java/com/baeldung/list/ignorecase/IgnoreCaseContainsUnitTest.java @@ -0,0 +1,45 @@ +package com.baeldung.list.ignorecase; + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class IgnoreCaseContainsUnitTest { + private static final List LANGUAGES = Arrays.asList("Java", "Python", "Kotlin", "Ruby", "Javascript", "Go"); + + @Test + void givenStringList_whenCallTheStandardContains_shouldReturnFalse() { + String searchStr = "jAvA"; + boolean result = LANGUAGES.contains(searchStr); + assertFalse(result); + } + + @Test + void givenStringList_whenSearchIgnoreCaseUsingStreamAPI_shouldReturnTrue() { + String searchStr = "koTliN"; + boolean result = LANGUAGES.stream().anyMatch(searchStr::equalsIgnoreCase); + assertTrue(result); + } + + @Test + void givenStringList_whenUsingUtilClass_shouldReturnTrue() { + String searchStr = "ruBY"; + boolean result = IgnoreCaseSearchUtil.ignoreCaseContains(LANGUAGES, searchStr); + assertTrue(result); + } + + @Test + void givenStringList_whenUsingIgnoreCaseStringList_shouldReturnTrue() { + String searchStr = "pYtHoN"; + List ignoreCaseList = new IgnoreCaseStringList(LANGUAGES); + boolean result = ignoreCaseList.contains(searchStr); + assertTrue(result); + + boolean resultContainAll = ignoreCaseList.containsAll(Arrays.asList("pYtHon", "jAvA", "koTliN", "ruBY")); + assertTrue(resultContainAll); + } +} diff --git a/core-java-modules/core-java-collections-array-list/src/test/java/com/baeldung/listofobjectstolistofstring/ConvertObjectListToStringListUnitTest.java b/core-java-modules/core-java-collections-array-list/src/test/java/com/baeldung/listofobjectstolistofstring/ConvertObjectListToStringListUnitTest.java new file mode 100644 index 0000000000..1d393a2945 --- /dev/null +++ b/core-java-modules/core-java-collections-array-list/src/test/java/com/baeldung/listofobjectstolistofstring/ConvertObjectListToStringListUnitTest.java @@ -0,0 +1,92 @@ +package com.baeldung.listofobjectstolistofstring; + +import com.google.common.collect.Lists; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class ConvertObjectListToStringListUnitTest { + + @Test + public void givenObjectList_whenForEachUsedToConvert_thenReturnSuccess() { + List outputList = new ArrayList<>(objectListWithNull().size()); + for (Object obj : objectListWithNull()) { + outputList.add(Objects.toString(obj, null)); + } + Assert.assertEquals(expectedStringListWithNull(), outputList); + } + + @Test + public void givenObjectList_whenUsingStreamsToConvert_thenReturnSuccess() { + List outputList; + outputList = objectListWithNull().stream() + .map((obj) -> Objects.toString(obj, null)) + .collect(Collectors.toList()); + Assert.assertEquals(expectedStringListWithNull(), outputList); + + } + + @Test + public void givenObjectList_whenUsingStreamsUnmodifiableListToConvert_thenReturnSuccess() { + List outputList; + outputList = objectListWithNull().stream() + .filter(Objects::nonNull) + .map((obj) -> Objects.toString(obj, null)) + .collect(Collectors.toUnmodifiableList()); + Assert.assertEquals(expectedStringListWithoutNull(), outputList); + + } + + @Test + public void givenObjectList_whenUsingGuavaTransform_thenReturnSuccess() { + List outputList; + outputList = Lists.transform(objectListWithNull(), obj -> Objects.toString(obj, null)); + Assert.assertEquals(expectedStringListWithNull(), outputList); + } + + @Test + public void givenObjectListWithNoNull_whenUsingToList_thenReturnSuccess() { + List outputList; + outputList = objectListWithoutNull().stream() + .map((obj) -> Objects.toString(obj, null)) + .toList(); + Assert.assertEquals(expectedStringListWithoutNull(), outputList); + } + + private List expectedStringListWithNull() { + List listOfStrings = new ArrayList<>(); + listOfStrings.add("1"); + listOfStrings.add("true"); + listOfStrings.add("hello"); + listOfStrings.add(Double.toString(273773.98)); + listOfStrings.add(null); + listOfStrings.add(new Node(2, 4).toString()); + listOfStrings.add(new User("John Doe").toString()); + return listOfStrings; + } + + private List objectListWithNull() { + List listOfStrings = new ArrayList<>(); + listOfStrings.add(1); + listOfStrings.add(true); + listOfStrings.add("hello"); + listOfStrings.add(Double.valueOf(273773.98)); + listOfStrings.add(null); + listOfStrings.add(new Node(2, 4)); + listOfStrings.add(new User("John Doe")); + return listOfStrings; + } + + private List expectedStringListWithoutNull() { + return List.of("1", "true", "hello", Double.toString(273773.98), new Node(2, 4).toString(), new User("John Doe").toString()); + } + + private List objectListWithoutNull() { + return List.of(1, true, "hello", Double.valueOf(273773.98), new Node(2, 4), new User("John Doe")); + } +} diff --git a/core-java-modules/core-java-collections-array-list/src/test/java/com/baeldung/triple/TripleInListUnitTest.java b/core-java-modules/core-java-collections-array-list/src/test/java/com/baeldung/triple/TripleInListUnitTest.java new file mode 100644 index 0000000000..2196ae687f --- /dev/null +++ b/core-java-modules/core-java-collections-array-list/src/test/java/com/baeldung/triple/TripleInListUnitTest.java @@ -0,0 +1,105 @@ +package com.baeldung.triple; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; + +public class TripleInListUnitTest { + + enum OP { + PLUS("+"), MINUS("-"), MULTIPLY("x"); + final String opSign; + + OP(String x) { + this.opSign = x; + } + } + + private String createQuestion(Long num1, OP operator, Long num2) { + long result; + switch (operator) { + case PLUS: + result = num1 + num2; + break; + case MINUS: + result = num1 - num2; + break; + case MULTIPLY: + result = num1 * num2; + break; + default: + throw new IllegalArgumentException("Unknown operator"); + } + return String.format("%d %s %d = ? ( answer: %d )", num1, operator.opSign, num2, result); + } + + private static final List EXPECTED_QUESTIONS = Arrays.asList( + "100 - 42 = ? ( answer: 58 )", + "100 + 42 = ? ( answer: 142 )", + "100 x 42 = ? ( answer: 4200 )"); + + @Test + void givenTripleValues_whenStoreAsList_thenTypeIsNotSafe() { + + List myTriple1 = new ArrayList(3); + myTriple1.add(100L); + myTriple1.add(OP.MINUS); + myTriple1.add(42L); + + List myTriple2 = new ArrayList(3); + myTriple2.add(100L); + myTriple2.add(OP.PLUS); + myTriple2.add(42L); + + List myTriple3 = new ArrayList(3); + myTriple3.add(100L); + myTriple3.add(OP.MULTIPLY); + myTriple3.add(42L); + + List listOfTriples = new ArrayList<>(Arrays.asList(myTriple1, myTriple2, myTriple3)); + + List oopsTriple = new ArrayList(3); + oopsTriple.add("Oops"); + oopsTriple.add(911L); + oopsTriple.add("The type is wrong"); + + listOfTriples.add(oopsTriple); + assertEquals(4, listOfTriples.size()); + + List questions = listOfTriples.stream() + .filter( + triple -> triple.size() == 3 + && triple.get(0) instanceof Long + && triple.get(1) instanceof OP + && triple.get(2) instanceof Long + ).map(triple -> { + Long left = (Long) triple.get(0); + OP op = (OP) triple.get(1); + Long right = (Long) triple.get(2); + return createQuestion(left, op, right); + }).collect(Collectors.toList()); + + assertEquals(EXPECTED_QUESTIONS, questions); + } + + @Test + void givenTripleValues_whenUsingTheTripleClass_thenTypeIsSafeAndNeat() { + Triple triple1 = new Triple<>(100L, OP.MINUS, 42L); + Triple triple2 = new Triple<>(100L, OP.PLUS, 42L); + Triple triple3 = new Triple<>(100L, OP.MULTIPLY, 42L); + Triple tripleOops = new Triple<>("Oops", 911L, "The type is wrong"); + + List> listOfTriples = new ArrayList<>(Arrays.asList(triple1, triple2, triple3)); + // listOfTriples.add(tripleOops); // Compiler error: "java: incompatible types ... " + + List questions = listOfTriples.stream() + .map(triple -> createQuestion(triple.getLeft(), triple.getMiddle(), triple.getRight())) + .collect(Collectors.toList()); + + assertEquals(EXPECTED_QUESTIONS, questions); + } +} diff --git a/core-java-modules/core-java-collections-maps/README.md b/core-java-modules/core-java-collections-maps/README.md index 15cb32fbe8..034c71ba9a 100644 --- a/core-java-modules/core-java-collections-maps/README.md +++ b/core-java-modules/core-java-collections-maps/README.md @@ -12,4 +12,5 @@ This module contains articles about Map data structures in Java. - [Immutable Map Implementations in Java](https://www.baeldung.com/java-immutable-maps) - [Guide to Apache Commons MultiValuedMap](https://www.baeldung.com/apache-commons-multi-valued-map) - [The Java HashMap Under the Hood](https://www.baeldung.com/java-hashmap-advanced) +- [Create an Empty Map in Java](https://www.baeldung.com/java-create-empty-map) - More articles: [[next -->]](/core-java-modules/core-java-collections-maps-2) diff --git a/core-java-modules/core-java-collections-maps/pom.xml b/core-java-modules/core-java-collections-maps/pom.xml index 34b878df53..3a1bf0d8a1 100644 --- a/core-java-modules/core-java-collections-maps/pom.xml +++ b/core-java-modules/core-java-collections-maps/pom.xml @@ -20,6 +20,12 @@ commons-collections4 ${commons-collections4.version} + + org.junit.platform + junit-platform-runner + ${junit-platform.version} + test + \ No newline at end of file diff --git a/core-java-modules/core-java-collections-4/src/main/java/com/baeldung/maps/initialize/EmptyMapInitializer.java b/core-java-modules/core-java-collections-maps/src/main/java/com/baeldung/map/EmptyMapInitializer.java similarity index 97% rename from core-java-modules/core-java-collections-4/src/main/java/com/baeldung/maps/initialize/EmptyMapInitializer.java rename to core-java-modules/core-java-collections-maps/src/main/java/com/baeldung/map/EmptyMapInitializer.java index 78819cc21e..106de799e7 100644 --- a/core-java-modules/core-java-collections-4/src/main/java/com/baeldung/maps/initialize/EmptyMapInitializer.java +++ b/core-java-modules/core-java-collections-maps/src/main/java/com/baeldung/map/EmptyMapInitializer.java @@ -1,7 +1,5 @@ -package com.baeldung.maps.initialize; +package com.baeldung.map; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Maps; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; @@ -10,6 +8,9 @@ import java.util.NavigableMap; import java.util.SortedMap; import java.util.TreeMap; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; + public class EmptyMapInitializer { public static Map articleMap; diff --git a/core-java-modules/core-java-collections-4/src/test/java/com/baeldung/maps/initialize/EmptyMapInitializerUnitTest.java b/core-java-modules/core-java-collections-maps/src/test/java/com/baeldung/map/EmptyMapInitializerUnitTest.java similarity index 96% rename from core-java-modules/core-java-collections-4/src/test/java/com/baeldung/maps/initialize/EmptyMapInitializerUnitTest.java rename to core-java-modules/core-java-collections-maps/src/test/java/com/baeldung/map/EmptyMapInitializerUnitTest.java index 57183734cb..94a70cdd5d 100644 --- a/core-java-modules/core-java-collections-4/src/test/java/com/baeldung/maps/initialize/EmptyMapInitializerUnitTest.java +++ b/core-java-modules/core-java-collections-maps/src/test/java/com/baeldung/map/EmptyMapInitializerUnitTest.java @@ -1,11 +1,12 @@ -package com.baeldung.maps.initialize; - -import java.util.Map; -import org.junit.Test; +package com.baeldung.map; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Map; + +import org.junit.Test; + public class EmptyMapInitializerUnitTest { @Test(expected=UnsupportedOperationException.class) diff --git a/core-java-modules/core-java-collections-set-2/README.md b/core-java-modules/core-java-collections-set-2/README.md index 48c70084ca..a11329365a 100644 --- a/core-java-modules/core-java-collections-set-2/README.md +++ b/core-java-modules/core-java-collections-set-2/README.md @@ -2,3 +2,5 @@ - [Using Streams to Collect Into a TreeSet](https://www.baeldung.com/java-stream-collect-into-treeset) - [A Guide to LinkedHashSet in Java](https://www.baeldung.com/java-linkedhashset) +- [Sorting a HashSet in Java](https://www.baeldung.com/java-sort-hashset) +- More articles: [[<-- prev]](/core-java-modules/core-java-collections-set) \ No newline at end of file diff --git a/core-java-modules/core-java-collections-set-2/pom.xml b/core-java-modules/core-java-collections-set-2/pom.xml index d3f54554e0..b1aadb0c22 100644 --- a/core-java-modules/core-java-collections-set-2/pom.xml +++ b/core-java-modules/core-java-collections-set-2/pom.xml @@ -15,6 +15,12 @@ + + org.junit.platform + junit-platform-runner + ${junit-platform.version} + test + diff --git a/core-java-modules/core-java-collections-4/src/test/java/com/baeldung/collections/sorting/HashSetUnitTest.java b/core-java-modules/core-java-collections-set-2/src/test/java/com/baeldung/hashset/sorting/HashSetUnitTest.java similarity index 97% rename from core-java-modules/core-java-collections-4/src/test/java/com/baeldung/collections/sorting/HashSetUnitTest.java rename to core-java-modules/core-java-collections-set-2/src/test/java/com/baeldung/hashset/sorting/HashSetUnitTest.java index de0b084216..ebb400570f 100644 --- a/core-java-modules/core-java-collections-4/src/test/java/com/baeldung/collections/sorting/HashSetUnitTest.java +++ b/core-java-modules/core-java-collections-set-2/src/test/java/com/baeldung/hashset/sorting/HashSetUnitTest.java @@ -1,4 +1,4 @@ -package com.baeldung.collections.sorting; +package com.baeldung.hashset.sorting; import static org.assertj.core.api.Assertions.assertThat; diff --git a/core-java-modules/core-java-io-4/README.md b/core-java-modules/core-java-io-4/README.md index 263077946d..738cbb5895 100644 --- a/core-java-modules/core-java-io-4/README.md +++ b/core-java-modules/core-java-io-4/README.md @@ -13,5 +13,6 @@ This module contains articles about core Java input and output (IO) - [Generate the MD5 Checksum for a File in Java](https://www.baeldung.com/java-md5-checksum-file) - [Getting the Filename From a String Containing an Absolute File Path](https://www.baeldung.com/java-filename-full-path) - [Mocking Java InputStream Object](https://www.baeldung.com/java-mocking-inputstream) +- [PrintStream vs PrintWriter in Java](https://www.baeldung.com/java-printstream-vs-printwriter) - [[<-- Prev]](/core-java-modules/core-java-io-3) diff --git a/core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/InMemoryClass.java b/core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/InMemoryClass.java new file mode 100644 index 0000000000..c61f28bb79 --- /dev/null +++ b/core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/InMemoryClass.java @@ -0,0 +1,6 @@ +package com.baeldung.inmemorycompilation; + +public interface InMemoryClass { + + void runCode(); +} diff --git a/core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/InMemoryClassLoader.java b/core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/InMemoryClassLoader.java new file mode 100644 index 0000000000..b4951e9d91 --- /dev/null +++ b/core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/InMemoryClassLoader.java @@ -0,0 +1,30 @@ +package com.baeldung.inmemorycompilation; + +import static java.util.Objects.requireNonNull; + +import java.util.Map; + +public class InMemoryClassLoader extends ClassLoader { + + private final InMemoryFileManager manager; + + public InMemoryClassLoader(ClassLoader parent, InMemoryFileManager manager) { + super(parent); + this.manager = requireNonNull(manager, "manager must not be null"); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + + Map compiledClasses = manager + .getBytesMap(); + + if (compiledClasses.containsKey(name)) { + byte[] bytes = compiledClasses.get(name) + .getBytes(); + return defineClass(name, bytes, 0, bytes.length); + } else { + throw new ClassNotFoundException(); + } + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/InMemoryFileManager.java b/core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/InMemoryFileManager.java new file mode 100644 index 0000000000..34ad78856a --- /dev/null +++ b/core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/InMemoryFileManager.java @@ -0,0 +1,52 @@ +package com.baeldung.inmemorycompilation; + +import java.util.Hashtable; +import java.util.Map; +import javax.tools.FileObject; +import javax.tools.ForwardingJavaFileManager; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.JavaFileObject.Kind; +import javax.tools.StandardJavaFileManager; + +public class InMemoryFileManager extends ForwardingJavaFileManager { + + private final Map compiledClasses; + private final ClassLoader loader; + + public InMemoryFileManager(StandardJavaFileManager standardManager) { + super(standardManager); + this.compiledClasses = new Hashtable<>(); + this.loader = new InMemoryClassLoader(this.getClass() + .getClassLoader(), + this + ); + } + + /** + * Used to get the class loader for our compiled class. It creates an anonymous class extending + * the SecureClassLoader which uses the byte code created by the compiler and stored in the + * JavaClassObject, and returns the Class for it + * + * @param location where to place or search for file objects. + */ + @Override + public ClassLoader getClassLoader(Location location) { + return loader; + } + + @Override + public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, + FileObject sibling) { + + JavaClassAsBytes classAsBytes = new JavaClassAsBytes( + className, kind); + compiledClasses.put(className, classAsBytes); + + return classAsBytes; + } + + public Map getBytesMap() { + return compiledClasses; + } +} diff --git a/core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/JavaClassAsBytes.java b/core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/JavaClassAsBytes.java new file mode 100644 index 0000000000..b7af9a76ba --- /dev/null +++ b/core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/JavaClassAsBytes.java @@ -0,0 +1,27 @@ +package com.baeldung.inmemorycompilation; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.net.URI; +import javax.tools.SimpleJavaFileObject; + +/** + * Represents a Java class file (compiled byte-code) + */ +public class JavaClassAsBytes extends SimpleJavaFileObject { + + protected final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + public JavaClassAsBytes(String name, Kind kind) { + super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind); + } + + public byte[] getBytes() { + return bos.toByteArray(); + } + + @Override + public OutputStream openOutputStream() { + return bos; + } +} diff --git a/core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/JavaSourceFromString.java b/core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/JavaSourceFromString.java new file mode 100644 index 0000000000..eaa6c21b54 --- /dev/null +++ b/core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/JavaSourceFromString.java @@ -0,0 +1,26 @@ +package com.baeldung.inmemorycompilation; + +import static java.util.Objects.requireNonNull; + +import java.net.URI; +import javax.tools.SimpleJavaFileObject; + +/** + * Represents a Java source code file + */ +public class JavaSourceFromString extends SimpleJavaFileObject { + + private final String sourceCode; + + public JavaSourceFromString(String name, String sourceCode) { + super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), + Kind.SOURCE + ); + this.sourceCode = requireNonNull(sourceCode, "sourceCode must not be null"); + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + return sourceCode; + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-jvm-3/src/test/java/com/baeldung/inmemorycompilation/InMemoryCompilationUnitTest.java b/core-java-modules/core-java-jvm-3/src/test/java/com/baeldung/inmemorycompilation/InMemoryCompilationUnitTest.java new file mode 100644 index 0000000000..410bdca866 --- /dev/null +++ b/core-java-modules/core-java-jvm-3/src/test/java/com/baeldung/inmemorycompilation/InMemoryCompilationUnitTest.java @@ -0,0 +1,57 @@ +package com.baeldung.inmemorycompilation; + +import java.util.Collections; +import java.util.List; + +import javax.tools.DiagnosticCollector; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.ToolProvider; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class InMemoryCompilationUnitTest { + + private static final Logger LOGGER = LoggerFactory.getLogger(InMemoryCompilationUnitTest.class); + + final static String QUALIFIED_CLASS_NAME = "com.baeldung.inmemorycompilation.TestClass"; + + final static String SOURCE_CODE = + "package com.baeldung.inmemorycompilation;\n" + + "public class TestClass implements InMemoryClass {\n" + + "@Override\n" + + " public void runCode() {\n" + + " System.out.println(\"code is running...\");\n" + + " }\n" + + "}\n"; + + @Test + public void whenStringIsCompiled_ThenCodeShouldExecute() throws ClassNotFoundException, InstantiationException, IllegalAccessException { + + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + DiagnosticCollector diagnostics = new DiagnosticCollector<>(); + InMemoryFileManager manager = new InMemoryFileManager(compiler.getStandardFileManager(null, null, null)); + + List sourceFiles = Collections.singletonList(new JavaSourceFromString(QUALIFIED_CLASS_NAME, SOURCE_CODE)); + + JavaCompiler.CompilationTask task = compiler.getTask(null, manager, diagnostics, null, null, sourceFiles); + + boolean result = task.call(); + + if (result) { + diagnostics.getDiagnostics() + .forEach(d -> LOGGER.error(String.valueOf(d))); + } else { + ClassLoader classLoader = manager.getClassLoader(null); + Class clazz = classLoader.loadClass(QUALIFIED_CLASS_NAME); + InMemoryClass instanceOfClass = (InMemoryClass) clazz.newInstance(); + + Assertions.assertInstanceOf(InMemoryClass.class, instanceOfClass); + + instanceOfClass.runCode(); + } + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-jvm-3/test/java/com/baeldung/resource/ClassGetResourceUnitTest.java b/core-java-modules/core-java-jvm-3/src/test/java/com/baeldung/resource/ClassGetResourceUnitTest.java similarity index 93% rename from core-java-modules/core-java-jvm-3/test/java/com/baeldung/resource/ClassGetResourceUnitTest.java rename to core-java-modules/core-java-jvm-3/src/test/java/com/baeldung/resource/ClassGetResourceUnitTest.java index fb0c88f4bb..2fe512384a 100644 --- a/core-java-modules/core-java-jvm-3/test/java/com/baeldung/resource/ClassGetResourceUnitTest.java +++ b/core-java-modules/core-java-jvm-3/src/test/java/com/baeldung/resource/ClassGetResourceUnitTest.java @@ -1,10 +1,12 @@ package com.baeldung.resource; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.net.URL; +@Disabled class ClassGetResourceUnitTest { @Test diff --git a/core-java-modules/core-java-jvm-3/test/java/com/baeldung/resource/ClassLoaderGetResourceUnitTest.java b/core-java-modules/core-java-jvm-3/src/test/java/com/baeldung/resource/ClassLoaderGetResourceUnitTest.java similarity index 93% rename from core-java-modules/core-java-jvm-3/test/java/com/baeldung/resource/ClassLoaderGetResourceUnitTest.java rename to core-java-modules/core-java-jvm-3/src/test/java/com/baeldung/resource/ClassLoaderGetResourceUnitTest.java index 54025c5404..d2d0600165 100644 --- a/core-java-modules/core-java-jvm-3/test/java/com/baeldung/resource/ClassLoaderGetResourceUnitTest.java +++ b/core-java-modules/core-java-jvm-3/src/test/java/com/baeldung/resource/ClassLoaderGetResourceUnitTest.java @@ -1,10 +1,12 @@ package com.baeldung.resource; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.net.URL; +@Disabled class ClassLoaderGetResourceUnitTest { @Test diff --git a/core-java-modules/core-java-lang-oop-others/pom.xml b/core-java-modules/core-java-lang-oop-others/pom.xml index d9d147f34a..dea92067f2 100644 --- a/core-java-modules/core-java-lang-oop-others/pom.xml +++ b/core-java-modules/core-java-lang-oop-others/pom.xml @@ -13,4 +13,12 @@ 0.0.1-SNAPSHOT + + + org.apache.commons + commons-lang3 + 3.12.0 + + + \ No newline at end of file diff --git a/core-java-modules/core-java-lang-oop-others/src/main/java/com/baeldung/nullchecking/Car.java b/core-java-modules/core-java-lang-oop-others/src/main/java/com/baeldung/nullchecking/Car.java new file mode 100644 index 0000000000..975006d8df --- /dev/null +++ b/core-java-modules/core-java-lang-oop-others/src/main/java/com/baeldung/nullchecking/Car.java @@ -0,0 +1,30 @@ +package com.baeldung.nullchecking; + +import java.util.Objects; +import java.util.stream.Stream; + +public class Car { + + Integer power; + + Integer year; + + public boolean allNull() { + + if (power != null) { + return false; + } + + if (year != null) { + return false; + } + + return true; + } + + public boolean allNullV2() { + + return Stream.of(power, year) + .allMatch(Objects::isNull); + } +} diff --git a/core-java-modules/core-java-lang-oop-others/src/main/java/com/baeldung/nullchecking/NullChecker.java b/core-java-modules/core-java-lang-oop-others/src/main/java/com/baeldung/nullchecking/NullChecker.java new file mode 100644 index 0000000000..5630577cdf --- /dev/null +++ b/core-java-modules/core-java-lang-oop-others/src/main/java/com/baeldung/nullchecking/NullChecker.java @@ -0,0 +1,25 @@ +package com.baeldung.nullchecking; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Objects; + +public class NullChecker { + + public static boolean allNull(Object target) { + + return Arrays.stream(target.getClass() + .getDeclaredFields()) + .peek(f -> f.setAccessible(true)) + .map(f -> getFieldValue(f, target)) + .allMatch(Objects::isNull); + } + + private static Object getFieldValue(Field field, Object target) { + try { + return field.get(target); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } +} diff --git a/core-java-modules/core-java-lang-oop-others/src/test/java/com/baeldung/nullchecking/NullCheckUnitTest.java b/core-java-modules/core-java-lang-oop-others/src/test/java/com/baeldung/nullchecking/NullCheckUnitTest.java new file mode 100644 index 0000000000..db8aab8193 --- /dev/null +++ b/core-java-modules/core-java-lang-oop-others/src/test/java/com/baeldung/nullchecking/NullCheckUnitTest.java @@ -0,0 +1,45 @@ +package com.baeldung.nullchecking; + +import org.apache.commons.lang3.ObjectUtils; +import org.junit.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class NullCheckUnitTest { + + @Test + public void givenNullFields_whenCheckForNullsUsingIfs_thenReturnCorrectValue(){ + Car car = new Car(); + + boolean result = car.allNull(); + + assertTrue(result); + } + + @Test + public void givenNullFields_whenCheckForNullsUsingStreams_thenReturnCorrectValue(){ + Car car = new Car(); + + boolean result = car.allNullV2(); + + assertTrue(result); + } + + @Test + public void givenNullFields_whenCheckForNullsUsingApacheCommons_thenReturnCorrectValue(){ + Car car = new Car(); + + boolean result = ObjectUtils.allNull(car.power, car.year); + + assertTrue(result); + } + + @Test + public void givenNullFields_whenCheckForNullsUsingReflection_thenReturnCorrectValue(){ + Car car = new Car(); + + boolean result = NullChecker.allNull(car); + + assertTrue(result); + } +} diff --git a/core-java-modules/core-java-reflection/README.md b/core-java-modules/core-java-reflection/README.md index 7c3ef69012..f68362611e 100644 --- a/core-java-modules/core-java-reflection/README.md +++ b/core-java-modules/core-java-reflection/README.md @@ -7,3 +7,4 @@ - [Dynamic Proxies in Java](http://www.baeldung.com/java-dynamic-proxies) - [What Causes java.lang.reflect.InvocationTargetException?](https://www.baeldung.com/java-lang-reflect-invocationtargetexception) - [How to Get a Name of a Method Being Executed?](http://www.baeldung.com/java-name-of-executing-method) +- [Getting Class Type From a String in Java](https://www.baeldung.com/java-get-class-object-from-string) diff --git a/core-java-modules/core-java-security-3/README.md b/core-java-modules/core-java-security-3/README.md index 9d82e829e2..a5cfa5bdca 100644 --- a/core-java-modules/core-java-security-3/README.md +++ b/core-java-modules/core-java-security-3/README.md @@ -11,4 +11,5 @@ This module contains articles about core Java Security - [Generating a Secure AES Key in Java](https://www.baeldung.com/java-secure-aes-key) - [Computing an X509 Certificate’s Thumbprint in Java](https://www.baeldung.com/java-x509-certificate-thumbprint) - [Error: “trustAnchors parameter must be non-empty”](https://www.baeldung.com/java-trustanchors-parameter-must-be-non-empty) +- [Common Exceptions of Crypto APIs in Java](https://www.baeldung.com/java-crypto-apis-exceptions) - More articles: [[<-- prev]](/core-java-modules/core-java-security-2) diff --git a/core-java-modules/core-java-streams-4/pom.xml b/core-java-modules/core-java-streams-4/pom.xml index ed4603796d..46c0f3f7e1 100644 --- a/core-java-modules/core-java-streams-4/pom.xml +++ b/core-java-modules/core-java-streams-4/pom.xml @@ -59,6 +59,36 @@ 3.12.0 test + + io.reactivex.rxjava3 + rxjava + ${rx.java3.version} + + + io.vavr + vavr + ${io.varv.version} + + + io.projectreactor + reactor-core + ${io.reactor3.version} + + + org.apache.commons + commons-collections4 + ${apache.commons.collection4.version} + + + com.google.guava + guava + ${google.guava.version} + + + com.oath.cyclops + cyclops + ${cyclops.version} + @@ -90,6 +120,12 @@ 12 1.2.5 2.2.2 + 3.1.5 + 1.0.0-alpha-4 + 3.5.1 + 4.4 + 31.1-jre + 10.4.1 \ No newline at end of file diff --git a/core-java-modules/core-java-streams-4/src/main/java/com/baeldung/streams/processing/CustomBatchIterator.java b/core-java-modules/core-java-streams-4/src/main/java/com/baeldung/streams/processing/CustomBatchIterator.java new file mode 100644 index 0000000000..b5407b7283 --- /dev/null +++ b/core-java-modules/core-java-streams-4/src/main/java/com/baeldung/streams/processing/CustomBatchIterator.java @@ -0,0 +1,47 @@ +package com.baeldung.streams.processing; + +import static java.util.Spliterator.ORDERED; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Spliterators; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +public class CustomBatchIterator implements Iterator> { + private final int batchSize; + private List currentBatch; + private final Iterator iterator; + + public CustomBatchIterator(Iterator sourceIterator, int batchSize) { + this.batchSize = batchSize; + this.iterator = sourceIterator; + } + + @Override + public List next() { + return currentBatch; + } + + @Override + public boolean hasNext() { + prepareNextBatch(); + return currentBatch != null && !currentBatch.isEmpty(); + } + + public static Stream> batchStreamOf(Stream stream, int batchSize) { + return stream(new CustomBatchIterator<>(stream.iterator(), batchSize)); + } + + private static Stream stream(Iterator iterator) { + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, ORDERED), false); + } + + private void prepareNextBatch() { + currentBatch = new ArrayList<>(batchSize); + while (iterator.hasNext() && currentBatch.size() < batchSize) { + currentBatch.add(iterator.next()); + } + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-streams-4/src/test/java/com/baeldung/streams/processing/StreamProcessingUnitTest.java b/core-java-modules/core-java-streams-4/src/test/java/com/baeldung/streams/processing/StreamProcessingUnitTest.java new file mode 100644 index 0000000000..f8f88387d5 --- /dev/null +++ b/core-java-modules/core-java-streams-4/src/test/java/com/baeldung/streams/processing/StreamProcessingUnitTest.java @@ -0,0 +1,141 @@ +package com.baeldung.streams.processing; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.apache.commons.collections4.ListUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.google.common.collect.Iterators; + +import cyclops.data.LazySeq; +import cyclops.reactive.ReactiveSeq; +import io.reactivex.rxjava3.core.Observable; +import reactor.core.publisher.Flux; + +public class StreamProcessingUnitTest { + public final int BATCH_SIZE = 10; + + private final List firstBatch = List.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); + private final List secondBatch = List.of(10, 11, 12, 13, 14, 15, 16, 17, 18, 19); + private final List thirdBatch = List.of(20, 21, 22, 23, 24, 25, 26, 27, 28, 29); + private final List fourthBatch = List.of(30, 31, 32, 33); + + public Stream data; + + @BeforeEach + public void setUp() { + data = IntStream.range(0, 34) + .boxed(); + } + + @Test + public void givenAStreamOfData_whenIsProcessingInBatchUsingSpliterator_thenFourBatchesAreObtained() { + Collection> result = new ArrayList<>(); + CustomBatchIterator.batchStreamOf(data, BATCH_SIZE) + .forEach(result::add); + assertTrue(result.contains(firstBatch)); + assertTrue(result.contains(secondBatch)); + assertTrue(result.contains(thirdBatch)); + assertTrue(result.contains(fourthBatch)); + } + + @Test + public void givenAStreamOfData_whenIsProcessingInBatchUsingCollectionAPI_thenFourBatchesAreObtained() { + Collection> result = data.collect(Collectors.groupingBy(it -> it / BATCH_SIZE)) + .values(); + assertTrue(result.contains(firstBatch)); + assertTrue(result.contains(secondBatch)); + assertTrue(result.contains(thirdBatch)); + assertTrue(result.contains(fourthBatch)); + } + + @Test + public void givenAStreamOfData_whenIsProcessingInBatchParallelUsingCollectionAPI_thenFourBatchesAreObtained() { + Collection> result = data.parallel() + .collect(Collectors.groupingBy(it -> it / BATCH_SIZE)) + .values(); + assertTrue(result.contains(firstBatch)); + assertTrue(result.contains(secondBatch)); + assertTrue(result.contains(thirdBatch)); + assertTrue(result.contains(fourthBatch)); + } + + @Test + public void givenAStreamOfData_whenIsProcessingInBatchUsingRxJavaV3_thenFourBatchesAreObtained() { + // RxJava v3 + Collection> result = new ArrayList<>(); + Observable.fromStream(data) + .buffer(BATCH_SIZE) + .subscribe(result::add); + assertTrue(result.contains(firstBatch)); + assertTrue(result.contains(secondBatch)); + assertTrue(result.contains(thirdBatch)); + assertTrue(result.contains(fourthBatch)); + } + + @Test + public void givenAStreamOfData_whenIsProcessingInBatchUsingReactor_thenFourBatchesAreObtained() { + Collection> result = new ArrayList<>(); + Flux.fromStream(data) + .buffer(BATCH_SIZE) + .subscribe(result::add); + assertTrue(result.contains(firstBatch)); + assertTrue(result.contains(secondBatch)); + assertTrue(result.contains(thirdBatch)); + assertTrue(result.contains(fourthBatch)); + } + + @Test + public void givenAStreamOfData_whenIsProcessingInBatchUsingApacheCommon_thenFourBatchesAreObtained() { + Collection> result = new ArrayList<>(ListUtils.partition(data.collect(Collectors.toList()), BATCH_SIZE)); + assertTrue(result.contains(firstBatch)); + assertTrue(result.contains(secondBatch)); + assertTrue(result.contains(thirdBatch)); + assertTrue(result.contains(fourthBatch)); + } + + @Test + public void givenAStreamOfData_whenIsProcessingInBatchUsingGuava_thenFourBatchesAreObtained() { + Collection> result = new ArrayList<>(); + Iterators.partition(data.iterator(), BATCH_SIZE) + .forEachRemaining(result::add); + assertTrue(result.contains(firstBatch)); + assertTrue(result.contains(secondBatch)); + assertTrue(result.contains(thirdBatch)); + assertTrue(result.contains(fourthBatch)); + } + + @Test + public void givenAStreamOfData_whenIsProcessingInBatchUsingCyclops_thenFourBatchesAreObtained() { + Collection> result = new ArrayList<>(); + ReactiveSeq.fromStream(data) + .grouped(BATCH_SIZE) + .toList() + .forEach(value -> result.add(value.collect(Collectors.toList()))); + assertTrue(result.contains(firstBatch)); + assertTrue(result.contains(secondBatch)); + assertTrue(result.contains(thirdBatch)); + assertTrue(result.contains(fourthBatch)); + } + + @Test + public void givenAStreamOfData_whenIsProcessingInBatchUsingCyclopsLazy_thenFourBatchesAreObtained() { + Collection> result = new ArrayList<>(); + LazySeq.fromStream(data) + .grouped(BATCH_SIZE) + .toList() + .forEach(value -> result.add(value.collect(Collectors.toList()))); + assertTrue(result.contains(firstBatch)); + assertTrue(result.contains(secondBatch)); + assertTrue(result.contains(thirdBatch)); + assertTrue(result.contains(fourthBatch)); + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-streams-4/src/test/java/com/baeldung/streams/processing/vavr/StreamProcessingWithVavrUnitTest.java b/core-java-modules/core-java-streams-4/src/test/java/com/baeldung/streams/processing/vavr/StreamProcessingWithVavrUnitTest.java new file mode 100644 index 0000000000..859b059889 --- /dev/null +++ b/core-java-modules/core-java-streams-4/src/test/java/com/baeldung/streams/processing/vavr/StreamProcessingWithVavrUnitTest.java @@ -0,0 +1,30 @@ +package com.baeldung.streams.processing.vavr; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import com.baeldung.streams.processing.StreamProcessingUnitTest; + +import io.vavr.collection.List; +import io.vavr.collection.Stream; + +public class StreamProcessingWithVavrUnitTest extends StreamProcessingUnitTest { + + private final List firstBatch = List.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); + private final List secondBatch = List.of(10, 11, 12, 13, 14, 15, 16, 17, 18, 19); + private final List thirdBatch = List.of(20, 21, 22, 23, 24, 25, 26, 27, 28, 29); + private final List fourthBatch = List.of(30, 31, 32, 33); + + @Test + public void givenAStreamOfData_whenIsProcessingInBatchUsingVavr_thenFourBatchesAreObtained() { + List> result = Stream.ofAll(data) + .toList() + .grouped(BATCH_SIZE) + .toList(); + assertTrue(result.contains(firstBatch)); + assertTrue(result.contains(secondBatch)); + assertTrue(result.contains(thirdBatch)); + assertTrue(result.contains(fourthBatch)); + } +} diff --git a/core-java-modules/core-java-uuid/src/main/java/com/baeldung/uuid/UUIDGenerator.java b/core-java-modules/core-java-uuid/src/main/java/com/baeldung/uuid/UUIDGenerator.java index 2170a72644..68ff9ce1d4 100644 --- a/core-java-modules/core-java-uuid/src/main/java/com/baeldung/uuid/UUIDGenerator.java +++ b/core-java-modules/core-java-uuid/src/main/java/com/baeldung/uuid/UUIDGenerator.java @@ -27,17 +27,17 @@ public final class UUIDGenerator { private static long get64LeastSignificantBitsForVersion1() { final long random63BitLong = new Random().nextLong() & 0x3FFFFFFFFFFFFFFFL; - final long variant3BitFlag = 0x8000000000000000L; - return random63BitLong + variant3BitFlag; + long variant3BitFlag = 0x8000000000000000L; + return random63BitLong | variant3BitFlag; } private static long get64MostSignificantBitsForVersion1() { - final long timeForUuidIn100Nanos = System.currentTimeMillis(); - final long time_low = (timeForUuidIn100Nanos & 0x0000_0000_FFFF_FFFFL) << 32; - final long time_mid = ((timeForUuidIn100Nanos >> 32) & 0xFFFF) << 16; + final long currentTimeMillis = System.currentTimeMillis(); + final long time_low = (currentTimeMillis & 0x0000_0000_FFFF_FFFFL) << 32; + final long time_mid = ((currentTimeMillis >> 32) & 0xFFFF) << 16; final long version = 1 << 12; - final long time_hi = ((timeForUuidIn100Nanos >> 48) & 0x0FFF); - return time_low + time_mid + version + time_hi; + final long time_high = ((currentTimeMillis >> 48) & 0x0FFF); + return time_low | time_mid | version | time_high; } /** diff --git a/core-java-modules/pom.xml b/core-java-modules/pom.xml index cc137d08b6..bbbca6adf5 100644 --- a/core-java-modules/pom.xml +++ b/core-java-modules/pom.xml @@ -31,7 +31,6 @@ core-java-collections-2 core-java-collections-3 core-java-collections-4 - core-java-collections-array-list core-java-collections-conversions core-java-collections-conversions-2 core-java-collections-set-2 diff --git a/gcp-firebase/src/main/java/com/baeldung/gcp/firebase/publisher/controller/FirebasePublisherController.java b/gcp-firebase/src/main/java/com/baeldung/gcp/firebase/publisher/controller/FirebasePublisherController.java index ca7467531e..f926c0ef88 100644 --- a/gcp-firebase/src/main/java/com/baeldung/gcp/firebase/publisher/controller/FirebasePublisherController.java +++ b/gcp-firebase/src/main/java/com/baeldung/gcp/firebase/publisher/controller/FirebasePublisherController.java @@ -2,6 +2,7 @@ package com.baeldung.gcp.firebase.publisher.controller; import java.util.List; +import java.util.Arrays; import java.util.stream.Collectors; import org.springframework.http.HttpStatus; @@ -103,7 +104,7 @@ public class FirebasePublisherController { @DeleteMapping("/subscriptions/{topic}/{registrationToken}") public ResponseEntity deleteSubscription(@PathVariable String topic, @PathVariable String registrationToken) throws FirebaseMessagingException { - fcm.subscribeToTopic(List.of(registrationToken), topic); + fcm.subscribeToTopic(Arrays.asList(registrationToken), topic); return ResponseEntity.ok().build(); } } diff --git a/gradle-modules/gradle-7/README.md b/gradle-modules/gradle-7/README.md index 19dfc7b15c..ef1e536229 100644 --- a/gradle-modules/gradle-7/README.md +++ b/gradle-modules/gradle-7/README.md @@ -2,3 +2,4 @@ ### Relevant Articles: - [How to Configure Conditional Dependencies in Gradle](https://www.baeldung.com/gradle-conditional-dependencies) +- [Working With Multiple Repositories in Gradle](https://www.baeldung.com/java-gradle-multiple-repositories) diff --git a/httpclient-simple/src/test/java/com/baeldung/httpclient/base/HttpClientBasicLiveTest.java b/httpclient-simple/src/test/java/com/baeldung/httpclient/base/HttpClientBasicLiveTest.java index d1b093394e..e05017ccdd 100644 --- a/httpclient-simple/src/test/java/com/baeldung/httpclient/base/HttpClientBasicLiveTest.java +++ b/httpclient-simple/src/test/java/com/baeldung/httpclient/base/HttpClientBasicLiveTest.java @@ -1,72 +1,77 @@ package com.baeldung.httpclient.base; -import org.apache.http.HttpStatus; -import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.entity.ContentType; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.util.EntityUtils; -import com.baeldung.httpclient.ResponseUtil; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import com.baeldung.handler.CustomHttpClientResponseHandler; + +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; + +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.ParseException; +import org.junit.jupiter.api.Test; import java.io.IOException; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.notNullValue; -import static org.junit.Assert.assertThat; + public class HttpClientBasicLiveTest { private static final String SAMPLE_URL = "http://www.github.com"; - private CloseableHttpClient instance; - - private CloseableHttpResponse response; - - @Before - public final void before() { - instance = HttpClientBuilder.create().build(); - } - - @After - public final void after() throws IllegalStateException, IOException { - ResponseUtil.closeResponse(response); - } - - // tests - - // simple request - response - @Test - public final void whenExecutingBasicGetRequest_thenNoExceptions() throws ClientProtocolException, IOException { - response = instance.execute(new HttpGet(SAMPLE_URL)); + public final void whenExecutingBasicGetRequest_thenNoExceptions() throws IOException { + final HttpGet request = new HttpGet(SAMPLE_URL); + + try (CloseableHttpClient client = HttpClientBuilder.create().build(); + + CloseableHttpResponse response = (CloseableHttpResponse) client + .execute(request, new CustomHttpClientResponseHandler())) { + } } @Test - public final void givenGetRequestExecuted_whenAnalyzingTheResponse_thenCorrectStatusCode() throws ClientProtocolException, IOException { - response = instance.execute(new HttpGet(SAMPLE_URL)); - final int statusCode = response.getStatusLine().getStatusCode(); - assertThat(statusCode, equalTo(HttpStatus.SC_OK)); + public final void givenGetRequestExecuted_whenAnalyzingTheResponse_thenCorrectStatusCode() throws IOException { + final HttpGet request = new HttpGet(SAMPLE_URL); + try (CloseableHttpClient client = HttpClientBuilder.create().build(); + + CloseableHttpResponse response = (CloseableHttpResponse) client + .execute(request, new CustomHttpClientResponseHandler())) { + + assertThat(response.getCode(), equalTo(HttpStatus.SC_OK)); + } } - @Test - public final void givenGetRequestExecuted_whenAnalyzingTheResponse_thenCorrectMimeType() throws ClientProtocolException, IOException { - response = instance.execute(new HttpGet(SAMPLE_URL)); - final String contentMimeType = ContentType.getOrDefault(response.getEntity()).getMimeType(); - assertThat(contentMimeType, equalTo(ContentType.TEXT_HTML.getMimeType())); + @Test + public final void givenGetRequestExecuted_whenAnalyzingTheResponse_thenCorrectMimeType() throws IOException { + final HttpGet request = new HttpGet(SAMPLE_URL); + + try (CloseableHttpClient client = HttpClientBuilder.create().build(); + + CloseableHttpResponse response = (CloseableHttpResponse) client + .execute(request, new CustomHttpClientResponseHandler())) { + + final String contentMimeType = ContentType.parse(response.getEntity().getContentType()).getMimeType(); + assertThat(contentMimeType, equalTo(ContentType.TEXT_HTML.getMimeType())); + } } - @Test - public final void givenGetRequestExecuted_whenAnalyzingTheResponse_thenCorrectBody() throws ClientProtocolException, IOException { - response = instance.execute(new HttpGet(SAMPLE_URL)); - final String bodyAsString = EntityUtils.toString(response.getEntity()); - assertThat(bodyAsString, notNullValue()); + @Test + public final void givenGetRequestExecuted_whenAnalyzingTheResponse_thenCorrectBody() throws IOException, ParseException { + final HttpGet request = new HttpGet(SAMPLE_URL); + try (CloseableHttpClient client = HttpClientBuilder.create().build(); + + CloseableHttpResponse response = (CloseableHttpResponse) client + .execute(request, new CustomHttpClientResponseHandler())) { + + assertThat(response, notNullValue()); + } } } diff --git a/httpclient-simple/src/test/java/com/baeldung/httpclient/sec/HttpClientAuthLiveTest.java b/httpclient-simple/src/test/java/com/baeldung/httpclient/sec/HttpClientAuthLiveTest.java index 0f7018a9ac..35af84679b 100644 --- a/httpclient-simple/src/test/java/com/baeldung/httpclient/sec/HttpClientAuthLiveTest.java +++ b/httpclient-simple/src/test/java/com/baeldung/httpclient/sec/HttpClientAuthLiveTest.java @@ -1,118 +1,119 @@ package com.baeldung.httpclient.sec; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + import org.apache.commons.codec.binary.Base64; -import org.apache.http.HttpHeaders; -import org.apache.http.HttpHost; -import org.apache.http.HttpStatus; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.AuthCache; -import org.apache.http.client.CredentialsProvider; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.protocol.HttpClientContext; -import org.apache.http.impl.auth.BasicScheme; -import org.apache.http.impl.client.BasicAuthCache; -import org.apache.http.impl.client.BasicCredentialsProvider; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.protocol.HttpContext; -import com.baeldung.httpclient.ResponseUtil; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; + +import com.baeldung.handler.CustomHttpClientResponseHandler; + +import org.apache.hc.client5.http.auth.AuthScope; +import org.apache.hc.client5.http.auth.AuthCache; +import org.apache.hc.client5.http.auth.CredentialsProvider; +import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.impl.auth.BasicAuthCache; +import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; +import org.apache.hc.client5.http.impl.auth.BasicScheme; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +import org.apache.hc.client5.http.protocol.HttpClientContext; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.protocol.HttpContext; + +import org.junit.jupiter.api.Test; import java.io.IOException; import java.nio.charset.StandardCharsets; -import static org.hamcrest.Matchers.equalTo; -import static org.junit.Assert.assertThat; - /* * NOTE : Need module httpclient-simple to be running */ - -public class HttpClientAuthLiveTest { +class HttpClientAuthLiveTest { private static final String URL_SECURED_BY_BASIC_AUTHENTICATION = "http://localhost:8082/httpclient-simple/api/foos/1"; private static final String DEFAULT_USER = "user1"; private static final String DEFAULT_PASS = "user1Pass"; - - private CloseableHttpClient client; - - private CloseableHttpResponse response; - - @Before - public final void before() { - client = HttpClientBuilder.create().build(); - } - - @After - public final void after() throws IllegalStateException, IOException { - ResponseUtil.closeResponse(response); - } - - // tests + private final char[] DEFAULT_PASS_ARRAY = DEFAULT_PASS.toCharArray() ; @Test - public final void whenExecutingBasicGetRequestWithBasicAuthenticationEnabled_thenSuccess() throws IOException { - client = HttpClientBuilder.create().setDefaultCredentialsProvider(provider()).build(); + final void whenExecutingBasicGetRequestWithBasicAuthenticationEnabled_thenSuccess() throws IOException { + final HttpGet request = new HttpGet(URL_SECURED_BY_BASIC_AUTHENTICATION); + try (CloseableHttpClient client = HttpClientBuilder.create() + .setDefaultCredentialsProvider(provider()) + .build(); - response = client.execute(new HttpGet(URL_SECURED_BY_BASIC_AUTHENTICATION)); - - final int statusCode = response.getStatusLine().getStatusCode(); - assertThat(statusCode, equalTo(HttpStatus.SC_OK)); + CloseableHttpResponse response = (CloseableHttpResponse) client + .execute(request, new CustomHttpClientResponseHandler())) { + final int statusCode = response.getCode(); + assertThat(statusCode, equalTo(HttpStatus.SC_OK)); + } } @Test - public final void givenAuthenticationIsPreemptive_whenExecutingBasicGetRequestWithBasicAuthenticationEnabled_thenSuccess() throws IOException { - client = HttpClientBuilder.create().build(); - response = client.execute(new HttpGet(URL_SECURED_BY_BASIC_AUTHENTICATION), context()); + final void givenAuthenticationIsPreemptive_whenExecutingBasicGetRequestWithBasicAuthenticationEnabled_thenSuccess() throws IOException { + final HttpGet request = new HttpGet(URL_SECURED_BY_BASIC_AUTHENTICATION); + try (CloseableHttpClient client = HttpClientBuilder.create() + .build(); - final int statusCode = response.getStatusLine().getStatusCode(); - assertThat(statusCode, equalTo(HttpStatus.SC_OK)); + CloseableHttpResponse response = (CloseableHttpResponse) client + .execute(request, context(), new CustomHttpClientResponseHandler())) { + final int statusCode = response.getCode(); + assertThat(statusCode, equalTo(200)); + } } @Test - public final void givenAuthorizationHeaderIsSetManually_whenExecutingGetRequest_thenSuccess() throws IOException { - client = HttpClientBuilder.create().build(); - + final void givenAuthorizationHeaderIsSetManually_whenExecutingGetRequest_thenSuccess() throws IOException { final HttpGet request = new HttpGet(URL_SECURED_BY_BASIC_AUTHENTICATION); request.setHeader(HttpHeaders.AUTHORIZATION, authorizationHeader(DEFAULT_USER, DEFAULT_PASS)); - response = client.execute(request); + try (CloseableHttpClient client = HttpClientBuilder.create() + .build(); - final int statusCode = response.getStatusLine().getStatusCode(); - assertThat(statusCode, equalTo(HttpStatus.SC_OK)); + CloseableHttpResponse response = (CloseableHttpResponse) client + .execute(request, context(), new CustomHttpClientResponseHandler())) { + final int statusCode = response.getCode(); + assertThat(statusCode, equalTo(HttpStatus.SC_OK)); + } } @Test - public final void givenAuthorizationHeaderIsSetManually_whenExecutingGetRequest_thenSuccess2() throws IOException { + final void givenAuthorizationHeaderIsSetManually_whenExecutingGetRequest_thenSuccess2() throws IOException { final HttpGet request = new HttpGet(URL_SECURED_BY_BASIC_AUTHENTICATION); final String auth = DEFAULT_USER + ":" + DEFAULT_PASS; final byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(StandardCharsets.ISO_8859_1)); final String authHeader = "Basic " + new String(encodedAuth); request.setHeader(HttpHeaders.AUTHORIZATION, authHeader); - client = HttpClientBuilder.create().build(); - response = client.execute(request); + try (CloseableHttpClient client = HttpClientBuilder.create() + .build(); - final int statusCode = response.getStatusLine().getStatusCode(); - assertThat(statusCode, equalTo(HttpStatus.SC_OK)); + CloseableHttpResponse response = (CloseableHttpResponse) client + .execute(request, new CustomHttpClientResponseHandler())) { + final int statusCode = response.getCode(); + assertThat(statusCode, equalTo(HttpStatus.SC_OK)); + } } // UTILS private CredentialsProvider provider() { - final CredentialsProvider provider = new BasicCredentialsProvider(); - final UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(DEFAULT_USER, DEFAULT_PASS); - provider.setCredentials(AuthScope.ANY, credentials); + final HttpHost targetHost = new HttpHost("http", "localhost", 8082); + final BasicCredentialsProvider provider = new BasicCredentialsProvider(); + AuthScope authScope = new AuthScope(targetHost); + provider.setCredentials(authScope, new UsernamePasswordCredentials(DEFAULT_USER, DEFAULT_PASS_ARRAY)); return provider; } private HttpContext context() { - final HttpHost targetHost = new HttpHost("localhost", 8082, "http"); - final CredentialsProvider credsProvider = new BasicCredentialsProvider(); - credsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(DEFAULT_USER, DEFAULT_PASS)); + final HttpHost targetHost = new HttpHost("http", "localhost", 8082); + final BasicCredentialsProvider credsProvider = new BasicCredentialsProvider(); + AuthScope authScope = new AuthScope(targetHost); + credsProvider.setCredentials(authScope, new UsernamePasswordCredentials(DEFAULT_USER, DEFAULT_PASS_ARRAY)); // Create AuthCache instance final AuthCache authCache = new BasicAuthCache(); diff --git a/httpclient-simple/src/test/java/com/baeldung/httpclient/sec/HttpClientCookieLiveTest.java b/httpclient-simple/src/test/java/com/baeldung/httpclient/sec/HttpClientCookieLiveTest.java index 82a2d8e9d6..90376acf02 100644 --- a/httpclient-simple/src/test/java/com/baeldung/httpclient/sec/HttpClientCookieLiveTest.java +++ b/httpclient-simple/src/test/java/com/baeldung/httpclient/sec/HttpClientCookieLiveTest.java @@ -26,24 +26,10 @@ class HttpClientCookieLiveTest { final void whenSettingCookiesOnARequest_thenCorrect() throws IOException { final HttpGet request = new HttpGet("http://www.github.com"); request.setHeader("Cookie", "JSESSIONID=1234"); - try (CloseableHttpClient client = HttpClients.createDefault(); CloseableHttpResponse response = (CloseableHttpResponse) client.execute(request, new CustomHttpClientResponseHandler());) { - assertThat(response.getCode(), equalTo(200)); - } - } - - @Test - final void givenUsingDeprecatedApi_whenSettingCookiesOnTheHttpClient_thenCorrect() throws IOException { - final BasicCookieStore cookieStore = new BasicCookieStore(); - final BasicClientCookie cookie = new BasicClientCookie("JSESSIONID", "1234"); - cookie.setDomain(".github.com"); - cookie.setAttribute("domain", "true"); - cookie.setPath("/"); - cookieStore.addCookie(cookie); - final HttpGet request = new HttpGet("https://www.github.com"); - try (CloseableHttpClient client = HttpClientBuilder.create() - .setDefaultCookieStore(cookieStore) - .build(); CloseableHttpResponse response = (CloseableHttpResponse) client.execute(request, new CustomHttpClientResponseHandler())) { + try (CloseableHttpClient client = HttpClients.createDefault(); + CloseableHttpResponse response = (CloseableHttpResponse) client + .execute(request, new CustomHttpClientResponseHandler())) { assertThat(response.getCode(), equalTo(200)); } } @@ -58,9 +44,12 @@ class HttpClientCookieLiveTest { cookieStore.addCookie(cookie); final HttpGet request = new HttpGet("http://www.github.com"); - try (CloseableHttpClient client = HttpClientBuilder.create().setDefaultCookieStore(cookieStore).build(); - CloseableHttpResponse response = (CloseableHttpResponse) client.execute(request, new CustomHttpClientResponseHandler())) { + try (CloseableHttpClient client = HttpClientBuilder.create() + .setDefaultCookieStore(cookieStore) + .build(); + CloseableHttpResponse response = (CloseableHttpResponse) client + .execute(request, new CustomHttpClientResponseHandler())) { assertThat(response.getCode(), equalTo(200)); } @@ -79,7 +68,9 @@ class HttpClientCookieLiveTest { // localContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore); // before 4.3 try (CloseableHttpClient client = HttpClientBuilder.create().build(); - CloseableHttpResponse response = (CloseableHttpResponse) client.execute(request, localContext, new CustomHttpClientResponseHandler())) { + + CloseableHttpResponse response = (CloseableHttpResponse) client + .execute(request, localContext, new CustomHttpClientResponseHandler())) { assertThat(response.getCode(), equalTo(200)); } } diff --git a/jackson-modules/jackson-annotations/pom.xml b/jackson-modules/jackson-annotations/pom.xml index 56fd6cf2fa..4bb9341e43 100644 --- a/jackson-modules/jackson-annotations/pom.xml +++ b/jackson-modules/jackson-annotations/pom.xml @@ -25,6 +25,27 @@ ${rest-assured.version} test + + org.springframework.boot + spring-boot-starter-data-jpa + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-data-jdbc + ${spring-boot.version} + + + com.h2database + h2 + ${h2.version} + + + org.springframework.boot + spring-boot-starter-test + ${spring-boot.version} + test + @@ -38,7 +59,9 @@ + 2.1.214 3.1.1 + 2.5.0 \ No newline at end of file diff --git a/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/jsonignorevstransient/Application.java b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/jsonignorevstransient/Application.java new file mode 100644 index 0000000000..ad4ef9e9ac --- /dev/null +++ b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/jsonignorevstransient/Application.java @@ -0,0 +1,13 @@ +package com.baeldung.jackson.jsonignorevstransient; + +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/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/jsonignorevstransient/Person.java b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/jsonignorevstransient/Person.java new file mode 100644 index 0000000000..982d0e6440 --- /dev/null +++ b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/jsonignorevstransient/Person.java @@ -0,0 +1,34 @@ +package com.baeldung.jackson.jsonignorevstransient; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +import java.io.Serializable; + +class Person implements Serializable { + + @JsonIgnore + private final Long id; + + private final String firstName; + + private final String lastName; + + public Person(Long id, String firstName, String lastName) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + } + + public Long getId() { + return id; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + +} diff --git a/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/jsonignorevstransient/User.java b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/jsonignorevstransient/User.java new file mode 100644 index 0000000000..c12e5225db --- /dev/null +++ b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/jsonignorevstransient/User.java @@ -0,0 +1,64 @@ +package com.baeldung.jackson.jsonignorevstransient; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Transient; +import java.io.Serializable; + +@Entity +@Table(name = "Users") +class User implements Serializable { + + @Id + private Long id; + + private String username; + + private String password; + + @Transient + private String repeatedPassword; + + public User() { + } + + public User(Long id, String username, String password, String repeatedPassword) { + this.id = id; + this.username = username; + this.password = password; + this.repeatedPassword = repeatedPassword; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getRepeatedPassword() { + return repeatedPassword; + } + + public void setRepeatedPassword(String repeatedPassword) { + this.repeatedPassword = repeatedPassword; + } +} diff --git a/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/jsonignorevstransient/UserRepository.java b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/jsonignorevstransient/UserRepository.java new file mode 100644 index 0000000000..ca6428ca3e --- /dev/null +++ b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/jsonignorevstransient/UserRepository.java @@ -0,0 +1,8 @@ +package com.baeldung.jackson.jsonignorevstransient; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface UserRepository extends JpaRepository { +} diff --git a/jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/jsonignorevstransient/PersonUnitTest.java b/jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/jsonignorevstransient/PersonUnitTest.java new file mode 100644 index 0000000000..0595fc240c --- /dev/null +++ b/jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/jsonignorevstransient/PersonUnitTest.java @@ -0,0 +1,23 @@ +package com.baeldung.jackson.jsonignorevstransient; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; + +class PersonUnitTest { + + @Test + void givenPerson_whenSerializing_thenIdFieldIgnored() throws JsonProcessingException { + + Person person = new Person(1L, "My First Name", "My Last Name"); + String result = new ObjectMapper().writeValueAsString(person); + + assertThat(result, containsString("firstName")); + assertThat(result, containsString("lastName")); + assertThat(result, not(containsString("id"))); + } +} \ No newline at end of file diff --git a/jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/jsonignorevstransient/UserUnitTest.java b/jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/jsonignorevstransient/UserUnitTest.java new file mode 100644 index 0000000000..bc57b6f550 --- /dev/null +++ b/jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/jsonignorevstransient/UserUnitTest.java @@ -0,0 +1,39 @@ +package com.baeldung.jackson.jsonignorevstransient; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +@SpringBootTest +class UserUnitTest { + + @Autowired + UserRepository userRepository; + + @Test + void givenUser_whenSave_thenSkipTransientFields() { + User user = new User(1L, "user", "newPassword123", "newPassword123"); + User savedUser = userRepository.save(user); + + assertNotNull(savedUser); + assertNotNull(savedUser.getPassword()); + assertNull(savedUser.getRepeatedPassword()); + } + + @Test + void givenUser_whenSerializing_thenTransientFieldNotIgnored() throws JsonProcessingException { + User user = new User(1L, "user", "newPassword123", "newPassword123"); + String result = new ObjectMapper().writeValueAsString(user); + + assertThat(result, containsString("user")); + assertThat(result, containsString("repeatedPassword")); + } + +} \ No newline at end of file diff --git a/jackson-modules/jackson-annotations/src/test/resources/application.properties b/jackson-modules/jackson-annotations/src/test/resources/application.properties new file mode 100644 index 0000000000..d0f6dd95ef --- /dev/null +++ b/jackson-modules/jackson-annotations/src/test/resources/application.properties @@ -0,0 +1,5 @@ +hibernate.hbm2ddl.auto=create-drop + +spring.datasource.jdbcUrl=jdbc:h2:mem:testDb;DB_CLOSE_DELAY=-1 +spring.datasource.username=sa +spring.datasource.password=sa \ No newline at end of file diff --git a/jenkins-modules/jenkins-jobs/README.md b/jenkins-modules/jenkins-jobs/README.md index 510aa60963..7b33dc6d99 100644 --- a/jenkins-modules/jenkins-jobs/README.md +++ b/jenkins-modules/jenkins-jobs/README.md @@ -1,2 +1,4 @@ ## Relevant Articles - [Trigger Another Job from a Jenkins Pipeline](https://www.baeldung.com/ops/jenkins-pipeline-trigger-new-job) +- [Fixing the “No Such DSL method” Error in Jenkins Pipeline](https://www.baeldung.com/ops/jenkins-pipeline-no-such-dsl-method-error) +- [Jenkins Pipeline – Change to Another Folder](https://www.baeldung.com/ops/jenkins-pipeline-change-to-another-folder) diff --git a/jenkins-modules/jenkins-jobs/zombie-job/pipeline-zombie-job b/jenkins-modules/jenkins-jobs/zombie-job/pipeline-zombie-job new file mode 100644 index 0000000000..8c21ada8c6 --- /dev/null +++ b/jenkins-modules/jenkins-jobs/zombie-job/pipeline-zombie-job @@ -0,0 +1,15 @@ +pipeline { + agent any + stages { + stage('Infinite Loop') { + steps { + script { + while (true) { + println 'This is an infinite loop!' + Thread.sleep(10000) + } + } + } + } + } +} diff --git a/jenkins-modules/jenkins-jobs/zombie-job/zombie-finish-script b/jenkins-modules/jenkins-jobs/zombie-job/zombie-finish-script new file mode 100644 index 0000000000..41090599ee --- /dev/null +++ b/jenkins-modules/jenkins-jobs/zombie-job/zombie-finish-script @@ -0,0 +1,3 @@ +Jenkins.instance.getItemByFullName("sampleZombieJob") + .getBuildByNumber(17) + .finish(hudson.model.Result.ABORTED, new java.io.IOException("Aborting build")); diff --git a/jenkins-modules/jenkins-jobs/zombie-job/zombie-interrupt-script b/jenkins-modules/jenkins-jobs/zombie-job/zombie-interrupt-script new file mode 100644 index 0000000000..bf79f50ab9 --- /dev/null +++ b/jenkins-modules/jenkins-jobs/zombie-job/zombie-interrupt-script @@ -0,0 +1,6 @@ +Thread.getAllStackTraces().keySet().each() { + if (it.name.contains('sampleZombieJob')) { + println "Stopping $it.name" + it.interrupt() + } +} diff --git a/jenkins-modules/jenkins-jobs/zombie-job/zombie-stop-script b/jenkins-modules/jenkins-jobs/zombie-job/zombie-stop-script new file mode 100644 index 0000000000..40edcf3ff6 --- /dev/null +++ b/jenkins-modules/jenkins-jobs/zombie-job/zombie-stop-script @@ -0,0 +1,6 @@ +Thread.getAllStackTraces().keySet().each() { + if (it.name.contains('sampleZombieJob')) { + println "Stopping $it.name" + it.stop() + } +} diff --git a/json-modules/json-2/src/main/java/com/baeldung/jsonvalidation/JacksonValidator.java b/json-modules/json-2/src/main/java/com/baeldung/jsonvalidation/JacksonValidator.java index 8c339f46c8..4385b39bb3 100644 --- a/json-modules/json-2/src/main/java/com/baeldung/jsonvalidation/JacksonValidator.java +++ b/json-modules/json-2/src/main/java/com/baeldung/jsonvalidation/JacksonValidator.java @@ -1,11 +1,15 @@ package com.baeldung.jsonvalidation; import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; public class JacksonValidator { - final ObjectMapper mapper = new ObjectMapper(); + final ObjectMapper mapper = JsonMapper.builder() + .enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS) + .build(); public boolean isValid(String json) { try { diff --git a/libraries-files/README.md b/libraries-files/README.md index 4157769a0d..feb09332df 100644 --- a/libraries-files/README.md +++ b/libraries-files/README.md @@ -2,3 +2,4 @@ ### Relevant Articles: - [How to Parse an INI File in Java](https://www.baeldung.com/java-parse-ini-file) +- [Using Watermarks with iText in Java](https://www.baeldung.com/java-watermarks-with-itext) diff --git a/mapstruct/pom.xml b/mapstruct/pom.xml index 4696a46abb..cdcbc798a4 100644 --- a/mapstruct/pom.xml +++ b/mapstruct/pom.xml @@ -76,7 +76,7 @@ - 1.4.2.Final + 1.5.3.Final 4.3.4.RELEASE 1.8 1.8 diff --git a/microservices-modules/pom.xml b/microservices-modules/pom.xml index 4c7079f3cd..a9cd8d2cd9 100644 --- a/microservices-modules/pom.xml +++ b/microservices-modules/pom.xml @@ -19,6 +19,7 @@ microprofile msf4j open-liberty + rest-express \ No newline at end of file diff --git a/microservices-modules/rest-express/README.md b/microservices-modules/rest-express/README.md new file mode 100644 index 0000000000..0fdff99fb7 --- /dev/null +++ b/microservices-modules/rest-express/README.md @@ -0,0 +1,5 @@ +## RestExpress + +This module contains articles about RestExpress. + +### Relevant articles \ No newline at end of file diff --git a/microservices-modules/rest-express/pom.xml b/microservices-modules/rest-express/pom.xml new file mode 100644 index 0000000000..f222998340 --- /dev/null +++ b/microservices-modules/rest-express/pom.xml @@ -0,0 +1,153 @@ + + + + microservices-modules + com.baeldung + 1.0.0-SNAPSHOT + + 4.0.0 + + rest-express + + A Basic, MongoDB-backed Service Suite + https://github.com/RestExpress/RestExpress-Scaffold + 1.0.0-SNAPSHOT + rest-express + jar + + + 0.3.3 + 3.1.2 + 2.6 + 0.11.3 + 1.0 + 0.4.8 + 4.11 + + + + + com.strategicgains + RestExpress + ${RestExpress.version} + + + com.strategicgains + Syntaxe + ${Syntaxe.version} + + + com.strategicgains.repoexpress + repoexpress-mongodb + ${repoexpress-mongodb.version} + + + com.strategicgains.plugin-express + CacheControlPlugin + ${RestExpress.plugin.version} + + + com.strategicgains + HyperExpressPlugin + ${HyperExpressPlugin.version} + + + com.strategicgains.plugin-express + MetricsPlugin + ${RestExpress.plugin.version} + + + com.strategicgains.plugin-express + SwaggerPlugin + ${RestExpress.plugin.version} + + + com.strategicgains.plugin-express + CORSPlugin + ${RestExpress.plugin.version} + + + io.dropwizard.metrics + metrics-graphite + ${metrics-graphite.version} + + + junit + junit + ${junit4.version} + jar + test + true + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.0 + + 1.8 + 1.8 + UTF-8 + + + + org.codehaus.mojo + exec-maven-plugin + 1.2.1 + + com.baeldung.restexpress.Main + + + + org.apache.maven.plugins + maven-shade-plugin + 2.4.1 + + false + + + *:* + + + + + + package + + shade + + + + + com.baeldung.restexpress.Main + + + + + + + + + + + + + org.codehaus.mojo + versions-maven-plugin + 2.0 + + + + diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Configuration.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Configuration.java new file mode 100644 index 0000000000..22161a265b --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Configuration.java @@ -0,0 +1,75 @@ +package com.baeldung.restexpress; + +import com.baeldung.restexpress.objectid.SampleOidEntityController; +import com.baeldung.restexpress.objectid.SampleOidEntityRepository; +import com.baeldung.restexpress.objectid.SampleOidEntityService; +import com.baeldung.restexpress.uuid.SampleUuidEntityController; +import com.baeldung.restexpress.uuid.SampleUuidEntityRepository; +import com.baeldung.restexpress.uuid.SampleUuidEntityService; +import com.strategicgains.repoexpress.mongodb.MongoConfig; +import com.strategicgains.restexpress.plugin.metrics.MetricsConfig; +import org.restexpress.RestExpress; +import org.restexpress.util.Environment; + +import java.util.Properties; + +public class Configuration + extends Environment { + private static final String DEFAULT_EXECUTOR_THREAD_POOL_SIZE = "20"; + + private static final String PORT_PROPERTY = "port"; + private static final String BASE_URL_PROPERTY = "base.url"; + private static final String EXECUTOR_THREAD_POOL_SIZE = "executor.threadPool.size"; + + private int port; + private String baseUrl; + private int executorThreadPoolSize; + private MetricsConfig metricsSettings; + + private SampleUuidEntityController sampleUuidController; + private SampleOidEntityController sampleOidController; + + @Override + protected void fillValues(Properties p) { + this.port = Integer.parseInt(p.getProperty(PORT_PROPERTY, String.valueOf(RestExpress.DEFAULT_PORT))); + this.baseUrl = p.getProperty(BASE_URL_PROPERTY, "http://localhost:" + String.valueOf(port)); + this.executorThreadPoolSize = Integer.parseInt(p.getProperty(EXECUTOR_THREAD_POOL_SIZE, DEFAULT_EXECUTOR_THREAD_POOL_SIZE)); + this.metricsSettings = new MetricsConfig(p); + MongoConfig mongo = new MongoConfig(p); + initialize(mongo); + } + + private void initialize(MongoConfig mongo) { + SampleUuidEntityRepository samplesUuidRepository = new SampleUuidEntityRepository(mongo.getClient(), mongo.getDbName()); + SampleUuidEntityService sampleUuidService = new SampleUuidEntityService(samplesUuidRepository); + sampleUuidController = new SampleUuidEntityController(sampleUuidService); + + SampleOidEntityRepository samplesOidRepository = new SampleOidEntityRepository(mongo.getClient(), mongo.getDbName()); + SampleOidEntityService sampleOidService = new SampleOidEntityService(samplesOidRepository); + sampleOidController = new SampleOidEntityController(sampleOidService); + } + + public int getPort() { + return port; + } + + public String getBaseUrl() { + return baseUrl; + } + + public int getExecutorThreadPoolSize() { + return executorThreadPoolSize; + } + + public MetricsConfig getMetricsConfig() { + return metricsSettings; + } + + public SampleUuidEntityController getSampleUuidEntityController() { + return sampleUuidController; + } + + public SampleOidEntityController getSampleOidEntityController() { + return sampleOidController; + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Constants.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Constants.java new file mode 100644 index 0000000000..914c0a0a07 --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Constants.java @@ -0,0 +1,23 @@ +package com.baeldung.restexpress; + +public class Constants { + /** + * These define the URL parmaeters used in the route definition strings (e.g. '{userId}'). + */ + public class Url { + //TODO: Your URL parameter names here... + public static final String SAMPLE_ID = "uuid"; + } + + /** + * These define the route names used in naming each route definitions. These names are used + * to retrieve URL patterns within the controllers by name to create links in responses. + */ + public class Routes { + //TODO: Your Route names here... + public static final String SINGLE_UUID_SAMPLE = "sample.single.route.uuid"; + public static final String SAMPLE_UUID_COLLECTION = "sample.collection.route.uuid"; + public static final String SINGLE_OID_SAMPLE = "sample.single.route.oid"; + public static final String SAMPLE_OID_COLLECTION = "sample.collection.route.oid"; + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/LastModifiedHeaderPostprocessor.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/LastModifiedHeaderPostprocessor.java new file mode 100644 index 0000000000..81314679a4 --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/LastModifiedHeaderPostprocessor.java @@ -0,0 +1,33 @@ +package com.baeldung.restexpress; + +import com.strategicgains.repoexpress.domain.Timestamped; +import com.strategicgains.util.date.DateAdapter; +import com.strategicgains.util.date.HttpHeaderTimestampAdapter; +import org.restexpress.Request; +import org.restexpress.Response; +import org.restexpress.pipeline.Postprocessor; + +import static io.netty.handler.codec.http.HttpHeaders.Names.LAST_MODIFIED; + +/** + * Assigns the Last-Modified HTTP header on the response for GET responses, if applicable. + * + * @author toddf + * @since May 15, 2012 + */ +public class LastModifiedHeaderPostprocessor + implements Postprocessor { + DateAdapter fmt = new HttpHeaderTimestampAdapter(); + + @Override + public void process(Request request, Response response) { + if (!request.isMethodGet()) return; + if (!response.hasBody()) return; + + Object body = response.getBody(); + + if (!response.hasHeader(LAST_MODIFIED) && body.getClass().isAssignableFrom(Timestamped.class)) { + response.addHeader(LAST_MODIFIED, fmt.format(((Timestamped) body).getUpdatedAt())); + } + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Main.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Main.java new file mode 100644 index 0000000000..1c02402d89 --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Main.java @@ -0,0 +1,11 @@ +package com.baeldung.restexpress; + +import org.restexpress.util.Environment; + +public class Main { + public static void main(String[] args) throws Exception { + Configuration config = Environment.load(args, Configuration.class); + Server server = new Server(config); + server.start().awaitShutdown(); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Relationships.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Relationships.java new file mode 100644 index 0000000000..4a94e96952 --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Relationships.java @@ -0,0 +1,55 @@ +package com.baeldung.restexpress; + +import com.baeldung.restexpress.objectid.SampleOidEntity; +import com.baeldung.restexpress.uuid.SampleUuidEntity; +import com.strategicgains.hyperexpress.HyperExpress; +import com.strategicgains.hyperexpress.RelTypes; +import org.restexpress.RestExpress; +import org.restexpress.common.exception.ConfigurationException; + +import java.util.Map; + +public abstract class Relationships { + private static Map ROUTES; + + public static void define(RestExpress server) { + ROUTES = server.getRouteUrlsByName(); + + HyperExpress.relationships() + .forCollectionOf(SampleUuidEntity.class) + .rel(RelTypes.SELF, href(Constants.Routes.SAMPLE_UUID_COLLECTION)) + .withQuery("limit={limit}") + .withQuery("offset={offset}") + .rel(RelTypes.NEXT, href(Constants.Routes.SAMPLE_UUID_COLLECTION) + "?offset={nextOffset}") + .withQuery("limit={limit}") + .optional() + .rel(RelTypes.PREV, href(Constants.Routes.SAMPLE_UUID_COLLECTION) + "?offset={prevOffset}") + .withQuery("limit={limit}") + .optional() + + .forClass(SampleUuidEntity.class) + .rel(RelTypes.SELF, href(Constants.Routes.SINGLE_UUID_SAMPLE)) + .rel(RelTypes.UP, href(Constants.Routes.SAMPLE_UUID_COLLECTION)) + + .forCollectionOf(SampleOidEntity.class) + .rel(RelTypes.SELF, href(Constants.Routes.SAMPLE_OID_COLLECTION)) + .withQuery("limit={limit}") + .withQuery("offset={offset}") + .rel(RelTypes.NEXT, href(Constants.Routes.SAMPLE_OID_COLLECTION) + "?offset={nextOffset}") + .withQuery("limit={limit}") + .optional() + .rel(RelTypes.PREV, href(Constants.Routes.SAMPLE_OID_COLLECTION) + "?offset={prevOffset}") + .withQuery("limit={limit}") + .optional() + + .forClass(SampleOidEntity.class) + .rel(RelTypes.SELF, href(Constants.Routes.SINGLE_OID_SAMPLE)) + .rel(RelTypes.UP, href(Constants.Routes.SAMPLE_OID_COLLECTION)); + } + + private static String href(String name) { + String href = ROUTES.get(name); + if (href == null) throw new ConfigurationException("Route name not found: " + name); + return href; + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Routes.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Routes.java new file mode 100644 index 0000000000..a510dd24fa --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Routes.java @@ -0,0 +1,30 @@ +package com.baeldung.restexpress; + +import io.netty.handler.codec.http.HttpMethod; +import org.restexpress.RestExpress; + +public abstract class Routes { + public static void define(Configuration config, RestExpress server) { + // TODO: Your routes here... + server.uri("/samples/uuid/{uuid}.{format}", config.getSampleUuidEntityController()) + .method(HttpMethod.GET, HttpMethod.PUT, HttpMethod.DELETE) + .name(Constants.Routes.SINGLE_UUID_SAMPLE); + + server.uri("/samples/uuid.{format}", config.getSampleUuidEntityController()) + .action("readAll", HttpMethod.GET) + .method(HttpMethod.POST) + .name(Constants.Routes.SAMPLE_UUID_COLLECTION); + + server.uri("/samples/oid/{uuid}.{format}", config.getSampleOidEntityController()) + .method(HttpMethod.GET, HttpMethod.PUT, HttpMethod.DELETE) + .name(Constants.Routes.SINGLE_OID_SAMPLE); + + server.uri("/samples/oid.{format}", config.getSampleOidEntityController()) + .action("readAll", HttpMethod.GET) + .method(HttpMethod.POST) + .name(Constants.Routes.SAMPLE_OID_COLLECTION); + + // or REGEX matching routes... + // server.regex("/some.regex", config.getRouteController()); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Server.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Server.java new file mode 100644 index 0000000000..d5864e607d --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/Server.java @@ -0,0 +1,129 @@ +package com.baeldung.restexpress; + +import com.baeldung.restexpress.serialization.SerializationProvider; +import com.codahale.metrics.MetricFilter; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.graphite.Graphite; +import com.codahale.metrics.graphite.GraphiteReporter; +import com.strategicgains.repoexpress.adapter.Identifiers; +import com.strategicgains.repoexpress.exception.DuplicateItemException; +import com.strategicgains.repoexpress.exception.InvalidObjectIdException; +import com.strategicgains.repoexpress.exception.ItemNotFoundException; +import com.strategicgains.restexpress.plugin.cache.CacheControlPlugin; +import com.strategicgains.restexpress.plugin.cors.CorsHeaderPlugin; +import com.strategicgains.restexpress.plugin.metrics.MetricsConfig; +import com.strategicgains.restexpress.plugin.metrics.MetricsPlugin; +import com.strategicgains.restexpress.plugin.swagger.SwaggerPlugin; +import com.strategicgains.syntaxe.ValidationException; +import org.restexpress.Flags; +import org.restexpress.RestExpress; +import org.restexpress.exception.BadRequestException; +import org.restexpress.exception.ConflictException; +import org.restexpress.exception.NotFoundException; +import org.restexpress.pipeline.SimpleConsoleLogMessageObserver; +import org.restexpress.plugin.hyperexpress.HyperExpressPlugin; +import org.restexpress.plugin.hyperexpress.Linkable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetSocketAddress; +import java.util.concurrent.TimeUnit; + +import static io.netty.handler.codec.http.HttpHeaders.Names.*; +import static org.restexpress.Flags.Auth.PUBLIC_ROUTE; + +public class Server { + private static final String SERVICE_NAME = "TODO: Enter service name"; + private static final Logger LOG = LoggerFactory.getLogger(SERVICE_NAME); + + private RestExpress server; + private Configuration config; + private boolean isStarted = false; + + public Server(Configuration config) { + this.config = config; + RestExpress.setDefaultSerializationProvider(new SerializationProvider()); + Identifiers.UUID.useShortUUID(true); + + this.server = new RestExpress() + .setName(SERVICE_NAME) + .setBaseUrl(config.getBaseUrl()) + .setExecutorThreadCount(config.getExecutorThreadPoolSize()) + .addMessageObserver(new SimpleConsoleLogMessageObserver()); + + Routes.define(config, server); + Relationships.define(server); + configurePlugins(config, server); + mapExceptions(server); + } + + public Server start() { + if (!isStarted) { + server.bind(config.getPort()); + isStarted = true; + } + + return this; + } + + public void awaitShutdown() { + if (isStarted) server.awaitShutdown(); + } + + public void shutdown() { + if (isStarted) server.shutdown(); + } + + private void configurePlugins(Configuration config, RestExpress server) { + configureMetrics(config, server); + + new SwaggerPlugin() + .flag(Flags.Auth.PUBLIC_ROUTE) + .register(server); + + new CacheControlPlugin() + .register(server); + + new HyperExpressPlugin(Linkable.class) + .register(server); + + new CorsHeaderPlugin("*") + .flag(PUBLIC_ROUTE) + .allowHeaders(CONTENT_TYPE, ACCEPT, AUTHORIZATION, REFERER, LOCATION) + .exposeHeaders(LOCATION) + .register(server); + } + + private void configureMetrics(Configuration config, RestExpress server) { + MetricsConfig mc = config.getMetricsConfig(); + + if (mc.isEnabled()) { + MetricRegistry registry = new MetricRegistry(); + new MetricsPlugin(registry) + .register(server); + + if (mc.isGraphiteEnabled()) { + final Graphite graphite = new Graphite(new InetSocketAddress(mc.getGraphiteHost(), mc.getGraphitePort())); + final GraphiteReporter reporter = GraphiteReporter.forRegistry(registry) + .prefixedWith(mc.getPrefix()) + .convertRatesTo(TimeUnit.SECONDS) + .convertDurationsTo(TimeUnit.MILLISECONDS) + .filter(MetricFilter.ALL) + .build(graphite); + reporter.start(mc.getPublishSeconds(), TimeUnit.SECONDS); + } else { + LOG.warn("*** Graphite Metrics Publishing is Disabled ***"); + } + } else { + LOG.warn("*** Metrics Generation is Disabled ***"); + } + } + + private void mapExceptions(RestExpress server) { + server + .mapException(ItemNotFoundException.class, NotFoundException.class) + .mapException(DuplicateItemException.class, ConflictException.class) + .mapException(ValidationException.class, BadRequestException.class) + .mapException(InvalidObjectIdException.class, BadRequestException.class); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/objectid/SampleOidEntity.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/objectid/SampleOidEntity.java new file mode 100644 index 0000000000..f92e56889b --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/objectid/SampleOidEntity.java @@ -0,0 +1,32 @@ +package com.baeldung.restexpress.objectid; + +import com.baeldung.restexpress.Constants; +import com.strategicgains.hyperexpress.annotation.BindToken; +import com.strategicgains.hyperexpress.annotation.TokenBindings; +import com.strategicgains.repoexpress.mongodb.AbstractMongodbEntity; +import org.restexpress.plugin.hyperexpress.Linkable; + +/** + * This is a sample entity identified by a MongoDB ObjectID (instead of a UUID). + * It also contains createdAt and updatedAt properties that are automatically maintained + * by the persistence layer (SampleOidEntityRepository). + */ +@TokenBindings({ + @BindToken(value = Constants.Url.SAMPLE_ID, field = "id") +}) +public class SampleOidEntity + extends AbstractMongodbEntity + implements Linkable { + private String name; + + public SampleOidEntity() { + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/objectid/SampleOidEntityController.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/objectid/SampleOidEntityController.java new file mode 100644 index 0000000000..1997ad9e10 --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/objectid/SampleOidEntityController.java @@ -0,0 +1,80 @@ +package com.baeldung.restexpress.objectid; + +import com.baeldung.restexpress.Constants; +import com.strategicgains.hyperexpress.builder.DefaultTokenResolver; +import com.strategicgains.hyperexpress.builder.DefaultUrlBuilder; +import com.strategicgains.hyperexpress.builder.UrlBuilder; +import com.strategicgains.repoexpress.mongodb.Identifiers; +import io.netty.handler.codec.http.HttpMethod; +import org.restexpress.Request; +import org.restexpress.Response; +import org.restexpress.common.query.QueryFilter; +import org.restexpress.common.query.QueryOrder; +import org.restexpress.common.query.QueryRange; +import org.restexpress.query.QueryFilters; +import org.restexpress.query.QueryOrders; +import org.restexpress.query.QueryRanges; + +import java.util.List; + +/** + * This is the 'controller' layer, where HTTP details are converted to domain concepts and passed to the service layer. + * Then service layer response information is enhanced with HTTP details, if applicable, for the response. + *

+ * This controller demonstrates how to process an entity that is identified by a MongoDB ObjectId. + */ +public class SampleOidEntityController { + private static final UrlBuilder LOCATION_BUILDER = new DefaultUrlBuilder(); + private SampleOidEntityService service; + + public SampleOidEntityController(SampleOidEntityService sampleService) { + super(); + this.service = sampleService; + } + + public SampleOidEntity create(Request request, Response response) { + SampleOidEntity entity = request.getBodyAs(SampleOidEntity.class, "Resource details not provided"); + SampleOidEntity saved = service.create(entity); + + // Construct the response for create... + response.setResponseCreated(); + + // Include the Location header... + String locationPattern = request.getNamedUrl(HttpMethod.GET, Constants.Routes.SINGLE_OID_SAMPLE); + response.addLocationHeader(LOCATION_BUILDER.build(locationPattern, new DefaultTokenResolver())); + + // Return the newly-created resource... + return saved; + } + + public SampleOidEntity read(Request request, Response response) { + String id = request.getHeader(Constants.Url.SAMPLE_ID, "No resource ID supplied"); + SampleOidEntity entity = service.read(Identifiers.MONGOID.parse(id)); + + return entity; + } + + public List readAll(Request request, Response response) { + QueryFilter filter = QueryFilters.parseFrom(request); + QueryOrder order = QueryOrders.parseFrom(request); + QueryRange range = QueryRanges.parseFrom(request, 20); + List entities = service.readAll(filter, range, order); + long count = service.count(filter); + response.setCollectionResponse(range, entities.size(), count); + return entities; + } + + public void update(Request request, Response response) { + String id = request.getHeader(Constants.Url.SAMPLE_ID, "No resource ID supplied"); + SampleOidEntity entity = request.getBodyAs(SampleOidEntity.class, "Resource details not provided"); + entity.setId(Identifiers.MONGOID.parse(id)); + service.update(entity); + response.setResponseNoContent(); + } + + public void delete(Request request, Response response) { + String id = request.getHeader(Constants.Url.SAMPLE_ID, "No resource ID supplied"); + service.delete(Identifiers.MONGOID.parse(id)); + response.setResponseNoContent(); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/objectid/SampleOidEntityRepository.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/objectid/SampleOidEntityRepository.java new file mode 100644 index 0000000000..d003e04254 --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/objectid/SampleOidEntityRepository.java @@ -0,0 +1,12 @@ +package com.baeldung.restexpress.objectid; + +import com.mongodb.MongoClient; +import com.strategicgains.repoexpress.mongodb.MongodbEntityRepository; + +public class SampleOidEntityRepository + extends MongodbEntityRepository { + @SuppressWarnings("unchecked") + public SampleOidEntityRepository(MongoClient mongo, String dbName) { + super(mongo, dbName, SampleOidEntity.class); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/objectid/SampleOidEntityService.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/objectid/SampleOidEntityService.java new file mode 100644 index 0000000000..076fa57e6b --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/objectid/SampleOidEntityService.java @@ -0,0 +1,48 @@ +package com.baeldung.restexpress.objectid; + +import com.strategicgains.repoexpress.domain.Identifier; +import com.strategicgains.syntaxe.ValidationEngine; +import org.restexpress.common.query.QueryFilter; +import org.restexpress.common.query.QueryOrder; +import org.restexpress.common.query.QueryRange; + +import java.util.List; + +/** + * This is the 'service' or 'business logic' layer, where business logic, syntactic and semantic + * domain validation occurs, along with calls to the persistence layer. + */ +public class SampleOidEntityService { + private SampleOidEntityRepository samples; + + public SampleOidEntityService(SampleOidEntityRepository samplesRepository) { + super(); + this.samples = samplesRepository; + } + + public SampleOidEntity create(SampleOidEntity entity) { + ValidationEngine.validateAndThrow(entity); + return samples.create(entity); + } + + public SampleOidEntity read(Identifier id) { + return samples.read(id); + } + + public void update(SampleOidEntity entity) { + ValidationEngine.validateAndThrow(entity); + samples.update(entity); + } + + public void delete(Identifier id) { + samples.delete(id); + } + + public List readAll(QueryFilter filter, QueryRange range, QueryOrder order) { + return samples.readAll(filter, range, order); + } + + public long count(QueryFilter filter) { + return samples.count(filter); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/JsonSerializationProcessor.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/JsonSerializationProcessor.java new file mode 100644 index 0000000000..e9487878f0 --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/JsonSerializationProcessor.java @@ -0,0 +1,35 @@ +package com.baeldung.restexpress.serialization; + +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.strategicgains.hyperexpress.domain.hal.HalResource; +import com.strategicgains.hyperexpress.serialization.jackson.HalResourceDeserializer; +import com.strategicgains.hyperexpress.serialization.jackson.HalResourceSerializer; +import org.bson.types.ObjectId; +import org.restexpress.ContentType; +import org.restexpress.serialization.json.JacksonJsonProcessor; + +import java.util.UUID; + +public class JsonSerializationProcessor + extends JacksonJsonProcessor { + public JsonSerializationProcessor() { + super(); + addSupportedMediaTypes(ContentType.HAL_JSON); + } + + @Override + protected void initializeModule(SimpleModule module) { + super.initializeModule(module); + // For UUID as entity identifiers... + module.addDeserializer(UUID.class, new UuidDeserializer()); + module.addSerializer(UUID.class, new UuidSerializer()); + + // For MongoDB ObjectId as entity identifiers... + module.addDeserializer(ObjectId.class, new ObjectIdDeserializer()); + module.addSerializer(ObjectId.class, new ObjectIdSerializer()); + + // Support HalResource (de)serialization. + module.addDeserializer(HalResource.class, new HalResourceDeserializer()); + module.addSerializer(HalResource.class, new HalResourceSerializer()); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/ObjectIdDeserializer.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/ObjectIdDeserializer.java new file mode 100644 index 0000000000..84c1aca83b --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/ObjectIdDeserializer.java @@ -0,0 +1,19 @@ +package com.baeldung.restexpress.serialization; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.strategicgains.repoexpress.mongodb.Identifiers; +import org.bson.types.ObjectId; + +import java.io.IOException; + +public class ObjectIdDeserializer + extends JsonDeserializer { + @Override + public ObjectId deserialize(JsonParser json, DeserializationContext context) + throws IOException, JsonProcessingException { + return (ObjectId) Identifiers.MONGOID.parse(json.getText()).primaryKey(); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/ObjectIdSerializer.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/ObjectIdSerializer.java new file mode 100644 index 0000000000..8c3e63b7f8 --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/ObjectIdSerializer.java @@ -0,0 +1,18 @@ +package com.baeldung.restexpress.serialization; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import org.bson.types.ObjectId; + +import java.io.IOException; + +public class ObjectIdSerializer + extends JsonSerializer { + @Override + public void serialize(ObjectId objectId, JsonGenerator json, SerializerProvider provider) + throws IOException, JsonProcessingException { + json.writeString(objectId.toString()); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/SerializationProvider.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/SerializationProvider.java new file mode 100644 index 0000000000..cb619be84f --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/SerializationProvider.java @@ -0,0 +1,29 @@ +package com.baeldung.restexpress.serialization; + +import org.restexpress.response.ErrorResponseWrapper; +import org.restexpress.response.ResponseWrapper; +import org.restexpress.serialization.AbstractSerializationProvider; +import org.restexpress.serialization.SerializationProcessor; + +public class SerializationProvider + extends AbstractSerializationProvider { + // SECTION: CONSTANTS + + private static final SerializationProcessor JSON_SERIALIZER = new JsonSerializationProcessor(); + private static final SerializationProcessor XML_SERIALIZER = new XmlSerializationProcessor(); + private static final ResponseWrapper RESPONSE_WRAPPER = new ErrorResponseWrapper(); + + public SerializationProvider() { + super(); + add(JSON_SERIALIZER, RESPONSE_WRAPPER, true); + add(XML_SERIALIZER, RESPONSE_WRAPPER); + } + + public static SerializationProcessor json() { + return JSON_SERIALIZER; + } + + public static SerializationProcessor xml() { + return XML_SERIALIZER; + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/UuidDeserializer.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/UuidDeserializer.java new file mode 100644 index 0000000000..ce676edff5 --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/UuidDeserializer.java @@ -0,0 +1,19 @@ +package com.baeldung.restexpress.serialization; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.strategicgains.repoexpress.util.UuidConverter; + +import java.io.IOException; +import java.util.UUID; + +public class UuidDeserializer + extends JsonDeserializer { + @Override + public UUID deserialize(JsonParser json, DeserializationContext context) + throws IOException, JsonProcessingException { + return UuidConverter.parse(json.getText()); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/UuidFormatter.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/UuidFormatter.java new file mode 100644 index 0000000000..c337dbeafd --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/UuidFormatter.java @@ -0,0 +1,14 @@ +package com.baeldung.restexpress.serialization; + +import com.strategicgains.hyperexpress.annotation.TokenFormatter; +import com.strategicgains.repoexpress.util.UuidConverter; + +import java.util.UUID; + +public class UuidFormatter + implements TokenFormatter { + @Override + public String format(Object field) { + return UuidConverter.format((UUID) field); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/UuidSerializer.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/UuidSerializer.java new file mode 100644 index 0000000000..4349e6d144 --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/UuidSerializer.java @@ -0,0 +1,19 @@ +package com.baeldung.restexpress.serialization; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.strategicgains.repoexpress.util.UuidConverter; + +import java.io.IOException; +import java.util.UUID; + +public class UuidSerializer + extends JsonSerializer { + @Override + public void serialize(UUID objectId, JsonGenerator json, SerializerProvider provider) + throws IOException, JsonProcessingException { + json.writeString(UuidConverter.format(objectId)); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/XmlSerializationProcessor.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/XmlSerializationProcessor.java new file mode 100644 index 0000000000..e1bdd229c1 --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/XmlSerializationProcessor.java @@ -0,0 +1,18 @@ +package com.baeldung.restexpress.serialization; + +import com.baeldung.restexpress.uuid.SampleUuidEntity; +import org.restexpress.serialization.xml.XstreamXmlProcessor; + +public class XmlSerializationProcessor + extends XstreamXmlProcessor { + public XmlSerializationProcessor() { + super(); + alias("sample", SampleUuidEntity.class); +// alias("element_name", Element.class); +// alias("element_name", Element.class); +// alias("element_name", Element.class); +// alias("element_name", Element.class); + registerConverter(new XstreamUuidConverter()); + registerConverter(new XstreamOidConverter()); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/XstreamOidConverter.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/XstreamOidConverter.java new file mode 100644 index 0000000000..5eb53814fb --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/XstreamOidConverter.java @@ -0,0 +1,28 @@ +package com.baeldung.restexpress.serialization; + +import com.strategicgains.repoexpress.mongodb.Identifiers; +import com.thoughtworks.xstream.converters.SingleValueConverter; +import org.bson.types.ObjectId; + +/** + * @author toddf + * @since Feb 16, 2011 + */ +public class XstreamOidConverter + implements SingleValueConverter { + @SuppressWarnings("rawtypes") + @Override + public boolean canConvert(Class aClass) { + return ObjectId.class.isAssignableFrom(aClass); + } + + @Override + public Object fromString(String value) { + return (ObjectId) Identifiers.MONGOID.parse(value).primaryKey(); + } + + @Override + public String toString(Object objectId) { + return ((ObjectId) objectId).toString(); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/XstreamUuidConverter.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/XstreamUuidConverter.java new file mode 100644 index 0000000000..82f34bc36d --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/serialization/XstreamUuidConverter.java @@ -0,0 +1,29 @@ +package com.baeldung.restexpress.serialization; + +import com.strategicgains.repoexpress.util.UuidConverter; +import com.thoughtworks.xstream.converters.SingleValueConverter; + +import java.util.UUID; + +/** + * @author toddf + * @since Feb 16, 2011 + */ +public class XstreamUuidConverter + implements SingleValueConverter { + @SuppressWarnings("rawtypes") + @Override + public boolean canConvert(Class aClass) { + return UUID.class.isAssignableFrom(aClass); + } + + @Override + public Object fromString(String value) { + return UuidConverter.parse(value); + } + + @Override + public String toString(Object objectId) { + return UuidConverter.format((UUID) objectId); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/uuid/SampleUuidEntity.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/uuid/SampleUuidEntity.java new file mode 100644 index 0000000000..724e681700 --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/uuid/SampleUuidEntity.java @@ -0,0 +1,23 @@ +package com.baeldung.restexpress.uuid; + +import com.baeldung.restexpress.Constants; +import com.baeldung.restexpress.serialization.UuidFormatter; +import com.strategicgains.hyperexpress.annotation.BindToken; +import com.strategicgains.hyperexpress.annotation.TokenBindings; +import com.strategicgains.repoexpress.mongodb.AbstractUuidMongodbEntity; +import org.restexpress.plugin.hyperexpress.Linkable; + +/** + * This is a sample entity identified by a UUID (instead of a MongoDB ObjectID). + * It also contains createdAt and updatedAt properties that are automatically maintained + * by the persistence layer (SampleUuidEntityRepository). + */ +@TokenBindings({ + @BindToken(value = Constants.Url.SAMPLE_ID, field = "id", formatter = UuidFormatter.class) +}) +public class SampleUuidEntity + extends AbstractUuidMongodbEntity + implements Linkable { + public SampleUuidEntity() { + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/uuid/SampleUuidEntityController.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/uuid/SampleUuidEntityController.java new file mode 100644 index 0000000000..addd94ee3b --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/uuid/SampleUuidEntityController.java @@ -0,0 +1,79 @@ +package com.baeldung.restexpress.uuid; + +import com.baeldung.restexpress.Constants; +import com.strategicgains.hyperexpress.builder.DefaultTokenResolver; +import com.strategicgains.hyperexpress.builder.DefaultUrlBuilder; +import com.strategicgains.hyperexpress.builder.UrlBuilder; +import com.strategicgains.repoexpress.adapter.Identifiers; +import io.netty.handler.codec.http.HttpMethod; +import org.restexpress.Request; +import org.restexpress.Response; +import org.restexpress.common.query.QueryFilter; +import org.restexpress.common.query.QueryOrder; +import org.restexpress.common.query.QueryRange; +import org.restexpress.query.QueryFilters; +import org.restexpress.query.QueryOrders; +import org.restexpress.query.QueryRanges; + +import java.util.List; + +/** + * This is the 'controller' layer, where HTTP details are converted to domain concepts and passed to the service layer. + * Then service layer response information is enhanced with HTTP details, if applicable, for the response. + *

+ * This controller demonstrates how to process a MongoDB entity that is identified by a UUID. + */ +public class SampleUuidEntityController { + private static final UrlBuilder LOCATION_BUILDER = new DefaultUrlBuilder(); + private SampleUuidEntityService service; + + public SampleUuidEntityController(SampleUuidEntityService sampleService) { + super(); + this.service = sampleService; + } + + public SampleUuidEntity create(Request request, Response response) { + SampleUuidEntity entity = request.getBodyAs(SampleUuidEntity.class, "Resource details not provided"); + SampleUuidEntity saved = service.create(entity); + + // Construct the response for create... + response.setResponseCreated(); + + // Include the Location header... + String locationPattern = request.getNamedUrl(HttpMethod.GET, Constants.Routes.SINGLE_UUID_SAMPLE); + response.addLocationHeader(LOCATION_BUILDER.build(locationPattern, new DefaultTokenResolver())); + + // Return the newly-created resource... + return saved; + } + + public SampleUuidEntity read(Request request, Response response) { + String id = request.getHeader(Constants.Url.SAMPLE_ID, "No resource ID supplied"); + SampleUuidEntity entity = service.read(Identifiers.UUID.parse(id)); + return entity; + } + + public List readAll(Request request, Response response) { + QueryFilter filter = QueryFilters.parseFrom(request); + QueryOrder order = QueryOrders.parseFrom(request); + QueryRange range = QueryRanges.parseFrom(request, 20); + List entities = service.readAll(filter, range, order); + long count = service.count(filter); + response.setCollectionResponse(range, entities.size(), count); + return entities; + } + + public void update(Request request, Response response) { + String id = request.getHeader(Constants.Url.SAMPLE_ID, "No resource ID supplied"); + SampleUuidEntity entity = request.getBodyAs(SampleUuidEntity.class, "Resource details not provided"); + entity.setId(Identifiers.UUID.parse(id)); + service.update(entity); + response.setResponseNoContent(); + } + + public void delete(Request request, Response response) { + String id = request.getHeader(Constants.Url.SAMPLE_ID, "No resource ID supplied"); + service.delete(Identifiers.UUID.parse(id)); + response.setResponseNoContent(); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/uuid/SampleUuidEntityRepository.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/uuid/SampleUuidEntityRepository.java new file mode 100644 index 0000000000..0b5eef1ea6 --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/uuid/SampleUuidEntityRepository.java @@ -0,0 +1,12 @@ +package com.baeldung.restexpress.uuid; + +import com.mongodb.MongoClient; +import com.strategicgains.repoexpress.mongodb.MongodbUuidEntityRepository; + +public class SampleUuidEntityRepository + extends MongodbUuidEntityRepository { + @SuppressWarnings("unchecked") + public SampleUuidEntityRepository(MongoClient mongo, String dbName) { + super(mongo, dbName, SampleUuidEntity.class); + } +} diff --git a/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/uuid/SampleUuidEntityService.java b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/uuid/SampleUuidEntityService.java new file mode 100644 index 0000000000..57a1c18d05 --- /dev/null +++ b/microservices-modules/rest-express/src/main/java/com/baeldung/restexpress/uuid/SampleUuidEntityService.java @@ -0,0 +1,48 @@ +package com.baeldung.restexpress.uuid; + +import com.strategicgains.repoexpress.domain.Identifier; +import com.strategicgains.syntaxe.ValidationEngine; +import org.restexpress.common.query.QueryFilter; +import org.restexpress.common.query.QueryOrder; +import org.restexpress.common.query.QueryRange; + +import java.util.List; + +/** + * This is the 'service' or 'business logic' layer, where business logic, syntactic and semantic + * domain validation occurs, along with calls to the persistence layer. + */ +public class SampleUuidEntityService { + private SampleUuidEntityRepository samples; + + public SampleUuidEntityService(SampleUuidEntityRepository samplesRepository) { + super(); + this.samples = samplesRepository; + } + + public SampleUuidEntity create(SampleUuidEntity entity) { + ValidationEngine.validateAndThrow(entity); + return samples.create(entity); + } + + public SampleUuidEntity read(Identifier id) { + return samples.read(id); + } + + public void update(SampleUuidEntity entity) { + ValidationEngine.validateAndThrow(entity); + samples.update(entity); + } + + public void delete(Identifier id) { + samples.delete(id); + } + + public List readAll(QueryFilter filter, QueryRange range, QueryOrder order) { + return samples.readAll(filter, range, order); + } + + public long count(QueryFilter filter) { + return samples.count(filter); + } +} diff --git a/microservices-modules/rest-express/src/main/resources/config/dev/environment.properties b/microservices-modules/rest-express/src/main/resources/config/dev/environment.properties new file mode 100644 index 0000000000..b81f9a84cb --- /dev/null +++ b/microservices-modules/rest-express/src/main/resources/config/dev/environment.properties @@ -0,0 +1,22 @@ +# Default is 8081 +port = 8081 + +# The size of the executor thread pool (that can handle blocking back-end processing). +executor.threadPool.size = 20 + +# A MongoDB URI/Connection string +# see: http://docs.mongodb.org/manual/reference/connection-string/ +mongodb.uri = mongodb://localhost:27017/scaffolding_mongodb + +# The base URL, used as a prefix for links returned in data +# default is http://localhost: +#base.url = http://localhost:8081 + +#Configuration for the MetricsPlugin/Graphite +metrics.isEnabled = true +#metrics.machineName = +metrics.prefix = web1.example.com +metrics.graphite.isEnabled = false +metrics.graphite.host = graphite.example.com +metrics.graphite.port = 2003 +metrics.graphite.publishSeconds = 60 \ No newline at end of file diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/pom.xml b/persistence-modules/spring-boot-persistence-mongodb-3/pom.xml index efb988d0a0..b9a47aa703 100644 --- a/persistence-modules/spring-boot-persistence-mongodb-3/pom.xml +++ b/persistence-modules/spring-boot-persistence-mongodb-3/pom.xml @@ -24,6 +24,11 @@ org.springframework.boot spring-boot-starter-data-mongodb + + org.mongodb + mongodb-crypt + ${mongodb-crypt.version} + de.flapdoodle.embed de.flapdoodle.embed.mongo @@ -31,4 +36,8 @@ + + 1.6.1 + + diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/MongoDbCsfleApplication.java b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/MongoDbCsfleApplication.java new file mode 100644 index 0000000000..fac296a208 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/MongoDbCsfleApplication.java @@ -0,0 +1,12 @@ +package com.baeldung.boot.csfle; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class MongoDbCsfleApplication { + + public static void main(String... args) { + SpringApplication.run(MongoDbCsfleApplication.class, args); + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/EncryptionConfig.java b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/EncryptionConfig.java new file mode 100644 index 0000000000..1495822bc0 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/EncryptionConfig.java @@ -0,0 +1,59 @@ +package com.baeldung.boot.csfle.config; + +import org.bson.BsonBinary; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +import com.mongodb.client.vault.ClientEncryption; + +@Configuration +public class EncryptionConfig { + + @Value("${com.baeldung.csfle.master-key-path}") + private String masterKeyPath; + + @Value("${com.baeldung.csfle.key-vault.namespace}") + private String keyVaultNamespace; + + @Value("${com.baeldung.csfle.key-vault.alias}") + private String keyVaultAlias; + + @Value("${com.baeldung.csfle.auto-decryption:false}") + private Boolean autoDecryption; + + private ClientEncryption encryption; + + private BsonBinary dataKeyId; + + public void setEncryption(ClientEncryption encryption) { + this.encryption = encryption; + } + + public ClientEncryption getEncryption() { + return encryption; + } + + public void setDataKeyId(BsonBinary dataKeyId) { + this.dataKeyId = dataKeyId; + } + + public BsonBinary getDataKeyId() { + return dataKeyId; + } + + public String getKeyVaultNamespace() { + return keyVaultNamespace; + } + + public String getKeyVaultAlias() { + return keyVaultAlias; + } + + public String getMasterKeyPath() { + return masterKeyPath; + } + + public Boolean getAutoDecryption() { + return autoDecryption; + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/LocalKmsUtils.java b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/LocalKmsUtils.java new file mode 100644 index 0000000000..e5daf781f0 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/LocalKmsUtils.java @@ -0,0 +1,55 @@ +package com.baeldung.boot.csfle.config; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.security.SecureRandom; +import java.util.HashMap; +import java.util.Map; + +public class LocalKmsUtils { + + private static final int KEY_SIZE = 96; + + private LocalKmsUtils() { + } + + public static byte[] createMasterKey(String path) throws FileNotFoundException, IOException { + byte[] masterKey = new byte[KEY_SIZE]; + new SecureRandom().nextBytes(masterKey); + + try (FileOutputStream stream = new FileOutputStream(path)) { + stream.write(masterKey); + } + + return masterKey; + } + + public static byte[] readMasterKey(String path) throws FileNotFoundException, IOException { + byte[] masterKey = new byte[KEY_SIZE]; + + try (FileInputStream stream = new FileInputStream(path)) { + stream.read(masterKey, 0, KEY_SIZE); + } + + return masterKey; + } + + public static Map> providersMap(String masterKeyPath) throws FileNotFoundException, IOException { + if (masterKeyPath == null) + throw new IllegalArgumentException("master key path cannot be null"); + + File masterKeyFile = new File(masterKeyPath); + byte[] masterKey = masterKeyFile.isFile() + ? readMasterKey(masterKeyPath) + : createMasterKey(masterKeyPath); + + Map masterKeyMap = new HashMap<>(); + masterKeyMap.put("key", masterKey); + Map> providersMap = new HashMap<>(); + providersMap.put("local", masterKeyMap); + return providersMap; + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/MongoClientConfig.java b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/MongoClientConfig.java new file mode 100644 index 0000000000..29076f4e61 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/MongoClientConfig.java @@ -0,0 +1,122 @@ +package com.baeldung.boot.csfle.config; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Arrays; +import java.util.Map; + +import org.bson.BsonBinary; +import org.bson.BsonDocument; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration; +import org.springframework.data.mongodb.core.convert.MongoCustomConversions; + +import com.baeldung.boot.csfle.config.converter.IntegerConverter; +import com.baeldung.boot.csfle.config.converter.StringConverter; +import com.mongodb.AutoEncryptionSettings; +import com.mongodb.ClientEncryptionSettings; +import com.mongodb.ConnectionString; +import com.mongodb.MongoClientSettings; +import com.mongodb.MongoClientSettings.Builder; +import com.mongodb.MongoNamespace; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.IndexOptions; +import com.mongodb.client.model.Indexes; +import com.mongodb.client.model.vault.DataKeyOptions; +import com.mongodb.client.vault.ClientEncryption; +import com.mongodb.client.vault.ClientEncryptions; + +@Configuration +public class MongoClientConfig extends AbstractMongoClientConfiguration { + + @Value("${spring.data.mongodb.uri}") + private String uri; + + @Value("${spring.data.mongodb.database}") + private String db; + + @Autowired + private EncryptionConfig encryptionConfig; + + @Override + protected String getDatabaseName() { + return db; + } + + @Override + public MongoCustomConversions customConversions() { + return new MongoCustomConversions(Arrays.asList(new StringConverter(encryptionConfig), new IntegerConverter(encryptionConfig))); + } + + @Override + public MongoClient mongoClient() { + MongoClient client; + try { + client = MongoClients.create(clientSettings()); + + ClientEncryption encryption = createClientEncryption(); + encryptionConfig.setDataKeyId(createOrRetrieveDataKey(client, encryption)); + + return client; + } catch (IOException e) { + throw new IllegalStateException("unable to create client", e); + } + } + + private BsonBinary createOrRetrieveDataKey(MongoClient client, ClientEncryption encryption) { + MongoNamespace namespace = new MongoNamespace(encryptionConfig.getKeyVaultNamespace()); + MongoCollection keyVault = client.getDatabase(namespace.getDatabaseName()) + .getCollection(namespace.getCollectionName()); + + Bson query = Filters.in("keyAltNames", encryptionConfig.getKeyVaultAlias()); + BsonDocument key = keyVault.withDocumentClass(BsonDocument.class) + .find(query) + .first(); + + if (key == null) { + keyVault.createIndex(Indexes.ascending("keyAltNames"), new IndexOptions().unique(true) + .partialFilterExpression(Filters.exists("keyAltNames"))); + + DataKeyOptions options = new DataKeyOptions(); + options.keyAltNames(Arrays.asList(encryptionConfig.getKeyVaultAlias())); + return encryption.createDataKey("local", options); + } else { + return (BsonBinary) key.get("_id"); + } + } + + private ClientEncryption createClientEncryption() throws FileNotFoundException, IOException { + Map> kmsProviders = LocalKmsUtils.providersMap(encryptionConfig.getMasterKeyPath()); + + ClientEncryptionSettings encryptionSettings = ClientEncryptionSettings.builder() + .keyVaultMongoClientSettings(clientSettings()) + .keyVaultNamespace(encryptionConfig.getKeyVaultNamespace()) + .kmsProviders(kmsProviders) + .build(); + + encryptionConfig.setEncryption(ClientEncryptions.create(encryptionSettings)); + return encryptionConfig.getEncryption(); + } + + private MongoClientSettings clientSettings() throws FileNotFoundException, IOException { + Builder settings = MongoClientSettings.builder() + .applyConnectionString(new ConnectionString(uri)); + + if (encryptionConfig.getAutoDecryption()) { + settings.autoEncryptionSettings(AutoEncryptionSettings.builder() + .keyVaultNamespace(encryptionConfig.getKeyVaultNamespace()) + .kmsProviders(LocalKmsUtils.providersMap(encryptionConfig.getMasterKeyPath())) + .bypassAutoEncryption(true) + .build()); + } + + return settings.build(); + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/converter/IntegerConverter.java b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/converter/IntegerConverter.java new file mode 100644 index 0000000000..020513ebff --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/converter/IntegerConverter.java @@ -0,0 +1,27 @@ +package com.baeldung.boot.csfle.config.converter; + +import org.bson.BsonBinary; +import org.bson.BsonValue; +import org.bson.types.Binary; +import org.springframework.core.convert.converter.Converter; + +import com.baeldung.boot.csfle.config.EncryptionConfig; + +public class IntegerConverter implements Converter { + + private EncryptionConfig encryptionConfig; + + public IntegerConverter(EncryptionConfig config) { + this.encryptionConfig = config; + } + + @Override + public Integer convert(Binary source) { + BsonBinary bin = new BsonBinary(source.getType(), source.getData()); + BsonValue value = encryptionConfig.getEncryption() + .decrypt(bin); + + return value.asInt32() + .getValue(); + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/converter/StringConverter.java b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/converter/StringConverter.java new file mode 100644 index 0000000000..7f8193ce43 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/config/converter/StringConverter.java @@ -0,0 +1,27 @@ +package com.baeldung.boot.csfle.config.converter; + +import org.bson.BsonBinary; +import org.bson.BsonValue; +import org.bson.types.Binary; +import org.springframework.core.convert.converter.Converter; + +import com.baeldung.boot.csfle.config.EncryptionConfig; + +public class StringConverter implements Converter { + + private EncryptionConfig encryptionConfig; + + public StringConverter(EncryptionConfig config) { + this.encryptionConfig = config; + } + + @Override + public String convert(Binary source) { + BsonBinary bin = new BsonBinary(source.getType(), source.getData()); + BsonValue value = encryptionConfig.getEncryption() + .decrypt(bin); + + return value.asString() + .getValue(); + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/data/Citizen.java b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/data/Citizen.java new file mode 100644 index 0000000000..9d6496a17b --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/data/Citizen.java @@ -0,0 +1,47 @@ +package com.baeldung.boot.csfle.data; + +import org.springframework.data.mongodb.core.mapping.Document; + +@Document("citizens") +public class Citizen { + + private String name; + private String email; + private Integer birthYear; + + public Citizen() { + } + + public Citizen(EncryptedCitizen encryptedCitizen) { + this.name = encryptedCitizen.getName(); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public Integer getBirthYear() { + return birthYear; + } + + public void setBirthYear(Integer birthYear) { + this.birthYear = birthYear; + } + + @Override + public String toString() { + return "Citizen [name=" + name + ", email=" + email + ", birthYear=" + birthYear + "]"; + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/data/EncryptedCitizen.java b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/data/EncryptedCitizen.java new file mode 100644 index 0000000000..01c9245fbf --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/data/EncryptedCitizen.java @@ -0,0 +1,48 @@ +package com.baeldung.boot.csfle.data; + +import org.bson.BsonBinary; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document("citizens") +public class EncryptedCitizen { + + private String name; + private BsonBinary email; + private BsonBinary birthYear; + + public EncryptedCitizen() { + } + + public EncryptedCitizen(Citizen citizen) { + this.name = citizen.getName(); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public BsonBinary getEmail() { + return email; + } + + public void setEmail(BsonBinary email) { + this.email = email; + } + + public BsonBinary getBirthYear() { + return birthYear; + } + + public void setBirthYear(BsonBinary birthYear) { + this.birthYear = birthYear; + } + + @Override + public String toString() { + return "Citizen [name=" + name + ", email=" + email + ", birthYear=" + birthYear + "]"; + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/service/CitizenService.java b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/service/CitizenService.java new file mode 100644 index 0000000000..9cc0753289 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/service/CitizenService.java @@ -0,0 +1,63 @@ +package com.baeldung.boot.csfle.service; + +import java.util.List; + +import org.bson.BsonBinary; +import org.bson.BsonInt32; +import org.bson.BsonString; +import org.bson.BsonValue; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.stereotype.Service; + +import com.baeldung.boot.csfle.config.EncryptionConfig; +import com.baeldung.boot.csfle.data.Citizen; +import com.baeldung.boot.csfle.data.EncryptedCitizen; +import com.mongodb.client.model.vault.EncryptOptions; + +@Service +public class CitizenService { + + public static final String DETERMINISTIC_ALGORITHM = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"; + public static final String RANDOM_ALGORITHM = "AEAD_AES_256_CBC_HMAC_SHA_512-Random"; + + @Autowired + private MongoTemplate mongo; + + @Autowired + private EncryptionConfig encryptionConfig; + + public EncryptedCitizen save(Citizen citizen) { + EncryptedCitizen encryptedCitizen = new EncryptedCitizen(citizen); + encryptedCitizen.setEmail(encrypt(citizen.getEmail(), DETERMINISTIC_ALGORITHM)); + encryptedCitizen.setBirthYear(encrypt(citizen.getBirthYear(), RANDOM_ALGORITHM)); + + return mongo.save(encryptedCitizen); + } + + public List findAll() { + return mongo.findAll(Citizen.class); + } + + public Citizen findByEmail(String email) { + Query byEmail = new Query(Criteria.where("email") + .is(encrypt(email, DETERMINISTIC_ALGORITHM))); + return mongo.findOne(byEmail, Citizen.class); + } + + public BsonBinary encrypt(Object value, String algorithm) { + if (value == null) + return null; + + BsonValue bsonValue = value instanceof Integer + ? new BsonInt32((Integer) value) + : new BsonString(value.toString()); + + EncryptOptions options = new EncryptOptions(algorithm); + options.keyId(encryptionConfig.getDataKeyId()); + return encryptionConfig.getEncryption() + .encrypt(bsonValue, options); + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/web/CitizenController.java b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/web/CitizenController.java new file mode 100644 index 0000000000..d17435fb5e --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/csfle/web/CitizenController.java @@ -0,0 +1,38 @@ +package com.baeldung.boot.csfle.web; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.baeldung.boot.csfle.data.Citizen; +import com.baeldung.boot.csfle.data.EncryptedCitizen; +import com.baeldung.boot.csfle.service.CitizenService; + +@RestController +@RequestMapping("/citizen") +public class CitizenController { + + @Autowired + private CitizenService service; + + @GetMapping + public List get() { + return service.findAll(); + } + + @GetMapping("by") + public Citizen getBy(@RequestParam String email) { + return service.findByEmail(email); + } + + @PostMapping + public EncryptedCitizen post(@RequestBody Citizen citizen) { + return service.save(citizen); + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/test/java/com/baeldung/boot/csfle/CitizenServiceLiveTest.java b/persistence-modules/spring-boot-persistence-mongodb-3/src/test/java/com/baeldung/boot/csfle/CitizenServiceLiveTest.java new file mode 100644 index 0000000000..5d0a931bb9 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-3/src/test/java/com/baeldung/boot/csfle/CitizenServiceLiveTest.java @@ -0,0 +1,73 @@ +package com.baeldung.boot.csfle; + +import static org.junit.jupiter.api.Assertions.*; + +import org.bson.BsonBinary; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; + +import com.baeldung.boot.csfle.data.Citizen; +import com.baeldung.boot.csfle.data.EncryptedCitizen; +import com.baeldung.boot.csfle.service.CitizenService; + +@DirtiesContext +@RunWith(SpringRunner.class) +@TestPropertySource("/embedded.properties") +@SpringBootTest(classes = MongoDbCsfleApplication.class) +public class CitizenServiceLiveTest { + + @Autowired + private MongoTemplate mongo; + + @Autowired + private CitizenService service; + + @Test + public void givenCitizen_whenEncryptingEmail_thenEncryptedCitizenEmailMatches() { + final Citizen citizen = new Citizen(); + citizen.setName("Foo"); + citizen.setEmail("foo@citizen.com"); + + BsonBinary encryptedEmail = service.encrypt(citizen.getEmail(), CitizenService.DETERMINISTIC_ALGORITHM); + + EncryptedCitizen saved = service.save(citizen); + assertEquals(encryptedEmail, saved.getEmail()); + } + + @Test + public void givenRandomEncryptedField_whenFilteringByField_thenDocumentNotFound() { + Citizen john = new Citizen(); + john.setName("Jane Doe"); + john.setEmail("jane.doe@citizen.com"); + john.setBirthYear(1852); + + service.save(john); + + Query byBirthYear = new Query(Criteria.where("birthYear") + .is(service.encrypt(john.getBirthYear(), CitizenService.RANDOM_ALGORITHM))); + Citizen result = mongo.findOne(byBirthYear, Citizen.class); + + assertNull(result); + } + + @Test + public void givenDeterministicallyEncryptedField_whenFilteringByField_thenDocumentFound() { + Citizen jane = new Citizen(); + jane.setName("Jane Doe"); + jane.setEmail("jane.doe@citizen.com"); + jane.setBirthYear(1952); + + service.save(jane); + Citizen result = service.findByEmail(jane.getEmail()); + + assertNotNull(result); + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/test/resources/embedded.properties b/persistence-modules/spring-boot-persistence-mongodb-3/src/test/resources/embedded.properties index a5b5fb9804..5325354e55 100644 --- a/persistence-modules/spring-boot-persistence-mongodb-3/src/test/resources/embedded.properties +++ b/persistence-modules/spring-boot-persistence-mongodb-3/src/test/resources/embedded.properties @@ -1 +1,10 @@ -spring.mongodb.embedded.version=4.4.9 \ No newline at end of file +spring.mongodb.embedded.version=4.4.9 + +spring.data.mongodb.uri=changeit +spring.data.mongodb.database=changeit + +com.baeldung.csfle.kms-provider=local +com.baeldung.csfle.key-vault.namespace=encryption._keyVault +com.baeldung.csfle.key-vault.alias=master.key +com.baeldung.csfle.master-key-path=/tmp/master.key +com.baeldung.csfle.auto-decryption=false diff --git a/persistence-modules/spring-data-jpa-query-3/README.md b/persistence-modules/spring-data-jpa-query-3/README.md index 268bce96ca..f49bb19217 100644 --- a/persistence-modules/spring-data-jpa-query-3/README.md +++ b/persistence-modules/spring-data-jpa-query-3/README.md @@ -6,6 +6,7 @@ This module contains articles about querying data using Spring Data JPA. - [Query Entities by Dates and Times with Spring Data JPA](https://www.baeldung.com/spring-data-jpa-query-by-date) - [JPA and Hibernate – Criteria vs. JPQL vs. HQL Query](https://www.baeldung.com/jpql-hql-criteria-query) - [Joining Tables With Spring Data JPA Specifications](https://www.baeldung.com/spring-jpa-joining-tables) +- [NonUniqueResultException in Spring Data JPA](https://www.baeldung.com/spring-jpa-non-unique-result-exception) - More articles: [[<-- prev]](../spring-data-jpa-query-2) ### Eclipse Config diff --git a/persistence-modules/spring-data-jpa-repo-2/README.md b/persistence-modules/spring-data-jpa-repo-2/README.md index 6403510e6f..f51e7135ae 100644 --- a/persistence-modules/spring-data-jpa-repo-2/README.md +++ b/persistence-modules/spring-data-jpa-repo-2/README.md @@ -6,4 +6,5 @@ - [Performance Difference Between save() and saveAll() in Spring Data](https://www.baeldung.com/spring-data-save-saveall) - [LIKE Queries in Spring JPA Repositories](https://www.baeldung.com/spring-jpa-like-queries) - [How to Access EntityManager with Spring Data](https://www.baeldung.com/spring-data-entitymanager) +- [Difference Between JPA and Spring Data JPA](https://www.baeldung.com/spring-data-jpa-vs-jpa) - More articles: [[<-- prev]](../spring-data-jpa-repo) diff --git a/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/search/Student.java b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/search/Student.java new file mode 100644 index 0000000000..1d6eaa3b33 --- /dev/null +++ b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/search/Student.java @@ -0,0 +1,75 @@ +package com.baeldung.spring.data.persistence.search; + +import java.util.Objects; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class Student { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private long id; + + private String name; + private int score; + + public Student() { + } + + public Student(String name, int score) { + + this.name = name; + this.score = score; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getScore() { + return score; + } + + public void setScore(int score) { + this.score = score; + } + + @Override + public String toString() { + return "Student [id=" + id + ", name=" + name + ", score=" + score + "]"; + } + + @Override + public int hashCode() { + return Objects.hash(id, name, score); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Student other = (Student) obj; + return id == other.id && Objects.equals(name, other.name) && score == other.score; + } + +} \ No newline at end of file diff --git a/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/search/StudentApplication.java b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/search/StudentApplication.java new file mode 100644 index 0000000000..6aa895d067 --- /dev/null +++ b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/search/StudentApplication.java @@ -0,0 +1,16 @@ +package com.baeldung.spring.data.persistence.search; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class StudentApplication { + + private static final Logger log = LoggerFactory.getLogger(StudentApplication.class); + + public static void main(String[] args) { + SpringApplication.run(StudentApplication.class, args); + } +} \ No newline at end of file diff --git a/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/search/StudentRepository.java b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/search/StudentRepository.java new file mode 100644 index 0000000000..29aade5886 --- /dev/null +++ b/persistence-modules/spring-data-jpa-repo-2/src/main/java/com/baeldung/spring/data/persistence/search/StudentRepository.java @@ -0,0 +1,31 @@ +package com.baeldung.spring.data.persistence.search; + +import java.util.List; + +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface StudentRepository extends JpaRepository { + + Student findFirstByOrderByScoreDesc(); + + Student findFirstBy(Sort sort); + + Student findFirstByNameLike(String name, Sort sort); + + List findFirst3ByOrderByScoreDesc(); + + List findFirst2ByScoreBetween(int startScore, int endScore, Sort sort); + + Student findTopByOrderByScoreDesc(); + + Student findTopBy(Sort sort); + + Student findTopByNameLike(String name, Sort sort); + + List findTop3ByOrderByScoreDesc(); + + List findTop2ByScoreBetween(int startScore, int endScore, Sort sort); +} diff --git a/persistence-modules/spring-data-jpa-repo-2/src/main/resources/application.properties b/persistence-modules/spring-data-jpa-repo-2/src/main/resources/application.properties index 30cc5abbcc..3ca0cc1242 100644 --- a/persistence-modules/spring-data-jpa-repo-2/src/main/resources/application.properties +++ b/persistence-modules/spring-data-jpa-repo-2/src/main/resources/application.properties @@ -3,3 +3,4 @@ spring.datasource.username=sa spring.datasource.password=sa spring.jpa.properties.hibernate.globally_quoted_identifiers=true +logging.level.com.baeldung.spring.data.persistence.search=debug \ No newline at end of file diff --git a/persistence-modules/spring-data-jpa-repo-2/src/test/java/com/baeldung/spring/data/persistence/search/StudentApplicationUnitTest.java b/persistence-modules/spring-data-jpa-repo-2/src/test/java/com/baeldung/spring/data/persistence/search/StudentApplicationUnitTest.java new file mode 100644 index 0000000000..ea16b3597d --- /dev/null +++ b/persistence-modules/spring-data-jpa-repo-2/src/test/java/com/baeldung/spring/data/persistence/search/StudentApplicationUnitTest.java @@ -0,0 +1,100 @@ +package com.baeldung.spring.data.persistence.search; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Random; +import java.util.stream.Collectors; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Sort; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class StudentApplicationUnitTest { + + @Autowired + private StudentRepository studentRepo; + private List students; + + @Before + public void fillData() { + students = new ArrayList<>(); + int count = 10; + Random r = new Random(); + List scores = r.ints(0, 101) + .distinct() + .limit(count) + .boxed() + .collect(Collectors.toList()); + + for (int i = 0; i < count; i++) { + Integer score = scores.get(i); + Student s = new Student("Student-" + i, score); + students.add(s); + } + + studentRepo.saveAll(students); + Comparator c = Comparator.comparing(a -> a.getScore()); + c = c.reversed(); + students.sort(c); + } + + @After + public void clearData() { + studentRepo.deleteAll(); + } + + @Test + public void givenStudentScores_whenMoreThanOne_thenFindFirst() { + + Student student = studentRepo.findFirstByOrderByScoreDesc(); + Student s = students.get(0); + assertEquals(student, s); + } + + @Test + public void givenStudentScores_whenMoreThan3_thenFindFirstThree() { + + List firstThree = studentRepo.findFirst3ByOrderByScoreDesc(); + List sList = students.subList(0, 3); + assertArrayEquals(firstThree.toArray(), sList.toArray()); + } + + @Test + public void givenStudentScores_whenNameMatches_thenFindFirstStudent() { + + String matchString = "3"; + Student student = studentRepo.findFirstByNameLike("%" + matchString + "%", Sort.by("score") + .descending()); + Student s = students.stream() + .filter(a -> a.getName() + .contains(matchString)) + .findFirst() + .orElse(null); + assertEquals(student, s); + } + + @Test + public void givenStudentScores_whenBetweenRange_thenFindFirstTwoStudents() { + + List topTwoBetweenRange = studentRepo.findFirst2ByScoreBetween(50, 60, Sort.by("score") + .descending()); + List _students = students.stream() + .filter(a -> a.getScore() >= 50 && a.getScore() <= 60) + .limit(2) + .collect(Collectors.toList()); + assertArrayEquals(_students.toArray(), topTwoBetweenRange.toArray()); + } +} diff --git a/pom.xml b/pom.xml index c4e5c25d9d..0b19c6eee6 100644 --- a/pom.xml +++ b/pom.xml @@ -1133,6 +1133,7 @@ core-java-modules/core-java-collections-set core-java-modules/core-java-collections-list-4 + core-java-modules/core-java-collections-array-list core-java-modules/core-java-collections-maps-4 core-java-modules/core-java-collections-maps-5 core-java-modules/core-java-concurrency-simple diff --git a/saas-modules/sentry-servlet/README.md b/saas-modules/sentry-servlet/README.md new file mode 100644 index 0000000000..b2f03453b5 --- /dev/null +++ b/saas-modules/sentry-servlet/README.md @@ -0,0 +1,2 @@ +## Relevant Articles +- [Quick Guide to Sentry](https://www.baeldung.com/ops/java-sentry) diff --git a/spring-boot-modules/spring-boot-3-observation/README.md b/spring-boot-modules/spring-boot-3-observation/README.md new file mode 100644 index 0000000000..edfb23ce2b --- /dev/null +++ b/spring-boot-modules/spring-boot-3-observation/README.md @@ -0,0 +1,2 @@ +## Relevant Articles +- [Observability with Spring Boot 3](https://www.baeldung.com/spring-boot-3-observability) diff --git a/spring-boot-modules/spring-boot-keycloak-adapters/README.md b/spring-boot-modules/spring-boot-keycloak-adapters/README.md new file mode 100644 index 0000000000..d24d315f50 --- /dev/null +++ b/spring-boot-modules/spring-boot-keycloak-adapters/README.md @@ -0,0 +1,7 @@ +## Spring Boot Keycloak + +This module contains articles about Keycloak in Spring Boot projects. + +## Relevant articles: +- [Custom User Attributes with Keycloak](https://www.baeldung.com/keycloak-custom-user-attributes) +- [Get Keycloak User ID in Spring](https://www.baeldung.com/spring-keycloak-get-user-id) diff --git a/spring-boot-modules/spring-boot-keycloak-adapters/pom.xml b/spring-boot-modules/spring-boot-keycloak-adapters/pom.xml new file mode 100644 index 0000000000..0da8d920d1 --- /dev/null +++ b/spring-boot-modules/spring-boot-keycloak-adapters/pom.xml @@ -0,0 +1,91 @@ + + + 4.0.0 + com.baeldung.keycloak + spring-boot-keycloak-adapters + 0.0.1 + spring-boot-keycloak-adapters + jar + This is a simple application demonstrating integration between Keycloak and Spring Boot. + + + com.baeldung + parent-boot-2 + 0.0.1-SNAPSHOT + ../../parent-boot-2 + + + + + + org.keycloak.bom + keycloak-adapter-bom + ${keycloak-adapter-bom.version} + pom + import + + + + + + + org.springframework.boot + spring-boot-starter + + + org.keycloak + keycloak-spring-boot-starter + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-oauth2-client + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-web + + + org.hsqldb + hsqldb + runtime + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.security + spring-security-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + 15.0.2 + + + \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/keycloak/CustomUserAttrController.java b/spring-boot-modules/spring-boot-keycloak-adapters/src/main/java/com/baeldung/keycloak/CustomUserAttrController.java similarity index 100% rename from spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/keycloak/CustomUserAttrController.java rename to spring-boot-modules/spring-boot-keycloak-adapters/src/main/java/com/baeldung/keycloak/CustomUserAttrController.java diff --git a/spring-boot-modules/spring-boot-keycloak-adapters/src/main/java/com/baeldung/keycloak/Customer.java b/spring-boot-modules/spring-boot-keycloak-adapters/src/main/java/com/baeldung/keycloak/Customer.java new file mode 100644 index 0000000000..3293446b1d --- /dev/null +++ b/spring-boot-modules/spring-boot-keycloak-adapters/src/main/java/com/baeldung/keycloak/Customer.java @@ -0,0 +1,49 @@ +package com.baeldung.keycloak; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class Customer { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + private String name; + private String serviceRendered; + private String address; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getServiceRendered() { + return serviceRendered; + } + + public void setServiceRendered(String serviceRendered) { + this.serviceRendered = serviceRendered; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + +} diff --git a/spring-boot-modules/spring-boot-keycloak-adapters/src/main/java/com/baeldung/keycloak/CustomerDAO.java b/spring-boot-modules/spring-boot-keycloak-adapters/src/main/java/com/baeldung/keycloak/CustomerDAO.java new file mode 100644 index 0000000000..20d992d335 --- /dev/null +++ b/spring-boot-modules/spring-boot-keycloak-adapters/src/main/java/com/baeldung/keycloak/CustomerDAO.java @@ -0,0 +1,7 @@ +package com.baeldung.keycloak; + +import org.springframework.data.repository.CrudRepository; + +public interface CustomerDAO extends CrudRepository { + +} diff --git a/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/keycloak/KeycloakConfig.java b/spring-boot-modules/spring-boot-keycloak-adapters/src/main/java/com/baeldung/keycloak/KeycloakConfig.java similarity index 100% rename from spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/keycloak/KeycloakConfig.java rename to spring-boot-modules/spring-boot-keycloak-adapters/src/main/java/com/baeldung/keycloak/KeycloakConfig.java diff --git a/spring-boot-modules/spring-boot-keycloak-adapters/src/main/java/com/baeldung/keycloak/KeycloakLogoutHandler.java b/spring-boot-modules/spring-boot-keycloak-adapters/src/main/java/com/baeldung/keycloak/KeycloakLogoutHandler.java new file mode 100644 index 0000000000..06c41e9b1d --- /dev/null +++ b/spring-boot-modules/spring-boot-keycloak-adapters/src/main/java/com/baeldung/keycloak/KeycloakLogoutHandler.java @@ -0,0 +1,45 @@ +package com.baeldung.keycloak; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.security.web.authentication.logout.LogoutHandler; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@Component +public class KeycloakLogoutHandler implements LogoutHandler { + + private static final Logger logger = LoggerFactory.getLogger(KeycloakLogoutHandler.class); + private final RestTemplate restTemplate; + + public KeycloakLogoutHandler(RestTemplate restTemplate) { + this.restTemplate = restTemplate; + } + + @Override + public void logout(HttpServletRequest request, HttpServletResponse response, Authentication auth) { + logoutFromKeycloak((OidcUser) auth.getPrincipal()); + } + + private void logoutFromKeycloak(OidcUser user) { + String endSessionEndpoint = user.getIssuer() + "/protocol/openid-connect/logout"; + UriComponentsBuilder builder = UriComponentsBuilder + .fromUriString(endSessionEndpoint) + .queryParam("id_token_hint", user.getIdToken().getTokenValue()); + + ResponseEntity logoutResponse = restTemplate.getForEntity(builder.toUriString(), String.class); + if (logoutResponse.getStatusCode().is2xxSuccessful()) { + logger.info("Successfulley logged out from Keycloak"); + } else { + logger.error("Could not propagate logout to Keycloak"); + } + } + +} diff --git a/spring-boot-modules/spring-boot-keycloak-adapters/src/main/java/com/baeldung/keycloak/SecurityConfig.java b/spring-boot-modules/spring-boot-keycloak-adapters/src/main/java/com/baeldung/keycloak/SecurityConfig.java new file mode 100644 index 0000000000..c39e37cfaa --- /dev/null +++ b/spring-boot-modules/spring-boot-keycloak-adapters/src/main/java/com/baeldung/keycloak/SecurityConfig.java @@ -0,0 +1,41 @@ +package com.baeldung.keycloak; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.core.session.SessionRegistryImpl; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy; +import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; + +@Configuration +@EnableWebSecurity +class SecurityConfig { + + private final KeycloakLogoutHandler keycloakLogoutHandler; + + SecurityConfig(KeycloakLogoutHandler keycloakLogoutHandler) { + this.keycloakLogoutHandler = keycloakLogoutHandler; + } + + @Bean + protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { + return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl()); + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http.authorizeRequests() + .antMatchers("/customers*", "/users*") + .hasRole("USER") + .anyRequest() + .permitAll(); + http.oauth2Login() + .and() + .logout() + .addLogoutHandler(keycloakLogoutHandler) + .logoutSuccessUrl("/"); + return http.build(); + } +} diff --git a/spring-boot-modules/spring-boot-keycloak-adapters/src/main/java/com/baeldung/keycloak/SpringBoot.java b/spring-boot-modules/spring-boot-keycloak-adapters/src/main/java/com/baeldung/keycloak/SpringBoot.java new file mode 100644 index 0000000000..90d7e774a4 --- /dev/null +++ b/spring-boot-modules/spring-boot-keycloak-adapters/src/main/java/com/baeldung/keycloak/SpringBoot.java @@ -0,0 +1,20 @@ +package com.baeldung.keycloak; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.web.client.RestTemplate; + +@SpringBootApplication + +public class SpringBoot { + + public static void main(String[] args) { + SpringApplication.run(SpringBoot.class, args); + } + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } +} diff --git a/spring-boot-modules/spring-boot-keycloak-adapters/src/main/java/com/baeldung/keycloak/WebController.java b/spring-boot-modules/spring-boot-keycloak-adapters/src/main/java/com/baeldung/keycloak/WebController.java new file mode 100644 index 0000000000..bbd96c8135 --- /dev/null +++ b/spring-boot-modules/spring-boot-keycloak-adapters/src/main/java/com/baeldung/keycloak/WebController.java @@ -0,0 +1,60 @@ +package com.baeldung.keycloak; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; + +import java.security.Principal; + +import org.springframework.beans.factory.annotation.Autowired; + +import javax.servlet.http.HttpServletRequest; + +@Controller +public class WebController { + + @Autowired + private CustomerDAO customerDAO; + + @GetMapping(path = "/") + public String index() { + return "external"; + } + + @GetMapping("/logout") + public String logout(HttpServletRequest request) throws Exception { + request.logout(); + return "redirect:/"; + } + + @GetMapping(path = "/customers") + public String customers(Principal principal, Model model) { + addCustomers(); + Iterable customers = customerDAO.findAll(); + model.addAttribute("customers", customers); + model.addAttribute("username", principal.getName()); + return "customers"; + } + + // add customers for demonstration + public void addCustomers() { + + Customer customer1 = new Customer(); + customer1.setAddress("1111 foo blvd"); + customer1.setName("Foo Industries"); + customer1.setServiceRendered("Important services"); + customerDAO.save(customer1); + + Customer customer2 = new Customer(); + customer2.setAddress("2222 bar street"); + customer2.setName("Bar LLP"); + customer2.setServiceRendered("Important services"); + customerDAO.save(customer2); + + Customer customer3 = new Customer(); + customer3.setAddress("33 main street"); + customer3.setName("Big LLC"); + customer3.setServiceRendered("Important services"); + customerDAO.save(customer3); + } +} diff --git a/spring-boot-modules/spring-boot-keycloak/src/main/resources/application-embedded.properties b/spring-boot-modules/spring-boot-keycloak-adapters/src/main/resources/application-embedded.properties similarity index 100% rename from spring-boot-modules/spring-boot-keycloak/src/main/resources/application-embedded.properties rename to spring-boot-modules/spring-boot-keycloak-adapters/src/main/resources/application-embedded.properties diff --git a/spring-boot-modules/spring-boot-keycloak-adapters/src/main/resources/application.properties b/spring-boot-modules/spring-boot-keycloak-adapters/src/main/resources/application.properties new file mode 100644 index 0000000000..323617e2ef --- /dev/null +++ b/spring-boot-modules/spring-boot-keycloak-adapters/src/main/resources/application.properties @@ -0,0 +1,15 @@ +### server port +server.port=8081 + +#Keycloak Configuration +keycloak.auth-server-url=http://localhost:8180/auth +keycloak.realm=SpringBootKeycloak +keycloak.resource=login-app +keycloak.public-client=true +keycloak.principal-attribute=preferred_username + +spring.security.oauth2.client.registration.keycloak.client-id=login-app +spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code +spring.security.oauth2.client.registration.keycloak.scope=openid +spring.security.oauth2.client.provider.keycloak.issuer-uri=http://localhost:8180/auth/realms/SpringBootKeycloak +spring.security.oauth2.client.provider.keycloak.user-name-attribute=preferred_username \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-keycloak-adapters/src/main/resources/logback.xml b/spring-boot-modules/spring-boot-keycloak-adapters/src/main/resources/logback.xml new file mode 100644 index 0000000000..7d900d8ea8 --- /dev/null +++ b/spring-boot-modules/spring-boot-keycloak-adapters/src/main/resources/logback.xml @@ -0,0 +1,13 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-keycloak-adapters/src/main/resources/templates/customers.html b/spring-boot-modules/spring-boot-keycloak-adapters/src/main/resources/templates/customers.html new file mode 100644 index 0000000000..de2df93ef1 --- /dev/null +++ b/spring-boot-modules/spring-boot-keycloak-adapters/src/main/resources/templates/customers.html @@ -0,0 +1,34 @@ + + + + + +

+

+ Hello, --name--. +

+ + + + + + + + + + + + + + + + + +
IDNameAddressService Rendered
Text ...Text ...Text ...Text...
+ + Logout +
+ + + diff --git a/spring-boot-modules/spring-boot-keycloak-adapters/src/main/resources/templates/external.html b/spring-boot-modules/spring-boot-keycloak-adapters/src/main/resources/templates/external.html new file mode 100644 index 0000000000..2f9cc76961 --- /dev/null +++ b/spring-boot-modules/spring-boot-keycloak-adapters/src/main/resources/templates/external.html @@ -0,0 +1,31 @@ + + + + + +
+
+

Customer Portal

+
+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam + erat lectus, vehicula feugiat ultricies at, tempus sed ante. Cras + arcu erat, lobortis vitae quam et, mollis pharetra odio. Nullam sit + amet congue ipsum. Nunc dapibus odio ut ligula venenatis porta non + id dui. Duis nec tempor tellus. Suspendisse id blandit ligula, sit + amet varius mauris. Nulla eu eros pharetra, tristique dui quis, + vehicula libero. Aenean a neque sit amet tellus porttitor rutrum nec + at leo.

+ +

Existing Customers

+
+ Enter the intranet: customers +
+
+ +
+ + + + diff --git a/spring-boot-modules/spring-boot-keycloak-adapters/src/main/resources/templates/layout.html b/spring-boot-modules/spring-boot-keycloak-adapters/src/main/resources/templates/layout.html new file mode 100644 index 0000000000..bab0c2982b --- /dev/null +++ b/spring-boot-modules/spring-boot-keycloak-adapters/src/main/resources/templates/layout.html @@ -0,0 +1,18 @@ + + + +Customer Portal + + + + + \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-keycloak/src/main/resources/templates/userInfo.html b/spring-boot-modules/spring-boot-keycloak-adapters/src/main/resources/templates/userInfo.html similarity index 100% rename from spring-boot-modules/spring-boot-keycloak/src/main/resources/templates/userInfo.html rename to spring-boot-modules/spring-boot-keycloak-adapters/src/main/resources/templates/userInfo.html diff --git a/spring-boot-modules/spring-boot-keycloak/src/test/java/com/baeldung/keycloak/KeycloakConfigurationLiveTest.java b/spring-boot-modules/spring-boot-keycloak-adapters/src/test/java/com/baeldung/keycloak/KeycloakConfigurationLiveTest.java similarity index 100% rename from spring-boot-modules/spring-boot-keycloak/src/test/java/com/baeldung/keycloak/KeycloakConfigurationLiveTest.java rename to spring-boot-modules/spring-boot-keycloak-adapters/src/test/java/com/baeldung/keycloak/KeycloakConfigurationLiveTest.java diff --git a/spring-boot-modules/spring-boot-keycloak/README.md b/spring-boot-modules/spring-boot-keycloak/README.md index e95ada0e05..b4545e121c 100644 --- a/spring-boot-modules/spring-boot-keycloak/README.md +++ b/spring-boot-modules/spring-boot-keycloak/README.md @@ -4,9 +4,8 @@ This module contains articles about Keycloak in Spring Boot projects. ## Relevant articles: - [A Quick Guide to Using Keycloak With Spring Boot](https://www.baeldung.com/spring-boot-keycloak) -- [Custom User Attributes with Keycloak](https://www.baeldung.com/keycloak-custom-user-attributes) - [Customizing the Login Page for Keycloak](https://www.baeldung.com/keycloak-custom-login-page) - [Keycloak User Self-Registration](https://www.baeldung.com/keycloak-user-registration) - [Customizing Themes for Keycloak](https://www.baeldung.com/spring-keycloak-custom-themes) - [Securing SOAP Web Services With Keycloak](https://www.baeldung.com/soap-keycloak) -- [Get Keycloak User ID in Spring](https://www.baeldung.com/spring-keycloak-get-user-id) + diff --git a/spring-boot-modules/spring-boot-keycloak/pom.xml b/spring-boot-modules/spring-boot-keycloak/pom.xml index 4f30d32bec..d13ef22345 100644 --- a/spring-boot-modules/spring-boot-keycloak/pom.xml +++ b/spring-boot-modules/spring-boot-keycloak/pom.xml @@ -17,26 +17,14 @@ ../../parent-boot-2 - - - - org.keycloak.bom - keycloak-adapter-bom - ${keycloak-adapter-bom.version} - pom - import - - - - org.springframework.boot spring-boot-starter - org.keycloak - keycloak-spring-boot-starter + org.springframework.boot + spring-boot-starter-oauth2-resource-server org.springframework.boot @@ -113,8 +101,4 @@
- - 15.0.2 - - \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/keycloak/SecurityConfig.java b/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/keycloak/SecurityConfig.java index c39e37cfaa..c85438952a 100644 --- a/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/keycloak/SecurityConfig.java +++ b/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/keycloak/SecurityConfig.java @@ -2,8 +2,11 @@ package com.baeldung.keycloak; 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.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer; import org.springframework.security.core.session.SessionRegistryImpl; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy; @@ -36,6 +39,13 @@ class SecurityConfig { .logout() .addLogoutHandler(keycloakLogoutHandler) .logoutSuccessUrl("/"); + http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt); return http.build(); } + + @Bean + public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception { + return http.getSharedObject(AuthenticationManagerBuilder.class) + .build(); + } } diff --git a/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/keycloaksoap/KeycloakSecurityConfig.java b/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/keycloaksoap/KeycloakSecurityConfig.java index 66a17f4967..e55d307e33 100644 --- a/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/keycloaksoap/KeycloakSecurityConfig.java +++ b/spring-boot-modules/spring-boot-keycloak/src/main/java/com/baeldung/keycloaksoap/KeycloakSecurityConfig.java @@ -1,54 +1,27 @@ package com.baeldung.keycloaksoap; -import org.keycloak.adapters.KeycloakConfigResolver; -import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver; -import org.keycloak.adapters.springsecurity.KeycloakConfiguration; -import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider; -import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper; -import org.springframework.security.core.session.SessionRegistryImpl; -import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy; -import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer; +import org.springframework.security.web.SecurityFilterChain; -@KeycloakConfiguration +@Configuration +@EnableWebSecurity @ConditionalOnProperty(name = "keycloak.enabled", havingValue = "true") @EnableGlobalMethodSecurity(jsr250Enabled = true) -public class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter { - @Override - protected void configure(HttpSecurity http) throws Exception { - super.configure(http); - //@formatter:off - http - .csrf() - .disable() - .authorizeRequests() - .anyRequest() - .permitAll(); - //@formatter:on - } - - @Autowired - public void configureGlobal(AuthenticationManagerBuilder auth) { - KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider(); - keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper()); - auth.authenticationProvider(keycloakAuthenticationProvider); - } +public class KeycloakSecurityConfig { @Bean - @Override - protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { - return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl()); + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http.csrf() + .disable() + .authorizeHttpRequests(auth -> auth.anyRequest() + .authenticated()) + .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt); + return http.build(); } - - @Bean - public KeycloakConfigResolver keycloakSpringBootConfigResolver() { - return new KeycloakSpringBootConfigResolver(); - } - } diff --git a/spring-boot-modules/spring-boot-keycloak/src/main/resources/application-keycloak.properties b/spring-boot-modules/spring-boot-keycloak/src/main/resources/application-keycloak.properties index 0a28b7ac48..474e671ce3 100644 --- a/spring-boot-modules/spring-boot-keycloak/src/main/resources/application-keycloak.properties +++ b/spring-boot-modules/spring-boot-keycloak/src/main/resources/application-keycloak.properties @@ -1,14 +1,8 @@ server.port=18080 keycloak.enabled=true -keycloak.realm=baeldung-soap-services -keycloak.auth-server-url=http://localhost:8080/auth -keycloak.bearer-only=true -keycloak.credentials.secret=14da6f9e-261f-489a-9bf0-1441e4a9ddc4 -keycloak.ssl-required=external -keycloak.resource=baeldung-soap-services -keycloak.use-resource-role-mappings=true +spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8080/realms/baeldung-soap-services # Custom properties begin here ws.api.path=/ws/api/v1/* diff --git a/spring-boot-modules/spring-boot-keycloak/src/main/resources/application.properties b/spring-boot-modules/spring-boot-keycloak/src/main/resources/application.properties index 323617e2ef..df2fadabae 100644 --- a/spring-boot-modules/spring-boot-keycloak/src/main/resources/application.properties +++ b/spring-boot-modules/spring-boot-keycloak/src/main/resources/application.properties @@ -1,15 +1,10 @@ ### server port server.port=8081 -#Keycloak Configuration -keycloak.auth-server-url=http://localhost:8180/auth -keycloak.realm=SpringBootKeycloak -keycloak.resource=login-app -keycloak.public-client=true -keycloak.principal-attribute=preferred_username - spring.security.oauth2.client.registration.keycloak.client-id=login-app spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code spring.security.oauth2.client.registration.keycloak.scope=openid -spring.security.oauth2.client.provider.keycloak.issuer-uri=http://localhost:8180/auth/realms/SpringBootKeycloak -spring.security.oauth2.client.provider.keycloak.user-name-attribute=preferred_username \ No newline at end of file +spring.security.oauth2.client.provider.keycloak.issuer-uri=http://localhost:8080/realms/SpringBootKeycloak +spring.security.oauth2.client.provider.keycloak.user-name-attribute=preferred_username + +spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8080/realms/SpringBootKeycloak \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-keycloak/src/test/java/com/baeldung/keycloak/KeycloakContextIntegrationTest.java b/spring-boot-modules/spring-boot-keycloak/src/test/java/com/baeldung/keycloak/KeycloakContextIntegrationTest.java new file mode 100644 index 0000000000..336c8364aa --- /dev/null +++ b/spring-boot-modules/spring-boot-keycloak/src/test/java/com/baeldung/keycloak/KeycloakContextIntegrationTest.java @@ -0,0 +1,18 @@ +package com.baeldung.keycloak; + +import org.junit.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import com.baeldung.keycloak.SpringBoot; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(classes = { SpringBoot.class }) +public class KeycloakContextIntegrationTest { + + @Test + public void whenLoadApplication_thenSuccess() { + + } + +} diff --git a/spring-boot-modules/spring-boot-keycloak/src/test/java/com/baeldung/keycloaksoap/KeycloakSoapLiveTest.java b/spring-boot-modules/spring-boot-keycloak/src/test/java/com/baeldung/keycloaksoap/KeycloakSoapLiveTest.java index 0327915399..171c7bf330 100644 --- a/spring-boot-modules/spring-boot-keycloak/src/test/java/com/baeldung/keycloaksoap/KeycloakSoapLiveTest.java +++ b/spring-boot-modules/spring-boot-keycloak/src/test/java/com/baeldung/keycloaksoap/KeycloakSoapLiveTest.java @@ -105,7 +105,7 @@ class KeycloakSoapLiveTest { void givenAccessToken_whenDeleteProduct_thenReturnSuccess() { HttpHeaders headers = new HttpHeaders(); headers.set("content-type", "text/xml"); - headers.set("Authorization", "Bearer " + generateToken("jhondoe", "password")); + headers.set("Authorization", "Bearer " + generateToken("johndoe", "password")); HttpEntity request = new HttpEntity<>(Utility.getDeleteProductsRequest(), headers); ResponseEntity responseEntity = restTemplate.postForEntity("http://localhost:" + port + "/ws/api/v1/", request, String.class); diff --git a/spring-boot-modules/spring-boot-keycloak/src/test/resources/application-test.properties b/spring-boot-modules/spring-boot-keycloak/src/test/resources/application-test.properties index a818b5be7a..609d59b4bf 100644 --- a/spring-boot-modules/spring-boot-keycloak/src/test/resources/application-test.properties +++ b/spring-boot-modules/spring-boot-keycloak/src/test/resources/application-test.properties @@ -1,4 +1,7 @@ grant.type=password client.id=baeldung-soap-services client.secret=d2ba7af8-f7d2-4c97-b4a5-3c88b59920ae -url=http://localhost:8080/auth/realms/baeldung-soap-services/protocol/openid-connect/token +url=http://localhost:8080/realms/baeldung-soap-services/protocol/openid-connect/token + +keycloak.enabled=true +spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8080/realms/baeldung-soap-services diff --git a/spring-reactive-modules/spring-5-reactive/pom.xml b/spring-reactive-modules/spring-5-reactive/pom.xml index 5eb98eb106..212281b6f9 100644 --- a/spring-reactive-modules/spring-5-reactive/pom.xml +++ b/spring-reactive-modules/spring-5-reactive/pom.xml @@ -26,7 +26,11 @@ org.springframework.boot - spring-boot-starter-webflux + spring-boot-starter-integration + + + org.springframework.boot + spring-boot-starter-websocket javax.json.bind @@ -111,6 +115,10 @@ org.apache.httpcomponents httpclient + + io.netty + netty-all + diff --git a/spring-reactive-modules/spring-5-reactive/src/main/java/com/baeldung/reactive/Spring5ReactiveApplication.java b/spring-reactive-modules/spring-5-reactive/src/main/java/com/baeldung/reactive/Spring5ReactiveApplication.java index a8cd18c470..ef862dd957 100644 --- a/spring-reactive-modules/spring-5-reactive/src/main/java/com/baeldung/reactive/Spring5ReactiveApplication.java +++ b/spring-reactive-modules/spring-5-reactive/src/main/java/com/baeldung/reactive/Spring5ReactiveApplication.java @@ -2,8 +2,9 @@ package com.baeldung.reactive; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; -@SpringBootApplication +@SpringBootApplication(exclude = { RedisAutoConfiguration.class }) public class Spring5ReactiveApplication{ public static void main(String[] args) { diff --git a/spring-reactive-modules/spring-5-reactive/src/main/java/com/baeldung/websocket/ReactiveWebSocketApplication.java b/spring-reactive-modules/spring-5-reactive/src/main/java/com/baeldung/websocket/ReactiveWebSocketApplication.java index 0e71673df6..148dd07510 100644 --- a/spring-reactive-modules/spring-5-reactive/src/main/java/com/baeldung/websocket/ReactiveWebSocketApplication.java +++ b/spring-reactive-modules/spring-5-reactive/src/main/java/com/baeldung/websocket/ReactiveWebSocketApplication.java @@ -2,8 +2,22 @@ package com.baeldung.websocket; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; +import org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration; +import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration; +import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration; +import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; -@SpringBootApplication +@SpringBootApplication(exclude = { + SecurityAutoConfiguration.class, + UserDetailsServiceAutoConfiguration.class, + ReactiveSecurityAutoConfiguration.class, + ReactiveUserDetailsServiceAutoConfiguration.class, + ReactiveOAuth2ClientAutoConfiguration.class, + WebMvcAutoConfiguration.class, + RedisAutoConfiguration.class}) public class ReactiveWebSocketApplication { public static void main(String[] args) { SpringApplication.run(ReactiveWebSocketApplication.class, args); diff --git a/spring-reactive-modules/spring-5-reactive/src/main/java/com/baeldung/websocket/ReactiveWebSocketConfiguration.java b/spring-reactive-modules/spring-5-reactive/src/main/java/com/baeldung/websocket/ReactiveWebSocketConfiguration.java index 43a98d068d..8ebd6118b2 100644 --- a/spring-reactive-modules/spring-5-reactive/src/main/java/com/baeldung/websocket/ReactiveWebSocketConfiguration.java +++ b/spring-reactive-modules/spring-5-reactive/src/main/java/com/baeldung/websocket/ReactiveWebSocketConfiguration.java @@ -10,19 +10,25 @@ import org.springframework.context.annotation.Configuration; import org.springframework.web.reactive.HandlerMapping; import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping; import org.springframework.web.reactive.socket.WebSocketHandler; +import org.springframework.web.reactive.socket.server.WebSocketService; +import org.springframework.web.reactive.socket.server.support.HandshakeWebSocketService; +import org.springframework.web.reactive.socket.server.upgrade.TomcatRequestUpgradeStrategy; +import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; @Configuration +@EnableWebSocket public class ReactiveWebSocketConfiguration { @Autowired @Qualifier("ReactiveWebSocketHandler") - private WebSocketHandler webSocketHandler; + private ReactiveWebSocketHandler reactiveWebSocketHandler; @Bean - public HandlerMapping webSocketHandlerMapping() { + public HandlerMapping reactiveWebSocketHandlerMapping() { Map map = new HashMap<>(); - map.put("/event-emitter", webSocketHandler); + map.put("/event-emitter", reactiveWebSocketHandler); SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping(); handlerMapping.setOrder(1); @@ -32,6 +38,26 @@ public class ReactiveWebSocketConfiguration { @Bean public WebSocketHandlerAdapter handlerAdapter() { - return new WebSocketHandlerAdapter(); + return new WebSocketHandlerAdapter(webSocketService()); + } + + @Bean + public WebSocketService webSocketService() { + TomcatRequestUpgradeStrategy tomcatRequestUpgradeStrategy = new TomcatRequestUpgradeStrategy(); + tomcatRequestUpgradeStrategy.setMaxSessionIdleTimeout(10000L); + tomcatRequestUpgradeStrategy.setAsyncSendTimeout(10000L); + return new HandshakeWebSocketService(tomcatRequestUpgradeStrategy); + } + + @Bean + public ServerEndpointExporter serverEndpointExporter() { + ServerEndpointExporter serverEndpointExporter = new ServerEndpointExporter(); + + /** + * Add one or more classes annotated with `@ServerEndpoint`. + */ + serverEndpointExporter.setAnnotatedEndpointClasses(WebSocketController.class); + + return serverEndpointExporter; } } \ No newline at end of file diff --git a/spring-reactive-modules/spring-5-reactive/src/main/java/com/baeldung/websocket/WebSocketController.java b/spring-reactive-modules/spring-5-reactive/src/main/java/com/baeldung/websocket/WebSocketController.java new file mode 100644 index 0000000000..bf4a463ae6 --- /dev/null +++ b/spring-reactive-modules/spring-5-reactive/src/main/java/com/baeldung/websocket/WebSocketController.java @@ -0,0 +1,40 @@ +package com.baeldung.websocket; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.websocket.*; +import javax.websocket.server.ServerEndpoint; +import java.io.IOException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +@ServerEndpoint("/event-emitter") +public class WebSocketController { + + private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketController.class); + @OnOpen + public void onOpen(Session session, EndpointConfig endpointConfig) throws IOException { + // Get session and WebSocket connection + session.setMaxIdleTimeout(0); + LOGGER.info("Get session and WebSocket connection"); + } + + @OnMessage + public void onMessage(String message, Session session) throws IOException { + // Handle new messages + LOGGER.info("Handle new messages -> {}", message ); + } + + @OnClose + public void onClose(Session session) throws IOException { + // WebSocket connection closes + LOGGER.info("WebSocket connection closes"); + } + + @OnError + public void onError(Session session, Throwable throwable) { + // Do error handling here + LOGGER.info("Do error handling here"); + } +} diff --git a/spring-reactive-modules/spring-5-reactive/src/main/resources/application.properties b/spring-reactive-modules/spring-5-reactive/src/main/resources/application.properties index 4b49e8e8a2..dfe4a4d994 100644 --- a/spring-reactive-modules/spring-5-reactive/src/main/resources/application.properties +++ b/spring-reactive-modules/spring-5-reactive/src/main/resources/application.properties @@ -1 +1,2 @@ -logging.level.root=INFO \ No newline at end of file +logging.level.root=INFO +server.tomcat.max-keep-alive-requests=1 \ No newline at end of file diff --git a/spring-scheduling/src/main/java/com/baeldung/async/config/SpringAsyncConfig.java b/spring-scheduling/src/main/java/com/baeldung/async/config/SpringAsyncConfig.java index 56fa643017..872f59ebb6 100644 --- a/spring-scheduling/src/main/java/com/baeldung/async/config/SpringAsyncConfig.java +++ b/spring-scheduling/src/main/java/com/baeldung/async/config/SpringAsyncConfig.java @@ -24,7 +24,9 @@ public class SpringAsyncConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { - return new SimpleAsyncTaskExecutor(); + ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor(); + threadPoolTaskExecutor.initialize(); + return threadPoolTaskExecutor; } @Override 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 index fd7798dda9..332278225c 100644 --- a/spring-security-modules/spring-security-saml/src/main/resources/application.properties +++ b/spring-security-modules/spring-security-saml/src/main/resources/application.properties @@ -1,9 +1,9 @@ -saml.keystore.location=classpath:/saml/saml-keystore +saml.keystore.location=classpath:/saml/saml-keystore.jks # Password for Java keystore and item therein -saml.keystore.password= -saml.keystore.alias= +saml.keystore.password=baeldungsamlokta +saml.keystore.alias=baeldungspringsaml # SAML Entity ID extracted from top of SAML metadata file -saml.idp= +saml.idp=http://www.okta.com/exk26fxqrz8LLk9dV4x7 saml.sp=http://localhost:8080/saml/metadata spring.main.allow-circular-references=true \ No newline at end of file diff --git a/spring-security-modules/spring-security-saml/src/main/resources/saml/saml-keystore.jks b/spring-security-modules/spring-security-saml/src/main/resources/saml/saml-keystore.jks new file mode 100644 index 0000000000..535c611180 Binary files /dev/null and b/spring-security-modules/spring-security-saml/src/main/resources/saml/saml-keystore.jks differ 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 deleted file mode 100644 index 7f3a5850d9..0000000000 Binary files a/spring-security-modules/spring-security-saml/src/main/resources/saml/samlKeystore.jks and /dev/null differ diff --git a/spring-web-modules/spring-resttemplate-3/README.md b/spring-web-modules/spring-resttemplate-3/README.md index 68f6b8c5e4..52eba522a5 100644 --- a/spring-web-modules/spring-resttemplate-3/README.md +++ b/spring-web-modules/spring-resttemplate-3/README.md @@ -10,3 +10,4 @@ The "REST With Spring" Classes: http://bit.ly/restwithspring - [Get and Post Lists of Objects with RestTemplate](https://www.baeldung.com/spring-rest-template-list) - [Download a Large File Through a Spring RestTemplate](https://www.baeldung.com/spring-resttemplate-download-large-file) - [Access HTTPS REST Service Using Spring RestTemplate](https://www.baeldung.com/spring-resttemplate-secure-https-service) +- [Encoding of URI Variables on RestTemplate](https://www.baeldung.com/spring-resttemplate-uri-variables-encode) diff --git a/spring-web-modules/spring-resttemplate-3/src/main/java/com/baeldung/encoding/config/RestTemplateConfig.java b/spring-web-modules/spring-resttemplate-3/src/main/java/com/baeldung/encoding/config/RestTemplateConfig.java index a39ba55ccc..a820a3d4cb 100644 --- a/spring-web-modules/spring-resttemplate-3/src/main/java/com/baeldung/encoding/config/RestTemplateConfig.java +++ b/spring-web-modules/spring-resttemplate-3/src/main/java/com/baeldung/encoding/config/RestTemplateConfig.java @@ -13,7 +13,7 @@ public class RestTemplateConfig { RestTemplate restTemplate = new RestTemplate(); DefaultUriBuilderFactory defaultUriBuilderFactory = new DefaultUriBuilderFactory(); defaultUriBuilderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY); - restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory()); + restTemplate.setUriTemplateHandler(defaultUriBuilderFactory); return restTemplate; } } \ No newline at end of file diff --git a/spring-web-modules/spring-web-url/src/main/java/com/baeldung/rootmapping/RootMappingApplication.java b/spring-web-modules/spring-web-url/src/main/java/com/baeldung/rootmapping/RootMappingApplication.java new file mode 100644 index 0000000000..f64753fa36 --- /dev/null +++ b/spring-web-modules/spring-web-url/src/main/java/com/baeldung/rootmapping/RootMappingApplication.java @@ -0,0 +1,14 @@ +package com.baeldung.rootmapping; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +@SpringBootApplication +@EnableWebMvc +public class RootMappingApplication { + + public static void main(String[] args) { + SpringApplication.run(RootMappingApplication.class, args); + } +} \ No newline at end of file diff --git a/spring-web-modules/spring-web-url/src/main/java/com/baeldung/rootmapping/config/WebConfig.java b/spring-web-modules/spring-web-url/src/main/java/com/baeldung/rootmapping/config/WebConfig.java new file mode 100644 index 0000000000..ba15bc992c --- /dev/null +++ b/spring-web-modules/spring-web-url/src/main/java/com/baeldung/rootmapping/config/WebConfig.java @@ -0,0 +1,13 @@ +package com.baeldung.rootmapping.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + @Override + public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("/").setViewName("index"); + } +} diff --git a/spring-web-modules/spring-web-url/src/main/java/com/baeldung/rootmapping/controller/RootController.java b/spring-web-modules/spring-web-url/src/main/java/com/baeldung/rootmapping/controller/RootController.java new file mode 100644 index 0000000000..7724e43e71 --- /dev/null +++ b/spring-web-modules/spring-web-url/src/main/java/com/baeldung/rootmapping/controller/RootController.java @@ -0,0 +1,12 @@ +package com.baeldung.rootmapping.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class RootController { + @GetMapping("/") + public String index() { + return "index"; + } +} \ No newline at end of file diff --git a/spring-web-modules/spring-web-url/src/main/resources/templates/index.html b/spring-web-modules/spring-web-url/src/main/resources/templates/index.html new file mode 100644 index 0000000000..acfb847868 --- /dev/null +++ b/spring-web-modules/spring-web-url/src/main/resources/templates/index.html @@ -0,0 +1,10 @@ + + + + + Index Page + + +

Hello World!

+ + \ No newline at end of file diff --git a/web-modules/ninja/src/test/java/controllers/ApiControllerDocTesterManualTest.java b/web-modules/ninja/src/test/java/controllers/ApiControllerDocTesterIntegrationTest.java similarity index 91% rename from web-modules/ninja/src/test/java/controllers/ApiControllerDocTesterManualTest.java rename to web-modules/ninja/src/test/java/controllers/ApiControllerDocTesterIntegrationTest.java index b77901c8e2..a075497dc8 100644 --- a/web-modules/ninja/src/test/java/controllers/ApiControllerDocTesterManualTest.java +++ b/web-modules/ninja/src/test/java/controllers/ApiControllerDocTesterIntegrationTest.java @@ -7,7 +7,7 @@ import org.doctester.testbrowser.Response; import org.junit.Test; import ninja.NinjaDocTester; -public class ApiControllerDocTesterManualTest extends NinjaDocTester { +public class ApiControllerDocTesterIntegrationTest extends NinjaDocTester { String URL_INDEX = "/"; String URL_HELLO = "/hello"; diff --git a/web-modules/ninja/src/test/java/controllers/ApiControllerMockManualTest.java b/web-modules/ninja/src/test/java/controllers/ApiControllerMockIntegrationTest.java similarity index 94% rename from web-modules/ninja/src/test/java/controllers/ApiControllerMockManualTest.java rename to web-modules/ninja/src/test/java/controllers/ApiControllerMockIntegrationTest.java index 2fa76bca4e..53a760aadc 100644 --- a/web-modules/ninja/src/test/java/controllers/ApiControllerMockManualTest.java +++ b/web-modules/ninja/src/test/java/controllers/ApiControllerMockIntegrationTest.java @@ -10,7 +10,7 @@ import ninja.Result; import services.UserService; @RunWith(NinjaRunner.class) -public class ApiControllerMockManualTest { +public class ApiControllerMockIntegrationTest { @Inject private UserService userService;