Merge branch 'eugenp:master' into master
This commit is contained in:
+2
@@ -0,0 +1,2 @@
|
||||
## Relevant Articles
|
||||
- [Implement Connect 4 Game with Java](https://www.baeldung.com/java-connect-4-game)
|
||||
@@ -28,7 +28,7 @@
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<commons-codec.version>1.11</commons-codec.version>
|
||||
<commons-codec.version>1.16.0</commons-codec.version>
|
||||
<commons-math3.version>3.6.1</commons-math3.version>
|
||||
<cobertura.plugin.version>2.7</cobertura.plugin.version>
|
||||
<tradukisto.version>1.0.1</tradukisto.version>
|
||||
|
||||
@@ -58,11 +58,16 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.tomakehurst</groupId>
|
||||
<groupId>org.wiremock</groupId>
|
||||
<artifactId>wiremock</artifactId>
|
||||
<version>${wiremock.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>${httpclient.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@@ -77,11 +82,12 @@
|
||||
|
||||
<properties>
|
||||
<mockserver.version>5.6.1</mockserver.version>
|
||||
<wiremock.version>2.5.1</wiremock.version>
|
||||
<wiremock.version>3.3.1</wiremock.version>
|
||||
<!-- http client & core 5 -->
|
||||
<httpcore5.version>5.2</httpcore5.version>
|
||||
<httpclient5.version>5.2</httpclient5.version>
|
||||
<httpclient5-fluent.version>5.2</httpclient5-fluent.version>
|
||||
<httpclient.version>4.5.14</httpclient.version>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -8,7 +8,7 @@ import static org.mockserver.model.HttpResponse.response;
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.hc.core5.http.HttpStatus;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.mockserver.client.MockServerClient;
|
||||
|
||||
+2
-4
@@ -38,8 +38,6 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
|
||||
class HttpAsyncClientLiveTest extends GetRequestMockServer {
|
||||
|
||||
private static final String HOST = "http://www.google.com";
|
||||
private static final String HOST_WITH_SSL = "https://mms.nw.ru/";
|
||||
private static final String HOST_WITH_PROXY = "http://httpbin.org/";
|
||||
private static final String URL_SECURED_BY_BASIC_AUTHENTICATION = "http://browserspy.dk/password-ok.php";// "http://localhost:8080/spring-security-rest-basic-auth/api/foos/1";
|
||||
@@ -136,7 +134,7 @@ class HttpAsyncClientLiveTest extends GetRequestMockServer {
|
||||
|
||||
client.start();
|
||||
|
||||
final SimpleHttpRequest request = new SimpleHttpRequest("GET",HOST_WITH_SSL);
|
||||
final SimpleHttpRequest request = new SimpleHttpRequest("GET", HOST_WITH_SSL);
|
||||
final Future<SimpleHttpResponse> future = client.execute(request, null);
|
||||
|
||||
final HttpResponse response = future.get();
|
||||
@@ -201,7 +199,7 @@ class HttpAsyncClientLiveTest extends GetRequestMockServer {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
final Future<SimpleHttpResponse> future = client.execute(SimpleHttpRequest.copy(request), context, null);
|
||||
final Future<SimpleHttpResponse> future = client.execute(SimpleRequestBuilder.copy(request).build(), context, null);
|
||||
final HttpResponse response = future.get();
|
||||
assertThat(response.getCode(), equalTo(200));
|
||||
} catch (final Exception ex) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.baeldung.httpclient;
|
||||
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
|
||||
import org.apache.hc.core5.http.HttpEntity;
|
||||
|
||||
public final class ResponseUtil {
|
||||
private ResponseUtil() {
|
||||
}
|
||||
|
||||
+2
-2
@@ -103,7 +103,7 @@ class HttpClientAdvancedConfigurationIntegrationTest {
|
||||
void givenServerThatIsBehindProxy_whenClientIsConfiguredToSendRequestViaProxy_shouldReturn200() throws IOException {
|
||||
//given
|
||||
proxyMock.stubFor(get(urlMatching(".*"))
|
||||
.willReturn(aResponse().proxiedFrom("http://localhost:8089/")));
|
||||
.willReturn(aResponse().proxiedFrom("http://localhost:8089")));
|
||||
|
||||
serviceMock.stubFor(get(urlEqualTo("/private"))
|
||||
.willReturn(aResponse().withStatus(200)));
|
||||
@@ -129,7 +129,7 @@ class HttpClientAdvancedConfigurationIntegrationTest {
|
||||
public void givenServerThatIsBehindAuthorizationProxy_whenClientSendRequest_shouldAuthorizeProperly() throws IOException {
|
||||
//given
|
||||
proxyMock.stubFor(get(urlMatching("/private"))
|
||||
.willReturn(aResponse().proxiedFrom("http://localhost:8089/")));
|
||||
.willReturn(aResponse().proxiedFrom("http://localhost:8089")));
|
||||
serviceMock.stubFor(get(urlEqualTo("/private"))
|
||||
.willReturn(aResponse().withStatus(200)));
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@
|
||||
<version>${mockserver.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.tomakehurst</groupId>
|
||||
<groupId>org.wiremock</groupId>
|
||||
<artifactId>wiremock</artifactId>
|
||||
<version>${wiremock.version}</version>
|
||||
<scope>test</scope>
|
||||
@@ -234,10 +234,10 @@
|
||||
|
||||
<properties>
|
||||
<!-- util -->
|
||||
<commons-codec.version>1.10</commons-codec.version>
|
||||
<commons-codec.version>1.16.0</commons-codec.version>
|
||||
<httpasyncclient.version>4.1.5</httpasyncclient.version>
|
||||
<!-- testing -->
|
||||
<wiremock.version>2.5.1</wiremock.version>
|
||||
<wiremock.version>3.3.1</wiremock.version>
|
||||
<httpcore.version>4.4.16</httpcore.version>
|
||||
<httpclient.version>4.5.14</httpclient.version>
|
||||
<mockserver.version>5.11.2</mockserver.version>
|
||||
|
||||
+2
-2
@@ -102,7 +102,7 @@ class HttpClientAdvancedConfigurationIntegrationTest {
|
||||
void givenServerThatIsBehindProxy_whenClientIsConfiguredToSendRequestViaProxy_shouldReturn200() throws IOException {
|
||||
//given
|
||||
proxyMock.stubFor(get(urlMatching(".*"))
|
||||
.willReturn(aResponse().proxiedFrom("http://localhost:8089/")));
|
||||
.willReturn(aResponse().proxiedFrom("http://localhost:8089")));
|
||||
|
||||
serviceMock.stubFor(get(urlEqualTo("/private"))
|
||||
.willReturn(aResponse().withStatus(200)));
|
||||
@@ -128,7 +128,7 @@ class HttpClientAdvancedConfigurationIntegrationTest {
|
||||
void givenServerThatIsBehindAuthorizationProxy_whenClientSendRequest_shouldAuthorizeProperly() throws IOException {
|
||||
//given
|
||||
proxyMock.stubFor(get(urlMatching("/private"))
|
||||
.willReturn(aResponse().proxiedFrom("http://localhost:8089/")));
|
||||
.willReturn(aResponse().proxiedFrom("http://localhost:8089")));
|
||||
serviceMock.stubFor(get(urlEqualTo("/private"))
|
||||
.willReturn(aResponse().withStatus(200)));
|
||||
|
||||
|
||||
@@ -14,3 +14,4 @@ You can build the project from the command line using: *mvn clean install*, or i
|
||||
- [Get Partition Count for a Topic in Kafka](https://www.baeldung.com/java-kafka-partition-count-topic)
|
||||
- [bootstrap-server in Kafka Configuration](https://www.baeldung.com/java-kafka-bootstrap-server)
|
||||
- [Introduction to Apache Kafka](https://www.baeldung.com/apache-kafka)
|
||||
- [Ensuring Message Ordering in Kafka: Strategies and Configurations](https://www.baeldung.com/kafka-message-ordering)
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
log4j.rootLogger=INFO, stdout
|
||||
@@ -23,11 +23,6 @@
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>${org.slf4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
<version>${org.slf4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
@@ -57,6 +52,11 @@
|
||||
<version>${lombok.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>${jackson.databind.version} </version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
@@ -64,6 +64,7 @@
|
||||
<kafka.version>2.8.0</kafka.version>
|
||||
<testcontainers-kafka.version>1.15.3</testcontainers-kafka.version>
|
||||
<testcontainers-jupiter.version>1.15.3</testcontainers-jupiter.version>
|
||||
<jackson.databind.version>2.15.2</jackson.databind.version>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.baeldung.kafka.message.ordering;
|
||||
|
||||
public class Config {
|
||||
public static final String CONSUMER_VALUE_DESERIALIZER_SERIALIZED_CLASS = "value.deserializer.serializedClass";
|
||||
public static final String MULTI_PARTITION_TOPIC = "multi_partition_topic";
|
||||
public static final String SINGLE_PARTITION_TOPIC = "single_partition_topic";
|
||||
|
||||
public static final int MULTIPLE_PARTITIONS = 5;
|
||||
public static final int SINGLE_PARTITION = 1;
|
||||
public static final short REPLICATION_FACTOR = 1;
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package com.baeldung.kafka.message.ordering.payload;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class UserEvent implements Comparable<UserEvent> {
|
||||
private String userEventId;
|
||||
private long eventNanoTime;
|
||||
private long globalSequenceNumber;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public UserEvent() {
|
||||
// Required for Jackson Serialization and Deserialization
|
||||
}
|
||||
|
||||
public UserEvent(String userEventId) {
|
||||
this.userEventId = userEventId;
|
||||
}
|
||||
|
||||
public String getUserEventId() {
|
||||
return userEventId;
|
||||
}
|
||||
|
||||
public long getEventNanoTime() {
|
||||
return eventNanoTime;
|
||||
}
|
||||
|
||||
public void setEventNanoTime(long eventNanoTime) {
|
||||
this.eventNanoTime = eventNanoTime;
|
||||
}
|
||||
|
||||
public long getGlobalSequenceNumber() {
|
||||
return globalSequenceNumber;
|
||||
}
|
||||
|
||||
public void setGlobalSequenceNumber(long globalSequenceNumber) {
|
||||
this.globalSequenceNumber = globalSequenceNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(UserEvent other) {
|
||||
return Long.compare(this.globalSequenceNumber, other.globalSequenceNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof UserEvent)) {
|
||||
return false;
|
||||
}
|
||||
UserEvent userEvent = (UserEvent) obj;
|
||||
return this.globalSequenceNumber == userEvent.globalSequenceNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(globalSequenceNumber);
|
||||
}
|
||||
}
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.baeldung.kafka.message.ordering.serialization;
|
||||
|
||||
import com.baeldung.kafka.message.ordering.Config;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.apache.kafka.common.serialization.Deserializer;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Configured via {@link org.apache.kafka.clients.consumer.ConsumerConfig#VALUE_DESERIALIZER_CLASS_CONFIG}
|
||||
*/
|
||||
public class JacksonDeserializer<T> implements Deserializer<T> {
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private Class<T> type;
|
||||
|
||||
@Override
|
||||
public void configure(Map<String, ?> configs, boolean isKey) {
|
||||
this.type = (Class<T>) configs.get(Config.CONSUMER_VALUE_DESERIALIZER_SERIALIZED_CLASS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T deserialize(String topic, byte[] bytes) {
|
||||
if (bytes == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(bytes, type);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error deserializing value", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.baeldung.kafka.message.ordering.serialization;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.apache.kafka.common.serialization.Serializer;
|
||||
|
||||
/**
|
||||
* Configured via {@link org.apache.kafka.clients.producer.ProducerConfig#VALUE_SERIALIZER_CLASS_CONFIG}
|
||||
*/
|
||||
public class JacksonSerializer<T> implements Serializer<T> {
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
public byte[] serialize(String topic, T data) {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return objectMapper.writeValueAsBytes(data);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error serializing value", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
package com.baeldung.kafka.message.ordering;
|
||||
|
||||
import com.baeldung.kafka.message.ordering.payload.UserEvent;
|
||||
import com.baeldung.kafka.message.ordering.serialization.JacksonDeserializer;
|
||||
import com.baeldung.kafka.message.ordering.serialization.JacksonSerializer;
|
||||
|
||||
import org.apache.kafka.clients.admin.*;
|
||||
import org.apache.kafka.clients.consumer.ConsumerConfig;
|
||||
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.LongDeserializer;
|
||||
import org.apache.kafka.common.serialization.LongSerializer;
|
||||
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;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
|
||||
|
||||
@Testcontainers
|
||||
public class ExternalSequenceWithTimeWindowLiveTest {
|
||||
|
||||
private static Admin admin;
|
||||
private static KafkaProducer<Long, UserEvent> producer;
|
||||
private static KafkaConsumer<Long, UserEvent> consumer;
|
||||
private static final Duration TIMEOUT_WAIT_FOR_MESSAGES = Duration.ofSeconds(5);
|
||||
private static final long BUFFER_PERIOD_NS = Duration.ofSeconds(5)
|
||||
.toNanos();
|
||||
private static Logger logger = LoggerFactory.getLogger(ExternalSequenceWithTimeWindowLiveTest.class);
|
||||
|
||||
@Container
|
||||
private static final KafkaContainer KAFKA_CONTAINER = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:latest"));
|
||||
|
||||
@BeforeAll
|
||||
static void setup() throws ExecutionException, InterruptedException {
|
||||
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, LongSerializer.class.getName());
|
||||
producerProperties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JacksonSerializer.class.getName());
|
||||
|
||||
Properties consumerProperties = new Properties();
|
||||
consumerProperties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA_CONTAINER.getBootstrapServers());
|
||||
consumerProperties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, LongDeserializer.class.getName());
|
||||
consumerProperties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JacksonDeserializer.class.getName());
|
||||
consumerProperties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
|
||||
consumerProperties.put(Config.CONSUMER_VALUE_DESERIALIZER_SERIALIZED_CLASS, UserEvent.class);
|
||||
consumerProperties.put(ConsumerConfig.GROUP_ID_CONFIG, "test-group");
|
||||
admin = Admin.create(adminProperties);
|
||||
producer = new KafkaProducer<>(producerProperties);
|
||||
consumer = new KafkaConsumer<>(consumerProperties);
|
||||
admin.createTopics(ImmutableList.of(new NewTopic(Config.MULTI_PARTITION_TOPIC, Config.MULTIPLE_PARTITIONS, Config.REPLICATION_FACTOR)))
|
||||
.all()
|
||||
.get();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void destroy() {
|
||||
KAFKA_CONTAINER.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
void givenMultiplePartitions_whenPublishedToKafkaAndConsumedWithExtSeqNumberAndTimeWindow_thenCheckForMessageOrder() throws ExecutionException, InterruptedException {
|
||||
List<UserEvent> sentUserEventList = new ArrayList<>();
|
||||
List<UserEvent> receivedUserEventList = new ArrayList<>();
|
||||
for (long sequenceNumber = 1; sequenceNumber <= 10; sequenceNumber++) {
|
||||
UserEvent userEvent = new UserEvent(UUID.randomUUID()
|
||||
.toString());
|
||||
userEvent.setEventNanoTime(System.nanoTime());
|
||||
userEvent.setGlobalSequenceNumber(sequenceNumber);
|
||||
Future<RecordMetadata> future = producer.send(new ProducerRecord<>(Config.MULTI_PARTITION_TOPIC, sequenceNumber, userEvent));
|
||||
sentUserEventList.add(userEvent);
|
||||
RecordMetadata metadata = future.get();
|
||||
logger.info("User Event ID: " + userEvent.getUserEventId() + ", Partition : " + metadata.partition());
|
||||
}
|
||||
|
||||
consumer.subscribe(Collections.singletonList(Config.MULTI_PARTITION_TOPIC));
|
||||
List<UserEvent> buffer = new ArrayList<>();
|
||||
long lastProcessedTime = System.nanoTime();
|
||||
ConsumerRecords<Long, UserEvent> records = consumer.poll(TIMEOUT_WAIT_FOR_MESSAGES);
|
||||
records.forEach(record -> {
|
||||
buffer.add(record.value());
|
||||
});
|
||||
while (!buffer.isEmpty()) {
|
||||
if (System.nanoTime() - lastProcessedTime > BUFFER_PERIOD_NS) {
|
||||
processBuffer(buffer, receivedUserEventList);
|
||||
lastProcessedTime = System.nanoTime();
|
||||
}
|
||||
records = consumer.poll(TIMEOUT_WAIT_FOR_MESSAGES);
|
||||
records.forEach(record -> {
|
||||
buffer.add(record.value());
|
||||
});
|
||||
}
|
||||
assertThat(receivedUserEventList).isEqualTo(sentUserEventList)
|
||||
.containsExactlyElementsOf(sentUserEventList);
|
||||
}
|
||||
|
||||
private static void processBuffer(List<UserEvent> buffer, List<UserEvent> receivedUserEventList) {
|
||||
Collections.sort(buffer);
|
||||
buffer.forEach(userEvent -> {
|
||||
receivedUserEventList.add(userEvent);
|
||||
logger.info("Processing message with Global Sequence number: " + userEvent.getGlobalSequenceNumber() + ", User Event Id: " + userEvent.getUserEventId());
|
||||
});
|
||||
buffer.clear();
|
||||
}
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
package com.baeldung.kafka.message.ordering;
|
||||
|
||||
import com.baeldung.kafka.message.ordering.payload.UserEvent;
|
||||
import com.baeldung.kafka.message.ordering.serialization.JacksonDeserializer;
|
||||
import com.baeldung.kafka.message.ordering.serialization.JacksonSerializer;
|
||||
|
||||
import org.apache.kafka.clients.admin.*;
|
||||
import org.apache.kafka.clients.consumer.ConsumerConfig;
|
||||
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.LongDeserializer;
|
||||
import org.apache.kafka.common.serialization.LongSerializer;
|
||||
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;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
|
||||
|
||||
@Testcontainers
|
||||
public class MultiplePartitionLiveTest {
|
||||
|
||||
private static Admin admin;
|
||||
private static KafkaProducer<Long, UserEvent> producer;
|
||||
private static KafkaConsumer<Long, UserEvent> consumer;
|
||||
private static final Duration TIMEOUT_WAIT_FOR_MESSAGES = Duration.ofSeconds(5);
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(MultiplePartitionLiveTest.class);
|
||||
@Container
|
||||
private static final KafkaContainer KAFKA_CONTAINER = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:latest"));
|
||||
|
||||
@BeforeAll
|
||||
static void setup() throws ExecutionException, InterruptedException {
|
||||
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, LongSerializer.class.getName());
|
||||
producerProperties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JacksonSerializer.class.getName());
|
||||
|
||||
Properties consumerProperties = new Properties();
|
||||
consumerProperties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA_CONTAINER.getBootstrapServers());
|
||||
consumerProperties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, LongDeserializer.class.getName());
|
||||
consumerProperties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JacksonDeserializer.class.getName());
|
||||
consumerProperties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
|
||||
consumerProperties.put(Config.CONSUMER_VALUE_DESERIALIZER_SERIALIZED_CLASS, UserEvent.class);
|
||||
consumerProperties.put(ConsumerConfig.GROUP_ID_CONFIG, "test-group");
|
||||
admin = Admin.create(adminProperties);
|
||||
producer = new KafkaProducer<>(producerProperties);
|
||||
consumer = new KafkaConsumer<>(consumerProperties);
|
||||
admin.createTopics(ImmutableList.of(new NewTopic(Config.MULTI_PARTITION_TOPIC, Config.MULTIPLE_PARTITIONS, Config.REPLICATION_FACTOR)))
|
||||
.all()
|
||||
.get();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void destroy() {
|
||||
KAFKA_CONTAINER.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
void givenMultiplePartitions_whenPublishedToKafkaAndConsumed_thenCheckForMessageOrder() throws ExecutionException, InterruptedException {
|
||||
List<UserEvent> sentUserEventList = new ArrayList<>();
|
||||
List<UserEvent> receivedUserEventList = new ArrayList<>();
|
||||
for (long sequenceNumber = 1; sequenceNumber <= 10; sequenceNumber++) {
|
||||
UserEvent userEvent = new UserEvent(UUID.randomUUID()
|
||||
.toString());
|
||||
userEvent.setGlobalSequenceNumber(sequenceNumber);
|
||||
userEvent.setEventNanoTime(System.nanoTime());
|
||||
Future<RecordMetadata> future = producer.send(new ProducerRecord<>(Config.MULTI_PARTITION_TOPIC, sequenceNumber, userEvent));
|
||||
sentUserEventList.add(userEvent);
|
||||
RecordMetadata metadata = future.get();
|
||||
logger.info("User Event ID: " + userEvent.getUserEventId() + ", Partition : " + metadata.partition());
|
||||
}
|
||||
|
||||
consumer.subscribe(Collections.singletonList(Config.MULTI_PARTITION_TOPIC));
|
||||
ConsumerRecords<Long, UserEvent> records = consumer.poll(TIMEOUT_WAIT_FOR_MESSAGES);
|
||||
records.forEach(record -> {
|
||||
UserEvent userEvent = record.value();
|
||||
receivedUserEventList.add(userEvent);
|
||||
logger.info("User Event ID: " + userEvent.getUserEventId());
|
||||
});
|
||||
assertThat(receivedUserEventList).isNotEqualTo(sentUserEventList)
|
||||
.containsExactlyInAnyOrderElementsOf(sentUserEventList);
|
||||
}
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
package com.baeldung.kafka.message.ordering;
|
||||
|
||||
import com.baeldung.kafka.message.ordering.payload.UserEvent;
|
||||
import com.baeldung.kafka.message.ordering.serialization.JacksonDeserializer;
|
||||
import com.baeldung.kafka.message.ordering.serialization.JacksonSerializer;
|
||||
|
||||
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.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.LongDeserializer;
|
||||
import org.apache.kafka.common.serialization.LongSerializer;
|
||||
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;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
|
||||
|
||||
@Testcontainers
|
||||
public class SinglePartitionLiveTest {
|
||||
|
||||
private static Admin admin;
|
||||
private static KafkaProducer<Long, UserEvent> producer;
|
||||
private static KafkaConsumer<Long, UserEvent> consumer;
|
||||
|
||||
private static final Duration TIMEOUT_WAIT_FOR_MESSAGES = Duration.ofSeconds(5);
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(SinglePartitionLiveTest.class);
|
||||
@Container
|
||||
private static final KafkaContainer KAFKA_CONTAINER = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:latest"));
|
||||
|
||||
@BeforeAll
|
||||
static void setup() throws ExecutionException, InterruptedException {
|
||||
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, LongSerializer.class.getName());
|
||||
producerProperties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JacksonSerializer.class.getName());
|
||||
|
||||
Properties consumerProperties = new Properties();
|
||||
consumerProperties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA_CONTAINER.getBootstrapServers());
|
||||
consumerProperties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, LongDeserializer.class.getName());
|
||||
consumerProperties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JacksonDeserializer.class.getName());
|
||||
consumerProperties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
|
||||
consumerProperties.put(Config.CONSUMER_VALUE_DESERIALIZER_SERIALIZED_CLASS, UserEvent.class);
|
||||
consumerProperties.put(ConsumerConfig.GROUP_ID_CONFIG, "test-group");
|
||||
admin = Admin.create(adminProperties);
|
||||
producer = new KafkaProducer<>(producerProperties);
|
||||
consumer = new KafkaConsumer<>(consumerProperties);
|
||||
admin.createTopics(ImmutableList.of(new NewTopic(Config.SINGLE_PARTITION_TOPIC, Config.SINGLE_PARTITION, Config.REPLICATION_FACTOR)))
|
||||
.all()
|
||||
.get();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void destroy() {
|
||||
KAFKA_CONTAINER.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
void givenASinglePartition_whenPublishedToKafkaAndConsumed_thenCheckForMessageOrder() throws ExecutionException, InterruptedException {
|
||||
List<UserEvent> sentUserEventList = new ArrayList<>();
|
||||
List<UserEvent> receivedUserEventList = new ArrayList<>();
|
||||
for (long sequenceNumber = 1; sequenceNumber <= 10; sequenceNumber++) {
|
||||
UserEvent userEvent = new UserEvent(UUID.randomUUID()
|
||||
.toString());
|
||||
userEvent.setGlobalSequenceNumber(sequenceNumber);
|
||||
userEvent.setEventNanoTime(System.nanoTime());
|
||||
ProducerRecord<Long, UserEvent> producerRecord = new ProducerRecord<>(Config.SINGLE_PARTITION_TOPIC, userEvent);
|
||||
Future<RecordMetadata> future = producer.send(producerRecord);
|
||||
sentUserEventList.add(userEvent);
|
||||
RecordMetadata metadata = future.get();
|
||||
logger.info("User Event ID: " + userEvent.getUserEventId() + ", Partition : " + metadata.partition());
|
||||
}
|
||||
|
||||
consumer.subscribe(Collections.singletonList(Config.SINGLE_PARTITION_TOPIC));
|
||||
ConsumerRecords<Long, UserEvent> records = consumer.poll(TIMEOUT_WAIT_FOR_MESSAGES);
|
||||
records.forEach(record -> {
|
||||
UserEvent userEvent = record.value();
|
||||
receivedUserEventList.add(userEvent);
|
||||
logger.info("User Event ID: " + userEvent.getUserEventId());
|
||||
});
|
||||
assertThat(receivedUserEventList).isEqualTo(sentUserEventList)
|
||||
.containsExactlyElementsOf(sentUserEventList);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
</configuration>
|
||||
+1
-19
@@ -82,26 +82,11 @@
|
||||
<artifactId>geronimo-jta_1.0.1B_spec</artifactId>
|
||||
<version>${geronimo.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.validation</groupId>
|
||||
<artifactId>validation-api</artifactId>
|
||||
<version>${validation-api.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate.validator</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
<version>${hibernate-validator.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.el</groupId>
|
||||
<artifactId>javax.el-api</artifactId>
|
||||
<version>${javax.el-api.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.web</groupId>
|
||||
<artifactId>javax.el</artifactId>
|
||||
<version>${javax.el.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
@@ -113,10 +98,7 @@
|
||||
<derby.version>10.8.1.2</derby.version>
|
||||
<jta.version>1.1</jta.version>
|
||||
<geronimo.version>1.0</geronimo.version>
|
||||
<validation-api.version>2.0.1.Final</validation-api.version>
|
||||
<hibernate-validator.version>6.1.2.Final</hibernate-validator.version>
|
||||
<javax.el-api.version>3.0.0</javax.el-api.version>
|
||||
<javax.el.version>2.2.4</javax.el.version>
|
||||
<hibernate-validator.version>8.0.1.Final</hibernate-validator.version>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
@@ -3,10 +3,10 @@ package com.baeldung.atomikos.spring.jpa;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
import javax.validation.ValidatorFactory;
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.Validation;
|
||||
import jakarta.validation.Validator;
|
||||
import jakarta.validation.ValidatorFactory;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.baeldung.atomikos.spring.jpa.order;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import javax.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Max;
|
||||
|
||||
@Entity
|
||||
@Table(name = "ORDERS")
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
</build>
|
||||
|
||||
<properties>
|
||||
<gson.version>2.8.0</gson.version>
|
||||
<gson.version>2.10.1</gson.version>
|
||||
<dynamodblocal.version>1.21.1</dynamodblocal.version>
|
||||
<maven-plugins-version>3.1.1</maven-plugins-version>
|
||||
</properties>
|
||||
|
||||
@@ -73,9 +73,9 @@
|
||||
</build>
|
||||
|
||||
<properties>
|
||||
<gson.version>2.8.0</gson.version>
|
||||
<gson.version>2.10.1</gson.version>
|
||||
<dynamodblocal.version>1.21.1</dynamodblocal.version>
|
||||
<commons-codec-version>1.10.L001</commons-codec-version>
|
||||
<commons-codec-version>1.16.0</commons-codec-version>
|
||||
<jets3t-version>0.9.4.0006L</jets3t-version>
|
||||
<maven-plugins-version>3.1.1</maven-plugins-version>
|
||||
</properties>
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package com.baeldung.deserializationfilters;
|
||||
|
||||
import java.io.ObjectInputFilter;
|
||||
import java.util.function.BinaryOperator;
|
||||
|
||||
import com.baeldung.deserializationfilters.service.DeserializationService;
|
||||
import com.baeldung.deserializationfilters.service.LimitedArrayService;
|
||||
import com.baeldung.deserializationfilters.service.LowDepthService;
|
||||
import com.baeldung.deserializationfilters.service.SmallObjectService;
|
||||
import com.baeldung.deserializationfilters.utils.FilterUtils;
|
||||
|
||||
public class ContextSpecificDeserializationFilterFactory implements BinaryOperator<ObjectInputFilter> {
|
||||
|
||||
@Override
|
||||
public ObjectInputFilter apply(ObjectInputFilter current, ObjectInputFilter next) {
|
||||
if (current == null) {
|
||||
Class<?> caller = findInStack(DeserializationService.class);
|
||||
|
||||
if (caller == null) {
|
||||
current = FilterUtils.fallbackFilter();
|
||||
} else if (caller.equals(SmallObjectService.class)) {
|
||||
current = FilterUtils.safeSizeFilter(190);
|
||||
} else if (caller.equals(LowDepthService.class)) {
|
||||
current = FilterUtils.safeDepthFilter(2);
|
||||
} else if (caller.equals(LimitedArrayService.class)) {
|
||||
current = FilterUtils.safeArrayFilter(3);
|
||||
}
|
||||
}
|
||||
|
||||
return ObjectInputFilter.merge(current, next);
|
||||
}
|
||||
|
||||
private static Class<?> findInStack(Class<?> superType) {
|
||||
for (StackTraceElement element : Thread.currentThread()
|
||||
.getStackTrace()) {
|
||||
try {
|
||||
Class<?> subType = Class.forName(element.getClassName());
|
||||
if (superType.isAssignableFrom(subType)) {
|
||||
return subType;
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package com.baeldung.deserializationfilters.pojo;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public interface ContextSpecific extends Serializable {
|
||||
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.baeldung.deserializationfilters.pojo;
|
||||
|
||||
public class NestedSample implements ContextSpecific {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Sample optional;
|
||||
|
||||
public NestedSample(Sample optional) {
|
||||
this.optional = optional;
|
||||
}
|
||||
|
||||
public Sample getOptional() {
|
||||
return optional;
|
||||
}
|
||||
|
||||
public void setOptional(Sample optional) {
|
||||
this.optional = optional;
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package com.baeldung.deserializationfilters.pojo;
|
||||
|
||||
public class Sample implements ContextSpecific, Comparable<Sample> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private int[] array;
|
||||
private String name;
|
||||
private NestedSample nested;
|
||||
|
||||
public Sample(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Sample(int[] array) {
|
||||
this.array = array;
|
||||
}
|
||||
|
||||
public Sample(NestedSample nested) {
|
||||
this.nested = nested;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int[] getArray() {
|
||||
return array;
|
||||
}
|
||||
|
||||
public void setArray(int[] array) {
|
||||
this.array = array;
|
||||
}
|
||||
|
||||
public NestedSample getNested() {
|
||||
return nested;
|
||||
}
|
||||
|
||||
public void setNested(NestedSample nested) {
|
||||
this.nested = nested;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Sample o) {
|
||||
if (name == null)
|
||||
return -1;
|
||||
|
||||
if (o == null || o.getName() == null)
|
||||
return 1;
|
||||
|
||||
return getName().compareTo(o.getName());
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.baeldung.deserializationfilters.pojo;
|
||||
|
||||
public class SampleExploit extends Sample {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public SampleExploit() {
|
||||
super("exploit");
|
||||
}
|
||||
|
||||
public static void maliciousCode() {
|
||||
System.out.println("exploit executed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
maliciousCode();
|
||||
return "exploit";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Sample o) {
|
||||
maliciousCode();
|
||||
return super.compareTo(o);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user