diff --git a/aws-app-sync/README.md b/aws-app-sync/README.md new file mode 100644 index 0000000000..976a999f40 --- /dev/null +++ b/aws-app-sync/README.md @@ -0,0 +1,3 @@ +### Relevant Articles: + +- [AWS AppSync With Spring Boot](https://www.baeldung.com/aws-appsync-spring) diff --git a/core-java-modules/core-java-9-new-features/README.md b/core-java-modules/core-java-9-new-features/README.md index d547b9a221..c2ef63a530 100644 --- a/core-java-modules/core-java-9-new-features/README.md +++ b/core-java-modules/core-java-9-new-features/README.md @@ -12,3 +12,4 @@ This module contains articles about core Java features that have been introduced - [Introduction to Java 9 StackWalking API](https://www.baeldung.com/java-9-stackwalking-api) - [Java 9 Platform Logging API](https://www.baeldung.com/java-9-logging-api) - [Java 9 Reactive Streams](https://www.baeldung.com/java-9-reactive-streams) +- [Multi-Release JAR Files with Maven](https://www.baeldung.com/maven-multi-release-jars) diff --git a/core-java-modules/core-java-9-new-features/pom.xml b/core-java-modules/core-java-9-new-features/pom.xml index 23dc659af8..08e52b2990 100644 --- a/core-java-modules/core-java-9-new-features/pom.xml +++ b/core-java-modules/core-java-9-new-features/pom.xml @@ -33,8 +33,104 @@ ${junit.platform.version} test + + org.awaitility + awaitility + ${awaitility.version} + test + - + + + incubator-features + + core-java-9-new-features + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${maven.compiler.source} + ${maven.compiler.target} + --add-modules=jdk.incubator.httpclient + + + + maven-surefire-plugin + + --add-modules=jdk.incubator.httpclient + + + + + + + mrjar-generation + + + + org.apache.maven.plugins + maven-compiler-plugin + + + compile-java-8 + + compile + + + 1.8 + 1.8 + + ${project.basedir}/src/main/java8 + + + + + compile-java-9 + compile + + compile + + + 9 + + ${project.basedir}/src/main/java9 + + ${project.build.outputDirectory}/META-INF/versions/9 + + + + default-testCompile + test-compile + + testCompile + + + true + + + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + + true + + + com.baeldung.multireleaseapp.App + + + + + + + + core-java-9-new-features @@ -61,8 +157,10 @@ 3.10.0 1.2.0 + 4.0.2 1.9 1.9 + 3.2.0 3.0.0 diff --git a/core-java-modules/core-java-9-new-features/src/main/java8/com/baeldung/multireleaseapp/App.java b/core-java-modules/core-java-9-new-features/src/main/java8/com/baeldung/multireleaseapp/App.java new file mode 100644 index 0000000000..cc00223e05 --- /dev/null +++ b/core-java-modules/core-java-9-new-features/src/main/java8/com/baeldung/multireleaseapp/App.java @@ -0,0 +1,14 @@ +package com.baeldung.multireleaseapp; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class App { + + private static final Logger logger = LoggerFactory.getLogger(App.class); + + public static void main(String[] args) { + logger.info(String.format("Running on %s", new DefaultVersion().version())); + } + +} diff --git a/core-java-modules/core-java-9-new-features/src/main/java8/com/baeldung/multireleaseapp/DefaultVersion.java b/core-java-modules/core-java-9-new-features/src/main/java8/com/baeldung/multireleaseapp/DefaultVersion.java new file mode 100644 index 0000000000..b24be606de --- /dev/null +++ b/core-java-modules/core-java-9-new-features/src/main/java8/com/baeldung/multireleaseapp/DefaultVersion.java @@ -0,0 +1,9 @@ +package com.baeldung.multireleaseapp; + +public class DefaultVersion implements Version { + + @Override + public String version() { + return System.getProperty("java.version"); + } +} diff --git a/core-java-modules/core-java-9-new-features/src/main/java8/com/baeldung/multireleaseapp/Version.java b/core-java-modules/core-java-9-new-features/src/main/java8/com/baeldung/multireleaseapp/Version.java new file mode 100644 index 0000000000..ef95f08205 --- /dev/null +++ b/core-java-modules/core-java-9-new-features/src/main/java8/com/baeldung/multireleaseapp/Version.java @@ -0,0 +1,5 @@ +package com.baeldung.multireleaseapp; + +interface Version { + public String version(); +} \ No newline at end of file diff --git a/core-java-modules/core-java-9-new-features/src/main/java9/com/baeldung/multireleaseapp/DefaultVersion.java b/core-java-modules/core-java-9-new-features/src/main/java9/com/baeldung/multireleaseapp/DefaultVersion.java new file mode 100644 index 0000000000..0842f578dd --- /dev/null +++ b/core-java-modules/core-java-9-new-features/src/main/java9/com/baeldung/multireleaseapp/DefaultVersion.java @@ -0,0 +1,9 @@ +package com.baeldung.multireleaseapp; + +public class DefaultVersion implements Version { + + @Override + public String version() { + return Runtime.version().toString(); + } +} diff --git a/core-java-modules/core-java-9-new-features/src/test/java/com/baeldung/java9/httpclient/HttpClientTest.java b/core-java-modules/core-java-9-new-features/src/test/java/com/baeldung/java9/httpclient/HttpClientIntegrationTest.java similarity index 98% rename from core-java-modules/core-java-9-new-features/src/test/java/com/baeldung/java9/httpclient/HttpClientTest.java rename to core-java-modules/core-java-9-new-features/src/test/java/com/baeldung/java9/httpclient/HttpClientIntegrationTest.java index 5cf3b9098f..fc59ae8d8d 100644 --- a/core-java-modules/core-java-9-new-features/src/test/java/com/baeldung/java9/httpclient/HttpClientTest.java +++ b/core-java-modules/core-java-9-new-features/src/test/java/com/baeldung/java9/httpclient/HttpClientIntegrationTest.java @@ -24,7 +24,7 @@ import static org.junit.Assert.assertThat; /** * Created by adam. */ -public class HttpClientTest { +public class HttpClientIntegrationTest { @Test public void shouldReturnSampleDataContentWhenConnectViaSystemProxy() throws IOException, InterruptedException, URISyntaxException { @@ -55,7 +55,7 @@ public class HttpClientTest { .send(request, HttpResponse.BodyHandler.asString()); assertThat(response.statusCode(), equalTo(HttpURLConnection.HTTP_MOVED_PERM)); - assertThat(response.body(), containsString("https://stackoverflow.com/")); + assertThat(response.body(), containsString("")); } @Test diff --git a/core-java-modules/core-java-9-new-features/src/test/java/com/baeldung/java9/httpclient/HttpRequestTest.java b/core-java-modules/core-java-9-new-features/src/test/java/com/baeldung/java9/httpclient/HttpRequestIntegrationTest.java similarity index 99% rename from core-java-modules/core-java-9-new-features/src/test/java/com/baeldung/java9/httpclient/HttpRequestTest.java rename to core-java-modules/core-java-9-new-features/src/test/java/com/baeldung/java9/httpclient/HttpRequestIntegrationTest.java index 7c0e9a90e0..17af7bd8ba 100644 --- a/core-java-modules/core-java-9-new-features/src/test/java/com/baeldung/java9/httpclient/HttpRequestTest.java +++ b/core-java-modules/core-java-9-new-features/src/test/java/com/baeldung/java9/httpclient/HttpRequestIntegrationTest.java @@ -22,7 +22,7 @@ import static org.junit.Assert.assertThat; /** * Created by adam. */ -public class HttpRequestTest { +public class HttpRequestIntegrationTest { @Test public void shouldReturnStatusOKWhenSendGetRequest() throws IOException, InterruptedException, URISyntaxException { diff --git a/core-java-modules/core-java-9-new-features/src/test/java/com/baeldung/java9/httpclient/HttpResponseTest.java b/core-java-modules/core-java-9-new-features/src/test/java/com/baeldung/java9/httpclient/HttpResponseIntegrationTest.java similarity index 97% rename from core-java-modules/core-java-9-new-features/src/test/java/com/baeldung/java9/httpclient/HttpResponseTest.java rename to core-java-modules/core-java-9-new-features/src/test/java/com/baeldung/java9/httpclient/HttpResponseIntegrationTest.java index 80295ff34c..5c6f9c8a52 100644 --- a/core-java-modules/core-java-9-new-features/src/test/java/com/baeldung/java9/httpclient/HttpResponseTest.java +++ b/core-java-modules/core-java-9-new-features/src/test/java/com/baeldung/java9/httpclient/HttpResponseIntegrationTest.java @@ -18,7 +18,7 @@ import static org.junit.Assert.assertThat; /** * Created by adam. */ -public class HttpResponseTest { +public class HttpResponseIntegrationTest { @Test public void shouldReturnStatusOKWhenSendGetRequest() throws IOException, InterruptedException, URISyntaxException { diff --git a/core-java-modules/core-java-9-new-features/src/test/java/com/baeldung/java9/streams.reactive/ReactiveStreamsTest.java b/core-java-modules/core-java-9-new-features/src/test/java/com/baeldung/java9/streams/reactive/ReactiveStreamsUnitTest.java similarity index 86% rename from core-java-modules/core-java-9-new-features/src/test/java/com/baeldung/java9/streams.reactive/ReactiveStreamsTest.java rename to core-java-modules/core-java-9-new-features/src/test/java/com/baeldung/java9/streams/reactive/ReactiveStreamsUnitTest.java index 647557532d..92cdc1c074 100644 --- a/core-java-modules/core-java-9-new-features/src/test/java/com/baeldung/java9/streams.reactive/ReactiveStreamsTest.java +++ b/core-java-modules/core-java-9-new-features/src/test/java/com/baeldung/java9/streams/reactive/ReactiveStreamsUnitTest.java @@ -5,10 +5,12 @@ import org.junit.Test; import java.util.List; import java.util.concurrent.SubmissionPublisher; +import java.util.concurrent.TimeUnit; -import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; -public class ReactiveStreamsTest { +public class ReactiveStreamsUnitTest { @Test public void givenPublisher_whenSubscribeToIt_thenShouldConsumeAllElements() throws InterruptedException { @@ -25,7 +27,7 @@ public class ReactiveStreamsTest { //then - await().atMost(1000, TimeUnit.MILLISECONDS).until( + await().atMost(1000, TimeUnit.MILLISECONDS).untilAsserted( () -> assertThat(subscriber.consumedElements).containsExactlyElementsOf(items) ); } @@ -46,7 +48,7 @@ public class ReactiveStreamsTest { publisher.close(); //then - await().atMost(1000, TimeUnit.MILLISECONDS).until( + await().atMost(1000, TimeUnit.MILLISECONDS).untilAsserted( () -> assertThat(subscriber.consumedElements).containsExactlyElementsOf(expectedResult) ); } @@ -66,7 +68,7 @@ public class ReactiveStreamsTest { publisher.close(); //then - await().atMost(1000, TimeUnit.MILLISECONDS).until( + await().atMost(1000, TimeUnit.MILLISECONDS).untilAsserted( () -> assertThat(subscriber.consumedElements).containsExactlyElementsOf(expected) ); } diff --git a/core-java-modules/core-java-9-new-features/src/test/java/com/baeldung/java9/varhandles/VariableHandlesTest.java b/core-java-modules/core-java-9-new-features/src/test/java/com/baeldung/java9/varhandles/VariableHandlesUnitTest.java similarity index 75% rename from core-java-modules/core-java-9-new-features/src/test/java/com/baeldung/java9/varhandles/VariableHandlesTest.java rename to core-java-modules/core-java-9-new-features/src/test/java/com/baeldung/java9/varhandles/VariableHandlesUnitTest.java index 50766502ec..56e34f06a0 100644 --- a/core-java-modules/core-java-9-new-features/src/test/java/com/baeldung/java9/varhandles/VariableHandlesTest.java +++ b/core-java-modules/core-java-9-new-features/src/test/java/com/baeldung/java9/varhandles/VariableHandlesUnitTest.java @@ -7,7 +7,7 @@ import java.lang.invoke.VarHandle; import static org.assertj.core.api.Assertions.assertThat; -public class VariableHandlesTest { +public class VariableHandlesUnitTest { public int publicTestVariable = 1; private int privateTestVariable = 1; @@ -20,22 +20,22 @@ public class VariableHandlesTest { public void whenVariableHandleForPublicVariableIsCreated_ThenItIsInitializedProperly() throws NoSuchFieldException, IllegalAccessException { VarHandle publicIntHandle = MethodHandles .lookup() - .in(VariableHandlesTest.class) - .findVarHandle(VariableHandlesTest.class, "publicTestVariable", int.class); + .in(VariableHandlesUnitTest.class) + .findVarHandle(VariableHandlesUnitTest.class, "publicTestVariable", int.class); assertThat(publicIntHandle.coordinateTypes().size() == 1); - assertThat(publicIntHandle.coordinateTypes().get(0) == VariableHandles.class); + assertThat(publicIntHandle.coordinateTypes().get(0) == VariableHandlesUnitTest.class); } @Test public void whenVariableHandleForPrivateVariableIsCreated_ThenItIsInitializedProperly() throws NoSuchFieldException, IllegalAccessException { VarHandle privateIntHandle = MethodHandles - .privateLookupIn(VariableHandlesTest.class, MethodHandles.lookup()) - .findVarHandle(VariableHandlesTest.class, "privateTestVariable", int.class); + .privateLookupIn(VariableHandlesUnitTest.class, MethodHandles.lookup()) + .findVarHandle(VariableHandlesUnitTest.class, "privateTestVariable", int.class); assertThat(privateIntHandle.coordinateTypes().size() == 1); - assertThat(privateIntHandle.coordinateTypes().get(0) == VariableHandlesTest.class); + assertThat(privateIntHandle.coordinateTypes().get(0) == VariableHandlesUnitTest.class); } @@ -52,8 +52,8 @@ public class VariableHandlesTest { public void givenVarHandle_whenGetIsInvoked_ThenValueOfVariableIsReturned() throws NoSuchFieldException, IllegalAccessException { VarHandle publicIntHandle = MethodHandles .lookup() - .in(VariableHandlesTest.class) - .findVarHandle(VariableHandlesTest.class, "publicTestVariable", int.class); + .in(VariableHandlesUnitTest.class) + .findVarHandle(VariableHandlesUnitTest.class, "publicTestVariable", int.class); assertThat((int) publicIntHandle.get(this) == 1); } @@ -62,8 +62,8 @@ public class VariableHandlesTest { public void givenVarHandle_whenSetIsInvoked_ThenValueOfVariableIsChanged() throws NoSuchFieldException, IllegalAccessException { VarHandle publicIntHandle = MethodHandles .lookup() - .in(VariableHandlesTest.class) - .findVarHandle(VariableHandlesTest.class, "variableToSet", int.class); + .in(VariableHandlesUnitTest.class) + .findVarHandle(VariableHandlesUnitTest.class, "variableToSet", int.class); publicIntHandle.set(this, 15); assertThat((int) publicIntHandle.get(this) == 15); @@ -73,8 +73,8 @@ public class VariableHandlesTest { public void givenVarHandle_whenCompareAndSetIsInvoked_ThenValueOfVariableIsChanged() throws NoSuchFieldException, IllegalAccessException { VarHandle publicIntHandle = MethodHandles .lookup() - .in(VariableHandlesTest.class) - .findVarHandle(VariableHandlesTest.class, "variableToCompareAndSet", int.class); + .in(VariableHandlesUnitTest.class) + .findVarHandle(VariableHandlesUnitTest.class, "variableToCompareAndSet", int.class); publicIntHandle.compareAndSet(this, 1, 100); assertThat((int) publicIntHandle.get(this) == 100); @@ -84,8 +84,8 @@ public class VariableHandlesTest { public void givenVarHandle_whenGetAndAddIsInvoked_ThenValueOfVariableIsChanged() throws NoSuchFieldException, IllegalAccessException { VarHandle publicIntHandle = MethodHandles .lookup() - .in(VariableHandlesTest.class) - .findVarHandle(VariableHandlesTest.class, "variableToGetAndAdd", int.class); + .in(VariableHandlesUnitTest.class) + .findVarHandle(VariableHandlesUnitTest.class, "variableToGetAndAdd", int.class); int before = (int) publicIntHandle.getAndAdd(this, 200); assertThat(before == 0); @@ -96,8 +96,8 @@ public class VariableHandlesTest { public void givenVarHandle_whenGetAndBitwiseOrIsInvoked_ThenValueOfVariableIsChanged() throws NoSuchFieldException, IllegalAccessException { VarHandle publicIntHandle = MethodHandles .lookup() - .in(VariableHandlesTest.class) - .findVarHandle(VariableHandlesTest.class, "variableToBitwiseOr", byte.class); + .in(VariableHandlesUnitTest.class) + .findVarHandle(VariableHandlesUnitTest.class, "variableToBitwiseOr", byte.class); byte before = (byte) publicIntHandle.getAndBitwiseOr(this, (byte) 127); assertThat(before == 0); diff --git a/core-java-modules/core-java-io-conversions-2/README.md b/core-java-modules/core-java-io-conversions-2/README.md index 5cb9c21c54..9ce36e7437 100644 --- a/core-java-modules/core-java-io-conversions-2/README.md +++ b/core-java-modules/core-java-io-conversions-2/README.md @@ -5,4 +5,5 @@ This module contains articles about core Java input/output(IO) conversions. ### Relevant Articles: - [Java InputStream to String](https://www.baeldung.com/convert-input-stream-to-string) - [Java – Write an InputStream to a File](https://www.baeldung.com/convert-input-stream-to-a-file) +- [Converting a BufferedReader to a JSONObject](https://www.baeldung.com/java-bufferedreader-to-jsonobject) - More articles: [[<-- prev]](/core-java-modules/core-java-io-conversions) diff --git a/core-java-modules/core-java-io-conversions-2/pom.xml b/core-java-modules/core-java-io-conversions-2/pom.xml index 5312ad645d..46bce7988b 100644 --- a/core-java-modules/core-java-io-conversions-2/pom.xml +++ b/core-java-modules/core-java-io-conversions-2/pom.xml @@ -21,6 +21,11 @@ commons-lang3 ${commons-lang3.version} + + org.json + json + 20200518 + diff --git a/core-java-modules/core-java-io-conversions-2/src/test/java/com/baeldung/bufferedreadertojsonobject/JavaBufferedReaderToJSONObjectUnitTest.java b/core-java-modules/core-java-io-conversions-2/src/test/java/com/baeldung/bufferedreadertojsonobject/JavaBufferedReaderToJSONObjectUnitTest.java new file mode 100644 index 0000000000..80007e9c2f --- /dev/null +++ b/core-java-modules/core-java-io-conversions-2/src/test/java/com/baeldung/bufferedreadertojsonobject/JavaBufferedReaderToJSONObjectUnitTest.java @@ -0,0 +1,48 @@ +package com.baeldung.bufferedreadertojsonobject; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +import org.json.JSONObject; +import org.json.JSONTokener; +import org.junit.Test; + +public class JavaBufferedReaderToJSONObjectUnitTest { + + @Test + public void givenValidJson_whenUsingBufferedReader_thenJSONTokenerConverts() { + byte[] b = "{ \"name\" : \"John\", \"age\" : 18 }".getBytes(StandardCharsets.UTF_8); + InputStream is = new ByteArrayInputStream(b); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is)); + JSONTokener tokener = new JSONTokener(bufferedReader); + JSONObject json = new JSONObject(tokener); + + assertNotNull(json); + assertEquals("John", json.get("name")); + assertEquals(18, json.get("age")); + } + + @Test + public void givenValidJson_whenUsingString_thenJSONObjectConverts() throws IOException { + byte[] b = "{ \"name\" : \"John\", \"age\" : 18 }".getBytes(StandardCharsets.UTF_8); + InputStream is = new ByteArrayInputStream(b); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is)); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = bufferedReader.readLine()) != null) { + sb.append(line); + } + JSONObject json = new JSONObject(sb.toString()); + + assertNotNull(json); + assertEquals("John", json.get("name")); + assertEquals(18, json.get("age")); + } +} diff --git a/core-java-modules/core-java-jvm/src/test/java/com/baeldung/error/oom/ExecutorServiceUnitTest.java b/core-java-modules/core-java-jvm/src/test/java/com/baeldung/error/oom/ExecutorServiceUnitTest.java new file mode 100644 index 0000000000..47bb668727 --- /dev/null +++ b/core-java-modules/core-java-jvm/src/test/java/com/baeldung/error/oom/ExecutorServiceUnitTest.java @@ -0,0 +1,40 @@ +package com.baeldung.error.oom; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.stream.IntStream; + +import org.junit.jupiter.api.Test; + +public class ExecutorServiceUnitTest { + + @Test + public void givenAnExecutorService_WhenMoreTasksSubmitted_ThenAdditionalTasksWait() { + + // Given + int noOfThreads = 5; + ExecutorService executorService = Executors.newFixedThreadPool(noOfThreads); + + Runnable runnableTask = () -> { + try { + TimeUnit.HOURS.sleep(1); + } catch (InterruptedException e) { + e.printStackTrace(); + } + }; + + // When + IntStream.rangeClosed(1, 10) + .forEach(i -> executorService.submit(runnableTask)); + + // Then + assertThat(((ThreadPoolExecutor) executorService).getQueue() + .size(), is(equalTo(5))); + } +} diff --git a/core-java-modules/core-java-lang-2/src/main/java/com/baeldung/inttoenum/PizzaStatus.java b/core-java-modules/core-java-lang-2/src/main/java/com/baeldung/inttoenum/PizzaStatus.java new file mode 100644 index 0000000000..8d7c626521 --- /dev/null +++ b/core-java-modules/core-java-lang-2/src/main/java/com/baeldung/inttoenum/PizzaStatus.java @@ -0,0 +1,36 @@ +package com.baeldung.inttoenum; + +import java.util.HashMap; +import java.util.Map; + +public enum PizzaStatus { + ORDERED(5), + READY(2), + DELIVERED(0); + + private int timeToDelivery; + + PizzaStatus(int timeToDelivery) { + this.timeToDelivery = timeToDelivery; + } + + public int getTimeToDelivery() { + return timeToDelivery; + } + + private static Map timeToDeliveryToEnumValuesMapping = new HashMap<>(); + + static { + PizzaStatus[] pizzaStatuses = PizzaStatus.values(); + for (int pizzaStatusIndex = 0; pizzaStatusIndex < pizzaStatuses.length; pizzaStatusIndex++) { + timeToDeliveryToEnumValuesMapping.put( + pizzaStatuses[pizzaStatusIndex].getTimeToDelivery(), + pizzaStatuses[pizzaStatusIndex] + ); + } + } + + public static PizzaStatus castIntToEnum(int timeToDelivery) { + return timeToDeliveryToEnumValuesMapping.get(timeToDelivery); + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-lang-2/src/test/java/com/baeldung/inttoenum/IntToEnumUnitTest.java b/core-java-modules/core-java-lang-2/src/test/java/com/baeldung/inttoenum/IntToEnumUnitTest.java new file mode 100644 index 0000000000..876c230827 --- /dev/null +++ b/core-java-modules/core-java-lang-2/src/test/java/com/baeldung/inttoenum/IntToEnumUnitTest.java @@ -0,0 +1,27 @@ +package com.baeldung.inttoenum; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class IntToEnumUnitTest { + + @Test + public void whenIntToEnumUsingValuesMethod_thenReturnEnumObject() { + int timeToDeliveryForOrderedPizzaStatus = 5; + PizzaStatus[] pizzaStatuses = PizzaStatus.values(); + PizzaStatus pizzaOrderedStatus = null; + for (int pizzaStatusIndex = 0; pizzaStatusIndex < pizzaStatuses.length; pizzaStatusIndex++) { + if (pizzaStatuses[pizzaStatusIndex].getTimeToDelivery() == timeToDeliveryForOrderedPizzaStatus) { + pizzaOrderedStatus = pizzaStatuses[pizzaStatusIndex]; + } + } + assertEquals(pizzaOrderedStatus, PizzaStatus.ORDERED); + } + + @Test + public void whenIntToEnumUsingMap_thenReturnEnumObject() { + int timeToDeliveryForOrderedPizzaStatus = 5; + assertEquals(PizzaStatus.castIntToEnum(timeToDeliveryForOrderedPizzaStatus), PizzaStatus.ORDERED); + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-lang-oop-generics/src/main/java/com/baeldung/supertype/TypeReference.java b/core-java-modules/core-java-lang-oop-generics/src/main/java/com/baeldung/supertype/TypeReference.java new file mode 100644 index 0000000000..2021f42239 --- /dev/null +++ b/core-java-modules/core-java-lang-oop-generics/src/main/java/com/baeldung/supertype/TypeReference.java @@ -0,0 +1,18 @@ +package com.baeldung.supertype; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +public abstract class TypeReference { + + private final Type type; + + public TypeReference() { + Type superclass = getClass().getGenericSuperclass(); + type = ((ParameterizedType) superclass).getActualTypeArguments()[0]; + } + + public Type getType() { + return type; + } +} diff --git a/core-java-modules/core-java-lang-oop-generics/src/test/java/com/baeldung/supertype/TypeReferenceUnitTest.java b/core-java-modules/core-java-lang-oop-generics/src/test/java/com/baeldung/supertype/TypeReferenceUnitTest.java new file mode 100644 index 0000000000..24e3b698e2 --- /dev/null +++ b/core-java-modules/core-java-lang-oop-generics/src/test/java/com/baeldung/supertype/TypeReferenceUnitTest.java @@ -0,0 +1,24 @@ +package com.baeldung.supertype; + +import org.junit.Test; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Map; + +import static org.junit.Assert.assertEquals; + +public class TypeReferenceUnitTest { + + @Test + public void givenGenericToken_whenUsingSuperTypeToken_thenPreservesTheTypeInfo() { + TypeReference> token = new TypeReference>() {}; + Type type = token.getType(); + + assertEquals("java.util.Map", type.getTypeName()); + + Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments(); + assertEquals("java.lang.String", typeArguments[0].getTypeName()); + assertEquals("java.lang.Integer", typeArguments[1].getTypeName()); + } +} diff --git a/core-kotlin-modules/core-kotlin-2/src/test/kotlin/com/baeldung/nullassertion/NotNullAssertionUnitTest.kt b/core-kotlin-modules/core-kotlin-2/src/test/kotlin/com/baeldung/nullassertion/NotNullAssertionUnitTest.kt new file mode 100644 index 0000000000..434f177927 --- /dev/null +++ b/core-kotlin-modules/core-kotlin-2/src/test/kotlin/com/baeldung/nullassertion/NotNullAssertionUnitTest.kt @@ -0,0 +1,20 @@ +package com.baeldung.nullassertion + +import org.junit.Test +import kotlin.test.assertEquals + +class NotNullAssertionUnitTest { + + @Test + fun givenNullableValue_WhenNotNull_ShouldExtractTheValue() { + val answer: String? = "42" + + assertEquals(42, answer!!.toInt()) + } + + @Test(expected = KotlinNullPointerException::class) + fun givenNullableValue_WhenIsNull_ThenShouldThrow() { + val noAnswer: String? = null + noAnswer!! + } +} diff --git a/core-kotlin-modules/core-kotlin-strings/src/test/kotlin/com/baeldung/stringcomparison/StringComparisonTest.kt b/core-kotlin-modules/core-kotlin-strings/src/test/kotlin/com/baeldung/stringcomparison/StringComparisonTest.kt index 9528f62df5..49ff798faa 100644 --- a/core-kotlin-modules/core-kotlin-strings/src/test/kotlin/com/baeldung/stringcomparison/StringComparisonTest.kt +++ b/core-kotlin-modules/core-kotlin-strings/src/test/kotlin/com/baeldung/stringcomparison/StringComparisonTest.kt @@ -19,9 +19,9 @@ class StringComparisonUnitTest { fun `compare using referential equals operator`() { val first = "kotlin" val second = "kotlin" - val copyOfFirst = buildString { "kotlin" } + val third = String("kotlin".toCharArray()) assertTrue { first === second } - assertFalse { first === copyOfFirst } + assertFalse { first === third } } @Test diff --git a/jee-kotlin/pom.xml b/jee-kotlin/pom.xml index 9191885bd4..45d5d8ece1 100644 --- a/jee-kotlin/pom.xml +++ b/jee-kotlin/pom.xml @@ -221,6 +221,7 @@ org.jboss.logmanager.LogManager ${project.basedir}/target/wildfly-${wildfly.version} + 8756 ${project.basedir}/target/wildfly-${wildfly.version}/modules false @@ -278,9 +279,10 @@ 2.0.1.Final 1.0.0.Alpha4 + 1.1.7 + 3.8.0.Final 3.1.3 - diff --git a/jee-kotlin/src/main/resources/META-INF/persistence.xml b/jee-kotlin/src/main/resources/META-INF/persistence.xml index daac86868b..0093792810 100644 --- a/jee-kotlin/src/main/resources/META-INF/persistence.xml +++ b/jee-kotlin/src/main/resources/META-INF/persistence.xml @@ -7,7 +7,7 @@ java:jboss/datasources/ExampleDS - com.enpy.entity.Student + com.baeldung.jeekotlin.entity.Student diff --git a/jhipster/jhipster-uaa/gateway/src/main/java/com/baeldung/jhipster/gateway/web/rest/errors/ExceptionTranslator.java b/jhipster/jhipster-uaa/gateway/src/main/java/com/baeldung/jhipster/gateway/web/rest/errors/ExceptionTranslator.java index d5c9e577a6..fd6e04dadc 100644 --- a/jhipster/jhipster-uaa/gateway/src/main/java/com/baeldung/jhipster/gateway/web/rest/errors/ExceptionTranslator.java +++ b/jhipster/jhipster-uaa/gateway/src/main/java/com/baeldung/jhipster/gateway/web/rest/errors/ExceptionTranslator.java @@ -14,6 +14,7 @@ import org.zalando.problem.Problem; import org.zalando.problem.ProblemBuilder; import org.zalando.problem.Status; import org.zalando.problem.spring.web.advice.ProblemHandling; +import org.zalando.problem.spring.web.advice.security.SecurityAdviceTrait; import org.zalando.problem.violations.ConstraintViolationProblem; import javax.annotation.Nonnull; @@ -28,7 +29,7 @@ import java.util.stream.Collectors; * The error response follows RFC7807 - Problem Details for HTTP APIs (https://tools.ietf.org/html/rfc7807) */ @ControllerAdvice -public class ExceptionTranslator implements ProblemHandling { +public class ExceptionTranslator implements ProblemHandling, SecurityAdviceTrait { /** * Post-process the Problem payload to add the message key for the front-end if needed diff --git a/jhipster/jhipster-uaa/quotes/src/main/java/com/baeldung/jhipster/quotes/web/rest/errors/ExceptionTranslator.java b/jhipster/jhipster-uaa/quotes/src/main/java/com/baeldung/jhipster/quotes/web/rest/errors/ExceptionTranslator.java index 18baa42736..3bf4995405 100644 --- a/jhipster/jhipster-uaa/quotes/src/main/java/com/baeldung/jhipster/quotes/web/rest/errors/ExceptionTranslator.java +++ b/jhipster/jhipster-uaa/quotes/src/main/java/com/baeldung/jhipster/quotes/web/rest/errors/ExceptionTranslator.java @@ -14,6 +14,7 @@ import org.zalando.problem.Problem; import org.zalando.problem.ProblemBuilder; import org.zalando.problem.Status; import org.zalando.problem.spring.web.advice.ProblemHandling; +import org.zalando.problem.spring.web.advice.security.SecurityAdviceTrait; import org.zalando.problem.violations.ConstraintViolationProblem; import javax.annotation.Nonnull; @@ -28,7 +29,7 @@ import java.util.stream.Collectors; * The error response follows RFC7807 - Problem Details for HTTP APIs (https://tools.ietf.org/html/rfc7807) */ @ControllerAdvice -public class ExceptionTranslator implements ProblemHandling { +public class ExceptionTranslator implements ProblemHandling, SecurityAdviceTrait { /** * Post-process the Problem payload to add the message key for the front-end if needed diff --git a/jhipster/jhipster-uaa/uaa/src/main/java/com/baeldung/jhipster/uaa/web/rest/errors/ExceptionTranslator.java b/jhipster/jhipster-uaa/uaa/src/main/java/com/baeldung/jhipster/uaa/web/rest/errors/ExceptionTranslator.java index 320636c51d..6af9fd126c 100644 --- a/jhipster/jhipster-uaa/uaa/src/main/java/com/baeldung/jhipster/uaa/web/rest/errors/ExceptionTranslator.java +++ b/jhipster/jhipster-uaa/uaa/src/main/java/com/baeldung/jhipster/uaa/web/rest/errors/ExceptionTranslator.java @@ -14,6 +14,7 @@ import org.zalando.problem.Problem; import org.zalando.problem.ProblemBuilder; import org.zalando.problem.Status; import org.zalando.problem.spring.web.advice.ProblemHandling; +import org.zalando.problem.spring.web.advice.security.SecurityAdviceTrait; import org.zalando.problem.violations.ConstraintViolationProblem; import javax.annotation.Nonnull; @@ -28,7 +29,7 @@ import java.util.stream.Collectors; * The error response follows RFC7807 - Problem Details for HTTP APIs (https://tools.ietf.org/html/rfc7807) */ @ControllerAdvice -public class ExceptionTranslator implements ProblemHandling { +public class ExceptionTranslator implements ProblemHandling, SecurityAdviceTrait { /** * Post-process the Problem payload to add the message key for the front-end if needed diff --git a/jmh/src/main/java/com/baeldung/BenchMark.java b/jmh/src/main/java/com/baeldung/BenchMark.java index b0e1caf4dc..3c5c840db2 100644 --- a/jmh/src/main/java/com/baeldung/BenchMark.java +++ b/jmh/src/main/java/com/baeldung/BenchMark.java @@ -1,14 +1,20 @@ package com.baeldung; -import com.google.common.hash.HashFunction; import com.google.common.hash.Hasher; import com.google.common.hash.Hashing; import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; import java.nio.charset.Charset; +import java.util.concurrent.TimeUnit; public class BenchMark { + @State(Scope.Benchmark) + public static class Log { + public int x = 8; + } + @State(Scope.Benchmark) public static class ExecutionPlan { @@ -45,4 +51,44 @@ public class BenchMark { // Do nothing } + @Benchmark + @OutputTimeUnit(TimeUnit.NANOSECONDS) + @BenchmarkMode(Mode.AverageTime) + public void doNothing() { + + } + + @Benchmark + @OutputTimeUnit(TimeUnit.NANOSECONDS) + @BenchmarkMode(Mode.AverageTime) + public void objectCreation() { + new Object(); + } + + @Benchmark + @OutputTimeUnit(TimeUnit.NANOSECONDS) + @BenchmarkMode(Mode.AverageTime) + public Object pillarsOfCreation() { + return new Object(); + } + + @Benchmark + @OutputTimeUnit(TimeUnit.NANOSECONDS) + @BenchmarkMode(Mode.AverageTime) + public void blackHole(Blackhole blackhole) { + blackhole.consume(new Object()); + } + + @Benchmark + public double foldedLog() { + int x = 8; + + return Math.log(x); + } + + @Benchmark + public double log(Log input) { + return Math.log(input.x); + } + } diff --git a/language-interop/README.md b/language-interop/README.md index a28c4a5405..458f34a119 100644 --- a/language-interop/README.md +++ b/language-interop/README.md @@ -3,3 +3,5 @@ This module contains articles about Java interop with other language integrations. ### Relevant Articles: + +- [How to Call Python From Java](https://www.baeldung.com/java-working-with-python) diff --git a/libraries-data-2/pom.xml b/libraries-data-2/pom.xml index cbb24edd3f..bdfb2c5ed6 100644 --- a/libraries-data-2/pom.xml +++ b/libraries-data-2/pom.xml @@ -121,6 +121,11 @@ univocity-parsers ${univocity.version} + + org.apache.kafka + kafka-clients + ${kafka.version} + org.awaitility awaitility @@ -184,6 +189,7 @@ RELEASE 3.0 1.8.1 + 2.5.0 diff --git a/libraries-data-2/src/main/java/com/baeldung/kafka/consumer/CountryPopulation.java b/libraries-data-2/src/main/java/com/baeldung/kafka/consumer/CountryPopulation.java new file mode 100644 index 0000000000..8c1351642f --- /dev/null +++ b/libraries-data-2/src/main/java/com/baeldung/kafka/consumer/CountryPopulation.java @@ -0,0 +1,28 @@ +package com.baeldung.kafka.consumer; + +class CountryPopulation { + + private String country; + private Integer population; + + public CountryPopulation(String country, Integer population) { + this.country = country; + this.population = population; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public Integer getPopulation() { + return population; + } + + public void setPopulation(Integer population) { + this.population = population; + } +} \ No newline at end of file diff --git a/libraries-data-2/src/main/java/com/baeldung/kafka/consumer/CountryPopulationConsumer.java b/libraries-data-2/src/main/java/com/baeldung/kafka/consumer/CountryPopulationConsumer.java new file mode 100644 index 0000000000..ba4dfe6f3b --- /dev/null +++ b/libraries-data-2/src/main/java/com/baeldung/kafka/consumer/CountryPopulationConsumer.java @@ -0,0 +1,60 @@ +package com.baeldung.kafka.consumer; + +import java.time.Duration; +import java.util.Collections; +import java.util.stream.StreamSupport; + +import org.apache.kafka.clients.consumer.Consumer; +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.apache.kafka.common.TopicPartition; +import org.apache.kafka.common.errors.WakeupException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CountryPopulationConsumer { + + private static Logger logger = LoggerFactory.getLogger(CountryPopulationConsumer.class); + + private Consumer consumer; + private java.util.function.Consumer exceptionConsumer; + private java.util.function.Consumer countryPopulationConsumer; + + public CountryPopulationConsumer( + Consumer consumer, java.util.function.Consumer exceptionConsumer, + java.util.function.Consumer countryPopulationConsumer) { + this.consumer = consumer; + this.exceptionConsumer = exceptionConsumer; + this.countryPopulationConsumer = countryPopulationConsumer; + } + + void startBySubscribing(String topic) { + consume(() -> consumer.subscribe(Collections.singleton(topic))); + } + + void startByAssigning(String topic, int partition) { + consume(() -> consumer.assign(Collections.singleton(new TopicPartition(topic, partition)))); + } + + private void consume(Runnable beforePollingTask) { + try { + beforePollingTask.run(); + while (true) { + ConsumerRecords records = consumer.poll(Duration.ofMillis(1000)); + StreamSupport.stream(records.spliterator(), false) + .map(record -> new CountryPopulation(record.key(), record.value())) + .forEach(countryPopulationConsumer); + consumer.commitSync(); + } + } catch (WakeupException e) { + logger.info("Shutting down..."); + } catch (RuntimeException ex) { + exceptionConsumer.accept(ex); + } finally { + consumer.close(); + } + } + + public void stop() { + consumer.wakeup(); + } +} \ No newline at end of file diff --git a/libraries-data-2/src/test/java/com/baeldung/kafka/consumer/CountryPopulationConsumerUnitTest.java b/libraries-data-2/src/test/java/com/baeldung/kafka/consumer/CountryPopulationConsumerUnitTest.java new file mode 100644 index 0000000000..1b49c71716 --- /dev/null +++ b/libraries-data-2/src/test/java/com/baeldung/kafka/consumer/CountryPopulationConsumerUnitTest.java @@ -0,0 +1,100 @@ +package com.baeldung.kafka.consumer; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.clients.consumer.MockConsumer; +import org.apache.kafka.clients.consumer.OffsetResetStrategy; +import org.apache.kafka.common.KafkaException; +import org.apache.kafka.common.TopicPartition; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class CountryPopulationConsumerUnitTest { + + private static final String TOPIC = "topic"; + private static final int PARTITION = 0; + + private CountryPopulationConsumer countryPopulationConsumer; + + private List updates; + private Throwable pollException; + + private MockConsumer consumer; + + @BeforeEach + void setUp() { + consumer = new MockConsumer<>(OffsetResetStrategy.EARLIEST); + updates = new ArrayList<>(); + countryPopulationConsumer = new CountryPopulationConsumer(consumer, ex -> this.pollException = ex, updates::add); + } + + @Test + void whenStartingByAssigningTopicPartition_thenExpectUpdatesAreConsumedCorrectly() { + // GIVEN + consumer.schedulePollTask(() -> consumer.addRecord(record(TOPIC, PARTITION, "Romania", 19_410_000))); + consumer.schedulePollTask(() -> countryPopulationConsumer.stop()); + + HashMap startOffsets = new HashMap<>(); + TopicPartition tp = new TopicPartition(TOPIC, PARTITION); + startOffsets.put(tp, 0L); + consumer.updateBeginningOffsets(startOffsets); + + // WHEN + countryPopulationConsumer.startByAssigning(TOPIC, PARTITION); + + // THEN + assertThat(updates).hasSize(1); + assertThat(consumer.closed()).isTrue(); + } + + @Test + void whenStartingBySubscribingToTopic_thenExpectUpdatesAreConsumedCorrectly() { + // GIVEN + consumer.schedulePollTask(() -> { + consumer.rebalance(Collections.singletonList(new TopicPartition(TOPIC, 0))); + consumer.addRecord(record(TOPIC, PARTITION, "Romania", 19_410_000)); + }); + consumer.schedulePollTask(() -> countryPopulationConsumer.stop()); + + HashMap startOffsets = new HashMap<>(); + TopicPartition tp = new TopicPartition(TOPIC, PARTITION); + startOffsets.put(tp, 0L); + consumer.updateBeginningOffsets(startOffsets); + + // WHEN + countryPopulationConsumer.startBySubscribing(TOPIC); + + // THEN + assertThat(updates).hasSize(1); + assertThat(consumer.closed()).isTrue(); + } + + @Test + void whenStartingBySubscribingToTopicAndExceptionOccurs_thenExpectExceptionIsHandledCorrectly() { + // GIVEN + consumer.schedulePollTask(() -> consumer.setPollException(new KafkaException("poll exception"))); + consumer.schedulePollTask(() -> countryPopulationConsumer.stop()); + + HashMap startOffsets = new HashMap<>(); + TopicPartition tp = new TopicPartition(TOPIC, 0); + startOffsets.put(tp, 0L); + consumer.updateBeginningOffsets(startOffsets); + + // WHEN + countryPopulationConsumer.startBySubscribing(TOPIC); + + // THEN + assertThat(pollException).isInstanceOf(KafkaException.class).hasMessage("poll exception"); + assertThat(consumer.closed()).isTrue(); + } + + private ConsumerRecord record(String topic, int partition, String country, int population) { + return new ConsumerRecord<>(topic, partition, 0, country, population); + } +} \ No newline at end of file diff --git a/libraries-data-db/pom.xml b/libraries-data-db/pom.xml index f028ffe8c3..d51580ccbc 100644 --- a/libraries-data-db/pom.xml +++ b/libraries-data-db/pom.xml @@ -211,7 +211,7 @@ 5.0.2 5.0.4 3.2.0-m7 - 2.7.2 + 3.4.5 11.22.4 diff --git a/libraries-rpc/README.md b/libraries-rpc/README.md new file mode 100644 index 0000000000..472aa883ad --- /dev/null +++ b/libraries-rpc/README.md @@ -0,0 +1,3 @@ +### Relevant Articles: + +- [Introduction to Finagle](https://www.baeldung.com/java-finagle) diff --git a/libraries-server-2/.gitignore b/libraries-server-2/.gitignore new file mode 100644 index 0000000000..e594daf27a --- /dev/null +++ b/libraries-server-2/.gitignore @@ -0,0 +1,9 @@ +*.class + +# Folders # +/gensrc +/target + +# Packaged files # +*.jar +/bin/ diff --git a/libraries-server-2/README.md b/libraries-server-2/README.md new file mode 100644 index 0000000000..38166bcd77 --- /dev/null +++ b/libraries-server-2/README.md @@ -0,0 +1,8 @@ +## Server + +This module contains articles about server libraries. + +### Relevant Articles: + +- [HTTP/2 in Jetty](https://www.baeldung.com/jetty-http-2) +- More articles: [[<-- prev]](../libraries-server) diff --git a/libraries-server-2/pom.xml b/libraries-server-2/pom.xml new file mode 100644 index 0000000000..5f500a7ced --- /dev/null +++ b/libraries-server-2/pom.xml @@ -0,0 +1,77 @@ + + + 4.0.0 + libraries-server-2 + 0.0.1-SNAPSHOT + libraries-server-2 + war + + + com.baeldung + parent-modules + 1.0.0-SNAPSHOT + + + + + org.eclipse.jetty + jetty-server + ${jetty.version} + + + org.eclipse.jetty + jetty-servlet + ${jetty.version} + + + org.eclipse.jetty + jetty-webapp + ${jetty.version} + + + + + + + org.eclipse.jetty + jetty-maven-plugin + ${jetty.version} + + 8888 + quit + + -Xbootclasspath/p:${settings.localRepository}/org/mortbay/jetty/alpn/alpn-boot/${alpn.version}/alpn-boot-${alpn.version}.jar + + ${basedir}/src/main/config/jetty.xml + + / + + + + + org.eclipse.jetty.http2 + http2-server + ${jetty.version} + + + org.eclipse.jetty + jetty-alpn-openjdk8-server + ${jetty.version} + + + org.eclipse.jetty + jetty-servlets + ${jetty.version} + + + + + + + + 9.4.27.v20200227 + 8.1.11.v20170118 + + + \ No newline at end of file diff --git a/libraries-server/src/main/config/jetty.xml b/libraries-server-2/src/main/config/jetty.xml similarity index 100% rename from libraries-server/src/main/config/jetty.xml rename to libraries-server-2/src/main/config/jetty.xml diff --git a/libraries-server/src/main/java/com/baeldung/jetty/http2/Http2JettyServlet.java b/libraries-server-2/src/main/java/com/baeldung/jetty/http2/Http2JettyServlet.java similarity index 100% rename from libraries-server/src/main/java/com/baeldung/jetty/http2/Http2JettyServlet.java rename to libraries-server-2/src/main/java/com/baeldung/jetty/http2/Http2JettyServlet.java diff --git a/libraries-server/src/main/resources/keystore.jks b/libraries-server-2/src/main/resources/keystore.jks similarity index 100% rename from libraries-server/src/main/resources/keystore.jks rename to libraries-server-2/src/main/resources/keystore.jks diff --git a/libraries-server-2/src/main/resources/logback.xml b/libraries-server-2/src/main/resources/logback.xml new file mode 100644 index 0000000000..7d900d8ea8 --- /dev/null +++ b/libraries-server-2/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/libraries-server/src/main/resources/truststore.jks b/libraries-server-2/src/main/resources/truststore.jks similarity index 100% rename from libraries-server/src/main/resources/truststore.jks rename to libraries-server-2/src/main/resources/truststore.jks diff --git a/libraries-server/src/main/webapp/WEB-INF/web.xml b/libraries-server-2/src/main/webapp/WEB-INF/web.xml similarity index 100% rename from libraries-server/src/main/webapp/WEB-INF/web.xml rename to libraries-server-2/src/main/webapp/WEB-INF/web.xml diff --git a/libraries-server/src/main/webapp/http2.html b/libraries-server-2/src/main/webapp/http2.html similarity index 100% rename from libraries-server/src/main/webapp/http2.html rename to libraries-server-2/src/main/webapp/http2.html diff --git a/libraries-server/src/main/webapp/images/homepage-latest_articles.jpg b/libraries-server-2/src/main/webapp/images/homepage-latest_articles.jpg similarity index 100% rename from libraries-server/src/main/webapp/images/homepage-latest_articles.jpg rename to libraries-server-2/src/main/webapp/images/homepage-latest_articles.jpg diff --git a/libraries-server/src/main/webapp/images/homepage-rest_with_spring.jpg b/libraries-server-2/src/main/webapp/images/homepage-rest_with_spring.jpg similarity index 100% rename from libraries-server/src/main/webapp/images/homepage-rest_with_spring.jpg rename to libraries-server-2/src/main/webapp/images/homepage-rest_with_spring.jpg diff --git a/libraries-server/src/main/webapp/images/homepage-weekly_reviews.jpg b/libraries-server-2/src/main/webapp/images/homepage-weekly_reviews.jpg similarity index 100% rename from libraries-server/src/main/webapp/images/homepage-weekly_reviews.jpg rename to libraries-server-2/src/main/webapp/images/homepage-weekly_reviews.jpg diff --git a/libraries-server/src/main/webapp/index.html b/libraries-server-2/src/main/webapp/index.html similarity index 100% rename from libraries-server/src/main/webapp/index.html rename to libraries-server-2/src/main/webapp/index.html diff --git a/libraries-server/README.md b/libraries-server/README.md index 7e41f33a0c..570806611f 100644 --- a/libraries-server/README.md +++ b/libraries-server/README.md @@ -13,4 +13,4 @@ This module contains articles about server libraries. - [MQTT Client in Java](https://www.baeldung.com/java-mqtt-client) - [Guide to XMPP Smack Client](https://www.baeldung.com/xmpp-smack-chat-client) - [A Guide to NanoHTTPD](https://www.baeldung.com/nanohttpd) -- [HTTP/2 in Jetty](https://www.baeldung.com/jetty-http-2) +- More articles: [[more -->]](../libraries-server-2) \ No newline at end of file diff --git a/libraries-server/pom.xml b/libraries-server/pom.xml index eb9cb61e56..d9546f1678 100644 --- a/libraries-server/pom.xml +++ b/libraries-server/pom.xml @@ -5,7 +5,6 @@ libraries-server 0.0.1-SNAPSHOT libraries-server - war com.baeldung @@ -107,50 +106,11 @@ - - - - org.eclipse.jetty - jetty-maven-plugin - ${jetty.version} - - 8888 - quit - - -Xbootclasspath/p:${settings.localRepository}/org/mortbay/jetty/alpn/alpn-boot/${alpn.version}/alpn-boot-${alpn.version}.jar - - ${basedir}/src/main/config/jetty.xml - - / - - - - - org.eclipse.jetty.http2 - http2-server - ${jetty.version} - - - org.eclipse.jetty - jetty-alpn-openjdk8-server - ${jetty.version} - - - org.eclipse.jetty - jetty-servlets - ${jetty.version} - - - - - - 3.6.2 4.5.3 9.4.27.v20200227 4.1.20.Final - 8.1.11.v20170118 8.5.24 4.3.1 1.2.0 diff --git a/netflix-modules/genie/README.md b/netflix-modules/genie/README.md new file mode 100644 index 0000000000..f6e15ba403 --- /dev/null +++ b/netflix-modules/genie/README.md @@ -0,0 +1,3 @@ +### Relevant Articles: + +- [Introduction to Netflix Genie](https://www.baeldung.com/netflix-genie-intro) diff --git a/persistence-modules/hibernate-libraries/src/test/java/com/baeldung/hibernate/types/HibernateTypesIntegrationTest.java b/persistence-modules/hibernate-libraries/src/test/java/com/baeldung/hibernate/types/HibernateTypesLiveTest.java similarity index 99% rename from persistence-modules/hibernate-libraries/src/test/java/com/baeldung/hibernate/types/HibernateTypesIntegrationTest.java rename to persistence-modules/hibernate-libraries/src/test/java/com/baeldung/hibernate/types/HibernateTypesLiveTest.java index 9d7479e77d..4b551386ad 100644 --- a/persistence-modules/hibernate-libraries/src/test/java/com/baeldung/hibernate/types/HibernateTypesIntegrationTest.java +++ b/persistence-modules/hibernate-libraries/src/test/java/com/baeldung/hibernate/types/HibernateTypesLiveTest.java @@ -13,7 +13,7 @@ import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest -public class HibernateTypesIntegrationTest { +public class HibernateTypesLiveTest { @Autowired AlbumRepository albumRepository; diff --git a/persistence-modules/redis/README.md b/persistence-modules/redis/README.md index 71d009241a..75c1c18de4 100644 --- a/persistence-modules/redis/README.md +++ b/persistence-modules/redis/README.md @@ -4,3 +4,4 @@ - [Introduction to Lettuce – the Java Redis Client](https://www.baeldung.com/java-redis-lettuce) - [List All Available Redis Keys](https://www.baeldung.com/redis-list-available-keys) - [Spring Data Redis’s Property-Based Configuration](https://www.baeldung.com/spring-data-redis-properties) +- [Delete Everything in Redis](https://www.baeldung.com/redis-delete-data) diff --git a/persistence-modules/redis/pom.xml b/persistence-modules/redis/pom.xml index c9206e5f92..dab7fc5654 100644 --- a/persistence-modules/redis/pom.xml +++ b/persistence-modules/redis/pom.xml @@ -33,7 +33,8 @@ redis.clients jedis - + 3.3.0 + com.github.kstyrc embedded-redis diff --git a/persistence-modules/redis/src/test/java/com/baeldung/redis/deleteeverything/DeleteEverythingInRedisIntegrationTest.java b/persistence-modules/redis/src/test/java/com/baeldung/redis/deleteeverything/DeleteEverythingInRedisIntegrationTest.java new file mode 100644 index 0000000000..e0376fc6a5 --- /dev/null +++ b/persistence-modules/redis/src/test/java/com/baeldung/redis/deleteeverything/DeleteEverythingInRedisIntegrationTest.java @@ -0,0 +1,91 @@ +package com.baeldung.redis.deleteeverything; + +import org.junit.*; +import redis.clients.jedis.Jedis; +import redis.embedded.RedisServer; + +import java.io.IOException; +import java.net.ServerSocket; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class DeleteEverythingInRedisIntegrationTest { + private Jedis jedis; + private RedisServer redisServer; + private int port; + + @Before + public void setUp() throws IOException { + + // Take an available port + ServerSocket s = new ServerSocket(0); + port = s.getLocalPort(); + s.close(); + + redisServer = new RedisServer(port); + redisServer.start(); + + // Configure JEDIS + jedis = new Jedis("localhost", port); + } + + @After + public void destroy() { + redisServer.stop(); + } + + @Test + public void whenPutDataIntoRedis_thenCanBeFound() { + String key = "key"; + String value = "value"; + + jedis.set(key, value); + String received = jedis.get(key); + + assertEquals(value, received); + } + + @Test + public void whenPutDataIntoRedisAndThenFlush_thenCannotBeFound() { + String key = "key"; + String value = "value"; + + jedis.set(key, value); + + jedis.flushDB(); + + String received = jedis.get(key); + + assertNull(received); + } + + @Test + public void whenPutDataIntoMultipleDatabases_thenFlushAllRemovesAll() { + // add keys in different databases + jedis.select(0); + jedis.set("key1", "value1"); + jedis.select(1); + jedis.set("key2", "value2"); + + // we'll find the correct keys in the correct dbs + jedis.select(0); + assertEquals("value1", jedis.get("key1")); + assertNull(jedis.get("key2")); + + jedis.select(1); + assertEquals("value2", jedis.get("key2")); + assertNull(jedis.get("key1")); + + // then, when we flush + jedis.flushAll(); + + // the keys will have gone + jedis.select(0); + assertNull(jedis.get("key1")); + assertNull(jedis.get("key2")); + jedis.select(1); + assertNull(jedis.get("key1")); + assertNull(jedis.get("key2")); + } +} diff --git a/persistence-modules/spring-boot-persistence-2/README.md b/persistence-modules/spring-boot-persistence-2/README.md index e0479ffe10..392218d2bf 100644 --- a/persistence-modules/spring-boot-persistence-2/README.md +++ b/persistence-modules/spring-boot-persistence-2/README.md @@ -3,6 +3,6 @@ - [Using JDBI with Spring Boot](https://www.baeldung.com/spring-boot-jdbi) - [Configuring a Tomcat Connection Pool in Spring Boot](https://www.baeldung.com/spring-boot-tomcat-connection-pool) - [Integrating Spring Boot with HSQLDB](https://www.baeldung.com/spring-boot-hsqldb) -- [List of In-Memory Databases](http://www.baeldung.com/java-in-memory-databases) +- [List of In-Memory Databases](https://www.baeldung.com/java-in-memory-databases) - [Oracle Connection Pooling With Spring](https://www.baeldung.com/spring-oracle-connection-pooling) - More articles: [[<-- prev]](../spring-boot-persistence) diff --git a/persistence-modules/spring-boot-persistence-2/src/test/java/com/baeldung/tomcatconnectionpool/test/application/SpringBootTomcatConnectionPoolIntegrationTest.java b/persistence-modules/spring-boot-persistence-2/src/test/java/com/baeldung/tomcatconnectionpool/test/application/SpringBootTomcatConnectionPoolIntegrationTest.java index 5400d76fbe..4422c27150 100644 --- a/persistence-modules/spring-boot-persistence-2/src/test/java/com/baeldung/tomcatconnectionpool/test/application/SpringBootTomcatConnectionPoolIntegrationTest.java +++ b/persistence-modules/spring-boot-persistence-2/src/test/java/com/baeldung/tomcatconnectionpool/test/application/SpringBootTomcatConnectionPoolIntegrationTest.java @@ -4,6 +4,7 @@ import javax.sql.DataSource; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringRunner; import com.baeldung.tomcatconnectionpool.application.SpringBootConsoleApplication; @@ -13,6 +14,7 @@ import org.springframework.boot.test.context.SpringBootTest; @RunWith(SpringRunner.class) @SpringBootTest(classes = {SpringBootConsoleApplication.class}) +@TestPropertySource(properties = "spring.datasource.type=org.apache.tomcat.jdbc.pool.DataSource") public class SpringBootTomcatConnectionPoolIntegrationTest { @Autowired diff --git a/persistence-modules/spring-boot-persistence-h2/pom.xml b/persistence-modules/spring-boot-persistence-h2/pom.xml index 777bc6cb2d..7070b5e674 100644 --- a/persistence-modules/spring-boot-persistence-h2/pom.xml +++ b/persistence-modules/spring-boot-persistence-h2/pom.xml @@ -35,23 +35,21 @@ ${lombok.version} compile - - org.hibernate - hibernate-core - ${hibernate.version} - com.vladmihalcea db-util ${db-util.version} + + net.bytebuddy + byte-buddy + ${byte-buddy.version} + com.baeldung.h2db.demo.server.SpringBootApp - 2.0.4.RELEASE - 5.3.11.Final 1.0.4 diff --git a/persistence-modules/spring-boot-persistence-h2/src/main/resources/application.properties b/persistence-modules/spring-boot-persistence-h2/src/main/resources/application.properties index ed1ffc63c3..0466eaac79 100644 --- a/persistence-modules/spring-boot-persistence-h2/src/main/resources/application.properties +++ b/persistence-modules/spring-boot-persistence-h2/src/main/resources/application.properties @@ -8,5 +8,4 @@ spring.jpa.properties.hibernate.format_sql=true spring.jpa.properties.hibernate.validator.apply_to_ddl=false #spring.jpa.properties.hibernate.check_nullability=true spring.h2.console.enabled=true -spring.h2.console.path=/h2-console -debug=true \ No newline at end of file +spring.h2.console.path=/h2-console \ No newline at end of file diff --git a/persistence-modules/spring-boot-persistence/README.MD b/persistence-modules/spring-boot-persistence/README.MD index d6ef239448..5b9fbf7b79 100644 --- a/persistence-modules/spring-boot-persistence/README.MD +++ b/persistence-modules/spring-boot-persistence/README.MD @@ -1,8 +1,8 @@ ### Relevant Articles: -- [Spring Boot with Multiple SQL Import Files](http://www.baeldung.com/spring-boot-sql-import-files) -- [Configuring Separate Spring DataSource for Tests](http://www.baeldung.com/spring-testing-separate-data-source) -- [Quick Guide on Loading Initial Data with Spring Boot](http://www.baeldung.com/spring-boot-data-sql-and-schema-sql) +- [Spring Boot with Multiple SQL Import Files](https://www.baeldung.com/spring-boot-sql-import-files) +- [Configuring Separate Spring DataSource for Tests](https://www.baeldung.com/spring-testing-separate-data-source) +- [Quick Guide on Loading Initial Data with Spring Boot](https://www.baeldung.com/spring-boot-data-sql-and-schema-sql) - [Configuring a DataSource Programmatically in Spring Boot](https://www.baeldung.com/spring-boot-configure-data-source-programmatic) - [Resolving “Failed to Configure a DataSource” Error](https://www.baeldung.com/spring-boot-failed-to-configure-data-source) - [Hibernate Field Naming with Spring Boot](https://www.baeldung.com/hibernate-field-naming-spring-boot) diff --git a/persistence-modules/spring-data-elasticsearch/pom.xml b/persistence-modules/spring-data-elasticsearch/pom.xml index 3446528323..6a983145ee 100644 --- a/persistence-modules/spring-data-elasticsearch/pom.xml +++ b/persistence-modules/spring-data-elasticsearch/pom.xml @@ -15,13 +15,7 @@ org.springframework - spring-core - ${spring.version} - - - - org.springframework - spring-context + spring-web ${spring.version} @@ -36,6 +30,7 @@ elasticsearch ${elasticsearch.version} + com.alibaba fastjson @@ -49,8 +44,8 @@ - com.vividsolutions - jts + org.locationtech.jts + jts-core ${jts.version} @@ -60,41 +55,19 @@ - - org.apache.logging.log4j - log4j-core - ${log4j.version} - - - - org.elasticsearch.client - transport - ${elasticsearch.version} - - org.springframework spring-test ${spring.version} test - - - net.java.dev.jna - jna - ${jna.version} - test - - 3.0.8.RELEASE - 4.5.2 - 5.6.0 + 4.0.0.RELEASE + 7.6.2 1.2.47 - 0.6 - 1.13 - 2.9.1 + 0.7 + 1.15.0 - \ No newline at end of file diff --git a/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/config/Config.java b/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/config/Config.java index e6ce795b45..51bbe73e9e 100644 --- a/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/config/Config.java +++ b/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/config/Config.java @@ -1,19 +1,13 @@ package com.baeldung.spring.data.es.config; -import java.net.InetAddress; -import java.net.UnknownHostException; - -import org.elasticsearch.client.Client; -import org.elasticsearch.client.transport.TransportClient; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.transport.InetSocketTransportAddress; -import org.elasticsearch.transport.client.PreBuiltTransportClient; -import org.springframework.beans.factory.annotation.Value; +import org.elasticsearch.client.RestHighLevelClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; +import org.springframework.data.elasticsearch.client.ClientConfiguration; +import org.springframework.data.elasticsearch.client.RestClients; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; -import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; +import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; @Configuration @@ -21,30 +15,18 @@ import org.springframework.data.elasticsearch.repository.config.EnableElasticsea @ComponentScan(basePackages = { "com.baeldung.spring.data.es.service" }) public class Config { - @Value("${elasticsearch.home:/usr/local/Cellar/elasticsearch/5.6.0}") - private String elasticsearchHome; - - @Value("${elasticsearch.cluster.name:elasticsearch}") - private String clusterName; - @Bean - public Client client() { - TransportClient client = null; - try { - final Settings elasticsearchSettings = Settings.builder() - .put("client.transport.sniff", true) - .put("path.home", elasticsearchHome) - .put("cluster.name", clusterName).build(); - client = new PreBuiltTransportClient(elasticsearchSettings); - client.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("127.0.0.1"), 9300)); - } catch (UnknownHostException e) { - e.printStackTrace(); - } - return client; + RestHighLevelClient client() { + ClientConfiguration clientConfiguration = ClientConfiguration.builder() + .connectedTo("localhost:9200") + .build(); + + return RestClients.create(clientConfiguration) + .rest(); } @Bean public ElasticsearchOperations elasticsearchTemplate() { - return new ElasticsearchTemplate(client()); + return new ElasticsearchRestTemplate(client()); } } diff --git a/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/model/Author.java b/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/model/Author.java index 38f50e1614..1d596cd92b 100644 --- a/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/model/Author.java +++ b/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/model/Author.java @@ -1,7 +1,12 @@ package com.baeldung.spring.data.es.model; +import static org.springframework.data.elasticsearch.annotations.FieldType.Text; + +import org.springframework.data.elasticsearch.annotations.Field; + public class Author { + @Field(type = Text) private String name; public Author() { diff --git a/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/service/ArticleService.java b/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/service/ArticleService.java deleted file mode 100644 index a0f72aa5f7..0000000000 --- a/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/service/ArticleService.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.baeldung.spring.data.es.service; - -import java.util.Optional; - -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; - -import com.baeldung.spring.data.es.model.Article; - -public interface ArticleService { - Article save(Article article); - - Optional
findOne(String id); - - Iterable
findAll(); - - Page
findByAuthorName(String name, Pageable pageable); - - Page
findByAuthorNameUsingCustomQuery(String name, Pageable pageable); - - Page
findByFilteredTagQuery(String tag, Pageable pageable); - - Page
findByAuthorsNameAndFilteredTagQuery(String name, String tag, Pageable pageable); - - long count(); - - void delete(Article article); -} diff --git a/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/service/ArticleServiceImpl.java b/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/service/ArticleServiceImpl.java deleted file mode 100644 index 5064f16508..0000000000 --- a/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/service/ArticleServiceImpl.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.baeldung.spring.data.es.service; - -import java.util.Optional; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; - -import com.baeldung.spring.data.es.model.Article; -import com.baeldung.spring.data.es.repository.ArticleRepository; - -@Service -public class ArticleServiceImpl implements ArticleService { - - private final ArticleRepository articleRepository; - - @Autowired - public ArticleServiceImpl(ArticleRepository articleRepository) { - this.articleRepository = articleRepository; - } - - @Override - public Article save(Article article) { - return articleRepository.save(article); - } - - @Override - public Optional
findOne(String id) { - return articleRepository.findById(id); - } - - @Override - public Iterable
findAll() { - return articleRepository.findAll(); - } - - @Override - public Page
findByAuthorName(String name, Pageable pageable) { - return articleRepository.findByAuthorsName(name, pageable); - } - - @Override - public Page
findByAuthorNameUsingCustomQuery(String name, Pageable pageable) { - return articleRepository.findByAuthorsNameUsingCustomQuery(name, pageable); - } - - @Override - public Page
findByFilteredTagQuery(String tag, Pageable pageable) { - return articleRepository.findByFilteredTagQuery(tag, pageable); - } - - @Override - public Page
findByAuthorsNameAndFilteredTagQuery(String name, String tag, Pageable pageable) { - return articleRepository.findByAuthorsNameAndFilteredTagQuery(name, tag, pageable); - } - - @Override - public long count() { - return articleRepository.count(); - } - - @Override - public void delete(Article article) { - articleRepository.delete(article); - } -} diff --git a/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/SpringContextManualTest.java b/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/SpringContextManualTest.java index c69deeb77c..6572896eca 100644 --- a/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/SpringContextManualTest.java +++ b/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/SpringContextManualTest.java @@ -8,10 +8,10 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.baeldung.spring.data.es.config.Config; /** + * This Manual test requires: Elasticsearch instance running on localhost:9200. * - * This Manual test requires: - * * Elasticsearch instance running on host - * + * The following docker command can be used: docker run -d --name es762 -p + * 9200:9200 -e "discovery.type=single-node" elasticsearch:7.6.2 */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = Config.class) diff --git a/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/elasticsearch/ElasticSearchManualTest.java b/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/elasticsearch/ElasticSearchManualTest.java index e43dcdf43e..2ca5f28f13 100644 --- a/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/elasticsearch/ElasticSearchManualTest.java +++ b/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/elasticsearch/ElasticSearchManualTest.java @@ -3,43 +3,48 @@ package com.baeldung.elasticsearch; import static org.junit.Assert.assertEquals; import java.io.IOException; -import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.alibaba.fastjson.JSON; import org.elasticsearch.action.DocWriteResponse.Result; +import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.delete.DeleteResponse; +import org.elasticsearch.action.get.GetRequest; +import org.elasticsearch.action.get.GetResponse; +import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.index.IndexResponse; +import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.SearchType; -import org.elasticsearch.client.Client; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.transport.InetSocketTransportAddress; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.SearchHit; -import org.elasticsearch.transport.client.PreBuiltTransportClient; +import org.elasticsearch.search.builder.SearchSourceBuilder; import org.junit.Before; import org.junit.Test; - -import com.alibaba.fastjson.JSON; +import org.springframework.data.elasticsearch.client.ClientConfiguration; +import org.springframework.data.elasticsearch.client.RestClients; /** + * This Manual test requires: Elasticsearch instance running on localhost:9200. * - * This Manual test requires: - * * Elasticsearch instance running on host - * * with cluster name = elasticsearch - * + * The following docker command can be used: docker run -d --name es762 -p + * 9200:9200 -e "discovery.type=single-node" elasticsearch:7.6.2 */ public class ElasticSearchManualTest { private List listOfPersons = new ArrayList<>(); - private Client client = null; + private RestHighLevelClient client = null; @Before public void setUp() throws UnknownHostException { @@ -47,115 +52,122 @@ public class ElasticSearchManualTest { Person person2 = new Person(25, "Janette Doe", new Date()); listOfPersons.add(person1); listOfPersons.add(person2); - - client = new PreBuiltTransportClient(Settings.builder().put("client.transport.sniff", true) - .put("cluster.name","elasticsearch").build()) - .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("127.0.0.1"), 9300)); + + ClientConfiguration clientConfiguration = ClientConfiguration.builder() + .connectedTo("localhost:9200") + .build(); + client = RestClients.create(clientConfiguration) + .rest(); } @Test - public void givenJsonString_whenJavaObject_thenIndexDocument() { + public void givenJsonString_whenJavaObject_thenIndexDocument() throws Exception { String jsonObject = "{\"age\":20,\"dateOfBirth\":1471466076564,\"fullName\":\"John Doe\"}"; - IndexResponse response = client - .prepareIndex("people", "Doe") - .setSource(jsonObject, XContentType.JSON) - .get(); + IndexRequest request = new IndexRequest("people"); + request.source(jsonObject, XContentType.JSON); + + IndexResponse response = client.index(request, RequestOptions.DEFAULT); String index = response.getIndex(); - String type = response.getType(); + long version = response.getVersion(); assertEquals(Result.CREATED, response.getResult()); - assertEquals(index, "people"); - assertEquals(type, "Doe"); + assertEquals(1, version); + assertEquals("people", index); } @Test - public void givenDocumentId_whenJavaObject_thenDeleteDocument() { + public void givenDocumentId_whenJavaObject_thenDeleteDocument() throws Exception { String jsonObject = "{\"age\":10,\"dateOfBirth\":1471455886564,\"fullName\":\"Johan Doe\"}"; - IndexResponse response = client - .prepareIndex("people", "Doe") - .setSource(jsonObject, XContentType.JSON) - .get(); + IndexRequest indexRequest = new IndexRequest("people"); + indexRequest.source(jsonObject, XContentType.JSON); + + IndexResponse response = client.index(indexRequest, RequestOptions.DEFAULT); String id = response.getId(); - DeleteResponse deleteResponse = client - .prepareDelete("people", "Doe", id) - .get(); - assertEquals(Result.DELETED,deleteResponse.getResult()); + GetRequest getRequest = new GetRequest("people"); + getRequest.id(id); + + GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT); + System.out.println(getResponse.getSourceAsString()); + + DeleteRequest deleteRequest = new DeleteRequest("people"); + deleteRequest.id(id); + + DeleteResponse deleteResponse = client.delete(deleteRequest, RequestOptions.DEFAULT); + + assertEquals(Result.DELETED, deleteResponse.getResult()); } @Test - public void givenSearchRequest_whenMatchAll_thenReturnAllResults() { - SearchResponse response = client - .prepareSearch() - .execute() - .actionGet(); - SearchHit[] searchHits = response - .getHits() - .getHits(); + public void givenSearchRequest_whenMatchAll_thenReturnAllResults() throws Exception { + SearchRequest searchRequest = new SearchRequest(); + SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT); + SearchHit[] searchHits = response.getHits() + .getHits(); List results = Arrays.stream(searchHits) - .map(hit -> JSON.parseObject(hit.getSourceAsString(), Person.class)) - .collect(Collectors.toList()); + .map(hit -> JSON.parseObject(hit.getSourceAsString(), Person.class)) + .collect(Collectors.toList()); + + results.forEach(System.out::println); } @Test - public void givenSearchParameters_thenReturnResults() { - SearchResponse response = client - .prepareSearch() - .setTypes() - .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) - .setPostFilter(QueryBuilders - .rangeQuery("age") + public void givenSearchParameters_thenReturnResults() throws Exception { + SearchSourceBuilder builder = new SearchSourceBuilder().postFilter(QueryBuilders.rangeQuery("age") .from(5) - .to(15)) - .setFrom(0) - .setSize(60) - .setExplain(true) - .execute() - .actionGet(); + .to(15)); - SearchResponse response2 = client - .prepareSearch() - .setTypes() - .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) - .setPostFilter(QueryBuilders.simpleQueryStringQuery("+John -Doe OR Janette")) - .setFrom(0) - .setSize(60) - .setExplain(true) - .execute() - .actionGet(); + SearchRequest searchRequest = new SearchRequest(); + searchRequest.searchType(SearchType.DFS_QUERY_THEN_FETCH); + searchRequest.source(builder); + + SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT); + + builder = new SearchSourceBuilder().postFilter(QueryBuilders.simpleQueryStringQuery("+John -Doe OR Janette")); + + searchRequest = new SearchRequest(); + searchRequest.searchType(SearchType.DFS_QUERY_THEN_FETCH); + searchRequest.source(builder); + + SearchResponse response2 = client.search(searchRequest, RequestOptions.DEFAULT); + + builder = new SearchSourceBuilder().postFilter(QueryBuilders.matchQuery("John", "Name*")); + searchRequest = new SearchRequest(); + searchRequest.searchType(SearchType.DFS_QUERY_THEN_FETCH); + searchRequest.source(builder); + + SearchResponse response3 = client.search(searchRequest, RequestOptions.DEFAULT); - SearchResponse response3 = client - .prepareSearch() - .setTypes() - .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) - .setPostFilter(QueryBuilders.matchQuery("John", "Name*")) - .setFrom(0) - .setSize(60) - .setExplain(true) - .execute() - .actionGet(); response2.getHits(); response3.getHits(); - final List results = Arrays.stream(response.getHits().getHits()) - .map(hit -> JSON.parseObject(hit.getSourceAsString(), Person.class)) - .collect(Collectors.toList()); + final List results = Stream.of(response.getHits() + .getHits(), + response2.getHits() + .getHits(), + response3.getHits() + .getHits()) + .flatMap(Arrays::stream) + .map(hit -> JSON.parseObject(hit.getSourceAsString(), Person.class)) + .collect(Collectors.toList()); + + results.forEach(System.out::println); } @Test public void givenContentBuilder_whenHelpers_thanIndexJson() throws IOException { - XContentBuilder builder = XContentFactory - .jsonBuilder() - .startObject() - .field("fullName", "Test") - .field("salary", "11500") - .field("age", "10") - .endObject(); - IndexResponse response = client - .prepareIndex("people", "Doe") - .setSource(builder) - .get(); + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .field("fullName", "Test") + .field("salary", "11500") + .field("age", "10") + .endObject(); - assertEquals(Result.CREATED, response.getResult()); + IndexRequest indexRequest = new IndexRequest("people"); + indexRequest.source(builder); + + IndexResponse response = client.index(indexRequest, RequestOptions.DEFAULT); + + assertEquals(Result.CREATED, response.getResult()); } } diff --git a/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/elasticsearch/GeoQueriesManualTest.java b/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/elasticsearch/GeoQueriesManualTest.java index f9a42050b6..64b2ea2437 100644 --- a/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/elasticsearch/GeoQueriesManualTest.java +++ b/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/elasticsearch/GeoQueriesManualTest.java @@ -1,4 +1,5 @@ package com.baeldung.elasticsearch; + import static org.junit.Assert.assertTrue; import java.io.IOException; @@ -7,162 +8,169 @@ import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; -import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; +import com.baeldung.spring.data.es.config.Config; + +import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; +import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; +import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.index.IndexResponse; +import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.client.Client; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.client.indices.CreateIndexRequest; import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.ShapeRelation; -import org.elasticsearch.common.geo.builders.ShapeBuilders; +import org.elasticsearch.common.geo.builders.EnvelopeBuilder; import org.elasticsearch.common.unit.DistanceUnit; import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.index.query.GeoShapeQueryBuilder; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.SearchHit; +import org.elasticsearch.search.builder.SearchSourceBuilder; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.locationtech.jts.geom.Coordinate; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import com.baeldung.spring.data.es.config.Config; -import com.vividsolutions.jts.geom.Coordinate; - /** + * This Manual test requires: Elasticsearch instance running on localhost:9200. * - * This Manual test requires: - * * Elasticsearch instance running on host - * * with cluster name = elasticsearch - * * and further configurations - * + * The following docker command can be used: docker run -d --name es762 -p + * 9200:9200 -e "discovery.type=single-node" elasticsearch:7.6.2 */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = Config.class) public class GeoQueriesManualTest { private static final String WONDERS_OF_WORLD = "wonders-of-world"; - private static final String WONDERS = "Wonders"; @Autowired - private ElasticsearchTemplate elasticsearchTemplate; - - @Autowired - private Client client; + private RestHighLevelClient client; @Before - public void setUp() { - String jsonObject = "{\"Wonders\":{\"properties\":{\"name\":{\"type\":\"string\",\"index\":\"not_analyzed\"},\"region\":{\"type\":\"geo_shape\",\"tree\":\"quadtree\",\"precision\":\"1m\"},\"location\":{\"type\":\"geo_point\"}}}}"; + public void setUp() throws Exception { + String jsonObject = "{\"properties\":{\"name\":{\"type\":\"text\",\"index\":false},\"region\":{\"type\":\"geo_shape\"},\"location\":{\"type\":\"geo_point\"}}}"; + CreateIndexRequest req = new CreateIndexRequest(WONDERS_OF_WORLD); - req.mapping(WONDERS, jsonObject, XContentType.JSON); - client.admin() - .indices() - .create(req) - .actionGet(); + req.mapping(jsonObject, XContentType.JSON); + + client.indices() + .create(req, RequestOptions.DEFAULT); } @Test - public void givenGeoShapeData_whenExecutedGeoShapeQuery_thenResultNonEmpty() throws IOException{ + public void givenGeoShapeData_whenExecutedGeoShapeQuery_thenResultNonEmpty() throws IOException { String jsonObject = "{\"name\":\"Agra\",\"region\":{\"type\":\"envelope\",\"coordinates\":[[75,30.2],[80.1, 25]]}}"; - IndexResponse response = client.prepareIndex(WONDERS_OF_WORLD, WONDERS) - .setSource(jsonObject, XContentType.JSON) - .get(); - + IndexRequest indexRequest = new IndexRequest(WONDERS_OF_WORLD); + indexRequest.source(jsonObject, XContentType.JSON); + IndexResponse response = client.index(indexRequest, RequestOptions.DEFAULT); + String tajMahalId = response.getId(); - client.admin() - .indices() - .prepareRefresh(WONDERS_OF_WORLD) - .get(); - Coordinate topLeft =new Coordinate(74, 31.2); - Coordinate bottomRight =new Coordinate(81.1, 24); - QueryBuilder qb = QueryBuilders - .geoShapeQuery("region", ShapeBuilders.newEnvelope(topLeft, bottomRight)) - .relation(ShapeRelation.WITHIN); + RefreshRequest refreshRequest = new RefreshRequest(WONDERS_OF_WORLD); + client.indices() + .refresh(refreshRequest, RequestOptions.DEFAULT); + Coordinate topLeft = new Coordinate(74, 31.2); + Coordinate bottomRight = new Coordinate(81.1, 24); - SearchResponse searchResponse = client.prepareSearch(WONDERS_OF_WORLD) - .setTypes(WONDERS) - .setQuery(qb) - .execute() - .actionGet(); + GeoShapeQueryBuilder qb = QueryBuilders.geoShapeQuery("region", new EnvelopeBuilder(topLeft, bottomRight).buildGeometry()); + qb.relation(ShapeRelation.INTERSECTS); + + SearchSourceBuilder source = new SearchSourceBuilder().query(qb); + SearchRequest searchRequest = new SearchRequest(WONDERS_OF_WORLD); + searchRequest.source(source); + + SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); List ids = Arrays.stream(searchResponse.getHits() - .getHits()) - .map(SearchHit::getId) - .collect(Collectors.toList()); + .getHits()) + .map(SearchHit::getId) + .collect(Collectors.toList()); assertTrue(ids.contains(tajMahalId)); } @Test - public void givenGeoPointData_whenExecutedGeoBoundingBoxQuery_thenResultNonEmpty() { + public void givenGeoPointData_whenExecutedGeoBoundingBoxQuery_thenResultNonEmpty() throws Exception { String jsonObject = "{\"name\":\"Pyramids of Giza\",\"location\":[31.131302,29.976480]}"; - IndexResponse response = client.prepareIndex(WONDERS_OF_WORLD, WONDERS) - .setSource(jsonObject, XContentType.JSON) - .get(); + + IndexRequest indexRequest = new IndexRequest(WONDERS_OF_WORLD); + indexRequest.source(jsonObject, XContentType.JSON); + IndexResponse response = client.index(indexRequest, RequestOptions.DEFAULT); + String pyramidsOfGizaId = response.getId(); - client.admin() - .indices() - .prepareRefresh(WONDERS_OF_WORLD) - .get(); + + RefreshRequest refreshRequest = new RefreshRequest(WONDERS_OF_WORLD); + client.indices() + .refresh(refreshRequest, RequestOptions.DEFAULT); QueryBuilder qb = QueryBuilders.geoBoundingBoxQuery("location") - .setCorners(31,30,28,32); - - SearchResponse searchResponse = client.prepareSearch(WONDERS_OF_WORLD) - .setTypes(WONDERS) - .setQuery(qb) - .execute() - .actionGet(); + .setCorners(31, 30, 28, 32); + + SearchSourceBuilder source = new SearchSourceBuilder().query(qb); + SearchRequest searchRequest = new SearchRequest(WONDERS_OF_WORLD); + searchRequest.source(source); + + SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); + List ids = Arrays.stream(searchResponse.getHits() - .getHits()) - .map(SearchHit::getId) - .collect(Collectors.toList()); + .getHits()) + .map(SearchHit::getId) + .collect(Collectors.toList()); assertTrue(ids.contains(pyramidsOfGizaId)); } @Test - public void givenGeoPointData_whenExecutedGeoDistanceQuery_thenResultNonEmpty() { + public void givenGeoPointData_whenExecutedGeoDistanceQuery_thenResultNonEmpty() throws Exception { String jsonObject = "{\"name\":\"Lighthouse of alexandria\",\"location\":[31.131302,29.976480]}"; - IndexResponse response = client.prepareIndex(WONDERS_OF_WORLD, WONDERS) - .setSource(jsonObject, XContentType.JSON) - .get(); + + IndexRequest indexRequest = new IndexRequest(WONDERS_OF_WORLD); + indexRequest.source(jsonObject, XContentType.JSON); + IndexResponse response = client.index(indexRequest, RequestOptions.DEFAULT); + String lighthouseOfAlexandriaId = response.getId(); - client.admin() - .indices() - .prepareRefresh(WONDERS_OF_WORLD) - .get(); + + RefreshRequest refreshRequest = new RefreshRequest(WONDERS_OF_WORLD); + client.indices() + .refresh(refreshRequest, RequestOptions.DEFAULT); QueryBuilder qb = QueryBuilders.geoDistanceQuery("location") - .point(29.976, 31.131) - .distance(10, DistanceUnit.MILES); + .point(29.976, 31.131) + .distance(10, DistanceUnit.MILES); + + SearchSourceBuilder source = new SearchSourceBuilder().query(qb); + SearchRequest searchRequest = new SearchRequest(WONDERS_OF_WORLD); + searchRequest.source(source); + + SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); - SearchResponse searchResponse = client.prepareSearch(WONDERS_OF_WORLD) - .setTypes(WONDERS) - .setQuery(qb) - .execute() - .actionGet(); List ids = Arrays.stream(searchResponse.getHits() - .getHits()) - .map(SearchHit::getId) - .collect(Collectors.toList()); + .getHits()) + .map(SearchHit::getId) + .collect(Collectors.toList()); assertTrue(ids.contains(lighthouseOfAlexandriaId)); } @Test - public void givenGeoPointData_whenExecutedGeoPolygonQuery_thenResultNonEmpty() { + public void givenGeoPointData_whenExecutedGeoPolygonQuery_thenResultNonEmpty() throws Exception { String jsonObject = "{\"name\":\"The Great Rann of Kutch\",\"location\":[69.859741,23.733732]}"; - IndexResponse response = client.prepareIndex(WONDERS_OF_WORLD, WONDERS) - .setSource(jsonObject, XContentType.JSON) - .get(); + + IndexRequest indexRequest = new IndexRequest(WONDERS_OF_WORLD); + indexRequest.source(jsonObject, XContentType.JSON); + IndexResponse response = client.index(indexRequest, RequestOptions.DEFAULT); + String greatRannOfKutchid = response.getId(); - client.admin() - .indices() - .prepareRefresh(WONDERS_OF_WORLD) - .get(); + + RefreshRequest refreshRequest = new RefreshRequest(WONDERS_OF_WORLD); + client.indices() + .refresh(refreshRequest, RequestOptions.DEFAULT); List allPoints = new ArrayList(); allPoints.add(new GeoPoint(22.733, 68.859)); @@ -170,20 +178,23 @@ public class GeoQueriesManualTest { allPoints.add(new GeoPoint(23, 70.859)); QueryBuilder qb = QueryBuilders.geoPolygonQuery("location", allPoints); - SearchResponse searchResponse = client.prepareSearch(WONDERS_OF_WORLD) - .setTypes(WONDERS) - .setQuery(qb) - .execute() - .actionGet(); + SearchSourceBuilder source = new SearchSourceBuilder().query(qb); + SearchRequest searchRequest = new SearchRequest(WONDERS_OF_WORLD); + searchRequest.source(source); + + SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); + List ids = Arrays.stream(searchResponse.getHits() - .getHits()) - .map(SearchHit::getId) - .collect(Collectors.toList()); + .getHits()) + .map(SearchHit::getId) + .collect(Collectors.toList()); assertTrue(ids.contains(greatRannOfKutchid)); } @After - public void destroy() { - elasticsearchTemplate.deleteIndex(WONDERS_OF_WORLD); + public void destroy() throws Exception { + DeleteIndexRequest deleteIndex = new DeleteIndexRequest(WONDERS_OF_WORLD); + client.indices() + .delete(deleteIndex, RequestOptions.DEFAULT); } } diff --git a/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/spring/data/es/ElasticSearchManualTest.java b/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/spring/data/es/ElasticSearchManualTest.java index bed2e2ff25..412cd04e09 100644 --- a/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/spring/data/es/ElasticSearchManualTest.java +++ b/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/spring/data/es/ElasticSearchManualTest.java @@ -10,68 +10,71 @@ import static org.junit.Assert.assertNotNull; import java.util.List; +import com.baeldung.spring.data.es.config.Config; +import com.baeldung.spring.data.es.model.Article; +import com.baeldung.spring.data.es.model.Author; +import com.baeldung.spring.data.es.repository.ArticleRepository; + +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; -import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; +import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; +import org.springframework.data.elasticsearch.core.SearchHits; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; -import org.springframework.data.elasticsearch.core.query.SearchQuery; +import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import com.baeldung.spring.data.es.config.Config; -import com.baeldung.spring.data.es.model.Article; -import com.baeldung.spring.data.es.model.Author; -import com.baeldung.spring.data.es.service.ArticleService; - /** + * This Manual test requires: Elasticsearch instance running on localhost:9200. * - * This Manual test requires: - * * Elasticsearch instance running on host - * * with cluster name = elasticsearch - * + * The following docker command can be used: docker run -d --name es762 -p + * 9200:9200 -e "discovery.type=single-node" elasticsearch:7.6.2 */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = Config.class) public class ElasticSearchManualTest { @Autowired - private ElasticsearchTemplate elasticsearchTemplate; + private ElasticsearchRestTemplate elasticsearchTemplate; @Autowired - private ArticleService articleService; + private ArticleRepository articleRepository; private final Author johnSmith = new Author("John Smith"); private final Author johnDoe = new Author("John Doe"); @Before public void before() { - elasticsearchTemplate.deleteIndex(Article.class); - elasticsearchTemplate.createIndex(Article.class); - // don't call putMapping() to test the default mappings - Article article = new Article("Spring Data Elasticsearch"); article.setAuthors(asList(johnSmith, johnDoe)); article.setTags("elasticsearch", "spring data"); - articleService.save(article); + articleRepository.save(article); article = new Article("Search engines"); article.setAuthors(asList(johnDoe)); article.setTags("search engines", "tutorial"); - articleService.save(article); + articleRepository.save(article); article = new Article("Second Article About Elasticsearch"); article.setAuthors(asList(johnSmith)); article.setTags("elasticsearch", "spring data"); - articleService.save(article); + articleRepository.save(article); article = new Article("Elasticsearch Tutorial"); article.setAuthors(asList(johnDoe)); article.setTags("elasticsearch"); - articleService.save(article); + articleRepository.save(article); + } + + @After + public void after() { + articleRepository.deleteAll(); } @Test @@ -81,82 +84,85 @@ public class ElasticSearchManualTest { Article article = new Article("Making Search Elastic"); article.setAuthors(authors); - article = articleService.save(article); + article = articleRepository.save(article); assertNotNull(article.getId()); } @Test public void givenPersistedArticles_whenSearchByAuthorsName_thenRightFound() { - - final Page
articleByAuthorName = articleService - .findByAuthorName(johnSmith.getName(), PageRequest.of(0, 10)); + final Page
articleByAuthorName = articleRepository.findByAuthorsName(johnSmith.getName(), PageRequest.of(0, 10)); assertEquals(2L, articleByAuthorName.getTotalElements()); } @Test public void givenCustomQuery_whenSearchByAuthorsName_thenArticleIsFound() { - final Page
articleByAuthorName = articleService.findByAuthorNameUsingCustomQuery("Smith", PageRequest.of(0, 10)); + final Page
articleByAuthorName = articleRepository.findByAuthorsNameUsingCustomQuery("Smith", PageRequest.of(0, 10)); assertEquals(2L, articleByAuthorName.getTotalElements()); } @Test public void givenTagFilterQuery_whenSearchByTag_thenArticleIsFound() { - final Page
articleByAuthorName = articleService.findByFilteredTagQuery("elasticsearch", PageRequest.of(0, 10)); + final Page
articleByAuthorName = articleRepository.findByFilteredTagQuery("elasticsearch", PageRequest.of(0, 10)); assertEquals(3L, articleByAuthorName.getTotalElements()); } @Test public void givenTagFilterQuery_whenSearchByAuthorsName_thenArticleIsFound() { - final Page
articleByAuthorName = articleService.findByAuthorsNameAndFilteredTagQuery("Doe", "elasticsearch", PageRequest.of(0, 10)); + final Page
articleByAuthorName = articleRepository.findByAuthorsNameAndFilteredTagQuery("Doe", "elasticsearch", PageRequest.of(0, 10)); assertEquals(2L, articleByAuthorName.getTotalElements()); } @Test public void givenPersistedArticles_whenUseRegexQuery_thenRightArticlesFound() { + final Query searchQuery = new NativeSearchQueryBuilder().withFilter(regexpQuery("title", ".*data.*")) + .build(); - final SearchQuery searchQuery = new NativeSearchQueryBuilder().withFilter(regexpQuery("title", ".*data.*")) - .build(); - final List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); + final SearchHits
articles = elasticsearchTemplate.search(searchQuery, Article.class, IndexCoordinates.of("blog")); - assertEquals(1, articles.size()); + assertEquals(1, articles.getTotalHits()); } @Test public void givenSavedDoc_whenTitleUpdated_thenCouldFindByUpdatedTitle() { - final SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(fuzzyQuery("title", "serch")).build(); - final List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); + final Query searchQuery = new NativeSearchQueryBuilder().withQuery(fuzzyQuery("title", "serch")) + .build(); + final SearchHits
articles = elasticsearchTemplate.search(searchQuery, Article.class, IndexCoordinates.of("blog")); - assertEquals(1, articles.size()); + assertEquals(1, articles.getTotalHits()); - final Article article = articles.get(0); + final Article article = articles.getSearchHit(0) + .getContent(); final String newTitle = "Getting started with Search Engines"; article.setTitle(newTitle); - articleService.save(article); + articleRepository.save(article); - assertEquals(newTitle, articleService.findOne(article.getId()).get().getTitle()); + assertEquals(newTitle, articleRepository.findById(article.getId()) + .get() + .getTitle()); } @Test public void givenSavedDoc_whenDelete_thenRemovedFromIndex() { - final String articleTitle = "Spring Data Elasticsearch"; - final SearchQuery searchQuery = new NativeSearchQueryBuilder() - .withQuery(matchQuery("title", articleTitle).minimumShouldMatch("75%")).build(); - final List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); - assertEquals(1, articles.size()); - final long count = articleService.count(); + final Query searchQuery = new NativeSearchQueryBuilder().withQuery(matchQuery("title", articleTitle).minimumShouldMatch("75%")) + .build(); + final SearchHits
articles = elasticsearchTemplate.search(searchQuery, Article.class, IndexCoordinates.of("blog")); - articleService.delete(articles.get(0)); + assertEquals(1, articles.getTotalHits()); + final long count = articleRepository.count(); - assertEquals(count - 1, articleService.count()); + articleRepository.delete(articles.getSearchHit(0) + .getContent()); + + assertEquals(count - 1, articleRepository.count()); } @Test public void givenSavedDoc_whenOneTermMatches_thenFindByTitle() { - final SearchQuery searchQuery = new NativeSearchQueryBuilder() - .withQuery(matchQuery("title", "Search engines").operator(AND)).build(); - final List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); - assertEquals(1, articles.size()); + final Query searchQuery = new NativeSearchQueryBuilder().withQuery(matchQuery("title", "Search engines").operator(AND)) + .build(); + final SearchHits
articles = elasticsearchTemplate.search(searchQuery, Article.class, IndexCoordinates.of("blog")); + assertEquals(1, articles.getTotalHits()); } } diff --git a/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/spring/data/es/ElasticSearchQueryManualTest.java b/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/spring/data/es/ElasticSearchQueryManualTest.java index 5e24d8398c..aaf0c80097 100644 --- a/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/spring/data/es/ElasticSearchQueryManualTest.java +++ b/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/spring/data/es/ElasticSearchQueryManualTest.java @@ -2,7 +2,6 @@ package com.baeldung.spring.data.es; import static java.util.Arrays.asList; import static java.util.stream.Collectors.toList; -import static org.elasticsearch.index.query.Operator.AND; import static org.elasticsearch.index.query.QueryBuilders.boolQuery; import static org.elasticsearch.index.query.QueryBuilders.matchPhraseQuery; import static org.elasticsearch.index.query.QueryBuilders.matchQuery; @@ -14,190 +13,225 @@ import static org.junit.Assert.assertEquals; import java.util.List; import java.util.Map; +import com.baeldung.spring.data.es.config.Config; +import com.baeldung.spring.data.es.model.Article; +import com.baeldung.spring.data.es.model.Author; +import com.baeldung.spring.data.es.repository.ArticleRepository; + import org.apache.lucene.search.join.ScoreMode; +import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.client.Client; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.index.query.MultiMatchQueryBuilder; +import org.elasticsearch.index.query.Operator; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.search.aggregations.Aggregation; import org.elasticsearch.search.aggregations.AggregationBuilders; +import org.elasticsearch.search.aggregations.BucketOrder; import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation; -import org.elasticsearch.search.aggregations.bucket.terms.StringTerms; -import org.elasticsearch.search.aggregations.bucket.terms.Terms; +import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms; import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder; +import org.elasticsearch.search.builder.SearchSourceBuilder; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; +import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; +import org.springframework.data.elasticsearch.core.SearchHits; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; -import org.springframework.data.elasticsearch.core.query.SearchQuery; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import com.baeldung.spring.data.es.config.Config; -import com.baeldung.spring.data.es.model.Article; -import com.baeldung.spring.data.es.model.Author; -import com.baeldung.spring.data.es.service.ArticleService; - /** + * This Manual test requires: Elasticsearch instance running on localhost:9200. * - * This Manual test requires: - * * Elasticsearch instance running on host - * * with cluster name = elasticsearch - * + * The following docker command can be used: docker run -d --name es762 -p + * 9200:9200 -e "discovery.type=single-node" elasticsearch:7.6.2 */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = Config.class) public class ElasticSearchQueryManualTest { @Autowired - private ElasticsearchTemplate elasticsearchTemplate; + private ElasticsearchRestTemplate elasticsearchTemplate; @Autowired - private ArticleService articleService; + private ArticleRepository articleRepository; @Autowired - private Client client; + private RestHighLevelClient client; private final Author johnSmith = new Author("John Smith"); private final Author johnDoe = new Author("John Doe"); @Before public void before() { - elasticsearchTemplate.deleteIndex(Article.class); - elasticsearchTemplate.createIndex(Article.class); - elasticsearchTemplate.putMapping(Article.class); - elasticsearchTemplate.refresh(Article.class); - Article article = new Article("Spring Data Elasticsearch"); article.setAuthors(asList(johnSmith, johnDoe)); article.setTags("elasticsearch", "spring data"); - articleService.save(article); + articleRepository.save(article); article = new Article("Search engines"); article.setAuthors(asList(johnDoe)); article.setTags("search engines", "tutorial"); - articleService.save(article); + articleRepository.save(article); article = new Article("Second Article About Elasticsearch"); article.setAuthors(asList(johnSmith)); article.setTags("elasticsearch", "spring data"); - articleService.save(article); + articleRepository.save(article); article = new Article("Elasticsearch Tutorial"); article.setAuthors(asList(johnDoe)); article.setTags("elasticsearch"); - articleService.save(article); + articleRepository.save(article); + } + + @After + public void after() { + articleRepository.deleteAll(); } @Test public void givenFullTitle_whenRunMatchQuery_thenDocIsFound() { - final SearchQuery searchQuery = new NativeSearchQueryBuilder() - .withQuery(matchQuery("title", "Search engines").operator(AND)).build(); - final List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); - assertEquals(1, articles.size()); + final NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchQuery("title", "Search engines").operator(Operator.AND)) + .build(); + final SearchHits
articles = elasticsearchTemplate.search(searchQuery, Article.class, IndexCoordinates.of("blog")); + assertEquals(1, articles.getTotalHits()); } @Test public void givenOneTermFromTitle_whenRunMatchQuery_thenDocIsFound() { - final SearchQuery searchQuery = new NativeSearchQueryBuilder() - .withQuery(matchQuery("title", "Engines Solutions")).build(); - final List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); - assertEquals(1, articles.size()); - assertEquals("Search engines", articles.get(0).getTitle()); + final NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchQuery("title", "Engines Solutions")) + .build(); + + final SearchHits
articles = elasticsearchTemplate.search(searchQuery, Article.class, IndexCoordinates.of("blog")); + + assertEquals(1, articles.getTotalHits()); + assertEquals("Search engines", articles.getSearchHit(0) + .getContent() + .getTitle()); } @Test public void givenPartTitle_whenRunMatchQuery_thenDocIsFound() { - final SearchQuery searchQuery = new NativeSearchQueryBuilder() - .withQuery(matchQuery("title", "elasticsearch data")).build(); - final List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); - assertEquals(3, articles.size()); + final NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchQuery("title", "elasticsearch data")) + .build(); + + final SearchHits
articles = elasticsearchTemplate.search(searchQuery, Article.class, IndexCoordinates.of("blog")); + + assertEquals(3, articles.getTotalHits()); } @Test public void givenFullTitle_whenRunMatchQueryOnVerbatimField_thenDocIsFound() { - SearchQuery searchQuery = new NativeSearchQueryBuilder() - .withQuery(matchQuery("title.verbatim", "Second Article About Elasticsearch")).build(); - List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); - assertEquals(1, articles.size()); + NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchQuery("title.verbatim", "Second Article About Elasticsearch")) + .build(); + + SearchHits
articles = elasticsearchTemplate.search(searchQuery, Article.class, IndexCoordinates.of("blog")); + + assertEquals(1, articles.getTotalHits()); searchQuery = new NativeSearchQueryBuilder().withQuery(matchQuery("title.verbatim", "Second Article About")) - .build(); - articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); - assertEquals(0, articles.size()); + .build(); + + articles = elasticsearchTemplate.search(searchQuery, Article.class, IndexCoordinates.of("blog")); + assertEquals(0, articles.getTotalHits()); } @Test public void givenNestedObject_whenQueryByAuthorsName_thenFoundArticlesByThatAuthor() { final QueryBuilder builder = nestedQuery("authors", boolQuery().must(termQuery("authors.name", "smith")), ScoreMode.None); - final SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(builder).build(); - final List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); + final NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(builder) + .build(); + final SearchHits
articles = elasticsearchTemplate.search(searchQuery, Article.class, IndexCoordinates.of("blog")); - assertEquals(2, articles.size()); + assertEquals(2, articles.getTotalHits()); } @Test - public void givenAnalyzedQuery_whenMakeAggregationOnTermCount_thenEachTokenCountsSeparately() { - final TermsAggregationBuilder aggregation = AggregationBuilders.terms("top_tags").field("title"); - final SearchResponse response = client.prepareSearch("blog").setTypes("article").addAggregation(aggregation) - .execute().actionGet(); + public void givenAnalyzedQuery_whenMakeAggregationOnTermCount_thenEachTokenCountsSeparately() throws Exception { + final TermsAggregationBuilder aggregation = AggregationBuilders.terms("top_tags") + .field("title"); - final Map results = response.getAggregations().asMap(); - final StringTerms topTags = (StringTerms) results.get("top_tags"); + final SearchSourceBuilder builder = new SearchSourceBuilder().aggregation(aggregation); + final SearchRequest searchRequest = new SearchRequest("blog").source(builder); - final List keys = topTags.getBuckets().stream() - .map(MultiBucketsAggregation.Bucket::getKeyAsString) - .sorted() - .collect(toList()); + final SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT); + + final Map results = response.getAggregations() + .asMap(); + final ParsedStringTerms topTags = (ParsedStringTerms) results.get("top_tags"); + + final List keys = topTags.getBuckets() + .stream() + .map(MultiBucketsAggregation.Bucket::getKeyAsString) + .sorted() + .collect(toList()); assertEquals(asList("about", "article", "data", "elasticsearch", "engines", "search", "second", "spring", "tutorial"), keys); } @Test - public void givenNotAnalyzedQuery_whenMakeAggregationOnTermCount_thenEachTermCountsIndividually() { - final TermsAggregationBuilder aggregation = AggregationBuilders.terms("top_tags").field("tags") - .order(Terms.Order.count(false)); - final SearchResponse response = client.prepareSearch("blog").setTypes("article").addAggregation(aggregation) - .execute().actionGet(); + public void givenNotAnalyzedQuery_whenMakeAggregationOnTermCount_thenEachTermCountsIndividually() throws Exception { + final TermsAggregationBuilder aggregation = AggregationBuilders.terms("top_tags") + .field("tags") + .order(BucketOrder.count(false)); - final Map results = response.getAggregations().asMap(); - final StringTerms topTags = (StringTerms) results.get("top_tags"); + final SearchSourceBuilder builder = new SearchSourceBuilder().aggregation(aggregation); + final SearchRequest searchRequest = new SearchRequest().indices("blog") + .source(builder); - final List keys = topTags.getBuckets().stream() - .map(MultiBucketsAggregation.Bucket::getKeyAsString) - .collect(toList()); + final SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT); + + final Map results = response.getAggregations() + .asMap(); + final ParsedStringTerms topTags = (ParsedStringTerms) results.get("top_tags"); + + final List keys = topTags.getBuckets() + .stream() + .map(MultiBucketsAggregation.Bucket::getKeyAsString) + .collect(toList()); assertEquals(asList("elasticsearch", "spring data", "search engines", "tutorial"), keys); } @Test public void givenNotExactPhrase_whenUseSlop_thenQueryMatches() { - final SearchQuery searchQuery = new NativeSearchQueryBuilder() - .withQuery(matchPhraseQuery("title", "spring elasticsearch").slop(1)).build(); - final List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); - assertEquals(1, articles.size()); + final NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchPhraseQuery("title", "spring elasticsearch").slop(1)) + .build(); + + final SearchHits
articles = elasticsearchTemplate.search(searchQuery, Article.class, IndexCoordinates.of("blog")); + + assertEquals(1, articles.getTotalHits()); } @Test public void givenPhraseWithType_whenUseFuzziness_thenQueryMatches() { - final SearchQuery searchQuery = new NativeSearchQueryBuilder() - .withQuery(matchQuery("title", "spring date elasticserch").operator(AND).fuzziness(Fuzziness.ONE) - .prefixLength(3)).build(); + final NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchQuery("title", "spring date elasticserch").operator(Operator.AND) + .fuzziness(Fuzziness.ONE) + .prefixLength(3)) + .build(); - final List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); - assertEquals(1, articles.size()); + final SearchHits
articles = elasticsearchTemplate.search(searchQuery, Article.class, IndexCoordinates.of("blog")); + + assertEquals(1, articles.getTotalHits()); } @Test public void givenMultimatchQuery_whenDoSearch_thenAllProvidedFieldsMatch() { - final SearchQuery searchQuery = new NativeSearchQueryBuilder() - .withQuery(multiMatchQuery("tutorial").field("title").field("tags") - .type(MultiMatchQueryBuilder.Type.BEST_FIELDS)).build(); + final NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(multiMatchQuery("tutorial").field("title") + .field("tags") + .type(MultiMatchQueryBuilder.Type.BEST_FIELDS)) + .build(); - final List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); - assertEquals(2, articles.size()); + final SearchHits
articles = elasticsearchTemplate.search(searchQuery, Article.class, IndexCoordinates.of("blog")); + + assertEquals(2, articles.getTotalHits()); } @Test @@ -205,10 +239,10 @@ public class ElasticSearchQueryManualTest { final QueryBuilder builder = boolQuery().must(nestedQuery("authors", boolQuery().must(termQuery("authors.name", "doe")), ScoreMode.None)) .filter(termQuery("tags", "elasticsearch")); - final SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(builder) + final NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(builder) .build(); - final List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); + final SearchHits
articles = elasticsearchTemplate.search(searchQuery, Article.class, IndexCoordinates.of("blog")); - assertEquals(2, articles.size()); + assertEquals(2, articles.getTotalHits()); } } diff --git a/persistence-modules/spring-data-jpa-5/pom.xml b/persistence-modules/spring-data-jpa-5/pom.xml index 3053384559..6a5cdc86c2 100644 --- a/persistence-modules/spring-data-jpa-5/pom.xml +++ b/persistence-modules/spring-data-jpa-5/pom.xml @@ -1,6 +1,7 @@ + 4.0.0 spring-data-jpa-5 spring-data-jpa-5 @@ -11,7 +12,7 @@ 0.0.1-SNAPSHOT ../../parent-boot-2 - + org.springframework.boot @@ -28,10 +29,43 @@ spring-boot-starter-data-jdbc + + org.springframework.boot + spring-boot-starter-cache + + com.h2database h2 + + + org.mapstruct + mapstruct-jdk8 + 1.3.1.Final + provided + + - + + src/main/java + + + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + + + org.mapstruct + mapstruct-processor + 1.3.1.Final + + + + + + + diff --git a/persistence-modules/spring-data-jpa-5/src/main/java/com/baeldung/partialupdate/PartialUpdateApplication.java b/persistence-modules/spring-data-jpa-5/src/main/java/com/baeldung/partialupdate/PartialUpdateApplication.java new file mode 100644 index 0000000000..a750fcadf7 --- /dev/null +++ b/persistence-modules/spring-data-jpa-5/src/main/java/com/baeldung/partialupdate/PartialUpdateApplication.java @@ -0,0 +1,12 @@ +package com.baeldung.partialupdate; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class PartialUpdateApplication { + + public static void main(String[] args) { + SpringApplication.run(PartialUpdateApplication.class, args); + } +} diff --git a/persistence-modules/spring-data-jpa-5/src/main/java/com/baeldung/partialupdate/model/ContactPhone.java b/persistence-modules/spring-data-jpa-5/src/main/java/com/baeldung/partialupdate/model/ContactPhone.java new file mode 100644 index 0000000000..352e361bd9 --- /dev/null +++ b/persistence-modules/spring-data-jpa-5/src/main/java/com/baeldung/partialupdate/model/ContactPhone.java @@ -0,0 +1,22 @@ +package com.baeldung.partialupdate.model; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class ContactPhone { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + public long id; + @Column(nullable=false) + public long customerId; + public String phone; + + @Override + public String toString() { + return phone; + } +} diff --git a/persistence-modules/spring-data-jpa-5/src/main/java/com/baeldung/partialupdate/model/Customer.java b/persistence-modules/spring-data-jpa-5/src/main/java/com/baeldung/partialupdate/model/Customer.java new file mode 100644 index 0000000000..b19d0b7952 --- /dev/null +++ b/persistence-modules/spring-data-jpa-5/src/main/java/com/baeldung/partialupdate/model/Customer.java @@ -0,0 +1,23 @@ +package com.baeldung.partialupdate.model; + +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) + public long id; + public String name; + public String phone; + //... + public String phone99; + + @Override public String toString() { + return String.format("Customer %s, Phone: %s", + this.name, this.phone); + } +} diff --git a/persistence-modules/spring-data-jpa-5/src/main/java/com/baeldung/partialupdate/model/CustomerDto.java b/persistence-modules/spring-data-jpa-5/src/main/java/com/baeldung/partialupdate/model/CustomerDto.java new file mode 100644 index 0000000000..0ecf206d9a --- /dev/null +++ b/persistence-modules/spring-data-jpa-5/src/main/java/com/baeldung/partialupdate/model/CustomerDto.java @@ -0,0 +1,31 @@ +package com.baeldung.partialupdate.model; + +public class CustomerDto { + private long id; + public String name; + public String phone; + //... + private String phone99; + + public CustomerDto(long id) { + this.id = id; + } + + public CustomerDto(Customer c) { + this.id = c.id; + this.name = c.name; + this.phone = c.phone; + } + + public long getId() { + return this.id; + } + + public Customer convertToEntity() { + Customer c = new Customer(); + c.id = id; + c.name = name; + c.phone = phone; + return c; + } +} diff --git a/persistence-modules/spring-data-jpa-5/src/main/java/com/baeldung/partialupdate/model/CustomerStructured.java b/persistence-modules/spring-data-jpa-5/src/main/java/com/baeldung/partialupdate/model/CustomerStructured.java new file mode 100644 index 0000000000..dd053a963d --- /dev/null +++ b/persistence-modules/spring-data-jpa-5/src/main/java/com/baeldung/partialupdate/model/CustomerStructured.java @@ -0,0 +1,27 @@ +package com.baeldung.partialupdate.model; + +import java.util.List; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.OneToMany; + +@Entity +public class CustomerStructured { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + public long id; + public String name; + @OneToMany(fetch = FetchType.EAGER, targetEntity = ContactPhone.class, mappedBy = "customerId") + public List contactPhones; + + @Override public String toString() { + return String.format("Customer %s, Phone: %s", + this.name, this.contactPhones.stream() + .map(e -> e.toString()).reduce("", String::concat)); + } +} diff --git a/persistence-modules/spring-data-jpa-5/src/main/java/com/baeldung/partialupdate/repository/ContactPhoneRepository.java b/persistence-modules/spring-data-jpa-5/src/main/java/com/baeldung/partialupdate/repository/ContactPhoneRepository.java new file mode 100644 index 0000000000..4668181e05 --- /dev/null +++ b/persistence-modules/spring-data-jpa-5/src/main/java/com/baeldung/partialupdate/repository/ContactPhoneRepository.java @@ -0,0 +1,12 @@ +package com.baeldung.partialupdate.repository; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +import com.baeldung.partialupdate.model.ContactPhone; + +@Repository +public interface ContactPhoneRepository extends CrudRepository { + ContactPhone findById(long id); + ContactPhone findByCustomerId(long id); +} \ No newline at end of file diff --git a/persistence-modules/spring-data-jpa-5/src/main/java/com/baeldung/partialupdate/repository/CustomerRepository.java b/persistence-modules/spring-data-jpa-5/src/main/java/com/baeldung/partialupdate/repository/CustomerRepository.java new file mode 100644 index 0000000000..43e61df8ab --- /dev/null +++ b/persistence-modules/spring-data-jpa-5/src/main/java/com/baeldung/partialupdate/repository/CustomerRepository.java @@ -0,0 +1,18 @@ +package com.baeldung.partialupdate.repository; + +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import com.baeldung.partialupdate.model.Customer; + +@Repository +public interface CustomerRepository extends CrudRepository { + Customer findById(long id); + + @Modifying + @Query("update Customer u set u.phone = :phone where u.id = :id") + void updatePhone(@Param(value = "id") long id, @Param(value = "phone") String phone); +} \ No newline at end of file diff --git a/persistence-modules/spring-data-jpa-5/src/main/java/com/baeldung/partialupdate/repository/CustomerStructuredRepository.java b/persistence-modules/spring-data-jpa-5/src/main/java/com/baeldung/partialupdate/repository/CustomerStructuredRepository.java new file mode 100644 index 0000000000..0f9fd1e92e --- /dev/null +++ b/persistence-modules/spring-data-jpa-5/src/main/java/com/baeldung/partialupdate/repository/CustomerStructuredRepository.java @@ -0,0 +1,11 @@ +package com.baeldung.partialupdate.repository; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +import com.baeldung.partialupdate.model.CustomerStructured; + +@Repository +public interface CustomerStructuredRepository extends CrudRepository { + CustomerStructured findById(long id); +} \ No newline at end of file diff --git a/persistence-modules/spring-data-jpa-5/src/main/java/com/baeldung/partialupdate/service/CustomerService.java b/persistence-modules/spring-data-jpa-5/src/main/java/com/baeldung/partialupdate/service/CustomerService.java new file mode 100644 index 0000000000..9da97a7775 --- /dev/null +++ b/persistence-modules/spring-data-jpa-5/src/main/java/com/baeldung/partialupdate/service/CustomerService.java @@ -0,0 +1,87 @@ +package com.baeldung.partialupdate.service; + +import javax.transaction.Transactional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.baeldung.partialupdate.model.ContactPhone; +import com.baeldung.partialupdate.model.Customer; +import com.baeldung.partialupdate.model.CustomerDto; +import com.baeldung.partialupdate.model.CustomerStructured; +import com.baeldung.partialupdate.repository.ContactPhoneRepository; +import com.baeldung.partialupdate.repository.CustomerRepository; +import com.baeldung.partialupdate.repository.CustomerStructuredRepository; +import com.baeldung.partialupdate.util.CustomerMapper; + +@Service +@Transactional +public class CustomerService { + + @Autowired + CustomerRepository repo; + @Autowired + CustomerStructuredRepository repo2; + @Autowired + ContactPhoneRepository repo3; + @Autowired + CustomerMapper mapper; + + public Customer getCustomer(long id) { + return repo.findById(id); + } + + public void updateCustomerWithCustomQuery(long id, String phone) { + repo.updatePhone(id, phone); + } + + public Customer addCustomer(String name) { + Customer myCustomer = new Customer(); + myCustomer.name = name; + repo.save(myCustomer); + return myCustomer; + } + + public Customer updateCustomer(long id, String phone) { + Customer myCustomer = repo.findById(id); + myCustomer.phone = phone; + repo.save(myCustomer); + return myCustomer; + } + + public Customer addCustomer(CustomerDto dto) { + Customer myCustomer = new Customer(); + mapper.updateCustomerFromDto(dto, myCustomer); + repo.save(myCustomer); + return myCustomer; + } + + public Customer updateCustomer(CustomerDto dto) { + Customer myCustomer = repo.findById(dto.getId()); + mapper.updateCustomerFromDto(dto, myCustomer); + repo.save(myCustomer); + return myCustomer; + } + + public CustomerStructured addCustomerStructured(String name) { + CustomerStructured myCustomer = new CustomerStructured(); + myCustomer.name = name; + repo2.save(myCustomer); + return myCustomer; + } + + public void addCustomerPhone(long customerId, String phone) { + ContactPhone myPhone = new ContactPhone(); + myPhone.phone = phone; + myPhone.customerId = customerId; + repo3.save(myPhone); + } + + public CustomerStructured updateCustomerStructured(long id, String name) { + CustomerStructured myCustomer = repo2.findById(id); + myCustomer.name = name; + repo2.save(myCustomer); + return myCustomer; + } + +} diff --git a/persistence-modules/spring-data-jpa-5/src/main/java/com/baeldung/partialupdate/util/CustomerMapper.java b/persistence-modules/spring-data-jpa-5/src/main/java/com/baeldung/partialupdate/util/CustomerMapper.java new file mode 100644 index 0000000000..8a666e3e6c --- /dev/null +++ b/persistence-modules/spring-data-jpa-5/src/main/java/com/baeldung/partialupdate/util/CustomerMapper.java @@ -0,0 +1,15 @@ +package com.baeldung.partialupdate.util; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; + +import com.baeldung.partialupdate.model.Customer; +import com.baeldung.partialupdate.model.CustomerDto; + +@Mapper(componentModel = "spring") +public interface CustomerMapper { + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) + void updateCustomerFromDto(CustomerDto dto, @MappingTarget Customer entity); +} diff --git a/persistence-modules/spring-data-jpa-5/src/test/java/com/baeldung/partialupdate/PartialUpdateUnitTest.java b/persistence-modules/spring-data-jpa-5/src/test/java/com/baeldung/partialupdate/PartialUpdateUnitTest.java new file mode 100644 index 0000000000..874e18c4ad --- /dev/null +++ b/persistence-modules/spring-data-jpa-5/src/test/java/com/baeldung/partialupdate/PartialUpdateUnitTest.java @@ -0,0 +1,63 @@ +package com.baeldung.partialupdate; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +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.test.context.junit4.SpringRunner; + +import com.baeldung.partialupdate.model.Customer; +import com.baeldung.partialupdate.model.CustomerDto; +import com.baeldung.partialupdate.model.CustomerStructured; +import com.baeldung.partialupdate.service.CustomerService; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = PartialUpdateApplication.class) +public class PartialUpdateUnitTest { + + @Autowired + CustomerService service; + + @Test + public void givenCustomer_whenUpdate_thenSuccess() { + Customer myCustomer = service.addCustomer("John"); + myCustomer = service.updateCustomer(myCustomer.id, "+00"); + assertEquals("+00", myCustomer.phone); + } + + @Test + public void givenCustomer_whenUpdateWithQuery_thenSuccess() { + Customer myCustomer = service.addCustomer("John"); + service.updateCustomerWithCustomQuery(myCustomer.id, "+88"); + myCustomer = service.getCustomer(myCustomer.id); + assertEquals("+88", myCustomer.phone); + } + + @Test + public void givenCustomerDto_whenUpdateWithMapper_thenSuccess() { + CustomerDto dto = new CustomerDto(new Customer()); + dto.name = "Johnny"; + Customer entity = service.addCustomer(dto); + + CustomerDto dto2 = new CustomerDto(entity.id); + dto2.phone = "+44"; + entity = service.updateCustomer(dto2); + + assertEquals("Johnny", entity.name); + } + + @Test + public void givenCustomerStructured_whenUpdateCustomerPhone_thenSuccess() { + CustomerStructured myCustomer = service.addCustomerStructured("John"); + assertEquals(null, myCustomer.contactPhones); + + service.addCustomerPhone(myCustomer.id, "+44"); + myCustomer = service.updateCustomerStructured(myCustomer.id, "Mr. John"); + + assertNotEquals(null, myCustomer.contactPhones); + assertEquals(1, myCustomer.contactPhones.size()); + } +} diff --git a/persistence-modules/spring-data-redis/README.md b/persistence-modules/spring-data-redis/README.md index 175634376b..95cba2c159 100644 --- a/persistence-modules/spring-data-redis/README.md +++ b/persistence-modules/spring-data-redis/README.md @@ -4,7 +4,6 @@ - [Introduction to Spring Data Redis](https://www.baeldung.com/spring-data-redis-tutorial) - [PubSub Messaging with Spring Data Redis](https://www.baeldung.com/spring-data-redis-pub-sub) - [An Introduction to Spring Data Redis Reactive](https://www.baeldung.com/spring-data-redis-reactive) -- [Delete Everything in Redis](https://www.baeldung.com/redis-delete-data) ### Build the Project with Tests Running ``` @@ -15,4 +14,3 @@ mvn clean install ``` mvn test ``` - diff --git a/persistence-modules/spring-data-redis/src/main/java/com/baeldung/spring/data/redis/config/RedisConfig.java b/persistence-modules/spring-data-redis/src/main/java/com/baeldung/spring/data/redis/config/RedisConfig.java index fdc279be42..497e1506bd 100644 --- a/persistence-modules/spring-data-redis/src/main/java/com/baeldung/spring/data/redis/config/RedisConfig.java +++ b/persistence-modules/spring-data-redis/src/main/java/com/baeldung/spring/data/redis/config/RedisConfig.java @@ -35,18 +35,6 @@ public class RedisConfig { template.setValueSerializer(new GenericToStringSerializer(Object.class)); return template; } - - @Bean - public LettuceConnectionFactory lettuceConnectionFactory() { - return new LettuceConnectionFactory(); - } - - @Bean(name = "flushRedisTemplate") - public RedisTemplate flushRedisTemplate() { - RedisTemplate template = new RedisTemplate<>(); - template.setConnectionFactory(lettuceConnectionFactory()); - return template; - } @Bean MessageListenerAdapter messageListener() { diff --git a/persistence-modules/spring-data-redis/src/test/java/com/baeldung/spring/data/redis/delete/RedisFlushDatabaseIntegrationTest.java b/persistence-modules/spring-data-redis/src/test/java/com/baeldung/spring/data/redis/delete/RedisFlushDatabaseIntegrationTest.java deleted file mode 100644 index 1f56cbb25d..0000000000 --- a/persistence-modules/spring-data-redis/src/test/java/com/baeldung/spring/data/redis/delete/RedisFlushDatabaseIntegrationTest.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.baeldung.spring.data.redis.delete; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.io.IOException; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.dao.DataAccessException; -import org.springframework.data.redis.connection.RedisConnection; -import org.springframework.data.redis.core.RedisCallback; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.ValueOperations; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.annotation.DirtiesContext.ClassMode; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -import com.baeldung.spring.data.redis.config.RedisConfig; - -import redis.embedded.RedisServer; - -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = { RedisConfig.class }) -@DirtiesContext(classMode = ClassMode.BEFORE_CLASS) -public class RedisFlushDatabaseIntegrationTest { - - private RedisServer redisServer; - - @Autowired - @Qualifier("flushRedisTemplate") - private RedisTemplate flushRedisTemplate; - - @Before - public void setup() throws IOException { - redisServer = new RedisServer(6390); - redisServer.start(); - } - - @After - public void tearDown() { - redisServer.stop(); - } - - @Test - public void whenFlushDB_thenAllKeysInDatabaseAreCleared() { - - ValueOperations simpleValues = flushRedisTemplate.opsForValue(); - String key = "key"; - String value = "value"; - simpleValues.set(key, value); - assertThat(simpleValues.get(key)).isEqualTo(value); - - flushRedisTemplate.execute(new RedisCallback() { - - @Override - public Void doInRedis(RedisConnection connection) throws DataAccessException { - connection.flushDb(); - return null; - } - }); - - assertThat(simpleValues.get(key)).isNull(); - - } - - @Test - public void whenFlushAll_thenAllKeysInDatabasesAreCleared() { - - ValueOperations simpleValues = flushRedisTemplate.opsForValue(); - String key = "key"; - String value = "value"; - simpleValues.set(key, value); - assertThat(simpleValues.get(key)).isEqualTo(value); - - flushRedisTemplate.execute(new RedisCallback() { - - @Override - public Void doInRedis(RedisConnection connection) throws DataAccessException { - connection.flushAll(); - return null; - } - }); - - assertThat(simpleValues.get(key)).isNull(); - - } -} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 2efea40c91..f797f1bbce 100644 --- a/pom.xml +++ b/pom.xml @@ -513,6 +513,7 @@ libraries-rpc libraries-security libraries-server + libraries-server-2 libraries-testing linkrest logging-modules @@ -1032,6 +1033,7 @@ libraries-primitive libraries-security libraries-server + libraries-server-2 libraries-testing linkrest logging-modules diff --git a/spring-boot-modules/spring-boot-libraries/pom.xml b/spring-boot-modules/spring-boot-libraries/pom.xml index 090967d8a8..189eb4cf1a 100644 --- a/spring-boot-modules/spring-boot-libraries/pom.xml +++ b/spring-boot-modules/spring-boot-libraries/pom.xml @@ -87,7 +87,35 @@ javase ${zxing.version} - + + + com.github.vladimir-bukhtoyarov + bucket4j-core + ${bucket4j.version} + + + com.giffing.bucket4j.spring.boot.starter + bucket4j-spring-boot-starter + ${bucket4j-spring-boot-starter.version} + + + org.springframework.boot + spring-boot-starter-cache + + + javax.cache + cache-api + + + com.github.ben-manes.caffeine + caffeine + ${caffeine.version} + + + com.github.ben-manes.caffeine + jcache + ${caffeine.version} + @@ -200,6 +228,9 @@ 2.1 2.6.0 3.3.0 + 4.10.0 + 0.2.0 + 2.8.2 diff --git a/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/bootstarterapp/Bucket4jRateLimitApp.java b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/bootstarterapp/Bucket4jRateLimitApp.java new file mode 100644 index 0000000000..f16d347f85 --- /dev/null +++ b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/bootstarterapp/Bucket4jRateLimitApp.java @@ -0,0 +1,21 @@ +package com.baeldung.ratelimiting.bootstarterapp; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.cache.annotation.EnableCaching; + +@SpringBootApplication(scanBasePackages = "com.baeldung.ratelimiting", exclude = { + DataSourceAutoConfiguration.class, + SecurityAutoConfiguration.class, +}) +@EnableCaching +public class Bucket4jRateLimitApp { + + public static void main(String[] args) { + new SpringApplicationBuilder(Bucket4jRateLimitApp.class) + .properties("spring.config.location=classpath:ratelimiting/application-bucket4j-starter.yml") + .run(args); + } +} diff --git a/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/bucket4japp/Bucket4jRateLimitApp.java b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/bucket4japp/Bucket4jRateLimitApp.java new file mode 100644 index 0000000000..bb179b9b38 --- /dev/null +++ b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/bucket4japp/Bucket4jRateLimitApp.java @@ -0,0 +1,35 @@ +package com.baeldung.ratelimiting.bucket4japp; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.context.annotation.Lazy; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import com.baeldung.ratelimiting.bucket4japp.interceptor.RateLimitInterceptor; + +@SpringBootApplication(scanBasePackages = "com.baeldung.ratelimiting", exclude = { + DataSourceAutoConfiguration.class, + SecurityAutoConfiguration.class +}) +public class Bucket4jRateLimitApp implements WebMvcConfigurer { + + @Autowired + @Lazy + private RateLimitInterceptor interceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(interceptor) + .addPathPatterns("/api/v1/area/**"); + } + + public static void main(String[] args) { + new SpringApplicationBuilder(Bucket4jRateLimitApp.class) + .properties("spring.config.location=classpath:ratelimiting/application-bucket4j.yml") + .run(args); + } +} diff --git a/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/bucket4japp/interceptor/RateLimitInterceptor.java b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/bucket4japp/interceptor/RateLimitInterceptor.java new file mode 100644 index 0000000000..8a18d6c2b5 --- /dev/null +++ b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/bucket4japp/interceptor/RateLimitInterceptor.java @@ -0,0 +1,57 @@ +package com.baeldung.ratelimiting.bucket4japp.interceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +import com.baeldung.ratelimiting.bucket4japp.service.PricingPlanService; + +import io.github.bucket4j.Bucket; +import io.github.bucket4j.ConsumptionProbe; + +@Component +public class RateLimitInterceptor implements HandlerInterceptor { + + private static final String HEADER_API_KEY = "X-api-key"; + private static final String HEADER_LIMIT_REMAINING = "X-Rate-Limit-Remaining"; + private static final String HEADER_RETRY_AFTER = "X-Rate-Limit-Retry-After-Seconds"; + + @Autowired + private PricingPlanService pricingPlanService; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + + String apiKey = request.getHeader(HEADER_API_KEY); + + if (apiKey == null || apiKey.isEmpty()) { + response.sendError(HttpStatus.BAD_REQUEST.value(), "Missing Header: " + HEADER_API_KEY); + return false; + } + + Bucket tokenBucket = pricingPlanService.resolveBucket(apiKey); + + ConsumptionProbe probe = tokenBucket.tryConsumeAndReturnRemaining(1); + + if (probe.isConsumed()) { + + response.addHeader(HEADER_LIMIT_REMAINING, String.valueOf(probe.getRemainingTokens())); + return true; + + } else { + + long waitForRefill = probe.getNanosToWaitForRefill() / 1_000_000_000; + + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.addHeader(HEADER_RETRY_AFTER, String.valueOf(waitForRefill)); + response.sendError(HttpStatus.TOO_MANY_REQUESTS.value(), "You have exhausted your API Request Quota"); // 429 + + return false; + } + } +} \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/bucket4japp/service/PricingPlan.java b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/bucket4japp/service/PricingPlan.java new file mode 100644 index 0000000000..27c30ba3a0 --- /dev/null +++ b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/bucket4japp/service/PricingPlan.java @@ -0,0 +1,42 @@ +package com.baeldung.ratelimiting.bucket4japp.service; + +import java.time.Duration; + +import io.github.bucket4j.Bandwidth; +import io.github.bucket4j.Refill; + +public enum PricingPlan { + + FREE(20), + + BASIC(40), + + PROFESSIONAL(100); + + private int bucketCapacity; + + private PricingPlan(int bucketCapacity) { + this.bucketCapacity = bucketCapacity; + } + + Bandwidth getLimit() { + return Bandwidth.classic(bucketCapacity, Refill.intervally(bucketCapacity, Duration.ofHours(1))); + } + + public int bucketCapacity() { + return bucketCapacity; + } + + static PricingPlan resolvePlanFromApiKey(String apiKey) { + if (apiKey == null || apiKey.isEmpty()) { + return FREE; + + } else if (apiKey.startsWith("PX001-")) { + return PROFESSIONAL; + + } else if (apiKey.startsWith("BX001-")) { + return BASIC; + } + return FREE; + } +} diff --git a/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/bucket4japp/service/PricingPlanService.java b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/bucket4japp/service/PricingPlanService.java new file mode 100644 index 0000000000..7d8a718601 --- /dev/null +++ b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/bucket4japp/service/PricingPlanService.java @@ -0,0 +1,31 @@ +package com.baeldung.ratelimiting.bucket4japp.service; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.stereotype.Service; + +import io.github.bucket4j.Bandwidth; +import io.github.bucket4j.Bucket; +import io.github.bucket4j.Bucket4j; + +@Service +public class PricingPlanService { + + private final Map cache = new ConcurrentHashMap<>(); + + public Bucket resolveBucket(String apiKey) { + return cache.computeIfAbsent(apiKey, this::newBucket); + } + + private Bucket newBucket(String apiKey) { + PricingPlan pricingPlan = PricingPlan.resolvePlanFromApiKey(apiKey); + return bucket(pricingPlan.getLimit()); + } + + private Bucket bucket(Bandwidth limit) { + return Bucket4j.builder() + .addLimit(limit) + .build(); + } +} diff --git a/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/controller/AreaCalculationController.java b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/controller/AreaCalculationController.java new file mode 100644 index 0000000000..f3fb63ebdd --- /dev/null +++ b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/controller/AreaCalculationController.java @@ -0,0 +1,29 @@ +package com.baeldung.ratelimiting.controller; + +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +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.RestController; + +import com.baeldung.ratelimiting.dto.AreaV1; +import com.baeldung.ratelimiting.dto.RectangleDimensionsV1; +import com.baeldung.ratelimiting.dto.TriangleDimensionsV1; + +@RestController +@RequestMapping(value = "/api/v1/area", consumes = MediaType.APPLICATION_JSON_VALUE) +class AreaCalculationController { + + @PostMapping(value = "/rectangle", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity rectangle(@RequestBody RectangleDimensionsV1 dimensions) { + + return ResponseEntity.ok(new AreaV1("rectangle", dimensions.getLength() * dimensions.getWidth())); + } + + @PostMapping(value = "/triangle", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity triangle(@RequestBody TriangleDimensionsV1 dimensions) { + + return ResponseEntity.ok(new AreaV1("triangle", 0.5d * dimensions.getHeight() * dimensions.getBase())); + } +} diff --git a/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/dto/AreaV1.java b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/dto/AreaV1.java new file mode 100644 index 0000000000..78097f55b2 --- /dev/null +++ b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/dto/AreaV1.java @@ -0,0 +1,20 @@ +package com.baeldung.ratelimiting.dto; + +public class AreaV1 { + + private String shape; + private Double area; + + public AreaV1(String shape, Double area) { + this.area = area; + this.shape = shape; + } + + public Double getArea() { + return area; + } + + public String getShape() { + return shape; + } +} diff --git a/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/dto/RectangleDimensionsV1.java b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/dto/RectangleDimensionsV1.java new file mode 100644 index 0000000000..e3c17e1ba7 --- /dev/null +++ b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/dto/RectangleDimensionsV1.java @@ -0,0 +1,15 @@ +package com.baeldung.ratelimiting.dto; + +public class RectangleDimensionsV1 { + + private double length; + private double width; + + public double getLength() { + return length; + } + + public double getWidth() { + return width; + } +} diff --git a/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/dto/TriangleDimensionsV1.java b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/dto/TriangleDimensionsV1.java new file mode 100644 index 0000000000..44c954bded --- /dev/null +++ b/spring-boot-modules/spring-boot-libraries/src/main/java/com/baeldung/ratelimiting/dto/TriangleDimensionsV1.java @@ -0,0 +1,15 @@ +package com.baeldung.ratelimiting.dto; + +public class TriangleDimensionsV1 { + + private double base; + private double height; + + public double getBase() { + return base; + } + + public double getHeight() { + return height; + } +} diff --git a/spring-boot-modules/spring-boot-libraries/src/main/resources/ratelimiting/application-bucket4j-starter.yml b/spring-boot-modules/spring-boot-libraries/src/main/resources/ratelimiting/application-bucket4j-starter.yml new file mode 100644 index 0000000000..ecc9f22e0a --- /dev/null +++ b/spring-boot-modules/spring-boot-libraries/src/main/resources/ratelimiting/application-bucket4j-starter.yml @@ -0,0 +1,40 @@ +server: + port: 9001 + +spring: + application: + name: bucket4j-starter-api-rate-limit-app + mvc: + throw-exception-if-no-handler-found: true + resources: + add-mappings: false + cache: + cache-names: + - rate-limit-buckets + caffeine: + spec: maximumSize=100000,expireAfterAccess=3600s + +bucket4j: + enabled: true + filters: + - cache-name: rate-limit-buckets + url: /api/v1/area.* + http-response-body: "{ \"status\": 429, \"error\": \"Too Many Requests\", \"message\": \"You have exhausted your API Request Quota\" }" + rate-limits: + - expression: "getHeader('X-api-key')" + execute-condition: "getHeader('X-api-key').startsWith('PX001-')" + bandwidths: + - capacity: 100 + time: 1 + unit: hours + - expression: "getHeader('X-api-key')" + execute-condition: "getHeader('X-api-key').startsWith('BX001-')" + bandwidths: + - capacity: 40 + time: 1 + unit: hours + - expression: "getHeader('X-api-key')" + bandwidths: + - capacity: 20 + time: 1 + unit: hours diff --git a/spring-boot-modules/spring-boot-libraries/src/main/resources/ratelimiting/application-bucket4j.yml b/spring-boot-modules/spring-boot-libraries/src/main/resources/ratelimiting/application-bucket4j.yml new file mode 100644 index 0000000000..ae19622d9b --- /dev/null +++ b/spring-boot-modules/spring-boot-libraries/src/main/resources/ratelimiting/application-bucket4j.yml @@ -0,0 +1,10 @@ +server: + port: 9000 + +spring: + application: + name: bucket4j-api-rate-limit-app + mvc: + throw-exception-if-no-handler-found: true + resources: + add-mappings: false diff --git a/spring-boot-modules/spring-boot-libraries/src/test/java/com/baeldung/ratelimiting/bootstarterapp/Bucket4jBootStarterRateLimitIntegrationTest.java b/spring-boot-modules/spring-boot-libraries/src/test/java/com/baeldung/ratelimiting/bootstarterapp/Bucket4jBootStarterRateLimitIntegrationTest.java new file mode 100644 index 0000000000..d93e61988b --- /dev/null +++ b/spring-boot-modules/spring-boot-libraries/src/test/java/com/baeldung/ratelimiting/bootstarterapp/Bucket4jBootStarterRateLimitIntegrationTest.java @@ -0,0 +1,63 @@ +package com.baeldung.ratelimiting.bootstarterapp; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.RequestBuilder; + +import com.baeldung.ratelimiting.bucket4japp.service.PricingPlan; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Bucket4jRateLimitApp.class) +@TestPropertySource(properties = "spring.config.location=classpath:ratelimiting/application-bucket4j-starter.yml") +@AutoConfigureMockMvc +public class Bucket4jBootStarterRateLimitIntegrationTest { + + @Autowired + private MockMvc mockMvc; + + @Test + public void givenTriangleAreaCalculator_whenRequestsWithinRateLimit_thenAccepted() throws Exception { + + RequestBuilder request = post("/api/v1/area/triangle").contentType(MediaType.APPLICATION_JSON_VALUE) + .content("{ \"height\": 8, \"base\": 10 }") + .header("X-api-key", "FX001-UBSZ5YRYQ"); + + for (int i = 1; i <= PricingPlan.FREE.bucketCapacity(); i++) { + mockMvc.perform(request) + .andExpect(status().isOk()) + .andExpect(header().exists("X-Rate-Limit-Remaining")) + .andExpect(jsonPath("$.shape", equalTo("triangle"))) + .andExpect(jsonPath("$.area", equalTo(40d))); + } + } + + @Test + public void givenTriangleAreaCalculator_whenRequestRateLimitTriggered_thenRejected() throws Exception { + + RequestBuilder request = post("/api/v1/area/triangle").contentType(MediaType.APPLICATION_JSON_VALUE) + .content("{ \"height\": 8, \"base\": 10 }") + .header("X-api-key", "FX001-ZBSY6YSLP"); + + for (int i = 1; i <= PricingPlan.FREE.bucketCapacity(); i++) { + mockMvc.perform(request); // exhaust limit + } + + mockMvc.perform(request) + .andExpect(status().isTooManyRequests()) + .andExpect(jsonPath("$.message", equalTo("You have exhausted your API Request Quota"))) + .andExpect(header().exists("X-Rate-Limit-Retry-After-Seconds")); + } +} diff --git a/spring-boot-modules/spring-boot-libraries/src/test/java/com/baeldung/ratelimiting/bucket4japp/Bucket4jRateLimitIntegrationTest.java b/spring-boot-modules/spring-boot-libraries/src/test/java/com/baeldung/ratelimiting/bucket4japp/Bucket4jRateLimitIntegrationTest.java new file mode 100644 index 0000000000..20f57a7021 --- /dev/null +++ b/spring-boot-modules/spring-boot-libraries/src/test/java/com/baeldung/ratelimiting/bucket4japp/Bucket4jRateLimitIntegrationTest.java @@ -0,0 +1,61 @@ +package com.baeldung.ratelimiting.bucket4japp; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.RequestBuilder; + +import com.baeldung.ratelimiting.bucket4japp.service.PricingPlan; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Bucket4jRateLimitApp.class) +@AutoConfigureMockMvc +public class Bucket4jRateLimitIntegrationTest { + + @Autowired + private MockMvc mockMvc; + + @Test + public void givenRectangleAreaCalculator_whenRequestsWithinRateLimit_thenAccepted() throws Exception { + + RequestBuilder request = post("/api/v1/area/rectangle").contentType(MediaType.APPLICATION_JSON_VALUE) + .content("{ \"length\": 12, \"width\": 10 }") + .header("X-api-key", "FX001-UBSZ5YRYQ"); + + for (int i = 1; i <= PricingPlan.FREE.bucketCapacity(); i++) { + mockMvc.perform(request) + .andExpect(status().isOk()) + .andExpect(header().exists("X-Rate-Limit-Remaining")) + .andExpect(jsonPath("$.shape", equalTo("rectangle"))) + .andExpect(jsonPath("$.area", equalTo(120d))); + } + } + + @Test + public void givenReactangleAreaCalculator_whenRequestRateLimitTriggered_thenRejected() throws Exception { + + RequestBuilder request = post("/api/v1/area/rectangle").contentType(MediaType.APPLICATION_JSON_VALUE) + .content("{ \"length\": 12, \"width\": 10 }") + .header("X-api-key", "FX001-ZBSY6YSLP"); + + for (int i = 1; i <= PricingPlan.FREE.bucketCapacity(); i++) { + mockMvc.perform(request); // exhaust limit + } + + mockMvc.perform(request) + .andExpect(status().isTooManyRequests()) + .andExpect(status().reason("You have exhausted your API Request Quota")) + .andExpect(header().exists("X-Rate-Limit-Retry-After-Seconds")); + } +} diff --git a/spring-boot-modules/spring-boot-libraries/src/test/java/com/baeldung/ratelimiting/bucket4japp/Bucket4jUsageUnitTest.java b/spring-boot-modules/spring-boot-libraries/src/test/java/com/baeldung/ratelimiting/bucket4japp/Bucket4jUsageUnitTest.java new file mode 100644 index 0000000000..fbf63ba403 --- /dev/null +++ b/spring-boot-modules/spring-boot-libraries/src/test/java/com/baeldung/ratelimiting/bucket4japp/Bucket4jUsageUnitTest.java @@ -0,0 +1,82 @@ +package com.baeldung.ratelimiting.bucket4japp; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.time.Duration; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; + +import io.github.bucket4j.Bandwidth; +import io.github.bucket4j.Bucket; +import io.github.bucket4j.Bucket4j; +import io.github.bucket4j.Refill; + +public class Bucket4jUsageUnitTest { + + @Test + public void givenBucketLimit_whenExceedLimit_thenConsumeReturnsFalse() { + Refill refill = Refill.intervally(10, Duration.ofMinutes(1)); + Bandwidth limit = Bandwidth.classic(10, refill); + Bucket bucket = Bucket4j.builder() + .addLimit(limit) + .build(); + + for (int i = 1; i <= 10; i++) { + assertTrue(bucket.tryConsume(1)); + } + assertFalse(bucket.tryConsume(1)); + } + + @Test + public void givenMultipletLimits_whenExceedSmallerLimit_thenConsumeReturnsFalse() { + Bucket bucket = Bucket4j.builder() + .addLimit(Bandwidth.classic(10, Refill.intervally(10, Duration.ofMinutes(1)))) + .addLimit(Bandwidth.classic(5, Refill.intervally(5, Duration.ofSeconds(20)))) + .build(); + + for (int i = 1; i <= 5; i++) { + assertTrue(bucket.tryConsume(1)); + } + assertFalse(bucket.tryConsume(1)); + } + + @Test + public void givenBucketLimit_whenThrottleRequests_thenConsumeReturnsTrue() throws InterruptedException { + Refill refill = Refill.intervally(1, Duration.ofSeconds(2)); + Bandwidth limit = Bandwidth.classic(1, refill); + Bucket bucket = Bucket4j.builder() + .addLimit(limit) + .build(); + + assertTrue(bucket.tryConsume(1)); + + ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); + CountDownLatch latch = new CountDownLatch(1); + + executor.schedule(new AssertTryConsume(bucket, latch), 2, TimeUnit.SECONDS); + + latch.await(); + } + + static class AssertTryConsume implements Runnable { + + private Bucket bucket; + private CountDownLatch latch; + + AssertTryConsume(Bucket bucket, CountDownLatch latch) { + this.bucket = bucket; + this.latch = latch; + } + + @Override + public void run() { + assertTrue(bucket.tryConsume(1)); + latch.countDown(); + } + } +} diff --git a/spring-boot-modules/spring-boot-libraries/src/test/java/com/baeldung/ratelimiting/bucket4japp/PricingPlanServiceUnitTest.java b/spring-boot-modules/spring-boot-libraries/src/test/java/com/baeldung/ratelimiting/bucket4japp/PricingPlanServiceUnitTest.java new file mode 100644 index 0000000000..325b898779 --- /dev/null +++ b/spring-boot-modules/spring-boot-libraries/src/test/java/com/baeldung/ratelimiting/bucket4japp/PricingPlanServiceUnitTest.java @@ -0,0 +1,36 @@ +package com.baeldung.ratelimiting.bucket4japp; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import com.baeldung.ratelimiting.bucket4japp.service.PricingPlan; +import com.baeldung.ratelimiting.bucket4japp.service.PricingPlanService; + +import io.github.bucket4j.Bucket; + +public class PricingPlanServiceUnitTest { + + private PricingPlanService service = new PricingPlanService(); + + @Test + public void givenAPIKey_whenFreePlan_thenReturnFreePlanBucket() { + Bucket bucket = service.resolveBucket("FX001-UBSZ5YRYQ"); + + assertEquals(PricingPlan.FREE.bucketCapacity(), bucket.getAvailableTokens()); + } + + @Test + public void givenAPIKey_whenBasiclan_thenReturnBasicPlanBucket() { + Bucket bucket = service.resolveBucket("BX001-MBSZ5YRYP"); + + assertEquals(PricingPlan.BASIC.bucketCapacity(), bucket.getAvailableTokens()); + } + + @Test + public void givenAPIKey_whenProfessionalPlan_thenReturnProfessionalPlanBucket() { + Bucket bucket = service.resolveBucket("PX001-NBSZ5YRYY"); + + assertEquals(PricingPlan.PROFESSIONAL.bucketCapacity(), bucket.getAvailableTokens()); + } +} diff --git a/spring-boot-modules/spring-boot-mvc-birt/pom.xml b/spring-boot-modules/spring-boot-mvc-birt/pom.xml index f65b851f30..0e8e231a84 100644 --- a/spring-boot-modules/spring-boot-mvc-birt/pom.xml +++ b/spring-boot-modules/spring-boot-mvc-birt/pom.xml @@ -75,7 +75,6 @@ - 2.1.1.RELEASE com.baeldung.birt.engine.ReportEngineApplication 1.8 1.8 diff --git a/spring-boot-modules/spring-boot-properties/.dockerignore b/spring-boot-modules/spring-boot-properties/.dockerignore new file mode 100644 index 0000000000..df36044e46 --- /dev/null +++ b/spring-boot-modules/spring-boot-properties/.dockerignore @@ -0,0 +1,13 @@ +# Logs +logs +*.log + +# Git +.git +.cache + +# Classes +**/*.class + +# Ignore md files +*.md diff --git a/spring-boot-modules/spring-boot-properties/Dockerfile b/spring-boot-modules/spring-boot-properties/Dockerfile new file mode 100644 index 0000000000..d6bd2a95ae --- /dev/null +++ b/spring-boot-modules/spring-boot-properties/Dockerfile @@ -0,0 +1,10 @@ +FROM maven:3.6.0-jdk-11 +WORKDIR /code/spring-boot-modules/spring-boot-properties/ +COPY ./spring-boot-modules/spring-boot-properties/pom.xml . +COPY ./spring-boot-modules/spring-boot-properties/src ./src +COPY ./parent-boot-2/pom.xml /code/parent-boot-2/pom.xml +COPY ./pom.xml /code/pom.xml +COPY ./custom-pmd-0.0.1.jar /code/custom-pmd-0.0.1.jar +COPY ./baeldung-pmd-rules.xml /code/baeldung-pmd-rules.xml +RUN mvn dependency:resolve +CMD ["mvn", "spring-boot:run"] \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-properties/pom.xml b/spring-boot-modules/spring-boot-properties/pom.xml index ef9c084f4c..98d328bd19 100644 --- a/spring-boot-modules/spring-boot-properties/pom.xml +++ b/spring-boot-modules/spring-boot-properties/pom.xml @@ -128,7 +128,8 @@ 4.4.11 @ 2.2.4.RELEASE - com.baeldung.buildproperties.Application + + com.baeldung.yaml.MyApplication diff --git a/spring-boot-modules/spring-boot-properties/src/main/resources/application.yml b/spring-boot-modules/spring-boot-properties/src/main/resources/application.yml index 6fc6f67cd0..4914ff15f7 100644 --- a/spring-boot-modules/spring-boot-properties/src/main/resources/application.yml +++ b/spring-boot-modules/spring-boot-properties/src/main/resources/application.yml @@ -1,7 +1,14 @@ +spring: + profiles: + active: + - test + +--- + spring: profiles: test name: test-YAML -environment: test +environment: testing servers: - www.abc.test.com - www.xyz.test.com @@ -15,3 +22,13 @@ environment: production servers: - www.abc.com - www.xyz.com + +--- + +spring: + profiles: dev +name: ${DEV_NAME:dev-YAML} +environment: development +servers: + - www.abc.dev.com + - www.xyz.dev.com diff --git a/spring-boot-modules/spring-boot-properties/src/test/java/com/baeldung/yaml/YAMLDevIntegrationTest.java b/spring-boot-modules/spring-boot-properties/src/test/java/com/baeldung/yaml/YAMLDevIntegrationTest.java new file mode 100644 index 0000000000..8dfc4c2208 --- /dev/null +++ b/spring-boot-modules/spring-boot-properties/src/test/java/com/baeldung/yaml/YAMLDevIntegrationTest.java @@ -0,0 +1,25 @@ +package com.baeldung.yaml; + +import static org.junit.Assert.assertTrue; + +import org.junit.jupiter.api.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = MyApplication.class) +@TestPropertySource(properties = {"spring.profiles.active = dev"}) +class YAMLDevIntegrationTest { + + @Autowired + private YAMLConfig config; + + @Test + void whenProfileTest_thenNameTesting() { + assertTrue("development".equalsIgnoreCase(config.getEnvironment())); + assertTrue("dev-YAML".equalsIgnoreCase(config.getName())); + } +} diff --git a/spring-boot-modules/spring-boot-properties/src/test/java/com/baeldung/yaml/YAMLIntegrationTest.java b/spring-boot-modules/spring-boot-properties/src/test/java/com/baeldung/yaml/YAMLIntegrationTest.java new file mode 100644 index 0000000000..090d5c592e --- /dev/null +++ b/spring-boot-modules/spring-boot-properties/src/test/java/com/baeldung/yaml/YAMLIntegrationTest.java @@ -0,0 +1,24 @@ +package com.baeldung.yaml; + +import static org.junit.Assert.assertTrue; + +import org.junit.jupiter.api.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = MyApplication.class) +class YAMLIntegrationTest { + + @Autowired + private YAMLConfig config; + + @Test + void whenProfileTest_thenNameTesting() { + assertTrue("testing".equalsIgnoreCase(config.getEnvironment())); + assertTrue("test-YAML".equalsIgnoreCase(config.getName())); + } +} diff --git a/spring-caching/src/test/java/com/baeldung/multiplecachemanager/MultipleCacheManagerIntegrationUnitTest.java b/spring-caching/src/test/java/com/baeldung/multiplecachemanager/MultipleCacheManagerIntegrationTest.java similarity index 97% rename from spring-caching/src/test/java/com/baeldung/multiplecachemanager/MultipleCacheManagerIntegrationUnitTest.java rename to spring-caching/src/test/java/com/baeldung/multiplecachemanager/MultipleCacheManagerIntegrationTest.java index e02e5da246..c83d4f9e96 100644 --- a/spring-caching/src/test/java/com/baeldung/multiplecachemanager/MultipleCacheManagerIntegrationUnitTest.java +++ b/spring-caching/src/test/java/com/baeldung/multiplecachemanager/MultipleCacheManagerIntegrationTest.java @@ -17,7 +17,7 @@ import com.baeldung.multiplecachemanager.repository.OrderDetailRepository; @SpringBootApplication @SpringBootTest -public class MultipleCacheManagerIntegrationUnitTest { +public class MultipleCacheManagerIntegrationTest { @MockBean private OrderDetailRepository orderDetailRepository; diff --git a/spring-cloud-bus/pom.xml b/spring-cloud-bus/pom.xml index 513c8bade6..ec56e23ac7 100644 --- a/spring-cloud-bus/pom.xml +++ b/spring-cloud-bus/pom.xml @@ -11,9 +11,9 @@ com.baeldung - parent-boot-1 + parent-boot-2 0.0.1-SNAPSHOT - ../parent-boot-1 + ../parent-boot-2 @@ -34,7 +34,7 @@ - Brixton.SR7 + Hoxton.SR4 diff --git a/spring-cloud-bus/spring-cloud-config-client/pom.xml b/spring-cloud-bus/spring-cloud-config-client/pom.xml index 7e1185415b..cc1c237646 100644 --- a/spring-cloud-bus/spring-cloud-config-client/pom.xml +++ b/spring-cloud-bus/spring-cloud-config-client/pom.xml @@ -33,6 +33,11 @@ org.springframework.boot spring-boot-actuator + + + org.springframework.boot + spring-boot-actuator-autoconfigure + org.springframework.cloud diff --git a/spring-cloud-bus/spring-cloud-config-client/src/main/resources/application.yml b/spring-cloud-bus/spring-cloud-config-client/src/main/resources/application.yml index 547e0284f3..fbbc6d138f 100644 --- a/spring-cloud-bus/spring-cloud-config-client/src/main/resources/application.yml +++ b/spring-cloud-bus/spring-cloud-config-client/src/main/resources/application.yml @@ -4,4 +4,14 @@ spring: host: localhost port: 5672 username: guest - password: guest \ No newline at end of file + password: guest + cloud: + bus: + enabled: true + refresh: + enabled: true +management: + endpoints: + web: + exposure: + include: "*" \ No newline at end of file diff --git a/spring-cloud-bus/spring-cloud-config-server/src/main/resources/application.properties b/spring-cloud-bus/spring-cloud-config-server/src/main/resources/application.properties index 4c18c192c0..6d7a945612 100644 --- a/spring-cloud-bus/spring-cloud-config-server/src/main/resources/application.properties +++ b/spring-cloud-bus/spring-cloud-config-server/src/main/resources/application.properties @@ -1,11 +1,8 @@ server.port=8888 spring.cloud.config.server.git.uri= -security.user.name=root -security.user.password=s3cr3t -encrypt.key-store.location=classpath:/config-server.jks -encrypt.key-store.password=my-s70r3-s3cr3t -encrypt.key-store.alias=config-server-key -encrypt.key-store.secret=my-k34-s3cr3t +spring.cloud.bus.enabled=true +spring.security.user.name=root +spring.security.user.password=s3cr3t spring.rabbitmq.host=localhost spring.rabbitmq.port=5672 spring.rabbitmq.username=guest diff --git a/spring-cloud-bus/spring-cloud-config-server/src/main/resources/bootstrap.properties b/spring-cloud-bus/spring-cloud-config-server/src/main/resources/bootstrap.properties new file mode 100644 index 0000000000..b0c35c72a6 --- /dev/null +++ b/spring-cloud-bus/spring-cloud-config-server/src/main/resources/bootstrap.properties @@ -0,0 +1,4 @@ +encrypt.key-store.location=classpath:/config-server.jks +encrypt.key-store.password=my-s70r3-s3cr3t +encrypt.key-store.alias=config-server-key +encrypt.key-store.secret=my-k34-s3cr3t \ No newline at end of file diff --git a/spring-cloud/spring-cloud-kubernetes/kubernetes-selfhealing/liveness-example/pom.xml b/spring-cloud/spring-cloud-kubernetes/kubernetes-selfhealing/liveness-example/pom.xml index 66d8f096ce..f0d34d2231 100644 --- a/spring-cloud/spring-cloud-kubernetes/kubernetes-selfhealing/liveness-example/pom.xml +++ b/spring-cloud/spring-cloud-kubernetes/kubernetes-selfhealing/liveness-example/pom.xml @@ -8,10 +8,10 @@ 1.0-SNAPSHOT - com.baeldung - parent-boot-1 - 0.0.1-SNAPSHOT - ../../../../parent-boot-1 + com.baeldung.spring.cloud + spring-cloud-kubernetes + 1.0-SNAPSHOT + ../../../spring-cloud-kubernetes @@ -44,7 +44,6 @@ UTF-8 UTF-8 - 1.5.17.RELEASE \ No newline at end of file diff --git a/spring-cloud/spring-cloud-kubernetes/kubernetes-selfhealing/readiness-example/pom.xml b/spring-cloud/spring-cloud-kubernetes/kubernetes-selfhealing/readiness-example/pom.xml index fbb9e09d07..8bfd4d305d 100644 --- a/spring-cloud/spring-cloud-kubernetes/kubernetes-selfhealing/readiness-example/pom.xml +++ b/spring-cloud/spring-cloud-kubernetes/kubernetes-selfhealing/readiness-example/pom.xml @@ -8,10 +8,10 @@ 1.0-SNAPSHOT - com.baeldung - parent-boot-1 - 0.0.1-SNAPSHOT - ../../../../parent-boot-1 + com.baeldung.spring.cloud + spring-cloud-kubernetes + 1.0-SNAPSHOT + ../../../spring-cloud-kubernetes @@ -44,7 +44,6 @@ UTF-8 UTF-8 - 1.5.17.RELEASE \ No newline at end of file diff --git a/spring-core-4/pom.xml b/spring-core-4/pom.xml index 53f7ca6912..fbec5ea9eb 100644 --- a/spring-core-4/pom.xml +++ b/spring-core-4/pom.xml @@ -24,6 +24,16 @@ spring-core ${spring.version} + + org.springframework + spring-expression + ${spring.version} + + + com.google.guava + guava + 28.2-jre + org.springframework spring-test @@ -42,6 +52,18 @@ ${junit-jupiter.version} test + + org.awaitility + awaitility + 4.0.2 + test + + + org.assertj + assertj-core + 2.9.1 + test + @@ -60,4 +82,4 @@ 2.2.2.RELEASE - \ No newline at end of file + diff --git a/spring-core-4/src/main/java/com/baeldung/beanpostprocessor/GlobalEventBus.java b/spring-core-4/src/main/java/com/baeldung/beanpostprocessor/GlobalEventBus.java new file mode 100644 index 0000000000..8b3c528c4d --- /dev/null +++ b/spring-core-4/src/main/java/com/baeldung/beanpostprocessor/GlobalEventBus.java @@ -0,0 +1,39 @@ +package com.baeldung.beanpostprocessor; + +import com.google.common.eventbus.AsyncEventBus; +import com.google.common.eventbus.EventBus; + +import java.util.concurrent.Executors; + +@SuppressWarnings("ALL") +public final class GlobalEventBus { + + public static final String GLOBAL_EVENT_BUS_EXPRESSION = "T(com.baeldung.postprocessor.GlobalEventBus).getEventBus()"; + + private static final String IDENTIFIER = "global-event-bus"; + + private static final GlobalEventBus GLOBAL_EVENT_BUS = new GlobalEventBus(); + + private final EventBus eventBus = new AsyncEventBus(IDENTIFIER, Executors.newCachedThreadPool()); + + private GlobalEventBus() { + } + + public static GlobalEventBus getInstance() { + return GlobalEventBus.GLOBAL_EVENT_BUS; + } + + public static EventBus getEventBus() { + return GlobalEventBus.GLOBAL_EVENT_BUS.eventBus; + } + + public static void subscribe(Object obj) { + getEventBus().register(obj); + } + public static void unsubscribe(Object obj) { + getEventBus().unregister(obj); + } + public static void post(Object event) { + getEventBus().post(event); + } +} diff --git a/spring-core-4/src/main/java/com/baeldung/beanpostprocessor/GuavaEventBusBeanFactoryPostProcessor.java b/spring-core-4/src/main/java/com/baeldung/beanpostprocessor/GuavaEventBusBeanFactoryPostProcessor.java new file mode 100644 index 0000000000..e0108655cf --- /dev/null +++ b/spring-core-4/src/main/java/com/baeldung/beanpostprocessor/GuavaEventBusBeanFactoryPostProcessor.java @@ -0,0 +1,63 @@ +package com.baeldung.beanpostprocessor; + +import com.google.common.eventbus.EventBus; + +import java.util.Iterator; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.aop.framework.Advised; +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.FatalBeanException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionException; +import org.springframework.expression.spel.standard.SpelExpressionParser; + +@SuppressWarnings("ALL") +public class GuavaEventBusBeanFactoryPostProcessor implements BeanFactoryPostProcessor { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private final SpelExpressionParser expressionParser = new SpelExpressionParser(); + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + for (Iterator names = beanFactory.getBeanNamesIterator(); names.hasNext(); ) { + Object proxy = this.getTargetObject(beanFactory.getBean(names.next())); + final Subscriber annotation = AnnotationUtils.getAnnotation(proxy.getClass(), Subscriber.class); + if (annotation == null) + continue; + this.logger.info("{}: processing bean of type {} during initialization", this.getClass().getSimpleName(), + proxy.getClass().getName()); + final String annotationValue = annotation.value(); + try { + final Expression expression = this.expressionParser.parseExpression(annotationValue); + final Object value = expression.getValue(); + if (!(value instanceof EventBus)) { + this.logger.error("{}: expression {} did not evaluate to an instance of EventBus for bean of type {}", + this.getClass().getSimpleName(), annotationValue, proxy.getClass().getSimpleName()); + return; + } + final EventBus eventBus = (EventBus)value; + eventBus.register(proxy); + } catch (ExpressionException ex) { + this.logger.error("{}: unable to parse/evaluate expression {} for bean of type {}", this.getClass().getSimpleName(), + annotationValue, proxy.getClass().getName()); + } + } + } + + private Object getTargetObject(Object proxy) throws BeansException { + if (AopUtils.isJdkDynamicProxy(proxy)) { + try { + return ((Advised)proxy).getTargetSource().getTarget(); + } catch (Exception e) { + throw new FatalBeanException("Error getting target of JDK proxy", e); + } + } + return proxy; + } +} diff --git a/spring-core-4/src/main/java/com/baeldung/beanpostprocessor/GuavaEventBusBeanPostProcessor.java b/spring-core-4/src/main/java/com/baeldung/beanpostprocessor/GuavaEventBusBeanPostProcessor.java new file mode 100644 index 0000000000..be3800c40a --- /dev/null +++ b/spring-core-4/src/main/java/com/baeldung/beanpostprocessor/GuavaEventBusBeanPostProcessor.java @@ -0,0 +1,87 @@ +package com.baeldung.beanpostprocessor; + +import com.google.common.eventbus.EventBus; + +import java.util.function.BiConsumer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.aop.framework.Advised; +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.FatalBeanException; +import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionException; +import org.springframework.expression.spel.standard.SpelExpressionParser; + +/** + * A {@link DestructionAwareBeanPostProcessor} which registers/un-registers subscribers to a Guava {@link EventBus}. The class must + * be annotated with {@link Subscriber} and each subscribing method must be annotated with + * {@link com.google.common.eventbus.Subscribe}. + */ +@SuppressWarnings("ALL") +public class GuavaEventBusBeanPostProcessor implements DestructionAwareBeanPostProcessor { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private final SpelExpressionParser expressionParser = new SpelExpressionParser(); + + @Override + public void postProcessBeforeDestruction(final Object bean, final String beanName) throws BeansException { + this.process(bean, EventBus::unregister, "destruction"); + } + + @Override + public boolean requiresDestruction(Object bean) { + return true; + } + + @Override + public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException { + return bean; + } + + @Override + public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException { + this.process(bean, EventBus::register, "initialization"); + return bean; + } + + private void process(final Object bean, final BiConsumer consumer, final String action) { + Object proxy = this.getTargetObject(bean); + final Subscriber annotation = AnnotationUtils.getAnnotation(proxy.getClass(), Subscriber.class); + if (annotation == null) + return; + this.logger.info("{}: processing bean of type {} during {}", this.getClass().getSimpleName(), proxy.getClass().getName(), + action); + final String annotationValue = annotation.value(); + try { + final Expression expression = this.expressionParser.parseExpression(annotationValue); + final Object value = expression.getValue(); + if (!(value instanceof EventBus)) { + this.logger.error("{}: expression {} did not evaluate to an instance of EventBus for bean of type {}", + this.getClass().getSimpleName(), annotationValue, proxy.getClass().getSimpleName()); + return; + } + final EventBus eventBus = (EventBus)value; + consumer.accept(eventBus, proxy); + } catch (ExpressionException ex) { + this.logger.error("{}: unable to parse/evaluate expression {} for bean of type {}", this.getClass().getSimpleName(), + annotationValue, proxy.getClass().getName()); + } + } + + private Object getTargetObject(Object proxy) throws BeansException { + if (AopUtils.isJdkDynamicProxy(proxy)) { + try { + return ((Advised)proxy).getTargetSource().getTarget(); + } catch (Exception e) { + throw new FatalBeanException("Error getting target of JDK proxy", e); + } + } + return proxy; + } +} + + diff --git a/spring-core-4/src/main/java/com/baeldung/beanpostprocessor/StockTrade.java b/spring-core-4/src/main/java/com/baeldung/beanpostprocessor/StockTrade.java new file mode 100644 index 0000000000..f27f9e7c9b --- /dev/null +++ b/spring-core-4/src/main/java/com/baeldung/beanpostprocessor/StockTrade.java @@ -0,0 +1,34 @@ +package com.baeldung.beanpostprocessor; + +import java.util.Date; + +public class StockTrade { + + private final String symbol; + private final int quantity; + private final double price; + private final Date tradeDate; + + public StockTrade(String symbol, int quantity, double price, Date tradeDate) { + this.symbol = symbol; + this.quantity = quantity; + this.price = price; + this.tradeDate = tradeDate; + } + + public String getSymbol() { + return this.symbol; + } + + public int getQuantity() { + return this.quantity; + } + + public double getPrice() { + return this.price; + } + + public Date getTradeDate() { + return this.tradeDate; + } +} diff --git a/spring-core-4/src/main/java/com/baeldung/beanpostprocessor/StockTradeListener.java b/spring-core-4/src/main/java/com/baeldung/beanpostprocessor/StockTradeListener.java new file mode 100644 index 0000000000..a0ee293293 --- /dev/null +++ b/spring-core-4/src/main/java/com/baeldung/beanpostprocessor/StockTradeListener.java @@ -0,0 +1,7 @@ +package com.baeldung.beanpostprocessor; + +@FunctionalInterface +public interface StockTradeListener { + + void stockTradePublished(StockTrade trade); +} diff --git a/spring-core-4/src/main/java/com/baeldung/beanpostprocessor/StockTradePublisher.java b/spring-core-4/src/main/java/com/baeldung/beanpostprocessor/StockTradePublisher.java new file mode 100644 index 0000000000..0058944b53 --- /dev/null +++ b/spring-core-4/src/main/java/com/baeldung/beanpostprocessor/StockTradePublisher.java @@ -0,0 +1,36 @@ +package com.baeldung.beanpostprocessor; + +import com.google.common.eventbus.AllowConcurrentEvents; +import com.google.common.eventbus.Subscribe; + +import java.util.HashSet; +import java.util.Set; + +@Subscriber +public class StockTradePublisher { + + private final Set stockTradeListeners = new HashSet<>(); + + public void addStockTradeListener(StockTradeListener listener) { + synchronized (this.stockTradeListeners) { + this.stockTradeListeners.add(listener); + } + } + + public void removeStockTradeListener(StockTradeListener listener) { + synchronized (this.stockTradeListeners) { + this.stockTradeListeners.remove(listener); + } + } + + @Subscribe + @AllowConcurrentEvents + private void handleNewStockTradeEvent(StockTrade trade) { + // publish to DB, send to PubNub, whatever you want here + final Set listeners; + synchronized (this.stockTradeListeners) { + listeners = new HashSet<>(this.stockTradeListeners); + } + listeners.forEach(li -> li.stockTradePublished(trade)); + } +} diff --git a/spring-core-4/src/main/java/com/baeldung/beanpostprocessor/Subscriber.java b/spring-core-4/src/main/java/com/baeldung/beanpostprocessor/Subscriber.java new file mode 100644 index 0000000000..1aca507555 --- /dev/null +++ b/spring-core-4/src/main/java/com/baeldung/beanpostprocessor/Subscriber.java @@ -0,0 +1,21 @@ +package com.baeldung.beanpostprocessor; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation which indicates which Guava {@link com.google.common.eventbus.EventBus} a Spring bean wishes to subscribe to. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +public @interface Subscriber { + + /** + * A SpEL expression which selects the {@link com.google.common.eventbus.EventBus}. + */ + String value() default GlobalEventBus.GLOBAL_EVENT_BUS_EXPRESSION; +} diff --git a/spring-core-4/src/test/java/com/baeldung/beanpostprocessor/PostProcessorConfiguration.java b/spring-core-4/src/test/java/com/baeldung/beanpostprocessor/PostProcessorConfiguration.java new file mode 100644 index 0000000000..842283f563 --- /dev/null +++ b/spring-core-4/src/test/java/com/baeldung/beanpostprocessor/PostProcessorConfiguration.java @@ -0,0 +1,23 @@ +package com.baeldung.beanpostprocessor; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class PostProcessorConfiguration { + + @Bean + public GlobalEventBus eventBus() { + return GlobalEventBus.getInstance(); + } + + @Bean + public GuavaEventBusBeanPostProcessor eventBusBeanPostProcessor() { + return new GuavaEventBusBeanPostProcessor(); + } + + @Bean + public StockTradePublisher stockTradePublisher() { + return new StockTradePublisher(); + } +} diff --git a/spring-core-4/src/test/java/com/baeldung/beanpostprocessor/StockTradeIntegrationTest.java b/spring-core-4/src/test/java/com/baeldung/beanpostprocessor/StockTradeIntegrationTest.java new file mode 100644 index 0000000000..74d6765ecd --- /dev/null +++ b/spring-core-4/src/test/java/com/baeldung/beanpostprocessor/StockTradeIntegrationTest.java @@ -0,0 +1,46 @@ +package com.baeldung.beanpostprocessor; + +import java.time.Duration; +import java.util.Date; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {PostProcessorConfiguration.class}) +public class StockTradeIntegrationTest { + + @Autowired + private StockTradePublisher stockTradePublisher; + + @Test + public void givenValidConfig_whenTradePublished_thenTradeReceived() { + Date tradeDate = new Date(); + StockTrade stockTrade = new StockTrade("AMZN", 100, 2483.52d, tradeDate); + AtomicBoolean assertionsPassed = new AtomicBoolean(false); + StockTradeListener listener = trade -> assertionsPassed.set(this.verifyExact(stockTrade, trade)); + this.stockTradePublisher.addStockTradeListener(listener); + try { + GlobalEventBus.post(stockTrade); + await().atMost(Duration.ofSeconds(2L)) + .untilAsserted(() -> assertThat(assertionsPassed.get()).isTrue()); + } finally { + this.stockTradePublisher.removeStockTradeListener(listener); + } + } + + private boolean verifyExact(StockTrade stockTrade, StockTrade trade) { + return Objects.equals(stockTrade.getSymbol(), trade.getSymbol()) + && Objects.equals(stockTrade.getTradeDate(), trade.getTradeDate()) + && stockTrade.getQuantity() == trade.getQuantity() + && stockTrade.getPrice() == trade.getPrice(); + } +} diff --git a/spring-security-modules/pom.xml b/spring-security-modules/pom.xml index 7ce33dd3e3..60a662781f 100644 --- a/spring-security-modules/pom.xml +++ b/spring-security-modules/pom.xml @@ -15,6 +15,7 @@ spring-security-acl + spring-security-auth0 spring-security-angular/server spring-security-cache-control spring-security-core diff --git a/spring-security-modules/spring-security-auth0/pom.xml b/spring-security-modules/spring-security-auth0/pom.xml new file mode 100644 index 0000000000..0bd879a40b --- /dev/null +++ b/spring-security-modules/spring-security-auth0/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + spring-security-auth0 + 1.0-SNAPSHOT + spring-security-auth0 + war + + + com.baeldung + parent-boot-2 + 0.0.1-SNAPSHOT + ../../parent-boot-2 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.security + spring-security-core + + + org.springframework.security + spring-security-oauth2-resource-server + + + com.auth0 + mvc-auth-commons + ${mvc-auth-commons.version} + + + org.json + json + ${json.version} + + + + + spring-security-auth0 + + + src/main/resources + + + + + org.springframework.boot + spring-boot-maven-plugin + + true + + + + + repackage + + + + + + + + + 20190722 + 1.2.0 + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/Application.java b/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/Application.java new file mode 100644 index 0000000000..42f8d946b5 --- /dev/null +++ b/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/Application.java @@ -0,0 +1,13 @@ +package com.baeldung.auth0; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + +} diff --git a/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/AuthConfig.java b/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/AuthConfig.java new file mode 100644 index 0000000000..69cf8b3071 --- /dev/null +++ b/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/AuthConfig.java @@ -0,0 +1,114 @@ +package com.baeldung.auth0; + +import java.io.UnsupportedEncodingException; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +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.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; + +import com.auth0.AuthenticationController; +import com.baeldung.auth0.controller.LogoutController; +import com.auth0.jwk.JwkProvider; +import com.auth0.jwk.JwkProviderBuilder; + +@Configuration +@EnableWebSecurity +public class AuthConfig extends WebSecurityConfigurerAdapter { + + @Value(value = "${com.auth0.domain}") + private String domain; + + @Value(value = "${com.auth0.clientId}") + private String clientId; + + @Value(value = "${com.auth0.clientSecret}") + private String clientSecret; + + @Value(value = "${com.auth0.managementApi.clientId}") + private String managementApiClientId; + + @Value(value = "${com.auth0.managementApi.clientSecret}") + private String managementApiClientSecret; + + @Value(value = "${com.auth0.managementApi.grantType}") + private String grantType; + + @Bean + public LogoutSuccessHandler logoutSuccessHandler() { + return new LogoutController(); + } + + @Bean + public AuthenticationController authenticationController() throws UnsupportedEncodingException { + JwkProvider jwkProvider = new JwkProviderBuilder(domain).build(); + return AuthenticationController.newBuilder(domain, clientId, clientSecret) + .withJwkProvider(jwkProvider) + .build(); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf().disable(); + http + .authorizeRequests() + .antMatchers("/callback", "/login", "/").permitAll() + .anyRequest().authenticated() + .and() + .formLogin() + .loginPage("/login") + .and() + .logout().logoutSuccessHandler(logoutSuccessHandler()).permitAll(); + } + + public String getDomain() { + return domain; + } + + public String getClientId() { + return clientId; + } + + public String getClientSecret() { + return clientSecret; + } + + public String getManagementApiClientId() { + return managementApiClientId; + } + + public String getManagementApiClientSecret() { + return managementApiClientSecret; + } + + public String getGrantType() { + return grantType; + } + + public String getUserInfoUrl() { + return "https://" + getDomain() + "/userinfo"; + } + + public String getUsersUrl() { + return "https://" + getDomain() + "/api/v2/users"; + } + + public String getUsersByEmailUrl() { + return "https://" + getDomain() + "/api/v2/users-by-email?email="; + } + + public String getLogoutUrl() { + return "https://" + getDomain() +"/v2/logout"; + } + + public String getContextPath(HttpServletRequest request) { + String path = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort(); + return path; + } +} diff --git a/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/controller/AuthController.java b/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/controller/AuthController.java new file mode 100644 index 0000000000..48d09db155 --- /dev/null +++ b/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/controller/AuthController.java @@ -0,0 +1,77 @@ +package com.baeldung.auth0.controller; + +import java.io.IOException; +import java.util.HashMap; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.json.JSONObject; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.client.RestTemplate; + +import com.auth0.AuthenticationController; +import com.auth0.IdentityVerificationException; +import com.auth0.Tokens; +import com.auth0.jwt.JWT; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.baeldung.auth0.AuthConfig; + +@Controller +public class AuthController { + + @Autowired + private AuthenticationController authenticationController; + + @Autowired + private AuthConfig config; + + private static final String AUTH0_TOKEN_URL = "https://dev-example.auth0.com/oauth/token"; + + @GetMapping(value = "/login") + protected void login(HttpServletRequest request, HttpServletResponse response) throws IOException { + String redirectUri = config.getContextPath(request) + "/callback"; + String authorizeUrl = authenticationController.buildAuthorizeUrl(request, response, redirectUri) + .withScope("openid email") + .build(); + response.sendRedirect(authorizeUrl); + } + + @GetMapping(value="/callback") + public void callback(HttpServletRequest request, HttpServletResponse response) throws IOException, IdentityVerificationException { + Tokens tokens = authenticationController.handle(request, response); + + DecodedJWT jwt = JWT.decode(tokens.getIdToken()); + TestingAuthenticationToken authToken2 = new TestingAuthenticationToken(jwt.getSubject(), jwt.getToken()); + authToken2.setAuthenticated(true); + + SecurityContextHolder.getContext().setAuthentication(authToken2); + response.sendRedirect(config.getContextPath(request) + "/"); + } + + public String getManagementApiToken() { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + JSONObject requestBody = new JSONObject(); + requestBody.put("client_id", config.getManagementApiClientId()); + requestBody.put("client_secret", config.getManagementApiClientSecret()); + requestBody.put("audience", "https://dev-example.auth0.com/api/v2/"); + requestBody.put("grant_type", config.getGrantType()); + + HttpEntity request = new HttpEntity(requestBody.toString(), headers); + + RestTemplate restTemplate = new RestTemplate(); + HashMap result = restTemplate.postForObject(AUTH0_TOKEN_URL, request, HashMap.class); + + return result.get("access_token"); + } + +} diff --git a/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/controller/HomeController.java b/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/controller/HomeController.java new file mode 100644 index 0000000000..8a4e650846 --- /dev/null +++ b/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/controller/HomeController.java @@ -0,0 +1,37 @@ +package com.baeldung.auth0.controller; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.interfaces.DecodedJWT; + +@Controller +public class HomeController { + + @GetMapping(value = "/") + @ResponseBody + public String home(HttpServletRequest request, HttpServletResponse response, final Authentication authentication) throws IOException { + + if (authentication!= null && authentication instanceof TestingAuthenticationToken) { + TestingAuthenticationToken token = (TestingAuthenticationToken) authentication; + + DecodedJWT jwt = JWT.decode(token.getCredentials().toString()); + String email = jwt.getClaims().get("email").asString(); + + return "Welcome, " + email + "!"; + } else { + response.sendRedirect("http://localhost:8080/login"); + return null; + } + } + +} diff --git a/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/controller/LogoutController.java b/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/controller/LogoutController.java new file mode 100755 index 0000000000..d508fe2c44 --- /dev/null +++ b/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/controller/LogoutController.java @@ -0,0 +1,35 @@ +package com.baeldung.auth0.controller; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; +import org.springframework.stereotype.Controller; + +import com.baeldung.auth0.AuthConfig; + +@Controller +public class LogoutController implements LogoutSuccessHandler { + + @Autowired + private AuthConfig config; + + @Override + public void onLogoutSuccess(HttpServletRequest req, HttpServletResponse res, Authentication authentication) { + if (req.getSession() != null) { + req.getSession().invalidate(); + } + String returnTo = config.getContextPath(req); + String logoutUrl = config.getLogoutUrl() + "?client_id=" + config.getClientId() + "&returnTo=" +returnTo; + try { + res.sendRedirect(logoutUrl); + } catch(IOException e){ + e.printStackTrace(); + } + } + +} diff --git a/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/controller/UserController.java b/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/controller/UserController.java new file mode 100644 index 0000000000..86601a06d3 --- /dev/null +++ b/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/controller/UserController.java @@ -0,0 +1,57 @@ +package com.baeldung.auth0.controller; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.json.JSONObject; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import com.auth0.IdentityVerificationException; +import com.baeldung.auth0.AuthConfig; +import com.baeldung.auth0.service.ApiService; + +@Controller +public class UserController { + + @Autowired + private ApiService apiService; + + @Autowired + private AuthConfig config; + + @GetMapping(value="/users") + @ResponseBody + public ResponseEntity users(HttpServletRequest request, HttpServletResponse response) throws IOException, IdentityVerificationException { + ResponseEntity result = apiService.getCall(config.getUsersUrl()); + return result; + } + + @GetMapping(value = "/userByEmail") + @ResponseBody + public ResponseEntity userByEmail(HttpServletResponse response, @RequestParam String email) { + ResponseEntity result = apiService.getCall(config.getUsersByEmailUrl()+email); + return result; + } + + @GetMapping(value = "/createUser") + @ResponseBody + public ResponseEntity createUser(HttpServletResponse response) { + JSONObject request = new JSONObject(); + request.put("email", "norman.lewis@email.com"); + request.put("given_name", "Norman"); + request.put("family_name", "Lewis"); + request.put("connection", "Username-Password-Authentication"); + request.put("password", "Pa33w0rd"); + + ResponseEntity result = apiService.postCall(config.getUsersUrl(), request.toString()); + return result; + } + +} diff --git a/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/service/ApiService.java b/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/service/ApiService.java new file mode 100644 index 0000000000..0d8263ae19 --- /dev/null +++ b/spring-security-modules/spring-security-auth0/src/main/java/com/baeldung/auth0/service/ApiService.java @@ -0,0 +1,44 @@ +package com.baeldung.auth0.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import com.baeldung.auth0.controller.AuthController; + +@Service +public class ApiService { + + @Autowired + private AuthController controller; + + public ResponseEntity getCall(String url) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("Authorization", "Bearer "+controller.getManagementApiToken()); + + HttpEntity entity = new HttpEntity(headers); + RestTemplate restTemplate = new RestTemplate(); + ResponseEntity result = restTemplate.exchange(url, HttpMethod.GET, entity, String.class); + + return result; + } + + public ResponseEntity postCall(String url, String requestBody) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("Authorization", "Bearer "+controller.getManagementApiToken()); + + HttpEntity request = new HttpEntity(requestBody, headers); + RestTemplate restTemplate = new RestTemplate(); + ResponseEntity result = restTemplate.postForEntity(url, request, String.class); + + return result; + } + +} diff --git a/spring-security-modules/spring-security-auth0/src/main/resources/application.properties b/spring-security-modules/spring-security-auth0/src/main/resources/application.properties new file mode 100644 index 0000000000..45492c5c00 --- /dev/null +++ b/spring-security-modules/spring-security-auth0/src/main/resources/application.properties @@ -0,0 +1,7 @@ +com.auth0.domain: dev-example.auth0.com +com.auth0.clientId: exampleClientId +com.auth0.clientSecret: exampleClientSecret + +com.auth0.managementApi.clientId: exampleManagementApiClientId +com.auth0.managementApi.clientSecret: exampleManagementApiClientSecret +com.auth0.managementApi.grantType: client_credentials \ No newline at end of file diff --git a/spring-thymeleaf-3/src/test/java/com/baeldung/thymeleaf/currencies/CurrenciesControllerIntegrationTest.java b/spring-thymeleaf-3/src/test/java/com/baeldung/thymeleaf/currencies/CurrenciesControllerIntegrationTest.java index 02bf8a9ee0..c1e3cf7458 100644 --- a/spring-thymeleaf-3/src/test/java/com/baeldung/thymeleaf/currencies/CurrenciesControllerIntegrationTest.java +++ b/spring-thymeleaf-3/src/test/java/com/baeldung/thymeleaf/currencies/CurrenciesControllerIntegrationTest.java @@ -27,7 +27,7 @@ public class CurrenciesControllerIntegrationTest { .header("Accept-Language", "es-ES") .param("amount", "10032.5")) .andExpect(status().isOk()) - .andExpect(content().string(containsString("10.032,50 €"))); + .andExpect(content().string(containsString("10.032,50"))); } @Test @@ -42,10 +42,10 @@ public class CurrenciesControllerIntegrationTest { @Test public void whenCallCurrencyWithRomanianLocaleWithArrays_ThenReturnLocaleCurrencies() throws Exception { mockMvc.perform(MockMvcRequestBuilders.get("/currency") - .header("Accept-Language", "ro-RO") + .header("Accept-Language", "en-GB") .param("amountList", "10", "20", "30")) .andExpect(status().isOk()) - .andExpect(content().string(containsString("10,00 RON, 20,00 RON, 30,00 RON"))); + .andExpect(content().string(containsString("£10.00, £20.00, £30.00"))); } @Test