From 992310bc7e981e7def6174264012b34cc43a0fb5 Mon Sep 17 00:00:00 2001 From: sharifi Date: Wed, 7 Jul 2021 16:30:58 +0430 Subject: [PATCH 01/15] BAEL-2670: update dependency --- metrics/pom.xml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/metrics/pom.xml b/metrics/pom.xml index 2020cd28cf..0ba590ceec 100644 --- a/metrics/pom.xml +++ b/metrics/pom.xml @@ -81,13 +81,18 @@ ${assertj-core.version} test + + com.netflix.spectator + spectator-api + 0.132.0 + 3.1.2 3.1.0 0.12.17 - 0.12.0.RELEASE + 1.7.1 2.0.7.RELEASE 3.11.1 From a6ffc87f6064bcafbc3fe7005b71c3c254c4932b Mon Sep 17 00:00:00 2001 From: sharifi Date: Wed, 7 Jul 2021 16:31:28 +0430 Subject: [PATCH 02/15] BAEL-2670: update main class --- .../java/com/baeldung/metrics/micrometer/MicrometerApp.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/metrics/src/main/java/com/baeldung/metrics/micrometer/MicrometerApp.java b/metrics/src/main/java/com/baeldung/metrics/micrometer/MicrometerApp.java index 1d738085ce..8fdb87cfe7 100644 --- a/metrics/src/main/java/com/baeldung/metrics/micrometer/MicrometerApp.java +++ b/metrics/src/main/java/com/baeldung/metrics/micrometer/MicrometerApp.java @@ -1,10 +1,10 @@ package com.baeldung.metrics.micrometer; +import io.micrometer.core.instrument.binder.jvm.JvmThreadMetrics; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; -import io.micrometer.core.instrument.binder.JvmThreadMetrics; @SpringBootApplication public class MicrometerApp { @@ -14,7 +14,7 @@ public class MicrometerApp { return new JvmThreadMetrics(); } - public static void main(String[] args) throws Exception { + public static void main(String[] args) { SpringApplication.run(MicrometerApp.class, args); } From 9c27a90786e00172605e924f67c8992b02935c25 Mon Sep 17 00:00:00 2001 From: sharifi Date: Wed, 7 Jul 2021 16:37:51 +0430 Subject: [PATCH 03/15] BAEL-2670: update test class --- .../MicrometerAtlasIntegrationTest.java | 107 +++++++++--------- 1 file changed, 54 insertions(+), 53 deletions(-) diff --git a/metrics/src/test/java/com/baeldung/metrics/micrometer/MicrometerAtlasIntegrationTest.java b/metrics/src/test/java/com/baeldung/metrics/micrometer/MicrometerAtlasIntegrationTest.java index 689e23ad6d..a5a8136ecb 100644 --- a/metrics/src/test/java/com/baeldung/metrics/micrometer/MicrometerAtlasIntegrationTest.java +++ b/metrics/src/test/java/com/baeldung/metrics/micrometer/MicrometerAtlasIntegrationTest.java @@ -1,11 +1,7 @@ package com.baeldung.metrics.micrometer; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.within; -import static org.assertj.core.api.Assertions.withinPercentage; -import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.collection.IsMapContaining.hasEntry; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -16,29 +12,25 @@ import io.micrometer.core.instrument.DistributionSummary; import io.micrometer.core.instrument.Gauge; import io.micrometer.core.instrument.LongTaskTimer; import io.micrometer.core.instrument.Measurement; -import io.micrometer.core.instrument.Meter.Type; -import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Metrics; -import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Timer; import io.micrometer.core.instrument.composite.CompositeMeterRegistry; +import io.micrometer.core.instrument.distribution.HistogramSnapshot; +import io.micrometer.core.instrument.distribution.ValueAtPercentile; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; -import io.micrometer.core.instrument.stats.hist.Histogram; -import io.micrometer.core.instrument.stats.quantile.WindowSketchQuantiles; import java.time.Duration; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Arrays; import java.util.Map; +import java.util.TreeMap; import java.util.Optional; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; -import org.assertj.core.data.Percentage; import org.junit.Before; import org.junit.Test; -import org.junit.jupiter.api.Assertions; import com.netflix.spectator.atlas.AtlasConfig; @@ -55,7 +47,7 @@ public class MicrometerAtlasIntegrationTest { @Override public Duration step() { - return Duration.ofSeconds(1); + return Duration.ofSeconds(10); } @Override @@ -77,9 +69,9 @@ public class MicrometerAtlasIntegrationTest { compositeRegistry.gauge("baeldung.heat", 90); - Optional oneGauge = oneSimpleMeter + Optional oneGauge = Optional.ofNullable(oneSimpleMeter .find("baeldung.heat") - .gauge(); + .gauge()); assertTrue(oneGauge.isPresent()); Iterator measurements = oneGauge .get() @@ -91,9 +83,9 @@ public class MicrometerAtlasIntegrationTest { .next() .getValue(), equalTo(90.00)); - Optional atlasGauge = atlasMeterRegistry + Optional atlasGauge = Optional.ofNullable(atlasMeterRegistry .find("baeldung.heat") - .gauge(); + .gauge()); assertTrue(atlasGauge.isPresent()); Iterator anotherMeasurements = atlasGauge .get() @@ -122,9 +114,9 @@ public class MicrometerAtlasIntegrationTest { .increment(); new CountedObject(); - Optional counterOptional = Metrics.globalRegistry + Optional counterOptional = Optional.ofNullable(Metrics.globalRegistry .find("objects.instance") - .counter(); + .counter()); assertTrue(counterOptional.isPresent()); assertTrue(counterOptional @@ -145,7 +137,7 @@ public class MicrometerAtlasIntegrationTest { assertTrue(counter.count() == 2); counter.increment(-1); - assertTrue(counter.count() == 2); + assertTrue(counter.count() == 1); } @Test @@ -162,7 +154,7 @@ public class MicrometerAtlasIntegrationTest { timer.record(30, TimeUnit.MILLISECONDS); assertTrue(2 == timer.count()); - + assertThat(timer.totalTime(TimeUnit.MILLISECONDS)).isBetween(40.0, 55.0); } @@ -173,12 +165,12 @@ public class MicrometerAtlasIntegrationTest { .builder("3rdPartyService") .register(registry); - long currentTaskId = longTaskTimer.start(); + LongTaskTimer.Sample currentTaskId = longTaskTimer.start(); try { TimeUnit.MILLISECONDS.sleep(2); } catch (InterruptedException ignored) { } - long timeElapsed = longTaskTimer.stop(currentTaskId); + long timeElapsed = currentTaskId.stop(); assertEquals(2L, timeElapsed/((int) 1e6),1L); } @@ -213,13 +205,12 @@ public class MicrometerAtlasIntegrationTest { } @Test - public void givenTimer_whenEnrichWithQuantile_thenQuantilesComputed() { + public void givenTimer_whenEnrichWithPercentile_thenPercentilesComputed() { SimpleMeterRegistry registry = new SimpleMeterRegistry(); Timer timer = Timer .builder("test.timer") - .quantiles(WindowSketchQuantiles - .quantiles(0.3, 0.5, 0.95) - .create()) + .publishPercentiles(0.3, 0.5, 0.95) + .publishPercentileHistogram() .register(registry); timer.record(2, TimeUnit.SECONDS); @@ -229,27 +220,18 @@ public class MicrometerAtlasIntegrationTest { timer.record(8, TimeUnit.SECONDS); timer.record(13, TimeUnit.SECONDS); - Map quantileMap = extractTagValueMap(registry, Type.Gauge, 1e9); - assertThat(quantileMap, allOf(hasEntry("quantile=0.3", 2), hasEntry("quantile=0.5", 3), hasEntry("quantile=0.95", 8))); - } + Map expectedMicrometer = new TreeMap<>(); + expectedMicrometer.put(0.3, 1946.157056); + expectedMicrometer.put(0.5, 3019.89888); + expectedMicrometer.put(0.95, 13354.663936); - private Map extractTagValueMap(MeterRegistry registry, Type meterType, double valueDivisor) { - return registry - .getMeters() - .stream() - .filter(meter -> meter.getType() == meterType) - .collect(Collectors.toMap(meter -> { - Tag tag = meter - .getId() - .getTags() - .iterator() - .next(); - return tag.getKey() + "=" + tag.getValue(); - }, meter -> (int) (meter - .measure() - .iterator() - .next() - .getValue() / valueDivisor))); + Map actualMicrometer = new TreeMap<>(); + ValueAtPercentile[] percentiles = timer.takeSnapshot().percentileValues(); + for (ValueAtPercentile percentile : percentiles) { + actualMicrometer.put(percentile.percentile(), percentile.value(TimeUnit.MILLISECONDS)); + } + + assertEquals(expectedMicrometer, actualMicrometer); } @Test @@ -257,7 +239,7 @@ public class MicrometerAtlasIntegrationTest { SimpleMeterRegistry registry = new SimpleMeterRegistry(); DistributionSummary hist = DistributionSummary .builder("summary") - .histogram(Histogram.linear(0, 10, 5)) + .serviceLevelObjectives(1, 10, 5) .register(registry); hist.record(3); @@ -267,17 +249,28 @@ public class MicrometerAtlasIntegrationTest { hist.record(13); hist.record(26); - Map histograms = extractTagValueMap(registry, Type.Counter, 1.0); + Map expectedMicrometer = new TreeMap<>(); + expectedMicrometer.put(1,0D); + expectedMicrometer.put(10,2D); + expectedMicrometer.put(5,1D); - assertThat(histograms, allOf(hasEntry("bucket=0.0", 0), hasEntry("bucket=10.0", 2), hasEntry("bucket=20.0", 2), hasEntry("bucket=30.0", 1), hasEntry("bucket=40.0", 1), hasEntry("bucket=Infinity", 0))); + Map actualMicrometer = new TreeMap<>(); + HistogramSnapshot snapshot = hist.takeSnapshot(); + Arrays.stream(snapshot.histogramCounts()).forEach(p -> { + actualMicrometer.put((Integer.valueOf((int) p.bucket())), p.count()); + }); + + assertEquals(expectedMicrometer, actualMicrometer); } @Test public void givenTimer_whenEnrichWithTimescaleHistogram_thenTimeScaleDataCollected() { SimpleMeterRegistry registry = new SimpleMeterRegistry(); + Duration[] durations = {Duration.ofMillis(25), Duration.ofMillis(300), Duration.ofMillis(600)}; Timer timer = Timer .builder("timer") - .histogram(Histogram.linearTime(TimeUnit.MILLISECONDS, 0, 200, 3)) + .sla(durations) + .publishPercentileHistogram() .register(registry); timer.record(1000, TimeUnit.MILLISECONDS); @@ -286,10 +279,18 @@ public class MicrometerAtlasIntegrationTest { timer.record(341, TimeUnit.MILLISECONDS); timer.record(500, TimeUnit.MILLISECONDS); - Map histograms = extractTagValueMap(registry, Type.Counter, 1.0); + Map expectedMicrometer = new TreeMap<>(); + expectedMicrometer.put(2.5E7,1D); + expectedMicrometer.put(3.0E8,1D); + expectedMicrometer.put(6.0E8,4D); - assertThat(histograms, allOf(hasEntry("bucket=0.0", 0), hasEntry("bucket=2.0E8", 1), hasEntry("bucket=4.0E8", 1), hasEntry("bucket=Infinity", 3))); + Map actualMicrometer = new TreeMap<>(); + HistogramSnapshot snapshot = timer.takeSnapshot(); + Arrays.stream(snapshot.histogramCounts()).forEach(p -> { + actualMicrometer.put((Double.valueOf((int) p.bucket())), p.count()); + }); + assertEquals(expectedMicrometer, actualMicrometer); } } From d77187d33d79250ea3e7984a43aaa1abc351918c Mon Sep 17 00:00:00 2001 From: Sarath Date: Thu, 8 Jul 2021 17:31:45 +0530 Subject: [PATCH 04/15] [BAEL-4990] Throwing exceptions in constructor --- .../constructors/exception/Animal.java | 32 +++++++++++++ .../baeldung/constructors/exception/Bird.java | 13 +++++ .../exception/AnimalUnitTest.java | 47 +++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 core-java-modules/core-java-lang-oop-constructors/src/main/java/com/baeldung/constructors/exception/Animal.java create mode 100644 core-java-modules/core-java-lang-oop-constructors/src/main/java/com/baeldung/constructors/exception/Bird.java create mode 100644 core-java-modules/core-java-lang-oop-constructors/src/test/java/com/baeldung/constructors/exception/AnimalUnitTest.java diff --git a/core-java-modules/core-java-lang-oop-constructors/src/main/java/com/baeldung/constructors/exception/Animal.java b/core-java-modules/core-java-lang-oop-constructors/src/main/java/com/baeldung/constructors/exception/Animal.java new file mode 100644 index 0000000000..1927bc4455 --- /dev/null +++ b/core-java-modules/core-java-lang-oop-constructors/src/main/java/com/baeldung/constructors/exception/Animal.java @@ -0,0 +1,32 @@ +package com.baeldung.constructors.exception; + +import java.io.File; +import java.io.IOException; + +public class Animal { + + public Animal() throws InstantiationException { + throw new InstantiationException("Cannot be instantiated"); + } + + public Animal(String id, int age) { + if (id == null) + throw new NullPointerException("Id cannot be null"); + if (age < 0) + throw new IllegalArgumentException("Age cannot be negative"); + } + + public Animal(File file) throws SecurityException, IOException { + // Avoiding Path traversal attacks + if (file.isAbsolute()) { + throw new SecurityException("Traversal attack - absolute path not allowed"); + } + // Avoiding directory traversal + // a/../..b/ + if (!file.getCanonicalPath() + .equals(file.getAbsolutePath())) { + throw new SecurityException("Directory traversal attempt?"); + } + } + +} diff --git a/core-java-modules/core-java-lang-oop-constructors/src/main/java/com/baeldung/constructors/exception/Bird.java b/core-java-modules/core-java-lang-oop-constructors/src/main/java/com/baeldung/constructors/exception/Bird.java new file mode 100644 index 0000000000..6e6e6558ba --- /dev/null +++ b/core-java-modules/core-java-lang-oop-constructors/src/main/java/com/baeldung/constructors/exception/Bird.java @@ -0,0 +1,13 @@ +package com.baeldung.constructors.exception; + +public class Bird extends Animal { + + // Note that we are throwing parent exception + public Bird() throws ReflectiveOperationException { + super(); + } + + public Bird(String id, int age) { + super(id, age); + } +} diff --git a/core-java-modules/core-java-lang-oop-constructors/src/test/java/com/baeldung/constructors/exception/AnimalUnitTest.java b/core-java-modules/core-java-lang-oop-constructors/src/test/java/com/baeldung/constructors/exception/AnimalUnitTest.java new file mode 100644 index 0000000000..8f7ed440f6 --- /dev/null +++ b/core-java-modules/core-java-lang-oop-constructors/src/test/java/com/baeldung/constructors/exception/AnimalUnitTest.java @@ -0,0 +1,47 @@ +package com.baeldung.constructors.exception; + +import java.io.File; + +import org.assertj.core.api.Assertions; +import org.junit.Test; + +public class AnimalUnitTest { + + @Test + public void givenNoArgument_thenFails() { + Assertions.assertThatThrownBy(() -> { + new Animal(); + }) + .isInstanceOf(Exception.class); + } + + @Test + public void givenInvalidArg_thenFails() { + Assertions.assertThatThrownBy(() -> { + new Animal(null, 30); + }) + .isInstanceOf(NullPointerException.class); + } + + @Test(expected = Test.None.class) + public void givenValidArg_thenSuccess() { + new Animal("1234", 30); + } + + @Test + public void givenAbsolutePath_thenFails() { + Assertions.assertThatThrownBy(() -> { + new Animal(new File("temp.txt").getAbsoluteFile()); + }) + .isInstanceOf(SecurityException.class); + } + + @Test + public void givenDirectoryTraversalPath_thenFails() { + Assertions.assertThatThrownBy(() -> { + new Animal(new File(File.separator + ".." + File.separator + "temp.txt")); + }) + .isInstanceOf(SecurityException.class); + } + +} From 2009f8a73ca9afe70c22b603f5601f963089787a Mon Sep 17 00:00:00 2001 From: sampadawagde Date: Sat, 7 Aug 2021 12:43:58 +0530 Subject: [PATCH 05/15] JAVA-5892: Upgrade spring-boot-keycloak to the latest Spring Boot version --- spring-boot-modules/spring-boot-keycloak/pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spring-boot-modules/spring-boot-keycloak/pom.xml b/spring-boot-modules/spring-boot-keycloak/pom.xml index 1d7dedf127..505d486509 100644 --- a/spring-boot-modules/spring-boot-keycloak/pom.xml +++ b/spring-boot-modules/spring-boot-keycloak/pom.xml @@ -77,7 +77,8 @@ 13.0.1 - 2.4.7 + + 2.5.3 \ No newline at end of file From d43ac8d1b5802e94c7312c1ca1c2002f48c28541 Mon Sep 17 00:00:00 2001 From: Gerald Boersma Date: Sat, 7 Aug 2021 17:55:35 -0700 Subject: [PATCH 06/15] BAEL-5049 spring-cloud-dapr (#11063) * BAEL-5049 Added tutorial code for article. * BAEL-5049 Added readme. * Initial code for 2-javasdk to properly include as part of Spring Boot project. * Added binding with rabbitmq. * Removed 1-intro module. * Updates from comments. * Added .gitignore back in. * Cleaned up POMs. * Get rid of .gitignore. * Change controller to use GetMapping. Co-authored-by: Gerald Boersma --- .../dapr-config/basic-config.yaml | 5 +++ .../dapr-config/consul-config.yaml | 9 ++++ .../dapr-config/consul-zipkin-config.yaml | 13 ++++++ .../spring-cloud-dapr/gateway/pom.xml | 44 +++++++++++++++++++ .../java/org/example/gateway/GatewayApp.java | 11 +++++ .../main/resources/application-no-dapr.yml | 14 ++++++ .../main/resources/application-with-dapr.yml | 13 ++++++ .../spring-cloud-dapr/greeting/pom.xml | 33 ++++++++++++++ .../java/org/example/hello/GreetingApp.java | 11 +++++ .../org/example/hello/GreetingController.java | 22 ++++++++++ .../src/main/resources/application.yml | 2 + spring-cloud/spring-cloud-dapr/pom.xml | 19 ++++++++ 12 files changed, 196 insertions(+) create mode 100644 spring-cloud/spring-cloud-dapr/dapr-config/basic-config.yaml create mode 100644 spring-cloud/spring-cloud-dapr/dapr-config/consul-config.yaml create mode 100644 spring-cloud/spring-cloud-dapr/dapr-config/consul-zipkin-config.yaml create mode 100644 spring-cloud/spring-cloud-dapr/gateway/pom.xml create mode 100644 spring-cloud/spring-cloud-dapr/gateway/src/main/java/org/example/gateway/GatewayApp.java create mode 100644 spring-cloud/spring-cloud-dapr/gateway/src/main/resources/application-no-dapr.yml create mode 100644 spring-cloud/spring-cloud-dapr/gateway/src/main/resources/application-with-dapr.yml create mode 100644 spring-cloud/spring-cloud-dapr/greeting/pom.xml create mode 100644 spring-cloud/spring-cloud-dapr/greeting/src/main/java/org/example/hello/GreetingApp.java create mode 100644 spring-cloud/spring-cloud-dapr/greeting/src/main/java/org/example/hello/GreetingController.java create mode 100644 spring-cloud/spring-cloud-dapr/greeting/src/main/resources/application.yml create mode 100644 spring-cloud/spring-cloud-dapr/pom.xml diff --git a/spring-cloud/spring-cloud-dapr/dapr-config/basic-config.yaml b/spring-cloud/spring-cloud-dapr/dapr-config/basic-config.yaml new file mode 100644 index 0000000000..d80655f0a2 --- /dev/null +++ b/spring-cloud/spring-cloud-dapr/dapr-config/basic-config.yaml @@ -0,0 +1,5 @@ +apiVersion: dapr.io/v1alpha1 +kind: Configuration +metadata: + name: daprConfig +spec: {} diff --git a/spring-cloud/spring-cloud-dapr/dapr-config/consul-config.yaml b/spring-cloud/spring-cloud-dapr/dapr-config/consul-config.yaml new file mode 100644 index 0000000000..638af7cf0d --- /dev/null +++ b/spring-cloud/spring-cloud-dapr/dapr-config/consul-config.yaml @@ -0,0 +1,9 @@ +apiVersion: dapr.io/v1alpha1 +kind: Configuration +metadata: + name: daprConfig +spec: + nameResolution: + component: "consul" + configuration: + selfRegister: true diff --git a/spring-cloud/spring-cloud-dapr/dapr-config/consul-zipkin-config.yaml b/spring-cloud/spring-cloud-dapr/dapr-config/consul-zipkin-config.yaml new file mode 100644 index 0000000000..8e50decf03 --- /dev/null +++ b/spring-cloud/spring-cloud-dapr/dapr-config/consul-zipkin-config.yaml @@ -0,0 +1,13 @@ +apiVersion: dapr.io/v1alpha1 +kind: Configuration +metadata: + name: daprConfig +spec: + nameResolution: + component: "consul" + configuration: + selfRegister: true + tracing: + samplingRate: "1" + zipkin: + endpointAddress: "http://localhost:9411/api/v2/spans" diff --git a/spring-cloud/spring-cloud-dapr/gateway/pom.xml b/spring-cloud/spring-cloud-dapr/gateway/pom.xml new file mode 100644 index 0000000000..13c1556cfe --- /dev/null +++ b/spring-cloud/spring-cloud-dapr/gateway/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + com.baeldung.spring.cloud.spring-cloud-dapr + gateway + 1.0-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 2.5.2 + + + + 11 + 11 + + + + + org.springframework.cloud + spring-cloud-dependencies + 2020.0.3 + pom + import + + + + + + org.springframework.cloud + spring-cloud-starter-gateway + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/spring-cloud/spring-cloud-dapr/gateway/src/main/java/org/example/gateway/GatewayApp.java b/spring-cloud/spring-cloud-dapr/gateway/src/main/java/org/example/gateway/GatewayApp.java new file mode 100644 index 0000000000..7166b5b31f --- /dev/null +++ b/spring-cloud/spring-cloud-dapr/gateway/src/main/java/org/example/gateway/GatewayApp.java @@ -0,0 +1,11 @@ +package org.example.gateway; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class GatewayApp { + public static void main(String[] args) { + SpringApplication.run(GatewayApp.class, args); + } +} diff --git a/spring-cloud/spring-cloud-dapr/gateway/src/main/resources/application-no-dapr.yml b/spring-cloud/spring-cloud-dapr/gateway/src/main/resources/application-no-dapr.yml new file mode 100644 index 0000000000..f25d7f0128 --- /dev/null +++ b/spring-cloud/spring-cloud-dapr/gateway/src/main/resources/application-no-dapr.yml @@ -0,0 +1,14 @@ +server: + port: 3000 + +spring: + cloud: + gateway: + routes: + - id: greeting-service + uri: http://localhost:3001/ + predicates: + - Path=/** + filters: + - RewritePath=/?(?.*), /$\{segment} + diff --git a/spring-cloud/spring-cloud-dapr/gateway/src/main/resources/application-with-dapr.yml b/spring-cloud/spring-cloud-dapr/gateway/src/main/resources/application-with-dapr.yml new file mode 100644 index 0000000000..ee67c76339 --- /dev/null +++ b/spring-cloud/spring-cloud-dapr/gateway/src/main/resources/application-with-dapr.yml @@ -0,0 +1,13 @@ +server: + port: 3000 + +spring: + cloud: + gateway: + routes: + - id: greeting-service + uri: http://localhost:4000/ + predicates: + - Path=/** + filters: + - RewritePath=//?(?.*), /v1.0/invoke/greeting/method/$\{segment} diff --git a/spring-cloud/spring-cloud-dapr/greeting/pom.xml b/spring-cloud/spring-cloud-dapr/greeting/pom.xml new file mode 100644 index 0000000000..58de7240a2 --- /dev/null +++ b/spring-cloud/spring-cloud-dapr/greeting/pom.xml @@ -0,0 +1,33 @@ + + + 4.0.0 + com.baeldung.spring.cloud.spring-cloud-dapr + greeting + 1.0-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 2.5.2 + + + + 11 + 11 + + + + org.springframework.boot + spring-boot-starter-web + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/spring-cloud/spring-cloud-dapr/greeting/src/main/java/org/example/hello/GreetingApp.java b/spring-cloud/spring-cloud-dapr/greeting/src/main/java/org/example/hello/GreetingApp.java new file mode 100644 index 0000000000..2feca49daa --- /dev/null +++ b/spring-cloud/spring-cloud-dapr/greeting/src/main/java/org/example/hello/GreetingApp.java @@ -0,0 +1,11 @@ +package org.example.hello; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class GreetingApp { + public static void main(String[] args) { + SpringApplication.run(GreetingApp.class, args); + } +} diff --git a/spring-cloud/spring-cloud-dapr/greeting/src/main/java/org/example/hello/GreetingController.java b/spring-cloud/spring-cloud-dapr/greeting/src/main/java/org/example/hello/GreetingController.java new file mode 100644 index 0000000000..5210add4e0 --- /dev/null +++ b/spring-cloud/spring-cloud-dapr/greeting/src/main/java/org/example/hello/GreetingController.java @@ -0,0 +1,22 @@ +package org.example.hello; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class GreetingController { + @GetMapping(value = "/") + public String getGreeting() { + return "Welcome to the greeting service."; + } + + @GetMapping(value = "/hello") + public String getHello() { + return "Hello world!"; + } + + @GetMapping(value = "/goodbye") + public String getGoodbye() { + return "Goodbye, cruel world!"; + } +} diff --git a/spring-cloud/spring-cloud-dapr/greeting/src/main/resources/application.yml b/spring-cloud/spring-cloud-dapr/greeting/src/main/resources/application.yml new file mode 100644 index 0000000000..7147f91d26 --- /dev/null +++ b/spring-cloud/spring-cloud-dapr/greeting/src/main/resources/application.yml @@ -0,0 +1,2 @@ +server: + port: 3001 diff --git a/spring-cloud/spring-cloud-dapr/pom.xml b/spring-cloud/spring-cloud-dapr/pom.xml new file mode 100644 index 0000000000..76d3cbdaf8 --- /dev/null +++ b/spring-cloud/spring-cloud-dapr/pom.xml @@ -0,0 +1,19 @@ + + + 4.0.0 + spring-cloud-dapr + pom + + + com.baeldung.spring.cloud + spring-cloud + 1.0.0-SNAPSHOT + + + + gateway + greeting + + From 58723823d31458aa50ff6e1c0473ca47004da0e7 Mon Sep 17 00:00:00 2001 From: johnA1331 <53036378+johnA1331@users.noreply.github.com> Date: Wed, 11 Aug 2021 11:33:49 +0800 Subject: [PATCH 07/15] Update README.md --- core-java-modules/core-java-lang-operators-2/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/core-java-modules/core-java-lang-operators-2/README.md b/core-java-modules/core-java-lang-operators-2/README.md index 1a4c01c6e5..bc1809a4a7 100644 --- a/core-java-modules/core-java-lang-operators-2/README.md +++ b/core-java-modules/core-java-lang-operators-2/README.md @@ -5,3 +5,4 @@ This module contains articles about Java operators ## Relevant Articles: - [Logical vs Bitwise OR Operator](https://www.baeldung.com/java-logical-vs-bitwise-or-operator) +- [Bitmasking in Java with Bitwise Operators](https://www.baeldung.com/java-bitmasking) From a00d8462e1bbd0a7bd851ab833cab9a31298c907 Mon Sep 17 00:00:00 2001 From: johnA1331 <53036378+johnA1331@users.noreply.github.com> Date: Wed, 11 Aug 2021 11:38:19 +0800 Subject: [PATCH 08/15] Create README.md --- spring-cloud/spring-cloud-dapr/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 spring-cloud/spring-cloud-dapr/README.md diff --git a/spring-cloud/spring-cloud-dapr/README.md b/spring-cloud/spring-cloud-dapr/README.md new file mode 100644 index 0000000000..8f6ccfd9b6 --- /dev/null +++ b/spring-cloud/spring-cloud-dapr/README.md @@ -0,0 +1,3 @@ +### Relevant Articles: + +- [An Intro to Dapr with Spring Cloud Gateway](https://www.baeldung.com/dapr-spring-cloud-gateway) From 1ddde25b1c030f2e0463df894113052bced3b1b5 Mon Sep 17 00:00:00 2001 From: johnA1331 <53036378+johnA1331@users.noreply.github.com> Date: Wed, 11 Aug 2021 11:41:11 +0800 Subject: [PATCH 09/15] Create README.md --- maven-modules/host-maven-repo-example/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 maven-modules/host-maven-repo-example/README.md diff --git a/maven-modules/host-maven-repo-example/README.md b/maven-modules/host-maven-repo-example/README.md new file mode 100644 index 0000000000..032be2416c --- /dev/null +++ b/maven-modules/host-maven-repo-example/README.md @@ -0,0 +1,3 @@ +### Relevant Articles: + +- [Hosting a Maven Repository on GitHub](https://www.baeldung.com/maven-repo-github) From b30c3976be7acf11731d597b52601aa604f5bd6a Mon Sep 17 00:00:00 2001 From: sharifi Date: Thu, 12 Aug 2021 11:17:50 +0430 Subject: [PATCH 10/15] BAEL-2670: change assertTrue(a==b) to assertEquals(a,b,delta) --- .../MicrometerAtlasIntegrationTest.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/metrics/src/test/java/com/baeldung/metrics/micrometer/MicrometerAtlasIntegrationTest.java b/metrics/src/test/java/com/baeldung/metrics/micrometer/MicrometerAtlasIntegrationTest.java index a5a8136ecb..02ef926794 100644 --- a/metrics/src/test/java/com/baeldung/metrics/micrometer/MicrometerAtlasIntegrationTest.java +++ b/metrics/src/test/java/com/baeldung/metrics/micrometer/MicrometerAtlasIntegrationTest.java @@ -119,9 +119,9 @@ public class MicrometerAtlasIntegrationTest { .counter()); assertTrue(counterOptional.isPresent()); - assertTrue(counterOptional + assertEquals(counterOptional .get() - .count() == 2.0); + .count() , 2.0, 0.0); } @Test @@ -134,10 +134,10 @@ public class MicrometerAtlasIntegrationTest { .register(registry); counter.increment(2.0); - assertTrue(counter.count() == 2); + assertEquals(counter.count(), 2, 0); counter.increment(-1); - assertTrue(counter.count() == 1); + assertEquals(counter.count(), 1, 0); } @Test @@ -153,7 +153,7 @@ public class MicrometerAtlasIntegrationTest { timer.record(30, TimeUnit.MILLISECONDS); - assertTrue(2 == timer.count()); + assertEquals(2, timer.count(), 0); assertThat(timer.totalTime(TimeUnit.MILLISECONDS)).isBetween(40.0, 55.0); } @@ -183,10 +183,10 @@ public class MicrometerAtlasIntegrationTest { .builder("cache.size", list, List::size) .register(registry); - assertTrue(gauge.value() == 0.0); + assertEquals(gauge.value(), 0.0, 0.0); list.add("1"); - assertTrue(gauge.value() == 1.0); + assertEquals(gauge.value(), 1.0, 0.0); } @Test @@ -200,8 +200,8 @@ public class MicrometerAtlasIntegrationTest { distributionSummary.record(4); distributionSummary.record(5); - assertTrue(3 == distributionSummary.count()); - assertTrue(12 == distributionSummary.totalAmount()); + assertEquals(3, distributionSummary.count(), 0); + assertEquals(12, distributionSummary.totalAmount(), 0); } @Test From c99989e5992223748f0a93e7df5086375b07e9a3 Mon Sep 17 00:00:00 2001 From: Mateusz Szablak Date: Thu, 5 Aug 2021 16:14:46 +0200 Subject: [PATCH 11/15] [BAEL-5041] Validate String as file name in Java --- .../StringFilenameValidationUtils.java | 61 ++++++++ .../StringFilenameValidationUnitTest.java | 130 ++++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 core-java-modules/core-java-string-operations-3/src/main/java/com/baeldung/stringfilenamevalidaiton/StringFilenameValidationUtils.java create mode 100644 core-java-modules/core-java-string-operations-3/src/test/java/com/baeldung/stringfilenamevalidaiton/StringFilenameValidationUnitTest.java diff --git a/core-java-modules/core-java-string-operations-3/src/main/java/com/baeldung/stringfilenamevalidaiton/StringFilenameValidationUtils.java b/core-java-modules/core-java-string-operations-3/src/main/java/com/baeldung/stringfilenamevalidaiton/StringFilenameValidationUtils.java new file mode 100644 index 0000000000..1a86edd45a --- /dev/null +++ b/core-java-modules/core-java-string-operations-3/src/main/java/com/baeldung/stringfilenamevalidaiton/StringFilenameValidationUtils.java @@ -0,0 +1,61 @@ +package com.baeldung.stringfilenamevalidaiton; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.Arrays; + +public class StringFilenameValidationUtils { + + public static final Character[] INVALID_WINDOWS_SPECIFIC_CHARS = {'"', '*', ':', '<', '>', '?', '\\', '|', 0x7F}; + public static final Character[] INVALID_UNIX_SPECIFIC_CHARS = {'\000'}; + + public static final String REGEX_PATTERN = "^[A-za-z0-9.]{1,255}$"; + + private StringFilenameValidationUtils() { + } + + public static boolean validateStringFilenameUsingIO(String filename) throws IOException { + File file = new File(filename); + boolean created = false; + try { + created = file.createNewFile(); + return created; + } finally { + if (created) { + file.delete(); + } + } + } + + public static boolean validateStringFilenameUsingNIO2(String filename) { + Paths.get(filename); + return true; + } + + public static boolean validateStringFilenameUsingContains(String filename) { + if (filename == null || filename.isEmpty() || filename.length() > 255) { + return false; + } + return Arrays.stream(getInvalidCharsByOS()) + .noneMatch(ch -> filename.contains(ch.toString())); + } + + public static boolean validateStringFilenameUsingRegex(String filename) { + if (filename == null) { + return false; + } + return filename.matches(REGEX_PATTERN); + } + + private static Character[] getInvalidCharsByOS() { + String os = System.getProperty("os.name").toLowerCase(); + if (os.contains("win")) { + return INVALID_WINDOWS_SPECIFIC_CHARS; + } else if (os.contains("nix") || os.contains("nux") || os.contains("mac")) { + return INVALID_UNIX_SPECIFIC_CHARS; + } else { + return new Character[]{}; + } + } +} diff --git a/core-java-modules/core-java-string-operations-3/src/test/java/com/baeldung/stringfilenamevalidaiton/StringFilenameValidationUnitTest.java b/core-java-modules/core-java-string-operations-3/src/test/java/com/baeldung/stringfilenamevalidaiton/StringFilenameValidationUnitTest.java new file mode 100644 index 0000000000..3e787f08be --- /dev/null +++ b/core-java-modules/core-java-string-operations-3/src/test/java/com/baeldung/stringfilenamevalidaiton/StringFilenameValidationUnitTest.java @@ -0,0 +1,130 @@ +package com.baeldung.stringfilenamevalidaiton; + +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.RandomUtils; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EmptySource; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.NullSource; + +import java.io.IOException; +import java.nio.file.InvalidPathException; +import java.util.Arrays; +import java.util.stream.Stream; + +import static com.baeldung.stringfilenamevalidaiton.StringFilenameValidationUtils.validateStringFilenameUsingContains; +import static com.baeldung.stringfilenamevalidaiton.StringFilenameValidationUtils.validateStringFilenameUsingIO; +import static com.baeldung.stringfilenamevalidaiton.StringFilenameValidationUtils.validateStringFilenameUsingNIO2; +import static com.baeldung.stringfilenamevalidaiton.StringFilenameValidationUtils.validateStringFilenameUsingRegex; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class StringFilenameValidationUnitTest { + + private static final String CORRECT_FILENAME_PATTERN = "baeldung.txt"; + + @ParameterizedTest + @MethodSource("correctAlphanumericFilenamesProvider") + public void givenCorrectAlphanumericRandomFilenameString_whenValidateUsingIO_thenReturnTrue(String filename) throws IOException { + assertThat(validateStringFilenameUsingIO(filename)).isTrue(); + assertThat(validateStringFilenameUsingNIO2(filename)).isTrue(); + assertThat(validateStringFilenameUsingContains(filename)).isTrue(); + assertThat(validateStringFilenameUsingRegex(filename)).isTrue(); + } + + @Test + public void givenTooLongFileNameString_whenValidate_thenIOAndCustomFailsNIO2Succeed() { + String filename = RandomStringUtils.randomAlphabetic(500); + assertThatThrownBy(() -> validateStringFilenameUsingIO(filename)) + .isInstanceOf(IOException.class) + .hasMessageContaining("File name too long"); + assertThat(validateStringFilenameUsingNIO2(filename)).isTrue(); + assertThat(validateStringFilenameUsingContains(filename)).isFalse(); + assertThat(validateStringFilenameUsingRegex(filename)).isFalse(); + } + + @ParameterizedTest + @NullSource + public void givenNullString_whenValidate_thenFails(String filename) { + assertThatThrownBy(() -> validateStringFilenameUsingIO(filename)) + .isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> validateStringFilenameUsingNIO2(filename)) + .isInstanceOf(NullPointerException.class); + assertThat(validateStringFilenameUsingContains(filename)).isFalse(); + assertThat(validateStringFilenameUsingRegex(filename)).isFalse(); + } + + @ParameterizedTest + @EmptySource + public void givenEmptyString_whenValidate_thenIOAndCustomFailsNIO2Succeed(String filename) { + assertThatThrownBy(() -> validateStringFilenameUsingIO(filename)) + .isInstanceOf(IOException.class); + assertThat(validateStringFilenameUsingNIO2(filename)).isTrue(); + assertThat(validateStringFilenameUsingContains(filename)).isFalse(); + assertThat(validateStringFilenameUsingRegex(filename)).isFalse(); + } + + @ParameterizedTest + @EnabledOnOs({OS.LINUX, OS.MAC}) + @MethodSource("filenamesWithInvalidWindowsChars") + public void givenFilenameStringWithInvalidWindowsCharAndIsUnix_whenValidateUsingIO_thenReturnTrue(String filename) throws IOException { + assertThat(validateStringFilenameUsingIO(filename)).isTrue(); + assertThat(validateStringFilenameUsingNIO2(filename)).isTrue(); + assertThat(validateStringFilenameUsingContains(filename)).isTrue(); + } + + @ParameterizedTest + @EnabledOnOs(OS.WINDOWS) + @MethodSource("filenamesWithInvalidWindowsChars") + public void givenFilenameStringWithInvalidWindowsCharAndIsWindows_whenValidateUsingIO_thenRaiseException(String filename) { + assertThatThrownBy(() -> validateStringFilenameUsingIO(filename)) + .isInstanceOf(IOException.class) + .hasMessageContaining("Invalid file path"); + + assertThatThrownBy(() -> validateStringFilenameUsingNIO2(filename)) + .isInstanceOf(InvalidPathException.class) + .hasMessage("character not allowed"); + + assertThat(validateStringFilenameUsingContains(filename)).isFalse(); + } + + @ParameterizedTest + @EnabledOnOs({OS.LINUX, OS.MAC}) + @MethodSource("filenamesWithInvalidUnixChars") + public void givenFilenameStringWithInvalidUnixCharAndIsUnix_whenValidate_thenRaiseException(String filename) { + assertThatThrownBy(() -> validateStringFilenameUsingIO(filename)) + .isInstanceOf(IOException.class) + .hasMessageContaining("Invalid file path"); + + assertThatThrownBy(() -> validateStringFilenameUsingNIO2(filename)) + .isInstanceOf(InvalidPathException.class) + .hasMessageContaining("character not allowed"); + + assertThat(validateStringFilenameUsingContains(filename)).isFalse(); + } + + + private static Stream correctAlphanumericFilenamesProvider() { + return Stream.generate(() -> RandomStringUtils.randomAlphanumeric(1, 10) + "." + RandomStringUtils.randomAlphabetic(3, 5)).limit(10); + } + + private static Stream filenamesWithInvalidWindowsChars() { + return Arrays.stream(StringFilenameValidationUtils.INVALID_WINDOWS_SPECIFIC_CHARS) + .map(character -> { + int idx = RandomUtils.nextInt(0, CORRECT_FILENAME_PATTERN.length()); + return CORRECT_FILENAME_PATTERN.substring(0, idx) + character + CORRECT_FILENAME_PATTERN.substring(idx); + }); + } + + private static Stream filenamesWithInvalidUnixChars() { + return Arrays.stream(StringFilenameValidationUtils.INVALID_UNIX_SPECIFIC_CHARS) + .map(character -> { + int idx = RandomUtils.nextInt(0, CORRECT_FILENAME_PATTERN.length()); + return CORRECT_FILENAME_PATTERN.substring(0, idx) + character + CORRECT_FILENAME_PATTERN.substring(idx); + }); + } + +} From 439b5a4fdef56d4a348acfaa2ded229316415fe3 Mon Sep 17 00:00:00 2001 From: Krzysztof Majewski Date: Fri, 13 Aug 2021 17:13:26 +0200 Subject: [PATCH 12/15] BAEL-5009 Spring Data with ArangoDB (#11115) * BAEL-5009 Spring Data with ArangoDB * BAEL-5009 Spring Data with ArangoDB * BAEL-5009 Spring Data with ArangoDB * BAEL-5009 Spring Data with ArangoDB * BAEL-5009 Spring Data with ArangoDB * BAEL-5009 Spring Data with ArangoDB Co-authored-by: majewsk6 --- persistence-modules/pom.xml | 1 + .../spring-data-arangodb/README.md | 6 + .../spring-data-arangodb/pom.xml | 29 +++++ .../src/live-test/resources/Dockerfile | 7 ++ .../src/live-test/resources/init-session.js | 1 + .../live-test/resources/live-test-setup.sh | 5 + .../live-test/resources/live-test-teardown.sh | 4 + .../src/live-test/resources/live-test.sh | 3 + .../ArangoDbSpringDataApplication.java | 13 ++ .../configuration/ArangoDbConfiguration.java | 24 ++++ .../com/baeldung/arangodb/model/Article.java | 86 +++++++++++++ .../baeldung/arangodb/model/ArticleLink.java | 39 ++++++ .../com/baeldung/arangodb/model/Author.java | 49 ++++++++ .../repository/ArticleRepository.java | 17 +++ .../src/main/resources/arangodb.properties | 3 + .../ArticleRepositoryIntegrationTest.java | 113 ++++++++++++++++++ 16 files changed, 400 insertions(+) create mode 100644 persistence-modules/spring-data-arangodb/README.md create mode 100644 persistence-modules/spring-data-arangodb/pom.xml create mode 100644 persistence-modules/spring-data-arangodb/src/live-test/resources/Dockerfile create mode 100644 persistence-modules/spring-data-arangodb/src/live-test/resources/init-session.js create mode 100644 persistence-modules/spring-data-arangodb/src/live-test/resources/live-test-setup.sh create mode 100644 persistence-modules/spring-data-arangodb/src/live-test/resources/live-test-teardown.sh create mode 100644 persistence-modules/spring-data-arangodb/src/live-test/resources/live-test.sh create mode 100644 persistence-modules/spring-data-arangodb/src/main/java/com/baeldung/arangodb/ArangoDbSpringDataApplication.java create mode 100644 persistence-modules/spring-data-arangodb/src/main/java/com/baeldung/arangodb/configuration/ArangoDbConfiguration.java create mode 100644 persistence-modules/spring-data-arangodb/src/main/java/com/baeldung/arangodb/model/Article.java create mode 100644 persistence-modules/spring-data-arangodb/src/main/java/com/baeldung/arangodb/model/ArticleLink.java create mode 100644 persistence-modules/spring-data-arangodb/src/main/java/com/baeldung/arangodb/model/Author.java create mode 100644 persistence-modules/spring-data-arangodb/src/main/java/com/baeldung/arangodb/repository/ArticleRepository.java create mode 100644 persistence-modules/spring-data-arangodb/src/main/resources/arangodb.properties create mode 100644 persistence-modules/spring-data-arangodb/src/test/java/com/baeldung/arangodb/ArticleRepositoryIntegrationTest.java diff --git a/persistence-modules/pom.xml b/persistence-modules/pom.xml index 86e496e3f8..a8f1d5c103 100644 --- a/persistence-modules/pom.xml +++ b/persistence-modules/pom.xml @@ -57,6 +57,7 @@ spring-boot-persistence spring-boot-persistence-h2 spring-boot-persistence-mongodb + spring-data-arangodb spring-data-cassandra spring-data-cassandra-test spring-data-cassandra-reactive diff --git a/persistence-modules/spring-data-arangodb/README.md b/persistence-modules/spring-data-arangodb/README.md new file mode 100644 index 0000000000..632d9a256e --- /dev/null +++ b/persistence-modules/spring-data-arangodb/README.md @@ -0,0 +1,6 @@ +========= + +## Spring Data ArangoDB + + +### Relevant Articles: diff --git a/persistence-modules/spring-data-arangodb/pom.xml b/persistence-modules/spring-data-arangodb/pom.xml new file mode 100644 index 0000000000..562f06ae40 --- /dev/null +++ b/persistence-modules/spring-data-arangodb/pom.xml @@ -0,0 +1,29 @@ + + + 4.0.0 + spring-data-arangodb + spring-data-arangodb + + + com.baeldung + parent-boot-2 + 0.0.1-SNAPSHOT + ../../parent-boot-2 + + + + + org.springframework.boot + spring-boot-starter + + + + com.arangodb + arangodb-spring-data + 3.5.0 + + + + \ No newline at end of file diff --git a/persistence-modules/spring-data-arangodb/src/live-test/resources/Dockerfile b/persistence-modules/spring-data-arangodb/src/live-test/resources/Dockerfile new file mode 100644 index 0000000000..8edb2bbbf6 --- /dev/null +++ b/persistence-modules/spring-data-arangodb/src/live-test/resources/Dockerfile @@ -0,0 +1,7 @@ +FROM arangodb:3.8.0 + +COPY init-session.js /docker-entrypoint-initdb.d/ + +EXPOSE 8529 + +ENV ARANGO_ROOT_PASSWORD=password diff --git a/persistence-modules/spring-data-arangodb/src/live-test/resources/init-session.js b/persistence-modules/spring-data-arangodb/src/live-test/resources/init-session.js new file mode 100644 index 0000000000..2e968884cc --- /dev/null +++ b/persistence-modules/spring-data-arangodb/src/live-test/resources/init-session.js @@ -0,0 +1 @@ +rs.initiate(); diff --git a/persistence-modules/spring-data-arangodb/src/live-test/resources/live-test-setup.sh b/persistence-modules/spring-data-arangodb/src/live-test/resources/live-test-setup.sh new file mode 100644 index 0000000000..b1a4cfb9d0 --- /dev/null +++ b/persistence-modules/spring-data-arangodb/src/live-test/resources/live-test-setup.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +docker image build -t spring-data-arangodb:live-test . + +docker run -p 8529:8529 -e ARANGO_ROOT_PASSWORD=password --name spring-data-arangodb-live-test spring-data-arangodb:live-test diff --git a/persistence-modules/spring-data-arangodb/src/live-test/resources/live-test-teardown.sh b/persistence-modules/spring-data-arangodb/src/live-test/resources/live-test-teardown.sh new file mode 100644 index 0000000000..2199cf7403 --- /dev/null +++ b/persistence-modules/spring-data-arangodb/src/live-test/resources/live-test-teardown.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +docker stop spring-data-arangodb-live-test +docker rm spring-data-arangodb-live-test diff --git a/persistence-modules/spring-data-arangodb/src/live-test/resources/live-test.sh b/persistence-modules/spring-data-arangodb/src/live-test/resources/live-test.sh new file mode 100644 index 0000000000..307a68a3bd --- /dev/null +++ b/persistence-modules/spring-data-arangodb/src/live-test/resources/live-test.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +mvn clean compile test -P live-all -f ../../../pom.xml diff --git a/persistence-modules/spring-data-arangodb/src/main/java/com/baeldung/arangodb/ArangoDbSpringDataApplication.java b/persistence-modules/spring-data-arangodb/src/main/java/com/baeldung/arangodb/ArangoDbSpringDataApplication.java new file mode 100644 index 0000000000..355001df06 --- /dev/null +++ b/persistence-modules/spring-data-arangodb/src/main/java/com/baeldung/arangodb/ArangoDbSpringDataApplication.java @@ -0,0 +1,13 @@ +package com.baeldung.arangodb; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ArangoDbSpringDataApplication { + + public static void main(String[] args) { + SpringApplication.run(ArangoDbSpringDataApplication.class, args); + } + +} \ No newline at end of file diff --git a/persistence-modules/spring-data-arangodb/src/main/java/com/baeldung/arangodb/configuration/ArangoDbConfiguration.java b/persistence-modules/spring-data-arangodb/src/main/java/com/baeldung/arangodb/configuration/ArangoDbConfiguration.java new file mode 100644 index 0000000000..3aae10ce60 --- /dev/null +++ b/persistence-modules/spring-data-arangodb/src/main/java/com/baeldung/arangodb/configuration/ArangoDbConfiguration.java @@ -0,0 +1,24 @@ +package com.baeldung.arangodb.configuration; + +import com.arangodb.ArangoDB; +import com.arangodb.springframework.annotation.EnableArangoRepositories; +import com.arangodb.springframework.config.ArangoConfiguration; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableArangoRepositories(basePackages = {"com.baeldung"}) +public class ArangoDbConfiguration implements ArangoConfiguration { + + @Override + public ArangoDB.Builder arango() { + return new ArangoDB.Builder() + .host("127.0.0.1", 8529) + .user("root") + .password("password"); + } + + @Override + public String database() { + return "baeldung-database"; + } +} diff --git a/persistence-modules/spring-data-arangodb/src/main/java/com/baeldung/arangodb/model/Article.java b/persistence-modules/spring-data-arangodb/src/main/java/com/baeldung/arangodb/model/Article.java new file mode 100644 index 0000000000..ea7ee1d2a6 --- /dev/null +++ b/persistence-modules/spring-data-arangodb/src/main/java/com/baeldung/arangodb/model/Article.java @@ -0,0 +1,86 @@ +package com.baeldung.arangodb.model; + +import com.arangodb.springframework.annotation.ArangoId; +import com.arangodb.springframework.annotation.Document; +import com.arangodb.springframework.annotation.Relations; +import org.springframework.data.annotation.Id; + +import java.time.ZonedDateTime; +import java.util.Collection; + +@Document("articles") +public class Article { + + @Id + private String id; + + @ArangoId + private String arangoId; + + private String name; + private String author; + private ZonedDateTime publishDate; + private String htmlContent; + + @Relations(edges = ArticleLink.class, lazy = true) + private Collection authors; + + public Article() { + super(); + } + + public Article(String name, String author, ZonedDateTime publishDate, String htmlContent) { + this.name = name; + this.author = author; + this.publishDate = publishDate; + this.htmlContent = htmlContent; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getArangoId() { + return arangoId; + } + + public void setArangoId(String arangoId) { + this.arangoId = arangoId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public ZonedDateTime getPublishDate() { + return publishDate; + } + + public void setPublishDate(ZonedDateTime publishDate) { + this.publishDate = publishDate; + } + + public String getHtmlContent() { + return htmlContent; + } + + public void setHtmlContent(String htmlContent) { + this.htmlContent = htmlContent; + } +} diff --git a/persistence-modules/spring-data-arangodb/src/main/java/com/baeldung/arangodb/model/ArticleLink.java b/persistence-modules/spring-data-arangodb/src/main/java/com/baeldung/arangodb/model/ArticleLink.java new file mode 100644 index 0000000000..18a35815e1 --- /dev/null +++ b/persistence-modules/spring-data-arangodb/src/main/java/com/baeldung/arangodb/model/ArticleLink.java @@ -0,0 +1,39 @@ +package com.baeldung.arangodb.model; + +import com.arangodb.springframework.annotation.Edge; +import com.arangodb.springframework.annotation.From; +import com.arangodb.springframework.annotation.To; + +@Edge +public class ArticleLink { + + @From + private Article article; + + @To + private Author author; + + public ArticleLink() { + } + + public ArticleLink(Article article, Author author) { + this.article = article; + this.author = author; + } + + public Article getArticle() { + return article; + } + + public void setArticle(Article article) { + this.article = article; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } +} diff --git a/persistence-modules/spring-data-arangodb/src/main/java/com/baeldung/arangodb/model/Author.java b/persistence-modules/spring-data-arangodb/src/main/java/com/baeldung/arangodb/model/Author.java new file mode 100644 index 0000000000..6e9fed3312 --- /dev/null +++ b/persistence-modules/spring-data-arangodb/src/main/java/com/baeldung/arangodb/model/Author.java @@ -0,0 +1,49 @@ +package com.baeldung.arangodb.model; + +import com.arangodb.springframework.annotation.ArangoId; +import com.arangodb.springframework.annotation.Document; +import org.springframework.data.annotation.Id; + +@Document("articles") +public class Author { + + @Id + private String id; + + @ArangoId + private String arangoId; + + private String name; + + public Author() { + super(); + } + + public Author(String name) { + this.name = name; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getArangoId() { + return arangoId; + } + + public void setArangoId(String arangoId) { + this.arangoId = arangoId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/persistence-modules/spring-data-arangodb/src/main/java/com/baeldung/arangodb/repository/ArticleRepository.java b/persistence-modules/spring-data-arangodb/src/main/java/com/baeldung/arangodb/repository/ArticleRepository.java new file mode 100644 index 0000000000..fdc434cae4 --- /dev/null +++ b/persistence-modules/spring-data-arangodb/src/main/java/com/baeldung/arangodb/repository/ArticleRepository.java @@ -0,0 +1,17 @@ +package com.baeldung.arangodb.repository; + +import com.arangodb.springframework.annotation.Query; +import com.arangodb.springframework.repository.ArangoRepository; +import com.baeldung.arangodb.model.Article; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +@Repository +public interface ArticleRepository extends ArangoRepository { + + Iterable
findByAuthor(String author); + + @Query("FOR a IN articles FILTER a.author == @author SORT a.publishDate ASC RETURN a") + Iterable
getByAuthor(@Param("author") String author); + +} diff --git a/persistence-modules/spring-data-arangodb/src/main/resources/arangodb.properties b/persistence-modules/spring-data-arangodb/src/main/resources/arangodb.properties new file mode 100644 index 0000000000..7f8ded478c --- /dev/null +++ b/persistence-modules/spring-data-arangodb/src/main/resources/arangodb.properties @@ -0,0 +1,3 @@ +arangodb.hosts=127.0.0.1:8529 +arangodb.user=root +arangodb.password=password \ No newline at end of file diff --git a/persistence-modules/spring-data-arangodb/src/test/java/com/baeldung/arangodb/ArticleRepositoryIntegrationTest.java b/persistence-modules/spring-data-arangodb/src/test/java/com/baeldung/arangodb/ArticleRepositoryIntegrationTest.java new file mode 100644 index 0000000000..1d4258688a --- /dev/null +++ b/persistence-modules/spring-data-arangodb/src/test/java/com/baeldung/arangodb/ArticleRepositoryIntegrationTest.java @@ -0,0 +1,113 @@ +package com.baeldung.arangodb; + +import com.baeldung.arangodb.model.Article; +import com.baeldung.arangodb.repository.ArticleRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest +public class ArticleRepositoryIntegrationTest { + + @Autowired + ArticleRepository articleRepository; + + @Test + public void givenNewArticle_whenSaveInArangoDb_thenDataIsCorrect() { + Article newArticle = new Article( + "ArangoDb with Spring Data", + "Baeldung Writer", + ZonedDateTime.now(), + "Some HTML content" + ); + + Article savedArticle = articleRepository.save(newArticle); + + assertNotNull(savedArticle.getId()); + assertNotNull(savedArticle.getArangoId()); + + assertEquals(savedArticle.getName(), newArticle.getName()); + assertEquals(savedArticle.getAuthor(), newArticle.getAuthor()); + assertEquals(savedArticle.getPublishDate(), newArticle.getPublishDate()); + assertEquals(savedArticle.getHtmlContent(), newArticle.getHtmlContent()); + } + + @Test + public void givenArticleId_whenReadFromArangoDb_thenDataIsCorrect() { + Article newArticle = new Article( + "ArangoDb with Spring Data", + "Baeldung Writer", + ZonedDateTime.now(), + "Some HTML content" + ); + + Article savedArticle = articleRepository.save(newArticle); + + String articleId = savedArticle.getId(); + + Optional
article = articleRepository.findById(articleId); + assertTrue(article.isPresent()); + + Article foundArticle = article.get(); + + assertEquals(foundArticle.getId(), articleId); + assertEquals(foundArticle.getArangoId(), savedArticle.getArangoId()); + assertEquals(foundArticle.getName(), savedArticle.getName()); + assertEquals(foundArticle.getAuthor(), savedArticle.getAuthor()); + assertEquals(foundArticle.getPublishDate(), savedArticle.getPublishDate()); + assertEquals(foundArticle.getHtmlContent(), savedArticle.getHtmlContent()); + } + + @Test + public void givenArticleId_whenDeleteFromArangoDb_thenDataIsGone() { + Article newArticle = new Article( + "ArangoDb with Spring Data", + "Baeldung Writer", + ZonedDateTime.now(), + "Some HTML content" + ); + + Article savedArticle = articleRepository.save(newArticle); + + String articleId = savedArticle.getId(); + + articleRepository.deleteById(articleId); + + Optional
article = articleRepository.findById(articleId); + assertFalse(article.isPresent()); + } + + @Test + public void givenAuthorName_whenGetByAuthor_thenListOfArticles() { + Article newArticle = new Article( + "ArangoDb with Spring Data", + "Baeldung Writer", + ZonedDateTime.now(), + "Some HTML content" + ); + articleRepository.save(newArticle); + + Iterable
articlesByAuthor = articleRepository.findByAuthor(newArticle.getAuthor()); + List
articlesByAuthorList = new ArrayList<>(); + articlesByAuthor.forEach(articlesByAuthorList::add); + + assertEquals(1, articlesByAuthorList.size()); + + Article foundArticle = articlesByAuthorList.get(0); + assertEquals(foundArticle.getName(), newArticle.getName()); + assertEquals(foundArticle.getAuthor(), newArticle.getAuthor()); + assertEquals(foundArticle.getPublishDate(), newArticle.getPublishDate()); + assertEquals(foundArticle.getHtmlContent(), newArticle.getHtmlContent()); + } + +} From 8900c1ebba592f845b35230f8fbfa8156547beb8 Mon Sep 17 00:00:00 2001 From: Haroon Khan Date: Sat, 14 Aug 2021 08:38:04 +0100 Subject: [PATCH 13/15] [BAEL-5012] Intro to ksqlDB (#11113) * [BAEL-5012] Intro to ksqlDB * [BAEL-5012] Fix POM file and code cleanup * [BAEL-5012] Code cleanup --- ksqldb/pom.xml | 74 ++++++++ .../main/java/com/baeldung/ksqldb/Alert.java | 29 ++++ .../baeldung/ksqldb/KsqlDBApplication.java | 75 ++++++++ .../java/com/baeldung/ksqldb/Reading.java | 10 ++ .../com/baeldung/ksqldb/RowSubscriber.java | 60 +++++++ .../ksqldb/KsqlDBApplicationLiveTest.java | 160 ++++++++++++++++++ .../test/resources/docker/docker-compose.yml | 49 ++++++ ksqldb/src/test/resources/log4j.properties | 6 + pom.xml | 3 + 9 files changed, 466 insertions(+) create mode 100644 ksqldb/pom.xml create mode 100644 ksqldb/src/main/java/com/baeldung/ksqldb/Alert.java create mode 100644 ksqldb/src/main/java/com/baeldung/ksqldb/KsqlDBApplication.java create mode 100644 ksqldb/src/main/java/com/baeldung/ksqldb/Reading.java create mode 100644 ksqldb/src/main/java/com/baeldung/ksqldb/RowSubscriber.java create mode 100644 ksqldb/src/test/java/com/baeldung/ksqldb/KsqlDBApplicationLiveTest.java create mode 100644 ksqldb/src/test/resources/docker/docker-compose.yml create mode 100644 ksqldb/src/test/resources/log4j.properties diff --git a/ksqldb/pom.xml b/ksqldb/pom.xml new file mode 100644 index 0000000000..13867b16e3 --- /dev/null +++ b/ksqldb/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + ksqldb-app + 0.0.1-SNAPSHOT + ksqldb + + + com.baeldung + parent-modules + 1.0.0-SNAPSHOT + ../pom.xml + + + + + confluent + confluent-repo + http://packages.confluent.io/maven/ + + + + + + io.confluent.ksql + ksqldb-api-client + ${ksqldb.version} + + + + org.projectlombok + lombok + ${lombok.version} + + + + org.awaitility + awaitility + ${awaitility.version} + test + + + + org.assertj + assertj-core + ${assertj.version} + test + + + + org.testcontainers + testcontainers + ${testcontainers.version} + test + + + + org.testcontainers + junit-jupiter + ${testcontainers.version} + test + + + + + 6.2.0 + 3.20.2 + 4.1.0 + 1.15.3 + + + diff --git a/ksqldb/src/main/java/com/baeldung/ksqldb/Alert.java b/ksqldb/src/main/java/com/baeldung/ksqldb/Alert.java new file mode 100644 index 0000000000..badb00f114 --- /dev/null +++ b/ksqldb/src/main/java/com/baeldung/ksqldb/Alert.java @@ -0,0 +1,29 @@ +package com.baeldung.ksqldb; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class Alert { + + @JsonProperty(value = "SENSOR_ID") + private String sensorId; + + @JsonProperty(value = "START_PERIOD") + private String startPeriod; + + @JsonProperty(value = "END_PERIOD") + private String endPeriod; + + @JsonProperty(value = "AVERAGE_READING") + private double averageReading; + +} diff --git a/ksqldb/src/main/java/com/baeldung/ksqldb/KsqlDBApplication.java b/ksqldb/src/main/java/com/baeldung/ksqldb/KsqlDBApplication.java new file mode 100644 index 0000000000..35ad3ebbb0 --- /dev/null +++ b/ksqldb/src/main/java/com/baeldung/ksqldb/KsqlDBApplication.java @@ -0,0 +1,75 @@ +package com.baeldung.ksqldb; + +import io.confluent.ksql.api.client.Client; +import io.confluent.ksql.api.client.ExecuteStatementResult; +import io.confluent.ksql.api.client.KsqlObject; +import io.confluent.ksql.api.client.Row; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.reactivestreams.Subscriber; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +@AllArgsConstructor +@Slf4j +public class KsqlDBApplication { + + private static final String CREATE_READINGS_STREAM = "" + + " CREATE STREAM readings (sensor_id VARCHAR KEY, timestamp VARCHAR, reading INT)" + + " WITH (KAFKA_TOPIC = 'readings'," + + " VALUE_FORMAT = 'JSON'," + + " TIMESTAMP = 'timestamp'," + + " TIMESTAMP_FORMAT = 'yyyy-MM-dd HH:mm:ss'," + + " PARTITIONS = 1);"; + + private static final String CREATE_ALERTS_TABLE = "" + + " CREATE TABLE alerts AS" + + " SELECT" + + " sensor_id," + + " TIMESTAMPTOSTRING(WINDOWSTART, 'yyyy-MM-dd HH:mm:ss', 'UTC') AS start_period," + + " TIMESTAMPTOSTRING(WINDOWEND, 'yyyy-MM-dd HH:mm:ss', 'UTC') AS end_period," + + " AVG(reading) AS average_reading" + + " FROM readings" + + " WINDOW TUMBLING (SIZE 30 MINUTES)" + + " GROUP BY sensor_id" + + " HAVING AVG(reading) > 25" + + " EMIT CHANGES;"; + + private static final String ALERTS_QUERY = "SELECT * FROM alerts EMIT CHANGES;"; + + private static final String READINGS_STREAM = "readings"; + + private static final Map PROPERTIES = Collections.singletonMap("auto.offset.reset", "earliest"); + + private final Client client; + + public CompletableFuture createReadingsStream() { + return client.executeStatement(CREATE_READINGS_STREAM, PROPERTIES); + } + + public CompletableFuture createAlertsTable() { + return client.executeStatement(CREATE_ALERTS_TABLE, PROPERTIES); + } + + public CompletableFuture insert(Collection rows) { + return CompletableFuture.allOf( + rows.stream() + .map(row -> client.insertInto(READINGS_STREAM, row)) + .toArray(CompletableFuture[]::new) + ); + } + + public CompletableFuture subscribeOnAlerts(Subscriber subscriber) { + return client.streamQuery(ALERTS_QUERY, PROPERTIES) + .thenAccept(streamedQueryResult -> streamedQueryResult.subscribe(subscriber)) + .whenComplete((result, ex) -> { + if (ex != null) { + log.error("Alerts push query failed", ex); + } + }); + } + +} diff --git a/ksqldb/src/main/java/com/baeldung/ksqldb/Reading.java b/ksqldb/src/main/java/com/baeldung/ksqldb/Reading.java new file mode 100644 index 0000000000..8964ff5801 --- /dev/null +++ b/ksqldb/src/main/java/com/baeldung/ksqldb/Reading.java @@ -0,0 +1,10 @@ +package com.baeldung.ksqldb; + +import lombok.Data; + +@Data +public class Reading { + private String id; + private String timestamp; + private int reading; +} diff --git a/ksqldb/src/main/java/com/baeldung/ksqldb/RowSubscriber.java b/ksqldb/src/main/java/com/baeldung/ksqldb/RowSubscriber.java new file mode 100644 index 0000000000..7d09583a39 --- /dev/null +++ b/ksqldb/src/main/java/com/baeldung/ksqldb/RowSubscriber.java @@ -0,0 +1,60 @@ +package com.baeldung.ksqldb; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.confluent.ksql.api.client.Row; +import lombok.extern.slf4j.Slf4j; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import java.util.ArrayList; +import java.util.List; + +@Slf4j +public class RowSubscriber implements Subscriber { + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private final Class clazz; + + private Subscription subscription; + + public List consumedItems = new ArrayList<>(); + + public RowSubscriber(Class clazz) { + this.clazz = clazz; + } + + @Override + public synchronized void onSubscribe(Subscription subscription) { + log.info("Subscriber is subscribed."); + this.subscription = subscription; + subscription.request(1); + } + + @Override + public synchronized void onNext(Row row) { + String jsonString = row.asObject().toJsonString(); + log.info("Row JSON: {}", jsonString); + try { + T item = OBJECT_MAPPER.readValue(jsonString, this.clazz); + log.info("Item: {}", item); + consumedItems.add(item); + } catch (JsonProcessingException e) { + log.error("Unable to parse json", e); + } + + // Request the next row + subscription.request(1); + } + + @Override + public synchronized void onError(Throwable t) { + log.error("Received an error", t); + } + + @Override + public synchronized void onComplete() { + log.info("Query has ended."); + } +} diff --git a/ksqldb/src/test/java/com/baeldung/ksqldb/KsqlDBApplicationLiveTest.java b/ksqldb/src/test/java/com/baeldung/ksqldb/KsqlDBApplicationLiveTest.java new file mode 100644 index 0000000000..f13f418048 --- /dev/null +++ b/ksqldb/src/test/java/com/baeldung/ksqldb/KsqlDBApplicationLiveTest.java @@ -0,0 +1,160 @@ +package com.baeldung.ksqldb; + +import io.confluent.ksql.api.client.Client; +import io.confluent.ksql.api.client.ClientOptions; +import io.confluent.ksql.api.client.KsqlObject; +import io.confluent.ksql.api.client.QueryInfo; +import io.confluent.ksql.api.client.QueryInfo.QueryType; +import io.confluent.ksql.api.client.Row; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.DockerComposeContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.io.File; +import java.time.Duration; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.awaitility.Awaitility.given; + +@Testcontainers +class KsqlDBApplicationLiveTest { + + private static final File KSQLDB_COMPOSE_FILE = new File("src/test/resources/docker/docker-compose.yml"); + + private static final Map PROPERTIES = Collections.singletonMap("auto.offset.reset", "earliest"); + + private static final String KSQLDB_SERVER_HOST = "localhost"; + private static final int KSQLDB_SERVER_PORT = 8088; + + @Container + public static DockerComposeContainer dockerComposeContainer = + new DockerComposeContainer<>(KSQLDB_COMPOSE_FILE) + .withServices("zookeeper", "broker", "ksqldb-server") + .withExposedService("ksqldb-server", 8088, + Wait.forHealthcheck().withStartupTimeout(Duration.ofMinutes(5))) + .withLocalCompose(true); + + private KsqlDBApplication ksqlDBApplication; + + private Client client; + + @BeforeEach + void setup() { + ClientOptions options = ClientOptions.create() + .setHost(KSQLDB_SERVER_HOST) + .setPort(KSQLDB_SERVER_PORT); + client = Client.create(options); + + ksqlDBApplication = new KsqlDBApplication(client); + } + + @AfterEach + void tearDown() { + deleteAlerts(); + } + + @Test + void givenSensorReadings_whenSubscribedToAlerts_thenAlertsAreConsumed() { + createAlertsMaterializedView(); + RowSubscriber alertSubscriber = new RowSubscriber<>(Alert.class); + + CompletableFuture result = ksqlDBApplication.subscribeOnAlerts(alertSubscriber); + insertSampleData(); + + assertThat(result).isNotNull(); + await().atMost(Duration.ofMinutes(3)).untilAsserted(() -> + assertThat(alertSubscriber.consumedItems) + .containsOnly( + expectedAlert("sensor-1", "2021-08-01 09:30:00", "2021-08-01 10:00:00", 28.0), + expectedAlert("sensor-2", "2021-08-01 10:00:00", "2021-08-01 10:30:00", 26.0) + ) + ); + } + + @Test + void givenSensorReadings_whenPullQueryForRow_thenRowIsReturned() { + createAlertsMaterializedView(); + insertSampleData(); + + String pullQuery = "SELECT * FROM alerts WHERE sensor_id = 'sensor-2';"; + + given().ignoreExceptions() + .await().atMost(Duration.ofMinutes(1)) + .untilAsserted(() -> { + // it may be possible that the materialized view is not updated with sample data yet + // so ignore TimeoutException and try again + List rows = client.executeQuery(pullQuery, PROPERTIES) + .get(10, TimeUnit.SECONDS); + + assertThat(rows).hasSize(1); + + Row row = rows.get(0); + assertThat(row.getString("SENSOR_ID")).isEqualTo("sensor-2"); + assertThat(row.getString("START_PERIOD")).isEqualTo("2021-08-01 10:00:00"); + assertThat(row.getString("END_PERIOD")).isEqualTo("2021-08-01 10:30:00"); + assertThat(row.getDouble("AVERAGE_READING")).isEqualTo(26.0); + }); + } + + private void createAlertsMaterializedView() { + ksqlDBApplication.createReadingsStream().join(); + ksqlDBApplication.createAlertsTable().join(); + } + + private void insertSampleData() { + ksqlDBApplication.insert( + Arrays.asList( + new KsqlObject().put("sensor_id", "sensor-1").put("timestamp", "2021-08-01 09:00:00").put("reading", 22), + new KsqlObject().put("sensor_id", "sensor-1").put("timestamp", "2021-08-01 09:10:00").put("reading", 20), + new KsqlObject().put("sensor_id", "sensor-1").put("timestamp", "2021-08-01 09:20:00").put("reading", 20), + + // these reading will exceed the alert threshold (sensor-1) + new KsqlObject().put("sensor_id", "sensor-1").put("timestamp", "2021-08-01 09:30:00").put("reading", 24), + new KsqlObject().put("sensor_id", "sensor-1").put("timestamp", "2021-08-01 09:40:00").put("reading", 30), + new KsqlObject().put("sensor_id", "sensor-1").put("timestamp", "2021-08-01 09:50:00").put("reading", 30), + + new KsqlObject().put("sensor_id", "sensor-1").put("timestamp", "2021-08-01 10:00:00").put("reading", 24), + + // these reading will exceed the alert threshold (sensor-2) + new KsqlObject().put("sensor_id", "sensor-2").put("timestamp", "2021-08-01 10:00:00").put("reading", 26), + new KsqlObject().put("sensor_id", "sensor-2").put("timestamp", "2021-08-01 10:10:00").put("reading", 26), + new KsqlObject().put("sensor_id", "sensor-2").put("timestamp", "2021-08-01 10:20:00").put("reading", 26), + + new KsqlObject().put("sensor_id", "sensor-1").put("timestamp", "2021-08-01 10:30:00").put("reading", 24) + ) + ).join(); + } + + private void deleteAlerts() { + client.listQueries() + .thenApply(queryInfos -> queryInfos.stream() + .filter(queryInfo -> queryInfo.getQueryType() == QueryType.PERSISTENT) + .map(QueryInfo::getId) + .findFirst() + .orElseThrow(() -> new RuntimeException("Persistent query not found"))) + .thenCompose(id -> client.executeStatement("TERMINATE " + id + ";")) + .thenCompose(result -> client.executeStatement("DROP TABLE alerts DELETE TOPIC;")) + .thenCompose(result -> client.executeStatement("DROP STREAM readings DELETE TOPIC;")) + .join(); + } + + private Alert expectedAlert(String sensorId, String startPeriod, String endPeriod, double average) { + return Alert.builder() + .sensorId(sensorId) + .startPeriod(startPeriod) + .endPeriod(endPeriod) + .averageReading(average) + .build(); + } +} diff --git a/ksqldb/src/test/resources/docker/docker-compose.yml b/ksqldb/src/test/resources/docker/docker-compose.yml new file mode 100644 index 0000000000..c90fe85e45 --- /dev/null +++ b/ksqldb/src/test/resources/docker/docker-compose.yml @@ -0,0 +1,49 @@ +--- +version: '3' + +services: + zookeeper: + image: confluentinc/cp-zookeeper:6.2.0 + hostname: zookeeper + environment: + ZOOKEEPER_CLIENT_PORT: 2181 + ZOOKEEPER_TICK_TIME: 2000 + + broker: + image: confluentinc/cp-kafka:6.2.0 + hostname: broker + depends_on: + - zookeeper + environment: + KAFKA_BROKER_ID: 1 + KAFKA_ZOOKEEPER_CONNECT: 'zookeeper:2181' + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://broker:9092,PLAINTEXT_HOST://localhost:29092 + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0 + KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 + KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 + + ksqldb-server: + image: confluentinc/ksqldb-server:0.19.0 + hostname: ksqldb-server + depends_on: + - broker + ports: + - "8088:8088" + healthcheck: + test: curl -f http://ksqldb-server:8088/ || exit 1 + environment: + KSQL_LISTENERS: http://0.0.0.0:8088 + KSQL_BOOTSTRAP_SERVERS: broker:9092 + KSQL_KSQL_LOGGING_PROCESSING_STREAM_AUTO_CREATE: "true" + KSQL_KSQL_LOGGING_PROCESSING_TOPIC_AUTO_CREATE: "true" + + ksqldb-cli: + image: confluentinc/ksqldb-cli:0.19.0 + hostname: ksqldb-cli + depends_on: + - broker + - ksqldb-server + entrypoint: /bin/sh + tty: true diff --git a/ksqldb/src/test/resources/log4j.properties b/ksqldb/src/test/resources/log4j.properties new file mode 100644 index 0000000000..31a98608fb --- /dev/null +++ b/ksqldb/src/test/resources/log4j.properties @@ -0,0 +1,6 @@ +log4j.rootLogger=INFO, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.Target=System.out +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1} - %m%n \ No newline at end of file diff --git a/pom.xml b/pom.xml index abdb851734..f56006713a 100644 --- a/pom.xml +++ b/pom.xml @@ -472,6 +472,7 @@ jsoup jta kubernetes + ksqldb language-interop libraries-2 @@ -940,6 +941,8 @@ jsoup jta + ksqldb + libraries-2 libraries-3 From abaa46fd99bfa4dc21fd1eced2ef3838f387c018 Mon Sep 17 00:00:00 2001 From: freelansam <79205526+freelansam@users.noreply.github.com> Date: Sun, 15 Aug 2021 20:11:09 +0530 Subject: [PATCH 14/15] JAVA-6403: Fix references to parents (#11102) * JAVA-6403: Fix references to parents * JAVA-6403: removed relativePath --- docker/heap-sizing/pom.xml | 26 +++++++++++-------- .../spring-boot-actuator/pom.xml | 7 +++-- .../spring-boot-jersey/pom.xml | 7 +++-- ... => JerseyApplicationIntegrationTest.java} | 2 +- .../spring-boot-mvc/pom.xml | 7 +++-- ...ava => MvcApplicationIntegrationTest.java} | 2 +- 6 files changed, 26 insertions(+), 25 deletions(-) rename spring-boot-modules/spring-boot-mvc-jersey/spring-boot-jersey/src/test/java/com/baeldung/boot/jersey/{JerseyApplicationIntegrationTests.java => JerseyApplicationIntegrationTest.java} (82%) rename spring-boot-modules/spring-boot-mvc-jersey/spring-boot-mvc/src/test/java/com/baeldung/boot/mvc/{MvcApplicationIntegrationTests.java => MvcApplicationIntegrationTest.java} (83%) diff --git a/docker/heap-sizing/pom.xml b/docker/heap-sizing/pom.xml index a86a67fdcd..2cc354f6cf 100644 --- a/docker/heap-sizing/pom.xml +++ b/docker/heap-sizing/pom.xml @@ -1,21 +1,21 @@ - + 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 2.4.2 - - com.baeldung.docker heap-sizing 0.0.1-SNAPSHOT heap-sizing Demo project for Spring Boot - - 11 - + + + com.baeldung + parent-boot-2 + 0.0.1-SNAPSHOT + ../../parent-boot-2 + + org.springframework.boot @@ -58,4 +58,8 @@ + + 11 + + diff --git a/spring-boot-modules/spring-boot-actuator/pom.xml b/spring-boot-modules/spring-boot-actuator/pom.xml index 1865974ab0..eaeb3b4ac7 100644 --- a/spring-boot-modules/spring-boot-actuator/pom.xml +++ b/spring-boot-modules/spring-boot-actuator/pom.xml @@ -9,10 +9,9 @@ 0.0.1-SNAPSHOT - org.springframework.boot - spring-boot-starter-parent - 2.3.2.RELEASE - + com.baeldung.spring-boot-modules + spring-boot-modules + 1.0.0-SNAPSHOT diff --git a/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-jersey/pom.xml b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-jersey/pom.xml index e0b261510c..a16c8b0a83 100644 --- a/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-jersey/pom.xml +++ b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-jersey/pom.xml @@ -8,10 +8,9 @@ 0.0.1-SNAPSHOT - org.springframework.boot - spring-boot-starter-parent - 2.4.2 - + com.baeldung.spring-boot-modules + spring-boot-mvc-jersey + 0.0.1-SNAPSHOT diff --git a/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-jersey/src/test/java/com/baeldung/boot/jersey/JerseyApplicationIntegrationTests.java b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-jersey/src/test/java/com/baeldung/boot/jersey/JerseyApplicationIntegrationTest.java similarity index 82% rename from spring-boot-modules/spring-boot-mvc-jersey/spring-boot-jersey/src/test/java/com/baeldung/boot/jersey/JerseyApplicationIntegrationTests.java rename to spring-boot-modules/spring-boot-mvc-jersey/spring-boot-jersey/src/test/java/com/baeldung/boot/jersey/JerseyApplicationIntegrationTest.java index 9bd6bbf029..d9018efc4a 100644 --- a/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-jersey/src/test/java/com/baeldung/boot/jersey/JerseyApplicationIntegrationTests.java +++ b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-jersey/src/test/java/com/baeldung/boot/jersey/JerseyApplicationIntegrationTest.java @@ -4,7 +4,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest -class JerseyApplicationIntegrationTests { +class JerseyApplicationIntegrationTest { @Test void contextLoads() { diff --git a/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-mvc/pom.xml b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-mvc/pom.xml index 77c8027974..8521ab25ac 100644 --- a/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-mvc/pom.xml +++ b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-mvc/pom.xml @@ -8,10 +8,9 @@ 0.0.1-SNAPSHOT - org.springframework.boot - spring-boot-starter-parent - 2.4.2 - + com.baeldung.spring-boot-modules + spring-boot-mvc-jersey + 0.0.1-SNAPSHOT diff --git a/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-mvc/src/test/java/com/baeldung/boot/mvc/MvcApplicationIntegrationTests.java b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-mvc/src/test/java/com/baeldung/boot/mvc/MvcApplicationIntegrationTest.java similarity index 83% rename from spring-boot-modules/spring-boot-mvc-jersey/spring-boot-mvc/src/test/java/com/baeldung/boot/mvc/MvcApplicationIntegrationTests.java rename to spring-boot-modules/spring-boot-mvc-jersey/spring-boot-mvc/src/test/java/com/baeldung/boot/mvc/MvcApplicationIntegrationTest.java index d05cb97e47..14b0915fa7 100644 --- a/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-mvc/src/test/java/com/baeldung/boot/mvc/MvcApplicationIntegrationTests.java +++ b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-mvc/src/test/java/com/baeldung/boot/mvc/MvcApplicationIntegrationTest.java @@ -4,7 +4,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest -class MvcApplicationIntegrationTests { +class MvcApplicationIntegrationTest { @Test void contextLoads() { From 6365185c705b1d77c36aeaa5f171d1dc7c775617 Mon Sep 17 00:00:00 2001 From: Roger <587230+rojyates@users.noreply.github.com> Date: Mon, 16 Aug 2021 04:46:43 +1000 Subject: [PATCH 15/15] BAEL-4914 Code for EnableGlobalMethodSecurity vs EnableWebSecurity (#11053) * BAEL-4914 Code for EnableGlobalMethodSecurity vs EnableWebSecurity * BAEL-4914 Add newline after @Autowired in Test --- .../AnnotationSecuredApplication.java | 15 +++ .../AnnotationSecuredController.java | 50 +++++++++ ...AnnotationSecuredStaticResourceConfig.java | 22 ++++ .../globalmethod/DifferentClass.java | 13 +++ .../websecurity/ConfigSecuredApplication.java | 14 +++ .../websecurity/ConfigSecuredController.java | 30 +++++ .../websecurity/CustomWebSecurityConfig.java | 29 +++++ .../main/resources/public/hello/baeldung.txt | 1 + ...GlobalMethodSpringBootIntegrationTest.java | 104 ++++++++++++++++++ .../WebSecuritySpringBootIntegrationTest.java | 62 +++++++++++ 10 files changed, 340 insertions(+) create mode 100644 spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/annotations/globalmethod/AnnotationSecuredApplication.java create mode 100644 spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/annotations/globalmethod/AnnotationSecuredController.java create mode 100644 spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/annotations/globalmethod/AnnotationSecuredStaticResourceConfig.java create mode 100644 spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/annotations/globalmethod/DifferentClass.java create mode 100644 spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/annotations/websecurity/ConfigSecuredApplication.java create mode 100644 spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/annotations/websecurity/ConfigSecuredController.java create mode 100644 spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/annotations/websecurity/CustomWebSecurityConfig.java create mode 100644 spring-boot-modules/spring-boot-security/src/main/resources/public/hello/baeldung.txt create mode 100644 spring-boot-modules/spring-boot-security/src/test/java/com/baeldung/annotations/globalmethod/GlobalMethodSpringBootIntegrationTest.java create mode 100644 spring-boot-modules/spring-boot-security/src/test/java/com/baeldung/annotations/websecurity/WebSecuritySpringBootIntegrationTest.java diff --git a/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/annotations/globalmethod/AnnotationSecuredApplication.java b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/annotations/globalmethod/AnnotationSecuredApplication.java new file mode 100644 index 0000000000..0495e0f716 --- /dev/null +++ b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/annotations/globalmethod/AnnotationSecuredApplication.java @@ -0,0 +1,15 @@ +package com.baeldung.annotations.globalmethod; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + +@SpringBootApplication +@EnableWebSecurity +public class AnnotationSecuredApplication { + + public static void main(String[] args) { + SpringApplication.run(AnnotationSecuredApplication.class, args); + } +} diff --git a/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/annotations/globalmethod/AnnotationSecuredController.java b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/annotations/globalmethod/AnnotationSecuredController.java new file mode 100644 index 0000000000..4687299ae5 --- /dev/null +++ b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/annotations/globalmethod/AnnotationSecuredController.java @@ -0,0 +1,50 @@ +package com.baeldung.annotations.globalmethod; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.security.RolesAllowed; + +@RestController +@EnableGlobalMethodSecurity(jsr250Enabled = true, prePostEnabled = true) +public class AnnotationSecuredController { + + @Autowired + DifferentClass differentClass; + + @GetMapping("/public") + public String publicHello() { + return "Hello Public"; + } + + @RolesAllowed("ADMIN") + @GetMapping("/admin") + public String adminHello() { + return "Hello Admin"; + } + + @RolesAllowed("USER") + @GetMapping("/protected") + public String jsr250Hello() { + return "Hello Jsr250"; + } + + @GetMapping("/indirect") + public String indirectHello() { + return jsr250Hello(); + } + + @GetMapping("/differentclass") + public String differentClassHello() { + return differentClass.differentJsr250Hello(); + } + + @PreAuthorize("hasRole('USER')") + public String preAuthorizeHello() { + return "Hello PreAuthorize"; + } + +} diff --git a/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/annotations/globalmethod/AnnotationSecuredStaticResourceConfig.java b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/annotations/globalmethod/AnnotationSecuredStaticResourceConfig.java new file mode 100644 index 0000000000..467285adfa --- /dev/null +++ b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/annotations/globalmethod/AnnotationSecuredStaticResourceConfig.java @@ -0,0 +1,22 @@ +package com.baeldung.annotations.globalmethod; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.SecurityConfigurerAdapter; +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.config.annotation.web.configuration.WebSecurityCustomizer; + + +@Configuration +@EnableWebSecurity +public class AnnotationSecuredStaticResourceConfig extends WebSecurityConfigurerAdapter { + + @Bean + public WebSecurityCustomizer ignoreResources() { + return (webSecurity) -> webSecurity + .ignoring() + .antMatchers("/hello/*"); + } +} \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/annotations/globalmethod/DifferentClass.java b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/annotations/globalmethod/DifferentClass.java new file mode 100644 index 0000000000..9bcc352d9a --- /dev/null +++ b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/annotations/globalmethod/DifferentClass.java @@ -0,0 +1,13 @@ +package com.baeldung.annotations.globalmethod; + +import org.springframework.stereotype.Component; + +import javax.annotation.security.RolesAllowed; + +@Component +public class DifferentClass { + @RolesAllowed("USER") + public String differentJsr250Hello() { + return "Hello Jsr250"; + } +} diff --git a/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/annotations/websecurity/ConfigSecuredApplication.java b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/annotations/websecurity/ConfigSecuredApplication.java new file mode 100644 index 0000000000..13e405ee22 --- /dev/null +++ b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/annotations/websecurity/ConfigSecuredApplication.java @@ -0,0 +1,14 @@ +package com.baeldung.annotations.websecurity; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + +@SpringBootApplication +@EnableWebSecurity +public class ConfigSecuredApplication { + + public static void main(String[] args) { + SpringApplication.run(ConfigSecuredApplication.class, args); + } +} diff --git a/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/annotations/websecurity/ConfigSecuredController.java b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/annotations/websecurity/ConfigSecuredController.java new file mode 100644 index 0000000000..198efb8353 --- /dev/null +++ b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/annotations/websecurity/ConfigSecuredController.java @@ -0,0 +1,30 @@ +package com.baeldung.annotations.websecurity; + +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@RestController +@EnableWebSecurity +public class ConfigSecuredController { + + @GetMapping("/public") + public String publicHello() { + return "Hello Public"; + } + + @GetMapping("/protected") + public String protectedHello() { + return "Hello from protected"; + } + + @GetMapping("/admin") + public String adminHello() { + return "Hello from admin"; + } + +} diff --git a/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/annotations/websecurity/CustomWebSecurityConfig.java b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/annotations/websecurity/CustomWebSecurityConfig.java new file mode 100644 index 0000000000..ce874e313e --- /dev/null +++ b/spring-boot-modules/spring-boot-security/src/main/java/com/baeldung/annotations/websecurity/CustomWebSecurityConfig.java @@ -0,0 +1,29 @@ +package com.baeldung.annotations.websecurity; + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.builders.WebSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@Configuration +@EnableWebSecurity +public class CustomWebSecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .antMatchers("/admin/**") + .hasRole("ADMIN") + .antMatchers("/protected/**") + .hasRole("USER"); + } + + @Override + public void configure(WebSecurity web) throws Exception { + web + .ignoring() + .antMatchers("/public/*"); + } +} diff --git a/spring-boot-modules/spring-boot-security/src/main/resources/public/hello/baeldung.txt b/spring-boot-modules/spring-boot-security/src/main/resources/public/hello/baeldung.txt new file mode 100644 index 0000000000..b58fe6d244 --- /dev/null +++ b/spring-boot-modules/spring-boot-security/src/main/resources/public/hello/baeldung.txt @@ -0,0 +1 @@ +Hello From Baeldung \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-security/src/test/java/com/baeldung/annotations/globalmethod/GlobalMethodSpringBootIntegrationTest.java b/spring-boot-modules/spring-boot-security/src/test/java/com/baeldung/annotations/globalmethod/GlobalMethodSpringBootIntegrationTest.java new file mode 100644 index 0000000000..be9dff714b --- /dev/null +++ b/spring-boot-modules/spring-boot-security/src/test/java/com/baeldung/annotations/globalmethod/GlobalMethodSpringBootIntegrationTest.java @@ -0,0 +1,104 @@ +package com.baeldung.annotations.globalmethod; + +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.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.test.context.support.WithAnonymousUser; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = RANDOM_PORT) +public class GlobalMethodSpringBootIntegrationTest { + public static final String HELLO_JSR_250 = "Hello Jsr250"; + public static final String HELLO_PUBLIC = "Hello Public"; + public static final String HELLO_PRE_AUTHORIZE = "Hello PreAuthorize"; + public static final String PUBLIC_RESOURCE = "/hello/baeldung.txt"; + public static final String HELLO_FROM_PUBLIC_RESOURCE = "Hello From Baeldung"; + private static final String PROTECTED_METHOD = "/protected"; + + @Autowired + private TestRestTemplate template; + + @Autowired + private AnnotationSecuredController api; + + @WithMockUser(username="baeldung", roles = "USER") + @Test + public void givenUserWithRole_whenJsr250_thenOk() { + assertThat(api.jsr250Hello()).isEqualTo(HELLO_JSR_250); + } + + @WithMockUser(username="baeldung", roles = "NOT-USER") + @Test(expected = AccessDeniedException.class) + public void givenWrongRole_whenJsr250_thenAccessDenied() { + api.jsr250Hello(); + } + + @Test + @WithAnonymousUser + public void givenAnonymousUser_whenPublic_thenOk() { + assertThat(api.publicHello()).isEqualTo(HELLO_PUBLIC); + } + + @Test(expected = AccessDeniedException.class) + @WithAnonymousUser + public void givenAnonymousUser_whenJsr250_thenAccessDenied() { + api.jsr250Hello(); + } + + // Tests for indirect calling of method + @Test + @WithAnonymousUser + public void givenAnonymousUser_whenIndirectCall_thenNoSecurity() { + assertThat(api.indirectHello()).isEqualTo(HELLO_JSR_250); + } + + @Test(expected = AccessDeniedException.class) + @WithAnonymousUser + public void givenAnonymousUser_whenIndirectToDifferentClass_thenAccessDenied() { + api.differentClassHello(); + } + + // Tests for static resource + @Test + public void givenPublicResource_whenGetViaWeb_thenOk() { + ResponseEntity result = template.getForEntity(PUBLIC_RESOURCE, String.class); + assertEquals(HELLO_FROM_PUBLIC_RESOURCE, result.getBody()); + } + + @Test + public void givenProtectedMethod_whenGetViaWeb_thenRedirectToLogin() { + ResponseEntity result = template.getForEntity(PROTECTED_METHOD, String.class); + assertEquals(HttpStatus.FOUND, result.getStatusCode()); + } + + // Tests for preAuthorize annotations + @WithMockUser(username="baeldung", roles = "USER") + @Test + public void givenUserWithRole_whenCallPreAuthorize_thenOk() { + assertThat(api.preAuthorizeHello()).isEqualTo(HELLO_PRE_AUTHORIZE); + } + + @WithMockUser(username="baeldung", roles = "NOT-USER") + @Test(expected = AccessDeniedException.class) + public void givenWrongRole_whenCallPreAuthorize_thenAccessDenied() { + api.preAuthorizeHello(); + } + + @Test(expected = AccessDeniedException.class) + @WithAnonymousUser + public void givenAnonymousUser_whenCallPreAuthorize_thenAccessDenied() { + api.preAuthorizeHello(); + } + +} diff --git a/spring-boot-modules/spring-boot-security/src/test/java/com/baeldung/annotations/websecurity/WebSecuritySpringBootIntegrationTest.java b/spring-boot-modules/spring-boot-security/src/test/java/com/baeldung/annotations/websecurity/WebSecuritySpringBootIntegrationTest.java new file mode 100644 index 0000000000..1360ae939d --- /dev/null +++ b/spring-boot-modules/spring-boot-security/src/test/java/com/baeldung/annotations/websecurity/WebSecuritySpringBootIntegrationTest.java @@ -0,0 +1,62 @@ +package com.baeldung.annotations.websecurity; + +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.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = RANDOM_PORT) +public class WebSecuritySpringBootIntegrationTest { + private static final String PUBLIC_RESOURCE = "/hello/baeldung.txt"; + private static final String HELLO_FROM_PUBLIC_RESOURCE = "Hello From Baeldung"; + + @Autowired + private ConfigSecuredController api; + + @Autowired + private TestRestTemplate template; + + @Test + public void whenCallPublicDirectly_thenOk() { + assertThat(api.publicHello()).isEqualTo("Hello Public"); + } + + @Test + public void whenCallProtectedDirectly_thenNoSecurity() { + assertThat(api.protectedHello()).isEqualTo("Hello from protected"); + } + + @Test + public void whenGetProtectedViaWeb_thenForbidden() { + ResponseEntity result = template.getForEntity("/protected", String.class); + assertEquals(HttpStatus.FORBIDDEN, result.getStatusCode()); + } + + @Test + public void whenGetAdminViaWeb_thenForbidden() { + ResponseEntity result = template.getForEntity("/admin", String.class); + assertEquals(HttpStatus.FORBIDDEN, result.getStatusCode()); + } + + @Test + public void whenGetPublicViaWeb_thenSuccess() { + ResponseEntity result = template.getForEntity("/public", String.class); + assertEquals(HttpStatus.OK, result.getStatusCode()); + } + + @Test + public void givenPublicResource_whenGetViaWeb_thenOk() { + ResponseEntity result = template.getForEntity(PUBLIC_RESOURCE, String.class); + assertEquals(HELLO_FROM_PUBLIC_RESOURCE, result.getBody()); + } + +}