Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
ee27de7b46
|
@ -1,2 +1,3 @@
|
||||||
## Relevant Articles
|
## Relevant Articles
|
||||||
- [Understanding XSLT Processing in Java](https://www.baeldung.com/java-extensible-stylesheet-language-transformations)
|
- [Understanding XSLT Processing in Java](https://www.baeldung.com/java-extensible-stylesheet-language-transformations)
|
||||||
|
- [Add Camel Route at Runtime in Java](https://www.baeldung.com/java-camel-dynamic-route)
|
||||||
|
|
|
@ -32,17 +32,22 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.guava</groupId>
|
<groupId>com.google.guava</groupId>
|
||||||
<artifactId>guava</artifactId>
|
<artifactId>guava</artifactId>
|
||||||
<version>32.1.2-jre</version>
|
<version>${guava.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openjdk.jmh</groupId>
|
<groupId>org.openjdk.jmh</groupId>
|
||||||
<artifactId>jmh-core</artifactId>
|
<artifactId>jmh-core</artifactId>
|
||||||
<version>1.37</version>
|
<version>${jmh.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openjdk.jmh</groupId>
|
<groupId>org.openjdk.jmh</groupId>
|
||||||
<artifactId>jmh-generator-annprocess</artifactId>
|
<artifactId>jmh-generator-annprocess</artifactId>
|
||||||
<version>1.37</version>
|
<version>${jmh.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-collections4</artifactId>
|
||||||
|
<version>${commons-collections.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
@ -63,6 +68,9 @@
|
||||||
<properties>
|
<properties>
|
||||||
<gson.version>2.10.1</gson.version>
|
<gson.version>2.10.1</gson.version>
|
||||||
<csv.version>1.5</csv.version>
|
<csv.version>1.5</csv.version>
|
||||||
|
<guava.version>32.1.2-jre</guava.version>
|
||||||
|
<jmh.version>1.37</jmh.version>
|
||||||
|
<commons-collections.version>4.4</commons-collections.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -0,0 +1,202 @@
|
||||||
|
package com.baeldung.map.prettyprintmap;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import org.apache.commons.collections4.MapUtils;
|
||||||
|
import org.assertj.core.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
class PrettyPrintMapUnitTest {
|
||||||
|
|
||||||
|
private static final Map<String, Object> MAP;
|
||||||
|
|
||||||
|
static {
|
||||||
|
// using LinkedHashMap to keep insertion order, helpful in assertions
|
||||||
|
MAP = new LinkedHashMap<>();
|
||||||
|
MAP.put("one", 1);
|
||||||
|
MAP.put("two", 2);
|
||||||
|
|
||||||
|
Map<String, Integer> innerMap = new LinkedHashMap<>();
|
||||||
|
innerMap.put("ten", 10);
|
||||||
|
innerMap.put("eleven", 11);
|
||||||
|
|
||||||
|
MAP.put("inner", innerMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenMap_whenToString_thenOneLine() {
|
||||||
|
String result = MAP.toString();
|
||||||
|
|
||||||
|
String expected = "{one=1, two=2, inner={ten=10, eleven=11}}";
|
||||||
|
Assertions.assertThat(result).isEqualTo(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenMap_whenSimpleForEachLoop_thenPrettyPrintWithoutNested() {
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
|
||||||
|
for (Map.Entry<?, ?> entry : MAP.entrySet()) {
|
||||||
|
result.append(String.format("%-15s : %s%n", entry.getKey(), entry.getValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
String expected =
|
||||||
|
"one : 1\n" +
|
||||||
|
"two : 2\n" +
|
||||||
|
"inner : {ten=10, eleven=11}\n";
|
||||||
|
Assertions.assertThat(result.toString()).isEqualToIgnoringNewLines(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenMap_whenRecursionForEachLoop_thenPrettyPrint() {
|
||||||
|
String result = printMap(0, MAP);
|
||||||
|
|
||||||
|
String expected =
|
||||||
|
"one : 1\n" +
|
||||||
|
"two : 2\n" +
|
||||||
|
"inner :\n" +
|
||||||
|
" ten : 10\n" +
|
||||||
|
" eleven : 11\n";
|
||||||
|
Assertions.assertThat(result).isEqualToIgnoringNewLines(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenMap_whenStream_thenPrettyPrintWithoutNested() {
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
|
||||||
|
MAP.forEach((k, v) -> result.append(String.format("%-15s : %s%n", k, v)));
|
||||||
|
|
||||||
|
String expected =
|
||||||
|
"one : 1\n" +
|
||||||
|
"two : 2\n" +
|
||||||
|
"inner : {ten=10, eleven=11}\n";
|
||||||
|
Assertions.assertThat(result.toString()).isEqualToIgnoringNewLines(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenMap_whenExtendedStream_thenPrettyPrintWithoutNested() {
|
||||||
|
String result = MAP.entrySet().stream()
|
||||||
|
.map(entry -> String.format("%-15s : %s", entry.getKey(), entry.getValue()))
|
||||||
|
.collect(Collectors.joining("\n"));
|
||||||
|
|
||||||
|
String expected =
|
||||||
|
"one : 1\n" +
|
||||||
|
"two : 2\n" +
|
||||||
|
"inner : {ten=10, eleven=11}\n";
|
||||||
|
Assertions.assertThat(result).isEqualToIgnoringNewLines(expected);
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
void givenMap_whenJackson_thenPrettyPrint() throws JsonProcessingException {
|
||||||
|
String result = new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(MAP);
|
||||||
|
|
||||||
|
String expected =
|
||||||
|
"{\n" +
|
||||||
|
" \"one\" : 1,\n" +
|
||||||
|
" \"two\" : 2,\n" +
|
||||||
|
" \"inner\" : {\n" +
|
||||||
|
" \"ten\" : 10,\n" +
|
||||||
|
" \"eleven\" : 11\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}";
|
||||||
|
Assertions.assertThat(result).isEqualToIgnoringNewLines(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenMap_whenGson_thenPrettyPrint() {
|
||||||
|
String result = new GsonBuilder().setPrettyPrinting().create().toJson(MAP);
|
||||||
|
|
||||||
|
String expected =
|
||||||
|
"{\n" +
|
||||||
|
" \"one\": 1,\n" +
|
||||||
|
" \"two\": 2,\n" +
|
||||||
|
" \"inner\": {\n" +
|
||||||
|
" \"ten\": 10,\n" +
|
||||||
|
" \"eleven\": 11\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}";
|
||||||
|
Assertions.assertThat(result).isEqualToIgnoringNewLines(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenMap_whenApacheCommonsCollectionsDebugPrint_thenPrettyPrintWithClassNames() throws IOException {
|
||||||
|
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
PrintStream ps = new PrintStream(baos)) {
|
||||||
|
|
||||||
|
MapUtils.debugPrint(ps, "map", MAP);
|
||||||
|
String result = baos.toString();
|
||||||
|
|
||||||
|
String expected =
|
||||||
|
"map = \n" +
|
||||||
|
"{\n" +
|
||||||
|
" one = 1 java.lang.Integer\n" +
|
||||||
|
" two = 2 java.lang.Integer\n" +
|
||||||
|
" inner = \n" +
|
||||||
|
" {\n" +
|
||||||
|
" ten = 10 java.lang.Integer\n" +
|
||||||
|
" eleven = 11 java.lang.Integer\n" +
|
||||||
|
" } java.util.LinkedHashMap\n" +
|
||||||
|
"} java.util.LinkedHashMap\n";
|
||||||
|
Assertions.assertThat(result).isEqualToIgnoringNewLines(expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenMap_whenApacheCommonsCollectionsVerbosePrint_thenPrettyPrint() throws IOException {
|
||||||
|
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
PrintStream ps = new PrintStream(baos)) {
|
||||||
|
|
||||||
|
MapUtils.verbosePrint(ps, "map", MAP);
|
||||||
|
String result = baos.toString();
|
||||||
|
|
||||||
|
String expected =
|
||||||
|
"map = \n" +
|
||||||
|
"{\n" +
|
||||||
|
" one = 1\n" +
|
||||||
|
" two = 2\n" +
|
||||||
|
" inner = \n" +
|
||||||
|
" {\n" +
|
||||||
|
" ten = 10\n" +
|
||||||
|
" eleven = 11\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}\n";
|
||||||
|
Assertions.assertThat(result).isEqualToIgnoringNewLines(expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenMap_whenGuavaJoiner_thenPrettyPrintWithoutNested() {
|
||||||
|
String result = Joiner.on(",\n").withKeyValueSeparator("=").join(MAP);
|
||||||
|
|
||||||
|
String expected =
|
||||||
|
"one=1,\n" +
|
||||||
|
"two=2,\n" +
|
||||||
|
"inner={ten=10, eleven=11}";
|
||||||
|
Assertions.assertThat(result).isEqualToIgnoringNewLines(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String printMap(int leftPadding, Map<?, ?> map) {
|
||||||
|
StringBuilder ret = new StringBuilder();
|
||||||
|
|
||||||
|
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
||||||
|
if (entry.getValue() instanceof Map) {
|
||||||
|
ret.append(String.format("%-15s :%n", entry.getKey()));
|
||||||
|
ret.append(printMap(leftPadding + 4, (Map<?, ?>) entry.getValue()));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ret.append(String.format("%" + (leftPadding > 0 ? leftPadding : "") + "s" // adding padding
|
||||||
|
+ "%-15s : %s%n",
|
||||||
|
"", entry.getKey(), entry.getValue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ import java.util.function.Function;
|
||||||
|
|
||||||
public class Currying {
|
public class Currying {
|
||||||
|
|
||||||
private static Function<Double, Function<Double, Double>> weight = mass -> gravity -> mass * gravity;
|
private static Function<Double, Function<Double, Double>> weight = gravity -> mass -> mass * gravity;
|
||||||
|
|
||||||
private static Function<Double, Double> weightOnEarth = weight.apply(9.81);
|
private static Function<Double, Double> weightOnEarth = weight.apply(9.81);
|
||||||
|
|
||||||
|
|
|
@ -229,8 +229,8 @@
|
||||||
<maven-jar-plugin.version>3.3.0</maven-jar-plugin.version>
|
<maven-jar-plugin.version>3.3.0</maven-jar-plugin.version>
|
||||||
<maven-resources-plugin.version>3.3.0</maven-resources-plugin.version>
|
<maven-resources-plugin.version>3.3.0</maven-resources-plugin.version>
|
||||||
<maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
|
<maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
|
||||||
<spring-boot.version>3.2.2</spring-boot.version>
|
<spring-boot.version>3.1.5</spring-boot.version>
|
||||||
<junit-jupiter.version>5.10.2</junit-jupiter.version>
|
<junit-jupiter.version>5.8.2</junit-jupiter.version>
|
||||||
<native-build-tools-plugin.version>0.9.17</native-build-tools-plugin.version>
|
<native-build-tools-plugin.version>0.9.17</native-build-tools-plugin.version>
|
||||||
<logback.version>1.4.4</logback.version>
|
<logback.version>1.4.4</logback.version>
|
||||||
<slf4j.version>2.0.3</slf4j.version>
|
<slf4j.version>2.0.3</slf4j.version>
|
||||||
|
|
|
@ -9,9 +9,9 @@
|
||||||
|
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.baeldung</groupId>
|
<groupId>com.baeldung</groupId>
|
||||||
<artifactId>parent-boot-2</artifactId>
|
<artifactId>parent-boot-3</artifactId>
|
||||||
<version>0.0.1-SNAPSHOT</version>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
<relativePath>../../parent-boot-2</relativePath>
|
<relativePath>../../parent-boot-3</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
|
@ -74,12 +74,6 @@
|
||||||
<version>${reactor.version}</version>
|
<version>${reactor.version}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>de.flapdoodle.embed</groupId>
|
|
||||||
<artifactId>de.flapdoodle.embed.mongo</artifactId>
|
|
||||||
<version>${de.flapdoodle.embed.mongo.version}</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.awaitility</groupId>
|
<groupId>org.awaitility</groupId>
|
||||||
<artifactId>awaitility</artifactId>
|
<artifactId>awaitility</artifactId>
|
||||||
|
@ -95,6 +89,17 @@
|
||||||
<artifactId>reactor-netty-http</artifactId>
|
<artifactId>reactor-netty-http</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testcontainers</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testcontainers</groupId>
|
||||||
|
<artifactId>mongodb</artifactId>
|
||||||
|
<version>${mongodb.testcontainer.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
@ -114,9 +119,9 @@
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<axon-bom.version>4.6.3</axon-bom.version>
|
<axon-bom.version>4.9.3</axon-bom.version>
|
||||||
<de.flapdoodle.embed.mongo.version>3.4.8</de.flapdoodle.embed.mongo.version>
|
|
||||||
<reactor.version>3.6.0</reactor.version>
|
<reactor.version>3.6.0</reactor.version>
|
||||||
|
<mongodb.testcontainer.version>1.17.6</mongodb.testcontainer.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
</project>
|
</project>
|
|
@ -18,7 +18,6 @@ import com.mongodb.client.model.IndexOptions;
|
||||||
import com.mongodb.client.model.Indexes;
|
import com.mongodb.client.model.Indexes;
|
||||||
import com.mongodb.client.result.UpdateResult;
|
import com.mongodb.client.result.UpdateResult;
|
||||||
|
|
||||||
import groovyjarjarantlr4.v4.runtime.misc.NotNull;
|
|
||||||
|
|
||||||
import org.axonframework.config.ProcessingGroup;
|
import org.axonframework.config.ProcessingGroup;
|
||||||
import org.axonframework.eventhandling.EventHandler;
|
import org.axonframework.eventhandling.EventHandler;
|
||||||
|
@ -30,6 +29,7 @@ import org.reactivestreams.Publisher;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.context.annotation.Profile;
|
import org.springframework.context.annotation.Profile;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
|
@ -172,7 +172,7 @@ public class MongoOrdersEventHandler implements OrdersEventHandler {
|
||||||
.toString());
|
.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Order documentToOrder(@NotNull Document document) {
|
private Order documentToOrder(@NonNull Document document) {
|
||||||
Order order = new Order(document.getString(ORDER_ID_PROPERTY_NAME));
|
Order order = new Order(document.getString(ORDER_ID_PROPERTY_NAME));
|
||||||
Document products = document.get(PRODUCTS_PROPERTY_NAME, Document.class);
|
Document products = document.get(PRODUCTS_PROPERTY_NAME, Document.class);
|
||||||
products.forEach((k, v) -> order.getProducts()
|
products.forEach((k, v) -> order.getProducts()
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
package com.baeldung.axon.querymodel;
|
||||||
|
|
||||||
|
import com.mongodb.client.MongoClient;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
|
||||||
|
import org.springframework.test.context.DynamicPropertyRegistry;
|
||||||
|
import org.springframework.test.context.DynamicPropertySource;
|
||||||
|
import org.testcontainers.containers.MongoDBContainer;
|
||||||
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
|
|
||||||
|
@DataMongoTest
|
||||||
|
public class MongoOrdersEventHandlerLiveTest extends AbstractOrdersEventHandlerUnitTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
MongoClient mongoClient;
|
||||||
|
|
||||||
|
@Container
|
||||||
|
static MongoDBContainer mongoDBContainer = new MongoDBContainer("mongo:6.0").withExposedPorts(27017);
|
||||||
|
|
||||||
|
@DynamicPropertySource
|
||||||
|
static void mongoDbProperties(DynamicPropertyRegistry registry) {
|
||||||
|
|
||||||
|
mongoDBContainer.start();
|
||||||
|
registry.add("spring.data.mongodb.uri", mongoDBContainer::getReplicaSetUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected OrdersEventHandler getHandler() {
|
||||||
|
mongoClient.getDatabase("axonframework")
|
||||||
|
.drop();
|
||||||
|
return new MongoOrdersEventHandler(mongoClient, emitter);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,20 +0,0 @@
|
||||||
package com.baeldung.axon.querymodel;
|
|
||||||
|
|
||||||
import com.mongodb.client.MongoClient;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
|
|
||||||
|
|
||||||
@DataMongoTest
|
|
||||||
public class MongoOrdersEventHandlerUnitTest extends AbstractOrdersEventHandlerUnitTest {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
MongoClient mongoClient;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected OrdersEventHandler getHandler() {
|
|
||||||
mongoClient.getDatabase("axonframework")
|
|
||||||
.drop();
|
|
||||||
return new MongoOrdersEventHandler(mongoClient, emitter);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -108,7 +108,7 @@
|
||||||
<module>spring-jpa-2</module>
|
<module>spring-jpa-2</module>
|
||||||
<module>spring-jdbc</module>
|
<module>spring-jdbc</module>
|
||||||
<module>spring-jdbc-2</module>
|
<module>spring-jdbc-2</module>
|
||||||
<module>spring-jooq</module>
|
<!--<module>spring-jooq</module>-->
|
||||||
<module>spring-mybatis</module>
|
<module>spring-mybatis</module>
|
||||||
<module>spring-persistence-simple</module>
|
<module>spring-persistence-simple</module>
|
||||||
<module>spring-data-yugabytedb</module>
|
<module>spring-data-yugabytedb</module>
|
||||||
|
|
4
pom.xml
4
pom.xml
|
@ -833,7 +833,7 @@
|
||||||
<!--<module>spring-integration</module>--><!-- failing after upgrading to jdk17-->
|
<!--<module>spring-integration</module>--><!-- failing after upgrading to jdk17-->
|
||||||
<module>spring-jenkins-pipeline</module>
|
<module>spring-jenkins-pipeline</module>
|
||||||
<module>spring-jersey</module>
|
<module>spring-jersey</module>
|
||||||
<module>spring-jinq</module>
|
<!--<module>spring-jinq</module>-->
|
||||||
<module>spring-kafka-2</module>
|
<module>spring-kafka-2</module>
|
||||||
<module>spring-kafka-3</module>
|
<module>spring-kafka-3</module>
|
||||||
<module>spring-kafka</module>
|
<module>spring-kafka</module>
|
||||||
|
@ -1079,7 +1079,7 @@
|
||||||
<!--<module>spring-integration</module>--><!-- failing after upgrading to jdk17-->
|
<!--<module>spring-integration</module>--><!-- failing after upgrading to jdk17-->
|
||||||
<module>spring-jenkins-pipeline</module>
|
<module>spring-jenkins-pipeline</module>
|
||||||
<module>spring-jersey</module>
|
<module>spring-jersey</module>
|
||||||
<module>spring-jinq</module>
|
<!--<module>spring-jinq</module>-->
|
||||||
<module>spring-kafka-2</module>
|
<module>spring-kafka-2</module>
|
||||||
<module>spring-kafka-3</module>
|
<module>spring-kafka-3</module>
|
||||||
<module>spring-kafka</module>
|
<module>spring-kafka</module>
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
server.port=8081
|
server.port=8081
|
||||||
|
|
||||||
logging.level.root=INFO
|
logging.level.root=INFO
|
||||||
|
|
||||||
|
server.error.include-message=always
|
|
@ -154,6 +154,8 @@
|
||||||
<resource.delimiter>@</resource.delimiter>
|
<resource.delimiter>@</resource.delimiter>
|
||||||
<!-- <start-class>com.baeldung.buildproperties.Application</start-class> -->
|
<!-- <start-class>com.baeldung.buildproperties.Application</start-class> -->
|
||||||
<start-class>com.baeldung.yaml.MyApplication</start-class>
|
<start-class>com.baeldung.yaml.MyApplication</start-class>
|
||||||
|
<spring-boot.version>3.2.2</spring-boot.version>
|
||||||
|
<junit-jupiter.version>5.10.2</junit-jupiter.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
</project>
|
</project>
|
|
@ -0,0 +1,33 @@
|
||||||
|
HELP.md
|
||||||
|
target/
|
||||||
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
|
!**/src/main/**/target/
|
||||||
|
!**/src/test/**/target/
|
||||||
|
|
||||||
|
### STS ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
build/
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
Binary file not shown.
2
spring-cloud-modules/spring-cloud-aws-v3/.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
2
spring-cloud-modules/spring-cloud-aws-v3/.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip
|
||||||
|
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
|
|
@ -0,0 +1,308 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The ASF licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Apache Maven Wrapper startup batch script, version 3.2.0
|
||||||
|
#
|
||||||
|
# Required ENV vars:
|
||||||
|
# ------------------
|
||||||
|
# JAVA_HOME - location of a JDK home dir
|
||||||
|
#
|
||||||
|
# Optional ENV vars
|
||||||
|
# -----------------
|
||||||
|
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||||
|
# e.g. to debug Maven itself, use
|
||||||
|
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||||
|
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
if [ -z "$MAVEN_SKIP_RC" ] ; then
|
||||||
|
|
||||||
|
if [ -f /usr/local/etc/mavenrc ] ; then
|
||||||
|
. /usr/local/etc/mavenrc
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f /etc/mavenrc ] ; then
|
||||||
|
. /etc/mavenrc
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "$HOME/.mavenrc" ] ; then
|
||||||
|
. "$HOME/.mavenrc"
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
# OS specific support. $var _must_ be set to either true or false.
|
||||||
|
cygwin=false;
|
||||||
|
darwin=false;
|
||||||
|
mingw=false
|
||||||
|
case "$(uname)" in
|
||||||
|
CYGWIN*) cygwin=true ;;
|
||||||
|
MINGW*) mingw=true;;
|
||||||
|
Darwin*) darwin=true
|
||||||
|
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
|
||||||
|
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
|
||||||
|
if [ -z "$JAVA_HOME" ]; then
|
||||||
|
if [ -x "/usr/libexec/java_home" ]; then
|
||||||
|
JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
|
||||||
|
else
|
||||||
|
JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -z "$JAVA_HOME" ] ; then
|
||||||
|
if [ -r /etc/gentoo-release ] ; then
|
||||||
|
JAVA_HOME=$(java-config --jre-home)
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin, ensure paths are in UNIX format before anything is touched
|
||||||
|
if $cygwin ; then
|
||||||
|
[ -n "$JAVA_HOME" ] &&
|
||||||
|
JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
|
||||||
|
[ -n "$CLASSPATH" ] &&
|
||||||
|
CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Mingw, ensure paths are in UNIX format before anything is touched
|
||||||
|
if $mingw ; then
|
||||||
|
[ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
|
||||||
|
JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$JAVA_HOME" ]; then
|
||||||
|
javaExecutable="$(which javac)"
|
||||||
|
if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
|
||||||
|
# readlink(1) is not available as standard on Solaris 10.
|
||||||
|
readLink=$(which readlink)
|
||||||
|
if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
|
||||||
|
if $darwin ; then
|
||||||
|
javaHome="$(dirname "\"$javaExecutable\"")"
|
||||||
|
javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
|
||||||
|
else
|
||||||
|
javaExecutable="$(readlink -f "\"$javaExecutable\"")"
|
||||||
|
fi
|
||||||
|
javaHome="$(dirname "\"$javaExecutable\"")"
|
||||||
|
javaHome=$(expr "$javaHome" : '\(.*\)/bin')
|
||||||
|
JAVA_HOME="$javaHome"
|
||||||
|
export JAVA_HOME
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$JAVACMD" ] ; then
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
echo "Error: JAVA_HOME is not defined correctly." >&2
|
||||||
|
echo " We cannot execute $JAVACMD" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$JAVA_HOME" ] ; then
|
||||||
|
echo "Warning: JAVA_HOME environment variable is not set."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# traverses directory structure from process work directory to filesystem root
|
||||||
|
# first directory with .mvn subdirectory is considered project base directory
|
||||||
|
find_maven_basedir() {
|
||||||
|
if [ -z "$1" ]
|
||||||
|
then
|
||||||
|
echo "Path not specified to find_maven_basedir"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
basedir="$1"
|
||||||
|
wdir="$1"
|
||||||
|
while [ "$wdir" != '/' ] ; do
|
||||||
|
if [ -d "$wdir"/.mvn ] ; then
|
||||||
|
basedir=$wdir
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
|
||||||
|
if [ -d "${wdir}" ]; then
|
||||||
|
wdir=$(cd "$wdir/.." || exit 1; pwd)
|
||||||
|
fi
|
||||||
|
# end of workaround
|
||||||
|
done
|
||||||
|
printf '%s' "$(cd "$basedir" || exit 1; pwd)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# concatenates all lines of a file
|
||||||
|
concat_lines() {
|
||||||
|
if [ -f "$1" ]; then
|
||||||
|
# Remove \r in case we run on Windows within Git Bash
|
||||||
|
# and check out the repository with auto CRLF management
|
||||||
|
# enabled. Otherwise, we may read lines that are delimited with
|
||||||
|
# \r\n and produce $'-Xarg\r' rather than -Xarg due to word
|
||||||
|
# splitting rules.
|
||||||
|
tr -s '\r\n' ' ' < "$1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
log() {
|
||||||
|
if [ "$MVNW_VERBOSE" = true ]; then
|
||||||
|
printf '%s\n' "$1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
|
||||||
|
if [ -z "$BASE_DIR" ]; then
|
||||||
|
exit 1;
|
||||||
|
fi
|
||||||
|
|
||||||
|
MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
|
||||||
|
log "$MAVEN_PROJECTBASEDIR"
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||||
|
# This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||||
|
##########################################################################################
|
||||||
|
wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
|
||||||
|
if [ -r "$wrapperJarPath" ]; then
|
||||||
|
log "Found $wrapperJarPath"
|
||||||
|
else
|
||||||
|
log "Couldn't find $wrapperJarPath, downloading it ..."
|
||||||
|
|
||||||
|
if [ -n "$MVNW_REPOURL" ]; then
|
||||||
|
wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
|
||||||
|
else
|
||||||
|
wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
|
||||||
|
fi
|
||||||
|
while IFS="=" read -r key value; do
|
||||||
|
# Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
|
||||||
|
safeValue=$(echo "$value" | tr -d '\r')
|
||||||
|
case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
|
||||||
|
esac
|
||||||
|
done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
|
||||||
|
log "Downloading from: $wrapperUrl"
|
||||||
|
|
||||||
|
if $cygwin; then
|
||||||
|
wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v wget > /dev/null; then
|
||||||
|
log "Found wget ... using wget"
|
||||||
|
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
|
||||||
|
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
||||||
|
wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
|
||||||
|
else
|
||||||
|
wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
|
||||||
|
fi
|
||||||
|
elif command -v curl > /dev/null; then
|
||||||
|
log "Found curl ... using curl"
|
||||||
|
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
|
||||||
|
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
||||||
|
curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
|
||||||
|
else
|
||||||
|
curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log "Falling back to using Java to download"
|
||||||
|
javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
|
||||||
|
javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
|
||||||
|
# For Cygwin, switch paths to Windows format before running javac
|
||||||
|
if $cygwin; then
|
||||||
|
javaSource=$(cygpath --path --windows "$javaSource")
|
||||||
|
javaClass=$(cygpath --path --windows "$javaClass")
|
||||||
|
fi
|
||||||
|
if [ -e "$javaSource" ]; then
|
||||||
|
if [ ! -e "$javaClass" ]; then
|
||||||
|
log " - Compiling MavenWrapperDownloader.java ..."
|
||||||
|
("$JAVA_HOME/bin/javac" "$javaSource")
|
||||||
|
fi
|
||||||
|
if [ -e "$javaClass" ]; then
|
||||||
|
log " - Running MavenWrapperDownloader.java ..."
|
||||||
|
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
##########################################################################################
|
||||||
|
# End of extension
|
||||||
|
##########################################################################################
|
||||||
|
|
||||||
|
# If specified, validate the SHA-256 sum of the Maven wrapper jar file
|
||||||
|
wrapperSha256Sum=""
|
||||||
|
while IFS="=" read -r key value; do
|
||||||
|
case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
|
||||||
|
esac
|
||||||
|
done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
|
||||||
|
if [ -n "$wrapperSha256Sum" ]; then
|
||||||
|
wrapperSha256Result=false
|
||||||
|
if command -v sha256sum > /dev/null; then
|
||||||
|
if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
|
||||||
|
wrapperSha256Result=true
|
||||||
|
fi
|
||||||
|
elif command -v shasum > /dev/null; then
|
||||||
|
if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
|
||||||
|
wrapperSha256Result=true
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
|
||||||
|
echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ $wrapperSha256Result = false ]; then
|
||||||
|
echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
|
||||||
|
echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
|
||||||
|
echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
|
||||||
|
|
||||||
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
|
if $cygwin; then
|
||||||
|
[ -n "$JAVA_HOME" ] &&
|
||||||
|
JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
|
||||||
|
[ -n "$CLASSPATH" ] &&
|
||||||
|
CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
|
||||||
|
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
|
||||||
|
MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Provide a "standardized" way to retrieve the CLI args that will
|
||||||
|
# work with both Windows and non-Windows executions.
|
||||||
|
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
|
||||||
|
export MAVEN_CMD_LINE_ARGS
|
||||||
|
|
||||||
|
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||||
|
|
||||||
|
# shellcheck disable=SC2086 # safe args
|
||||||
|
exec "$JAVACMD" \
|
||||||
|
$MAVEN_OPTS \
|
||||||
|
$MAVEN_DEBUG_OPTS \
|
||||||
|
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
|
||||||
|
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
|
||||||
|
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
|
|
@ -0,0 +1,205 @@
|
||||||
|
@REM ----------------------------------------------------------------------------
|
||||||
|
@REM Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
@REM or more contributor license agreements. See the NOTICE file
|
||||||
|
@REM distributed with this work for additional information
|
||||||
|
@REM regarding copyright ownership. The ASF licenses this file
|
||||||
|
@REM to you under the Apache License, Version 2.0 (the
|
||||||
|
@REM "License"); you may not use this file except in compliance
|
||||||
|
@REM with the License. You may obtain a copy of the License at
|
||||||
|
@REM
|
||||||
|
@REM https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@REM
|
||||||
|
@REM Unless required by applicable law or agreed to in writing,
|
||||||
|
@REM software distributed under the License is distributed on an
|
||||||
|
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
@REM KIND, either express or implied. See the License for the
|
||||||
|
@REM specific language governing permissions and limitations
|
||||||
|
@REM under the License.
|
||||||
|
@REM ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@REM ----------------------------------------------------------------------------
|
||||||
|
@REM Apache Maven Wrapper startup batch script, version 3.2.0
|
||||||
|
@REM
|
||||||
|
@REM Required ENV vars:
|
||||||
|
@REM JAVA_HOME - location of a JDK home dir
|
||||||
|
@REM
|
||||||
|
@REM Optional ENV vars
|
||||||
|
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
|
||||||
|
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
|
||||||
|
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||||
|
@REM e.g. to debug Maven itself, use
|
||||||
|
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||||
|
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||||
|
@REM ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
|
||||||
|
@echo off
|
||||||
|
@REM set title of command window
|
||||||
|
title %0
|
||||||
|
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
|
||||||
|
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
|
||||||
|
|
||||||
|
@REM set %HOME% to equivalent of $HOME
|
||||||
|
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
|
||||||
|
|
||||||
|
@REM Execute a user defined script before this one
|
||||||
|
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
|
||||||
|
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
|
||||||
|
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
|
||||||
|
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
|
||||||
|
:skipRcPre
|
||||||
|
|
||||||
|
@setlocal
|
||||||
|
|
||||||
|
set ERROR_CODE=0
|
||||||
|
|
||||||
|
@REM To isolate internal variables from possible post scripts, we use another setlocal
|
||||||
|
@setlocal
|
||||||
|
|
||||||
|
@REM ==== START VALIDATION ====
|
||||||
|
if not "%JAVA_HOME%" == "" goto OkJHome
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo Error: JAVA_HOME not found in your environment. >&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||||
|
echo location of your Java installation. >&2
|
||||||
|
echo.
|
||||||
|
goto error
|
||||||
|
|
||||||
|
:OkJHome
|
||||||
|
if exist "%JAVA_HOME%\bin\java.exe" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo Error: JAVA_HOME is set to an invalid directory. >&2
|
||||||
|
echo JAVA_HOME = "%JAVA_HOME%" >&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||||
|
echo location of your Java installation. >&2
|
||||||
|
echo.
|
||||||
|
goto error
|
||||||
|
|
||||||
|
@REM ==== END VALIDATION ====
|
||||||
|
|
||||||
|
:init
|
||||||
|
|
||||||
|
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
|
||||||
|
@REM Fallback to current working directory if not found.
|
||||||
|
|
||||||
|
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
|
||||||
|
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
|
||||||
|
|
||||||
|
set EXEC_DIR=%CD%
|
||||||
|
set WDIR=%EXEC_DIR%
|
||||||
|
:findBaseDir
|
||||||
|
IF EXIST "%WDIR%"\.mvn goto baseDirFound
|
||||||
|
cd ..
|
||||||
|
IF "%WDIR%"=="%CD%" goto baseDirNotFound
|
||||||
|
set WDIR=%CD%
|
||||||
|
goto findBaseDir
|
||||||
|
|
||||||
|
:baseDirFound
|
||||||
|
set MAVEN_PROJECTBASEDIR=%WDIR%
|
||||||
|
cd "%EXEC_DIR%"
|
||||||
|
goto endDetectBaseDir
|
||||||
|
|
||||||
|
:baseDirNotFound
|
||||||
|
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
|
||||||
|
cd "%EXEC_DIR%"
|
||||||
|
|
||||||
|
:endDetectBaseDir
|
||||||
|
|
||||||
|
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
|
||||||
|
|
||||||
|
@setlocal EnableExtensions EnableDelayedExpansion
|
||||||
|
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
|
||||||
|
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
|
||||||
|
|
||||||
|
:endReadAdditionalConfig
|
||||||
|
|
||||||
|
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
|
||||||
|
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
|
||||||
|
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||||
|
|
||||||
|
set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
|
||||||
|
|
||||||
|
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
|
||||||
|
IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
|
||||||
|
)
|
||||||
|
|
||||||
|
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||||
|
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||||
|
if exist %WRAPPER_JAR% (
|
||||||
|
if "%MVNW_VERBOSE%" == "true" (
|
||||||
|
echo Found %WRAPPER_JAR%
|
||||||
|
)
|
||||||
|
) else (
|
||||||
|
if not "%MVNW_REPOURL%" == "" (
|
||||||
|
SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
|
||||||
|
)
|
||||||
|
if "%MVNW_VERBOSE%" == "true" (
|
||||||
|
echo Couldn't find %WRAPPER_JAR%, downloading it ...
|
||||||
|
echo Downloading from: %WRAPPER_URL%
|
||||||
|
)
|
||||||
|
|
||||||
|
powershell -Command "&{"^
|
||||||
|
"$webclient = new-object System.Net.WebClient;"^
|
||||||
|
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
|
||||||
|
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
|
||||||
|
"}"^
|
||||||
|
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
|
||||||
|
"}"
|
||||||
|
if "%MVNW_VERBOSE%" == "true" (
|
||||||
|
echo Finished downloading %WRAPPER_JAR%
|
||||||
|
)
|
||||||
|
)
|
||||||
|
@REM End of extension
|
||||||
|
|
||||||
|
@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
|
||||||
|
SET WRAPPER_SHA_256_SUM=""
|
||||||
|
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
|
||||||
|
IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
|
||||||
|
)
|
||||||
|
IF NOT %WRAPPER_SHA_256_SUM%=="" (
|
||||||
|
powershell -Command "&{"^
|
||||||
|
"$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
|
||||||
|
"If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
|
||||||
|
" Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
|
||||||
|
" Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
|
||||||
|
" Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
|
||||||
|
" exit 1;"^
|
||||||
|
"}"^
|
||||||
|
"}"
|
||||||
|
if ERRORLEVEL 1 goto error
|
||||||
|
)
|
||||||
|
|
||||||
|
@REM Provide a "standardized" way to retrieve the CLI args that will
|
||||||
|
@REM work with both Windows and non-Windows executions.
|
||||||
|
set MAVEN_CMD_LINE_ARGS=%*
|
||||||
|
|
||||||
|
%MAVEN_JAVA_EXE% ^
|
||||||
|
%JVM_CONFIG_MAVEN_PROPS% ^
|
||||||
|
%MAVEN_OPTS% ^
|
||||||
|
%MAVEN_DEBUG_OPTS% ^
|
||||||
|
-classpath %WRAPPER_JAR% ^
|
||||||
|
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
|
||||||
|
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
|
||||||
|
if ERRORLEVEL 1 goto error
|
||||||
|
goto end
|
||||||
|
|
||||||
|
:error
|
||||||
|
set ERROR_CODE=1
|
||||||
|
|
||||||
|
:end
|
||||||
|
@endlocal & set ERROR_CODE=%ERROR_CODE%
|
||||||
|
|
||||||
|
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
|
||||||
|
@REM check for post script, once with legacy .bat ending and once with .cmd ending
|
||||||
|
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
|
||||||
|
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
|
||||||
|
:skipRcPost
|
||||||
|
|
||||||
|
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
|
||||||
|
if "%MAVEN_BATCH_PAUSE%"=="on" pause
|
||||||
|
|
||||||
|
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
|
||||||
|
|
||||||
|
cmd /C exit /B %ERROR_CODE%
|
|
@ -1,14 +1,18 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>com.baeldung.spring.cloud</groupId>
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>3.2.2</version>
|
||||||
|
<relativePath/> <!-- lookup parent from repository -->
|
||||||
|
</parent>
|
||||||
|
<groupId>com.baeldung.spring.cloud.aws.sqs</groupId>
|
||||||
<artifactId>spring-cloud-aws-v3</artifactId>
|
<artifactId>spring-cloud-aws-v3</artifactId>
|
||||||
<version>0.0.1-SNAPSHOT</version>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
<name>spring-cloud-aws-v3</name>
|
<name>spring-cloud-aws-v3</name>
|
||||||
<packaging>jar</packaging>
|
<description>spring-cloud-aws-v3</description>
|
||||||
<description>Spring Cloud AWS Examples</description>
|
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
@ -55,14 +59,29 @@
|
||||||
<artifactId>awaitility</artifactId>
|
<artifactId>awaitility</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.assertj</groupId>
|
||||||
|
<artifactId>assertj-core</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<start-class>com.baeldung.spring.cloud.aws.SpringCloudAwsApplication</start-class>
|
<start-class>com.baeldung.spring.cloud.aws.sqs.SpringCloudAwsApplication</start-class>
|
||||||
<spring-cloud-aws.version>3.1.0</spring-cloud-aws.version>
|
<spring-cloud-aws.version>3.1.0</spring-cloud-aws.version>
|
||||||
|
<maven.surefire.version>3.1.0</maven.surefire.version>
|
||||||
<maven.compiler.source>17</maven.compiler.source>
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
<maven.compiler.target>17</maven.compiler.target>
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
<java.version>17</java.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.baeldung.spring.cloud.aws.sqs.acknowledgement;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class OrderProcessingApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication app = new SpringApplication(OrderProcessingApplication.class);
|
||||||
|
app.setAdditionalProfiles("acknowledgement");
|
||||||
|
app.run(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package com.baeldung.spring.cloud.aws.sqs.acknowledgement.configuration;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
@ConfigurationProperties(prefix = "events.queues")
|
||||||
|
public class EventsQueuesProperties {
|
||||||
|
|
||||||
|
private String orderProcessingRetryQueue;
|
||||||
|
|
||||||
|
private String orderProcessingAsyncQueue;
|
||||||
|
|
||||||
|
private String orderProcessingNoRetriesQueue;
|
||||||
|
|
||||||
|
public String getOrderProcessingRetryQueue() {
|
||||||
|
return orderProcessingRetryQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOrderProcessingRetryQueue(String orderProcessingRetryQueue) {
|
||||||
|
this.orderProcessingRetryQueue = orderProcessingRetryQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOrderProcessingAsyncQueue() {
|
||||||
|
return orderProcessingAsyncQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOrderProcessingAsyncQueue(String orderProcessingAsyncQueue) {
|
||||||
|
this.orderProcessingAsyncQueue = orderProcessingAsyncQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOrderProcessingNoRetriesQueue() {
|
||||||
|
return orderProcessingNoRetriesQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOrderProcessingNoRetriesQueue(String orderProcessingNoRetriesQueue) {
|
||||||
|
this.orderProcessingNoRetriesQueue = orderProcessingNoRetriesQueue;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.baeldung.spring.cloud.aws.sqs.acknowledgement.configuration;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@EnableConfigurationProperties({ EventsQueuesProperties.class, ProductIdProperties.class})
|
||||||
|
@Configuration
|
||||||
|
public class OrderProcessingConfiguration {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package com.baeldung.spring.cloud.aws.sqs.acknowledgement.configuration;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
@ConfigurationProperties("product.id")
|
||||||
|
public class ProductIdProperties {
|
||||||
|
|
||||||
|
private UUID smartphone;
|
||||||
|
|
||||||
|
private UUID wirelessHeadphones;
|
||||||
|
|
||||||
|
private UUID laptop;
|
||||||
|
|
||||||
|
public UUID getSmartphone() {
|
||||||
|
return smartphone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSmartphone(UUID smartphone) {
|
||||||
|
this.smartphone = smartphone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getWirelessHeadphones() {
|
||||||
|
return wirelessHeadphones;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWirelessHeadphones(UUID wirelessHeadphones) {
|
||||||
|
this.wirelessHeadphones = wirelessHeadphones;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getLaptop() {
|
||||||
|
return laptop;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLaptop(UUID laptop) {
|
||||||
|
this.laptop = laptop;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.baeldung.spring.cloud.aws.sqs.acknowledgement.exception;
|
||||||
|
|
||||||
|
public class OutOfStockException extends RuntimeException {
|
||||||
|
|
||||||
|
public OutOfStockException(String errorMessage) {
|
||||||
|
super(errorMessage);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.baeldung.spring.cloud.aws.sqs.acknowledgement.exception;
|
||||||
|
|
||||||
|
public class ProductNotFoundException extends RuntimeException {
|
||||||
|
|
||||||
|
public ProductNotFoundException(String errorMessage) {
|
||||||
|
super(errorMessage);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
package com.baeldung.spring.cloud.aws.sqs.acknowledgement.listener;
|
||||||
|
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import com.baeldung.spring.cloud.aws.sqs.acknowledgement.model.OrderCreatedEvent;
|
||||||
|
import com.baeldung.spring.cloud.aws.sqs.acknowledgement.model.OrderStatus;
|
||||||
|
import com.baeldung.spring.cloud.aws.sqs.acknowledgement.service.OrderService;
|
||||||
|
import com.baeldung.spring.cloud.aws.sqs.acknowledgement.service.InventoryService;
|
||||||
|
|
||||||
|
import io.awspring.cloud.sqs.annotation.SqsListener;
|
||||||
|
import io.awspring.cloud.sqs.annotation.SqsListenerAcknowledgementMode;
|
||||||
|
import io.awspring.cloud.sqs.listener.acknowledgement.Acknowledgement;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class OrderProcessingListeners {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(OrderProcessingListeners.class);
|
||||||
|
|
||||||
|
private final InventoryService inventoryService;
|
||||||
|
|
||||||
|
private final OrderService orderService;
|
||||||
|
|
||||||
|
public OrderProcessingListeners(InventoryService inventoryService, OrderService orderService) {
|
||||||
|
this.inventoryService = inventoryService;
|
||||||
|
this.orderService = orderService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SqsListener(value = "${events.queues.order-processing-retry-queue}", id = "retry-order-processing-container", messageVisibilitySeconds = "1")
|
||||||
|
public void stockCheckRetry(OrderCreatedEvent orderCreatedEvent) {
|
||||||
|
logger.info("Message received: {}", orderCreatedEvent);
|
||||||
|
|
||||||
|
orderService.updateOrderStatus(orderCreatedEvent.id(), OrderStatus.PROCESSING);
|
||||||
|
inventoryService.checkInventory(orderCreatedEvent.productId(), orderCreatedEvent.quantity());
|
||||||
|
orderService.updateOrderStatus(orderCreatedEvent.id(), OrderStatus.PROCESSED);
|
||||||
|
logger.info("Message processed successfully: {}", orderCreatedEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SqsListener(value = "${events.queues.order-processing-async-queue}", acknowledgementMode = SqsListenerAcknowledgementMode.MANUAL, id = "async-order-processing-container", messageVisibilitySeconds = "3")
|
||||||
|
public void slowStockCheckAsynchronous(OrderCreatedEvent orderCreatedEvent, Acknowledgement acknowledgement) {
|
||||||
|
logger.info("Message received: {}", orderCreatedEvent);
|
||||||
|
|
||||||
|
orderService.updateOrderStatus(orderCreatedEvent.id(), OrderStatus.PROCESSING);
|
||||||
|
CompletableFuture.runAsync(() -> inventoryService.slowCheckInventory(orderCreatedEvent.productId(), orderCreatedEvent.quantity()))
|
||||||
|
.thenRun(() -> orderService.updateOrderStatus(orderCreatedEvent.id(), OrderStatus.PROCESSED))
|
||||||
|
.thenCompose(voidFuture -> acknowledgement.acknowledgeAsync())
|
||||||
|
.thenRun(() -> logger.info("Message for order {} acknowledged", orderCreatedEvent.id()));
|
||||||
|
|
||||||
|
logger.info("Releasing processing thread.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@SqsListener(value = "${events.queues.order-processing-no-retries-queue}", acknowledgementMode = "${events.acknowledgment.order-processing-no-retries-queue}", id = "no-retries-order-processing-container", messageVisibilitySeconds = "3")
|
||||||
|
public void stockCheckNoRetries(OrderCreatedEvent orderCreatedEvent) {
|
||||||
|
logger.info("Message received: {}", orderCreatedEvent);
|
||||||
|
|
||||||
|
// Fire and forget scenario where we're not interested on the outcome, e.g. a sales event with limited inventory.
|
||||||
|
orderService.updateOrderStatus(orderCreatedEvent.id(), OrderStatus.RECEIVED);
|
||||||
|
inventoryService.checkInventory(orderCreatedEvent.productId(), orderCreatedEvent.quantity());
|
||||||
|
logger.info("Message processed: {}", orderCreatedEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.baeldung.spring.cloud.aws.sqs.acknowledgement.model;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public record OrderCreatedEvent(UUID id, UUID productId, int quantity) {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.baeldung.spring.cloud.aws.sqs.acknowledgement.model;
|
||||||
|
|
||||||
|
public enum OrderStatus {
|
||||||
|
|
||||||
|
RECEIVED,
|
||||||
|
|
||||||
|
PROCESSING,
|
||||||
|
|
||||||
|
PROCESSED,
|
||||||
|
|
||||||
|
ERROR,
|
||||||
|
|
||||||
|
UNKNOWN
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
package com.baeldung.spring.cloud.aws.sqs.acknowledgement.service;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.baeldung.spring.cloud.aws.sqs.acknowledgement.configuration.ProductIdProperties;
|
||||||
|
import com.baeldung.spring.cloud.aws.sqs.acknowledgement.exception.OutOfStockException;
|
||||||
|
import com.baeldung.spring.cloud.aws.sqs.acknowledgement.exception.ProductNotFoundException;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class InventoryService implements InitializingBean {
|
||||||
|
|
||||||
|
private final ProductIdProperties productIdProperties;
|
||||||
|
|
||||||
|
// Using a Map to simulate storage
|
||||||
|
private Map<UUID, Integer> inventory;
|
||||||
|
|
||||||
|
public InventoryService(ProductIdProperties productIdProperties) {
|
||||||
|
this.productIdProperties = productIdProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterPropertiesSet() {
|
||||||
|
this.inventory = new ConcurrentHashMap<>(Map.of(productIdProperties.getSmartphone(), 10,
|
||||||
|
productIdProperties.getWirelessHeadphones(), 15,
|
||||||
|
productIdProperties.getLaptop(), 5));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkInventory(UUID productId, int quantity) {
|
||||||
|
Integer stock = inventory.get(productId);
|
||||||
|
if (stock == null) {
|
||||||
|
throw new ProductNotFoundException("Product with id %s not found in Inventory".formatted(productId));
|
||||||
|
}
|
||||||
|
if (stock < quantity) {
|
||||||
|
// Simulate Stock Replenishment for Retries
|
||||||
|
inventory.put(productId, stock + (int) (Math.random() * 5));
|
||||||
|
throw new OutOfStockException(
|
||||||
|
"Product with id %s is out of stock. Quantity requested: %s ".formatted(productId, quantity));
|
||||||
|
}
|
||||||
|
// Decrease inventory
|
||||||
|
inventory.put(productId, stock - quantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void slowCheckInventory(UUID productId, int quantity) {
|
||||||
|
simulateBusyConnection();
|
||||||
|
checkInventory(productId, quantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void simulateBusyConnection() {
|
||||||
|
try {
|
||||||
|
Thread.sleep(2000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.baeldung.spring.cloud.aws.sqs.acknowledgement.service;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.baeldung.spring.cloud.aws.sqs.acknowledgement.model.OrderStatus;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class OrderService {
|
||||||
|
|
||||||
|
Map<UUID, OrderStatus> ORDER_STATUS_STORAGE = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public void updateOrderStatus(UUID orderId, OrderStatus status) {
|
||||||
|
ORDER_STATUS_STORAGE.put(orderId, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public OrderStatus getOrderStatus(UUID orderId) {
|
||||||
|
return ORDER_STATUS_STORAGE.getOrDefault(orderId, OrderStatus.UNKNOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package com.baeldung.spring.cloud.aws.sqs;
|
package com.baeldung.spring.cloud.aws.sqs.introduction;
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package com.baeldung.spring.cloud.aws.sqs;
|
package com.baeldung.spring.cloud.aws.sqs.introduction;
|
||||||
|
|
||||||
public record User(String id, String name, String email) {
|
public record User(String id, String name, String email) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package com.baeldung.spring.cloud.aws.sqs;
|
package com.baeldung.spring.cloud.aws.sqs.introduction;
|
||||||
|
|
||||||
public record UserCreatedEvent(String id, String username, String email) {
|
public record UserCreatedEvent(String id, String username, String email) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package com.baeldung.spring.cloud.aws.sqs;
|
package com.baeldung.spring.cloud.aws.sqs.introduction;
|
||||||
|
|
||||||
import static io.awspring.cloud.sqs.listener.SqsHeaders.MessageSystemAttributes.SQS_APPROXIMATE_FIRST_RECEIVE_TIMESTAMP;
|
import static io.awspring.cloud.sqs.listener.SqsHeaders.MessageSystemAttributes.SQS_APPROXIMATE_FIRST_RECEIVE_TIMESTAMP;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package com.baeldung.spring.cloud.aws.sqs;
|
package com.baeldung.spring.cloud.aws.sqs.introduction;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
|
@ -1,17 +1,19 @@
|
||||||
package com.baeldung.spring.cloud.aws;
|
package com.baeldung.spring.cloud.aws.sqs.introduction;
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
|
||||||
import com.baeldung.spring.cloud.aws.sqs.EventQueuesProperties;
|
import com.baeldung.spring.cloud.aws.sqs.acknowledgement.OrderProcessingApplication;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@EnableConfigurationProperties(EventQueuesProperties.class)
|
@EnableConfigurationProperties(EventQueuesProperties.class)
|
||||||
public class SpringCloudAwsApplication {
|
public class UserServiceApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(SpringCloudAwsApplication.class, args);
|
SpringApplication app = new SpringApplication(OrderProcessingApplication.class);
|
||||||
|
app.setAdditionalProfiles("introduction");
|
||||||
|
app.run(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
events:
|
||||||
|
queues:
|
||||||
|
order-processing-retry-queue: order_processing_retry_queue
|
||||||
|
order-processing-async-queue: order_processing_async_queue
|
||||||
|
order-processing-no-retries-queue: order_processing_no_retries_queue
|
||||||
|
acknowledgment:
|
||||||
|
order-processing-no-retries-queue: ALWAYS
|
||||||
|
|
||||||
|
product:
|
||||||
|
id:
|
||||||
|
smartphone: 123e4567-e89b-12d3-a456-426614174000
|
||||||
|
wireless-headphones: 123e4567-e89b-12d3-a456-426614174001
|
||||||
|
laptop: 123e4567-e89b-12d3-a456-426614174002
|
|
@ -2,7 +2,6 @@ package com.baeldung.spring.cloud.aws.sqs;
|
||||||
|
|
||||||
import static org.testcontainers.containers.localstack.LocalStackContainer.Service.SQS;
|
import static org.testcontainers.containers.localstack.LocalStackContainer.Service.SQS;
|
||||||
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
import org.springframework.test.context.DynamicPropertyRegistry;
|
import org.springframework.test.context.DynamicPropertyRegistry;
|
||||||
import org.springframework.test.context.DynamicPropertySource;
|
import org.springframework.test.context.DynamicPropertySource;
|
||||||
import org.testcontainers.containers.localstack.LocalStackContainer;
|
import org.testcontainers.containers.localstack.LocalStackContainer;
|
||||||
|
@ -10,9 +9,8 @@ import org.testcontainers.junit.jupiter.Container;
|
||||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
import org.testcontainers.utility.DockerImageName;
|
import org.testcontainers.utility.DockerImageName;
|
||||||
|
|
||||||
@SpringBootTest
|
|
||||||
@Testcontainers
|
@Testcontainers
|
||||||
public class BaseSqsIntegrationTest {
|
public class BaseSqsLiveTest {
|
||||||
|
|
||||||
private static final String LOCAL_STACK_VERSION = "localstack/localstack:2.3.2";
|
private static final String LOCAL_STACK_VERSION = "localstack/localstack:2.3.2";
|
||||||
|
|
||||||
|
@ -26,7 +24,6 @@ public class BaseSqsIntegrationTest {
|
||||||
registry.add("spring.cloud.aws.credentials.secret-key", () -> localStack.getSecretKey());
|
registry.add("spring.cloud.aws.credentials.secret-key", () -> localStack.getSecretKey());
|
||||||
registry.add("spring.cloud.aws.sqs.endpoint", () -> localStack.getEndpointOverride(SQS)
|
registry.add("spring.cloud.aws.sqs.endpoint", () -> localStack.getEndpointOverride(SQS)
|
||||||
.toString());
|
.toString());
|
||||||
// ...other AWS services endpoints can be added here
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package com.baeldung.spring.cloud.aws.sqs.acknowledgement;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.awaitility.Awaitility;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
|
||||||
|
import com.baeldung.spring.cloud.aws.sqs.BaseSqsLiveTest;
|
||||||
|
import com.baeldung.spring.cloud.aws.sqs.acknowledgement.configuration.EventsQueuesProperties;
|
||||||
|
import com.baeldung.spring.cloud.aws.sqs.acknowledgement.configuration.ProductIdProperties;
|
||||||
|
import com.baeldung.spring.cloud.aws.sqs.acknowledgement.model.OrderCreatedEvent;
|
||||||
|
import com.baeldung.spring.cloud.aws.sqs.acknowledgement.model.OrderStatus;
|
||||||
|
import com.baeldung.spring.cloud.aws.sqs.acknowledgement.service.OrderService;
|
||||||
|
|
||||||
|
import io.awspring.cloud.sqs.listener.MessageListenerContainerRegistry;
|
||||||
|
import io.awspring.cloud.sqs.operations.SqsTemplate;
|
||||||
|
|
||||||
|
@ActiveProfiles("acknowledgement")
|
||||||
|
@SpringBootTest
|
||||||
|
class OrderProcessingApplicationLiveTest extends BaseSqsLiveTest {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(OrderProcessingApplicationLiveTest.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EventsQueuesProperties eventsQueuesProperties;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ProductIdProperties productIdProperties;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SqsTemplate sqsTemplate;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OrderService orderService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private MessageListenerContainerRegistry registry;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenOnSuccessAcknowledgementMode_whenProcessingThrows_shouldRetry() {
|
||||||
|
var orderId = UUID.randomUUID();
|
||||||
|
var queueName = eventsQueuesProperties.getOrderProcessingRetryQueue();
|
||||||
|
sqsTemplate.send(queueName, new OrderCreatedEvent(orderId, productIdProperties.getLaptop(), 10));
|
||||||
|
Awaitility.await()
|
||||||
|
.atMost(Duration.ofMinutes(1))
|
||||||
|
.until(() -> orderService.getOrderStatus(orderId)
|
||||||
|
.equals(OrderStatus.PROCESSED));
|
||||||
|
assertQueueIsEmpty(queueName, "retry-order-processing-container");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenManualAcknowledgementMode_whenManuallyAcknowledge_shouldAcknowledge() {
|
||||||
|
var orderId = UUID.randomUUID();
|
||||||
|
var queueName = eventsQueuesProperties.getOrderProcessingAsyncQueue();
|
||||||
|
sqsTemplate.send(queueName, new OrderCreatedEvent(orderId, productIdProperties.getSmartphone(), 1));
|
||||||
|
Awaitility.await()
|
||||||
|
.atMost(Duration.ofMinutes(1))
|
||||||
|
.until(() -> orderService.getOrderStatus(orderId)
|
||||||
|
.equals(OrderStatus.PROCESSED));
|
||||||
|
assertQueueIsEmpty(queueName, "async-order-processing-container");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenAlwaysAcknowledgementMode_whenProcessThrows_shouldAcknowledge() {
|
||||||
|
var orderId = UUID.randomUUID();
|
||||||
|
var queueName = eventsQueuesProperties.getOrderProcessingNoRetriesQueue();
|
||||||
|
sqsTemplate.send(queueName, new OrderCreatedEvent(orderId, productIdProperties.getWirelessHeadphones(), 20));
|
||||||
|
Awaitility.await()
|
||||||
|
.atMost(Duration.ofMinutes(1))
|
||||||
|
.until(() -> orderService.getOrderStatus(orderId)
|
||||||
|
.equals(OrderStatus.RECEIVED));
|
||||||
|
assertQueueIsEmpty(queueName, "no-retries-order-processing-container");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertQueueIsEmpty(String queueName, String containerId) {
|
||||||
|
// Stop the listener so it doesn't pick the message again if it's still there
|
||||||
|
logger.info("Stopping container {}", containerId);
|
||||||
|
var container = Objects
|
||||||
|
.requireNonNull(registry.getContainerById(containerId), () -> "could not find container " + containerId);
|
||||||
|
container.stop();
|
||||||
|
// Look for messages in the queue
|
||||||
|
logger.info("Checking for messages in queue {}", queueName);
|
||||||
|
var message = sqsTemplate.receive(from -> from.queue(queueName)
|
||||||
|
// Polltimeout here must be set to a higher value than the message visibility set in the annotation
|
||||||
|
.pollTimeout(Duration.ofSeconds(5)));
|
||||||
|
assertThat(message).isEmpty();
|
||||||
|
logger.info("No messages found in queue {}", queueName);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
package com.baeldung.spring.cloud.aws.sqs;
|
package com.baeldung.spring.cloud.aws.sqs.introduction;
|
||||||
|
|
||||||
import static com.baeldung.spring.cloud.aws.sqs.UserEventListeners.EVENT_TYPE_CUSTOM_HEADER;
|
import static com.baeldung.spring.cloud.aws.sqs.introduction.UserEventListeners.EVENT_TYPE_CUSTOM_HEADER;
|
||||||
import static org.awaitility.Awaitility.await;
|
import static org.awaitility.Awaitility.await;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
@ -11,10 +11,16 @@ import org.junit.jupiter.api.Test;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
|
||||||
|
import com.baeldung.spring.cloud.aws.sqs.BaseSqsLiveTest;
|
||||||
|
|
||||||
import io.awspring.cloud.sqs.operations.SqsTemplate;
|
import io.awspring.cloud.sqs.operations.SqsTemplate;
|
||||||
|
|
||||||
public class SpringCloudAwsSQSLiveTest extends BaseSqsIntegrationTest {
|
@ActiveProfiles("introduction")
|
||||||
|
@SpringBootTest
|
||||||
|
public class SpringCloudAwsSQSLiveTest extends BaseSqsLiveTest {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(SpringCloudAwsSQSLiveTest.class);
|
private static final Logger logger = LoggerFactory.getLogger(SpringCloudAwsSQSLiveTest.class);
|
||||||
|
|
|
@ -75,7 +75,7 @@
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<start-class>com.baeldung.spring.cloud.aws.SpringCloudAwsApplication</start-class>
|
<start-class>com.baeldung.spring.cloud.aws.sqs.SpringCloudAwsApplication</start-class>
|
||||||
<spring-cloud.version>Dalston.SR4</spring-cloud.version>
|
<spring-cloud.version>Dalston.SR4</spring-cloud.version>
|
||||||
<spring-cloud>2.2.1.RELEASE</spring-cloud>
|
<spring-cloud>2.2.1.RELEASE</spring-cloud>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
|
@ -9,46 +9,24 @@
|
||||||
|
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.baeldung</groupId>
|
<groupId>com.baeldung</groupId>
|
||||||
<artifactId>parent-boot-2</artifactId>
|
<artifactId>parent-boot-3</artifactId>
|
||||||
<version>0.0.1-SNAPSHOT</version>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
<relativePath>../../../parent-boot-2</relativePath>
|
<relativePath>../../../parent-boot-3</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<dependencyManagement>
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.junit</groupId>
|
|
||||||
<artifactId>junit-bom</artifactId>
|
|
||||||
<version>${junit-jupiter.version}</version>
|
|
||||||
<type>pom</type>
|
|
||||||
<scope>import</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.cloud</groupId>
|
|
||||||
<artifactId>spring-cloud-dependencies</artifactId>
|
|
||||||
<version>${spring-cloud-dependencies.version}</version>
|
|
||||||
<type>pom</type>
|
|
||||||
<scope>import</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-dependencies</artifactId>
|
|
||||||
<version>${spring-boot.version}</version>
|
|
||||||
<type>pom</type>
|
|
||||||
<scope>import</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
</dependencyManagement>
|
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.cloud</groupId>
|
<groupId>org.springframework.cloud</groupId>
|
||||||
<artifactId>spring-cloud-starter-gateway</artifactId>
|
<artifactId>spring-cloud-starter-gateway</artifactId>
|
||||||
|
<version>${spring-cloud-starter-gateway.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<spring-cloud-dependencies.version>2021.0.3</spring-cloud-dependencies.version>
|
<spring-boot.version>3.2.2</spring-boot.version>
|
||||||
|
<junit-jupiter.version>5.10.2</junit-jupiter.version>
|
||||||
|
<spring-cloud-starter-gateway.version>4.1.1</spring-cloud-starter-gateway.version>
|
||||||
|
<start-class>com.baeldung.springcloudgateway.introduction.IntroductionGatewayApplication</start-class>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
</project>
|
</project>
|
|
@ -7,7 +7,7 @@ import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import javax.validation.constraints.NotEmpty;
|
import jakarta.validation.constraints.NotEmpty;
|
||||||
|
|
||||||
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
|
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
|
||||||
import org.springframework.http.HttpCookie;
|
import org.springframework.http.HttpCookie;
|
||||||
|
@ -29,13 +29,11 @@ public class GoldenCustomerRoutePredicateFactory extends AbstractRoutePredicateF
|
||||||
this.goldenCustomerService = goldenCustomerService;
|
this.goldenCustomerService = goldenCustomerService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> shortcutFieldOrder() {
|
public List<String> shortcutFieldOrder() {
|
||||||
return Arrays.asList("isGolden","customerIdCookie");
|
return Arrays.asList("isGolden","customerIdCookie");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Predicate<ServerWebExchange> apply(Config config) {
|
public Predicate<ServerWebExchange> apply(Config config) {
|
||||||
|
|
||||||
|
@ -94,9 +92,5 @@ public class GoldenCustomerRoutePredicateFactory extends AbstractRoutePredicateF
|
||||||
public void setCustomerIdCookie(String customerIdCookie) {
|
public void setCustomerIdCookie(String customerIdCookie) {
|
||||||
this.customerIdCookie = customerIdCookie;
|
this.customerIdCookie = customerIdCookie;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,17 @@
|
||||||
package com.baeldung.springcloudgateway.custompredicates;
|
package com.baeldung.springcloudgateway.custompredicates;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import org.json.JSONTokener;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||||
import org.springframework.boot.web.server.LocalServerPort;
|
import org.springframework.boot.test.web.server.LocalServerPort;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.RequestEntity;
|
import org.springframework.http.RequestEntity;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
@ -29,7 +26,7 @@ public class CustomPredicatesApplicationLiveTest {
|
||||||
|
|
||||||
@LocalServerPort
|
@LocalServerPort
|
||||||
String serverPort;
|
String serverPort;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private TestRestTemplate restTemplate;
|
private TestRestTemplate restTemplate;
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,6 @@ package com.baeldung.springcloudgateway.introduction;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
import com.baeldung.springcloudgateway.introduction.IntroductionGatewayApplication;
|
|
||||||
|
|
||||||
|
|
||||||
@SpringBootTest(classes = IntroductionGatewayApplication.class)
|
@SpringBootTest(classes = IntroductionGatewayApplication.class)
|
||||||
public class SpringContextTest {
|
public class SpringContextTest {
|
||||||
|
|
||||||
|
|
|
@ -77,7 +77,7 @@
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<spring-cloud.version>2020.0.3</spring-cloud.version>
|
<spring-cloud.version>2023.0.0</spring-cloud.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
</project>
|
</project>
|
|
@ -18,7 +18,7 @@ public class AccountResource {
|
||||||
|
|
||||||
Account acc = repo.findById(id).orElse(null);
|
Account acc = repo.findById(id).orElse(null);
|
||||||
if ( acc != null ) {
|
if ( acc != null ) {
|
||||||
return new ResponseEntity<Account>(acc, HttpStatus.OK);
|
return new ResponseEntity<>(acc, HttpStatus.OK);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||||
|
|
|
@ -28,10 +28,10 @@ public class SecretResource {
|
||||||
String value = env.getProperty(key);
|
String value = env.getProperty(key);
|
||||||
|
|
||||||
if ( value != null ) {
|
if ( value != null ) {
|
||||||
return new ResponseEntity<String>(value, HttpStatus.OK);
|
return new ResponseEntity<>(value, HttpStatus.OK);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return new ResponseEntity<String>("not found", HttpStatus.NOT_FOUND);
|
return new ResponseEntity<>("not found", HttpStatus.NOT_FOUND);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,7 +116,7 @@ public class KafkaConsumerConfig {
|
||||||
public ConcurrentKafkaListenerContainerFactory<String, Object> multiTypeKafkaListenerContainerFactory() {
|
public ConcurrentKafkaListenerContainerFactory<String, Object> multiTypeKafkaListenerContainerFactory() {
|
||||||
ConcurrentKafkaListenerContainerFactory<String, Object> factory = new ConcurrentKafkaListenerContainerFactory<>();
|
ConcurrentKafkaListenerContainerFactory<String, Object> factory = new ConcurrentKafkaListenerContainerFactory<>();
|
||||||
factory.setConsumerFactory(multiTypeConsumerFactory());
|
factory.setConsumerFactory(multiTypeConsumerFactory());
|
||||||
factory.setRecordMessageConverter(multiTypeConverter());
|
factory.setMessageConverter(multiTypeConverter());
|
||||||
return factory;
|
return factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
**/*.pem
|
||||||
|
**/*.crt
|
||||||
|
**/.env.*
|
||||||
|
**/target/
|
||||||
|
**/build/
|
||||||
|
**/.metadata
|
||||||
|
**/*.project
|
||||||
|
**/*.settings
|
||||||
|
**/*.classpath
|
||||||
|
**/*.factorypath
|
|
@ -0,0 +1,6 @@
|
||||||
|
## Spring Security Oauth2 Backend-for-Frontend
|
||||||
|
|
||||||
|
This module contains articles about core Spring Security Oauth2 Backend-for-Frontend
|
||||||
|
|
||||||
|
### Relevant Articles:
|
||||||
|
- [OAuth2 Backend for Frontend With Spring Cloud Gateway](https://www.baeldung.com/spring-backend-for-frontend-with-spring-cloud-gateway)
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Editor configuration, see https://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.ts]
|
||||||
|
quote_type = single
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
max_line_length = off
|
||||||
|
trim_trailing_whitespace = false
|
|
@ -0,0 +1,42 @@
|
||||||
|
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# Compiled output
|
||||||
|
/dist
|
||||||
|
/tmp
|
||||||
|
/out-tsc
|
||||||
|
/bazel-out
|
||||||
|
|
||||||
|
# Node
|
||||||
|
/node_modules
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
.idea/
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.c9/
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# Visual Studio Code
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.history/*
|
||||||
|
|
||||||
|
# Miscellaneous
|
||||||
|
/.angular/cache
|
||||||
|
.sass-cache/
|
||||||
|
/connect.lock
|
||||||
|
/coverage
|
||||||
|
/libpeerconnection.log
|
||||||
|
testem.log
|
||||||
|
/typings
|
||||||
|
|
||||||
|
# System files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
|
@ -0,0 +1,110 @@
|
||||||
|
{
|
||||||
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
|
"version": 1,
|
||||||
|
"newProjectRoot": "projects",
|
||||||
|
"projects": {
|
||||||
|
"angular-ui": {
|
||||||
|
"projectType": "application",
|
||||||
|
"schematics": {
|
||||||
|
"@schematics/angular:component": {
|
||||||
|
"style": "scss"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "",
|
||||||
|
"sourceRoot": "src",
|
||||||
|
"prefix": "app",
|
||||||
|
"architect": {
|
||||||
|
"build": {
|
||||||
|
"builder": "@angular-devkit/build-angular:application",
|
||||||
|
"options": {
|
||||||
|
"baseHref": "/angular-ui/",
|
||||||
|
"outputPath": "dist/angular-ui",
|
||||||
|
"index": "src/index.html",
|
||||||
|
"browser": "src/main.ts",
|
||||||
|
"polyfills": [
|
||||||
|
"zone.js"
|
||||||
|
],
|
||||||
|
"tsConfig": "tsconfig.app.json",
|
||||||
|
"inlineStyleLanguage": "scss",
|
||||||
|
"assets": [
|
||||||
|
"src/favicon.ico",
|
||||||
|
"src/assets"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"src/styles.scss"
|
||||||
|
],
|
||||||
|
"scripts": []
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"budgets": [
|
||||||
|
{
|
||||||
|
"type": "initial",
|
||||||
|
"maximumWarning": "500kb",
|
||||||
|
"maximumError": "1mb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "anyComponentStyle",
|
||||||
|
"maximumWarning": "2kb",
|
||||||
|
"maximumError": "4kb"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputHashing": "all"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"optimization": false,
|
||||||
|
"extractLicenses": false,
|
||||||
|
"sourceMap": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "production"
|
||||||
|
},
|
||||||
|
"serve": {
|
||||||
|
"builder": "@angular-devkit/build-angular:dev-server",
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"buildTarget": "angular-ui:build:production"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"buildTarget": "angular-ui:build:development"
|
||||||
|
},
|
||||||
|
"local": {
|
||||||
|
"buildTarget": "angular-ui:build:development",
|
||||||
|
"host": "0.0.0.0",
|
||||||
|
"port": 4201
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "development"
|
||||||
|
},
|
||||||
|
"extract-i18n": {
|
||||||
|
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||||
|
"options": {
|
||||||
|
"buildTarget": "angular-ui:build"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"builder": "@angular-devkit/build-angular:karma",
|
||||||
|
"options": {
|
||||||
|
"polyfills": [
|
||||||
|
"zone.js",
|
||||||
|
"zone.js/testing"
|
||||||
|
],
|
||||||
|
"tsConfig": "tsconfig.spec.json",
|
||||||
|
"inlineStyleLanguage": "scss",
|
||||||
|
"assets": [
|
||||||
|
"src/favicon.ico",
|
||||||
|
"src/assets"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"src/styles.scss"
|
||||||
|
],
|
||||||
|
"scripts": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cli": {
|
||||||
|
"analytics": "23e97199-7e93-4604-8730-91fe13971aa4"
|
||||||
|
}
|
||||||
|
}
|
12944
spring-security-modules/spring-security-oauth2-bff/angular-ui/package-lock.json
generated
Normal file
12944
spring-security-modules/spring-security-oauth2-bff/angular-ui/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"name": "angular-ui",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"ng": "ng",
|
||||||
|
"start": "ng serve -c local",
|
||||||
|
"build": "ng build",
|
||||||
|
"watch": "ng build --watch --configuration development",
|
||||||
|
"test": "ng test"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@angular/animations": "^17.0.0",
|
||||||
|
"@angular/common": "^17.0.0",
|
||||||
|
"@angular/compiler": "^17.0.0",
|
||||||
|
"@angular/core": "^17.0.0",
|
||||||
|
"@angular/forms": "^17.0.0",
|
||||||
|
"@angular/platform-browser": "^17.0.0",
|
||||||
|
"@angular/platform-browser-dynamic": "^17.0.0",
|
||||||
|
"@angular/router": "^17.0.0",
|
||||||
|
"rxjs": "~7.8.0",
|
||||||
|
"tslib": "^2.3.0",
|
||||||
|
"zone.js": "~0.14.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@angular-devkit/build-angular": "^17.0.10",
|
||||||
|
"@angular/cli": "^17.0.10",
|
||||||
|
"@angular/compiler-cli": "^17.0.0",
|
||||||
|
"@types/jasmine": "~5.1.0",
|
||||||
|
"jasmine-core": "~5.1.0",
|
||||||
|
"karma": "~6.4.0",
|
||||||
|
"karma-chrome-launcher": "~3.2.0",
|
||||||
|
"karma-coverage": "~2.2.0",
|
||||||
|
"karma-jasmine": "~5.1.0",
|
||||||
|
"karma-jasmine-html-reporter": "~2.1.0",
|
||||||
|
"typescript": "~5.2.2"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { NavigationComponent } from './navigation.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-about',
|
||||||
|
standalone: true,
|
||||||
|
imports: [NavigationComponent],
|
||||||
|
template: `<app-navigation
|
||||||
|
[destination]="['']"
|
||||||
|
label="HOME"
|
||||||
|
></app-navigation>
|
||||||
|
<p>
|
||||||
|
This application is a show-case for an Angular app consuming a REST API
|
||||||
|
through an OAuth2 BFF.
|
||||||
|
</p>`,
|
||||||
|
styles: ``,
|
||||||
|
})
|
||||||
|
export class AboutView {}
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { RouterOutlet } from '@angular/router';
|
||||||
|
import { AuthenticationComponent } from './auth/authentication.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-root',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
RouterOutlet,
|
||||||
|
HttpClientModule,
|
||||||
|
AuthenticationComponent,
|
||||||
|
],
|
||||||
|
template: `<div style="display: flex;">
|
||||||
|
<div style="margin: auto;"></div>
|
||||||
|
<h1>Angular UI</h1>
|
||||||
|
<div style="margin: auto;"></div>
|
||||||
|
<app-authentication style="margin: auto 1em;"></app-authentication>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</div>`,
|
||||||
|
styles: [],
|
||||||
|
})
|
||||||
|
export class AppComponent {}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { ApplicationConfig } from '@angular/core';
|
||||||
|
import { provideRouter } from '@angular/router';
|
||||||
|
|
||||||
|
import { provideHttpClient } from '@angular/common/http';
|
||||||
|
import { routes } from './app.routes';
|
||||||
|
|
||||||
|
export const appConfig: ApplicationConfig = {
|
||||||
|
providers: [provideRouter(routes), provideHttpClient()],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const reverseProxyUri = 'http://localhost:7080';
|
||||||
|
export const baseUri = `${reverseProxyUri}/angular-ui/`;
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { Routes } from '@angular/router';
|
||||||
|
import { AboutView } from './about.view';
|
||||||
|
import { HomeView } from './home.view';
|
||||||
|
|
||||||
|
export const routes: Routes = [
|
||||||
|
{ path: '', component: HomeView },
|
||||||
|
{ path: 'about', component: AboutView },
|
||||||
|
{ path: '**', redirectTo: '/' },
|
||||||
|
];
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
|
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { baseUri, reverseProxyUri } from '../app.config';
|
||||||
|
import { UserService } from './user.service';
|
||||||
|
import { LoginComponent } from './login.component';
|
||||||
|
import { LogoutComponent } from './logout.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-authentication',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, ReactiveFormsModule, LoginComponent, LogoutComponent],
|
||||||
|
template: `<span>
|
||||||
|
<app-login *ngIf="!isAuthenticated"></app-login>
|
||||||
|
<app-logout *ngIf="isAuthenticated"></app-logout>
|
||||||
|
</span>`,
|
||||||
|
styles: ``,
|
||||||
|
})
|
||||||
|
export class AuthenticationComponent {
|
||||||
|
constructor(private user: UserService) {}
|
||||||
|
|
||||||
|
get isAuthenticated(): boolean {
|
||||||
|
return this.user.current.isAuthenticated;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,152 @@
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
|
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
|
||||||
|
import { Observable, map } from 'rxjs';
|
||||||
|
import { UserService } from './user.service';
|
||||||
|
import { baseUri } from '../app.config';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
|
enum LoginExperience {
|
||||||
|
IFRAME,
|
||||||
|
DEFAULT,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LoginOptionDto {
|
||||||
|
label: string;
|
||||||
|
loginUri: string;
|
||||||
|
isSameAuthority: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loginOptions(http: HttpClient): Observable<Array<LoginOptionDto>> {
|
||||||
|
return http
|
||||||
|
.get('/bff/login-options')
|
||||||
|
.pipe(map((dto: any) => dto as LoginOptionDto[]));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-login',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, ReactiveFormsModule],
|
||||||
|
template: `<span>
|
||||||
|
<select *ngIf="loginExperiences.length > 1" [formControl]="selectedLoginExperience">
|
||||||
|
<option *ngFor="let le of loginExperiences">
|
||||||
|
{{ loginExperienceLabel(le) }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<button (click)="login()" [disabled]="!isLoginEnabled">Login</button>
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
class="modal-overlay"
|
||||||
|
*ngIf="isLoginModalDisplayed && !isAuthenticated"
|
||||||
|
(click)="isLoginModalDisplayed = false"
|
||||||
|
>
|
||||||
|
<div class="modal">
|
||||||
|
<iframe
|
||||||
|
[src]="iframeSrc"
|
||||||
|
frameborder="0"
|
||||||
|
(load)="onIframeLoad($event)"
|
||||||
|
></iframe>
|
||||||
|
<button class="close-button" (click)="isLoginModalDisplayed = false">
|
||||||
|
Discard
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>`,
|
||||||
|
styles: `.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 600px;
|
||||||
|
border: none;
|
||||||
|
}`,
|
||||||
|
})
|
||||||
|
export class LoginComponent {
|
||||||
|
isLoginModalDisplayed = false;
|
||||||
|
iframeSrc?: SafeUrl;
|
||||||
|
loginExperiences: LoginExperience[] = [];
|
||||||
|
selectedLoginExperience = new FormControl<LoginExperience | null>(null, [
|
||||||
|
Validators.required,
|
||||||
|
]);
|
||||||
|
|
||||||
|
private loginUri?: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
http: HttpClient,
|
||||||
|
private user: UserService,
|
||||||
|
private router: Router,
|
||||||
|
private sanitizer: DomSanitizer
|
||||||
|
) {
|
||||||
|
loginOptions(http).subscribe((opts) => {
|
||||||
|
if (opts.length) {
|
||||||
|
this.loginUri = opts[0].loginUri;
|
||||||
|
if (opts[0].isSameAuthority) {
|
||||||
|
this.loginExperiences.push(LoginExperience.IFRAME);
|
||||||
|
}
|
||||||
|
this.loginExperiences.push(LoginExperience.DEFAULT);
|
||||||
|
this.selectedLoginExperience.patchValue(this.loginExperiences[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get isLoginEnabled(): boolean {
|
||||||
|
return (
|
||||||
|
this.selectedLoginExperience.valid && !this.user.current.isAuthenticated
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get isAuthenticated(): boolean {
|
||||||
|
return this.user.current.isAuthenticated;
|
||||||
|
}
|
||||||
|
|
||||||
|
login() {
|
||||||
|
if (!this.loginUri) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(this.loginUri);
|
||||||
|
url.searchParams.append(
|
||||||
|
'post_login_success_uri',
|
||||||
|
`${baseUri}${this.router.url}`
|
||||||
|
);
|
||||||
|
const loginUrl = url.toString();
|
||||||
|
|
||||||
|
if (this.selectedLoginExperience.value === LoginExperience.IFRAME) {
|
||||||
|
this.iframeSrc = this.sanitizer.bypassSecurityTrustResourceUrl(loginUrl);
|
||||||
|
this.isLoginModalDisplayed = true;
|
||||||
|
} else {
|
||||||
|
window.location.href = loginUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onIframeLoad(event: any) {
|
||||||
|
if (!!event.currentTarget.src) {
|
||||||
|
this.user.refresh();
|
||||||
|
this.isLoginModalDisplayed = !this.user.current.isAuthenticated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loginExperienceLabel(le: LoginExperience) {
|
||||||
|
return LoginExperience[le].toLowerCase()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { UserService } from './user.service';
|
||||||
|
import { lastValueFrom } from 'rxjs';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { baseUri } from '../app.config';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-logout',
|
||||||
|
standalone: true,
|
||||||
|
imports: [],
|
||||||
|
template: `
|
||||||
|
<button (click)="logout()">Logout</button>
|
||||||
|
`,
|
||||||
|
styles: ``
|
||||||
|
})
|
||||||
|
export class LogoutComponent {
|
||||||
|
|
||||||
|
constructor(private http: HttpClient, private user: UserService) {}
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
lastValueFrom(
|
||||||
|
this.http.post('/bff/logout', null, {
|
||||||
|
headers: {
|
||||||
|
'X-POST-LOGOUT-SUCCESS-URI': baseUri,
|
||||||
|
},
|
||||||
|
observe: 'response',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.then((resp) => {
|
||||||
|
const logoutUri = resp.headers.get('Location');
|
||||||
|
if (!!logoutUri) {
|
||||||
|
window.location.href = logoutUri;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.user.refresh();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { BehaviorSubject, Observable, Subscription, interval } from 'rxjs';
|
||||||
|
|
||||||
|
interface UserinfoDto {
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
roles: string[];
|
||||||
|
exp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class User {
|
||||||
|
static readonly ANONYMOUS = new User('', '', []);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
readonly name: string,
|
||||||
|
readonly email: string,
|
||||||
|
readonly roles: string[]
|
||||||
|
) {}
|
||||||
|
|
||||||
|
get isAuthenticated(): boolean {
|
||||||
|
return !!this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasAnyRole(...roles: string[]): boolean {
|
||||||
|
for (const r of roles) {
|
||||||
|
if (this.roles.includes(r)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class UserService {
|
||||||
|
private user$ = new BehaviorSubject<User>(User.ANONYMOUS);
|
||||||
|
private refreshSub?: Subscription;
|
||||||
|
|
||||||
|
constructor(private http: HttpClient) {
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh(): void {
|
||||||
|
this.refreshSub?.unsubscribe();
|
||||||
|
this.http.get('/bff/api/me').subscribe({
|
||||||
|
next: (dto: any) => {
|
||||||
|
const user = dto as UserinfoDto;
|
||||||
|
if (
|
||||||
|
user.username !== this.user$.value.name ||
|
||||||
|
user.email !== this.user$.value.email ||
|
||||||
|
(user.roles || []).toString() !== this.user$.value.roles.toString()
|
||||||
|
) {
|
||||||
|
this.user$.next(
|
||||||
|
user.username
|
||||||
|
? new User(
|
||||||
|
user.username || '',
|
||||||
|
user.email || '',
|
||||||
|
user.roles || []
|
||||||
|
)
|
||||||
|
: User.ANONYMOUS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!!user.exp) {
|
||||||
|
const now = Date.now();
|
||||||
|
const delay = (1000 * user.exp - now) * 0.8;
|
||||||
|
if (delay > 2000) {
|
||||||
|
this.refreshSub = interval(delay).subscribe(() => this.refresh());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.warn(error);
|
||||||
|
this.user$.next(User.ANONYMOUS);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get valueChanges(): Observable<User> {
|
||||||
|
return this.user$;
|
||||||
|
}
|
||||||
|
|
||||||
|
get current(): User {
|
||||||
|
return this.user$.value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
import { NavigationComponent } from './navigation.component';
|
||||||
|
import { User, UserService } from './auth/user.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-home',
|
||||||
|
standalone: true,
|
||||||
|
imports: [NavigationComponent],
|
||||||
|
template: `<app-navigation
|
||||||
|
[destination]="['about']"
|
||||||
|
label="About"
|
||||||
|
></app-navigation>
|
||||||
|
<p>{{ message }}</p>`,
|
||||||
|
styles: [],
|
||||||
|
})
|
||||||
|
export class HomeView {
|
||||||
|
message = '';
|
||||||
|
|
||||||
|
private userSubscription?: Subscription;
|
||||||
|
|
||||||
|
constructor(user: UserService) {
|
||||||
|
this.userSubscription = user.valueChanges.subscribe((u) => {
|
||||||
|
this.message = u.isAuthenticated
|
||||||
|
? `Hi ${u.name}, you are granted with ${HomeView.rolesStr(u)}.`
|
||||||
|
: 'You are not authenticated.';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static rolesStr(user: User) {
|
||||||
|
if(!user?.roles?.length) {
|
||||||
|
return '[]'
|
||||||
|
}
|
||||||
|
return `["${user.roles.join('", "')}"]`
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.userSubscription?.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-navigation',
|
||||||
|
standalone: true,
|
||||||
|
imports: [],
|
||||||
|
template: `<button (click)="navigate()">{{ label }}</button>`,
|
||||||
|
styles: ``,
|
||||||
|
})
|
||||||
|
export class NavigationComponent {
|
||||||
|
@Input()
|
||||||
|
label!: string;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
destination!: string[];
|
||||||
|
|
||||||
|
private userSubscription?: Subscription;
|
||||||
|
|
||||||
|
constructor(private router: Router) {}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.userSubscription?.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
navigate() {
|
||||||
|
this.router.navigate(this.destination);
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -0,0 +1,13 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>AngularUi</title>
|
||||||
|
<base href="/">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<app-root></app-root>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { bootstrapApplication } from '@angular/platform-browser';
|
||||||
|
import { appConfig } from './app/app.config';
|
||||||
|
import { AppComponent } from './app/app.component';
|
||||||
|
|
||||||
|
bootstrapApplication(AppComponent, appConfig)
|
||||||
|
.catch((err) => console.error(err));
|
|
@ -0,0 +1 @@
|
||||||
|
/* You can add global styles to this file, and also import other style files */
|
|
@ -0,0 +1,14 @@
|
||||||
|
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./out-tsc/app",
|
||||||
|
"types": []
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"src/main.ts"
|
||||||
|
],
|
||||||
|
"include": [
|
||||||
|
"src/**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||||
|
{
|
||||||
|
"compileOnSave": false,
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist/out-tsc",
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"noPropertyAccessFromIndexSignature": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"declaration": false,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"importHelpers": true,
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "ES2022",
|
||||||
|
"useDefineForClassFields": false,
|
||||||
|
"lib": [
|
||||||
|
"ES2022",
|
||||||
|
"dom"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"enableI18nLegacyMessageIdFormat": false,
|
||||||
|
"strictInjectionParameters": true,
|
||||||
|
"strictInputAccessModifiers": true,
|
||||||
|
"strictTemplates": true
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./out-tsc/spec",
|
||||||
|
"types": [
|
||||||
|
"jasmine"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.spec.ts",
|
||||||
|
"src/**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
HELP.md
|
||||||
|
target/
|
||||||
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
|
!**/src/main/**/target/
|
||||||
|
!**/src/test/**/target/
|
||||||
|
|
||||||
|
### STS ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
build/
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
BIN
spring-security-modules/spring-security-oauth2-bff/backend/.mvn/wrapper/maven-wrapper.jar
vendored
Normal file
BIN
spring-security-modules/spring-security-oauth2-bff/backend/.mvn/wrapper/maven-wrapper.jar
vendored
Normal file
Binary file not shown.
|
@ -0,0 +1,2 @@
|
||||||
|
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip
|
||||||
|
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<pmd>
|
||||||
|
<useProjectRuleSet>true</useProjectRuleSet>
|
||||||
|
<ruleSetFile>D:\workspaces\baeldung\tutorials\spring-security-modules\spring-security-oauth2-bff\backend\bff\.pmdruleset.xml</ruleSetFile>
|
||||||
|
<includeDerivedFiles>false</includeDerivedFiles>
|
||||||
|
<violationsAsErrors>true</violationsAsErrors>
|
||||||
|
<fullBuildEnabled>true</fullBuildEnabled>
|
||||||
|
</pmd>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ruleset xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
name="M2Eclipse PMD RuleSet"
|
||||||
|
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
|
||||||
|
<description>M2Eclipse PMD RuleSet</description>
|
||||||
|
<exclude-pattern>.*D:/workspaces/baeldung/tutorials/spring-security-modules/spring-security-oauth2-bff/backend/bff/target.*</exclude-pattern>
|
||||||
|
<exclude-pattern>.*D:/workspaces/baeldung/tutorials/spring-security-modules/spring-security-oauth2-bff/backend/bff/target/generated-sources.*</exclude-pattern>
|
||||||
|
</ruleset>
|
|
@ -0,0 +1,92 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.baeldung.bff</groupId>
|
||||||
|
<artifactId>backend-parent</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<relativePath>..</relativePath>
|
||||||
|
</parent>
|
||||||
|
<artifactId>bff</artifactId>
|
||||||
|
<name>BFF</name>
|
||||||
|
<description>Backend For Frontend for the OAuth2 BFF article</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<tutorialsproject.basedir>../../../..</tutorialsproject.basedir>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-oauth2-client</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-gateway</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.c4-soft.springaddons</groupId>
|
||||||
|
<artifactId>spring-addons-starter-oidc</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-devtools</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.projectreactor</groupId>
|
||||||
|
<artifactId>reactor-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.c4-soft.springaddons</groupId>
|
||||||
|
<artifactId>spring-addons-starter-oidc-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.graalvm.buildtools</groupId>
|
||||||
|
<artifactId>native-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<excludes>
|
||||||
|
<exclude>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
</exclude>
|
||||||
|
</excludes>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.baeldung.bff;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class BffApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(BffApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package com.baeldung.bff;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import com.c4_soft.springaddons.security.oidc.starter.properties.SpringAddonsOidcProperties;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotEmpty;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class LoginOptionsController {
|
||||||
|
private final List<LoginOptionDto> loginOptions;
|
||||||
|
|
||||||
|
public LoginOptionsController(OAuth2ClientProperties clientProps, SpringAddonsOidcProperties addonsProperties) {
|
||||||
|
final var clientAuthority = addonsProperties.getClient()
|
||||||
|
.getClientUri()
|
||||||
|
.getAuthority();
|
||||||
|
this.loginOptions = clientProps.getRegistration()
|
||||||
|
.entrySet()
|
||||||
|
.stream()
|
||||||
|
.filter(e -> "authorization_code".equals(e.getValue()
|
||||||
|
.getAuthorizationGrantType()))
|
||||||
|
.map(e -> {
|
||||||
|
final var label = e.getValue()
|
||||||
|
.getProvider();
|
||||||
|
final var loginUri = "%s/oauth2/authorization/%s".formatted(addonsProperties.getClient()
|
||||||
|
.getClientUri(), e.getKey());
|
||||||
|
final var providerId = clientProps.getRegistration()
|
||||||
|
.get(e.getKey())
|
||||||
|
.getProvider();
|
||||||
|
final var providerIssuerAuthority = URI.create(clientProps.getProvider()
|
||||||
|
.get(providerId)
|
||||||
|
.getIssuerUri())
|
||||||
|
.getAuthority();
|
||||||
|
return new LoginOptionDto(label, loginUri, Objects.equals(clientAuthority, providerIssuerAuthority));
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/login-options", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public Mono<List<LoginOptionDto>> getLoginOptions() throws URISyntaxException {
|
||||||
|
return Mono.just(this.loginOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static record LoginOptionDto(@NotEmpty String label, @NotEmpty String loginUri, boolean isSameAuthority) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
{"properties": [{
|
||||||
|
"name": "scheme",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "Scheme to use for backend services"
|
||||||
|
},{
|
||||||
|
"name": "hostname",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "Name of the host on which the backend services run"
|
||||||
|
},{
|
||||||
|
"name": "reverse-proxy-port",
|
||||||
|
"type": "java.lang.Integer",
|
||||||
|
"description": "Port used by the reverse proxy"
|
||||||
|
},{
|
||||||
|
"name": "reverse-proxy-uri",
|
||||||
|
"type": "java.net.URI",
|
||||||
|
"description": "Public URI for the reverse proxy"
|
||||||
|
},{
|
||||||
|
"name": "angular-port",
|
||||||
|
"type": "java.lang.Integer",
|
||||||
|
"description": "Port from which the Angular app is served"
|
||||||
|
},{
|
||||||
|
"name": "angular-prefix",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "Path-prefix used by the reverse proxy to route to whatever serves Angular assets"
|
||||||
|
},{
|
||||||
|
"name": "angular-uri",
|
||||||
|
"type": "java.net.URI",
|
||||||
|
"description": "Internal URI to Angular assets"
|
||||||
|
},{
|
||||||
|
"name": "vue-port",
|
||||||
|
"type": "java.lang.Integer",
|
||||||
|
"description": "Port from which the Vue app is served"
|
||||||
|
},{
|
||||||
|
"name": "vue-prefix",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "Path-prefix used by the reverse proxy to route to whatever serves Vue assets"
|
||||||
|
},{
|
||||||
|
"name": "vue-uri",
|
||||||
|
"type": "java.net.URI",
|
||||||
|
"description": "Internal URI to Vue assets"
|
||||||
|
},{
|
||||||
|
"name": "react-port",
|
||||||
|
"type": "java.lang.Integer",
|
||||||
|
"description": "Port from which the React app is served"
|
||||||
|
},{
|
||||||
|
"name": "react-prefix",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "Path-prefix used by the reverse proxy to route to whatever serves React assets"
|
||||||
|
},{
|
||||||
|
"name": "react-uri",
|
||||||
|
"type": "java.net.URI",
|
||||||
|
"description": "Internal URI to React assets"
|
||||||
|
},{
|
||||||
|
"name": "authorization-server-port",
|
||||||
|
"type": "java.lang.Integer",
|
||||||
|
"description": "Port on which the authorization server listens"
|
||||||
|
},{
|
||||||
|
"name": "authorization-server-prefix",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "Path-prefix used by the reverse proxy to route to the authorization server"
|
||||||
|
},{
|
||||||
|
"name": "authorization-server-uri",
|
||||||
|
"type": "java.net.URI",
|
||||||
|
"description": "Internal base URI for the authorization server"
|
||||||
|
},{
|
||||||
|
"name": "issuer",
|
||||||
|
"type": "java.net.URI",
|
||||||
|
"description": "Exact value of the issuer claim in tokens"
|
||||||
|
},{
|
||||||
|
"name": "client-id",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "OAuth2 client-id"
|
||||||
|
},{
|
||||||
|
"name": "client-secret",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "OAuth2 client-secret"
|
||||||
|
},{
|
||||||
|
"name": "username-claim-json-path",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "JSON path to the claim to use as user name"
|
||||||
|
},{
|
||||||
|
"name": "authorities-json-path",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "JSON path to the claim to use as Spring authorities source"
|
||||||
|
},{
|
||||||
|
"name": "audience",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "Some OpenID providers (Auth0) require the client to provide the 'audience' he's going to use access token for"
|
||||||
|
},{
|
||||||
|
"name": "bff-port",
|
||||||
|
"type": "java.lang.Integer",
|
||||||
|
"description": "Port on which the BFF listens"
|
||||||
|
},{
|
||||||
|
"name": "bff-prefix",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "Path-prefix used by the reverse proxy to route REST requests to the BFF"
|
||||||
|
},{
|
||||||
|
"name": "bff-uri",
|
||||||
|
"type": "java.net.URI",
|
||||||
|
"description": "Internal URI to the BFF"
|
||||||
|
},{
|
||||||
|
"name": "resource-server-port",
|
||||||
|
"type": "java.lang.Integer",
|
||||||
|
"description": "Port on which the stateless REST API listens"
|
||||||
|
}]}
|
|
@ -0,0 +1,165 @@
|
||||||
|
# Custom properties to ease configuration overrides
|
||||||
|
# on command-line or IDE launch configurations
|
||||||
|
scheme: http
|
||||||
|
hostname: localhost
|
||||||
|
reverse-proxy-port: 7080
|
||||||
|
reverse-proxy-uri: ${scheme}://${hostname}:${reverse-proxy-port}
|
||||||
|
authorization-server-prefix: /auth
|
||||||
|
issuer: ${reverse-proxy-uri}${authorization-server-prefix}/realms/baeldung
|
||||||
|
client-id: baeldung-confidential
|
||||||
|
client-secret: change-me
|
||||||
|
username-claim-json-path: $.preferred_username
|
||||||
|
authorities-json-path: $.realm_access.roles
|
||||||
|
bff-port: 7081
|
||||||
|
bff-prefix: /bff
|
||||||
|
resource-server-port: 7084
|
||||||
|
audience:
|
||||||
|
|
||||||
|
server:
|
||||||
|
port: ${bff-port}
|
||||||
|
ssl:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
spring:
|
||||||
|
cloud:
|
||||||
|
gateway:
|
||||||
|
routes:
|
||||||
|
- id: bff
|
||||||
|
uri: ${scheme}://${hostname}:${resource-server-port}
|
||||||
|
predicates:
|
||||||
|
- Path=/api/**
|
||||||
|
filters:
|
||||||
|
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
|
||||||
|
- TokenRelay=
|
||||||
|
- SaveSession
|
||||||
|
- StripPrefix=1
|
||||||
|
security:
|
||||||
|
oauth2:
|
||||||
|
client:
|
||||||
|
provider:
|
||||||
|
baeldung:
|
||||||
|
issuer-uri: ${issuer}
|
||||||
|
registration:
|
||||||
|
baeldung:
|
||||||
|
provider: baeldung
|
||||||
|
authorization-grant-type: authorization_code
|
||||||
|
client-id: ${client-id}
|
||||||
|
client-secret: ${client-secret}
|
||||||
|
scope: openid,profile,email,offline_access
|
||||||
|
|
||||||
|
com:
|
||||||
|
c4-soft:
|
||||||
|
springaddons:
|
||||||
|
oidc:
|
||||||
|
ops:
|
||||||
|
- iss: ${issuer}
|
||||||
|
authorities:
|
||||||
|
- path: ${authorities-json-path}
|
||||||
|
aud: ${audience}
|
||||||
|
# SecurityFilterChain with oauth2Login() (sessions and CSRF protection enabled)
|
||||||
|
client:
|
||||||
|
client-uri: ${reverse-proxy-uri}${bff-prefix}
|
||||||
|
security-matchers:
|
||||||
|
- /api/**
|
||||||
|
- /login/**
|
||||||
|
- /oauth2/**
|
||||||
|
- /logout
|
||||||
|
permit-all:
|
||||||
|
- /api/**
|
||||||
|
- /login/**
|
||||||
|
- /oauth2/**
|
||||||
|
csrf: cookie-accessible-from-js
|
||||||
|
oauth2-redirections:
|
||||||
|
rp-initiated-logout: ACCEPTED
|
||||||
|
back-channel-logout:
|
||||||
|
enabled: true
|
||||||
|
# SecurityFilterChain with oauth2ResourceServer() (sessions and CSRF protection disabled)
|
||||||
|
resourceserver:
|
||||||
|
permit-all:
|
||||||
|
- /login-options
|
||||||
|
- /error
|
||||||
|
- /v3/api-docs/**
|
||||||
|
- /swagger-ui/**
|
||||||
|
- /actuator/health/readiness
|
||||||
|
- /actuator/health/liveness
|
||||||
|
|
||||||
|
management:
|
||||||
|
endpoint:
|
||||||
|
health:
|
||||||
|
probes:
|
||||||
|
enabled: true
|
||||||
|
endpoints:
|
||||||
|
web:
|
||||||
|
exposure:
|
||||||
|
include: '*'
|
||||||
|
health:
|
||||||
|
livenessstate:
|
||||||
|
enabled: true
|
||||||
|
readinessstate:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
root: INFO
|
||||||
|
org:
|
||||||
|
springframework:
|
||||||
|
boot: INFO
|
||||||
|
security: INFO
|
||||||
|
web: INFO
|
||||||
|
|
||||||
|
---
|
||||||
|
spring:
|
||||||
|
config:
|
||||||
|
activate:
|
||||||
|
on-profile: ssl
|
||||||
|
server:
|
||||||
|
ssl:
|
||||||
|
enabled: true
|
||||||
|
scheme: https
|
||||||
|
|
||||||
|
---
|
||||||
|
spring:
|
||||||
|
config:
|
||||||
|
activate:
|
||||||
|
on-profile: cognito
|
||||||
|
issuer: https://cognito-idp.us-west-2.amazonaws.com/us-west-2_RzhmgLwjl
|
||||||
|
client-id: 12olioff63qklfe9nio746es9f
|
||||||
|
client-secret: change-me
|
||||||
|
username-claim-json-path: username
|
||||||
|
authorities-json-path: $.cognito:groups
|
||||||
|
com:
|
||||||
|
c4-soft:
|
||||||
|
springaddons:
|
||||||
|
oidc:
|
||||||
|
client:
|
||||||
|
oauth2-logout:
|
||||||
|
baeldung:
|
||||||
|
uri: https://spring-addons.auth.us-west-2.amazoncognito.com/logout
|
||||||
|
client-id-request-param: client_id
|
||||||
|
post-logout-uri-request-param: logout_uri
|
||||||
|
|
||||||
|
---
|
||||||
|
spring:
|
||||||
|
config:
|
||||||
|
activate:
|
||||||
|
on-profile: auth0
|
||||||
|
issuer: https://dev-ch4mpy.eu.auth0.com/
|
||||||
|
client-id: yWgZDRJLAksXta8BoudYfkF5kus2zv2Q
|
||||||
|
client-secret: change-me
|
||||||
|
username-claim-json-path: $['https://c4-soft.com/user']['name']
|
||||||
|
authorities-json-path: $['https://c4-soft.com/user']['roles']
|
||||||
|
audience: bff.baeldung.com
|
||||||
|
com:
|
||||||
|
c4-soft:
|
||||||
|
springaddons:
|
||||||
|
oidc:
|
||||||
|
client:
|
||||||
|
authorization-request-params:
|
||||||
|
baeldung:
|
||||||
|
- name: audience
|
||||||
|
value: ${audience}
|
||||||
|
oauth2-logout:
|
||||||
|
baeldung:
|
||||||
|
uri: ${issuer}v2/logout
|
||||||
|
client-id-request-param: client_id
|
||||||
|
post-logout-uri-request-param: returnTo
|
|
@ -0,0 +1,308 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The ASF licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Apache Maven Wrapper startup batch script, version 3.2.0
|
||||||
|
#
|
||||||
|
# Required ENV vars:
|
||||||
|
# ------------------
|
||||||
|
# JAVA_HOME - location of a JDK home dir
|
||||||
|
#
|
||||||
|
# Optional ENV vars
|
||||||
|
# -----------------
|
||||||
|
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||||
|
# e.g. to debug Maven itself, use
|
||||||
|
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||||
|
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
if [ -z "$MAVEN_SKIP_RC" ] ; then
|
||||||
|
|
||||||
|
if [ -f /usr/local/etc/mavenrc ] ; then
|
||||||
|
. /usr/local/etc/mavenrc
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f /etc/mavenrc ] ; then
|
||||||
|
. /etc/mavenrc
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "$HOME/.mavenrc" ] ; then
|
||||||
|
. "$HOME/.mavenrc"
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
# OS specific support. $var _must_ be set to either true or false.
|
||||||
|
cygwin=false;
|
||||||
|
darwin=false;
|
||||||
|
mingw=false
|
||||||
|
case "$(uname)" in
|
||||||
|
CYGWIN*) cygwin=true ;;
|
||||||
|
MINGW*) mingw=true;;
|
||||||
|
Darwin*) darwin=true
|
||||||
|
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
|
||||||
|
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
|
||||||
|
if [ -z "$JAVA_HOME" ]; then
|
||||||
|
if [ -x "/usr/libexec/java_home" ]; then
|
||||||
|
JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
|
||||||
|
else
|
||||||
|
JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -z "$JAVA_HOME" ] ; then
|
||||||
|
if [ -r /etc/gentoo-release ] ; then
|
||||||
|
JAVA_HOME=$(java-config --jre-home)
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin, ensure paths are in UNIX format before anything is touched
|
||||||
|
if $cygwin ; then
|
||||||
|
[ -n "$JAVA_HOME" ] &&
|
||||||
|
JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
|
||||||
|
[ -n "$CLASSPATH" ] &&
|
||||||
|
CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Mingw, ensure paths are in UNIX format before anything is touched
|
||||||
|
if $mingw ; then
|
||||||
|
[ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
|
||||||
|
JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$JAVA_HOME" ]; then
|
||||||
|
javaExecutable="$(which javac)"
|
||||||
|
if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
|
||||||
|
# readlink(1) is not available as standard on Solaris 10.
|
||||||
|
readLink=$(which readlink)
|
||||||
|
if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
|
||||||
|
if $darwin ; then
|
||||||
|
javaHome="$(dirname "\"$javaExecutable\"")"
|
||||||
|
javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
|
||||||
|
else
|
||||||
|
javaExecutable="$(readlink -f "\"$javaExecutable\"")"
|
||||||
|
fi
|
||||||
|
javaHome="$(dirname "\"$javaExecutable\"")"
|
||||||
|
javaHome=$(expr "$javaHome" : '\(.*\)/bin')
|
||||||
|
JAVA_HOME="$javaHome"
|
||||||
|
export JAVA_HOME
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$JAVACMD" ] ; then
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
echo "Error: JAVA_HOME is not defined correctly." >&2
|
||||||
|
echo " We cannot execute $JAVACMD" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$JAVA_HOME" ] ; then
|
||||||
|
echo "Warning: JAVA_HOME environment variable is not set."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# traverses directory structure from process work directory to filesystem root
|
||||||
|
# first directory with .mvn subdirectory is considered project base directory
|
||||||
|
find_maven_basedir() {
|
||||||
|
if [ -z "$1" ]
|
||||||
|
then
|
||||||
|
echo "Path not specified to find_maven_basedir"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
basedir="$1"
|
||||||
|
wdir="$1"
|
||||||
|
while [ "$wdir" != '/' ] ; do
|
||||||
|
if [ -d "$wdir"/.mvn ] ; then
|
||||||
|
basedir=$wdir
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
|
||||||
|
if [ -d "${wdir}" ]; then
|
||||||
|
wdir=$(cd "$wdir/.." || exit 1; pwd)
|
||||||
|
fi
|
||||||
|
# end of workaround
|
||||||
|
done
|
||||||
|
printf '%s' "$(cd "$basedir" || exit 1; pwd)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# concatenates all lines of a file
|
||||||
|
concat_lines() {
|
||||||
|
if [ -f "$1" ]; then
|
||||||
|
# Remove \r in case we run on Windows within Git Bash
|
||||||
|
# and check out the repository with auto CRLF management
|
||||||
|
# enabled. Otherwise, we may read lines that are delimited with
|
||||||
|
# \r\n and produce $'-Xarg\r' rather than -Xarg due to word
|
||||||
|
# splitting rules.
|
||||||
|
tr -s '\r\n' ' ' < "$1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
log() {
|
||||||
|
if [ "$MVNW_VERBOSE" = true ]; then
|
||||||
|
printf '%s\n' "$1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
|
||||||
|
if [ -z "$BASE_DIR" ]; then
|
||||||
|
exit 1;
|
||||||
|
fi
|
||||||
|
|
||||||
|
MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
|
||||||
|
log "$MAVEN_PROJECTBASEDIR"
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||||
|
# This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||||
|
##########################################################################################
|
||||||
|
wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
|
||||||
|
if [ -r "$wrapperJarPath" ]; then
|
||||||
|
log "Found $wrapperJarPath"
|
||||||
|
else
|
||||||
|
log "Couldn't find $wrapperJarPath, downloading it ..."
|
||||||
|
|
||||||
|
if [ -n "$MVNW_REPOURL" ]; then
|
||||||
|
wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
|
||||||
|
else
|
||||||
|
wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
|
||||||
|
fi
|
||||||
|
while IFS="=" read -r key value; do
|
||||||
|
# Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
|
||||||
|
safeValue=$(echo "$value" | tr -d '\r')
|
||||||
|
case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
|
||||||
|
esac
|
||||||
|
done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
|
||||||
|
log "Downloading from: $wrapperUrl"
|
||||||
|
|
||||||
|
if $cygwin; then
|
||||||
|
wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v wget > /dev/null; then
|
||||||
|
log "Found wget ... using wget"
|
||||||
|
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
|
||||||
|
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
||||||
|
wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
|
||||||
|
else
|
||||||
|
wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
|
||||||
|
fi
|
||||||
|
elif command -v curl > /dev/null; then
|
||||||
|
log "Found curl ... using curl"
|
||||||
|
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
|
||||||
|
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
||||||
|
curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
|
||||||
|
else
|
||||||
|
curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log "Falling back to using Java to download"
|
||||||
|
javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
|
||||||
|
javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
|
||||||
|
# For Cygwin, switch paths to Windows format before running javac
|
||||||
|
if $cygwin; then
|
||||||
|
javaSource=$(cygpath --path --windows "$javaSource")
|
||||||
|
javaClass=$(cygpath --path --windows "$javaClass")
|
||||||
|
fi
|
||||||
|
if [ -e "$javaSource" ]; then
|
||||||
|
if [ ! -e "$javaClass" ]; then
|
||||||
|
log " - Compiling MavenWrapperDownloader.java ..."
|
||||||
|
("$JAVA_HOME/bin/javac" "$javaSource")
|
||||||
|
fi
|
||||||
|
if [ -e "$javaClass" ]; then
|
||||||
|
log " - Running MavenWrapperDownloader.java ..."
|
||||||
|
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
##########################################################################################
|
||||||
|
# End of extension
|
||||||
|
##########################################################################################
|
||||||
|
|
||||||
|
# If specified, validate the SHA-256 sum of the Maven wrapper jar file
|
||||||
|
wrapperSha256Sum=""
|
||||||
|
while IFS="=" read -r key value; do
|
||||||
|
case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
|
||||||
|
esac
|
||||||
|
done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
|
||||||
|
if [ -n "$wrapperSha256Sum" ]; then
|
||||||
|
wrapperSha256Result=false
|
||||||
|
if command -v sha256sum > /dev/null; then
|
||||||
|
if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
|
||||||
|
wrapperSha256Result=true
|
||||||
|
fi
|
||||||
|
elif command -v shasum > /dev/null; then
|
||||||
|
if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
|
||||||
|
wrapperSha256Result=true
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
|
||||||
|
echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ $wrapperSha256Result = false ]; then
|
||||||
|
echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
|
||||||
|
echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
|
||||||
|
echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
|
||||||
|
|
||||||
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
|
if $cygwin; then
|
||||||
|
[ -n "$JAVA_HOME" ] &&
|
||||||
|
JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
|
||||||
|
[ -n "$CLASSPATH" ] &&
|
||||||
|
CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
|
||||||
|
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
|
||||||
|
MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Provide a "standardized" way to retrieve the CLI args that will
|
||||||
|
# work with both Windows and non-Windows executions.
|
||||||
|
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
|
||||||
|
export MAVEN_CMD_LINE_ARGS
|
||||||
|
|
||||||
|
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||||
|
|
||||||
|
# shellcheck disable=SC2086 # safe args
|
||||||
|
exec "$JAVACMD" \
|
||||||
|
$MAVEN_OPTS \
|
||||||
|
$MAVEN_DEBUG_OPTS \
|
||||||
|
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
|
||||||
|
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
|
||||||
|
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
|
|
@ -0,0 +1,205 @@
|
||||||
|
@REM ----------------------------------------------------------------------------
|
||||||
|
@REM Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
@REM or more contributor license agreements. See the NOTICE file
|
||||||
|
@REM distributed with this work for additional information
|
||||||
|
@REM regarding copyright ownership. The ASF licenses this file
|
||||||
|
@REM to you under the Apache License, Version 2.0 (the
|
||||||
|
@REM "License"); you may not use this file except in compliance
|
||||||
|
@REM with the License. You may obtain a copy of the License at
|
||||||
|
@REM
|
||||||
|
@REM https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@REM
|
||||||
|
@REM Unless required by applicable law or agreed to in writing,
|
||||||
|
@REM software distributed under the License is distributed on an
|
||||||
|
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
@REM KIND, either express or implied. See the License for the
|
||||||
|
@REM specific language governing permissions and limitations
|
||||||
|
@REM under the License.
|
||||||
|
@REM ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@REM ----------------------------------------------------------------------------
|
||||||
|
@REM Apache Maven Wrapper startup batch script, version 3.2.0
|
||||||
|
@REM
|
||||||
|
@REM Required ENV vars:
|
||||||
|
@REM JAVA_HOME - location of a JDK home dir
|
||||||
|
@REM
|
||||||
|
@REM Optional ENV vars
|
||||||
|
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
|
||||||
|
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
|
||||||
|
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||||
|
@REM e.g. to debug Maven itself, use
|
||||||
|
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||||
|
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||||
|
@REM ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
|
||||||
|
@echo off
|
||||||
|
@REM set title of command window
|
||||||
|
title %0
|
||||||
|
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
|
||||||
|
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
|
||||||
|
|
||||||
|
@REM set %HOME% to equivalent of $HOME
|
||||||
|
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
|
||||||
|
|
||||||
|
@REM Execute a user defined script before this one
|
||||||
|
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
|
||||||
|
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
|
||||||
|
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
|
||||||
|
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
|
||||||
|
:skipRcPre
|
||||||
|
|
||||||
|
@setlocal
|
||||||
|
|
||||||
|
set ERROR_CODE=0
|
||||||
|
|
||||||
|
@REM To isolate internal variables from possible post scripts, we use another setlocal
|
||||||
|
@setlocal
|
||||||
|
|
||||||
|
@REM ==== START VALIDATION ====
|
||||||
|
if not "%JAVA_HOME%" == "" goto OkJHome
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo Error: JAVA_HOME not found in your environment. >&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||||
|
echo location of your Java installation. >&2
|
||||||
|
echo.
|
||||||
|
goto error
|
||||||
|
|
||||||
|
:OkJHome
|
||||||
|
if exist "%JAVA_HOME%\bin\java.exe" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo Error: JAVA_HOME is set to an invalid directory. >&2
|
||||||
|
echo JAVA_HOME = "%JAVA_HOME%" >&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||||
|
echo location of your Java installation. >&2
|
||||||
|
echo.
|
||||||
|
goto error
|
||||||
|
|
||||||
|
@REM ==== END VALIDATION ====
|
||||||
|
|
||||||
|
:init
|
||||||
|
|
||||||
|
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
|
||||||
|
@REM Fallback to current working directory if not found.
|
||||||
|
|
||||||
|
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
|
||||||
|
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
|
||||||
|
|
||||||
|
set EXEC_DIR=%CD%
|
||||||
|
set WDIR=%EXEC_DIR%
|
||||||
|
:findBaseDir
|
||||||
|
IF EXIST "%WDIR%"\.mvn goto baseDirFound
|
||||||
|
cd ..
|
||||||
|
IF "%WDIR%"=="%CD%" goto baseDirNotFound
|
||||||
|
set WDIR=%CD%
|
||||||
|
goto findBaseDir
|
||||||
|
|
||||||
|
:baseDirFound
|
||||||
|
set MAVEN_PROJECTBASEDIR=%WDIR%
|
||||||
|
cd "%EXEC_DIR%"
|
||||||
|
goto endDetectBaseDir
|
||||||
|
|
||||||
|
:baseDirNotFound
|
||||||
|
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
|
||||||
|
cd "%EXEC_DIR%"
|
||||||
|
|
||||||
|
:endDetectBaseDir
|
||||||
|
|
||||||
|
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
|
||||||
|
|
||||||
|
@setlocal EnableExtensions EnableDelayedExpansion
|
||||||
|
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
|
||||||
|
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
|
||||||
|
|
||||||
|
:endReadAdditionalConfig
|
||||||
|
|
||||||
|
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
|
||||||
|
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
|
||||||
|
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||||
|
|
||||||
|
set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
|
||||||
|
|
||||||
|
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
|
||||||
|
IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
|
||||||
|
)
|
||||||
|
|
||||||
|
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||||
|
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||||
|
if exist %WRAPPER_JAR% (
|
||||||
|
if "%MVNW_VERBOSE%" == "true" (
|
||||||
|
echo Found %WRAPPER_JAR%
|
||||||
|
)
|
||||||
|
) else (
|
||||||
|
if not "%MVNW_REPOURL%" == "" (
|
||||||
|
SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
|
||||||
|
)
|
||||||
|
if "%MVNW_VERBOSE%" == "true" (
|
||||||
|
echo Couldn't find %WRAPPER_JAR%, downloading it ...
|
||||||
|
echo Downloading from: %WRAPPER_URL%
|
||||||
|
)
|
||||||
|
|
||||||
|
powershell -Command "&{"^
|
||||||
|
"$webclient = new-object System.Net.WebClient;"^
|
||||||
|
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
|
||||||
|
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
|
||||||
|
"}"^
|
||||||
|
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
|
||||||
|
"}"
|
||||||
|
if "%MVNW_VERBOSE%" == "true" (
|
||||||
|
echo Finished downloading %WRAPPER_JAR%
|
||||||
|
)
|
||||||
|
)
|
||||||
|
@REM End of extension
|
||||||
|
|
||||||
|
@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
|
||||||
|
SET WRAPPER_SHA_256_SUM=""
|
||||||
|
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
|
||||||
|
IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
|
||||||
|
)
|
||||||
|
IF NOT %WRAPPER_SHA_256_SUM%=="" (
|
||||||
|
powershell -Command "&{"^
|
||||||
|
"$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
|
||||||
|
"If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
|
||||||
|
" Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
|
||||||
|
" Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
|
||||||
|
" Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
|
||||||
|
" exit 1;"^
|
||||||
|
"}"^
|
||||||
|
"}"
|
||||||
|
if ERRORLEVEL 1 goto error
|
||||||
|
)
|
||||||
|
|
||||||
|
@REM Provide a "standardized" way to retrieve the CLI args that will
|
||||||
|
@REM work with both Windows and non-Windows executions.
|
||||||
|
set MAVEN_CMD_LINE_ARGS=%*
|
||||||
|
|
||||||
|
%MAVEN_JAVA_EXE% ^
|
||||||
|
%JVM_CONFIG_MAVEN_PROPS% ^
|
||||||
|
%MAVEN_OPTS% ^
|
||||||
|
%MAVEN_DEBUG_OPTS% ^
|
||||||
|
-classpath %WRAPPER_JAR% ^
|
||||||
|
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
|
||||||
|
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
|
||||||
|
if ERRORLEVEL 1 goto error
|
||||||
|
goto end
|
||||||
|
|
||||||
|
:error
|
||||||
|
set ERROR_CODE=1
|
||||||
|
|
||||||
|
:end
|
||||||
|
@endlocal & set ERROR_CODE=%ERROR_CODE%
|
||||||
|
|
||||||
|
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
|
||||||
|
@REM check for post script, once with legacy .bat ending and once with .cmd ending
|
||||||
|
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
|
||||||
|
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
|
||||||
|
:skipRcPost
|
||||||
|
|
||||||
|
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
|
||||||
|
if "%MAVEN_BATCH_PAUSE%"=="on" pause
|
||||||
|
|
||||||
|
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
|
||||||
|
|
||||||
|
cmd /C exit /B %ERROR_CODE%
|
|
@ -0,0 +1,70 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.baeldung</groupId>
|
||||||
|
<artifactId>parent-boot-3</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<relativePath>../../../parent-boot-3</relativePath>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>com.baeldung.bff</groupId>
|
||||||
|
<artifactId>backend-parent</artifactId>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
<description>Parent pom for the backend services in the OAuth2 BFF article</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>17</java.version>
|
||||||
|
<tutorialsproject.basedir>../../..</tutorialsproject.basedir>
|
||||||
|
<spring-addons.version>7.5.3</spring-addons.version>
|
||||||
|
<spring-boot.version>3.2.2</spring-boot.version>
|
||||||
|
<spring-cloud.version>2023.0.0</spring-cloud.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<modules>
|
||||||
|
<module>reverse-proxy</module>
|
||||||
|
<module>bff</module>
|
||||||
|
<module>resource-server</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-dependencies</artifactId>
|
||||||
|
<version>${spring-cloud.version}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.c4-soft.springaddons</groupId>
|
||||||
|
<artifactId>spring-addons-starter-oidc</artifactId>
|
||||||
|
<version>${spring-addons.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.c4-soft.springaddons</groupId>
|
||||||
|
<artifactId>spring-addons-starter-oidc-test</artifactId>
|
||||||
|
<version>${spring-addons.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<systemPropertyVariables>
|
||||||
|
<testEnvironment>true</testEnvironment>
|
||||||
|
</systemPropertyVariables>
|
||||||
|
<argLine>-Dspring.profiles.active=no-ssl</argLine>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<pmd>
|
||||||
|
<useProjectRuleSet>true</useProjectRuleSet>
|
||||||
|
<ruleSetFile>D:\workspaces\baeldung\tutorials\spring-security-modules\spring-security-oauth2-bff\backend\resource-server\.pmdruleset.xml</ruleSetFile>
|
||||||
|
<includeDerivedFiles>false</includeDerivedFiles>
|
||||||
|
<violationsAsErrors>true</violationsAsErrors>
|
||||||
|
<fullBuildEnabled>true</fullBuildEnabled>
|
||||||
|
</pmd>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ruleset xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
name="M2Eclipse PMD RuleSet"
|
||||||
|
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
|
||||||
|
<description>M2Eclipse PMD RuleSet</description>
|
||||||
|
<exclude-pattern>.*D:/workspaces/baeldung/tutorials/spring-security-modules/spring-security-oauth2-bff/backend/resource-server/target/generated-sources.*</exclude-pattern>
|
||||||
|
<exclude-pattern>.*D:/workspaces/baeldung/tutorials/spring-security-modules/spring-security-oauth2-bff/backend/resource-server/target.*</exclude-pattern>
|
||||||
|
</ruleset>
|
|
@ -0,0 +1,86 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.baeldung.bff</groupId>
|
||||||
|
<artifactId>backend-parent</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<relativePath>..</relativePath>
|
||||||
|
</parent>
|
||||||
|
<artifactId>resource-server</artifactId>
|
||||||
|
<name>resource-server</name>
|
||||||
|
<description>Stateless REST API for the OAuth2 BFF article</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<tutorialsproject.basedir>../../../..</tutorialsproject.basedir>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.c4-soft.springaddons</groupId>
|
||||||
|
<artifactId>spring-addons-starter-oidc</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-devtools</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.c4-soft.springaddons</groupId>
|
||||||
|
<artifactId>spring-addons-starter-oidc-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.graalvm.buildtools</groupId>
|
||||||
|
<artifactId>native-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<excludes>
|
||||||
|
<exclude>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
</exclude>
|
||||||
|
</excludes>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,55 @@
|
||||||
|
package com.baeldung.bff;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtClaimNames;
|
||||||
|
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class MeController {
|
||||||
|
|
||||||
|
@GetMapping("/me")
|
||||||
|
public UserInfoDto getMe(Authentication auth) {
|
||||||
|
if (auth instanceof JwtAuthenticationToken jwtAuth) {
|
||||||
|
final var email = (String) jwtAuth.getTokenAttributes()
|
||||||
|
.getOrDefault(StandardClaimNames.EMAIL, "");
|
||||||
|
final var roles = auth.getAuthorities()
|
||||||
|
.stream()
|
||||||
|
.map(GrantedAuthority::getAuthority)
|
||||||
|
.toList();
|
||||||
|
final var exp = Optional.ofNullable(jwtAuth.getTokenAttributes()
|
||||||
|
.get(JwtClaimNames.EXP)).map(expClaim -> {
|
||||||
|
if(expClaim instanceof Long lexp) {
|
||||||
|
return lexp;
|
||||||
|
}
|
||||||
|
if(expClaim instanceof Instant iexp) {
|
||||||
|
return iexp.getEpochSecond();
|
||||||
|
}
|
||||||
|
if(expClaim instanceof Date dexp) {
|
||||||
|
return dexp.toInstant().getEpochSecond();
|
||||||
|
}
|
||||||
|
return Long.MAX_VALUE;
|
||||||
|
}).orElse(Long.MAX_VALUE);
|
||||||
|
return new UserInfoDto(auth.getName(), email, roles, exp);
|
||||||
|
}
|
||||||
|
return UserInfoDto.ANONYMOUS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param username a unique identifier for the resource owner in the token (sub claim by default)
|
||||||
|
* @param email OpenID email claim
|
||||||
|
* @param roles Spring authorities resolved for the authentication in the security context
|
||||||
|
* @param exp seconds from 1970-01-01T00:00:00Z UTC until the specified UTC date/time when the access token expires
|
||||||
|
*/
|
||||||
|
public static record UserInfoDto(String username, String email, List<String> roles, Long exp) {
|
||||||
|
public static final UserInfoDto ANONYMOUS = new UserInfoDto("", "", List.of(), Long.MAX_VALUE);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.baeldung.bff;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class ResourceServerApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(ResourceServerApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
{"properties": [{
|
||||||
|
"name": "scheme",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "Scheme to use for backend services"
|
||||||
|
},{
|
||||||
|
"name": "hostname",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "Name of the host on which the backend services run"
|
||||||
|
},{
|
||||||
|
"name": "reverse-proxy-port",
|
||||||
|
"type": "java.lang.Integer",
|
||||||
|
"description": "Port used by the reverse proxy"
|
||||||
|
},{
|
||||||
|
"name": "angular-port",
|
||||||
|
"type": "java.lang.Integer",
|
||||||
|
"description": "Port from which the Angular app is served"
|
||||||
|
},{
|
||||||
|
"name": "reverse-proxy-uri",
|
||||||
|
"type": "java.net.URI",
|
||||||
|
"description": "Public URI for the reverse proxy"
|
||||||
|
},{
|
||||||
|
"name": "angular-prefix",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "Path-prefix used by the reverse proxy to route to whatever serves Angular assets"
|
||||||
|
},{
|
||||||
|
"name": "angular-uri",
|
||||||
|
"type": "java.net.URI",
|
||||||
|
"description": "Internal URI to Angular assets"
|
||||||
|
},{
|
||||||
|
"name": "vue-port",
|
||||||
|
"type": "java.lang.Integer",
|
||||||
|
"description": "Port from which the Vue app is served"
|
||||||
|
},{
|
||||||
|
"name": "vue-prefix",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "Path-prefix used by the reverse proxy to route to whatever serves Vue assets"
|
||||||
|
},{
|
||||||
|
"name": "vue-uri",
|
||||||
|
"type": "java.net.URI",
|
||||||
|
"description": "Internal URI to Vue assets"
|
||||||
|
},{
|
||||||
|
"name": "react-port",
|
||||||
|
"type": "java.lang.Integer",
|
||||||
|
"description": "Port from which the React app is served"
|
||||||
|
},{
|
||||||
|
"name": "react-prefix",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "Path-prefix used by the reverse proxy to route to whatever serves React assets"
|
||||||
|
},{
|
||||||
|
"name": "react-uri",
|
||||||
|
"type": "java.net.URI",
|
||||||
|
"description": "Internal URI to React assets"
|
||||||
|
},{
|
||||||
|
"name": "authorization-server-port",
|
||||||
|
"type": "java.lang.Integer",
|
||||||
|
"description": "Port on which the authorization server listens"
|
||||||
|
},{
|
||||||
|
"name": "authorization-server-prefix",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "Path-prefix used by the reverse proxy to route to the authorization server"
|
||||||
|
},{
|
||||||
|
"name": "authorization-server-uri",
|
||||||
|
"type": "java.net.URI",
|
||||||
|
"description": "Internal base URI for the authorization server"
|
||||||
|
},{
|
||||||
|
"name": "issuer",
|
||||||
|
"type": "java.net.URI",
|
||||||
|
"description": "Exact value of the issuer claim in tokens"
|
||||||
|
},{
|
||||||
|
"name": "client-id",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "OAuth2 client-id"
|
||||||
|
},{
|
||||||
|
"name": "client-secret",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "OAuth2 client-secret"
|
||||||
|
},{
|
||||||
|
"name": "username-claim-json-path",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "JSON path to the claim to use as user name"
|
||||||
|
},{
|
||||||
|
"name": "authorities-json-path",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "JSON path to the claim to use as Spring authorities source"
|
||||||
|
},{
|
||||||
|
"name": "audience",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "Some OpenID providers (Auth0) require the client to provide the 'audience' he's going to use access token for"
|
||||||
|
},{
|
||||||
|
"name": "bff-port",
|
||||||
|
"type": "java.lang.Integer",
|
||||||
|
"description": "Port on which the BFF listens"
|
||||||
|
},{
|
||||||
|
"name": "bff-prefix",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "Path-prefix used by the reverse proxy to route REST requests to the BFF"
|
||||||
|
},{
|
||||||
|
"name": "bff-uri",
|
||||||
|
"type": "java.net.URI",
|
||||||
|
"description": "Internal URI to the BFF"
|
||||||
|
},{
|
||||||
|
"name": "resource-server-port",
|
||||||
|
"type": "java.lang.Integer",
|
||||||
|
"description": "Port on which the stateless REST API listens"
|
||||||
|
}]}
|
|
@ -0,0 +1,86 @@
|
||||||
|
scheme: http
|
||||||
|
hostname: localhost
|
||||||
|
reverse-proxy-port: 7080
|
||||||
|
reverse-proxy-uri: ${scheme}://${hostname}:${reverse-proxy-port}
|
||||||
|
authorization-server-prefix: /auth
|
||||||
|
issuer: ${reverse-proxy-uri}${authorization-server-prefix}/realms/baeldung
|
||||||
|
username-claim-json-path: $.preferred_username
|
||||||
|
authorities-json-path: $.realm_access.roles
|
||||||
|
resource-server-port: 7084
|
||||||
|
audience:
|
||||||
|
|
||||||
|
server:
|
||||||
|
port: ${resource-server-port}
|
||||||
|
ssl:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
com:
|
||||||
|
c4-soft:
|
||||||
|
springaddons:
|
||||||
|
oidc:
|
||||||
|
ops:
|
||||||
|
- iss: ${issuer}
|
||||||
|
username-claim: ${username-claim-json-path}
|
||||||
|
authorities:
|
||||||
|
- path: ${authorities-json-path}
|
||||||
|
aud: ${audience}
|
||||||
|
resourceserver:
|
||||||
|
permit-all:
|
||||||
|
- /me
|
||||||
|
- /v3/api-docs/**
|
||||||
|
- /swagger-ui/**
|
||||||
|
- /actuator/health/readiness
|
||||||
|
- /actuator/health/liveness
|
||||||
|
|
||||||
|
management:
|
||||||
|
endpoint:
|
||||||
|
health:
|
||||||
|
probes:
|
||||||
|
enabled: true
|
||||||
|
endpoints:
|
||||||
|
web:
|
||||||
|
exposure:
|
||||||
|
include: '*'
|
||||||
|
health:
|
||||||
|
livenessstate:
|
||||||
|
enabled: true
|
||||||
|
readinessstate:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
root: INFO
|
||||||
|
org:
|
||||||
|
springframework:
|
||||||
|
boot: INFO
|
||||||
|
security: INFO
|
||||||
|
web: INFO
|
||||||
|
|
||||||
|
---
|
||||||
|
spring:
|
||||||
|
config:
|
||||||
|
activate:
|
||||||
|
on-profile: ssl
|
||||||
|
server:
|
||||||
|
ssl:
|
||||||
|
enabled: true
|
||||||
|
scheme: https
|
||||||
|
|
||||||
|
---
|
||||||
|
spring:
|
||||||
|
config:
|
||||||
|
activate:
|
||||||
|
on-profile: cognito
|
||||||
|
issuer: https://cognito-idp.us-west-2.amazonaws.com/us-west-2_RzhmgLwjl
|
||||||
|
username-claim-json-path: username
|
||||||
|
authorities-json-path: $.cognito:groups
|
||||||
|
|
||||||
|
---
|
||||||
|
spring:
|
||||||
|
config:
|
||||||
|
activate:
|
||||||
|
on-profile: auth0
|
||||||
|
issuer: https://dev-ch4mpy.eu.auth0.com/
|
||||||
|
username-claim-json-path: $['https://c4-soft.com/user']['name']
|
||||||
|
authorities-json-path: $['https://c4-soft.com/user']['roles']
|
||||||
|
audience: bff.baeldung.com
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<pmd>
|
||||||
|
<useProjectRuleSet>true</useProjectRuleSet>
|
||||||
|
<ruleSetFile>D:\workspaces\baeldung\tutorials\spring-security-modules\spring-security-oauth2-bff\backend\reverse-proxy\.pmdruleset.xml</ruleSetFile>
|
||||||
|
<includeDerivedFiles>false</includeDerivedFiles>
|
||||||
|
<violationsAsErrors>true</violationsAsErrors>
|
||||||
|
<fullBuildEnabled>true</fullBuildEnabled>
|
||||||
|
</pmd>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ruleset xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
name="M2Eclipse PMD RuleSet"
|
||||||
|
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
|
||||||
|
<description>M2Eclipse PMD RuleSet</description>
|
||||||
|
<exclude-pattern>.*D:/workspaces/baeldung/tutorials/spring-security-modules/spring-security-oauth2-bff/backend/reverse-proxy/target.*</exclude-pattern>
|
||||||
|
<exclude-pattern>.*D:/workspaces/baeldung/tutorials/spring-security-modules/spring-security-oauth2-bff/backend/reverse-proxy/target/generated-sources.*</exclude-pattern>
|
||||||
|
</ruleset>
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.baeldung.bff</groupId>
|
||||||
|
<artifactId>backend-parent</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<relativePath>..</relativePath>
|
||||||
|
</parent>
|
||||||
|
<artifactId>reverse-proxy</artifactId>
|
||||||
|
<name>reverse-proxy</name>
|
||||||
|
<description>Reverse proxy for the OAuth2 BFF article</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<tutorialsproject.basedir>../../../..</tutorialsproject.basedir>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-gateway</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-devtools</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.projectreactor</groupId>
|
||||||
|
<artifactId>reactor-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.graalvm.buildtools</groupId>
|
||||||
|
<artifactId>native-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<excludes>
|
||||||
|
<exclude>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
</exclude>
|
||||||
|
</excludes>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.baeldung.bff;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class ReverseProxyApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(ReverseProxyApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
{"properties": [{
|
||||||
|
"name": "scheme",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "Scheme to use for backend services"
|
||||||
|
},{
|
||||||
|
"name": "hostname",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "Name of the host on which the backend services run"
|
||||||
|
},{
|
||||||
|
"name": "reverse-proxy-port",
|
||||||
|
"type": "java.lang.Integer",
|
||||||
|
"description": "Port used by the reverse proxy"
|
||||||
|
},{
|
||||||
|
"name": "angular-port",
|
||||||
|
"type": "java.lang.Integer",
|
||||||
|
"description": "Port from which the Angular app is served"
|
||||||
|
},{
|
||||||
|
"name": "reverse-proxy-uri",
|
||||||
|
"type": "java.net.URI",
|
||||||
|
"description": "Public URI for the reverse proxy"
|
||||||
|
},{
|
||||||
|
"name": "angular-prefix",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "Path-prefix used by the reverse proxy to route to whatever serves Angular assets"
|
||||||
|
},{
|
||||||
|
"name": "angular-uri",
|
||||||
|
"type": "java.net.URI",
|
||||||
|
"description": "Internal URI to Angular assets"
|
||||||
|
},{
|
||||||
|
"name": "vue-port",
|
||||||
|
"type": "java.lang.Integer",
|
||||||
|
"description": "Port from which the Vue app is served"
|
||||||
|
},{
|
||||||
|
"name": "vue-prefix",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "Path-prefix used by the reverse proxy to route to whatever serves Vue assets"
|
||||||
|
},{
|
||||||
|
"name": "vue-uri",
|
||||||
|
"type": "java.net.URI",
|
||||||
|
"description": "Internal URI to Vue assets"
|
||||||
|
},{
|
||||||
|
"name": "react-port",
|
||||||
|
"type": "java.lang.Integer",
|
||||||
|
"description": "Port from which the React app is served"
|
||||||
|
},{
|
||||||
|
"name": "react-prefix",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "Path-prefix used by the reverse proxy to route to whatever serves React assets"
|
||||||
|
},{
|
||||||
|
"name": "react-uri",
|
||||||
|
"type": "java.net.URI",
|
||||||
|
"description": "Internal URI to React assets"
|
||||||
|
},{
|
||||||
|
"name": "authorization-server-port",
|
||||||
|
"type": "java.lang.Integer",
|
||||||
|
"description": "Port on which the authorization server listens"
|
||||||
|
},{
|
||||||
|
"name": "authorization-server-prefix",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "Path-prefix used by the reverse proxy to route to the authorization server"
|
||||||
|
},{
|
||||||
|
"name": "authorization-server-uri",
|
||||||
|
"type": "java.net.URI",
|
||||||
|
"description": "Internal base URI for the authorization server"
|
||||||
|
},{
|
||||||
|
"name": "issuer",
|
||||||
|
"type": "java.net.URI",
|
||||||
|
"description": "Exact value of the issuer claim in tokens"
|
||||||
|
},{
|
||||||
|
"name": "client-id",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "OAuth2 client-id"
|
||||||
|
},{
|
||||||
|
"name": "client-secret",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "OAuth2 client-secret"
|
||||||
|
},{
|
||||||
|
"name": "username-claim-json-path",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "JSON path to the claim to use as user name"
|
||||||
|
},{
|
||||||
|
"name": "authorities-json-path",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "JSON path to the claim to use as Spring authorities source"
|
||||||
|
},{
|
||||||
|
"name": "audience",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "Some OpenID providers (Auth0) require the client to provide the 'audience' he's going to use access token for"
|
||||||
|
},{
|
||||||
|
"name": "bff-port",
|
||||||
|
"type": "java.lang.Integer",
|
||||||
|
"description": "Port on which the BFF listens"
|
||||||
|
},{
|
||||||
|
"name": "bff-prefix",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "Path-prefix used by the reverse proxy to route REST requests to the BFF"
|
||||||
|
},{
|
||||||
|
"name": "bff-uri",
|
||||||
|
"type": "java.net.URI",
|
||||||
|
"description": "Internal URI to the BFF"
|
||||||
|
},{
|
||||||
|
"name": "resource-server-port",
|
||||||
|
"type": "java.lang.Integer",
|
||||||
|
"description": "Port on which the stateless REST API listens"
|
||||||
|
}]}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue