Merge branch 'eugenp:master' into master

This commit is contained in:
Wynn Teo 2024-02-26 20:23:09 +08:00 committed by GitHub
commit ab2cf04104
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
188 changed files with 27913 additions and 201 deletions

View File

@ -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)

View File

@ -45,7 +45,6 @@
<properties> <properties>
<maven.compiler.source.version>11</maven.compiler.source.version> <maven.compiler.source.version>11</maven.compiler.source.version>
<maven.compiler.target.version>11</maven.compiler.target.version> <maven.compiler.target.version>11</maven.compiler.target.version>
<jackson.version>2.16.0</jackson.version>
<gson.version>2.10.1</gson.version> <gson.version>2.10.1</gson.version>
</properties> </properties>

View File

@ -60,7 +60,6 @@
<properties> <properties>
<jmh.version>1.21</jmh.version> <jmh.version>1.21</jmh.version>
<gson.version>2.10.1</gson.version> <gson.version>2.10.1</gson.version>
<jackson.version>2.16.0</jackson.version>
<org.json.version>20230618</org.json.version> <org.json.version>20230618</org.json.version>
</properties> </properties>
</project> </project>

View File

@ -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>

View File

@ -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();
}
}

View File

@ -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);

View File

@ -95,10 +95,10 @@ dependencies {
[group: 'org.springframework', name: 'spring-core', version: '4.3.5.RELEASE'], [group: 'org.springframework', name: 'spring-core', version: '4.3.5.RELEASE'],
[group: 'org.springframework', name: 'spring-aop', version: '4.3.5.RELEASE'] [group: 'org.springframework', name: 'spring-aop', version: '4.3.5.RELEASE']
) )
testImplementation('org.hibernate:hibernate-core:5.2.12.Final') { testImplementation('org.hibernate.orm:hibernate-core:6.4.2.Final') {
transitive = true transitive = true
} }
runtimeOnly(group: 'org.hibernate', name: 'hibernate-core', version: '5.2.12.Final') { runtimeOnly(group: 'org.hibernate.orm', name: 'hibernate-core', version: '6.4.2.Final') {
transitive = false transitive = false
} }
runtimeOnly "org.codehaus.groovy:groovy-all:2.4.11@jar" runtimeOnly "org.codehaus.groovy:groovy-all:2.4.11@jar"

View File

@ -36,7 +36,6 @@
</build> </build>
<properties> <properties>
<jackson.version>2.16.0</jackson.version>
<reflections.version>0.9.11</reflections.version> <reflections.version>0.9.11</reflections.version>
</properties> </properties>

View File

@ -30,9 +30,5 @@
</resource> </resource>
</resources> </resources>
</build> </build>
<properties>
<jackson.version>2.16.0</jackson.version>
</properties>
</project> </project>

View File

@ -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>

View File

@ -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>

View File

@ -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()

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -80,6 +80,7 @@
<!-- no longer available or maintained. Ref: https://spring.io/projects/spring-data-gemfire#overview --> <!-- no longer available or maintained. Ref: https://spring.io/projects/spring-data-gemfire#overview -->
<module>spring-data-geode</module> <module>spring-data-geode</module>
<module>spring-data-jpa-annotations</module> <module>spring-data-jpa-annotations</module>
<module>spring-data-jpa-annotations-2</module>
<module>spring-data-jpa-crud</module> <module>spring-data-jpa-crud</module>
<module>spring-data-jpa-crud-2</module> <module>spring-data-jpa-crud-2</module>
<module>spring-data-jpa-enterprise</module> <module>spring-data-jpa-enterprise</module>
@ -108,7 +109,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>

View File

@ -2,4 +2,5 @@
- [Patterns for Iterating Over Large Result Sets With Spring Data JPA](https://www.baeldung.com/spring-data-jpa-iterate-large-result-sets) - [Patterns for Iterating Over Large Result Sets With Spring Data JPA](https://www.baeldung.com/spring-data-jpa-iterate-large-result-sets)
- [Count the Number of Rows in Spring Data JPA](https://www.baeldung.com/spring-data-jpa-row-count) - [Count the Number of Rows in Spring Data JPA](https://www.baeldung.com/spring-data-jpa-row-count)
- [A Guide to Spring AbstractRoutingDatasource](https://www.baeldung.com/spring-abstract-routing-data-source) - [A Guide to Spring AbstractRoutingDatasource](https://www.baeldung.com/spring-abstract-routing-data-source)
- [How To Use findBy() With Multiple Columns in JPA](https://www.baeldung.com/spring-data-jpa-findby-multiple-columns)
- More articles: [[<-- prev]](../spring-boot-persistence-2) - More articles: [[<-- prev]](../spring-boot-persistence-2)

View File

@ -55,7 +55,6 @@
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId> <artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.modelmapper</groupId> <groupId>org.modelmapper</groupId>
@ -91,7 +90,6 @@
<maven.compiler.target>17</maven.compiler.target> <maven.compiler.target>17</maven.compiler.target>
<db.util.version>1.0.7</db.util.version> <db.util.version>1.0.7</db.util.version>
<hypersistence-utils.version>3.7.0</hypersistence-utils.version> <hypersistence-utils.version>3.7.0</hypersistence-utils.version>
<jackson.version>2.16.0</jackson.version>
<modelmapper.version>3.2.0</modelmapper.version> <modelmapper.version>3.2.0</modelmapper.version>
<commons-codec.version>1.16.1</commons-codec.version> <commons-codec.version>1.16.1</commons-codec.version>
<lombok.version>1.18.30</lombok.version> <lombok.version>1.18.30</lombok.version>

View File

@ -60,7 +60,6 @@
<properties> <properties>
<spring-data-elasticsearch.version>5.1.2</spring-data-elasticsearch.version> <spring-data-elasticsearch.version>5.1.2</spring-data-elasticsearch.version>
<elasticsearch.version>8.9.0</elasticsearch.version> <elasticsearch.version>8.9.0</elasticsearch.version>
<jackson.version>2.16.0</jackson.version>
</properties> </properties>
</project> </project>

View File

@ -0,0 +1,34 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-data-jpa-annotations-2</artifactId>
<name>spring-data-jpa-annotations-2</name>
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-boot-2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../parent-boot-2</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,42 @@
package com.baeldung.datajpatest;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "app_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@ -0,0 +1,12 @@
package com.baeldung.datajpatest;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class);
}
}

View File

@ -0,0 +1,8 @@
package com.baeldung.datajpatest;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}

View File

@ -0,0 +1,68 @@
package com.baeldung.datajpatest;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.After;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
@DataJpaTest
public class UserRepositoryIntegrationTest {
@Autowired
private UserRepository userRepository;
private User testUser;
@BeforeEach
public void setUp() {
// Initialize test data before each test method test
testUser = new User();
testUser.setUsername("testuser");
testUser.setPassword("password");
userRepository.save(testUser);
}
@After
public void tearDown() {
// Release test data after each test method
userRepository.delete(testUser);
}
@Test
void givenUser_whenSaved_thenCanBeFoundById() {
// Verify that the user has been saved successfully
User savedUser = userRepository.findById(testUser.getId())
.orElse(null);
assertNotNull(savedUser);
assertEquals(testUser.getUsername(), savedUser.getUsername());
assertEquals(testUser.getPassword(), savedUser.getPassword());
}
@Test
void givenUser_whenUpdated_thenCanBeFoundByIdWithUpdatedData() {
testUser.setUsername("updatedUsername");
userRepository.save(testUser);
User updatedUser = userRepository.findById(testUser.getId())
.orElse(null);
assertNotNull(updatedUser);
assertEquals("updatedUsername", updatedUser.getUsername());
}
@Test
void givenUser_whenFindByUsernameCalled_thenUserIsFound() {
User foundUser = userRepository.findByUsername("testuser");
assertNotNull(foundUser);
assertEquals("testuser", foundUser.getUsername());
}
}

10
pom.xml
View File

@ -450,6 +450,7 @@
<modules> <modules>
<module>apache-spark</module> <module>apache-spark</module>
<module>jhipster-modules</module> <module>jhipster-modules</module>
<module>spring-cloud-modules/spring-cloud-azure</module>
<module>web-modules/restx</module> <module>web-modules/restx</module>
</modules> </modules>
</profile> </profile>
@ -594,6 +595,7 @@
<module>apache-spark</module> <module>apache-spark</module>
<module>jhipster-modules</module> <module>jhipster-modules</module>
<module>spring-cloud-modules/spring-cloud-azure</module>
<module>web-modules/restx</module> <module>web-modules/restx</module>
</modules> </modules>
@ -808,7 +810,6 @@
<module>spring-batch</module> <module>spring-batch</module>
<module>spring-boot-modules</module> <module>spring-boot-modules</module>
<module>spring-boot-rest</module> <module>spring-boot-rest</module>
<module>spring-cloud-modules/spring-cloud-azure</module>
<module>spring-cloud-modules/spring-cloud-circuit-breaker</module> <module>spring-cloud-modules/spring-cloud-circuit-breaker</module>
<module>spring-cloud-modules/spring-cloud-contract</module> <module>spring-cloud-modules/spring-cloud-contract</module>
<!--<module>spring-cloud-modules/spring-cloud-data-flow</module>--><!-- failing after upgrading to jdk17--> <!--<module>spring-cloud-modules/spring-cloud-data-flow</module>--><!-- failing after upgrading to jdk17-->
@ -833,7 +834,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>
@ -1054,7 +1055,6 @@
<module>spring-batch</module> <module>spring-batch</module>
<module>spring-boot-modules</module> <module>spring-boot-modules</module>
<module>spring-boot-rest</module> <module>spring-boot-rest</module>
<module>spring-cloud-modules/spring-cloud-azure</module>
<module>spring-cloud-modules/spring-cloud-circuit-breaker</module> <module>spring-cloud-modules/spring-cloud-circuit-breaker</module>
<module>spring-cloud-modules/spring-cloud-contract</module> <module>spring-cloud-modules/spring-cloud-contract</module>
<!--<module>spring-cloud-modules/spring-cloud-data-flow</module>--><!-- failing after upgrading to jdk17--> <!--<module>spring-cloud-modules/spring-cloud-data-flow</module>--><!-- failing after upgrading to jdk17-->
@ -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>
@ -1192,7 +1192,7 @@
<jstl-api.version>1.2</jstl-api.version> <jstl-api.version>1.2</jstl-api.version>
<javax.servlet.jsp-api.version>2.3.3</javax.servlet.jsp-api.version> <javax.servlet.jsp-api.version>2.3.3</javax.servlet.jsp-api.version>
<jstl.version>1.2</jstl.version> <jstl.version>1.2</jstl.version>
<jackson.version>2.16.0</jackson.version> <jackson.version>2.16.1</jackson.version>
<commons-fileupload.version>1.5</commons-fileupload.version> <commons-fileupload.version>1.5</commons-fileupload.version>
<junit-platform.version>1.9.2</junit-platform.version> <junit-platform.version>1.9.2</junit-platform.version>
<junit-jupiter.version>5.9.2</junit-jupiter.version> <junit-jupiter.version>5.9.2</junit-jupiter.version>

View File

@ -1,3 +1,5 @@
server.port=8081 server.port=8081
logging.level.root=INFO logging.level.root=INFO
server.error.include-message=always

View File

@ -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>

View File

@ -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/

View 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

308
spring-cloud-modules/spring-cloud-aws-v3/mvnw vendored Executable file
View File

@ -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 "$@"

View File

@ -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%

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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 {
}

View File

@ -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;
}
}

View File

@ -0,0 +1,8 @@
package com.baeldung.spring.cloud.aws.sqs.acknowledgement.exception;
public class OutOfStockException extends RuntimeException {
public OutOfStockException(String errorMessage) {
super(errorMessage);
}
}

View File

@ -0,0 +1,8 @@
package com.baeldung.spring.cloud.aws.sqs.acknowledgement.exception;
public class ProductNotFoundException extends RuntimeException {
public ProductNotFoundException(String errorMessage) {
super(errorMessage);
}
}

View File

@ -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);
}
}

View File

@ -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) {
}

View File

@ -0,0 +1,15 @@
package com.baeldung.spring.cloud.aws.sqs.acknowledgement.model;
public enum OrderStatus {
RECEIVED,
PROCESSING,
PROCESSED,
ERROR,
UNKNOWN
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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) {

View File

@ -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) {

View File

@ -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;

View File

@ -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;

View File

@ -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);
} }
} }

View File

@ -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

View File

@ -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
} }
} }

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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>

View File

@ -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>

View File

@ -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;
} }
} }
} }

View File

@ -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;

View File

@ -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 {

View File

@ -8,9 +8,10 @@
<packaging>jar</packaging> <packaging>jar</packaging>
<parent> <parent>
<groupId>com.baeldung.spring.cloud</groupId> <groupId>com.baeldung</groupId>
<artifactId>spring-cloud-modules</artifactId> <artifactId>parent-boot-3</artifactId>
<version>1.0.0-SNAPSHOT</version> <version>0.0.1-SNAPSHOT</version>
<relativePath>../../parent-boot-3</relativePath>
</parent> </parent>
<dependencyManagement> <dependencyManagement>
@ -29,13 +30,6 @@
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </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> </dependencies>
</dependencyManagement> </dependencyManagement>
@ -67,8 +61,9 @@
<version>${hibernate-validator.version}</version> <version>${hibernate-validator.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>javax.validation</groupId> <groupId>jakarta.validation</groupId>
<artifactId>validation-api</artifactId> <artifactId>jakarta.validation-api</artifactId>
<version>${jakarta.validation-api.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
@ -109,6 +104,7 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>
<configuration> <configuration>
<mainClass>com.baeldung.springcloudgateway.introduction.IntroductionGatewayApplication</mainClass>
<excludes> <excludes>
<exclude> <exclude>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
@ -183,12 +179,12 @@
</profiles> </profiles>
<properties> <properties>
<!-- <spring-cloud-dependencies.version>Hoxton.SR3</spring-cloud-dependencies.version> --> <spring-cloud-dependencies.version>2023.0.0</spring-cloud-dependencies.version>
<!-- <spring-boot.version>2.2.6.RELEASE</spring-boot.version> -->
<hibernate-validator.version>8.0.1.Final</hibernate-validator.version> <hibernate-validator.version>8.0.1.Final</hibernate-validator.version>
<redis.version>0.7.2</redis.version> <redis.version>0.7.2</redis.version>
<oauth2-oidc-sdk.version>9.19</oauth2-oidc-sdk.version> <oauth2-oidc-sdk.version>9.19</oauth2-oidc-sdk.version>
<!-- <junit-bom.version>5.5.2</junit-bom.version> --> <!-- <junit-bom.version>5.5.2</junit-bom.version> -->
<jakarta.validation-api.version>3.0.2</jakarta.validation-api.version>
</properties> </properties>
</project> </project>

View File

@ -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;

View File

@ -1,6 +1,6 @@
package com.baeldung.springcloudgateway.oauth.backend.web; package com.baeldung.springcloudgateway.oauth.backend.web;
import javax.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority;

View File

@ -8,7 +8,7 @@ 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.web.server.LocalServerPort; import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.reactive.server.WebTestClient.ResponseSpec; import org.springframework.test.web.reactive.server.WebTestClient.ResponseSpec;

View File

@ -14,7 +14,7 @@ import org.springframework.boot.CommandLineRunner;
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.context.TestConfiguration; import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.web.server.LocalServerPort; import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.cloud.gateway.filter.factory.SetPathGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.SetPathGatewayFilterFactory;
import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;

View File

@ -14,7 +14,7 @@ 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;

View File

@ -13,7 +13,7 @@ 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.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.web.server.LocalServerPort; import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource; import org.springframework.test.context.DynamicPropertySource;

View File

@ -11,7 +11,7 @@ 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.context.TestConfiguration; import org.springframework.boot.test.context.TestConfiguration;
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.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
@ -51,9 +51,8 @@ public class RedisWebFilterFactoriesLiveTest {
ResponseEntity<String> r = restTemplate.getForEntity(url, String.class); ResponseEntity<String> r = restTemplate.getForEntity(url, String.class);
// assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); // assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
LOGGER.info("Received: status->{}, reason->{}, remaining->{}", LOGGER.info("Received: status->{}, remaining->{}",
r.getStatusCodeValue(), r.getStatusCode().getReasonPhrase(), r.getStatusCodeValue(), r.getHeaders().get("X-RateLimit-Remaining"));
r.getHeaders().get("X-RateLimit-Remaining"));
} }
@After @After

View File

@ -14,7 +14,7 @@ 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.HttpEntity; import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;

View File

@ -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>

View File

@ -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);

View File

@ -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);
} }
} }
} }

View File

@ -1,3 +1,5 @@
## Relevant Articles ## Relevant Articles
- [Spring Kafka Trusted Packages Feature](https://www.baeldung.com/spring-kafka-trusted-packages-feature) - [Spring Kafka Trusted Packages Feature](https://www.baeldung.com/spring-kafka-trusted-packages-feature)
- [How to Catch Deserialization Errors in Spring-Kafka?](https://www.baeldung.com/spring-kafka-deserialization-errors) - [How to Catch Deserialization Errors in Spring-Kafka?](https://www.baeldung.com/spring-kafka-deserialization-errors)
- [View Kafka Headers in Java](https://www.baeldung.com/java-kafka-view-headers)
- [Understanding Kafka InstanceAlreadyExistsException in Java](https://www.baeldung.com/kafka-instancealreadyexistsexception)

View File

@ -0,0 +1,47 @@
package com.baeldung.spring.kafka.groupId;
import java.util.HashMap;
import java.util.Map;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.annotation.EnableKafka;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.core.ConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
import org.springframework.kafka.listener.CommonErrorHandler;
@EnableKafka
@Configuration
class KafkaConfig {
@Bean
ConsumerFactory<String, String> consumerFactory(@Value("${spring.kafka.bootstrap-servers:localhost:9092}") String bootstrapServers) {
Map<String, Object> props = new HashMap<>();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
props.put(ConsumerConfig.GROUP_ID_CONFIG, "${kafka.consumer.groupId:test-consumer-group}");
props.put(ConsumerConfig.CLIENT_ID_CONFIG, "rex");
props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
return new DefaultKafkaConsumerFactory<>(props, new StringDeserializer(), new StringDeserializer());
}
@Bean
ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory(ConsumerFactory<String, String> consumerFactory,
CommonErrorHandler commonErrorHandler) {
var factory = new ConcurrentKafkaListenerContainerFactory<String, String>();
factory.setConsumerFactory(consumerFactory);
factory.setCommonErrorHandler(commonErrorHandler);
return factory;
}
@Bean
CommonErrorHandler kafkaErrorHandler() {
return new KafkaErrorHandler();
}
}

View File

@ -0,0 +1,30 @@
package com.baeldung.spring.kafka.groupId;
import org.apache.kafka.clients.consumer.Consumer;
import org.apache.kafka.common.errors.RecordDeserializationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.kafka.listener.CommonErrorHandler;
import org.springframework.kafka.listener.MessageListenerContainer;
import org.springframework.lang.NonNull;
class KafkaErrorHandler implements CommonErrorHandler {
private static final Logger log = LoggerFactory.getLogger(KafkaErrorHandler.class);
@Override
public void handleOtherException(@NonNull Exception exception, @NonNull Consumer<?, ?> consumer, @NonNull MessageListenerContainer container,
boolean batchListener) {
handle(exception, consumer);
}
private void handle(Exception exception, Consumer<?, ?> consumer) {
log.error("Exception thrown", exception);
if (exception instanceof RecordDeserializationException ex) {
consumer.seek(ex.topicPartition(), ex.offset() + 1L);
consumer.commitSync();
} else {
log.error("Exception not handled", exception);
}
}
}

View File

@ -0,0 +1,14 @@
package com.baeldung.spring.kafka.groupId;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan(basePackages = "com.baeldung.spring.kafka.groupId")
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}

View File

@ -0,0 +1,41 @@
package com.baeldung.spring.kafka.groupId;
import java.util.concurrent.CountDownLatch;
import org.apache.kafka.clients.consumer.Consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Service;
@Service
public class MyKafkaConsumer {
private static final Logger LOGGER = LoggerFactory.getLogger(MyKafkaConsumer.class);
private CountDownLatch latch = new CountDownLatch(1);
private String payload;
@KafkaListener(topics = "${kafka.topic.name:test-topic}", clientIdPrefix = "neo", groupId = "${kafka.consumer.groupId:test-consumer-group}", concurrency = "4")
public void receive(@Payload String payload, Consumer<String, String> consumer) {
LOGGER.info("Consumer='{}' received payload='{}'", consumer.groupMetadata()
.memberId(), payload);
this.payload = payload;
latch.countDown();
}
public CountDownLatch getLatch() {
return latch;
}
public void resetLatch() {
latch = new CountDownLatch(1);
}
public String getPayload() {
return payload;
}
}

View File

@ -0,0 +1,24 @@
package com.baeldung.spring.kafka.groupId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
@Service
public class MyKafkaProducer {
private static final Logger LOGGER = LoggerFactory.getLogger(MyKafkaProducer.class);
@Value("${kafka.topic.name:test-topic}")
private String topic;
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
public void send(String payload) {
LOGGER.info("Sending payload='{}' to topic='{}'", payload, topic);
kafkaTemplate.send(topic, payload);
}
}

View File

@ -0,0 +1,44 @@
package com.baeldung.spring.kafka.groupId;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.kafka.test.context.EmbeddedKafka;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles;
@SpringBootTest(classes = Main.class)
@ActiveProfiles("groupId")
@ComponentScan(basePackages = "com.baeldung.spring.kafka.groupId")
@DirtiesContext
@EmbeddedKafka(partitions = 4, topics = { "${kafka.topic.name:test-topic}" }, brokerProperties = { "listeners=PLAINTEXT://localhost:8000", "port=8000" })
public class MainLiveTest {
@Autowired
private MyKafkaConsumer consumer;
@Autowired
private MyKafkaProducer producer;
@BeforeEach
void setup() {
consumer.resetLatch();
}
@Test
public void givenEmbeddedKafkaBroker_whenSendingWithSimpleProducer_thenMessageReceived() throws Exception {
String data = "Test 123...";
producer.send(data);
boolean messageConsumed = consumer.getLatch()
.await(10, TimeUnit.SECONDS);
assertTrue(messageConsumed);
assertThat(consumer.getPayload(), containsString(data));
}
}

View File

@ -0,0 +1 @@
spring.kafka.bootstrap-servers=localhost:8000

View File

@ -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;
} }

View File

@ -10,8 +10,9 @@
<parent> <parent>
<groupId>com.baeldung</groupId> <groupId>com.baeldung</groupId>
<artifactId>spring-security-modules</artifactId> <artifactId>parent-boot-3</artifactId>
<version>0.0.1-SNAPSHOT</version> <version>0.0.1-SNAPSHOT</version>
<relativePath>../../parent-boot-3</relativePath>
</parent> </parent>
<dependencies> <dependencies>
@ -64,6 +65,11 @@
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
<version>${unboundid-ldapsdk.version}</version>
</dependency>
</dependencies> </dependencies>
<build> <build>
@ -78,6 +84,8 @@
<properties> <properties>
<apacheds.version>1.5.5</apacheds.version> <apacheds.version>1.5.5</apacheds.version>
<start-class>com.baeldung.SampleLDAPApplication</start-class>
<unboundid-ldapsdk.version>4.0.10</unboundid-ldapsdk.version>
</properties> </properties>
</project> </project>

View File

@ -3,9 +3,6 @@ package com.baeldung;
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.web.servlet.support.SpringBootServletInitializer; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
/** /**
* Main Application Class - uses Spring Boot. Just run this as a normal Java * Main Application Class - uses Spring Boot. Just run this as a normal Java

View File

@ -6,7 +6,7 @@ import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.ldap.LdapBindAuthenticationManagerFactory; import org.springframework.security.config.ldap.LdapBindAuthenticationManagerFactory;
import org.springframework.security.ldap.server.ApacheDSContainer; import org.springframework.security.ldap.server.UnboundIdContainer;
import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator; import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator; import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
@ -19,8 +19,8 @@ import org.springframework.security.web.SecurityFilterChain;
public class SecurityConfig { public class SecurityConfig {
@Bean @Bean
ApacheDSContainer ldapContainer() throws Exception { UnboundIdContainer ldapContainer() throws Exception {
return new ApacheDSContainer("dc=baeldung,dc=com", "classpath:users.ldif"); return new UnboundIdContainer("dc=baeldung,dc=com", "classpath:users.ldif");
} }
@Bean @Bean
@ -41,18 +41,13 @@ public class SecurityConfig {
@Bean @Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeRequests() return http.authorizeHttpRequests(auth -> auth
.antMatchers("/", "/home", "/css/**") .requestMatchers("/", "/home", "/css/**")
.permitAll() .permitAll()
.anyRequest() .anyRequest()
.authenticated() .authenticated())
.and() .formLogin(httpSecurityFormLoginConfigurer ->
.formLogin() httpSecurityFormLoginConfigurer.loginPage("/login").permitAll())
.loginPage("/login") .logout(logout -> logout.logoutSuccessUrl("/")).build();
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/");
return http.build();
} }
} }

View File

@ -0,0 +1,172 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed 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.
*/
package org.apache.directory.server.core.avltree;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Comparator;
import org.apache.directory.shared.ldap.util.StringTools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class to serialize the Array data.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
* @version $Rev$, $Date$
*/
@SuppressWarnings("unchecked")
public class ArrayMarshaller<E> implements Marshaller<ArrayTree<E>> {
/** static logger */
private static final Logger LOG = LoggerFactory.getLogger(ArrayMarshaller.class);
/** used for serialized form of an empty AvlTree */
private static final byte[] EMPTY_TREE = new byte[1];
/** marshaller to be used for marshalling the keys */
private Marshaller<E> keyMarshaller;
/** key Comparator for the AvlTree */
private Comparator<E> comparator;
/**
* Creates a new instance of AvlTreeMarshaller with a custom key Marshaller.
* @param comparator Comparator to be used for key comparision
* @param keyMarshaller marshaller for keys
*/
public ArrayMarshaller(Comparator<E> comparator, Marshaller<E> keyMarshaller) {
this.comparator = comparator;
this.keyMarshaller = keyMarshaller;
}
/**
* Creates a new instance of AvlTreeMarshaller with the default key Marshaller which
* uses Java Serialization.
* @param comparator Comparator to be used for key comparision
*/
public ArrayMarshaller(Comparator<E> comparator) {
this.comparator = comparator;
this.keyMarshaller = (Marshaller<E>) DefaultMarshaller.INSTANCE;
}
/**
* Marshals the given tree to bytes
* @param tree the tree to be marshalled
*/
public byte[] serialize(ArrayTree<E> tree) {
if ((tree == null) || (tree.size() == 0)) {
return EMPTY_TREE;
}
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(byteStream);
byte[] data = null;
try {
out.writeByte(0); // represents the start of an Array byte stream
out.writeInt(tree.size());
for (int position = 0; position < tree.size(); position++) {
E value = tree.get(position);
byte[] bytes = this.keyMarshaller.serialize(value);
// Write the key length
out.writeInt(bytes.length);
// Write the key if its length is not null
if (bytes.length != 0) {
out.write(bytes);
}
}
out.flush();
data = byteStream.toByteArray();
// Try to deserialize, just to see
try {
deserialize(data);
}
catch (NullPointerException npe) {
System.out.println("Bad serialization, tree : [" + StringTools.dumpBytes(data) + "]");
throw npe;
}
out.close();
}
catch (IOException ex) {
ex.printStackTrace();
}
return data;
}
/**
* Creates an Array from given bytes of data.
* @param data byte array to be converted into an array
*/
public ArrayTree<E> deserialize(byte[] data) throws IOException {
try {
if ((data == null) || (data.length == 0)) {
throw new IOException("Null or empty data array is invalid.");
}
if ((data.length == 1) && (data[0] == 0)) {
E[] array = (E[]) new Object[] {};
ArrayTree<E> tree = new ArrayTree<E>(this.comparator, array);
return tree;
}
ByteArrayInputStream bin = new ByteArrayInputStream(data);
DataInputStream din = new DataInputStream(bin);
byte startByte = din.readByte();
if (startByte != 0) {
throw new IOException("wrong array serialized data format");
}
int size = din.readInt();
E[] nodes = (E[]) new Object[size];
for (int i = 0; i < size; i++) {
// Read the object's size
int dataSize = din.readInt();
if (dataSize != 0) {
byte[] bytes = new byte[dataSize];
din.read(bytes);
E key = this.keyMarshaller.deserialize(bytes);
nodes[i] = key;
}
}
ArrayTree<E> arrayTree = new ArrayTree<E>(this.comparator, nodes);
return arrayTree;
}
catch (NullPointerException npe) {
System.out.println("Bad tree : [" + StringTools.dumpBytes(data) + "]");
throw npe;
}
}
}

View File

@ -0,0 +1,10 @@
**/*.pem
**/*.crt
**/.env.*
**/target/
**/build/
**/.metadata
**/*.project
**/*.settings
**/*.classpath
**/*.factorypath

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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"
}
}

View File

@ -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 {}

View File

@ -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 {}

View File

@ -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/`;

View File

@ -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: '/' },
];

View File

@ -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;
}
}

View File

@ -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()
}
}

View File

@ -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();
});
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

Some files were not shown because too many files have changed in this diff Show More