Merge branch 'master' into master

This commit is contained in:
Loredana Crusoveanu 2023-06-02 16:23:25 +03:00 committed by GitHub
commit 2c0482c409
136 changed files with 2488 additions and 618 deletions

View File

@ -90,6 +90,12 @@
</exclusions>
</dependency>
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-netty</artifactId>
<version>${mockserver.version}</version>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
@ -112,6 +118,7 @@
<!-- util -->
<httpasyncclient.version>4.1.4</httpasyncclient.version>
<!-- testing -->
<mockserver.version>5.6.1</mockserver.version>
<wiremock.version>2.5.1</wiremock.version>
<httpclient.version>4.5.8</httpclient.version> <!-- 4.3.6 --> <!-- 4.4-beta1 -->
<!-- http client & core 5 -->

View File

@ -0,0 +1,80 @@
package com.baeldung.httpclient;
import static org.mockserver.integration.ClientAndServer.startClientAndServer;
import static org.mockserver.matchers.Times.exactly;
import static org.mockserver.model.HttpRequest.request;
import static org.mockserver.model.HttpResponse.response;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.URISyntaxException;
import org.apache.http.HttpStatus;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.mockserver.client.MockServerClient;
import org.mockserver.integration.ClientAndServer;
public class GetRequestMockServer {
public static ClientAndServer mockServer;
public static String serviceOneUrl;
public static String serviceTwoUrl;
private static int serverPort;
public static final String SERVER_ADDRESS = "127.0.0.1";
public static final String PATH_ONE = "/test1";
public static final String PATH_TWO = "/test2";
public static final String METHOD = "GET";
@BeforeAll
static void startServer() throws IOException, URISyntaxException {
//serverPort = getFreePort();
serverPort = 8080;
System.out.println("Free port "+serverPort);
serviceOneUrl = "http://" + SERVER_ADDRESS + ":" + serverPort + PATH_ONE;
serviceTwoUrl = "http://" + SERVER_ADDRESS + ":" + serverPort + PATH_TWO;
mockServer = startClientAndServer(serverPort);
mockGetRequest();
}
@AfterAll
static void stopServer() {
mockServer.stop();
}
private static void mockGetRequest() {
new MockServerClient(SERVER_ADDRESS, serverPort)
.when(
request()
.withPath(PATH_ONE)
.withMethod(METHOD),
exactly(5)
)
.respond(
response()
.withStatusCode(HttpStatus.SC_OK)
.withBody("{\"status\":\"ok\"}")
);
new MockServerClient(SERVER_ADDRESS, serverPort)
.when(
request()
.withPath(PATH_TWO)
.withMethod(METHOD),
exactly(1)
)
.respond(
response()
.withStatusCode(HttpStatus.SC_OK)
.withBody("{\"status\":\"ok\"}")
);
}
private static int getFreePort () throws IOException {
try (ServerSocket serverSocket = new ServerSocket(0)) {
return serverSocket.getLocalPort();
}
}
}

View File

@ -38,7 +38,7 @@ import org.apache.hc.core5.ssl.SSLContexts;
import org.apache.hc.core5.ssl.TrustStrategy;
class HttpAsyncClientLiveTest {
class HttpAsyncClientLiveTest extends GetRequestMockServer {
private static final String HOST = "http://www.google.com";
private static final String HOST_WITH_SSL = "https://mms.nw.ru/";

View File

@ -9,3 +9,5 @@ You can build the project from the command line using: *mvn clean install*, or i
- [Guide to Check if Apache Kafka Server Is Running](https://www.baeldung.com/apache-kafka-check-server-is-running)
- [Add Custom Headers to a Kafka Message](https://www.baeldung.com/java-kafka-custom-headers)
- [Get Last N Messages in Apache Kafka Topic](https://www.baeldung.com/java-apache-kafka-get-last-n-messages)
- [Is a Key Required as Part of Sending Messages to Kafka?](https://www.baeldung.com/java-kafka-message-key)
- [Read Data From the Beginning Using Kafka Consumer API](https://www.baeldung.com/java-kafka-consumer-api-read)

View File

@ -0,0 +1,81 @@
package com.baeldung.kafka.consumer;
import java.time.Duration;
import java.util.Arrays;
import java.util.Properties;
import java.util.UUID;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ConsumeFromBeginning {
private static Logger logger = LoggerFactory.getLogger(ConsumeFromBeginning.class);
private static String TOPIC = "baeldung";
private static int messagesInTopic = 10;
private static KafkaProducer<String, String> producer;
private static KafkaConsumer<String, String> consumer;
public static void main(String[] args) {
setup();
publishMessages();
consumeFromBeginning();
}
private static void consumeFromBeginning() {
consumer.subscribe(Arrays.asList(TOPIC));
ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(10));
for (ConsumerRecord<String, String> record : records) {
logger.info(record.value());
}
consumer.seekToBeginning(consumer.assignment());
records = consumer.poll(Duration.ofSeconds(10));
for (ConsumerRecord<String, String> record : records) {
logger.info(record.value());
}
}
private static void publishMessages() {
for (int i = 1; i <= messagesInTopic; i++) {
ProducerRecord<String, String> record = new ProducerRecord<>(TOPIC, String.valueOf(i));
producer.send(record);
}
}
private static void setup() {
Properties producerProperties = new Properties();
producerProperties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
producerProperties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
producerProperties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
Properties consumerProperties = new Properties();
consumerProperties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
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, UUID.randomUUID()
.toString());
producer = new KafkaProducer<>(producerProperties);
consumer = new KafkaConsumer<>(consumerProperties);
}
}

View File

@ -0,0 +1,106 @@
package com.baeldung.kafka.message;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import org.apache.kafka.clients.admin.Admin;
import org.apache.kafka.clients.admin.AdminClientConfig;
import org.apache.kafka.clients.admin.NewTopic;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
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.clients.producer.RecordMetadata;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MessageWithKey {
private static Logger logger = LoggerFactory.getLogger(MessageWithKey.class);
private static String TOPIC = "baeldung";
private static int PARTITIONS = 5;
private static short REPLICATION_FACTOR = 1;
private static String MESSAGE_KEY = "message-key";
private static Admin admin;
private static KafkaProducer<String, String> producer;
private static KafkaConsumer<String, String> consumer;
public static void main(String[] args) throws ExecutionException, InterruptedException {
setup();
publishMessagesWithoutKey();
consumeMessages();
publishMessagesWithKey();
consumeMessages();
}
private static void consumeMessages() {
consumer.subscribe(Arrays.asList(TOPIC));
ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(5));
for (ConsumerRecord<String, String> record : records) {
logger.info("Key : {}, Value : {}", record.key(), record.value());
}
}
private static void publishMessagesWithKey() throws ExecutionException, InterruptedException {
for (int i = 1; i <= 10; i++) {
ProducerRecord<String, String> record = new ProducerRecord<>(TOPIC, MESSAGE_KEY, String.valueOf(i));
Future<RecordMetadata> future = producer.send(record);
RecordMetadata metadata = future.get();
logger.info(String.valueOf(metadata.partition()));
}
}
private static void publishMessagesWithoutKey() throws ExecutionException, InterruptedException {
for (int i = 1; i <= 10; i++) {
ProducerRecord<String, String> record = new ProducerRecord<>(TOPIC, String.valueOf(i));
Future<RecordMetadata> future = producer.send(record);
RecordMetadata metadata = future.get();
logger.info(String.valueOf(metadata.partition()));
}
}
private static void setup() {
Properties adminProperties = new Properties();
adminProperties.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
Properties producerProperties = new Properties();
producerProperties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
producerProperties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
producerProperties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
Properties consumerProperties = new Properties();
consumerProperties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
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, UUID.randomUUID()
.toString());
admin = Admin.create(adminProperties);
producer = new KafkaProducer<>(producerProperties);
consumer = new KafkaConsumer<>(consumerProperties);
admin.createTopics(Collections.singleton(new NewTopic(TOPIC, PARTITIONS, REPLICATION_FACTOR)));
}
}

View File

@ -0,0 +1,109 @@
package com.baeldung.kafka.consumer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.time.Duration;
import java.util.Arrays;
import java.util.Properties;
import java.util.UUID;
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.ConsumerRecords;
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 ConsumeFromBeginningLiveTest {
private static Logger logger = LoggerFactory.getLogger(ConsumeFromBeginningLiveTest.class);
private static String TOPIC = "baeldung";
private static int messagesInTopic = 10;
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);
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());
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, UUID.randomUUID()
.toString());
producer = new KafkaProducer<>(producerProperties);
consumer = new KafkaConsumer<>(consumerProperties);
}
private static void publishMessages() throws ExecutionException, InterruptedException {
for (int i = 1; i <= messagesInTopic; i++) {
ProducerRecord<String, String> record = new ProducerRecord<>(TOPIC, String.valueOf(i));
producer.send(record)
.get();
}
}
@AfterAll
static void destroy() {
KAFKA_CONTAINER.stop();
}
@Test
void givenMessages_whenConsumedFromBeginning_thenCheckIfConsumedFromBeginning() throws ExecutionException, InterruptedException {
publishMessages();
consumer.subscribe(Arrays.asList(TOPIC));
ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(10));
int messageCount = 0;
for (ConsumerRecord<String, String> record : records) {
logger.info(record.value());
messageCount++;
}
assertEquals(messagesInTopic, messageCount);
consumer.seekToBeginning(consumer.assignment());
records = consumer.poll(Duration.ofSeconds(10));
messageCount = 0;
for (ConsumerRecord<String, String> record : records) {
logger.info(record.value());
messageCount++;
}
assertEquals(messagesInTopic, messageCount);
}
}

View File

@ -0,0 +1,130 @@
package com.baeldung.kafka.message;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import org.apache.kafka.clients.admin.Admin;
import org.apache.kafka.clients.admin.AdminClientConfig;
import org.apache.kafka.clients.admin.NewTopic;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
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.clients.producer.RecordMetadata;
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.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 MessageWithKeyLiveTest {
private static String TOPIC = "baeldung";
private static int PARTITIONS = 5;
private static short REPLICATION_FACTOR = 1;
private static String MESSAGE_KEY = "message-key";
private static String MESSAGE_VALUE = "Hello World";
private static Admin admin;
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);
Properties adminProperties = new Properties();
adminProperties.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA_CONTAINER.getBootstrapServers());
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());
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, UUID.randomUUID()
.toString());
admin = Admin.create(adminProperties);
producer = new KafkaProducer<>(producerProperties);
consumer = new KafkaConsumer<>(consumerProperties);
admin.createTopics(Collections.singleton(new NewTopic(TOPIC, PARTITIONS, REPLICATION_FACTOR)));
}
@AfterAll
static void destroy() {
KAFKA_CONTAINER.stop();
}
@Test
void givenAMessageWithKey_whenPublishedToKafkaAndConsumed_thenCheckForKey() throws ExecutionException, InterruptedException {
ProducerRecord<String, String> producerRecord = new ProducerRecord<>(TOPIC, MESSAGE_KEY, MESSAGE_VALUE);
Future<RecordMetadata> future = producer.send(producerRecord);
RecordMetadata metadata = future.get();
assertNotNull(metadata);
consumer.subscribe(Arrays.asList(TOPIC));
ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(5));
for (ConsumerRecord<String, String> consumerRecord : records) {
assertEquals(MESSAGE_KEY, consumerRecord.key());
assertEquals(MESSAGE_VALUE, consumerRecord.value());
}
}
@Test
void givenAListOfMessageWithKeys_whenPublishedToKafka_thenCheckedIfPublishedToSamePartition() throws ExecutionException, InterruptedException {
boolean isSamePartition = true;
int partition = 0;
for (int i = 1; i <= 10; i++) {
ProducerRecord<String, String> producerRecord = new ProducerRecord<>(TOPIC, MESSAGE_KEY, MESSAGE_VALUE);
Future<RecordMetadata> future = producer.send(producerRecord);
RecordMetadata metadata = future.get();
assertNotNull(metadata);
if (i == 1) {
partition = metadata.partition();
} else {
if (partition != metadata.partition()) {
isSamePartition = false;
}
}
}
assertTrue(isSamePartition);
}
}

View File

@ -1,7 +1,7 @@
<?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">
<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>apache-poi-2</artifactId>
<version>0.0.1-SNAPSHOT</version>
@ -19,10 +19,15 @@
<artifactId>poi-ooxml</artifactId>
<version>${poi.version}</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>${poi.version}</version>
</dependency>
</dependencies>
<properties>
<poi.version>5.2.0</poi.version>
<poi.version>5.2.3</poi.version>
</properties>
</project>

View File

@ -0,0 +1,38 @@
package com.baeldung.poi.replacevariables;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.usermodel.Range;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
public class DocTextReplacer {
public void replaceText() throws IOException {
String filePath = getClass().getClassLoader()
.getResource("baeldung.doc")
.getPath();
try (InputStream inputStream = new FileInputStream(filePath); POIFSFileSystem fileSystem = new POIFSFileSystem(inputStream)) {
HWPFDocument doc = new HWPFDocument(fileSystem);
doc = replaceText(doc, "Baeldung", "Hello");
saveFile(filePath, doc);
doc.close();
}
}
private HWPFDocument replaceText(HWPFDocument doc, String originalText, String updatedText) {
Range range = doc.getRange();
range.replaceText(originalText, updatedText);
return doc;
}
private void saveFile(String filePath, HWPFDocument doc) throws IOException {
try (FileOutputStream out = new FileOutputStream(filePath)) {
doc.write(out);
}
}
}

View File

@ -0,0 +1,63 @@
package com.baeldung.poi.replacevariables;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
public class DocxNaiveTextReplacer {
public void replaceText() throws IOException {
String filePath = getClass().getClassLoader()
.getResource("baeldung-copy.docx")
.getPath();
try (InputStream inputStream = new FileInputStream(filePath)) {
XWPFDocument doc = new XWPFDocument(inputStream);
doc = replaceText(doc, "Baeldung", "Hello");
saveFile(filePath, doc);
doc.close();
}
}
private XWPFDocument replaceText(XWPFDocument doc, String originalText, String updatedText) {
replaceTextInParagraphs(doc.getParagraphs(), originalText, updatedText);
for (XWPFTable tbl : doc.getTables()) {
for (XWPFTableRow row : tbl.getRows()) {
for (XWPFTableCell cell : row.getTableCells()) {
replaceTextInParagraphs(cell.getParagraphs(), originalText, updatedText);
}
}
}
return doc;
}
private void replaceTextInParagraphs(List<XWPFParagraph> paragraphs, String originalText, String updatedText) {
paragraphs.forEach(paragraph -> replaceTextInParagraph(paragraph, originalText, updatedText));
}
private void replaceTextInParagraph(XWPFParagraph paragraph, String originalText, String updatedText) {
List<XWPFRun> runs = paragraph.getRuns();
for (XWPFRun run : runs) {
String text = run.getText(0);
if (text != null && text.contains(originalText)) {
String updatedRunText = text.replace(originalText, updatedText);
run.setText(updatedRunText, 0);
}
}
}
private void saveFile(String filePath, XWPFDocument doc) throws IOException {
try (FileOutputStream out = new FileOutputStream(filePath)) {
doc.write(out);
}
}
}

View File

@ -0,0 +1,65 @@
package com.baeldung.poi.replacevariables;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Objects;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
public class DocxTextReplacer {
public void replaceText() throws IOException {
String filePath = getClass().getClassLoader()
.getResource("baeldung.docx")
.getPath();
try (InputStream inputStream = new FileInputStream(filePath)) {
XWPFDocument doc = new XWPFDocument(inputStream);
doc = replaceText(doc, "Baeldung", "Hello");
saveFile(filePath, doc);
doc.close();
}
}
private XWPFDocument replaceText(XWPFDocument doc, String originalText, String updatedText) {
replaceTextInParagraphs(doc.getParagraphs(), originalText, updatedText);
for (XWPFTable tbl : doc.getTables()) {
for (XWPFTableRow row : tbl.getRows()) {
for (XWPFTableCell cell : row.getTableCells()) {
replaceTextInParagraphs(cell.getParagraphs(), originalText, updatedText);
}
}
}
return doc;
}
private void replaceTextInParagraphs(List<XWPFParagraph> paragraphs, String originalText, String updatedText) {
paragraphs.forEach(paragraph -> replaceTextInParagraph(paragraph, originalText, updatedText));
}
private void replaceTextInParagraph(XWPFParagraph paragraph, String originalText, String updatedText) {
String paragraphText = paragraph.getParagraphText();
if (paragraphText.contains(originalText)) {
String updatedParagraphText = paragraphText.replace(originalText, updatedText);
while (paragraph.getRuns().size() > 0) {
paragraph.removeRun(0);
}
XWPFRun newRun = paragraph.createRun();
newRun.setText(updatedParagraphText);
}
}
private void saveFile(String filePath, XWPFDocument doc) throws IOException {
try (FileOutputStream out = new FileOutputStream(filePath)) {
doc.write(out);
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,31 @@
package com.baeldung.poi.replacevariables;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.extractor.WordExtractor;
import org.junit.jupiter.api.Test;
class DocTextReplacerUnitTest {
@Test
void whenReplaceText_ThenTextReplaced() throws IOException {
new DocTextReplacer().replaceText();
String filePath = getClass().getClassLoader()
.getResource("baeldung.doc")
.getPath();
try (FileInputStream fis = new FileInputStream(filePath); HWPFDocument document = new HWPFDocument(fis); WordExtractor extractor = new WordExtractor(document)) {
long occurrencesOfHello = Arrays.stream(extractor.getText()
.split("\\s+"))
.filter(s -> s.contains("Hello"))
.count();
assertEquals(5, occurrencesOfHello);
}
}
}

View File

@ -0,0 +1,31 @@
package com.baeldung.poi.replacevariables;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.junit.jupiter.api.Test;
class DocxNaiveTextReplacerUnitTest {
@Test
void whenReplaceText_ThenTextReplaced() throws IOException {
new DocxNaiveTextReplacer().replaceText();
String filePath = getClass().getClassLoader()
.getResource("baeldung-copy.docx")
.getPath();
try (FileInputStream fis = new FileInputStream(filePath); XWPFDocument document = new XWPFDocument(fis); XWPFWordExtractor extractor = new XWPFWordExtractor(document)) {
long occurrencesOfHello = Arrays.stream(extractor.getText()
.split("\\s+"))
.filter(s -> s.contains("Hello"))
.count();
assertTrue(occurrencesOfHello < 5);
}
}
}

View File

@ -0,0 +1,31 @@
package com.baeldung.poi.replacevariables;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.junit.jupiter.api.Test;
class DocxTestReplacerUnitTest {
@Test
void whenReplaceText_ThenTextReplaced() throws IOException {
new DocxTextReplacer().replaceText();
String filePath = getClass().getClassLoader()
.getResource("baeldung.docx")
.getPath();
try (FileInputStream fis = new FileInputStream(filePath); XWPFDocument document = new XWPFDocument(fis); XWPFWordExtractor extractor = new XWPFWordExtractor(document)) {
long occurrencesOfHello = Arrays.stream(extractor.getText()
.split("\\s+"))
.filter(s -> s.contains("Hello"))
.count();
assertEquals(5, occurrencesOfHello);
}
}
}

View File

@ -7,3 +7,5 @@ This module contains articles about the Java List collection
- [Finding All Duplicates in a List in Java](https://www.baeldung.com/java-list-find-duplicates)
- [Moving Items Around in an Arraylist](https://www.baeldung.com/java-arraylist-move-items)
- [Check if a List Contains an Element From Another List in Java](https://www.baeldung.com/java-check-elements-between-lists)
- [Array vs. List Performance in Java](https://www.baeldung.com/java-array-vs-list-performance)
- [Set Default Value for Elements in List](https://www.baeldung.com/java-list-set-default-values)

View File

@ -1,2 +1,3 @@
## Relevant Articles
- [Copying All Keys and Values From One Hashmap Onto Another Without Replacing Existing Keys and Values](https://www.baeldung.com/java-copy-hashmap-no-changes)
- [Convert Hashmap to JSON Object in Java](https://www.baeldung.com/java-convert-hashmap-to-json-object)

View File

@ -0,0 +1,76 @@
package java.com.baeldung.objecttomap;
import com.google.gson.Gson;
import org.junit.Assert;
import org.junit.Test;
import wiremock.com.fasterxml.jackson.core.type.TypeReference;
import wiremock.com.fasterxml.jackson.databind.ObjectMapper;
import wiremock.com.google.common.reflect.TypeToken;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class ObjectToMapUnitTest {
Employee employee = new Employee("John", 3000.0);
@Test
public void givenJavaObject_whenUsingReflection_thenConvertToMap() throws IllegalAccessException {
Map<String, Object> map = convertUsingReflection(employee);
Assert.assertEquals(employee.getName(), map.get("name"));
Assert.assertEquals(employee.getSalary(), map.get("salary"));
}
private Map<String, Object> convertUsingReflection(Object object) throws IllegalAccessException {
Map<String, Object> map = new HashMap<>();
Field[] fields = object.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
map.put(field.getName(), field.get(object));
}
return map;
}
@Test
public void givenJavaObject_whenUsingJackson_thenConvertToMap() {
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> map = objectMapper.convertValue(employee, new TypeReference<Map<String, Object>>() {});
Assert.assertEquals(employee.getName(), map.get("name"));
Assert.assertEquals(employee.getSalary(), map.get("salary"));
}
@Test
public void givenJavaObject_whenUsingGson_thenConvertToMap() {
Gson gson = new Gson();
String json = gson.toJson(employee);
Map<String, Object> map = gson.fromJson(json, new TypeToken<Map<String, Object>>() {}.getType());
Assert.assertEquals(employee.getName(), map.get("name"));
Assert.assertEquals(employee.getSalary(), map.get("salary"));
}
private static class Employee {
private String name;
private Double salary;
public Employee(String name, Double salary) {
this.name = name;
this.salary = salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getSalary() {
return salary;
}
public void setSalary(Double age) {
this.salary = salary;
}
}
}

View File

@ -12,3 +12,5 @@ This module contains articles about core Java input/output(IO) APIs.
- [Storing Java Scanner Input in an Array](https://www.baeldung.com/java-store-scanner-input-in-array)
- [How to Take Input as String With Spaces in Java Using Scanner?](https://www.baeldung.com/java-scanner-input-with-spaces)
- [Write Console Output to Text File in Java](https://www.baeldung.com/java-write-console-output-file)
- [Whats the difference between Scanner next() and nextLine() methods?](https://www.baeldung.com/java-scanner-next-vs-nextline)
- [Handle NoSuchElementException When Reading a File Through Scanner](https://www.baeldung.com/java-scanner-nosuchelementexception-reading-file)

View File

@ -3,33 +3,81 @@ package com.baeldung.regex.z_regexp;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.regex.Pattern;
public class ZRegularExpressionUnitTest {
@Test
public void givenCreditCardNumber_thenReturnIfMatched() {
String creditCardNumber = "1234567890123456";
String creditCardNumber2 = "1234567890123456\n";
String pattern = "\\d{16}\\z";
Assertions.assertTrue(creditCardNumber.matches(pattern));
Assertions.assertTrue(Pattern.compile(pattern).matcher(creditCardNumber).find());
Assertions.assertFalse(Pattern.compile(pattern).matcher(creditCardNumber2).find());
}
@Test
public void givenCreditCardNumber_thenReturnIfNotMatched() {
String creditCardNumber = "1234567890123456\n";
String pattern = "\\d{16}\\z";
Assertions.assertFalse(Pattern.compile(pattern).matcher(creditCardNumber).find());
}
@Test
public void givenLogOutput_thenReturnIfMatched() {
String logLine = "2022-05-01 14:30:00,123 INFO Some log message";
String pattern = ".*message\\z";
Assertions.assertTrue(logLine.matches(pattern));
Assertions.assertTrue(Pattern.compile(pattern).matcher(logLine).find());
}
@Test
public void givenLogOutput_thenReturnIfNotMatched() {
String logLine = "2022-05-01 14:30:00,123 INFO Some log message\n";
String pattern = ".*message\\z";
Assertions.assertFalse(Pattern.compile(pattern).matcher(logLine).find());
}
@Test
public void givenEmailMessage_thenReturnIfMatched() {
String myMessage = "Hello HR, I hope i can write to Baeldung\n";
String pattern = ".*Baeldung\\s*\\Z";
Assertions.assertTrue(myMessage.matches(pattern));
String myMessage2 = "Hello HR, I hope\n i can write to Baeldung";
String pattern = ".*Baeldung\\Z";
String pattern2 = ".*hope\\Z";
Assertions.assertTrue(Pattern.compile(pattern).matcher(myMessage).find());
Assertions.assertFalse(Pattern.compile(pattern2).matcher(myMessage2).find());
}
@Test
public void givenEmailMessage_thenReturnIfNotMatched() {
String myMessage = "Hello HR, I hope\n i can write to Baeldung";
String pattern = ".*hope\\Z";
Assertions.assertFalse(Pattern.compile(pattern).matcher(myMessage).find());
}
@Test
public void givenFileExtension_thenReturnIfMatched() {
String fileName = "image.jpeg";
String fileName = "image.jpeg\n";
String fileName2 = "image2.jpeg\n.png";
String pattern = ".*\\.jpeg\\Z";
Assertions.assertTrue(fileName.matches(pattern));
Assertions.assertTrue(Pattern.compile(pattern).matcher(fileName).find());
Assertions.assertFalse(Pattern.compile(pattern).matcher(fileName2).find());
}
}
@Test
public void givenFileExtension_thenReturnIfNotMatched() {
String fileName = "image2.jpeg\n.png";
String pattern = ".*\\.jpeg\\Z";
Assertions.assertFalse(Pattern.compile(pattern).matcher(fileName).find());
}
@Test
public void givenURL_thenReturnIfMatched() {
String url = "https://www.example.com/api/endpoint\n";
String pattern = ".*/endpoint$";
Assertions.assertTrue(Pattern.compile(pattern).matcher(url).find());
}
@Test
public void givenSentence_thenReturnIfMatched() {
String sentence = "Hello, how are you?";
String pattern = ".*[.?!]$";
Assertions.assertTrue(Pattern.compile(pattern).matcher(sentence).find());
}
}

View File

@ -7,3 +7,4 @@
- [Deserialization Vulnerabilities in Java](https://www.baeldung.com/java-deserialization-vulnerabilities)
- [Serialization Validation in Java](https://www.baeldung.com/java-validate-serializable)
- [What Is the serialVersionUID?](https://www.baeldung.com/java-serial-version-uid)
- [Java Serialization: readObject() vs. readResolve()](https://www.baeldung.com/java-serialization-readobject-vs-readresolve)

View File

@ -0,0 +1,21 @@
package com.baeldung.readresolvevsreadobject;
import java.io.ObjectStreamException;
import java.io.Serializable;
public class Singleton implements Serializable {
private static final long serialVersionUID = 1L;
private static Singleton INSTANCE = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return INSTANCE;
}
private Object readResolve() throws ObjectStreamException {
return INSTANCE;
}
}

View File

@ -0,0 +1,59 @@
package com.baeldung.readresolvevsreadobject;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 3659932210257138726L;
private String userName;
private String password;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User [userName=" + userName + ", password=" + password + "]";
}
public User() {
}
public User(String userName, String password) {
super();
this.userName = userName;
this.password = password;
}
private void writeObject(ObjectOutputStream oos) throws IOException {
this.password = "xyz" + password;
oos.defaultWriteObject();
}
private void readObject(ObjectInputStream aInputStream)
throws ClassNotFoundException, IOException {
aInputStream.defaultReadObject();
this.password = password.substring(3);
}
private Object readResolve() {
return this;
}
}

View File

@ -0,0 +1,47 @@
package com.baeldung.readresolvevsreadobject;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
public class SingletonUnitTest {
@Test
public void testSingletonObj_withNoReadResolve() throws ClassNotFoundException, IOException {
// Serialization
FileOutputStream fos = new FileOutputStream("singleton.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
Singleton actualSingletonObject = Singleton.getInstance();
oos.writeObject(actualSingletonObject);
// Deserialization
Singleton deserializedSingletonObject = null;
FileInputStream fis = new FileInputStream("singleton.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
deserializedSingletonObject = (Singleton) ois.readObject();
// remove readResolve() from Singleton class and uncomment this to test.
//assertNotEquals(actualSingletonObject.hashCode(), deserializedSingletonObject.hashCode());
}
@Test
public void testSingletonObj_withCustomReadResolve()
throws ClassNotFoundException, IOException {
// Serialization
FileOutputStream fos = new FileOutputStream("singleton.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
Singleton actualSingletonObject = Singleton.getInstance();
oos.writeObject(actualSingletonObject);
// Deserialization
Singleton deserializedSingletonObject = null;
FileInputStream fis = new FileInputStream("singleton.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
deserializedSingletonObject = (Singleton) ois.readObject();
assertEquals(actualSingletonObject.hashCode(), deserializedSingletonObject.hashCode());
}
}

View File

@ -0,0 +1,52 @@
package com.baeldung.readresolvevsreadobject;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
public class UserUnitTest {
@Test
public void testDeserializeObj_withOverriddenReadObject()
throws ClassNotFoundException, IOException {
// Serialization
FileOutputStream fos = new FileOutputStream("user.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
User acutalObject = new User("Sachin", "Kumar");
oos.writeObject(acutalObject);
// Deserialization
User deserializedUser = null;
FileInputStream fis = new FileInputStream("user.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
deserializedUser = (User) ois.readObject();
assertNotEquals(deserializedUser.hashCode(), acutalObject.hashCode());
assertEquals(deserializedUser.getUserName(), "Sachin");
assertEquals(deserializedUser.getPassword(), "Kumar");
}
@Test
public void testDeserializeObj_withDefaultReadObject()
throws ClassNotFoundException, IOException {
// Serialization
FileOutputStream fos = new FileOutputStream("user.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
User acutalObject = new User("Sachin", "Kumar");
oos.writeObject(acutalObject);
// Deserialization
User deserializedUser = null;
FileInputStream fis = new FileInputStream("user.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
deserializedUser = (User) ois.readObject();
assertNotEquals(deserializedUser.hashCode(), acutalObject.hashCode());
assertEquals(deserializedUser.getUserName(), "Sachin");
// remove readObject() from User class and uncomment this to test.
//assertEquals(deserializedUser.getPassword(), "xyzKumar");
}
}

View File

@ -3,5 +3,5 @@
This module contains articles about Gson
### Relevant Articles:
- [Solving Gson Parsing Errors](https://www.baeldung.com/gson-parsing-errors)

View File

@ -129,13 +129,6 @@
</dependency>
</dependencies>
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<build>
<finalName>libraries-3</finalName>
<plugins>

View File

@ -49,10 +49,4 @@
<annotations.version>23.0.0</annotations.version>
</properties>
<repositories>
<repository>
<id>projectlombok.org</id>
<url>https://projectlombok.org/edge-releases</url>
</repository>
</repositories>
</project>

View File

@ -27,7 +27,7 @@
</dependencies>
<properties>
<optaplanner-core.version>8.24.0.Final</optaplanner-core.version>
<optaplanner-core.version>9.38.0.Final</optaplanner-core.version>
</properties>
</project>

View File

@ -67,10 +67,10 @@
<hibernate-core.version>5.2.16.Final</hibernate-core.version>
<mysql-connector.version>6.0.6</mysql-connector.version>
<spring-boot.version>2.7.5</spring-boot.version>
<rest-assured.version>3.3.0</rest-assured.version>
<rest-assured.version>5.3.0</rest-assured.version>
<spring-boot-starter-integration.version>2.7.5</spring-boot-starter-integration.version>
<spring-integration-test.version>5.5.14</spring-integration-test.version>
<camel-core.version>3.14.0</camel-core.version>
<camel-core.version>3.20.4</camel-core.version>
<camel-test-junit5.version>3.14.0</camel-test-junit5.version>
</properties>

View File

@ -17,8 +17,13 @@
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-retry</artifactId>
<version>1.7.1</version>
<version>${resilience4j-retry.version}</version>
</dependency>
</dependencies>
<properties>
<resilience4j-retry.version>2.0.2</resilience4j-retry.version>
</properties>
</project>

View File

@ -1,6 +1,6 @@
package com.baeldung.backoff.jitter;
import io.github.resilience4j.retry.IntervalFunction;
import io.github.resilience4j.core.IntervalFunction;
import io.github.resilience4j.retry.Retry;
import io.github.resilience4j.retry.RetryConfig;
import org.junit.Before;
@ -15,8 +15,8 @@ import java.util.concurrent.ExecutorService;
import java.util.function.Function;
import static com.baeldung.backoff.jitter.BackoffWithJitterUnitTest.RetryProperties.*;
import static io.github.resilience4j.retry.IntervalFunction.ofExponentialBackoff;
import static io.github.resilience4j.retry.IntervalFunction.ofExponentialRandomBackoff;
import static io.github.resilience4j.core.IntervalFunction.ofExponentialBackoff;
import static io.github.resilience4j.core.IntervalFunction.ofExponentialRandomBackoff;
import static java.util.Collections.nCopies;
import static java.util.concurrent.Executors.newFixedThreadPool;
import static org.mockito.ArgumentMatchers.anyString;

View File

@ -0,0 +1,42 @@
package com.baeldung.associations.biredirectional;
import jakarta.persistence.*;
import java.util.List;
@Entity
public class Course {
@Id
private Long id;
private String name;
@ManyToMany
@JoinTable(name = "course_student",
joinColumns = @JoinColumn(name = "course_id"),
inverseJoinColumns = @JoinColumn(name = "student_id"))
private List<Student> students;
public List<Student> getStudents() {
return students;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setStudents(List<Student> students) {
this.students = students;
}
}

View File

@ -0,0 +1,21 @@
package com.baeldung.associations.biredirectional;
import java.util.List;
import jakarta.persistence.*;
@Entity
public class Department {
@Id
private Long id;
@OneToMany(mappedBy = "department")
private List<Employee> employees;
public List<Employee> getEmployees() {
return employees;
}
}

View File

@ -0,0 +1,42 @@
package com.baeldung.associations.biredirectional;
import jakarta.persistence.*;
@Entity
public class Employee {
@Id
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "department_id")
private Department department;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
}

View File

@ -0,0 +1,43 @@
package com.baeldung.associations.biredirectional;
import java.util.List;
import jakarta.persistence.*;
@Entity
public class Student {
@Id
private Long id;
private String name;
@ManyToMany(mappedBy = "students")
private List<Course> courses;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Course> getCourses() {
return courses;
}
public void setCourses(List<Course> courses) {
this.courses = courses;
}
}

View File

@ -0,0 +1,40 @@
package com.baeldung.associations.unidirectional;
import jakarta.persistence.*;
import java.util.Set;
@Entity
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany(fetch = FetchType.LAZY, mappedBy = "authors")
private Set<Book> books;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Book> getBooks() {
return books;
}
public void setBooks(Set<Book> books) {
this.books = books;
}
}

View File

@ -0,0 +1,44 @@
package com.baeldung.associations.unidirectional;
import jakarta.persistence.*;
import java.util.Set;
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "book_author",
joinColumns = @JoinColumn(name = "book_id"),
inverseJoinColumns = @JoinColumn(name = "author_id"))
private Set<Author> authors;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Set<Author> getAuthors() {
return authors;
}
public void setAuthors(Set<Author> authors) {
this.authors = authors;
}
}

View File

@ -0,0 +1,33 @@
package com.baeldung.associations.unidirectional;
import jakarta.persistence.*;
import java.util.List;
@Entity
public class Department {
@Id
private Long id;
@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name = "department_id")
private List<Employee> employees;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public List<Employee> getEmployees() {
return employees;
}
public void setEmployees(List<Employee> employees) {
this.employees = employees;
}
}

View File

@ -0,0 +1,42 @@
package com.baeldung.associations.unidirectional;
import jakarta.persistence.*;
@Entity
public class Employee {
@Id
private Long id;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parking_spot_id")
private ParkingSpot parkingSpot;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "department_id")
private Department department;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public ParkingSpot getParkingSpot() {
return parkingSpot;
}
public void setParkingSpot(ParkingSpot parkingSpot) {
this.parkingSpot = parkingSpot;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
}

View File

@ -0,0 +1,11 @@
package com.baeldung.associations.unidirectional;
import jakarta.persistence.*;
@Entity
public class ParkingSpot {
@Id
private Long id;
}

View File

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

View File

@ -0,0 +1,68 @@
package com.baeldung.countrows.entity;
import javax.persistence.*;
import java.security.PrivateKey;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.Date;
@Entity
@Table(name = "ACCOUNTS")
public class Account {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "accounts_seq")
@SequenceGenerator(name = "accounts_seq", sequenceName = "accounts_seq", allocationSize = 1)
@Column(name = "user_id")
private int userId;
private String username;
private String password;
private String email;
private Timestamp createdOn;
private Timestamp lastLogin;
@OneToOne
@JoinColumn(name = "permissions_id")
private Permission permission;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setEmail(String email) {
this.email = email;
}
public Timestamp getCreatedOn() {
return createdOn;
}
public void setCreatedOn(Timestamp createdOn) {
this.createdOn = createdOn;
}
public void setLastLogin(Timestamp lastLogin) {
this.lastLogin = lastLogin;
}
public Permission getPermission() {
return permission;
}
public void setPermission(Permission permission) {
this.permission = permission;
}
@Override
public String toString() {
return "Account{" + "userId=" + userId + ", username='" + username + '\'' + ", password='" + password + '\'' + ", email='" + email + '\'' + ", createdOn=" + createdOn + ", lastLogin=" + lastLogin + ", permission=" + permission + '}';
}
}

View File

@ -0,0 +1,24 @@
package com.baeldung.countrows.entity;
import javax.persistence.*;
@Entity
@Table(name = "PERMISSIONS")
public class Permission {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "permissions_id_sq")
@SequenceGenerator(name = "permissions_id_sq", sequenceName = "permissions_id_sq", allocationSize = 1)
private int id;
private String type;
public void setType(String type) {
this.type = type;
}
@Override
public String toString() {
return "Permission{" + "id=" + id + ", type='" + type + '\'' + '}';
}
}

View File

@ -0,0 +1,23 @@
package com.baeldung.countrows.repository;
import com.baeldung.countrows.entity.Account;
import com.baeldung.countrows.entity.Permission;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.sql.Timestamp;
import java.util.Date;
import java.util.List;
@Repository
public interface AccountRepository extends JpaRepository<Account, Integer> {
long countByUsername(String username);
long countByPermission(Permission permission);
long countByPermissionAndCreatedOnGreaterThan(Permission permission, Timestamp ts);
}

View File

@ -0,0 +1,12 @@
package com.baeldung.countrows.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.baeldung.countrows.entity.Permission;
@Repository
public interface PermissionRepository extends JpaRepository<Permission, Integer> {
Permission findByType(String type);
}

View File

@ -0,0 +1,116 @@
package com.baeldung.countrows.service;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.criteria.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.baeldung.countrows.entity.Account;
import com.baeldung.countrows.entity.Permission;
import com.baeldung.countrows.repository.AccountRepository;
import com.baeldung.countrows.repository.PermissionRepository;
@Service
public class AccountStatsLogic {
@Autowired
private AccountRepository accountRepository;
@PersistenceContext
private EntityManager entityManager;
@Autowired
private PermissionRepository permissionRepository;
public long getAccountCount() {
return accountRepository.count();
}
public long getAccountCountByUsername(String username) {
return accountRepository.countByUsername(username);
}
public long getAccountCountByPermission(Permission permission) {
return accountRepository.countByPermission(permission);
}
public long getAccountCountByPermissionAndCreatedOn(Permission permission, Date date) throws ParseException {
return accountRepository.countByPermissionAndCreatedOnGreaterThan(permission, new Timestamp(date.getTime()));
}
public long getAccountsUsingCQ() throws ParseException {
// creating criteria builder and query
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> criteriaQuery = builder.createQuery(Long.class);
Root<Account> accountRoot = criteriaQuery.from(Account.class);
// select query
criteriaQuery.select(builder.count(accountRoot));
// execute and get the result
return entityManager.createQuery(criteriaQuery)
.getSingleResult();
}
public long getAccountsByPermissionUsingCQ(Permission permission) throws ParseException {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> criteriaQuery = builder.createQuery(Long.class);
Root<Account> accountRoot = criteriaQuery.from(Account.class);
List<Predicate> predicateList = new ArrayList<>(); // list of predicates that will go in where clause
predicateList.add(builder.equal(accountRoot.get("permission"), permission));
criteriaQuery.select(builder.count(accountRoot))
.where(builder.and(predicateList.toArray(new Predicate[0])));
return entityManager.createQuery(criteriaQuery)
.getSingleResult();
}
public long getAccountsByPermissionAndCreateOnUsingCQ(Permission permission, Date date) throws ParseException {
// creating criteria builder and query
CriteriaBuilder builder = entityManager.getCriteriaBuilder(); // create builder
CriteriaQuery<Long> criteriaQuery = builder.createQuery(Long.class);// query instance
Root<Account> accountRoot = criteriaQuery.from(Account.class); // root instance
// list of predicates that will go in where clause
List<Predicate> predicateList = new ArrayList<>();
predicateList.add(builder.equal(accountRoot.get("permission"), permission));
predicateList.add(builder.greaterThan(accountRoot.get("createdOn"), new Timestamp(date.getTime())));
// select query
criteriaQuery.select(builder.count(accountRoot))
.where(builder.and(predicateList.toArray(new Predicate[0])));
// execute and get the result
return entityManager.createQuery(criteriaQuery)
.getSingleResult();
}
public long getAccountsUsingJPQL() throws ParseException {
Query query = entityManager.createQuery("SELECT COUNT(*) FROM Account a");
return (long) query.getSingleResult();
}
public long getAccountsByPermissionUsingJPQL(Permission permission) throws ParseException {
Query query = entityManager.createQuery("SELECT COUNT(*) FROM Account a WHERE a.permission = ?1");
query.setParameter(1, permission);
return (long) query.getSingleResult();
}
public long getAccountsByPermissionAndCreatedOnUsingJPQL(Permission permission, Date date) throws ParseException {
Query query = entityManager.createQuery("SELECT COUNT(*) FROM Account a WHERE a.permission = ?1 and a.createdOn > ?2");
query.setParameter(1, permission);
query.setParameter(2, new Timestamp(date.getTime()));
return (long) query.getSingleResult();
}
}

View File

@ -0,0 +1,141 @@
package com.baeldung.boot.countrows.accountstatslogic;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.Date;
import java.util.UUID;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import com.baeldung.countrows.AccountStatsApplication;
import com.baeldung.countrows.entity.Account;
import com.baeldung.countrows.entity.Permission;
import com.baeldung.countrows.repository.AccountRepository;
import com.baeldung.countrows.repository.PermissionRepository;
import com.baeldung.countrows.service.AccountStatsLogic;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest(classes = AccountStatsApplication.class)
class AccountStatsUnitTest {
@Autowired
private PermissionRepository permissionRepository;
@Autowired
private AccountRepository accountRepository;
@Autowired
private AccountStatsLogic accountStatsLogic;
@AfterEach
public void afterEach() {
accountRepository.deleteAll();
permissionRepository.deleteAll();
}
@Test
public void givenAccountInTable_whenPerformCount_returnsAppropriateCount() {
savePermissions();
saveAccount();
assertThat(accountStatsLogic.getAccountCount()).isEqualTo(1);
}
@Test
public void givenAccountInTable_whenPerformCountByUsernameOrPermission_returnsAppropriateCount() {
savePermissions();
Account account = saveAccount();
assertThat(accountStatsLogic.getAccountCountByUsername(account.getUsername())).isEqualTo(1);
assertThat(accountStatsLogic.getAccountCountByPermission(account.getPermission())).isEqualTo(1);
}
@Test
public void givenAccountInTable_whenPerformCountByPermissionAndCreatedOn_returnsAppropriateCount() throws ParseException {
savePermissions();
Account account = saveAccount();
long count = accountStatsLogic.getAccountCountByPermissionAndCreatedOn(account.getPermission(), account.getCreatedOn());
assertThat(count).isEqualTo(1);
}
@Test
public void givenAccountInTable_whenPerformCountUsingCQ_returnsAppropriateCount() throws ParseException {
savePermissions();
saveAccount();
long count = accountStatsLogic.getAccountsUsingCQ();
assertThat(count).isEqualTo(1);
}
@Test
public void givenAccountInTable_whenPerformCountByPermissionUsingCQ_returnsAppropriateCount() throws ParseException {
savePermissions();
Account account = saveAccount();
long count = accountStatsLogic.getAccountsByPermissionUsingCQ(account.getPermission());
assertThat(count).isEqualTo(1);
}
@Test
public void givenAccountInTable_whenPerformCountByPermissionAndCreatedOnUsingCQ_returnsAppropriateCount() throws ParseException {
savePermissions();
Account account = saveAccount();
long count = accountStatsLogic.getAccountsByPermissionAndCreateOnUsingCQ(account.getPermission(), account.getCreatedOn());
assertThat(count).isEqualTo(1);
}
@Test
public void givenAccountInTable_whenPerformCountUsingJPQL_returnsAppropriateCount() throws ParseException {
savePermissions();
saveAccount();
long count = accountStatsLogic.getAccountsUsingJPQL();
assertThat(count).isEqualTo(1);
}
@Test
public void givenAccountInTable_whenPerformCountByPermissionUsingJPQL_returnsAppropriateCount() throws ParseException {
savePermissions();
Account account = saveAccount();
long count = accountStatsLogic.getAccountsByPermissionUsingJPQL(account.getPermission());
assertThat(count).isEqualTo(1);
}
@Test
public void givenAccountInTable_whenPerformCountByPermissionAndCreatedOnUsingJPQL_returnsAppropriateCount() throws ParseException {
savePermissions();
Account account = saveAccount();
long count = accountStatsLogic.getAccountsByPermissionAndCreatedOnUsingJPQL(account.getPermission(), account.getCreatedOn());
assertThat(count).isEqualTo(1);
}
private Account saveAccount() {
return accountRepository.save(getAccount());
}
private void savePermissions() {
Permission editor = new Permission();
editor.setType("editor");
permissionRepository.save(editor);
Permission admin = new Permission();
admin.setType("admin");
permissionRepository.save(admin);
}
private Account getAccount() {
Permission permission = permissionRepository.findByType("admin");
Account account = new Account();
String seed = UUID.randomUUID()
.toString();
account.setUsername("username_" + seed);
account.setEmail("username_" + seed + "@gmail.com");
account.setPermission(permission);
account.setPassword("password_q1234");
account.setCreatedOn(Timestamp.from(Instant.now()));
account.setLastLogin(Timestamp.from(Instant.now()));
return account;
}
}

View File

@ -169,14 +169,6 @@
</plugins>
</build>
<repositories>
<repository>
<id>dynamodb-local</id>
<name>DynamoDB Local Release Repository</name>
<url>${dynamodblocal.repository.url}</url>
</repository>
</repositories>
<properties>
<!-- The main class to start by executing java -jar -->
<start-class>com.baeldung.Application</start-class>
@ -186,9 +178,7 @@
<aws-java-sdk-dynamodb.version>1.11.64</aws-java-sdk-dynamodb.version>
<bootstrap.version>3.3.7-1</bootstrap.version>
<sqlite4java.version>1.0.392</sqlite4java.version>
<dynamodb.version>1.11.106</dynamodb.version>
<dynamodblocal.version>1.11.86</dynamodblocal.version>
<dynamodblocal.repository.url>https://s3-us-west-2.amazonaws.com/dynamodb-local/release</dynamodblocal.repository.url>
<dynamodblocal.version>1.21.1</dynamodblocal.version>
<maven-dependency-plugin.version>3.1.1</maven-dependency-plugin.version>
<spring-boot.version>2.4.7</spring-boot.version>
<log4j2.version>2.17.1</log4j2.version>

View File

@ -6,4 +6,5 @@ This module contains articles about Spring Data JPA.
- [New CRUD Repository Interfaces in Spring Data 3](https://www.baeldung.com/spring-data-3-crud-repository-interfaces)
- [How to Persist a List of String in JPA?](https://www.baeldung.com/java-jpa-persist-string-list)
- [Hibernate Natural IDs in Spring Boot](https://www.baeldung.com/spring-boot-hibernate-natural-ids)
- [Correct Use of flush() in JPA](https://www.baeldung.com/spring-jpa-flush)
- More articles: [[<-- prev]](../spring-data-jpa-repo-2)

13
pom.xml
View File

@ -366,6 +366,7 @@
<module>muleesb</module>
<module>web-modules/java-lite</module>
<module>web-modules/restx</module>
<module>web-modules/jee-7</module>
<module>persistence-modules/deltaspike</module> <!-- delta spike it doesn't support yet the jakarta API-->
<module>persistence-modules/hibernate-ogm</module> <!-- hibernate-ogm wasn't updated because it doesn't support jakarta API -->
<module>persistence-modules/java-cassandra</module> <!-- JAVA-21464 cassandra-unit library doesn't support to run with jdk9 and above -->
@ -425,11 +426,10 @@
<!-- <module>spring-roo</module> --> <!-- Not supported JAVA-17327 -->
<module>spring-security-modules</module>
<module>spring-security-modules/spring-security-ldap</module>
<module>spring-soap</module>
<module>spring-static-resources</module>
<module>spring-swagger-codegen</module>
<module>spring-web-modules</module>
<module>testing-modules</module>
<module>video-tutorials</module>
</modules>
@ -547,6 +547,7 @@
<module>muleesb</module>
<module>web-modules/java-lite</module>
<module>web-modules/restx</module>
<module>web-modules/jee-7</module>
<module>persistence-modules/deltaspike</module> <!-- delta spike it doesn't support yet the jakarta API-->
<module>persistence-modules/hibernate-ogm</module> <!-- hibernate-ogm wasn't updated because it doesn't support jakarta API -->
<module>persistence-modules/java-cassandra</module> <!-- JAVA-21464 cassandra-unit library doesn't support to run with jdk9 and above -->
@ -598,11 +599,10 @@
<!-- <module>spring-roo</module> --> <!-- Not supported JAVA-17327 -->
<module>spring-security-modules</module>
<module>spring-security-modules/spring-security-ldap</module>
<module>spring-soap</module>
<module>spring-static-resources</module>
<module>spring-swagger-codegen</module>
<module>spring-web-modules</module>
<module>testing-modules</module>
<module>video-tutorials</module>
</modules>
@ -913,7 +913,7 @@
<module>spring-kafka</module>
<module>spring-native</module>
<module>spring-security-modules/spring-security-oauth2-testing</module>
<module>spring-security-modules</module>
<module>spring-protobuf</module>
<module>spring-quartz</module>
@ -925,6 +925,7 @@
<module>spring-threads</module>
<module>spring-vault</module>
<module>spring-websockets</module>
<module>spring-web-modules</module>
<module>static-analysis</module>
<module>tensorflow-java</module>
<module>vertx-modules</module>
@ -1175,6 +1176,7 @@
<module>spring-kafka</module>
<module>spring-native</module>
<module>spring-security-modules</module>
<module>spring-protobuf</module>
<module>spring-quartz</module>
@ -1186,6 +1188,7 @@
<module>spring-threads</module>
<module>spring-vault</module>
<module>spring-websockets</module>
<module>spring-web-modules</module>
<module>static-analysis</module>
<module>tensorflow-java</module>
<module>vertx-modules</module>

View File

@ -41,7 +41,6 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${compiler.plugin.version}</version>
<configuration>
<annotationProcessorPaths>
<path>
@ -55,8 +54,4 @@
</plugins>
</build>
<properties>
<compiler.plugin.version>3.8.1</compiler.plugin.version>
</properties>
</project>

View File

@ -52,7 +52,6 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${compiler.plugin.version}</version>
<configuration>
<annotationProcessorPaths>
<path>
@ -68,7 +67,6 @@
<properties>
<liquibase-core.version>3.8.1</liquibase-core.version>
<compiler.plugin.version>3.8.1</compiler.plugin.version>
<liquibase.version>3.8.1</liquibase.version>
</properties>

View File

@ -6,17 +6,7 @@
<groupId>com.baeldung.quarkus</groupId>
<artifactId>quarkus-funqy</artifactId>
<version>1.0.0-SNAPSHOT</version>
<properties>
<compiler-plugin.version>3.10.1</compiler-plugin.version>
<failsafe.useModulePath>false</failsafe.useModulePath>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.version>2.16.0.Final</quarkus.platform.version>
<surefire-plugin.version>3.0.0-M7</surefire-plugin.version>
</properties>
<parent>
<groupId>com.baeldung</groupId>
<artifactId>quarkus-modules</artifactId>
@ -131,4 +121,16 @@
</properties>
</profile>
</profiles>
<properties>
<compiler-plugin.version>3.10.1</compiler-plugin.version>
<failsafe.useModulePath>false</failsafe.useModulePath>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.version>2.16.0.Final</quarkus.platform.version>
<surefire-plugin.version>3.0.0-M7</surefire-plugin.version>
</properties>
</project>

View File

@ -76,7 +76,6 @@
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${compiler-plugin.version}</version>
<configuration>
<parameters>${maven.compiler.parameters}</parameters>
</configuration>

View File

@ -29,7 +29,7 @@
<!-- https://github.com/wildfly/jandex-maven-plugin -->
<groupId>org.jboss.jandex</groupId>
<artifactId>jandex-maven-plugin</artifactId>
<version>1.2.1</version>
<version>${jandex-maven-plugin.version}</version>
<executions>
<execution>
<id>make-index</id>
@ -43,4 +43,8 @@
</plugins>
</build>
<properties>
<jandex-maven-plugin.version>1.2.1</jandex-maven-plugin.version>
</properties>
</project>

View File

@ -38,7 +38,6 @@
</dependencyManagement>
<properties>
<compiler-plugin.version>3.8.1</compiler-plugin.version>
<maven.compiler.parameters>true</maven.compiler.parameters>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>

View File

@ -3,7 +3,6 @@
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>
<groupId>com.baeldung</groupId>
<artifactId>spring-project</artifactId>
<version>0.1-SNAPSHOT</version>
@ -19,7 +18,7 @@
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-bom</artifactId>
<version>1.17.2</version>
<version>${testcontainers-bom.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@ -43,7 +42,7 @@
<dependency>
<groupId>com.github.jasync-sql</groupId>
<artifactId>jasync-r2dbc-mysql</artifactId>
<version>2.0.8</version>
<version>${jasync-r2dbc-mysql.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -104,7 +103,6 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${maven.compiler.source.version}</source>
<target>${maven.compiler.target.version}</target>
@ -182,7 +180,7 @@
<id>local-native</id>
<properties>
<repackage.classifier>exec</repackage.classifier>
<native-buildtools.version>0.9.11</native-buildtools.version>
<native-buildtools.version>${native-buildtools.version}</native-buildtools.version>
</properties>
<dependencies>
<dependency>
@ -241,7 +239,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M6</version>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<argLine>-DspringAot=true
-agentlib:native-image-agent=access-filter-file=src/test/resources/access-filter.json,config-merge-dir=target/classes/META-INF/native-image
@ -254,11 +252,15 @@
</profiles>
<properties>
<testcontainers-bom.version>1.17.2</testcontainers-bom.version>
<java.version>11</java.version>
<spring-native.version>0.12.1</spring-native.version>
<maven-compiler-plugin.version>3.10.1</maven-compiler-plugin.version>
<maven.compiler.source.version>11</maven.compiler.source.version>
<maven.compiler.target.version>11</maven.compiler.target.version>
<maven-surefire-plugin.version>3.1.0</maven-surefire-plugin.version>
<native-buildtools.version>0.9.11</native-buildtools.version>
<jasync-r2dbc-mysql.version>2.0.8</jasync-r2dbc-mysql.version>
</properties>
</project>

View File

@ -3,20 +3,15 @@
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>sentry-servlet</artifactId>
<name>sentry-servlet</name>
<packaging>war</packaging>
<parent>
<groupId>com.baeldung</groupId>
<artifactId>saas-modules</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>sentry-servlet</artifactId>
<name>sentry-servlet</name>
<packaging>war</packaging>
<properties>
<sentry.version>6.11.0</sentry.version>
<cargo.version>1.10.4</cargo.version>
<maven-war-plugin.version>3.3.2</maven-war-plugin.version>
</properties>
<dependencies>
<dependency>
@ -37,7 +32,7 @@
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven3-plugin</artifactId>
<version>${cargo.version}</version>
<version>${cargo-maven3-plugin.version}</version>
<configuration>
<container>
<containerId>tomcat9x</containerId>
@ -47,4 +42,10 @@
</plugin>
</plugins>
</build>
<properties>
<sentry.version>6.11.0</sentry.version>
<cargo-maven3-plugin.version>1.10.4</cargo-maven3-plugin.version>
<maven-war-plugin.version>3.3.2</maven-war-plugin.version>
</properties>
</project>

View File

@ -34,7 +34,6 @@
<plugins>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>${maven-war-plugin.version}</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
<packagingExcludes>pom.xml</packagingExcludes>

View File

@ -0,0 +1,2 @@
## Relevant Articles
- [Configuring Gradle Tasks in Spring Boot 3](https://www.baeldung.com/spring-boot-3-gradle-configure-tasks)

View File

@ -13,4 +13,5 @@
- [Using Environment Variables in Spring Boots application.properties](https://www.baeldung.com/spring-boot-properties-env-variables)
- [Loading Multiple YAML Configuration Files in Spring Boot](https://www.baeldung.com/spring-boot-load-multiple-yaml-configuration-files)
- [Using Environment Variables in Spring Boots Properties Files](https://www.baeldung.com/spring-boot-properties-env-variables)
- [Spring Boot Properties Prefix Must Be in Canonical Form](https://www.baeldung.com/spring-boot-properties-canonical-form)
- More articles: [[<-- prev]](../spring-boot-properties-2)

View File

@ -18,3 +18,5 @@ spring.redis.port=6379
spring.sleuth.sampler.percentage=1.0
spring.sleuth.web.skipPattern=(^cleanup.*)
spring.zipkin.baseUrl=http://localhost:9411

View File

@ -6,25 +6,13 @@ eureka.client.registryFetchIntervalSeconds = 5
management.security.sessions=always
zuul.routes.book-service.path=/book-service/**
zuul.routes.book-service.sensitive-headers=Set-Cookie,Authorization
hystrix.command.book-service.execution.isolation.thread.timeoutInMilliseconds=600000
zuul.routes.rating-service.path=/rating-service/**
zuul.routes.rating-service.sensitive-headers=Set-Cookie,Authorization
hystrix.command.rating-service.execution.isolation.thread.timeoutInMilliseconds=600000
zuul.routes.discovery.path=/discovery/**
zuul.routes.discovery.sensitive-headers=Set-Cookie,Authorization
zuul.routes.discovery.url=http://localhost:8082
hystrix.command.discovery.execution.isolation.thread.timeoutInMilliseconds=600000
logging.level.org.springframework.web.=debug
logging.level.org.springframework.security=debug
logging.level.org.springframework.cloud.netflix.zuul=debug
spring.redis.host=localhost
spring.redis.port=6379
spring.sleuth.sampler.percentage=1.0
spring.sleuth.web.skipPattern=(^cleanup.*|.+favicon.*)
spring.sleuth.web.skipPattern=(^cleanup.*|.+favicon.*)
spring.zipkin.baseUrl=http://localhost:9411

View File

@ -18,3 +18,5 @@ spring.redis.port=6379
spring.sleuth.sampler.percentage=1.0
spring.sleuth.web.skipPattern=(^cleanup.*)
spring.zipkin.baseUrl=http://localhost:9411

View File

@ -9,9 +9,9 @@
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-boot-1</artifactId>
<artifactId>parent-boot-2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../../parent-boot-1</relativePath>
<relativePath>../../../parent-boot-2</relativePath>
</parent>
<dependencyManagement>
@ -33,7 +33,7 @@
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -42,7 +42,7 @@
</dependencies>
<properties>
<spring-cloud-dependencies.version>Brixton.SR7</spring-cloud-dependencies.version>
<spring-cloud-dependencies.version>2021.0.7</spring-cloud-dependencies.version>
</properties>
</project>

View File

@ -2,12 +2,12 @@ package com.baeldung.spring.cloud.bootstrap.config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableConfigServer
@EnableEurekaClient
@EnableDiscoveryClient
public class ConfigApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigApplication.class, args);

View File

@ -1,23 +1,41 @@
package com.baeldung.spring.cloud.bootstrap.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.context.annotation.Bean;
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.core.userdetails.User;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
public class SecurityConfig {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("configUser").password("configPassword").roles("SYSTEM");
@Bean
public InMemoryUserDetailsManager userDetailsService(BCryptPasswordEncoder bCryptPasswordEncoder) {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("configUser")
.password(bCryptPasswordEncoder.encode("configPassword"))
.roles("SYSTEM")
.build());
return manager;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().hasRole("SYSTEM").and().httpBasic().and().csrf().disable();
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.hasRole("SYSTEM")
.and()
.httpBasic()
.and()
.csrf()
.disable();
return http.build();
}
@Bean
public BCryptPasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
}

View File

@ -9,9 +9,9 @@
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-boot-1</artifactId>
<artifactId>parent-boot-2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../../parent-boot-1</relativePath>
<relativePath>../../../parent-boot-2</relativePath>
</parent>
<dependencyManagement>
@ -33,7 +33,11 @@
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -41,7 +45,7 @@
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -50,7 +54,7 @@
</dependencies>
<properties>
<spring-cloud-dependencies.version>Edgware.SR5</spring-cloud-dependencies.version>
<spring-cloud-dependencies.version>2021.0.7</spring-cloud-dependencies.version>
</properties>
</project>

View File

@ -17,7 +17,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("discUser").password("discPassword").roles("SYSTEM");
auth.inMemoryAuthentication().withUser("discUser").password("{noop}discPassword").roles("SYSTEM");
}
@Override

View File

@ -9,9 +9,9 @@
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-boot-1</artifactId>
<artifactId>parent-boot-2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../../parent-boot-1</relativePath>
<relativePath>../../../parent-boot-2</relativePath>
</parent>
<dependencyManagement>
@ -33,11 +33,15 @@
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -45,7 +49,7 @@
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -53,11 +57,15 @@
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
@ -97,7 +105,7 @@
</build>
<properties>
<spring-cloud-dependencies.version>Dalston.RELEASE</spring-cloud-dependencies.version>
<spring-cloud-dependencies.version>2021.0.7</spring-cloud-dependencies.version>
</properties>
</project>

View File

@ -1,8 +1,8 @@
package com.baeldung.spring.cloud.bootstrap.gateway;
import org.springframework.boot.web.servlet.ErrorPage;
import org.springframework.boot.web.servlet.ErrorPageRegistrar;
import org.springframework.boot.web.servlet.ErrorPageRegistry;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.ErrorPageRegistrar;
import org.springframework.boot.web.server.ErrorPageRegistry;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

View File

@ -1,76 +1,15 @@
package com.baeldung.spring.cloud.bootstrap.gateway;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.EurekaClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.cloud.netflix.ribbon.RibbonClientSpecification;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.sleuth.metric.SpanMetricReporter;
import org.springframework.cloud.sleuth.zipkin.HttpZipkinSpanReporter;
import org.springframework.cloud.sleuth.zipkin.ZipkinProperties;
import org.springframework.cloud.sleuth.zipkin.ZipkinSpanReporter;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
import zipkin.Span;
import java.util.ArrayList;
import java.util.List;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient
@EnableFeignClients
@EnableDiscoveryClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
@Autowired(required = false)
private List<RibbonClientSpecification> configurations = new ArrayList<>();
@Autowired
private EurekaClient eurekaClient;
@Autowired
private SpanMetricReporter spanMetricReporter;
@Autowired
private ZipkinProperties zipkinProperties;
@Value("${spring.sleuth.web.skipPattern}")
private String skipPattern;
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public SpringClientFactory springClientFactory() {
SpringClientFactory factory = new SpringClientFactory();
factory.setConfigurations(this.configurations);
return factory;
}
@Bean
public ZipkinSpanReporter makeZipkinSpanReporter() {
return new ZipkinSpanReporter() {
private HttpZipkinSpanReporter delegate;
private String baseUrl;
@Override
public void report(Span span) {
InstanceInfo instance = eurekaClient.getNextServerFromEureka("zipkin", false);
if (baseUrl == null || !instance.getHomePageUrl().equals(baseUrl)) {
baseUrl = instance.getHomePageUrl();
}
delegate = new HttpZipkinSpanReporter(new RestTemplate(), baseUrl, zipkinProperties.getFlushInterval(), spanMetricReporter);
if (!span.name.matches(skipPattern)) delegate.report(span);
}
};
}
}

View File

@ -1,37 +1,59 @@
package com.baeldung.spring.cloud.bootstrap.gateway;
import org.springframework.beans.factory.annotation.Autowired;
import static org.springframework.security.config.Customizer.withDefaults;
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.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler;
@EnableWebSecurity
@EnableWebFluxSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
public class SecurityConfig {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER")
.and()
.withUser("admin").password("admin").roles("ADMIN");
@Bean
public MapReactiveUserDetailsService userDetailsService() {
UserDetails user = User.withUsername("user")
.password(passwordEncoder().encode("password"))
.roles("USER")
.build();
UserDetails adminUser = User.withUsername("admin")
.password(passwordEncoder().encode("admin"))
.roles("ADMIN")
.build();
return new MapReactiveUserDetailsService(user, adminUser);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.defaultSuccessUrl("/home/index.html", true)
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.formLogin()
.authenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler("/home/index.html"))
.and()
.authorizeRequests()
.antMatchers("/book-service/**", "/rating-service/**", "/login*", "/").permitAll()
.antMatchers("/eureka/**").hasRole("ADMIN")
.anyRequest().authenticated()
.authorizeExchange()
.pathMatchers("/book-service/**", "/rating-service/**", "/login*", "/")
.permitAll()
.pathMatchers("/eureka/**")
.hasRole("ADMIN")
.anyExchange()
.authenticated()
.and()
.logout()
.logout()
.and()
.csrf().disable();
.csrf()
.disable()
.httpBasic(withDefaults());
return http.build();
}
}

View File

@ -1,11 +1,10 @@
package com.baeldung.spring.cloud.bootstrap.gateway;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.RedisFlushMode;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer;
import org.springframework.session.data.redis.config.annotation.web.server.EnableRedisWebSession;
@Configuration
@EnableRedisHttpSession(redisFlushMode = RedisFlushMode.IMMEDIATE)
public class SessionConfig extends AbstractHttpSessionApplicationInitializer {
@EnableRedisWebSession
public class SessionConfig {
}

View File

@ -1,7 +1,6 @@
package com.baeldung.spring.cloud.bootstrap.gateway.client.book;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@ -9,6 +8,6 @@ import org.springframework.web.bind.annotation.RequestMethod;
@FeignClient(value = "book-service")
public interface BooksClient {
@RequestMapping(value = "/books/{bookId}", method = {RequestMethod.GET})
@RequestMapping(value = "/books/{bookId}", method = { RequestMethod.GET })
Book getBookById(@PathVariable("bookId") Long bookId);
}

View File

@ -1,17 +1,16 @@
package com.baeldung.spring.cloud.bootstrap.gateway.client.rating;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.List;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@FeignClient(value = "rating-service")
public interface RatingsClient {
@RequestMapping(value = "/ratings", method = {RequestMethod.GET})
@RequestMapping(value = "/ratings", method = { RequestMethod.GET })
List<Rating> getRatingsByBookId(@RequestParam("bookId") Long bookId, @RequestHeader("Cookie") String session);
}

View File

@ -0,0 +1,25 @@
package com.baeldung.spring.cloud.bootstrap.gateway.filter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class SessionSavingPreFilter implements GlobalFilter {
private static final Logger logger = LoggerFactory.getLogger(SessionSavingPreFilter.class);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return exchange.getSession()
.flatMap(session -> {
logger.debug("SessionId: {}", session.getId());
return chain.filter(exchange);
});
}
}

View File

@ -1,47 +0,0 @@
package com.baeldung.spring.cloud.bootstrap.gateway.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpSession;
@Component
public class SessionSavingZuulPreFilter extends ZuulFilter {
private Logger log = LoggerFactory.getLogger(this.getClass());
@Autowired
private SessionRepository repository;
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpSession httpSession = context.getRequest().getSession();
Session session = repository.getSession(httpSession.getId());
context.addZuulRequestHeader("Cookie", "SESSION=" + httpSession.getId());
log.info("ZuulPreFilter session proxy: {}", session.getId());
return null;
}
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
}

View File

@ -3,5 +3,7 @@ spring.cloud.config.discovery.service-id=config
spring.cloud.config.discovery.enabled=true
spring.cloud.config.username=configUser
spring.cloud.config.password=configPassword
spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true
eureka.client.serviceUrl.defaultZone=http://discUser:discPassword@localhost:8082/eureka/

View File

@ -20,7 +20,6 @@
<module>gateway</module>
<module>svc-book</module>
<module>svc-rating</module>
<module>zipkin</module>
<module>customer-service</module>
<module>order-service</module>
</modules>

View File

@ -10,9 +10,9 @@
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-boot-1</artifactId>
<artifactId>parent-boot-2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../../parent-boot-1</relativePath>
<relativePath>../../../parent-boot-2</relativePath>
</parent>
<dependencyManagement>
@ -34,7 +34,11 @@
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -46,7 +50,7 @@
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -63,12 +67,16 @@
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
</dependencies>
<properties>
<spring-cloud-dependencies.version>Dalston.RELEASE</spring-cloud-dependencies.version>
<spring-cloud-dependencies.version>2021.0.7</spring-cloud-dependencies.version>
</properties>
</project>

View File

@ -1,53 +1,14 @@
package com.baeldung.spring.cloud.bootstrap.svcbook;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.EurekaClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.sleuth.metric.SpanMetricReporter;
import org.springframework.cloud.sleuth.zipkin.HttpZipkinSpanReporter;
import org.springframework.cloud.sleuth.zipkin.ZipkinProperties;
import org.springframework.cloud.sleuth.zipkin.ZipkinSpanReporter;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
import zipkin.Span;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
public class BookServiceApplication {
@Autowired
private EurekaClient eurekaClient;
@Autowired
private SpanMetricReporter spanMetricReporter;
@Autowired
private ZipkinProperties zipkinProperties;
@Value("${spring.sleuth.web.skipPattern}")
private String skipPattern;
public static void main(String[] args) {
SpringApplication.run(BookServiceApplication.class, args);
}
@Bean
public ZipkinSpanReporter makeZipkinSpanReporter() {
return new ZipkinSpanReporter() {
private HttpZipkinSpanReporter delegate;
private String baseUrl;
@Override
public void report(Span span) {
InstanceInfo instance = eurekaClient.getNextServerFromEureka("zipkin", false);
if (baseUrl == null || !instance.getHomePageUrl().equals(baseUrl)) {
baseUrl = instance.getHomePageUrl();
}
delegate = new HttpZipkinSpanReporter(new RestTemplate(), baseUrl, zipkinProperties.getFlushInterval(), spanMetricReporter);
if (!span.name.matches(skipPattern)) delegate.report(span);
}
};
}
}

View File

@ -0,0 +1,16 @@
package com.baeldung.spring.cloud.bootstrap.svcbook;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.web.http.DefaultCookieSerializer;
@Configuration
public class CookieConfig {
@Bean
public DefaultCookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setUseBase64Encoding(false);
return serializer;
}
}

View File

@ -1,36 +1,37 @@
package com.baeldung.spring.cloud.bootstrap.svcbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.web.SecurityFilterChain;
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
public class SecurityConfig {
@Autowired
public void configureGlobal1(AuthenticationManagerBuilder auth) throws Exception {
//try in memory auth with no users to support the case that this will allow for users that are logged in to go anywhere
public void registerAuthProvider(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic()
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http.authorizeHttpRequests((auth) -> auth.antMatchers(HttpMethod.GET, "/books")
.permitAll()
.antMatchers(HttpMethod.GET, "/books/*")
.permitAll()
.antMatchers(HttpMethod.POST, "/books")
.hasRole("ADMIN")
.antMatchers(HttpMethod.PATCH, "/books/*")
.hasRole("ADMIN")
.antMatchers(HttpMethod.DELETE, "/books/*")
.hasRole("ADMIN"))
.csrf()
.disable()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/books").permitAll()
.antMatchers(HttpMethod.GET, "/books/*").permitAll()
.antMatchers(HttpMethod.POST, "/books").hasRole("ADMIN")
.antMatchers(HttpMethod.PATCH, "/books/*").hasRole("ADMIN")
.antMatchers(HttpMethod.DELETE, "/books/*").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.csrf()
.disable();
.build();
}
}

View File

@ -2,7 +2,6 @@ package com.baeldung.spring.cloud.bootstrap.svcbook.book;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -23,8 +22,8 @@ public class BookService {
}
public Book findBookById(Long bookId) {
return Optional.ofNullable(bookRepository.findOne(bookId))
.orElseThrow(() -> new BookNotFoundException("Book not found. ID: " + bookId));
return bookRepository.findById(bookId)
.orElseThrow(() -> new BookNotFoundException(String.format("Book not found. ID: %s", bookId)));
}
@Transactional(propagation = Propagation.REQUIRED)
@ -37,7 +36,7 @@ public class BookService {
@Transactional(propagation = Propagation.REQUIRED)
public void deleteBook(Long bookId) {
bookRepository.delete(bookId);
bookRepository.deleteById(bookId);
}
@Transactional(propagation = Propagation.REQUIRED)
@ -60,7 +59,8 @@ public class BookService {
public Book updateBook(Book book, Long bookId) {
Preconditions.checkNotNull(book);
Preconditions.checkState(book.getId() == bookId);
Preconditions.checkNotNull(bookRepository.findOne(bookId));
Preconditions.checkArgument(bookRepository.findById(bookId)
.isPresent());
return bookRepository.save(book);
}
}

View File

@ -3,5 +3,7 @@ spring.cloud.config.discovery.service-id=config
spring.cloud.config.discovery.enabled=true
spring.cloud.config.username=configUser
spring.cloud.config.password=configPassword
spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true
eureka.client.serviceUrl.defaultZone=http://discUser:discPassword@localhost:8082/eureka/

View File

@ -10,9 +10,9 @@
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-boot-1</artifactId>
<artifactId>parent-boot-2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../../parent-boot-1</relativePath>
<relativePath>../../../parent-boot-2</relativePath>
</parent>
<dependencyManagement>
@ -34,7 +34,11 @@
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -46,7 +50,7 @@
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -58,25 +62,29 @@
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
</dependencies>
<properties>
<spring-cloud-dependencies.version>Dalston.RELEASE</spring-cloud-dependencies.version>
<spring-cloud-dependencies.version>2021.0.7</spring-cloud-dependencies.version>
</properties>
</project>

View File

@ -0,0 +1,16 @@
package com.baeldung.spring.cloud.bootstrap.svcrating;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.web.http.DefaultCookieSerializer;
@Configuration
public class CookieConfig {
@Bean
public DefaultCookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setUseBase64Encoding(false);
return serializer;
}
}

View File

@ -1,69 +1,18 @@
package com.baeldung.spring.cloud.bootstrap.svcrating;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.sleuth.metric.SpanMetricReporter;
import org.springframework.cloud.sleuth.zipkin.HttpZipkinSpanReporter;
import org.springframework.cloud.sleuth.zipkin.ZipkinProperties;
import org.springframework.cloud.sleuth.zipkin.ZipkinSpanReporter;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.client.RestTemplate;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.EurekaClient;
import com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect;
import zipkin.Span;
@SpringBootApplication
@EnableEurekaClient
@EnableHystrix
@EnableTransactionManagement(order=Ordered.LOWEST_PRECEDENCE, mode=AdviceMode.ASPECTJ)
@EnableDiscoveryClient
@EnableTransactionManagement(order = Ordered.LOWEST_PRECEDENCE, mode = AdviceMode.ASPECTJ)
public class RatingServiceApplication {
@Autowired
private EurekaClient eurekaClient;
@Autowired
private SpanMetricReporter spanMetricReporter;
@Autowired
private ZipkinProperties zipkinProperties;
@Value("${spring.sleuth.web.skipPattern}")
private String skipPattern;
public static void main(String[] args) {
SpringApplication.run(RatingServiceApplication.class, args);
}
@Bean
public ZipkinSpanReporter makeZipkinSpanReporter() {
return new ZipkinSpanReporter() {
private HttpZipkinSpanReporter delegate;
private String baseUrl;
@Override
public void report(Span span) {
InstanceInfo instance = eurekaClient.getNextServerFromEureka("zipkin", false);
if (baseUrl == null || !instance.getHomePageUrl().equals(baseUrl)) {
baseUrl = instance.getHomePageUrl();
}
delegate = new HttpZipkinSpanReporter(new RestTemplate(), baseUrl, zipkinProperties.getFlushInterval(), spanMetricReporter);
if (!span.name.matches(skipPattern)) delegate.report(span);
}
};
}
@Bean
@Primary
@Order(value=Ordered.HIGHEST_PRECEDENCE)
public HystrixCommandAspect hystrixAspect() {
return new HystrixCommandAspect();
}
}

View File

@ -1,39 +1,41 @@
package com.baeldung.spring.cloud.bootstrap.svcrating;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
public class SecurityConfig {
@Autowired
public void configureGlobal1(AuthenticationManagerBuilder auth) throws Exception {
//try in memory auth with no users to support the case that this will allow for users that are logged in to go anywhere
auth.inMemoryAuthentication();
@Bean
public UserDetailsService users() {
return new InMemoryUserDetailsManager();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.regexMatchers("^/ratings\\?bookId.*$").authenticated()
.antMatchers(HttpMethod.POST,"/ratings").authenticated()
.antMatchers(HttpMethod.PATCH,"/ratings/*").hasRole("ADMIN")
.antMatchers(HttpMethod.DELETE,"/ratings/*").hasRole("ADMIN")
.antMatchers(HttpMethod.GET,"/ratings").hasRole("ADMIN")
.antMatchers(HttpMethod.GET,"/hystrix").authenticated()
.anyRequest().authenticated()
@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity.authorizeHttpRequests((auth) -> auth.regexMatchers("^/ratings\\?bookId.*$")
.authenticated()
.antMatchers(HttpMethod.POST, "/ratings")
.authenticated()
.antMatchers(HttpMethod.PATCH, "/ratings/*")
.hasRole("ADMIN")
.antMatchers(HttpMethod.DELETE, "/ratings/*")
.hasRole("ADMIN")
.antMatchers(HttpMethod.GET, "/ratings")
.hasRole("ADMIN")
.anyRequest()
.authenticated())
.httpBasic()
.and()
.httpBasic().and()
.csrf()
.disable();
.csrf()
.disable()
.build();
}
}

View File

@ -5,24 +5,24 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer;
@Configuration
@EnableRedisHttpSession
public class SessionConfig extends AbstractHttpSessionApplicationInitializer {
@Autowired
Environment properties;
private Environment properties;
@Bean
@Primary
public JedisConnectionFactory connectionFactory() {
JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setHostName(properties.getProperty("spring.redis.host","localhost"));
factory.setPort(properties.getProperty("spring.redis.port", Integer.TYPE,6379));
factory.afterPropertiesSet();
factory.setUsePool(true);
return factory;
public LettuceConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration redisConfiguration = new RedisStandaloneConfiguration();
redisConfiguration.setHostName(properties.getProperty("spring.redis.host", "localhost"));
redisConfiguration.setPort(properties.getProperty("spring.redis.port", Integer.TYPE, 6379));
return new LettuceConnectionFactory(redisConfiguration);
}
}

View File

@ -6,21 +6,21 @@ import java.util.stream.Collectors;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Repository;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Repository;
@Repository
public class RatingCacheRepository implements InitializingBean {
@Autowired
private JedisConnectionFactory cacheConnectionFactory;
private LettuceConnectionFactory cacheConnectionFactory;
private StringRedisTemplate redisTemplate;
private ValueOperations<String, String> valueOps;

View File

@ -2,7 +2,6 @@ package com.baeldung.spring.cloud.bootstrap.svcrating.rating;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -10,7 +9,8 @@ import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.google.common.base.Preconditions;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
@Service
@Transactional(readOnly = true)
@ -22,31 +22,31 @@ public class RatingService {
@Autowired
private RatingCacheRepository cacheRepository;
@HystrixCommand(commandKey = "ratingsByBookIdFromDB", fallbackMethod = "findCachedRatingsByBookId")
@CircuitBreaker(name = "ratingsByBookIdFromDB", fallbackMethod = "findCachedRatingsByBookId")
public List<Rating> findRatingsByBookId(Long bookId) {
return ratingRepository.findRatingsByBookId(bookId);
}
public List<Rating> findCachedRatingsByBookId(Long bookId) {
public List<Rating> findCachedRatingsByBookId(Long bookId, Exception exception) {
return cacheRepository.findCachedRatingsByBookId(bookId);
}
@HystrixCommand(commandKey = "ratingsFromDB", fallbackMethod = "findAllCachedRatings")
@CircuitBreaker(name = "ratingsFromDB", fallbackMethod = "findAllCachedRatings")
public List<Rating> findAllRatings() {
return ratingRepository.findAll();
}
public List<Rating> findAllCachedRatings() {
public List<Rating> findAllCachedRatings(Exception exception) {
return cacheRepository.findAllCachedRatings();
}
@HystrixCommand(commandKey = "ratingsByIdFromDB", fallbackMethod = "findCachedRatingById", ignoreExceptions = { RatingNotFoundException.class })
@CircuitBreaker(name = "ratingsByIdFromDB", fallbackMethod = "findCachedRatingById")
public Rating findRatingById(Long ratingId) {
return Optional.ofNullable(ratingRepository.findOne(ratingId))
return ratingRepository.findById(ratingId)
.orElseThrow(() -> new RatingNotFoundException("Rating not found. ID: " + ratingId));
}
public Rating findCachedRatingById(Long ratingId) {
public Rating findCachedRatingById(Long ratingId, Exception exception) {
return cacheRepository.findCachedRatingById(ratingId);
}
@ -62,7 +62,7 @@ public class RatingService {
@Transactional(propagation = Propagation.REQUIRED)
public void deleteRating(Long ratingId) {
ratingRepository.delete(ratingId);
ratingRepository.deleteById(ratingId);
cacheRepository.deleteRating(ratingId);
}
@ -86,7 +86,7 @@ public class RatingService {
public Rating updateRating(Rating rating, Long ratingId) {
Preconditions.checkNotNull(rating);
Preconditions.checkState(rating.getId() == ratingId);
Preconditions.checkNotNull(ratingRepository.findOne(ratingId));
Preconditions.checkNotNull(ratingRepository.findById(ratingId));
return ratingRepository.save(rating);
}

View File

@ -3,5 +3,7 @@ spring.cloud.config.discovery.service-id=config
spring.cloud.config.discovery.enabled=true
spring.cloud.config.username=configUser
spring.cloud.config.password=configPassword
spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true
eureka.client.serviceUrl.defaultZone=http://discUser:discPassword@localhost:8082/eureka/

View File

@ -0,0 +1,19 @@
# Zipkin server
Zipkin project [deprecated custom server](https://github.com/openzipkin/zipkin/tree/master/zipkin-server).
It's no longer possible to run a custom Zipkin server compatible with Spring Cloud or even Spring Boot.
The best approach to run a Zipkin server is to use docker. We provided a docker-compose file that you can run:
```bash
$ docker compose up -d
```
After that Zipkin is accessible via [http://localhost:9411](http://localhost:9411)
Alternatively, you can run the Zipkin Jar file,
```bash
$ curl -sSL https://zipkin.io/quickstart.sh | bash -s
$ java -jar zipkin.jar
```

View File

@ -0,0 +1,6 @@
version: "3.9"
services:
zipkin:
image: openzipkin/zipkin
ports:
- 9411:9411

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