diff --git a/apache-kafka-2/README.md b/apache-kafka-2/README.md index dc675a0811..40ee701be1 100644 --- a/apache-kafka-2/README.md +++ b/apache-kafka-2/README.md @@ -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) - [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) +- [Introduction to Apache Kafka](https://www.baeldung.com/apache-kafka) diff --git a/apache-kafka-2/src/test/java/com/baeldung/kafka/multipletopics/MultipleTopicsLiveTest.java b/apache-kafka-2/src/test/java/com/baeldung/kafka/multipletopics/MultipleTopicsLiveTest.java new file mode 100644 index 0000000000..653456a678 --- /dev/null +++ b/apache-kafka-2/src/test/java/com/baeldung/kafka/multipletopics/MultipleTopicsLiveTest.java @@ -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 producer; + private static KafkaConsumer 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 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 cardPayment = new ProducerRecord<>(CARD_PAYMENTS_TOPIC, createCardPayment()); + producer.send(cardPayment).get(); + + ProducerRecord 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\"}"; + } +} diff --git a/core-java-modules/core-java-21/README.md b/core-java-modules/core-java-21/README.md index 2c51db275a..ffb999a4ba 100644 --- a/core-java-modules/core-java-21/README.md +++ b/core-java-modules/core-java-21/README.md @@ -1,3 +1,5 @@ ## Relevant Articles - [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) +- [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) diff --git a/core-java-modules/core-java-char/README.md b/core-java-modules/core-java-char/README.md index e4af3121c5..56040a3ea5 100644 --- a/core-java-modules/core-java-char/README.md +++ b/core-java-modules/core-java-char/README.md @@ -5,3 +5,4 @@ This module contains articles about Java Character Class ### Relevant Articles: - [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) +- [Increment Character in Java](https://www.baeldung.com/java-char-sequence) diff --git a/core-java-modules/core-java-collections-5/README.md b/core-java-modules/core-java-collections-5/README.md index 1769d11686..e478d87ad0 100644 --- a/core-java-modules/core-java-collections-5/README.md +++ b/core-java-modules/core-java-collections-5/README.md @@ -5,4 +5,6 @@ ### Relevant Articles: - [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) +- [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) diff --git a/core-java-modules/core-java-collections-array-list-2/README.md b/core-java-modules/core-java-collections-array-list-2/README.md index dbf5c47edb..575e0dbb07 100644 --- a/core-java-modules/core-java-collections-array-list-2/README.md +++ b/core-java-modules/core-java-collections-array-list-2/README.md @@ -3,4 +3,4 @@ This module contains articles about the Java ArrayList collection ### 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) diff --git a/core-java-modules/core-java-collections-conversions-3/README.md b/core-java-modules/core-java-collections-conversions-3/README.md index f89cd199ff..653f518840 100644 --- a/core-java-modules/core-java-collections-conversions-3/README.md +++ b/core-java-modules/core-java-collections-conversions-3/README.md @@ -3,3 +3,4 @@ This module contains articles about conversions among Collection types in Java. ### Relevant Articles: +- [Converting HashMap Values to an ArrayList in Java](https://www.baeldung.com/java-hashmap-arraylist) diff --git a/core-java-modules/core-java-collections-maps-7/README.md b/core-java-modules/core-java-collections-maps-7/README.md new file mode 100644 index 0000000000..c63f3b360b --- /dev/null +++ b/core-java-modules/core-java-collections-maps-7/README.md @@ -0,0 +1 @@ +## Relevant Articles \ No newline at end of file diff --git a/core-java-modules/core-java-collections-maps-7/pom.xml b/core-java-modules/core-java-collections-maps-7/pom.xml new file mode 100644 index 0000000000..bb7c6e9fb5 --- /dev/null +++ b/core-java-modules/core-java-collections-maps-7/pom.xml @@ -0,0 +1,78 @@ + + + 4.0.0 + core-java-collections-maps-7 + core-java-collections-maps-7 + jar + + + core-java-modules + com.baeldung.core-java-modules + 0.0.1-SNAPSHOT + + + + 5.2.5.RELEASE + + + + com.fasterxml.jackson.core + jackson-databind + 2.12.4 + + + org.openjdk.jmh + jmh-core + 1.36 + + + com.google.code.gson + gson + 2.8.9 + + + org.json + json + 20230227 + + + junit + junit + 4.13.1 + test + + + org.junit.jupiter + junit-jupiter + 5.8.1 + test + + + org.junit.jupiter + junit-jupiter + 5.8.1 + test + + + junit + junit + 4.13.1 + test + + + org.junit.jupiter + junit-jupiter + 5.8.1 + test + + + junit + junit + 4.13.1 + test + + + + \ No newline at end of file diff --git a/core-java-modules/core-java-collections-maps-7/src/main/java/com/baeldung/map/ConvertHashMapStringToHashMapObjectUsingtoString.java b/core-java-modules/core-java-collections-maps-7/src/main/java/com/baeldung/map/ConvertHashMapStringToHashMapObjectUsingtoString.java new file mode 100644 index 0000000000..2b3d6db89c --- /dev/null +++ b/core-java-modules/core-java-collections-maps-7/src/main/java/com/baeldung/map/ConvertHashMapStringToHashMapObjectUsingtoString.java @@ -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 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 + "}"; + } +} diff --git a/core-java-modules/core-java-collections-maps-7/src/test/java/com/baeldung/map/ConvertHashMapStringToHashMapObjectUsingtoStringUnitTest.java b/core-java-modules/core-java-collections-maps-7/src/test/java/com/baeldung/map/ConvertHashMapStringToHashMapObjectUsingtoStringUnitTest.java new file mode 100644 index 0000000000..7d31402131 --- /dev/null +++ b/core-java-modules/core-java-collections-maps-7/src/test/java/com/baeldung/map/ConvertHashMapStringToHashMapObjectUsingtoStringUnitTest.java @@ -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); + } +} diff --git a/core-java-modules/core-java-datetime-conversion/src/test/java/com/baeldung/epochtolocaldate/EpochTimeToLocalDateTimeConverterUnitTest.java b/core-java-modules/core-java-datetime-conversion/src/test/java/com/baeldung/epochtolocaldate/EpochTimeToLocalDateTimeConverterUnitTest.java index 6e3b250938..80c403bb4e 100644 --- a/core-java-modules/core-java-datetime-conversion/src/test/java/com/baeldung/epochtolocaldate/EpochTimeToLocalDateTimeConverterUnitTest.java +++ b/core-java-modules/core-java-datetime-conversion/src/test/java/com/baeldung/epochtolocaldate/EpochTimeToLocalDateTimeConverterUnitTest.java @@ -12,10 +12,10 @@ public class EpochTimeToLocalDateTimeConverterUnitTest { @Test public void testConvertEpochTimeToLocalDateTime() { 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); - ZoneId zoneId = ZoneId.systemDefault(); + ZoneId zoneId = ZoneId.of("UTC"); LocalDateTime actualDateTime = instant.atZone(zoneId).toLocalDateTime(); assertEquals(expectedDateTime, actualDateTime); diff --git a/core-java-modules/core-java-io-5/README.md b/core-java-modules/core-java-io-5/README.md index 6072e0b6d9..9fb4b967a4 100644 --- a/core-java-modules/core-java-io-5/README.md +++ b/core-java-modules/core-java-io-5/README.md @@ -3,6 +3,6 @@ This module contains articles about core Java input and output (IO) ### 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) diff --git a/core-java-modules/core-java-os/README.md b/core-java-modules/core-java-os/README.md index 6d477de70a..81e67e4663 100644 --- a/core-java-modules/core-java-os/README.md +++ b/core-java-modules/core-java-os/README.md @@ -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) - [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) +- [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. diff --git a/core-java-modules/core-java-regex-2/README.md b/core-java-modules/core-java-regex-2/README.md index 02e470cce5..404b33b65f 100644 --- a/core-java-modules/core-java-regex-2/README.md +++ b/core-java-modules/core-java-regex-2/README.md @@ -9,4 +9,5 @@ - [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) - [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) diff --git a/core-java-modules/core-java-streams-5/pom.xml b/core-java-modules/core-java-streams-5/pom.xml index dc97d81b3d..d7baf84d30 100644 --- a/core-java-modules/core-java-streams-5/pom.xml +++ b/core-java-modules/core-java-streams-5/pom.xml @@ -43,6 +43,11 @@ vavr ${vavr.version} + + com.google.guava + guava + ${guava.version} + @@ -72,6 +77,7 @@ 12 12 0.10.2 + 32.1.2-jre \ No newline at end of file diff --git a/core-java-modules/core-java-streams-5/src/main/java/com/baeldung/streams/partitioning/PartitionStream.java b/core-java-modules/core-java-streams-5/src/main/java/com/baeldung/streams/partitioning/PartitionStream.java new file mode 100644 index 0000000000..1ef3fa7707 --- /dev/null +++ b/core-java-modules/core-java-streams-5/src/main/java/com/baeldung/streams/partitioning/PartitionStream.java @@ -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 Stream> partitionList(List 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 Iterable> partitionUsingGuava(Stream source, int batchSize) { + return Iterables.partition(source::iterator, batchSize); + } + + public static List> partitionStream(Stream source, int batchSize) { + return source.collect(partitionBySize(batchSize, Collectors.toList())); + } + + public static Collector partitionBySize(int batchSize, Collector, A, R> downstream) { + Supplier> supplier = () -> new Accumulator<>( + batchSize, + downstream.supplier().get(), + downstream.accumulator()::accept + ); + + BiConsumer, T> accumulator = (acc, value) -> acc.add(value); + + BinaryOperator> combiner = (acc1, acc2) -> acc1.combine(acc2, downstream.combiner()); + + Function, 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 { + private final List values = new ArrayList<>(); + private final int batchSize; + private A downstreamAccumulator; + private final BiConsumer> batchFullListener; + + Accumulator(int batchSize, A accumulator, BiConsumer> 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 combine(Accumulator other, BinaryOperator accumulatorCombiner) { + this.downstreamAccumulator = accumulatorCombiner.apply(downstreamAccumulator, other.downstreamAccumulator); + other.values.forEach(this::add); + return this; + } + } + +} diff --git a/core-java-modules/core-java-streams-5/src/test/java/com/baeldung/partitioning/PartitionStreamsUnitTest.java b/core-java-modules/core-java-streams-5/src/test/java/com/baeldung/partitioning/PartitionStreamsUnitTest.java new file mode 100644 index 0000000000..df75e69783 --- /dev/null +++ b/core-java-modules/core-java-streams-5/src/test/java/com/baeldung/partitioning/PartitionStreamsUnitTest.java @@ -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 source = List.of(1, 2, 3, 4, 5, 6, 7, 8); + + Stream> 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> 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 source = Stream.of(1, 2, 3, 4, 5, 6, 7, 8).parallel(); + + List> 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 source = Stream.empty().parallel(); + + List> result = partitionStream(source, 3); + + assertThat(result).isEmpty(); + } + + @Test + void whenPartitionParallelStreamWithGuava_thenReturnThreeSubLists() { + Stream source = Stream.of(1, 2, 3, 4, 5, 6, 7, 8).parallel(); + + Iterable> 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)); + } +} diff --git a/libraries-apache-commons-2/pom.xml b/libraries-apache-commons-2/pom.xml index d771aac9ab..ee9b51e6cc 100644 --- a/libraries-apache-commons-2/pom.xml +++ b/libraries-apache-commons-2/pom.xml @@ -28,12 +28,18 @@ commons-vfs2 ${commons-vfs2.version} + + org.apache.commons + commons-text + ${apache-commons-text.version} + 1.23.0 1.10.13 2.9.0 + 1.10.0 \ No newline at end of file diff --git a/libraries-apache-commons-2/src/main/java/com/baeldung/commons/convertunicode/UnicodeConverterUtil.java b/libraries-apache-commons-2/src/main/java/com/baeldung/commons/convertunicode/UnicodeConverterUtil.java new file mode 100644 index 0000000000..c788f6ee61 --- /dev/null +++ b/libraries-apache-commons-2/src/main/java/com/baeldung/commons/convertunicode/UnicodeConverterUtil.java @@ -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(); + } +} diff --git a/libraries-apache-commons-2/src/test/java/com/baeldung/commons/convertunicode/UnicodeConverterUnitTest.java b/libraries-apache-commons-2/src/test/java/com/baeldung/commons/convertunicode/UnicodeConverterUnitTest.java new file mode 100644 index 0000000000..f4a9bbcb77 --- /dev/null +++ b/libraries-apache-commons-2/src/test/java/com/baeldung/commons/convertunicode/UnicodeConverterUnitTest.java @@ -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)); + } +} diff --git a/libraries-io/pom.xml b/libraries-io/pom.xml index e92f6e11e1..fa89ebeabe 100644 --- a/libraries-io/pom.xml +++ b/libraries-io/pom.xml @@ -5,6 +5,18 @@ 4.0.0 libraries-io libraries-io + + + + org.apache.maven.plugins + maven-compiler-plugin + + 17 + 17 + + + + com.baeldung @@ -34,6 +46,11 @@ zip4j ${zip4j.version} + + com.opencsv + opencsv + ${opencsv.version} + @@ -42,6 +59,10 @@ 0.27.0 2.4 2.9.0 + 5.7.1 + 17 + 17 + UTF-8 \ No newline at end of file diff --git a/libraries-io/src/main/java/com/baeldung/java/io/pojotocsv/Application.java b/libraries-io/src/main/java/com/baeldung/java/io/pojotocsv/Application.java new file mode 100644 index 0000000000..b84da6229b --- /dev/null +++ b/libraries-io/src/main/java/com/baeldung/java/io/pojotocsv/Application.java @@ -0,0 +1,4 @@ +package com.baeldung.java.io.pojotocsv; + +public record Application(String id, String name, Integer age, String created_at) { +} diff --git a/libraries-io/src/main/java/com/baeldung/java/io/pojotocsv/ApplicationWithAnnotation.java b/libraries-io/src/main/java/com/baeldung/java/io/pojotocsv/ApplicationWithAnnotation.java new file mode 100644 index 0000000000..a0cd158843 --- /dev/null +++ b/libraries-io/src/main/java/com/baeldung/java/io/pojotocsv/ApplicationWithAnnotation.java @@ -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) { +} \ No newline at end of file diff --git a/libraries-io/src/main/java/com/baeldung/java/io/pojotocsv/BeanToCsv.java b/libraries-io/src/main/java/com/baeldung/java/io/pojotocsv/BeanToCsv.java new file mode 100644 index 0000000000..f66c16beda --- /dev/null +++ b/libraries-io/src/main/java/com/baeldung/java/io/pojotocsv/BeanToCsv.java @@ -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 applications) throws Exception { + try (FileWriter writer = new FileWriter("src/main/resources/application.csv")) { + var builder = new StatefulBeanToCsvBuilder(writer).withQuotechar(CSVWriter.NO_QUOTE_CHARACTER) + .withSeparator(',') + .build(); + builder.write(applications); + } + } + + public void beanToCSVWithCustomHeaderStrategy(List applications) throws IOException, CsvRequiredFieldEmptyException, CsvDataTypeMismatchException { + try (FileWriter writer = new FileWriter("src/main/resources/application2.csv")) { + var mappingStrategy = new CustomHeaderStrategy(); + mappingStrategy.setType(Application.class); + + var builder = new StatefulBeanToCsvBuilder(writer).withQuotechar(CSVWriter.NO_QUOTE_CHARACTER) + .withMappingStrategy(mappingStrategy) + .build(); + builder.write(applications); + } + } + + public void beanToCSVWithCustomPositionStrategy(List applications) throws Exception { + try (FileWriter writer = new FileWriter("src/main/resources/application3.csv")) { + var builder = new StatefulBeanToCsvBuilder(writer).withQuotechar(CSVWriter.NO_QUOTE_CHARACTER) + .build(); + builder.write(applications); + } + } + + public void beanToCSVWithCustomHeaderAndPositionStrategy(List applications) throws IOException, CsvRequiredFieldEmptyException, CsvDataTypeMismatchException { + try (FileWriter writer = new FileWriter("src/main/resources/application4.csv")) { + var mappingStrategy = new CustomColumnPositionStrategy(); + mappingStrategy.setType(ApplicationWithAnnotation.class); + + var builder = new StatefulBeanToCsvBuilder(writer).withQuotechar(CSVWriter.NO_QUOTE_CHARACTER) + .withMappingStrategy(mappingStrategy) + .build(); + builder.write(applications); + } + } + +} diff --git a/libraries-io/src/main/java/com/baeldung/java/io/pojotocsv/CustomColumnPositionStrategy.java b/libraries-io/src/main/java/com/baeldung/java/io/pojotocsv/CustomColumnPositionStrategy.java new file mode 100644 index 0000000000..204bf3f917 --- /dev/null +++ b/libraries-io/src/main/java/com/baeldung/java/io/pojotocsv/CustomColumnPositionStrategy.java @@ -0,0 +1,12 @@ +package com.baeldung.java.io.pojotocsv; + +import com.opencsv.bean.ColumnPositionMappingStrategy; +import com.opencsv.exceptions.CsvRequiredFieldEmptyException; + +public class CustomColumnPositionStrategy extends ColumnPositionMappingStrategy { + @Override + public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException { + super.generateHeader(bean); + return super.getColumnMapping(); + } +} diff --git a/libraries-io/src/main/java/com/baeldung/java/io/pojotocsv/CustomHeaderStrategy.java b/libraries-io/src/main/java/com/baeldung/java/io/pojotocsv/CustomHeaderStrategy.java new file mode 100644 index 0000000000..8e81c8ec28 --- /dev/null +++ b/libraries-io/src/main/java/com/baeldung/java/io/pojotocsv/CustomHeaderStrategy.java @@ -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 extends HeaderColumnNameMappingStrategy { + @Override + public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException { + String[] header = super.generateHeader(bean); + return Arrays.stream(header) + .map(String::toLowerCase) + .toArray(String[]::new); + } +} diff --git a/libraries-io/src/main/resources/application.csv b/libraries-io/src/main/resources/application.csv new file mode 100644 index 0000000000..d4040e18e1 --- /dev/null +++ b/libraries-io/src/main/resources/application.csv @@ -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 diff --git a/libraries-io/src/main/resources/application2.csv b/libraries-io/src/main/resources/application2.csv new file mode 100644 index 0000000000..92bbced703 --- /dev/null +++ b/libraries-io/src/main/resources/application2.csv @@ -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 diff --git a/libraries-io/src/main/resources/application3.csv b/libraries-io/src/main/resources/application3.csv new file mode 100644 index 0000000000..165107b103 --- /dev/null +++ b/libraries-io/src/main/resources/application3.csv @@ -0,0 +1,3 @@ +Sam,123,34,2023-08-11 +Tam,456,44,2023-02-11 +Jam,789,54,2023-03-11 diff --git a/libraries-io/src/main/resources/application4.csv b/libraries-io/src/main/resources/application4.csv new file mode 100644 index 0000000000..b7999ae227 --- /dev/null +++ b/libraries-io/src/main/resources/application4.csv @@ -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 diff --git a/libraries-io/src/test/java/com/baeldung/java/io/pojotocsv/BeanToCsvUnitTest.java b/libraries-io/src/test/java/com/baeldung/java/io/pojotocsv/BeanToCsvUnitTest.java new file mode 100644 index 0000000000..50cd1b1e0e --- /dev/null +++ b/libraries-io/src/test/java/com/baeldung/java/io/pojotocsv/BeanToCsvUnitTest.java @@ -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 applications = new ArrayList<>(); + List 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 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 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 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 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"); + } + } +} diff --git a/parent-spring-6/pom.xml b/parent-spring-6/pom.xml index 7b61ac524b..7b28afc9b1 100644 --- a/parent-spring-6/pom.xml +++ b/parent-spring-6/pom.xml @@ -35,7 +35,7 @@ - 6.0.10 + 6.0.12 diff --git a/spring-6-rsocket/README.md b/spring-6-rsocket/README.md index 21ab282bd1..95c805b0bf 100644 --- a/spring-6-rsocket/README.md +++ b/spring-6-rsocket/README.md @@ -3,5 +3,5 @@ This module contains articles about RSocket in Spring Framework 6. ### Relevant articles +- [RSocket Interface in Spring 6](https://www.baeldung.com/spring-rsocket) -- [Introduction to RSocket](#) \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-actuator/pom.xml b/spring-boot-modules/spring-boot-actuator/pom.xml index 1ccf436bbf..7f630fa96e 100644 --- a/spring-boot-modules/spring-boot-actuator/pom.xml +++ b/spring-boot-modules/spring-boot-actuator/pom.xml @@ -9,9 +9,10 @@ This is simple boot application for Spring boot actuator test - com.baeldung.spring-boot-modules - spring-boot-modules - 1.0.0-SNAPSHOT + com.baeldung + parent-boot-3 + 0.0.1-SNAPSHOT + ../../parent-boot-3 @@ -39,16 +40,6 @@ com.h2database h2 - - javax.servlet - javax.servlet-api - provided - - - javax.servlet - jstl - runtime - org.springframework.boot spring-boot-starter-test diff --git a/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/endpoints/enabling/SecurityConfiguration.java b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/endpoints/enabling/SecurityConfiguration.java index 894c24693e..20ee834d52 100644 --- a/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/endpoints/enabling/SecurityConfiguration.java +++ b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/endpoints/enabling/SecurityConfiguration.java @@ -1,36 +1,56 @@ package com.baeldung.endpoints.enabling; import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest; +import org.springframework.context.annotation.Bean; 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.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.crypto.factory.PasswordEncoderFactories; -import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +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 -public class SecurityConfiguration extends WebSecurityConfigurerAdapter { +@Configuration +public class SecurityConfiguration { - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder(); - auth.inMemoryAuthentication() - .withUser("user") - .password(encoder.encode("password")) - .roles("USER") - .and() - .withUser("admin") - .password(encoder.encode("admin")) - .roles("USER", "ADMIN"); + @Bean + MvcRequestMatcher.Builder mvc(HandlerMappingIntrospector introspector) { + return new MvcRequestMatcher.Builder(introspector); } - @Override - protected void configure(HttpSecurity http) throws Exception { - http.requestMatcher(EndpointRequest.toAnyEndpoint()) - .authorizeRequests((requests) -> requests.anyRequest() - .hasRole("ADMIN")); - http.httpBasic(); + @Bean + public SecurityFilterChain filterChain(HttpSecurity http, MvcRequestMatcher.Builder mvc) throws Exception { + http.httpBasic(Customizer.withDefaults()); + http.securityMatcher(EndpointRequest.toAnyEndpoint()); + http.authorizeHttpRequests(authz -> { + 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); } } diff --git a/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/endpoints/info/User.java b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/endpoints/info/User.java index db4e69127a..88f39d8ccc 100644 --- a/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/endpoints/info/User.java +++ b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/endpoints/info/User.java @@ -1,9 +1,9 @@ package com.baeldung.endpoints.info; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = "users") diff --git a/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/MetricsApplication.java b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/MetricsApplication.java index 729b3c0b96..ee87412986 100644 --- a/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/MetricsApplication.java +++ b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/MetricsApplication.java @@ -15,7 +15,7 @@ import org.springframework.context.annotation.ComponentScan; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.web.context.request.RequestContextListener; -import javax.servlet.ServletContext; +import jakarta.servlet.ServletContext; @EnableScheduling @ComponentScan("com.baeldung.metrics") diff --git a/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/filter/MetricFilter.java b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/filter/MetricFilter.java index 0f7579f060..a7aaddf0fb 100644 --- a/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/filter/MetricFilter.java +++ b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/filter/MetricFilter.java @@ -7,14 +7,14 @@ import org.springframework.stereotype.Component; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; @Component public class MetricFilter implements Filter { diff --git a/spring-boot-modules/spring-boot-actuator/src/test/java/com/baeldung/endpoints/enabling/EndpointEnablingIntegrationTest.java b/spring-boot-modules/spring-boot-actuator/src/test/java/com/baeldung/endpoints/enabling/EndpointEnablingIntegrationTest.java index 8274619517..079195714b 100644 --- a/spring-boot-modules/spring-boot-actuator/src/test/java/com/baeldung/endpoints/enabling/EndpointEnablingIntegrationTest.java +++ b/spring-boot-modules/spring-boot-actuator/src/test/java/com/baeldung/endpoints/enabling/EndpointEnablingIntegrationTest.java @@ -13,20 +13,20 @@ import org.springframework.test.web.servlet.MockMvc; @SpringBootTest @AutoConfigureMockMvc -public class EndpointEnablingIntegrationTest { +class EndpointEnablingIntegrationTest { @Autowired private MockMvc mockMvc; @Test @WithMockUser(username = "user", password = "password", roles = "USER") - public void givenWrongAuthentication_whenCallingActuator_thenReturns401() throws Exception { + void givenWrongAuthentication_whenCallingActuator_thenReturns401() throws Exception { mockMvc.perform(get("/actuator")) .andExpect(status().isForbidden()); } @Test @WithMockUser(username = "admin", password = "admin", roles = "ADMIN") - public void givenProperAuthentication_whenCallingActuator_thenReturnsExpectedEndpoints() throws Exception { + void givenProperAuthentication_whenCallingActuator_thenReturnsExpectedEndpoints() throws Exception { mockMvc.perform(get("/actuator")) .andExpect(jsonPath("$._links").exists()) .andExpect(jsonPath("$._links.beans").exists()) diff --git a/spring-boot-modules/spring-boot-graalvm-docker/README.md b/spring-boot-modules/spring-boot-graalvm-docker/README.md new file mode 100644 index 0000000000..10a764053e --- /dev/null +++ b/spring-boot-modules/spring-boot-graalvm-docker/README.md @@ -0,0 +1,2 @@ +## Relevant Articles +- [Create a GraalVM Docker Image](https://www.baeldung.com/java-graalvm-docker-image) diff --git a/spring-boot-modules/spring-boot-mvc-4/pom.xml b/spring-boot-modules/spring-boot-mvc-4/pom.xml index dbbb03ad0e..0ae05a764b 100644 --- a/spring-boot-modules/spring-boot-mvc-4/pom.xml +++ b/spring-boot-modules/spring-boot-mvc-4/pom.xml @@ -9,9 +9,10 @@ Module For Spring Boot MVC Web - com.baeldung.spring-boot-modules - spring-boot-modules - 1.0.0-SNAPSHOT + com.baeldung + parent-boot-3 + 0.0.1-SNAPSHOT + ../../parent-boot-3 diff --git a/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/boot/controller/servlet/HelloWorldServlet.java b/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/boot/controller/servlet/HelloWorldServlet.java index 80c75aa8b5..cedc60bd92 100644 --- a/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/boot/controller/servlet/HelloWorldServlet.java +++ b/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/boot/controller/servlet/HelloWorldServlet.java @@ -4,10 +4,10 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.Objects; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; public class HelloWorldServlet extends HttpServlet { private static final long serialVersionUID = 1L; diff --git a/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/boot/controller/servlet/SpringHelloWorldServlet.java b/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/boot/controller/servlet/SpringHelloWorldServlet.java index f276f94b7c..dbb8ea1b32 100644 --- a/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/boot/controller/servlet/SpringHelloWorldServlet.java +++ b/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/boot/controller/servlet/SpringHelloWorldServlet.java @@ -4,10 +4,10 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.Objects; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; public class SpringHelloWorldServlet extends HttpServlet { private static final long serialVersionUID = 1L; diff --git a/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/common/error/SpringHelloServletRegistrationBean.java b/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/common/error/SpringHelloServletRegistrationBean.java index 3f51a4ab69..b2d6f249a5 100644 --- a/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/common/error/SpringHelloServletRegistrationBean.java +++ b/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/common/error/SpringHelloServletRegistrationBean.java @@ -2,7 +2,7 @@ package com.baeldung.common.error; import org.springframework.boot.web.servlet.ServletRegistrationBean; -import javax.servlet.Servlet; +import jakarta.servlet.Servlet; public class SpringHelloServletRegistrationBean extends ServletRegistrationBean { diff --git a/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/servlets/configuration/WebAppInitializer.java b/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/servlets/configuration/WebAppInitializer.java index eadd40355a..7fd83fd59a 100644 --- a/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/servlets/configuration/WebAppInitializer.java +++ b/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/servlets/configuration/WebAppInitializer.java @@ -4,9 +4,9 @@ import org.springframework.web.WebApplicationInitializer; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.context.support.XmlWebApplicationContext; import org.springframework.web.servlet.DispatcherServlet; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.ServletRegistration; +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRegistration; public class WebAppInitializer implements WebApplicationInitializer { diff --git a/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/servlets/servlets/GenericCustomServlet.java b/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/servlets/servlets/GenericCustomServlet.java index 49dd9404b7..ee70a142be 100644 --- a/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/servlets/servlets/GenericCustomServlet.java +++ b/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/servlets/servlets/GenericCustomServlet.java @@ -1,9 +1,9 @@ package com.baeldung.servlets.servlets; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; diff --git a/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/servlets/servlets/javaee/AnnotationServlet.java b/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/servlets/servlets/javaee/AnnotationServlet.java index 992976ca0e..62706be348 100644 --- a/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/servlets/servlets/javaee/AnnotationServlet.java +++ b/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/servlets/servlets/javaee/AnnotationServlet.java @@ -1,10 +1,10 @@ package com.baeldung.servlets.servlets.javaee; -import javax.servlet.ServletException; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.ServletException; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet(name = "AnnotationServlet", description = "Example Servlet Using Annotations", urlPatterns = { "/annotationservlet" }) diff --git a/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/servlets/servlets/javaee/EEWebXmlServlet.java b/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/servlets/servlets/javaee/EEWebXmlServlet.java index c7b373064f..299a1b5d95 100644 --- a/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/servlets/servlets/javaee/EEWebXmlServlet.java +++ b/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/servlets/servlets/javaee/EEWebXmlServlet.java @@ -1,9 +1,9 @@ package com.baeldung.servlets.servlets.javaee; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; diff --git a/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/utils/UtilsApplication.java b/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/utils/UtilsApplication.java index ce3eae7ce0..eab5869cd5 100644 --- a/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/utils/UtilsApplication.java +++ b/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/utils/UtilsApplication.java @@ -1,6 +1,6 @@ package com.baeldung.utils; -import javax.annotation.security.RolesAllowed; +import jakarta.annotation.security.RolesAllowed; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/utils/controller/UtilsController.java b/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/utils/controller/UtilsController.java index 8c7f2f932a..40acc1801f 100644 --- a/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/utils/controller/UtilsController.java +++ b/spring-boot-modules/spring-boot-mvc-4/src/main/java/com/baeldung/utils/controller/UtilsController.java @@ -1,6 +1,6 @@ package com.baeldung.utils.controller; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; diff --git a/spring-boot-modules/spring-boot-mvc-4/src/test/java/com/baeldung/utils/UtilsControllerIntegrationTest.java b/spring-boot-modules/spring-boot-mvc-4/src/test/java/com/baeldung/utils/UtilsControllerIntegrationTest.java index 080f660c40..f59d00f871 100644 --- a/spring-boot-modules/spring-boot-mvc-4/src/test/java/com/baeldung/utils/UtilsControllerIntegrationTest.java +++ b/spring-boot-modules/spring-boot-mvc-4/src/test/java/com/baeldung/utils/UtilsControllerIntegrationTest.java @@ -1,32 +1,34 @@ package com.baeldung.utils; 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.MockitoAnnotations; + import org.springframework.test.web.servlet.MockMvc; 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.result.MockMvcResultMatchers.status; -public class UtilsControllerIntegrationTest { +class UtilsControllerIntegrationTest { @InjectMocks private UtilsController utilsController; private MockMvc mockMvc; - @Before + @BeforeEach public void setup() { - MockitoAnnotations.initMocks(this); + openMocks(this); this.mockMvc = MockMvcBuilders.standaloneSetup(utilsController).build(); } @Test - public void givenParameter_setRequestParam_andSetSessionAttribute() throws Exception { + void givenParameter_setRequestParam_andSetSessionAttribute() throws Exception { String param = "testparam"; this.mockMvc.perform(post("/setParam").param("param", param).sessionAttr("parameter", param)).andExpect(status().isOk()); } diff --git a/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/README.md b/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/README.md new file mode 100644 index 0000000000..b76ae19f26 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/README.md @@ -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) \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/pom.xml b/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/pom.xml new file mode 100644 index 0000000000..3d64edc338 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/pom.xml @@ -0,0 +1,54 @@ + + + 4.0.0 + gateway-2 + gateway-2 + jar + + + com.baeldung + parent-boot-2 + 0.0.1-SNAPSHOT + ../../../parent-boot-2 + + + + + + org.junit + junit-bom + ${junit-jupiter.version} + pom + import + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud-dependencies.version} + pom + import + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + + + org.springframework.cloud + spring-cloud-starter-gateway + + + + + 2021.0.3 + + + \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/main/java/com/baeldung/springcloudgateway/custompredicates/CustomPredicatesApplication.java b/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/main/java/com/baeldung/springcloudgateway/custompredicates/CustomPredicatesApplication.java new file mode 100644 index 0000000000..e209b6cdf0 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/main/java/com/baeldung/springcloudgateway/custompredicates/CustomPredicatesApplication.java @@ -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); + } + +} \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/main/java/com/baeldung/springcloudgateway/custompredicates/config/CustomPredicatesConfig.java b/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/main/java/com/baeldung/springcloudgateway/custompredicates/config/CustomPredicatesConfig.java new file mode 100644 index 0000000000..ea58eb7e46 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/main/java/com/baeldung/springcloudgateway/custompredicates/config/CustomPredicatesConfig.java @@ -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(); + } + +} diff --git a/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/main/java/com/baeldung/springcloudgateway/custompredicates/factories/GoldenCustomerRoutePredicateFactory.java b/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/main/java/com/baeldung/springcloudgateway/custompredicates/factories/GoldenCustomerRoutePredicateFactory.java new file mode 100644 index 0000000000..cb5c3a0b50 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/main/java/com/baeldung/springcloudgateway/custompredicates/factories/GoldenCustomerRoutePredicateFactory.java @@ -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 { + + private final GoldenCustomerService goldenCustomerService; + + public GoldenCustomerRoutePredicateFactory(GoldenCustomerService goldenCustomerService ) { + super(Config.class); + this.goldenCustomerService = goldenCustomerService; + } + + + @Override + public List shortcutFieldOrder() { + return Arrays.asList("isGolden","customerIdCookie"); + } + + + @Override + public Predicate apply(Config config) { + + return (ServerWebExchange t) -> { + List 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; + } + + + + } + +} diff --git a/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/main/java/com/baeldung/springcloudgateway/custompredicates/service/GoldenCustomerService.java b/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/main/java/com/baeldung/springcloudgateway/custompredicates/service/GoldenCustomerService.java new file mode 100644 index 0000000000..82bf2e6ae9 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/main/java/com/baeldung/springcloudgateway/custompredicates/service/GoldenCustomerService.java @@ -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; + } + } + +} diff --git a/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/main/java/com/baeldung/springcloudgateway/introduction/IntroductionGatewayApplication.java b/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/main/java/com/baeldung/springcloudgateway/introduction/IntroductionGatewayApplication.java new file mode 100644 index 0000000000..d276597a6b --- /dev/null +++ b/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/main/java/com/baeldung/springcloudgateway/introduction/IntroductionGatewayApplication.java @@ -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); + } + +} \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/main/resources/application-customroutes.yml b/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/main/resources/application-customroutes.yml new file mode 100644 index 0000000000..859aa60bda --- /dev/null +++ b/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/main/resources/application-customroutes.yml @@ -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 + + + \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/main/resources/application.yml b/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/main/resources/application.yml new file mode 100644 index 0000000000..a33bca2055 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/main/resources/application.yml @@ -0,0 +1,4 @@ +logging: + level: + org.springframework.cloud.gateway: DEBUG + reactor.netty.http.client: DEBUG diff --git a/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/main/resources/introduction-application.properties b/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/main/resources/introduction-application.properties new file mode 100644 index 0000000000..d7a6c4e072 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/main/resources/introduction-application.properties @@ -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 diff --git a/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/main/resources/logback.xml b/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/main/resources/logback.xml new file mode 100644 index 0000000000..7d900d8ea8 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/main/resources/logback.xml @@ -0,0 +1,13 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/test/java/com/baeldung/springcloudgateway/custompredicates/CustomPredicatesApplicationLiveTest.java b/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/test/java/com/baeldung/springcloudgateway/custompredicates/CustomPredicatesApplicationLiveTest.java new file mode 100644 index 0000000000..d9988ceb5e --- /dev/null +++ b/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/test/java/com/baeldung/springcloudgateway/custompredicates/CustomPredicatesApplicationLiveTest.java @@ -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 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 request = RequestEntity + .get(URI.create(url)) + .header("Cookie", "customerId=baeldung") + .build(); + + ResponseEntity 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"); + + } + +} diff --git a/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/test/java/com/baeldung/springcloudgateway/introduction/LoggerListAppender.java b/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/test/java/com/baeldung/springcloudgateway/introduction/LoggerListAppender.java new file mode 100644 index 0000000000..33855cd15d --- /dev/null +++ b/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/test/java/com/baeldung/springcloudgateway/introduction/LoggerListAppender.java @@ -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 { + + static private List events = new ArrayList<>(); + + @Override + protected void append(ILoggingEvent eventObject) { + events.add(eventObject); + } + + public static List getEvents() { + return events; + } + + public static void clearEventList() { + events.clear(); + } +} \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/test/java/com/baeldung/springcloudgateway/introduction/SpringContextTest.java b/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/test/java/com/baeldung/springcloudgateway/introduction/SpringContextTest.java new file mode 100644 index 0000000000..1550265f22 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/test/java/com/baeldung/springcloudgateway/introduction/SpringContextTest.java @@ -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() { + } +} diff --git a/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/test/resources/logback-test.xml b/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..90c8f570aa --- /dev/null +++ b/spring-cloud-modules/spring-cloud-bootstrap/gateway-2/src/test/resources/logback-test.xml @@ -0,0 +1,17 @@ + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-bootstrap/pom.xml b/spring-cloud-modules/spring-cloud-bootstrap/pom.xml index e7fe7e7485..f4d4073ef1 100644 --- a/spring-cloud-modules/spring-cloud-bootstrap/pom.xml +++ b/spring-cloud-modules/spring-cloud-bootstrap/pom.xml @@ -18,6 +18,7 @@ config discovery gateway + gateway-2 svc-book svc-rating customer-service diff --git a/spring-kafka-2/src/main/java/com/baeldung/spring/kafka/multipletopics/KafkaConsumerConfig.java b/spring-kafka-2/src/main/java/com/baeldung/spring/kafka/multipletopics/KafkaConsumerConfig.java new file mode 100644 index 0000000000..741fb6bba4 --- /dev/null +++ b/spring-kafka-2/src/main/java/com/baeldung/spring/kafka/multipletopics/KafkaConsumerConfig.java @@ -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 consumerFactory() { + Map config = new HashMap<>(); + config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); + + return new DefaultKafkaConsumerFactory<>(config, new StringDeserializer(), new JsonDeserializer<>(PaymentData.class)); + } + + @Bean + public ConcurrentKafkaListenerContainerFactory kafkaListenerContainerFactory() { + ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); + factory.setConsumerFactory(consumerFactory()); + return factory; + } +} \ No newline at end of file diff --git a/spring-kafka-2/src/main/java/com/baeldung/spring/kafka/multipletopics/KafkaMultipleTopicsApplication.java b/spring-kafka-2/src/main/java/com/baeldung/spring/kafka/multipletopics/KafkaMultipleTopicsApplication.java new file mode 100644 index 0000000000..2135a27f39 --- /dev/null +++ b/spring-kafka-2/src/main/java/com/baeldung/spring/kafka/multipletopics/KafkaMultipleTopicsApplication.java @@ -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); + } +} \ No newline at end of file diff --git a/spring-kafka-2/src/main/java/com/baeldung/spring/kafka/multipletopics/KafkaProducerConfig.java b/spring-kafka-2/src/main/java/com/baeldung/spring/kafka/multipletopics/KafkaProducerConfig.java new file mode 100644 index 0000000000..2cb0117bf1 --- /dev/null +++ b/spring-kafka-2/src/main/java/com/baeldung/spring/kafka/multipletopics/KafkaProducerConfig.java @@ -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 producerFactory() { + Map config = new HashMap<>(); + config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); + + return new DefaultKafkaProducerFactory<>(config, new StringSerializer(), new JsonSerializer<>()); + } + + @Bean + public KafkaTemplate kafkaProducer() { + return new KafkaTemplate<>(producerFactory()); + } +} \ No newline at end of file diff --git a/spring-kafka-2/src/main/java/com/baeldung/spring/kafka/multipletopics/PaymentData.java b/spring-kafka-2/src/main/java/com/baeldung/spring/kafka/multipletopics/PaymentData.java new file mode 100644 index 0000000000..e81138c089 --- /dev/null +++ b/spring-kafka-2/src/main/java/com/baeldung/spring/kafka/multipletopics/PaymentData.java @@ -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(); + } +} \ No newline at end of file diff --git a/spring-kafka-2/src/main/java/com/baeldung/spring/kafka/multipletopics/PaymentDataListener.java b/spring-kafka-2/src/main/java/com/baeldung/spring/kafka/multipletopics/PaymentDataListener.java new file mode 100644 index 0000000000..fb640cca25 --- /dev/null +++ b/spring-kafka-2/src/main/java/com/baeldung/spring/kafka/multipletopics/PaymentDataListener.java @@ -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); + } +} \ No newline at end of file diff --git a/spring-kafka-2/src/test/java/com/baeldung/spring/kafka/multipletopics/KafkaMultipleTopicsIntegrationTest.java b/spring-kafka-2/src/test/java/com/baeldung/spring/kafka/multipletopics/KafkaMultipleTopicsIntegrationTest.java new file mode 100644 index 0000000000..345e84b65b --- /dev/null +++ b/spring-kafka-2/src/test/java/com/baeldung/spring/kafka/multipletopics/KafkaMultipleTopicsIntegrationTest.java @@ -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 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; + } +} \ No newline at end of file diff --git a/testing-modules/junit-5-advanced/README.md b/testing-modules/junit-5-advanced/README.md index a89bcd2de2..873ab0835b 100644 --- a/testing-modules/junit-5-advanced/README.md +++ b/testing-modules/junit-5-advanced/README.md @@ -9,3 +9,4 @@ - [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) - [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)