Merge remote-tracking branch 'upstream/master' into feature/BAEL-6695-BooleanValidation
This commit is contained in:
commit
2958b275f7
|
@ -13,3 +13,4 @@ You can build the project from the command line using: *mvn clean install*, or i
|
||||||
- [Read Data From the Beginning Using Kafka Consumer API](https://www.baeldung.com/java-kafka-consumer-api-read)
|
- [Read Data From the Beginning Using Kafka Consumer API](https://www.baeldung.com/java-kafka-consumer-api-read)
|
||||||
- [Get Partition Count for a Topic in Kafka](https://www.baeldung.com/java-kafka-partition-count-topic)
|
- [Get Partition Count for a Topic in Kafka](https://www.baeldung.com/java-kafka-partition-count-topic)
|
||||||
- [bootstrap-server in Kafka Configuration](https://www.baeldung.com/java-kafka-bootstrap-server)
|
- [bootstrap-server in Kafka Configuration](https://www.baeldung.com/java-kafka-bootstrap-server)
|
||||||
|
- [Introduction to Apache Kafka](https://www.baeldung.com/apache-kafka)
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
package com.baeldung.kafka.multipletopics;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
|
import org.apache.kafka.clients.consumer.ConsumerConfig;
|
||||||
|
import org.apache.kafka.clients.consumer.ConsumerRecord;
|
||||||
|
import org.apache.kafka.clients.consumer.KafkaConsumer;
|
||||||
|
import org.apache.kafka.clients.producer.KafkaProducer;
|
||||||
|
import org.apache.kafka.clients.producer.ProducerConfig;
|
||||||
|
import org.apache.kafka.clients.producer.ProducerRecord;
|
||||||
|
import org.apache.kafka.common.serialization.StringDeserializer;
|
||||||
|
import org.apache.kafka.common.serialization.StringSerializer;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.testcontainers.containers.KafkaContainer;
|
||||||
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
|
import org.testcontainers.utility.DockerImageName;
|
||||||
|
|
||||||
|
// This live test needs a Docker Daemon running so that a kafka container can be created
|
||||||
|
|
||||||
|
@Testcontainers
|
||||||
|
public class MultipleTopicsLiveTest {
|
||||||
|
|
||||||
|
private final Logger log = LoggerFactory.getLogger(MultipleTopicsLiveTest.class);
|
||||||
|
|
||||||
|
private static final String CARD_PAYMENTS_TOPIC = "card-payments";
|
||||||
|
private static final String BANK_TRANSFERS_TOPIC = "bank-transfers";
|
||||||
|
private static KafkaProducer<String, String> producer;
|
||||||
|
private static KafkaConsumer<String, String> consumer;
|
||||||
|
|
||||||
|
@Container
|
||||||
|
private static final KafkaContainer KAFKA_CONTAINER = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:latest"));
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void setup() {
|
||||||
|
KAFKA_CONTAINER.addExposedPort(9092);
|
||||||
|
producer = new KafkaProducer<>(getProducerProperties());
|
||||||
|
consumer = new KafkaConsumer<>(getConsumerProperties());
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void destroy() {
|
||||||
|
KAFKA_CONTAINER.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Properties getProducerProperties() {
|
||||||
|
Properties producerProperties = new Properties();
|
||||||
|
producerProperties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA_CONTAINER.getBootstrapServers());
|
||||||
|
producerProperties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
|
||||||
|
producerProperties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
|
||||||
|
return producerProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Properties getConsumerProperties() {
|
||||||
|
Properties consumerProperties = new Properties();
|
||||||
|
consumerProperties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA_CONTAINER.getBootstrapServers());
|
||||||
|
consumerProperties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
|
||||||
|
consumerProperties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
|
||||||
|
consumerProperties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
|
||||||
|
consumerProperties.put(ConsumerConfig.GROUP_ID_CONFIG, "payments");
|
||||||
|
return consumerProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenSendingMessagesOnTwoTopics_thenConsumerReceivesMessages() throws Exception {
|
||||||
|
publishMessages();
|
||||||
|
|
||||||
|
consumer.subscribe(Arrays.asList(CARD_PAYMENTS_TOPIC, BANK_TRANSFERS_TOPIC));
|
||||||
|
|
||||||
|
int eventsProcessed = 0;
|
||||||
|
for (ConsumerRecord<String, String> record : consumer.poll(Duration.ofSeconds(10))) {
|
||||||
|
log.info("Event on topic={}, payload={}", record.topic(), record.value());
|
||||||
|
eventsProcessed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(eventsProcessed).isEqualTo(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void publishMessages() throws ExecutionException, InterruptedException {
|
||||||
|
ProducerRecord<String, String> cardPayment = new ProducerRecord<>(CARD_PAYMENTS_TOPIC, createCardPayment());
|
||||||
|
producer.send(cardPayment).get();
|
||||||
|
|
||||||
|
ProducerRecord<String, String> bankTransfer = new ProducerRecord<>(BANK_TRANSFERS_TOPIC, createBankTransfer());
|
||||||
|
producer.send(bankTransfer).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String createCardPayment() {
|
||||||
|
return "{\"paymentReference\":\"A184028KM0013790\", \"type\":\"card\", \"amount\":\"275\", \"currency\":\"GBP\"}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String createBankTransfer() {
|
||||||
|
return "{\"paymentReference\":\"19ae2-18mk73-009\", \"type\":\"bank\", \"amount\":\"150\", \"currency\":\"EUR\"}";
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
## Relevant Articles
|
## Relevant Articles
|
||||||
- [Sequenced Collections in Java 21](https://www.baeldung.com/java-21-sequenced-collections)
|
- [Sequenced Collections in Java 21](https://www.baeldung.com/java-21-sequenced-collections)
|
||||||
- [String Templates in Java 21](https://www.baeldung.com/java-21-string-templates)
|
- [String Templates in Java 21](https://www.baeldung.com/java-21-string-templates)
|
||||||
|
- [Unnamed Classes and Instance Main Methods in Java 21](https://www.baeldung.com/java-21-unnamed-class-instance-main)
|
||||||
|
- [Unnamed Patterns and Variables in Java 21](https://www.baeldung.com/java-unnamed-patterns-variables)
|
||||||
|
|
|
@ -5,3 +5,4 @@ This module contains articles about Java Character Class
|
||||||
### Relevant Articles:
|
### Relevant Articles:
|
||||||
- [Character#isAlphabetic vs. Character#isLetter](https://www.baeldung.com/java-character-isletter-isalphabetic)
|
- [Character#isAlphabetic vs. Character#isLetter](https://www.baeldung.com/java-character-isletter-isalphabetic)
|
||||||
- [Difference Between Java’s “char” and “String”](https://www.baeldung.com/java-char-vs-string)
|
- [Difference Between Java’s “char” and “String”](https://www.baeldung.com/java-char-vs-string)
|
||||||
|
- [Increment Character in Java](https://www.baeldung.com/java-char-sequence)
|
||||||
|
|
|
@ -5,4 +5,6 @@
|
||||||
### Relevant Articles:
|
### Relevant Articles:
|
||||||
- [Introduction to Roaring Bitmap](https://www.baeldung.com/java-roaring-bitmap-intro)
|
- [Introduction to Roaring Bitmap](https://www.baeldung.com/java-roaring-bitmap-intro)
|
||||||
- [Creating Custom Iterator in Java](https://www.baeldung.com/java-creating-custom-iterator)
|
- [Creating Custom Iterator in Java](https://www.baeldung.com/java-creating-custom-iterator)
|
||||||
|
- [Difference Between Arrays.sort() and Collections.sort()](https://www.baeldung.com/java-arrays-collections-sort-methods)
|
||||||
|
- [Skipping the First Iteration in Java](https://www.baeldung.com/java-skip-first-iteration)
|
||||||
- More articles: [[<-- prev]](/core-java-modules/core-java-collections-4)
|
- More articles: [[<-- prev]](/core-java-modules/core-java-collections-4)
|
||||||
|
|
|
@ -3,4 +3,4 @@
|
||||||
This module contains articles about the Java ArrayList collection
|
This module contains articles about the Java ArrayList collection
|
||||||
|
|
||||||
### Relevant Articles:
|
### Relevant Articles:
|
||||||
- [Create an ArrayList with Multiple Object Types](https://www.baeldung.com/arraylist-with-multiple-object-types)
|
- [Create an ArrayList with Multiple Object Types](https://www.baeldung.com/java-arraylist-multiple-object-types)
|
||||||
|
|
|
@ -3,3 +3,4 @@
|
||||||
This module contains articles about conversions among Collection types in Java.
|
This module contains articles about conversions among Collection types in Java.
|
||||||
|
|
||||||
### Relevant Articles:
|
### Relevant Articles:
|
||||||
|
- [Converting HashMap Values to an ArrayList in Java](https://www.baeldung.com/java-hashmap-arraylist)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
## Relevant Articles
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>core-java-collections-maps-7</artifactId>
|
||||||
|
<name>core-java-collections-maps-7</name>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<artifactId>core-java-modules</artifactId>
|
||||||
|
<groupId>com.baeldung.core-java-modules</groupId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<spring.version>5.2.5.RELEASE</spring.version>
|
||||||
|
</properties>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
<version>2.12.4</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openjdk.jmh</groupId>
|
||||||
|
<artifactId>jmh-core</artifactId>
|
||||||
|
<version>1.36</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.code.gson</groupId>
|
||||||
|
<artifactId>gson</artifactId>
|
||||||
|
<version>2.8.9</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.json</groupId>
|
||||||
|
<artifactId>json</artifactId>
|
||||||
|
<version>20230227</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>4.13.1</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<version>5.8.1</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<version>5.8.1</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>4.13.1</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<version>5.8.1</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>4.13.1</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,60 @@
|
||||||
|
package com.baeldung.map;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class ConvertHashMapStringToHashMapObjectUsingtoString {
|
||||||
|
public String name;
|
||||||
|
public int age;
|
||||||
|
|
||||||
|
public ConvertHashMapStringToHashMapObjectUsingtoString(String name, int age) {
|
||||||
|
this.name = name;
|
||||||
|
this.age = age;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ConvertHashMapStringToHashMapObjectUsingtoString deserializeCustomObject(String valueString) {
|
||||||
|
if (valueString.startsWith("{") && valueString.endsWith("}")) {
|
||||||
|
valueString = valueString.substring(1, valueString.length() - 1);
|
||||||
|
String[] parts = valueString.split(",");
|
||||||
|
String name = null;
|
||||||
|
int age = -1;
|
||||||
|
for (String part : parts) {
|
||||||
|
String[] keyValue = part.split("=");
|
||||||
|
if (keyValue.length == 2) {
|
||||||
|
String key = keyValue[0].trim();
|
||||||
|
String val = keyValue[1].trim();
|
||||||
|
if (key.equals("name")) {
|
||||||
|
name = val;
|
||||||
|
} else if (key.equals("age")) {
|
||||||
|
age = Integer.parseInt(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (name != null && age >= 0) {
|
||||||
|
return new ConvertHashMapStringToHashMapObjectUsingtoString(name, age);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ConvertHashMapStringToHashMapObjectUsingtoString("", -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
String hashMapString = "{key1={name=John, age=30}, key2={name=Alice, age=25}}";
|
||||||
|
String keyValuePairs = hashMapString.replaceAll("[{}\\s]", "");
|
||||||
|
String[] pairs = keyValuePairs.split(",");
|
||||||
|
Map<String, ConvertHashMapStringToHashMapObjectUsingtoString> actualHashMap = new HashMap<>();
|
||||||
|
for (String pair : pairs) {
|
||||||
|
String[] keyValue = pair.split("=");
|
||||||
|
if (keyValue.length == 2) {
|
||||||
|
String key = keyValue[0];
|
||||||
|
ConvertHashMapStringToHashMapObjectUsingtoString value = deserializeCustomObject(keyValue[1]);
|
||||||
|
actualHashMap.put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println(actualHashMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "{name=" + name + ", age=" + age + "}";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package com.baeldung.map;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
class ConvertHashMapStringToHashMapObjectUsingtoStringUnitTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenValidCustomObject_whenSerializing_thenSerializedStringIsCorrect() {
|
||||||
|
ConvertHashMapStringToHashMapObjectUsingtoString customObject = new ConvertHashMapStringToHashMapObjectUsingtoString("John", 30);
|
||||||
|
String expectedSerializedString = "{name=John, age=30}";
|
||||||
|
assertEquals(expectedSerializedString, customObject.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenValidSerializedString_whenDeserializing_thenCustomObjectIsCorrect() {
|
||||||
|
String serializedString = "{name=Alice, age=25}";
|
||||||
|
ConvertHashMapStringToHashMapObjectUsingtoString customObject = ConvertHashMapStringToHashMapObjectUsingtoString.deserializeCustomObject(serializedString);
|
||||||
|
assertEquals("Alice", customObject.name);
|
||||||
|
assertEquals(25, customObject.age);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenInvalidSerializedString_whenDeserializing_thenDefaultCustomObjectIsCreated() {
|
||||||
|
String invalidSerializedString = "{invalidString}";
|
||||||
|
ConvertHashMapStringToHashMapObjectUsingtoString customObject = ConvertHashMapStringToHashMapObjectUsingtoString.deserializeCustomObject(invalidSerializedString);
|
||||||
|
assertEquals("", customObject.name);
|
||||||
|
assertEquals(-1, customObject.age);
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,10 +12,10 @@ public class EpochTimeToLocalDateTimeConverterUnitTest {
|
||||||
@Test
|
@Test
|
||||||
public void testConvertEpochTimeToLocalDateTime() {
|
public void testConvertEpochTimeToLocalDateTime() {
|
||||||
long epochTimeMillis = 1624962431000L; // Example epoch time in milliseconds
|
long epochTimeMillis = 1624962431000L; // Example epoch time in milliseconds
|
||||||
LocalDateTime expectedDateTime = LocalDateTime.of(2021, 6, 29, 12, 13, 51);
|
LocalDateTime expectedDateTime = LocalDateTime.of(2021, 6, 29, 10, 27, 11);
|
||||||
|
|
||||||
Instant instant = Instant.ofEpochMilli(epochTimeMillis);
|
Instant instant = Instant.ofEpochMilli(epochTimeMillis);
|
||||||
ZoneId zoneId = ZoneId.systemDefault();
|
ZoneId zoneId = ZoneId.of("UTC");
|
||||||
LocalDateTime actualDateTime = instant.atZone(zoneId).toLocalDateTime();
|
LocalDateTime actualDateTime = instant.atZone(zoneId).toLocalDateTime();
|
||||||
|
|
||||||
assertEquals(expectedDateTime, actualDateTime);
|
assertEquals(expectedDateTime, actualDateTime);
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
This module contains articles about core Java input and output (IO)
|
This module contains articles about core Java input and output (IO)
|
||||||
|
|
||||||
### Relevant Articles:
|
### Relevant Articles:
|
||||||
|
- [Get File Extension From MIME Type in Java](https://www.baeldung.com/java-mime-type-file-extension)
|
||||||
- [[<-- Prev]](/core-java-modules/core-java-io-4)
|
- [[<-- Prev]](/core-java-modules/core-java-io-4)
|
||||||
|
|
||||||
|
|
|
@ -14,5 +14,6 @@ This module contains articles about working with the operating system (OS) in Ja
|
||||||
- [How to Run a Shell Command in Java](http://www.baeldung.com/run-shell-command-in-java)
|
- [How to Run a Shell Command in Java](http://www.baeldung.com/run-shell-command-in-java)
|
||||||
- [Taking Screenshots Using Java](https://www.baeldung.com/java-taking-screenshots)
|
- [Taking Screenshots Using Java](https://www.baeldung.com/java-taking-screenshots)
|
||||||
- [Java Sound API – Capturing Microphone](https://www.baeldung.com/java-sound-api-capture-mic)
|
- [Java Sound API – Capturing Microphone](https://www.baeldung.com/java-sound-api-capture-mic)
|
||||||
|
- [How to Detect the Username Using Java](https://www.baeldung.com/java-get-username)
|
||||||
|
|
||||||
This module uses Java 9, so make sure to have the JDK 9 installed to run it.
|
This module uses Java 9, so make sure to have the JDK 9 installed to run it.
|
||||||
|
|
|
@ -9,4 +9,5 @@
|
||||||
- [Regular Expression: \z vs \Z Anchors in Java](https://www.baeldung.com/java-regular-expression-z-vs-z-anchors)
|
- [Regular Expression: \z vs \Z Anchors in Java](https://www.baeldung.com/java-regular-expression-z-vs-z-anchors)
|
||||||
- [Extract Text Between Square Brackets](https://www.baeldung.com/java-get-content-between-square-brackets)
|
- [Extract Text Between Square Brackets](https://www.baeldung.com/java-get-content-between-square-brackets)
|
||||||
- [Get the Indexes of Regex Pattern Matches in Java](https://www.baeldung.com/java-indexes-regex-pattern-matches)
|
- [Get the Indexes of Regex Pattern Matches in Java](https://www.baeldung.com/java-indexes-regex-pattern-matches)
|
||||||
|
- [Check if a String is Strictly Alphanumeric With Java](https://www.baeldung.com/java-check-string-contains-only-letters-numbers)
|
||||||
- More articles: [[<-- prev]](/core-java-modules/core-java-regex)
|
- More articles: [[<-- prev]](/core-java-modules/core-java-regex)
|
||||||
|
|
|
@ -43,6 +43,11 @@
|
||||||
<artifactId>vavr</artifactId>
|
<artifactId>vavr</artifactId>
|
||||||
<version>${vavr.version}</version>
|
<version>${vavr.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.guava</groupId>
|
||||||
|
<artifactId>guava</artifactId>
|
||||||
|
<version>${guava.version}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
@ -72,6 +77,7 @@
|
||||||
<maven.compiler.source>12</maven.compiler.source>
|
<maven.compiler.source>12</maven.compiler.source>
|
||||||
<maven.compiler.target>12</maven.compiler.target>
|
<maven.compiler.target>12</maven.compiler.target>
|
||||||
<vavr.version>0.10.2</vavr.version>
|
<vavr.version>0.10.2</vavr.version>
|
||||||
|
<guava.version>32.1.2-jre</guava.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
</project>
|
</project>
|
|
@ -0,0 +1,90 @@
|
||||||
|
package com.baeldung.streams.partitioning;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.BinaryOperator;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Collector;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
|
public class PartitionStream {
|
||||||
|
|
||||||
|
public static <T> Stream<List<T>> partitionList(List<T> source, int batchSize) {
|
||||||
|
if (batchSize <= 0) {
|
||||||
|
throw new IllegalArgumentException(String.format("Expected the batchSize to be greater than ZERO, actual value was: %s", batchSize));
|
||||||
|
}
|
||||||
|
if (source.isEmpty()) {
|
||||||
|
return Stream.empty();
|
||||||
|
}
|
||||||
|
int nrOfFullBatches = (source.size() - 1) / batchSize;
|
||||||
|
return IntStream.rangeClosed(0, nrOfFullBatches)
|
||||||
|
.mapToObj(batch -> {
|
||||||
|
int startIndex = batch * batchSize;
|
||||||
|
int endIndex = (batch == nrOfFullBatches) ? source.size() : (batch + 1) * batchSize;
|
||||||
|
return source.subList(startIndex, endIndex);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Iterable<List<T>> partitionUsingGuava(Stream<T> source, int batchSize) {
|
||||||
|
return Iterables.partition(source::iterator, batchSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> List<List<T>> partitionStream(Stream<T> source, int batchSize) {
|
||||||
|
return source.collect(partitionBySize(batchSize, Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, A, R> Collector<T, ?, R> partitionBySize(int batchSize, Collector<List<T>, A, R> downstream) {
|
||||||
|
Supplier<Accumulator<T, A>> supplier = () -> new Accumulator<>(
|
||||||
|
batchSize,
|
||||||
|
downstream.supplier().get(),
|
||||||
|
downstream.accumulator()::accept
|
||||||
|
);
|
||||||
|
|
||||||
|
BiConsumer<Accumulator<T, A>, T> accumulator = (acc, value) -> acc.add(value);
|
||||||
|
|
||||||
|
BinaryOperator<Accumulator<T, A>> combiner = (acc1, acc2) -> acc1.combine(acc2, downstream.combiner());
|
||||||
|
|
||||||
|
Function<Accumulator<T, A>, R> finisher = acc -> {
|
||||||
|
if (!acc.values.isEmpty()) {
|
||||||
|
downstream.accumulator().accept(acc.downstreamAccumulator, acc.values);
|
||||||
|
}
|
||||||
|
return downstream.finisher().apply(acc.downstreamAccumulator);
|
||||||
|
};
|
||||||
|
|
||||||
|
return Collector.of(supplier, accumulator, combiner, finisher, Collector.Characteristics.UNORDERED);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Accumulator<T, A> {
|
||||||
|
private final List<T> values = new ArrayList<>();
|
||||||
|
private final int batchSize;
|
||||||
|
private A downstreamAccumulator;
|
||||||
|
private final BiConsumer<A, List<T>> batchFullListener;
|
||||||
|
|
||||||
|
Accumulator(int batchSize, A accumulator, BiConsumer<A, List<T>> onBatchFull) {
|
||||||
|
this.batchSize = batchSize;
|
||||||
|
this.downstreamAccumulator = accumulator;
|
||||||
|
this.batchFullListener = onBatchFull;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add(T value) {
|
||||||
|
values.add(value);
|
||||||
|
if (values.size() == batchSize) {
|
||||||
|
batchFullListener.accept(downstreamAccumulator, new ArrayList<>(values));
|
||||||
|
values.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Accumulator<T, A> combine(Accumulator<T, A> other, BinaryOperator<A> accumulatorCombiner) {
|
||||||
|
this.downstreamAccumulator = accumulatorCombiner.apply(downstreamAccumulator, other.downstreamAccumulator);
|
||||||
|
other.values.forEach(this::add);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
package com.baeldung.partitioning;
|
||||||
|
|
||||||
|
import static com.baeldung.streams.partitioning.PartitionStream.partitionList;
|
||||||
|
import static com.baeldung.streams.partitioning.PartitionStream.partitionStream;
|
||||||
|
import static com.baeldung.streams.partitioning.PartitionStream.partitionUsingGuava;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.assertj.core.api.Assertions.atIndex;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class PartitionStreamsUnitTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenPartitionList_thenReturnThreeSubLists() {
|
||||||
|
List<Integer> source = List.of(1, 2, 3, 4, 5, 6, 7, 8);
|
||||||
|
|
||||||
|
Stream<List<Integer>> result = partitionList(source, 3);
|
||||||
|
|
||||||
|
assertThat(result)
|
||||||
|
.containsExactlyInAnyOrder(
|
||||||
|
List.of(1, 2, 3),
|
||||||
|
List.of(4, 5, 6),
|
||||||
|
List.of(7, 8)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenPartitionEmptyList_thenReturnEmptyStream() {
|
||||||
|
Stream<List<Integer>> result = partitionList(Collections.emptyList(), 3);
|
||||||
|
|
||||||
|
assertThat(result).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenPartitionListWithNegativeBatchSize_thenThrowException() {
|
||||||
|
assertThatThrownBy(() -> partitionList(List.of(1,2,3), -1))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("Expected the batchSize to be greater than ZERO, actual value was: -1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenPartitionParallelStream_thenReturnThreeSubLists() {
|
||||||
|
Stream<Integer> source = Stream.of(1, 2, 3, 4, 5, 6, 7, 8).parallel();
|
||||||
|
|
||||||
|
List<List<Integer>> result = partitionStream(source, 3);
|
||||||
|
|
||||||
|
assertThat(result)
|
||||||
|
.hasSize(3)
|
||||||
|
.satisfies(batch -> assertThat(batch).hasSize(3), atIndex(0))
|
||||||
|
.satisfies(batch -> assertThat(batch).hasSize(3), atIndex(1))
|
||||||
|
.satisfies(batch -> assertThat(batch).hasSize(2), atIndex(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenPartitionEmptyParallelStream_thenReturnEmptyList() {
|
||||||
|
Stream<Integer> source = Stream.<Integer>empty().parallel();
|
||||||
|
|
||||||
|
List<List<Integer>> result = partitionStream(source, 3);
|
||||||
|
|
||||||
|
assertThat(result).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenPartitionParallelStreamWithGuava_thenReturnThreeSubLists() {
|
||||||
|
Stream<Integer> source = Stream.of(1, 2, 3, 4, 5, 6, 7, 8).parallel();
|
||||||
|
|
||||||
|
Iterable<List<Integer>> result = partitionUsingGuava(source, 3);
|
||||||
|
|
||||||
|
assertThat(result)
|
||||||
|
.map(ArrayList::new)
|
||||||
|
.hasSize(3)
|
||||||
|
.satisfies(batch -> assertThat(batch).asList().hasSize(3), atIndex(0))
|
||||||
|
.satisfies(batch -> assertThat(batch).asList().hasSize(3), atIndex(1))
|
||||||
|
.satisfies(batch -> assertThat(batch).asList().hasSize(2), atIndex(2));
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,12 +28,18 @@
|
||||||
<artifactId>commons-vfs2</artifactId>
|
<artifactId>commons-vfs2</artifactId>
|
||||||
<version>${commons-vfs2.version}</version>
|
<version>${commons-vfs2.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-text</artifactId>
|
||||||
|
<version>${apache-commons-text.version}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<commons-compress.version>1.23.0</commons-compress.version>
|
<commons-compress.version>1.23.0</commons-compress.version>
|
||||||
<ant.version>1.10.13</ant.version>
|
<ant.version>1.10.13</ant.version>
|
||||||
<commons-vfs2.version>2.9.0</commons-vfs2.version>
|
<commons-vfs2.version>2.9.0</commons-vfs2.version>
|
||||||
|
<apache-commons-text.version>1.10.0</apache-commons-text.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
</project>
|
</project>
|
|
@ -0,0 +1,29 @@
|
||||||
|
package com.baeldung.commons.convertunicode;
|
||||||
|
|
||||||
|
import org.apache.commons.text.StringEscapeUtils;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class UnicodeConverterUtil {
|
||||||
|
|
||||||
|
public static String decodeWithApacheCommons(String input) {
|
||||||
|
return StringEscapeUtils.unescapeJava(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String decodeWithPlainJava(String input) {
|
||||||
|
Pattern pattern = Pattern.compile("\\\\u[0-9a-fA-F]{4}");
|
||||||
|
Matcher matcher = pattern.matcher(input);
|
||||||
|
|
||||||
|
StringBuilder decodedString = new StringBuilder();
|
||||||
|
|
||||||
|
while (matcher.find()) {
|
||||||
|
String unicodeSequence = matcher.group();
|
||||||
|
char unicodeChar = (char) Integer.parseInt(unicodeSequence.substring(2), 16);
|
||||||
|
matcher.appendReplacement(decodedString, Character.toString(unicodeChar));
|
||||||
|
}
|
||||||
|
|
||||||
|
matcher.appendTail(decodedString);
|
||||||
|
return decodedString.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package com.baeldung.commons.convertunicode;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
public class UnicodeConverterUnitTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenInputHaveUnicodeSequences_ThenDecode() {
|
||||||
|
String encodedString = "\\u0048\\u0065\\u006C\\u006C\\u006F World";
|
||||||
|
String expectedDecodedString = "Hello World";
|
||||||
|
assertEquals(expectedDecodedString, UnicodeConverterUtil.decodeWithApacheCommons(encodedString));
|
||||||
|
assertEquals(expectedDecodedString, UnicodeConverterUtil.decodeWithPlainJava(encodedString));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenInputHaveNoUnicodeSequences_ThenDoNothing() {
|
||||||
|
String inputString = "Hello World";
|
||||||
|
assertEquals(inputString, UnicodeConverterUtil.decodeWithApacheCommons(inputString));
|
||||||
|
assertEquals(inputString, UnicodeConverterUtil.decodeWithPlainJava(inputString));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenInputHaveUnicodeSequencesInMiddle_ThenDecode() {
|
||||||
|
String encodedString = "This is a test \\u0069\\u006E the middle.";
|
||||||
|
String expectedDecodedString = "This is a test in the middle.";
|
||||||
|
assertEquals(expectedDecodedString, UnicodeConverterUtil.decodeWithApacheCommons(encodedString));
|
||||||
|
assertEquals(expectedDecodedString, UnicodeConverterUtil.decodeWithPlainJava(encodedString));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenInputHaveMultipleUnicodeSequences_ThenDecode() {
|
||||||
|
String encodedString = "Unicode: \\u0048\\u0065\\u006C\\u006C\\u006F \\u0057\\u006F\\u0072\\u006C\\u0064";
|
||||||
|
String expectedDecodedString = "Unicode: Hello World";
|
||||||
|
assertEquals(expectedDecodedString, UnicodeConverterUtil.decodeWithApacheCommons(encodedString));
|
||||||
|
assertEquals(expectedDecodedString, UnicodeConverterUtil.decodeWithPlainJava(encodedString));
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,18 @@
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<artifactId>libraries-io</artifactId>
|
<artifactId>libraries-io</artifactId>
|
||||||
<name>libraries-io</name>
|
<name>libraries-io</name>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>17</source>
|
||||||
|
<target>17</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.baeldung</groupId>
|
<groupId>com.baeldung</groupId>
|
||||||
|
@ -34,6 +46,11 @@
|
||||||
<artifactId>zip4j</artifactId>
|
<artifactId>zip4j</artifactId>
|
||||||
<version>${zip4j.version}</version>
|
<version>${zip4j.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.opencsv</groupId>
|
||||||
|
<artifactId>opencsv</artifactId>
|
||||||
|
<version>${opencsv.version}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
@ -42,6 +59,10 @@
|
||||||
<sshj.version>0.27.0</sshj.version>
|
<sshj.version>0.27.0</sshj.version>
|
||||||
<vfs.version>2.4</vfs.version>
|
<vfs.version>2.4</vfs.version>
|
||||||
<zip4j.version>2.9.0</zip4j.version>
|
<zip4j.version>2.9.0</zip4j.version>
|
||||||
|
<opencsv.version>5.7.1</opencsv.version>
|
||||||
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
</project>
|
</project>
|
|
@ -0,0 +1,4 @@
|
||||||
|
package com.baeldung.java.io.pojotocsv;
|
||||||
|
|
||||||
|
public record Application(String id, String name, Integer age, String created_at) {
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.baeldung.java.io.pojotocsv;
|
||||||
|
|
||||||
|
import com.opencsv.bean.CsvBindByName;
|
||||||
|
import com.opencsv.bean.CsvBindByPosition;
|
||||||
|
|
||||||
|
public record ApplicationWithAnnotation(@CsvBindByName(column = "id", required = true) @CsvBindByPosition(position = 1) String id, @CsvBindByName(column = "name", required = true) @CsvBindByPosition(position = 0) String name,
|
||||||
|
@CsvBindByName(column = "age", required = true) @CsvBindByPosition(position = 2) Integer age, @CsvBindByName(column = "position", required = true) @CsvBindByPosition(position = 3) String created_at) {
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package com.baeldung.java.io.pojotocsv;
|
||||||
|
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.opencsv.CSVWriter;
|
||||||
|
import com.opencsv.bean.StatefulBeanToCsvBuilder;
|
||||||
|
import com.opencsv.exceptions.CsvDataTypeMismatchException;
|
||||||
|
import com.opencsv.exceptions.CsvRequiredFieldEmptyException;
|
||||||
|
|
||||||
|
public class BeanToCsv {
|
||||||
|
|
||||||
|
public void beanToCSVWithDefault(List<Application> applications) throws Exception {
|
||||||
|
try (FileWriter writer = new FileWriter("src/main/resources/application.csv")) {
|
||||||
|
var builder = new StatefulBeanToCsvBuilder<Application>(writer).withQuotechar(CSVWriter.NO_QUOTE_CHARACTER)
|
||||||
|
.withSeparator(',')
|
||||||
|
.build();
|
||||||
|
builder.write(applications);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void beanToCSVWithCustomHeaderStrategy(List<Application> applications) throws IOException, CsvRequiredFieldEmptyException, CsvDataTypeMismatchException {
|
||||||
|
try (FileWriter writer = new FileWriter("src/main/resources/application2.csv")) {
|
||||||
|
var mappingStrategy = new CustomHeaderStrategy<Application>();
|
||||||
|
mappingStrategy.setType(Application.class);
|
||||||
|
|
||||||
|
var builder = new StatefulBeanToCsvBuilder<Application>(writer).withQuotechar(CSVWriter.NO_QUOTE_CHARACTER)
|
||||||
|
.withMappingStrategy(mappingStrategy)
|
||||||
|
.build();
|
||||||
|
builder.write(applications);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void beanToCSVWithCustomPositionStrategy(List<ApplicationWithAnnotation> applications) throws Exception {
|
||||||
|
try (FileWriter writer = new FileWriter("src/main/resources/application3.csv")) {
|
||||||
|
var builder = new StatefulBeanToCsvBuilder<ApplicationWithAnnotation>(writer).withQuotechar(CSVWriter.NO_QUOTE_CHARACTER)
|
||||||
|
.build();
|
||||||
|
builder.write(applications);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void beanToCSVWithCustomHeaderAndPositionStrategy(List<ApplicationWithAnnotation> applications) throws IOException, CsvRequiredFieldEmptyException, CsvDataTypeMismatchException {
|
||||||
|
try (FileWriter writer = new FileWriter("src/main/resources/application4.csv")) {
|
||||||
|
var mappingStrategy = new CustomColumnPositionStrategy<ApplicationWithAnnotation>();
|
||||||
|
mappingStrategy.setType(ApplicationWithAnnotation.class);
|
||||||
|
|
||||||
|
var builder = new StatefulBeanToCsvBuilder<ApplicationWithAnnotation>(writer).withQuotechar(CSVWriter.NO_QUOTE_CHARACTER)
|
||||||
|
.withMappingStrategy(mappingStrategy)
|
||||||
|
.build();
|
||||||
|
builder.write(applications);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.baeldung.java.io.pojotocsv;
|
||||||
|
|
||||||
|
import com.opencsv.bean.ColumnPositionMappingStrategy;
|
||||||
|
import com.opencsv.exceptions.CsvRequiredFieldEmptyException;
|
||||||
|
|
||||||
|
public class CustomColumnPositionStrategy<T> extends ColumnPositionMappingStrategy<T> {
|
||||||
|
@Override
|
||||||
|
public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {
|
||||||
|
super.generateHeader(bean);
|
||||||
|
return super.getColumnMapping();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.baeldung.java.io.pojotocsv;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import com.opencsv.bean.HeaderColumnNameMappingStrategy;
|
||||||
|
import com.opencsv.exceptions.CsvRequiredFieldEmptyException;
|
||||||
|
|
||||||
|
public class CustomHeaderStrategy<T> extends HeaderColumnNameMappingStrategy<T> {
|
||||||
|
@Override
|
||||||
|
public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {
|
||||||
|
String[] header = super.generateHeader(bean);
|
||||||
|
return Arrays.stream(header)
|
||||||
|
.map(String::toLowerCase)
|
||||||
|
.toArray(String[]::new);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
AGE,CREATED_AT,ID,NAME
|
||||||
|
34,2023-08-11,123,Sam
|
||||||
|
44,2023-02-11,456,Tam
|
||||||
|
54,2023-03-11,890,Jam
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
age,created_at,id,name
|
||||||
|
34,2023-08-11,123,Sam
|
||||||
|
44,2023-02-11,456,Tam
|
||||||
|
54,2023-03-11,890,Jam
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
Sam,123,34,2023-08-11
|
||||||
|
Tam,456,44,2023-02-11
|
||||||
|
Jam,789,54,2023-03-11
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
name,id,age,created_at
|
||||||
|
Sam,123,34,2023-08-11
|
||||||
|
Tam,456,44,2023-02-11
|
||||||
|
Jam,789,54,2023-03-11
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
package com.baeldung.java.io.pojotocsv;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class BeanToCsvUnitTest {
|
||||||
|
|
||||||
|
List<Application> applications = new ArrayList<>();
|
||||||
|
List<ApplicationWithAnnotation> applicationsWithAnnotation = new ArrayList<>();
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void beforeEach() {
|
||||||
|
applications = List.of(new Application("123", "Sam", 34, "2023-08-11"), new Application("456", "Tam", 44, "2023-02-11"), new Application("890", "Jam", 54, "2023-03-11"));
|
||||||
|
|
||||||
|
applicationsWithAnnotation = List.of(new ApplicationWithAnnotation("123", "Sam", 34, "2023-08-11"), new ApplicationWithAnnotation("456", "Tam", 44, "2023-02-11"), new ApplicationWithAnnotation("789", "Jam", 54, "2023-03-11"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenApplicationPOJO_whenUsingDefaultStrategy_thenReceiveCSVFormatWithAscendingOrderOfField() throws Exception {
|
||||||
|
BeanToCsv beanToCsv = new BeanToCsv();
|
||||||
|
beanToCsv.beanToCSVWithDefault(applications);
|
||||||
|
try (BufferedReader bufferedReader = Files.newBufferedReader(Path.of("src/main/resources/application.csv"))) {
|
||||||
|
List<String> content = bufferedReader.lines()
|
||||||
|
.toList();
|
||||||
|
assertThat(content.get(0)).isEqualTo("AGE,CREATED_AT,ID,NAME");
|
||||||
|
assertThat(content.get(1)).isEqualTo("34,2023-08-11,123,Sam");
|
||||||
|
assertThat(content.get(2)).isEqualTo("44,2023-02-11,456,Tam");
|
||||||
|
assertThat(content.get(3)).isEqualTo("54,2023-03-11,890,Jam");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenApplicationPOJO_whenUsingCustomHeaderStrategy_thenReceiveCSVFormatWithCustomHeaders() throws Exception {
|
||||||
|
BeanToCsv beanToCsv = new BeanToCsv();
|
||||||
|
beanToCsv.beanToCSVWithCustomHeaderStrategy(applications);
|
||||||
|
try (BufferedReader bufferedReader = Files.newBufferedReader(Path.of("src/main/resources/application2.csv"))) {
|
||||||
|
List<String> content = bufferedReader.lines()
|
||||||
|
.toList();
|
||||||
|
assertThat(content.get(0)).isEqualTo("age,created_at,id,name");
|
||||||
|
assertThat(content.get(1)).isEqualTo("34,2023-08-11,123,Sam");
|
||||||
|
assertThat(content.get(2)).isEqualTo("44,2023-02-11,456,Tam");
|
||||||
|
assertThat(content.get(3)).isEqualTo("54,2023-03-11,890,Jam");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenApplicationPOJOWithAnnotation_whenUsingCustomPositionStrategy_thenReceiveCSVFormatWithCustomPosition() throws Exception {
|
||||||
|
BeanToCsv beanToCsv = new BeanToCsv();
|
||||||
|
beanToCsv.beanToCSVWithCustomPositionStrategy(applicationsWithAnnotation);
|
||||||
|
try (BufferedReader bufferedReader = Files.newBufferedReader(Path.of("src/main/resources/application3.csv"))) {
|
||||||
|
List<String> content = bufferedReader.lines()
|
||||||
|
.toList();
|
||||||
|
assertThat(content.get(0)).isEqualTo("Sam,123,34,2023-08-11");
|
||||||
|
assertThat(content.get(1)).isEqualTo("Tam,456,44,2023-02-11");
|
||||||
|
assertThat(content.get(2)).isEqualTo("Jam,789,54,2023-03-11");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenApplicationPOJOWithAnnotation_whenUsingCustomHeaderPositionStrategy_thenReceiveCSVFormatWithCustomHeaderPosition() throws Exception {
|
||||||
|
BeanToCsv beanToCsv = new BeanToCsv();
|
||||||
|
beanToCsv.beanToCSVWithCustomHeaderAndPositionStrategy(applicationsWithAnnotation);
|
||||||
|
try (BufferedReader bufferedReader = Files.newBufferedReader(Path.of("src/main/resources/application4.csv"))) {
|
||||||
|
List<String> content = bufferedReader.lines()
|
||||||
|
.toList();
|
||||||
|
assertThat(content.get(0)).isEqualTo("name,id,age,created_at");
|
||||||
|
assertThat(content.get(1)).isEqualTo("Sam,123,34,2023-08-11");
|
||||||
|
assertThat(content.get(2)).isEqualTo("Tam,456,44,2023-02-11");
|
||||||
|
assertThat(content.get(3)).isEqualTo("Jam,789,54,2023-03-11");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,7 +35,7 @@
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<spring.version>6.0.10</spring.version>
|
<spring.version>6.0.12</spring.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -3,5 +3,5 @@
|
||||||
This module contains articles about RSocket in Spring Framework 6.
|
This module contains articles about RSocket in Spring Framework 6.
|
||||||
|
|
||||||
### Relevant articles
|
### Relevant articles
|
||||||
|
- [RSocket Interface in Spring 6](https://www.baeldung.com/spring-rsocket)
|
||||||
|
|
||||||
- [Introduction to RSocket](#)
|
|
|
@ -9,9 +9,10 @@
|
||||||
<description>This is simple boot application for Spring boot actuator test</description>
|
<description>This is simple boot application for Spring boot actuator test</description>
|
||||||
|
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.baeldung.spring-boot-modules</groupId>
|
<groupId>com.baeldung</groupId>
|
||||||
<artifactId>spring-boot-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>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
@ -39,16 +40,6 @@
|
||||||
<groupId>com.h2database</groupId>
|
<groupId>com.h2database</groupId>
|
||||||
<artifactId>h2</artifactId>
|
<artifactId>h2</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>javax.servlet</groupId>
|
|
||||||
<artifactId>javax.servlet-api</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>javax.servlet</groupId>
|
|
||||||
<artifactId>jstl</artifactId>
|
|
||||||
<scope>runtime</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-test</artifactId>
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
|
|
@ -1,36 +1,56 @@
|
||||||
package com.baeldung.endpoints.enabling;
|
package com.baeldung.endpoints.enabling;
|
||||||
|
|
||||||
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
|
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
import org.springframework.security.config.Customizer;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
import org.springframework.security.core.userdetails.User;
|
||||||
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
|
||||||
|
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
|
@Configuration
|
||||||
|
public class SecurityConfiguration {
|
||||||
|
|
||||||
@Override
|
@Bean
|
||||||
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
MvcRequestMatcher.Builder mvc(HandlerMappingIntrospector introspector) {
|
||||||
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
|
return new MvcRequestMatcher.Builder(introspector);
|
||||||
auth.inMemoryAuthentication()
|
|
||||||
.withUser("user")
|
|
||||||
.password(encoder.encode("password"))
|
|
||||||
.roles("USER")
|
|
||||||
.and()
|
|
||||||
.withUser("admin")
|
|
||||||
.password(encoder.encode("admin"))
|
|
||||||
.roles("USER", "ADMIN");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Bean
|
||||||
protected void configure(HttpSecurity http) throws Exception {
|
public SecurityFilterChain filterChain(HttpSecurity http, MvcRequestMatcher.Builder mvc) throws Exception {
|
||||||
http.requestMatcher(EndpointRequest.toAnyEndpoint())
|
http.httpBasic(Customizer.withDefaults());
|
||||||
.authorizeRequests((requests) -> requests.anyRequest()
|
http.securityMatcher(EndpointRequest.toAnyEndpoint());
|
||||||
.hasRole("ADMIN"));
|
http.authorizeHttpRequests(authz -> {
|
||||||
http.httpBasic();
|
authz.requestMatchers(mvc.pattern("/actuator/**"))
|
||||||
|
.hasRole("ADMIN")
|
||||||
|
.anyRequest()
|
||||||
|
.authenticated();
|
||||||
|
});
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public InMemoryUserDetailsManager userDetailsService() {
|
||||||
|
UserDetails user = User.withDefaultPasswordEncoder()
|
||||||
|
.username("user")
|
||||||
|
.password("password")
|
||||||
|
.roles("USER")
|
||||||
|
.build();
|
||||||
|
UserDetails admin = User.withDefaultPasswordEncoder()
|
||||||
|
.username("admin")
|
||||||
|
.password("password")
|
||||||
|
.roles("USER", "ADMIN")
|
||||||
|
.build();
|
||||||
|
return new InMemoryUserDetailsManager(user, admin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package com.baeldung.endpoints.info;
|
package com.baeldung.endpoints.info;
|
||||||
|
|
||||||
import javax.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
import javax.persistence.GeneratedValue;
|
import jakarta.persistence.GeneratedValue;
|
||||||
import javax.persistence.Id;
|
import jakarta.persistence.Id;
|
||||||
import javax.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "users")
|
@Table(name = "users")
|
||||||
|
|
|
@ -15,7 +15,7 @@ import org.springframework.context.annotation.ComponentScan;
|
||||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
import org.springframework.web.context.request.RequestContextListener;
|
import org.springframework.web.context.request.RequestContextListener;
|
||||||
|
|
||||||
import javax.servlet.ServletContext;
|
import jakarta.servlet.ServletContext;
|
||||||
|
|
||||||
@EnableScheduling
|
@EnableScheduling
|
||||||
@ComponentScan("com.baeldung.metrics")
|
@ComponentScan("com.baeldung.metrics")
|
||||||
|
|
|
@ -7,14 +7,14 @@ import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.context.WebApplicationContext;
|
import org.springframework.web.context.WebApplicationContext;
|
||||||
import org.springframework.web.context.support.WebApplicationContextUtils;
|
import org.springframework.web.context.support.WebApplicationContextUtils;
|
||||||
|
|
||||||
import javax.servlet.Filter;
|
import jakarta.servlet.Filter;
|
||||||
import javax.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
import javax.servlet.FilterConfig;
|
import jakarta.servlet.FilterConfig;
|
||||||
import javax.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import javax.servlet.ServletRequest;
|
import jakarta.servlet.ServletRequest;
|
||||||
import javax.servlet.ServletResponse;
|
import jakarta.servlet.ServletResponse;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class MetricFilter implements Filter {
|
public class MetricFilter implements Filter {
|
||||||
|
|
|
@ -13,20 +13,20 @@ import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
@AutoConfigureMockMvc
|
@AutoConfigureMockMvc
|
||||||
public class EndpointEnablingIntegrationTest {
|
class EndpointEnablingIntegrationTest {
|
||||||
@Autowired
|
@Autowired
|
||||||
private MockMvc mockMvc;
|
private MockMvc mockMvc;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@WithMockUser(username = "user", password = "password", roles = "USER")
|
@WithMockUser(username = "user", password = "password", roles = "USER")
|
||||||
public void givenWrongAuthentication_whenCallingActuator_thenReturns401() throws Exception {
|
void givenWrongAuthentication_whenCallingActuator_thenReturns401() throws Exception {
|
||||||
mockMvc.perform(get("/actuator"))
|
mockMvc.perform(get("/actuator"))
|
||||||
.andExpect(status().isForbidden());
|
.andExpect(status().isForbidden());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@WithMockUser(username = "admin", password = "admin", roles = "ADMIN")
|
@WithMockUser(username = "admin", password = "admin", roles = "ADMIN")
|
||||||
public void givenProperAuthentication_whenCallingActuator_thenReturnsExpectedEndpoints() throws Exception {
|
void givenProperAuthentication_whenCallingActuator_thenReturnsExpectedEndpoints() throws Exception {
|
||||||
mockMvc.perform(get("/actuator"))
|
mockMvc.perform(get("/actuator"))
|
||||||
.andExpect(jsonPath("$._links").exists())
|
.andExpect(jsonPath("$._links").exists())
|
||||||
.andExpect(jsonPath("$._links.beans").exists())
|
.andExpect(jsonPath("$._links.beans").exists())
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
## Relevant Articles
|
||||||
|
- [Create a GraalVM Docker Image](https://www.baeldung.com/java-graalvm-docker-image)
|
|
@ -9,9 +9,10 @@
|
||||||
<description>Module For Spring Boot MVC Web</description>
|
<description>Module For Spring Boot MVC Web</description>
|
||||||
|
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.baeldung.spring-boot-modules</groupId>
|
<groupId>com.baeldung</groupId>
|
||||||
<artifactId>spring-boot-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>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
|
@ -4,10 +4,10 @@ import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServlet;
|
import jakarta.servlet.http.HttpServlet;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
public class HelloWorldServlet extends HttpServlet {
|
public class HelloWorldServlet extends HttpServlet {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
|
@ -4,10 +4,10 @@ import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServlet;
|
import jakarta.servlet.http.HttpServlet;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
public class SpringHelloWorldServlet extends HttpServlet {
|
public class SpringHelloWorldServlet extends HttpServlet {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
|
@ -2,7 +2,7 @@ package com.baeldung.common.error;
|
||||||
|
|
||||||
import org.springframework.boot.web.servlet.ServletRegistrationBean;
|
import org.springframework.boot.web.servlet.ServletRegistrationBean;
|
||||||
|
|
||||||
import javax.servlet.Servlet;
|
import jakarta.servlet.Servlet;
|
||||||
|
|
||||||
public class SpringHelloServletRegistrationBean extends ServletRegistrationBean {
|
public class SpringHelloServletRegistrationBean extends ServletRegistrationBean {
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,9 @@ import org.springframework.web.WebApplicationInitializer;
|
||||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||||
import org.springframework.web.context.support.XmlWebApplicationContext;
|
import org.springframework.web.context.support.XmlWebApplicationContext;
|
||||||
import org.springframework.web.servlet.DispatcherServlet;
|
import org.springframework.web.servlet.DispatcherServlet;
|
||||||
import javax.servlet.ServletContext;
|
import jakarta.servlet.ServletContext;
|
||||||
import javax.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import javax.servlet.ServletRegistration;
|
import jakarta.servlet.ServletRegistration;
|
||||||
|
|
||||||
public class WebAppInitializer implements WebApplicationInitializer {
|
public class WebAppInitializer implements WebApplicationInitializer {
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package com.baeldung.servlets.servlets;
|
package com.baeldung.servlets.servlets;
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServlet;
|
import jakarta.servlet.http.HttpServlet;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package com.baeldung.servlets.servlets.javaee;
|
package com.baeldung.servlets.servlets.javaee;
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import javax.servlet.annotation.WebServlet;
|
import jakarta.servlet.annotation.WebServlet;
|
||||||
import javax.servlet.http.HttpServlet;
|
import jakarta.servlet.http.HttpServlet;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
@WebServlet(name = "AnnotationServlet", description = "Example Servlet Using Annotations", urlPatterns = { "/annotationservlet" })
|
@WebServlet(name = "AnnotationServlet", description = "Example Servlet Using Annotations", urlPatterns = { "/annotationservlet" })
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package com.baeldung.servlets.servlets.javaee;
|
package com.baeldung.servlets.servlets.javaee;
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServlet;
|
import jakarta.servlet.http.HttpServlet;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package com.baeldung.utils;
|
package com.baeldung.utils;
|
||||||
|
|
||||||
import javax.annotation.security.RolesAllowed;
|
import jakarta.annotation.security.RolesAllowed;
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package com.baeldung.utils.controller;
|
package com.baeldung.utils.controller;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
|
|
|
@ -1,32 +1,34 @@
|
||||||
package com.baeldung.utils;
|
package com.baeldung.utils;
|
||||||
|
|
||||||
import com.baeldung.utils.controller.UtilsController;
|
import com.baeldung.utils.controller.UtilsController;
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.MockitoAnnotations;
|
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||||
|
|
||||||
|
import static org.mockito.MockitoAnnotations.openMocks;
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
public class UtilsControllerIntegrationTest {
|
class UtilsControllerIntegrationTest {
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private UtilsController utilsController;
|
private UtilsController utilsController;
|
||||||
|
|
||||||
private MockMvc mockMvc;
|
private MockMvc mockMvc;
|
||||||
|
|
||||||
@Before
|
@BeforeEach
|
||||||
public void setup() {
|
public void setup() {
|
||||||
MockitoAnnotations.initMocks(this);
|
openMocks(this);
|
||||||
this.mockMvc = MockMvcBuilders.standaloneSetup(utilsController).build();
|
this.mockMvc = MockMvcBuilders.standaloneSetup(utilsController).build();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void givenParameter_setRequestParam_andSetSessionAttribute() throws Exception {
|
void givenParameter_setRequestParam_andSetSessionAttribute() throws Exception {
|
||||||
String param = "testparam";
|
String param = "testparam";
|
||||||
this.mockMvc.perform(post("/setParam").param("param", param).sessionAttr("parameter", param)).andExpect(status().isOk());
|
this.mockMvc.perform(post("/setParam").param("param", param).sessionAttr("parameter", param)).andExpect(status().isOk());
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
## Spring Cloud Gateway
|
||||||
|
|
||||||
|
This module contains articles about Spring Cloud Gateway
|
||||||
|
|
||||||
|
### Relevant Articles:
|
||||||
|
|
||||||
|
- [Exploring the New Spring Cloud Gateway](http://www.baeldung.com/spring-cloud-gateway)
|
|
@ -0,0 +1,54 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
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>gateway-2</artifactId>
|
||||||
|
<name>gateway-2</name>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.baeldung</groupId>
|
||||||
|
<artifactId>parent-boot-2</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<relativePath>../../../parent-boot-2</relativePath>
|
||||||
|
</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>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-gateway</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<spring-cloud-dependencies.version>2021.0.3</spring-cloud-dependencies.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.baeldung.springcloudgateway.custompredicates;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class CustomPredicatesApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
new SpringApplicationBuilder(CustomPredicatesApplication.class)
|
||||||
|
.profiles("customroutes")
|
||||||
|
.run(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package com.baeldung.springcloudgateway.custompredicates.config;
|
||||||
|
|
||||||
|
import org.springframework.cloud.gateway.route.RouteLocator;
|
||||||
|
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import com.baeldung.springcloudgateway.custompredicates.factories.GoldenCustomerRoutePredicateFactory;
|
||||||
|
import com.baeldung.springcloudgateway.custompredicates.factories.GoldenCustomerRoutePredicateFactory.Config;
|
||||||
|
import com.baeldung.springcloudgateway.custompredicates.service.GoldenCustomerService;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class CustomPredicatesConfig {
|
||||||
|
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public GoldenCustomerRoutePredicateFactory goldenCustomer(GoldenCustomerService goldenCustomerService) {
|
||||||
|
return new GoldenCustomerRoutePredicateFactory(goldenCustomerService);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//@Bean
|
||||||
|
public RouteLocator routes(RouteLocatorBuilder builder, GoldenCustomerRoutePredicateFactory gf ) {
|
||||||
|
|
||||||
|
return builder.routes()
|
||||||
|
.route("dsl_golden_route", r ->
|
||||||
|
r.predicate(gf.apply(new Config(true, "customerId")))
|
||||||
|
.and()
|
||||||
|
.path("/dsl_api/**")
|
||||||
|
.filters(f -> f.stripPrefix(1))
|
||||||
|
.uri("https://httpbin.org")
|
||||||
|
)
|
||||||
|
.route("dsl_common_route", r ->
|
||||||
|
r.predicate(gf.apply(new Config(false, "customerId")))
|
||||||
|
.and()
|
||||||
|
.path("/dsl_api/**")
|
||||||
|
.filters(f -> f.stripPrefix(1))
|
||||||
|
.uri("https://httpbin.org")
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.baeldung.springcloudgateway.custompredicates.factories;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotEmpty;
|
||||||
|
|
||||||
|
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
|
||||||
|
import org.springframework.http.HttpCookie;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
|
||||||
|
import com.baeldung.springcloudgateway.custompredicates.service.GoldenCustomerService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Philippe
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class GoldenCustomerRoutePredicateFactory extends AbstractRoutePredicateFactory<GoldenCustomerRoutePredicateFactory.Config> {
|
||||||
|
|
||||||
|
private final GoldenCustomerService goldenCustomerService;
|
||||||
|
|
||||||
|
public GoldenCustomerRoutePredicateFactory(GoldenCustomerService goldenCustomerService ) {
|
||||||
|
super(Config.class);
|
||||||
|
this.goldenCustomerService = goldenCustomerService;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> shortcutFieldOrder() {
|
||||||
|
return Arrays.asList("isGolden","customerIdCookie");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Predicate<ServerWebExchange> apply(Config config) {
|
||||||
|
|
||||||
|
return (ServerWebExchange t) -> {
|
||||||
|
List<HttpCookie> cookies = t.getRequest()
|
||||||
|
.getCookies()
|
||||||
|
.get(config.getCustomerIdCookie());
|
||||||
|
|
||||||
|
boolean isGolden;
|
||||||
|
if ( cookies == null || cookies.isEmpty()) {
|
||||||
|
isGolden = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
String customerId = cookies.get(0).getValue();
|
||||||
|
isGolden = goldenCustomerService.isGoldenCustomer(customerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return config.isGolden()?isGolden:!isGolden;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Validated
|
||||||
|
public static class Config {
|
||||||
|
boolean isGolden = true;
|
||||||
|
|
||||||
|
@NotEmpty
|
||||||
|
String customerIdCookie = "customerId";
|
||||||
|
|
||||||
|
|
||||||
|
public Config() {}
|
||||||
|
|
||||||
|
public Config( boolean isGolden, String customerIdCookie) {
|
||||||
|
this.isGolden = isGolden;
|
||||||
|
this.customerIdCookie = customerIdCookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isGolden() {
|
||||||
|
return isGolden;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGolden(boolean value) {
|
||||||
|
this.isGolden = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the customerIdCookie
|
||||||
|
*/
|
||||||
|
public String getCustomerIdCookie() {
|
||||||
|
return customerIdCookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param customerIdCookie the customerIdCookie to set
|
||||||
|
*/
|
||||||
|
public void setCustomerIdCookie(String customerIdCookie) {
|
||||||
|
this.customerIdCookie = customerIdCookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.baeldung.springcloudgateway.custompredicates.service;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Philippe
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class GoldenCustomerService {
|
||||||
|
|
||||||
|
public boolean isGoldenCustomer(String customerId) {
|
||||||
|
|
||||||
|
// TODO: Add some AI logic to check is this customer deserves a "golden" status ;^)
|
||||||
|
if ( "baeldung".equalsIgnoreCase(customerId)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.baeldung.springcloudgateway.introduction;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.context.annotation.PropertySource;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
@PropertySource("classpath:introduction-application.properties")
|
||||||
|
public class IntroductionGatewayApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(IntroductionGatewayApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
spring:
|
||||||
|
cloud:
|
||||||
|
gateway:
|
||||||
|
routes:
|
||||||
|
- id: golden_route
|
||||||
|
uri: https://httpbin.org
|
||||||
|
predicates:
|
||||||
|
- Path=/api/**
|
||||||
|
- GoldenCustomer=true
|
||||||
|
filters:
|
||||||
|
- StripPrefix=1
|
||||||
|
- AddRequestHeader=GoldenCustomer,true
|
||||||
|
- id: common_route
|
||||||
|
uri: https://httpbin.org
|
||||||
|
predicates:
|
||||||
|
- Path=/api/**
|
||||||
|
- name: GoldenCustomer
|
||||||
|
args:
|
||||||
|
golden: false
|
||||||
|
customerIdCookie: customerId
|
||||||
|
filters:
|
||||||
|
- StripPrefix=1
|
||||||
|
- AddRequestHeader=GoldenCustomer,false
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
org.springframework.cloud.gateway: DEBUG
|
||||||
|
reactor.netty.http.client: DEBUG
|
|
@ -0,0 +1,7 @@
|
||||||
|
spring.cloud.gateway.routes[0].id=baeldung_route
|
||||||
|
spring.cloud.gateway.routes[0].uri=http://www.baeldung.com
|
||||||
|
spring.cloud.gateway.routes[0].predicates[0]=Path=/baeldung
|
||||||
|
|
||||||
|
management.endpoints.web.exposure.include=*
|
||||||
|
|
||||||
|
server.port=80
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration>
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
|
||||||
|
</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</root>
|
||||||
|
</configuration>
|
|
@ -0,0 +1,67 @@
|
||||||
|
package com.baeldung.springcloudgateway.custompredicates;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.json.JSONTokener;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||||
|
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||||
|
import org.springframework.boot.web.server.LocalServerPort;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.RequestEntity;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test requires
|
||||||
|
*/
|
||||||
|
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||||
|
@ActiveProfiles("customroutes")
|
||||||
|
public class CustomPredicatesApplicationLiveTest {
|
||||||
|
|
||||||
|
@LocalServerPort
|
||||||
|
String serverPort;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TestRestTemplate restTemplate;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenNormalCustomer_whenCallHeadersApi_thenResponseForNormalCustomer() throws JSONException {
|
||||||
|
|
||||||
|
String url = "http://localhost:" + serverPort + "/api/headers";
|
||||||
|
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
|
|
||||||
|
JSONObject json = new JSONObject(response.getBody());
|
||||||
|
JSONObject headers = json.getJSONObject("headers");
|
||||||
|
assertThat(headers.getString("Goldencustomer")).isEqualTo("false");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenGoldenCustomer_whenCallHeadersApi_thenResponseForGoldenCustomer() throws JSONException {
|
||||||
|
|
||||||
|
String url = "http://localhost:" + serverPort + "/api/headers";
|
||||||
|
RequestEntity<Void> request = RequestEntity
|
||||||
|
.get(URI.create(url))
|
||||||
|
.header("Cookie", "customerId=baeldung")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
ResponseEntity<String> response = restTemplate.exchange(request, String.class);
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
|
|
||||||
|
JSONObject json = new JSONObject(response.getBody());
|
||||||
|
JSONObject headers = json.getJSONObject("headers");
|
||||||
|
assertThat(headers.getString("Goldencustomer")).isEqualTo("true");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.baeldung.springcloudgateway.introduction;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||||
|
import ch.qos.logback.core.AppenderBase;
|
||||||
|
|
||||||
|
public class LoggerListAppender extends AppenderBase<ILoggingEvent> {
|
||||||
|
|
||||||
|
static private List<ILoggingEvent> events = new ArrayList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void append(ILoggingEvent eventObject) {
|
||||||
|
events.add(eventObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ILoggingEvent> getEvents() {
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clearEventList() {
|
||||||
|
events.clear();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.baeldung.springcloudgateway.introduction;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
import com.baeldung.springcloudgateway.introduction.IntroductionGatewayApplication;
|
||||||
|
|
||||||
|
|
||||||
|
@SpringBootTest(classes = IntroductionGatewayApplication.class)
|
||||||
|
public class SpringContextTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenSpringContextIsBootstrapped_thenNoExceptions() {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration>
|
||||||
|
<appender name="LISTAPPENDER"
|
||||||
|
class="com.baeldung.springcloudgateway.introduction.LoggerListAppender">
|
||||||
|
</appender>
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="LISTAPPENDER" />
|
||||||
|
</root>
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</root>
|
||||||
|
</configuration>
|
|
@ -18,6 +18,7 @@
|
||||||
<module>config</module>
|
<module>config</module>
|
||||||
<module>discovery</module>
|
<module>discovery</module>
|
||||||
<module>gateway</module>
|
<module>gateway</module>
|
||||||
|
<module>gateway-2</module>
|
||||||
<module>svc-book</module>
|
<module>svc-book</module>
|
||||||
<module>svc-rating</module>
|
<module>svc-rating</module>
|
||||||
<module>customer-service</module>
|
<module>customer-service</module>
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
package com.baeldung.spring.kafka.multipletopics;
|
||||||
|
|
||||||
|
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.config.ConcurrentKafkaListenerContainerFactory;
|
||||||
|
import org.springframework.kafka.core.ConsumerFactory;
|
||||||
|
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
|
||||||
|
import org.springframework.kafka.support.serializer.JsonDeserializer;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class KafkaConsumerConfig {
|
||||||
|
|
||||||
|
@Value(value = "${spring.kafka.bootstrap-servers}")
|
||||||
|
private String bootstrapServers;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ConsumerFactory<String, PaymentData> consumerFactory() {
|
||||||
|
Map<String, Object> config = new HashMap<>();
|
||||||
|
config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
|
||||||
|
|
||||||
|
return new DefaultKafkaConsumerFactory<>(config, new StringDeserializer(), new JsonDeserializer<>(PaymentData.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ConcurrentKafkaListenerContainerFactory<String, PaymentData> kafkaListenerContainerFactory() {
|
||||||
|
ConcurrentKafkaListenerContainerFactory<String, PaymentData> factory = new ConcurrentKafkaListenerContainerFactory<>();
|
||||||
|
factory.setConsumerFactory(consumerFactory());
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.baeldung.spring.kafka.multipletopics;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.kafka.annotation.EnableKafka;
|
||||||
|
|
||||||
|
@EnableKafka
|
||||||
|
@SpringBootApplication
|
||||||
|
public class KafkaMultipleTopicsApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(KafkaMultipleTopicsApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.baeldung.spring.kafka.multipletopics;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.kafka.clients.producer.ProducerConfig;
|
||||||
|
import org.apache.kafka.common.serialization.StringSerializer;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
|
||||||
|
import org.springframework.kafka.core.KafkaTemplate;
|
||||||
|
import org.springframework.kafka.core.ProducerFactory;
|
||||||
|
import org.springframework.kafka.support.serializer.JsonSerializer;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class KafkaProducerConfig {
|
||||||
|
|
||||||
|
@Value(value = "${spring.kafka.bootstrap-servers}")
|
||||||
|
private String bootstrapServers;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ProducerFactory<String, PaymentData> producerFactory() {
|
||||||
|
Map<String, Object> config = new HashMap<>();
|
||||||
|
config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
|
||||||
|
|
||||||
|
return new DefaultKafkaProducerFactory<>(config, new StringSerializer(), new JsonSerializer<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public KafkaTemplate<String, PaymentData> kafkaProducer() {
|
||||||
|
return new KafkaTemplate<>(producerFactory());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package com.baeldung.spring.kafka.multipletopics;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Currency;
|
||||||
|
import java.util.StringJoiner;
|
||||||
|
|
||||||
|
public class PaymentData {
|
||||||
|
private String paymentReference;
|
||||||
|
private String type;
|
||||||
|
private BigDecimal amount;
|
||||||
|
private Currency currency;
|
||||||
|
|
||||||
|
public String getPaymentReference() {
|
||||||
|
return paymentReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPaymentReference(String paymentReference) {
|
||||||
|
this.paymentReference = paymentReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getAmount() {
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAmount(BigDecimal amount) {
|
||||||
|
this.amount = amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Currency getCurrency() {
|
||||||
|
return currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrency(Currency currency) {
|
||||||
|
this.currency = currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return new StringJoiner(", ", PaymentData.class.getSimpleName() + "[", "]")
|
||||||
|
.add("paymentReference='" + paymentReference + "'")
|
||||||
|
.add("type='" + type + "'")
|
||||||
|
.add("amount=" + amount)
|
||||||
|
.add("currency=" + currency)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.baeldung.spring.kafka.multipletopics;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.kafka.annotation.KafkaListener;
|
||||||
|
import org.springframework.kafka.support.KafkaHeaders;
|
||||||
|
import org.springframework.messaging.handler.annotation.Header;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class PaymentDataListener {
|
||||||
|
private final Logger log = LoggerFactory.getLogger(PaymentDataListener.class);
|
||||||
|
|
||||||
|
@KafkaListener(topics = { "card-payments", "bank-transfers" }, groupId = "payments")
|
||||||
|
public void handlePaymentEvents(PaymentData paymentData, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) {
|
||||||
|
log.info("Event on topic={}, payload={}", topic, paymentData);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package com.baeldung.spring.kafka.multipletopics;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Currency;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
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.boot.test.mock.mockito.SpyBean;
|
||||||
|
import org.springframework.kafka.config.KafkaListenerEndpointRegistry;
|
||||||
|
import org.springframework.kafka.core.KafkaTemplate;
|
||||||
|
import org.springframework.kafka.listener.MessageListenerContainer;
|
||||||
|
import org.springframework.kafka.test.context.EmbeddedKafka;
|
||||||
|
import org.springframework.kafka.test.utils.ContainerTestUtils;
|
||||||
|
|
||||||
|
@SpringBootTest(classes = KafkaMultipleTopicsApplication.class)
|
||||||
|
@EmbeddedKafka(partitions = 1, brokerProperties = { "listeners=PLAINTEXT://localhost:9092", "port=9092" })
|
||||||
|
public class KafkaMultipleTopicsIntegrationTest {
|
||||||
|
private static final String CARD_PAYMENTS_TOPIC = "card-payments";
|
||||||
|
private static final String BANK_TRANSFERS_TOPIC = "bank-transfers";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private KafkaListenerEndpointRegistry kafkaListenerEndpointRegistry;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private KafkaTemplate<String, PaymentData> kafkaProducer;
|
||||||
|
|
||||||
|
@SpyBean
|
||||||
|
private PaymentDataListener paymentsConsumer;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
// wait for embedded Kafka
|
||||||
|
for (MessageListenerContainer messageListenerContainer : kafkaListenerEndpointRegistry.getListenerContainers()) {
|
||||||
|
ContainerTestUtils.waitForAssignment(messageListenerContainer, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenSendingMessagesOnTwoTopics_thenConsumerReceivesMessages() throws Exception {
|
||||||
|
CountDownLatch countDownLatch = new CountDownLatch(2);
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
countDownLatch.countDown();
|
||||||
|
return null;
|
||||||
|
}).when(paymentsConsumer)
|
||||||
|
.handlePaymentEvents(any(), any());
|
||||||
|
|
||||||
|
kafkaProducer.send(CARD_PAYMENTS_TOPIC, createCardPayment());
|
||||||
|
kafkaProducer.send(BANK_TRANSFERS_TOPIC, createBankTransfer());
|
||||||
|
|
||||||
|
assertThat(countDownLatch.await(5, TimeUnit.SECONDS)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private PaymentData createCardPayment() {
|
||||||
|
PaymentData cardPayment = new PaymentData();
|
||||||
|
cardPayment.setAmount(BigDecimal.valueOf(275));
|
||||||
|
cardPayment.setPaymentReference("A184028KM0013790");
|
||||||
|
cardPayment.setCurrency(Currency.getInstance("GBP"));
|
||||||
|
cardPayment.setType("card");
|
||||||
|
return cardPayment;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PaymentData createBankTransfer() {
|
||||||
|
PaymentData bankTransfer = new PaymentData();
|
||||||
|
bankTransfer.setAmount(BigDecimal.valueOf(150));
|
||||||
|
bankTransfer.setPaymentReference("19ae2-18mk73-009");
|
||||||
|
bankTransfer.setCurrency(Currency.getInstance("EUR"));
|
||||||
|
bankTransfer.setType("bank");
|
||||||
|
return bankTransfer;
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,3 +9,4 @@
|
||||||
- [JUnit – Testing Methods That Call System.exit()](https://www.baeldung.com/junit-system-exit)
|
- [JUnit – Testing Methods That Call System.exit()](https://www.baeldung.com/junit-system-exit)
|
||||||
- [Single Assert Call for Multiple Properties in Java Unit Testing](https://www.baeldung.com/java-testing-single-assert-multiple-properties)
|
- [Single Assert Call for Multiple Properties in Java Unit Testing](https://www.baeldung.com/java-testing-single-assert-multiple-properties)
|
||||||
- [Creating a Test Suite With JUnit](https://www.baeldung.com/java-junit-test-suite)
|
- [Creating a Test Suite With JUnit](https://www.baeldung.com/java-junit-test-suite)
|
||||||
|
- [Testing Interface Contract in Java](https://www.baeldung.com/java-junit-verify-interface-contract)
|
||||||
|
|
Loading…
Reference in New Issue