Merge branch 'eugenp:master' into master

This commit is contained in:
Ashish Gupta
2021-08-16 11:49:48 +05:30
committed by GitHub
65 changed files with 1790 additions and 90 deletions
@@ -0,0 +1,32 @@
package com.baeldung.constructors.exception;
import java.io.File;
import java.io.IOException;
public class Animal {
public Animal() throws InstantiationException {
throw new InstantiationException("Cannot be instantiated");
}
public Animal(String id, int age) {
if (id == null)
throw new NullPointerException("Id cannot be null");
if (age < 0)
throw new IllegalArgumentException("Age cannot be negative");
}
public Animal(File file) throws SecurityException, IOException {
// Avoiding Path traversal attacks
if (file.isAbsolute()) {
throw new SecurityException("Traversal attack - absolute path not allowed");
}
// Avoiding directory traversal
// a/../..b/
if (!file.getCanonicalPath()
.equals(file.getAbsolutePath())) {
throw new SecurityException("Directory traversal attempt?");
}
}
}
@@ -0,0 +1,13 @@
package com.baeldung.constructors.exception;
public class Bird extends Animal {
// Note that we are throwing parent exception
public Bird() throws ReflectiveOperationException {
super();
}
public Bird(String id, int age) {
super(id, age);
}
}
@@ -0,0 +1,47 @@
package com.baeldung.constructors.exception;
import java.io.File;
import org.assertj.core.api.Assertions;
import org.junit.Test;
public class AnimalUnitTest {
@Test
public void givenNoArgument_thenFails() {
Assertions.assertThatThrownBy(() -> {
new Animal();
})
.isInstanceOf(Exception.class);
}
@Test
public void givenInvalidArg_thenFails() {
Assertions.assertThatThrownBy(() -> {
new Animal(null, 30);
})
.isInstanceOf(NullPointerException.class);
}
@Test(expected = Test.None.class)
public void givenValidArg_thenSuccess() {
new Animal("1234", 30);
}
@Test
public void givenAbsolutePath_thenFails() {
Assertions.assertThatThrownBy(() -> {
new Animal(new File("temp.txt").getAbsoluteFile());
})
.isInstanceOf(SecurityException.class);
}
@Test
public void givenDirectoryTraversalPath_thenFails() {
Assertions.assertThatThrownBy(() -> {
new Animal(new File(File.separator + ".." + File.separator + "temp.txt"));
})
.isInstanceOf(SecurityException.class);
}
}
@@ -5,3 +5,4 @@ This module contains articles about Java operators
## Relevant Articles:
- [Logical vs Bitwise OR Operator](https://www.baeldung.com/java-logical-vs-bitwise-or-operator)
- [Bitmasking in Java with Bitwise Operators](https://www.baeldung.com/java-bitmasking)
@@ -0,0 +1,61 @@
package com.baeldung.stringfilenamevalidaiton;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Arrays;
public class StringFilenameValidationUtils {
public static final Character[] INVALID_WINDOWS_SPECIFIC_CHARS = {'"', '*', ':', '<', '>', '?', '\\', '|', 0x7F};
public static final Character[] INVALID_UNIX_SPECIFIC_CHARS = {'\000'};
public static final String REGEX_PATTERN = "^[A-za-z0-9.]{1,255}$";
private StringFilenameValidationUtils() {
}
public static boolean validateStringFilenameUsingIO(String filename) throws IOException {
File file = new File(filename);
boolean created = false;
try {
created = file.createNewFile();
return created;
} finally {
if (created) {
file.delete();
}
}
}
public static boolean validateStringFilenameUsingNIO2(String filename) {
Paths.get(filename);
return true;
}
public static boolean validateStringFilenameUsingContains(String filename) {
if (filename == null || filename.isEmpty() || filename.length() > 255) {
return false;
}
return Arrays.stream(getInvalidCharsByOS())
.noneMatch(ch -> filename.contains(ch.toString()));
}
public static boolean validateStringFilenameUsingRegex(String filename) {
if (filename == null) {
return false;
}
return filename.matches(REGEX_PATTERN);
}
private static Character[] getInvalidCharsByOS() {
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
return INVALID_WINDOWS_SPECIFIC_CHARS;
} else if (os.contains("nix") || os.contains("nux") || os.contains("mac")) {
return INVALID_UNIX_SPECIFIC_CHARS;
} else {
return new Character[]{};
}
}
}
@@ -0,0 +1,130 @@
package com.baeldung.stringfilenamevalidaiton;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.RandomUtils;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EmptySource;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.NullSource;
import java.io.IOException;
import java.nio.file.InvalidPathException;
import java.util.Arrays;
import java.util.stream.Stream;
import static com.baeldung.stringfilenamevalidaiton.StringFilenameValidationUtils.validateStringFilenameUsingContains;
import static com.baeldung.stringfilenamevalidaiton.StringFilenameValidationUtils.validateStringFilenameUsingIO;
import static com.baeldung.stringfilenamevalidaiton.StringFilenameValidationUtils.validateStringFilenameUsingNIO2;
import static com.baeldung.stringfilenamevalidaiton.StringFilenameValidationUtils.validateStringFilenameUsingRegex;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
public class StringFilenameValidationUnitTest {
private static final String CORRECT_FILENAME_PATTERN = "baeldung.txt";
@ParameterizedTest
@MethodSource("correctAlphanumericFilenamesProvider")
public void givenCorrectAlphanumericRandomFilenameString_whenValidateUsingIO_thenReturnTrue(String filename) throws IOException {
assertThat(validateStringFilenameUsingIO(filename)).isTrue();
assertThat(validateStringFilenameUsingNIO2(filename)).isTrue();
assertThat(validateStringFilenameUsingContains(filename)).isTrue();
assertThat(validateStringFilenameUsingRegex(filename)).isTrue();
}
@Test
public void givenTooLongFileNameString_whenValidate_thenIOAndCustomFailsNIO2Succeed() {
String filename = RandomStringUtils.randomAlphabetic(500);
assertThatThrownBy(() -> validateStringFilenameUsingIO(filename))
.isInstanceOf(IOException.class)
.hasMessageContaining("File name too long");
assertThat(validateStringFilenameUsingNIO2(filename)).isTrue();
assertThat(validateStringFilenameUsingContains(filename)).isFalse();
assertThat(validateStringFilenameUsingRegex(filename)).isFalse();
}
@ParameterizedTest
@NullSource
public void givenNullString_whenValidate_thenFails(String filename) {
assertThatThrownBy(() -> validateStringFilenameUsingIO(filename))
.isInstanceOf(NullPointerException.class);
assertThatThrownBy(() -> validateStringFilenameUsingNIO2(filename))
.isInstanceOf(NullPointerException.class);
assertThat(validateStringFilenameUsingContains(filename)).isFalse();
assertThat(validateStringFilenameUsingRegex(filename)).isFalse();
}
@ParameterizedTest
@EmptySource
public void givenEmptyString_whenValidate_thenIOAndCustomFailsNIO2Succeed(String filename) {
assertThatThrownBy(() -> validateStringFilenameUsingIO(filename))
.isInstanceOf(IOException.class);
assertThat(validateStringFilenameUsingNIO2(filename)).isTrue();
assertThat(validateStringFilenameUsingContains(filename)).isFalse();
assertThat(validateStringFilenameUsingRegex(filename)).isFalse();
}
@ParameterizedTest
@EnabledOnOs({OS.LINUX, OS.MAC})
@MethodSource("filenamesWithInvalidWindowsChars")
public void givenFilenameStringWithInvalidWindowsCharAndIsUnix_whenValidateUsingIO_thenReturnTrue(String filename) throws IOException {
assertThat(validateStringFilenameUsingIO(filename)).isTrue();
assertThat(validateStringFilenameUsingNIO2(filename)).isTrue();
assertThat(validateStringFilenameUsingContains(filename)).isTrue();
}
@ParameterizedTest
@EnabledOnOs(OS.WINDOWS)
@MethodSource("filenamesWithInvalidWindowsChars")
public void givenFilenameStringWithInvalidWindowsCharAndIsWindows_whenValidateUsingIO_thenRaiseException(String filename) {
assertThatThrownBy(() -> validateStringFilenameUsingIO(filename))
.isInstanceOf(IOException.class)
.hasMessageContaining("Invalid file path");
assertThatThrownBy(() -> validateStringFilenameUsingNIO2(filename))
.isInstanceOf(InvalidPathException.class)
.hasMessage("character not allowed");
assertThat(validateStringFilenameUsingContains(filename)).isFalse();
}
@ParameterizedTest
@EnabledOnOs({OS.LINUX, OS.MAC})
@MethodSource("filenamesWithInvalidUnixChars")
public void givenFilenameStringWithInvalidUnixCharAndIsUnix_whenValidate_thenRaiseException(String filename) {
assertThatThrownBy(() -> validateStringFilenameUsingIO(filename))
.isInstanceOf(IOException.class)
.hasMessageContaining("Invalid file path");
assertThatThrownBy(() -> validateStringFilenameUsingNIO2(filename))
.isInstanceOf(InvalidPathException.class)
.hasMessageContaining("character not allowed");
assertThat(validateStringFilenameUsingContains(filename)).isFalse();
}
private static Stream<String> correctAlphanumericFilenamesProvider() {
return Stream.generate(() -> RandomStringUtils.randomAlphanumeric(1, 10) + "." + RandomStringUtils.randomAlphabetic(3, 5)).limit(10);
}
private static Stream<String> filenamesWithInvalidWindowsChars() {
return Arrays.stream(StringFilenameValidationUtils.INVALID_WINDOWS_SPECIFIC_CHARS)
.map(character -> {
int idx = RandomUtils.nextInt(0, CORRECT_FILENAME_PATTERN.length());
return CORRECT_FILENAME_PATTERN.substring(0, idx) + character + CORRECT_FILENAME_PATTERN.substring(idx);
});
}
private static Stream<String> filenamesWithInvalidUnixChars() {
return Arrays.stream(StringFilenameValidationUtils.INVALID_UNIX_SPECIFIC_CHARS)
.map(character -> {
int idx = RandomUtils.nextInt(0, CORRECT_FILENAME_PATTERN.length());
return CORRECT_FILENAME_PATTERN.substring(0, idx) + character + CORRECT_FILENAME_PATTERN.substring(idx);
});
}
}
+15 -11
View File
@@ -1,21 +1,21 @@
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.baeldung.docker</groupId>
<artifactId>heap-sizing</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>heap-sizing</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-boot-2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../parent-boot-2</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -58,4 +58,8 @@
</plugins>
</build>
<properties>
<java.version>11</java.version>
</properties>
</project>
+74
View File
@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>ksqldb-app</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>ksqldb</name>
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-modules</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<repositories>
<repository>
<id>confluent</id>
<name>confluent-repo</name>
<url>http://packages.confluent.io/maven/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>io.confluent.ksql</groupId>
<artifactId>ksqldb-api-client</artifactId>
<version>${ksqldb.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>${awaitility.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assertj.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<ksqldb.version>6.2.0</ksqldb.version>
<assertj.version>3.20.2</assertj.version>
<awaitility.version>4.1.0</awaitility.version>
<testcontainers.version>1.15.3</testcontainers.version>
</properties>
</project>
@@ -0,0 +1,29 @@
package com.baeldung.ksqldb;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class Alert {
@JsonProperty(value = "SENSOR_ID")
private String sensorId;
@JsonProperty(value = "START_PERIOD")
private String startPeriod;
@JsonProperty(value = "END_PERIOD")
private String endPeriod;
@JsonProperty(value = "AVERAGE_READING")
private double averageReading;
}
@@ -0,0 +1,75 @@
package com.baeldung.ksqldb;
import io.confluent.ksql.api.client.Client;
import io.confluent.ksql.api.client.ExecuteStatementResult;
import io.confluent.ksql.api.client.KsqlObject;
import io.confluent.ksql.api.client.Row;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Subscriber;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
@AllArgsConstructor
@Slf4j
public class KsqlDBApplication {
private static final String CREATE_READINGS_STREAM = ""
+ " CREATE STREAM readings (sensor_id VARCHAR KEY, timestamp VARCHAR, reading INT)"
+ " WITH (KAFKA_TOPIC = 'readings',"
+ " VALUE_FORMAT = 'JSON',"
+ " TIMESTAMP = 'timestamp',"
+ " TIMESTAMP_FORMAT = 'yyyy-MM-dd HH:mm:ss',"
+ " PARTITIONS = 1);";
private static final String CREATE_ALERTS_TABLE = ""
+ " CREATE TABLE alerts AS"
+ " SELECT"
+ " sensor_id,"
+ " TIMESTAMPTOSTRING(WINDOWSTART, 'yyyy-MM-dd HH:mm:ss', 'UTC') AS start_period,"
+ " TIMESTAMPTOSTRING(WINDOWEND, 'yyyy-MM-dd HH:mm:ss', 'UTC') AS end_period,"
+ " AVG(reading) AS average_reading"
+ " FROM readings"
+ " WINDOW TUMBLING (SIZE 30 MINUTES)"
+ " GROUP BY sensor_id"
+ " HAVING AVG(reading) > 25"
+ " EMIT CHANGES;";
private static final String ALERTS_QUERY = "SELECT * FROM alerts EMIT CHANGES;";
private static final String READINGS_STREAM = "readings";
private static final Map<String, Object> PROPERTIES = Collections.singletonMap("auto.offset.reset", "earliest");
private final Client client;
public CompletableFuture<ExecuteStatementResult> createReadingsStream() {
return client.executeStatement(CREATE_READINGS_STREAM, PROPERTIES);
}
public CompletableFuture<ExecuteStatementResult> createAlertsTable() {
return client.executeStatement(CREATE_ALERTS_TABLE, PROPERTIES);
}
public CompletableFuture<Void> insert(Collection<KsqlObject> rows) {
return CompletableFuture.allOf(
rows.stream()
.map(row -> client.insertInto(READINGS_STREAM, row))
.toArray(CompletableFuture[]::new)
);
}
public CompletableFuture<Void> subscribeOnAlerts(Subscriber<Row> subscriber) {
return client.streamQuery(ALERTS_QUERY, PROPERTIES)
.thenAccept(streamedQueryResult -> streamedQueryResult.subscribe(subscriber))
.whenComplete((result, ex) -> {
if (ex != null) {
log.error("Alerts push query failed", ex);
}
});
}
}
@@ -0,0 +1,10 @@
package com.baeldung.ksqldb;
import lombok.Data;
@Data
public class Reading {
private String id;
private String timestamp;
private int reading;
}
@@ -0,0 +1,60 @@
package com.baeldung.ksqldb;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.confluent.ksql.api.client.Row;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import java.util.ArrayList;
import java.util.List;
@Slf4j
public class RowSubscriber<T> implements Subscriber<Row> {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private final Class<T> clazz;
private Subscription subscription;
public List<T> consumedItems = new ArrayList<>();
public RowSubscriber(Class<T> clazz) {
this.clazz = clazz;
}
@Override
public synchronized void onSubscribe(Subscription subscription) {
log.info("Subscriber is subscribed.");
this.subscription = subscription;
subscription.request(1);
}
@Override
public synchronized void onNext(Row row) {
String jsonString = row.asObject().toJsonString();
log.info("Row JSON: {}", jsonString);
try {
T item = OBJECT_MAPPER.readValue(jsonString, this.clazz);
log.info("Item: {}", item);
consumedItems.add(item);
} catch (JsonProcessingException e) {
log.error("Unable to parse json", e);
}
// Request the next row
subscription.request(1);
}
@Override
public synchronized void onError(Throwable t) {
log.error("Received an error", t);
}
@Override
public synchronized void onComplete() {
log.info("Query has ended.");
}
}
@@ -0,0 +1,160 @@
package com.baeldung.ksqldb;
import io.confluent.ksql.api.client.Client;
import io.confluent.ksql.api.client.ClientOptions;
import io.confluent.ksql.api.client.KsqlObject;
import io.confluent.ksql.api.client.QueryInfo;
import io.confluent.ksql.api.client.QueryInfo.QueryType;
import io.confluent.ksql.api.client.Row;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.DockerComposeContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.io.File;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import static org.awaitility.Awaitility.given;
@Testcontainers
class KsqlDBApplicationLiveTest {
private static final File KSQLDB_COMPOSE_FILE = new File("src/test/resources/docker/docker-compose.yml");
private static final Map<String, Object> PROPERTIES = Collections.singletonMap("auto.offset.reset", "earliest");
private static final String KSQLDB_SERVER_HOST = "localhost";
private static final int KSQLDB_SERVER_PORT = 8088;
@Container
public static DockerComposeContainer dockerComposeContainer =
new DockerComposeContainer<>(KSQLDB_COMPOSE_FILE)
.withServices("zookeeper", "broker", "ksqldb-server")
.withExposedService("ksqldb-server", 8088,
Wait.forHealthcheck().withStartupTimeout(Duration.ofMinutes(5)))
.withLocalCompose(true);
private KsqlDBApplication ksqlDBApplication;
private Client client;
@BeforeEach
void setup() {
ClientOptions options = ClientOptions.create()
.setHost(KSQLDB_SERVER_HOST)
.setPort(KSQLDB_SERVER_PORT);
client = Client.create(options);
ksqlDBApplication = new KsqlDBApplication(client);
}
@AfterEach
void tearDown() {
deleteAlerts();
}
@Test
void givenSensorReadings_whenSubscribedToAlerts_thenAlertsAreConsumed() {
createAlertsMaterializedView();
RowSubscriber<Alert> alertSubscriber = new RowSubscriber<>(Alert.class);
CompletableFuture<Void> result = ksqlDBApplication.subscribeOnAlerts(alertSubscriber);
insertSampleData();
assertThat(result).isNotNull();
await().atMost(Duration.ofMinutes(3)).untilAsserted(() ->
assertThat(alertSubscriber.consumedItems)
.containsOnly(
expectedAlert("sensor-1", "2021-08-01 09:30:00", "2021-08-01 10:00:00", 28.0),
expectedAlert("sensor-2", "2021-08-01 10:00:00", "2021-08-01 10:30:00", 26.0)
)
);
}
@Test
void givenSensorReadings_whenPullQueryForRow_thenRowIsReturned() {
createAlertsMaterializedView();
insertSampleData();
String pullQuery = "SELECT * FROM alerts WHERE sensor_id = 'sensor-2';";
given().ignoreExceptions()
.await().atMost(Duration.ofMinutes(1))
.untilAsserted(() -> {
// it may be possible that the materialized view is not updated with sample data yet
// so ignore TimeoutException and try again
List<Row> rows = client.executeQuery(pullQuery, PROPERTIES)
.get(10, TimeUnit.SECONDS);
assertThat(rows).hasSize(1);
Row row = rows.get(0);
assertThat(row.getString("SENSOR_ID")).isEqualTo("sensor-2");
assertThat(row.getString("START_PERIOD")).isEqualTo("2021-08-01 10:00:00");
assertThat(row.getString("END_PERIOD")).isEqualTo("2021-08-01 10:30:00");
assertThat(row.getDouble("AVERAGE_READING")).isEqualTo(26.0);
});
}
private void createAlertsMaterializedView() {
ksqlDBApplication.createReadingsStream().join();
ksqlDBApplication.createAlertsTable().join();
}
private void insertSampleData() {
ksqlDBApplication.insert(
Arrays.asList(
new KsqlObject().put("sensor_id", "sensor-1").put("timestamp", "2021-08-01 09:00:00").put("reading", 22),
new KsqlObject().put("sensor_id", "sensor-1").put("timestamp", "2021-08-01 09:10:00").put("reading", 20),
new KsqlObject().put("sensor_id", "sensor-1").put("timestamp", "2021-08-01 09:20:00").put("reading", 20),
// these reading will exceed the alert threshold (sensor-1)
new KsqlObject().put("sensor_id", "sensor-1").put("timestamp", "2021-08-01 09:30:00").put("reading", 24),
new KsqlObject().put("sensor_id", "sensor-1").put("timestamp", "2021-08-01 09:40:00").put("reading", 30),
new KsqlObject().put("sensor_id", "sensor-1").put("timestamp", "2021-08-01 09:50:00").put("reading", 30),
new KsqlObject().put("sensor_id", "sensor-1").put("timestamp", "2021-08-01 10:00:00").put("reading", 24),
// these reading will exceed the alert threshold (sensor-2)
new KsqlObject().put("sensor_id", "sensor-2").put("timestamp", "2021-08-01 10:00:00").put("reading", 26),
new KsqlObject().put("sensor_id", "sensor-2").put("timestamp", "2021-08-01 10:10:00").put("reading", 26),
new KsqlObject().put("sensor_id", "sensor-2").put("timestamp", "2021-08-01 10:20:00").put("reading", 26),
new KsqlObject().put("sensor_id", "sensor-1").put("timestamp", "2021-08-01 10:30:00").put("reading", 24)
)
).join();
}
private void deleteAlerts() {
client.listQueries()
.thenApply(queryInfos -> queryInfos.stream()
.filter(queryInfo -> queryInfo.getQueryType() == QueryType.PERSISTENT)
.map(QueryInfo::getId)
.findFirst()
.orElseThrow(() -> new RuntimeException("Persistent query not found")))
.thenCompose(id -> client.executeStatement("TERMINATE " + id + ";"))
.thenCompose(result -> client.executeStatement("DROP TABLE alerts DELETE TOPIC;"))
.thenCompose(result -> client.executeStatement("DROP STREAM readings DELETE TOPIC;"))
.join();
}
private Alert expectedAlert(String sensorId, String startPeriod, String endPeriod, double average) {
return Alert.builder()
.sensorId(sensorId)
.startPeriod(startPeriod)
.endPeriod(endPeriod)
.averageReading(average)
.build();
}
}
@@ -0,0 +1,49 @@
---
version: '3'
services:
zookeeper:
image: confluentinc/cp-zookeeper:6.2.0
hostname: zookeeper
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME: 2000
broker:
image: confluentinc/cp-kafka:6.2.0
hostname: broker
depends_on:
- zookeeper
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: 'zookeeper:2181'
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://broker:9092,PLAINTEXT_HOST://localhost:29092
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
ksqldb-server:
image: confluentinc/ksqldb-server:0.19.0
hostname: ksqldb-server
depends_on:
- broker
ports:
- "8088:8088"
healthcheck:
test: curl -f http://ksqldb-server:8088/ || exit 1
environment:
KSQL_LISTENERS: http://0.0.0.0:8088
KSQL_BOOTSTRAP_SERVERS: broker:9092
KSQL_KSQL_LOGGING_PROCESSING_STREAM_AUTO_CREATE: "true"
KSQL_KSQL_LOGGING_PROCESSING_TOPIC_AUTO_CREATE: "true"
ksqldb-cli:
image: confluentinc/ksqldb-cli:0.19.0
hostname: ksqldb-cli
depends_on:
- broker
- ksqldb-server
entrypoint: /bin/sh
tty: true
@@ -0,0 +1,6 @@
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1} - %m%n
@@ -0,0 +1,3 @@
### Relevant Articles:
- [Hosting a Maven Repository on GitHub](https://www.baeldung.com/maven-repo-github)
+6 -1
View File
@@ -81,13 +81,18 @@
<version>${assertj-core.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.netflix.spectator</groupId>
<artifactId>spectator-api</artifactId>
<version>0.132.0</version>
</dependency>
</dependencies>
<properties>
<dep.ver.metrics>3.1.2</dep.ver.metrics>
<dep.ver.servlet>3.1.0</dep.ver.servlet>
<netflix.servo.ver>0.12.17</netflix.servo.ver>
<micrometer.ver>0.12.0.RELEASE</micrometer.ver>
<micrometer.ver>1.7.1</micrometer.ver>
<!-- <fasterxml.jackson.version>2.9.1</fasterxml.jackson.version> -->
<spring-boot-starter-web.version>2.0.7.RELEASE</spring-boot-starter-web.version>
<assertj-core.version>3.11.1</assertj-core.version>
@@ -1,10 +1,10 @@
package com.baeldung.metrics.micrometer;
import io.micrometer.core.instrument.binder.jvm.JvmThreadMetrics;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import io.micrometer.core.instrument.binder.JvmThreadMetrics;
@SpringBootApplication
public class MicrometerApp {
@@ -14,7 +14,7 @@ public class MicrometerApp {
return new JvmThreadMetrics();
}
public static void main(String[] args) throws Exception {
public static void main(String[] args) {
SpringApplication.run(MicrometerApp.class, args);
}
@@ -1,11 +1,7 @@
package com.baeldung.metrics.micrometer;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.within;
import static org.assertj.core.api.Assertions.withinPercentage;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.collection.IsMapContaining.hasEntry;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
@@ -16,29 +12,25 @@ import io.micrometer.core.instrument.DistributionSummary;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.LongTaskTimer;
import io.micrometer.core.instrument.Measurement;
import io.micrometer.core.instrument.Meter.Type;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
import io.micrometer.core.instrument.distribution.HistogramSnapshot;
import io.micrometer.core.instrument.distribution.ValueAtPercentile;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import io.micrometer.core.instrument.stats.hist.Histogram;
import io.micrometer.core.instrument.stats.quantile.WindowSketchQuantiles;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Arrays;
import java.util.Map;
import java.util.TreeMap;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.assertj.core.data.Percentage;
import org.junit.Before;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import com.netflix.spectator.atlas.AtlasConfig;
@@ -55,7 +47,7 @@ public class MicrometerAtlasIntegrationTest {
@Override
public Duration step() {
return Duration.ofSeconds(1);
return Duration.ofSeconds(10);
}
@Override
@@ -77,9 +69,9 @@ public class MicrometerAtlasIntegrationTest {
compositeRegistry.gauge("baeldung.heat", 90);
Optional<Gauge> oneGauge = oneSimpleMeter
Optional<Gauge> oneGauge = Optional.ofNullable(oneSimpleMeter
.find("baeldung.heat")
.gauge();
.gauge());
assertTrue(oneGauge.isPresent());
Iterator<Measurement> measurements = oneGauge
.get()
@@ -91,9 +83,9 @@ public class MicrometerAtlasIntegrationTest {
.next()
.getValue(), equalTo(90.00));
Optional<Gauge> atlasGauge = atlasMeterRegistry
Optional<Gauge> atlasGauge = Optional.ofNullable(atlasMeterRegistry
.find("baeldung.heat")
.gauge();
.gauge());
assertTrue(atlasGauge.isPresent());
Iterator<Measurement> anotherMeasurements = atlasGauge
.get()
@@ -122,14 +114,14 @@ public class MicrometerAtlasIntegrationTest {
.increment();
new CountedObject();
Optional<Counter> counterOptional = Metrics.globalRegistry
Optional<Counter> counterOptional = Optional.ofNullable(Metrics.globalRegistry
.find("objects.instance")
.counter();
.counter());
assertTrue(counterOptional.isPresent());
assertTrue(counterOptional
assertEquals(counterOptional
.get()
.count() == 2.0);
.count() , 2.0, 0.0);
}
@Test
@@ -142,10 +134,10 @@ public class MicrometerAtlasIntegrationTest {
.register(registry);
counter.increment(2.0);
assertTrue(counter.count() == 2);
assertEquals(counter.count(), 2, 0);
counter.increment(-1);
assertTrue(counter.count() == 2);
assertEquals(counter.count(), 1, 0);
}
@Test
@@ -161,8 +153,8 @@ public class MicrometerAtlasIntegrationTest {
timer.record(30, TimeUnit.MILLISECONDS);
assertTrue(2 == timer.count());
assertEquals(2, timer.count(), 0);
assertThat(timer.totalTime(TimeUnit.MILLISECONDS)).isBetween(40.0, 55.0);
}
@@ -173,12 +165,12 @@ public class MicrometerAtlasIntegrationTest {
.builder("3rdPartyService")
.register(registry);
long currentTaskId = longTaskTimer.start();
LongTaskTimer.Sample currentTaskId = longTaskTimer.start();
try {
TimeUnit.MILLISECONDS.sleep(2);
} catch (InterruptedException ignored) {
}
long timeElapsed = longTaskTimer.stop(currentTaskId);
long timeElapsed = currentTaskId.stop();
assertEquals(2L, timeElapsed/((int) 1e6),1L);
}
@@ -191,10 +183,10 @@ public class MicrometerAtlasIntegrationTest {
.builder("cache.size", list, List::size)
.register(registry);
assertTrue(gauge.value() == 0.0);
assertEquals(gauge.value(), 0.0, 0.0);
list.add("1");
assertTrue(gauge.value() == 1.0);
assertEquals(gauge.value(), 1.0, 0.0);
}
@Test
@@ -208,18 +200,17 @@ public class MicrometerAtlasIntegrationTest {
distributionSummary.record(4);
distributionSummary.record(5);
assertTrue(3 == distributionSummary.count());
assertTrue(12 == distributionSummary.totalAmount());
assertEquals(3, distributionSummary.count(), 0);
assertEquals(12, distributionSummary.totalAmount(), 0);
}
@Test
public void givenTimer_whenEnrichWithQuantile_thenQuantilesComputed() {
public void givenTimer_whenEnrichWithPercentile_thenPercentilesComputed() {
SimpleMeterRegistry registry = new SimpleMeterRegistry();
Timer timer = Timer
.builder("test.timer")
.quantiles(WindowSketchQuantiles
.quantiles(0.3, 0.5, 0.95)
.create())
.publishPercentiles(0.3, 0.5, 0.95)
.publishPercentileHistogram()
.register(registry);
timer.record(2, TimeUnit.SECONDS);
@@ -229,27 +220,18 @@ public class MicrometerAtlasIntegrationTest {
timer.record(8, TimeUnit.SECONDS);
timer.record(13, TimeUnit.SECONDS);
Map<String, Integer> quantileMap = extractTagValueMap(registry, Type.Gauge, 1e9);
assertThat(quantileMap, allOf(hasEntry("quantile=0.3", 2), hasEntry("quantile=0.5", 3), hasEntry("quantile=0.95", 8)));
}
Map<Double, Double> expectedMicrometer = new TreeMap<>();
expectedMicrometer.put(0.3, 1946.157056);
expectedMicrometer.put(0.5, 3019.89888);
expectedMicrometer.put(0.95, 13354.663936);
private Map<String, Integer> extractTagValueMap(MeterRegistry registry, Type meterType, double valueDivisor) {
return registry
.getMeters()
.stream()
.filter(meter -> meter.getType() == meterType)
.collect(Collectors.toMap(meter -> {
Tag tag = meter
.getId()
.getTags()
.iterator()
.next();
return tag.getKey() + "=" + tag.getValue();
}, meter -> (int) (meter
.measure()
.iterator()
.next()
.getValue() / valueDivisor)));
Map<Double, Double> actualMicrometer = new TreeMap<>();
ValueAtPercentile[] percentiles = timer.takeSnapshot().percentileValues();
for (ValueAtPercentile percentile : percentiles) {
actualMicrometer.put(percentile.percentile(), percentile.value(TimeUnit.MILLISECONDS));
}
assertEquals(expectedMicrometer, actualMicrometer);
}
@Test
@@ -257,7 +239,7 @@ public class MicrometerAtlasIntegrationTest {
SimpleMeterRegistry registry = new SimpleMeterRegistry();
DistributionSummary hist = DistributionSummary
.builder("summary")
.histogram(Histogram.linear(0, 10, 5))
.serviceLevelObjectives(1, 10, 5)
.register(registry);
hist.record(3);
@@ -267,17 +249,28 @@ public class MicrometerAtlasIntegrationTest {
hist.record(13);
hist.record(26);
Map<String, Integer> histograms = extractTagValueMap(registry, Type.Counter, 1.0);
Map<Integer, Double> expectedMicrometer = new TreeMap<>();
expectedMicrometer.put(1,0D);
expectedMicrometer.put(10,2D);
expectedMicrometer.put(5,1D);
assertThat(histograms, allOf(hasEntry("bucket=0.0", 0), hasEntry("bucket=10.0", 2), hasEntry("bucket=20.0", 2), hasEntry("bucket=30.0", 1), hasEntry("bucket=40.0", 1), hasEntry("bucket=Infinity", 0)));
Map<Integer, Double> actualMicrometer = new TreeMap<>();
HistogramSnapshot snapshot = hist.takeSnapshot();
Arrays.stream(snapshot.histogramCounts()).forEach(p -> {
actualMicrometer.put((Integer.valueOf((int) p.bucket())), p.count());
});
assertEquals(expectedMicrometer, actualMicrometer);
}
@Test
public void givenTimer_whenEnrichWithTimescaleHistogram_thenTimeScaleDataCollected() {
SimpleMeterRegistry registry = new SimpleMeterRegistry();
Duration[] durations = {Duration.ofMillis(25), Duration.ofMillis(300), Duration.ofMillis(600)};
Timer timer = Timer
.builder("timer")
.histogram(Histogram.linearTime(TimeUnit.MILLISECONDS, 0, 200, 3))
.sla(durations)
.publishPercentileHistogram()
.register(registry);
timer.record(1000, TimeUnit.MILLISECONDS);
@@ -286,10 +279,18 @@ public class MicrometerAtlasIntegrationTest {
timer.record(341, TimeUnit.MILLISECONDS);
timer.record(500, TimeUnit.MILLISECONDS);
Map<String, Integer> histograms = extractTagValueMap(registry, Type.Counter, 1.0);
Map<Double, Double> expectedMicrometer = new TreeMap<>();
expectedMicrometer.put(2.5E7,1D);
expectedMicrometer.put(3.0E8,1D);
expectedMicrometer.put(6.0E8,4D);
assertThat(histograms, allOf(hasEntry("bucket=0.0", 0), hasEntry("bucket=2.0E8", 1), hasEntry("bucket=4.0E8", 1), hasEntry("bucket=Infinity", 3)));
Map<Double, Double> actualMicrometer = new TreeMap<>();
HistogramSnapshot snapshot = timer.takeSnapshot();
Arrays.stream(snapshot.histogramCounts()).forEach(p -> {
actualMicrometer.put((Double.valueOf((int) p.bucket())), p.count());
});
assertEquals(expectedMicrometer, actualMicrometer);
}
}
+1
View File
@@ -57,6 +57,7 @@
<module>spring-boot-persistence</module>
<module>spring-boot-persistence-h2</module>
<module>spring-boot-persistence-mongodb</module>
<module>spring-data-arangodb</module>
<module>spring-data-cassandra</module>
<module>spring-data-cassandra-test</module>
<module>spring-data-cassandra-reactive</module>
@@ -0,0 +1,6 @@
=========
## Spring Data ArangoDB
### Relevant Articles:
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-data-arangodb</artifactId>
<name>spring-data-arangodb</name>
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-boot-2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../parent-boot-2</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.arangodb</groupId>
<artifactId>arangodb-spring-data</artifactId>
<version>3.5.0</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,7 @@
FROM arangodb:3.8.0
COPY init-session.js /docker-entrypoint-initdb.d/
EXPOSE 8529
ENV ARANGO_ROOT_PASSWORD=password
@@ -0,0 +1 @@
rs.initiate();
@@ -0,0 +1,5 @@
#!/bin/bash
docker image build -t spring-data-arangodb:live-test .
docker run -p 8529:8529 -e ARANGO_ROOT_PASSWORD=password --name spring-data-arangodb-live-test spring-data-arangodb:live-test
@@ -0,0 +1,4 @@
#!/bin/bash
docker stop spring-data-arangodb-live-test
docker rm spring-data-arangodb-live-test
@@ -0,0 +1,3 @@
#!/bin/bash
mvn clean compile test -P live-all -f ../../../pom.xml
@@ -0,0 +1,13 @@
package com.baeldung.arangodb;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ArangoDbSpringDataApplication {
public static void main(String[] args) {
SpringApplication.run(ArangoDbSpringDataApplication.class, args);
}
}
@@ -0,0 +1,24 @@
package com.baeldung.arangodb.configuration;
import com.arangodb.ArangoDB;
import com.arangodb.springframework.annotation.EnableArangoRepositories;
import com.arangodb.springframework.config.ArangoConfiguration;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableArangoRepositories(basePackages = {"com.baeldung"})
public class ArangoDbConfiguration implements ArangoConfiguration {
@Override
public ArangoDB.Builder arango() {
return new ArangoDB.Builder()
.host("127.0.0.1", 8529)
.user("root")
.password("password");
}
@Override
public String database() {
return "baeldung-database";
}
}
@@ -0,0 +1,86 @@
package com.baeldung.arangodb.model;
import com.arangodb.springframework.annotation.ArangoId;
import com.arangodb.springframework.annotation.Document;
import com.arangodb.springframework.annotation.Relations;
import org.springframework.data.annotation.Id;
import java.time.ZonedDateTime;
import java.util.Collection;
@Document("articles")
public class Article {
@Id
private String id;
@ArangoId
private String arangoId;
private String name;
private String author;
private ZonedDateTime publishDate;
private String htmlContent;
@Relations(edges = ArticleLink.class, lazy = true)
private Collection<Author> authors;
public Article() {
super();
}
public Article(String name, String author, ZonedDateTime publishDate, String htmlContent) {
this.name = name;
this.author = author;
this.publishDate = publishDate;
this.htmlContent = htmlContent;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getArangoId() {
return arangoId;
}
public void setArangoId(String arangoId) {
this.arangoId = arangoId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public ZonedDateTime getPublishDate() {
return publishDate;
}
public void setPublishDate(ZonedDateTime publishDate) {
this.publishDate = publishDate;
}
public String getHtmlContent() {
return htmlContent;
}
public void setHtmlContent(String htmlContent) {
this.htmlContent = htmlContent;
}
}

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