Merge branch 'eugenp:master' into master
This commit is contained in:
commit
500eb2ef90
@ -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:
|
## Relevant Articles:
|
||||||
|
|
||||||
- [Logical vs Bitwise OR Operator](https://www.baeldung.com/java-logical-vs-bitwise-or-operator)
|
- [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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,21 +1,21 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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"
|
<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">
|
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>
|
<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>
|
<groupId>com.baeldung.docker</groupId>
|
||||||
<artifactId>heap-sizing</artifactId>
|
<artifactId>heap-sizing</artifactId>
|
||||||
<version>0.0.1-SNAPSHOT</version>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
<name>heap-sizing</name>
|
<name>heap-sizing</name>
|
||||||
<description>Demo project for Spring Boot</description>
|
<description>Demo project for Spring Boot</description>
|
||||||
<properties>
|
|
||||||
<java.version>11</java.version>
|
<parent>
|
||||||
</properties>
|
<groupId>com.baeldung</groupId>
|
||||||
|
<artifactId>parent-boot-2</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<relativePath>../../parent-boot-2</relativePath>
|
||||||
|
</parent>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
@ -58,4 +58,8 @@
|
|||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>11</java.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
74
ksqldb/pom.xml
Normal file
74
ksqldb/pom.xml
Normal 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>
|
29
ksqldb/src/main/java/com/baeldung/ksqldb/Alert.java
Normal file
29
ksqldb/src/main/java/com/baeldung/ksqldb/Alert.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
10
ksqldb/src/main/java/com/baeldung/ksqldb/Reading.java
Normal file
10
ksqldb/src/main/java/com/baeldung/ksqldb/Reading.java
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package com.baeldung.ksqldb;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class Reading {
|
||||||
|
private String id;
|
||||||
|
private String timestamp;
|
||||||
|
private int reading;
|
||||||
|
}
|
60
ksqldb/src/main/java/com/baeldung/ksqldb/RowSubscriber.java
Normal file
60
ksqldb/src/main/java/com/baeldung/ksqldb/RowSubscriber.java
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
49
ksqldb/src/test/resources/docker/docker-compose.yml
Normal file
49
ksqldb/src/test/resources/docker/docker-compose.yml
Normal file
@ -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
|
6
ksqldb/src/test/resources/log4j.properties
Normal file
6
ksqldb/src/test/resources/log4j.properties
Normal file
@ -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
|
3
maven-modules/host-maven-repo-example/README.md
Normal file
3
maven-modules/host-maven-repo-example/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
### Relevant Articles:
|
||||||
|
|
||||||
|
- [Hosting a Maven Repository on GitHub](https://www.baeldung.com/maven-repo-github)
|
@ -81,13 +81,18 @@
|
|||||||
<version>${assertj-core.version}</version>
|
<version>${assertj-core.version}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.netflix.spectator</groupId>
|
||||||
|
<artifactId>spectator-api</artifactId>
|
||||||
|
<version>0.132.0</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<dep.ver.metrics>3.1.2</dep.ver.metrics>
|
<dep.ver.metrics>3.1.2</dep.ver.metrics>
|
||||||
<dep.ver.servlet>3.1.0</dep.ver.servlet>
|
<dep.ver.servlet>3.1.0</dep.ver.servlet>
|
||||||
<netflix.servo.ver>0.12.17</netflix.servo.ver>
|
<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> -->
|
<!-- <fasterxml.jackson.version>2.9.1</fasterxml.jackson.version> -->
|
||||||
<spring-boot-starter-web.version>2.0.7.RELEASE</spring-boot-starter-web.version>
|
<spring-boot-starter-web.version>2.0.7.RELEASE</spring-boot-starter-web.version>
|
||||||
<assertj-core.version>3.11.1</assertj-core.version>
|
<assertj-core.version>3.11.1</assertj-core.version>
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
package com.baeldung.metrics.micrometer;
|
package com.baeldung.metrics.micrometer;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.binder.jvm.JvmThreadMetrics;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|
||||||
import io.micrometer.core.instrument.binder.JvmThreadMetrics;
|
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
public class MicrometerApp {
|
public class MicrometerApp {
|
||||||
@ -14,7 +14,7 @@ public class MicrometerApp {
|
|||||||
return new JvmThreadMetrics();
|
return new JvmThreadMetrics();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(MicrometerApp.class, args);
|
SpringApplication.run(MicrometerApp.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
package com.baeldung.metrics.micrometer;
|
package com.baeldung.metrics.micrometer;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
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.CoreMatchers.equalTo;
|
||||||
import static org.hamcrest.collection.IsMapContaining.hasEntry;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
import static org.junit.Assert.assertTrue;
|
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.Gauge;
|
||||||
import io.micrometer.core.instrument.LongTaskTimer;
|
import io.micrometer.core.instrument.LongTaskTimer;
|
||||||
import io.micrometer.core.instrument.Measurement;
|
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.Metrics;
|
||||||
import io.micrometer.core.instrument.Tag;
|
|
||||||
import io.micrometer.core.instrument.Timer;
|
import io.micrometer.core.instrument.Timer;
|
||||||
import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
|
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.simple.SimpleMeterRegistry;
|
||||||
import io.micrometer.core.instrument.stats.hist.Histogram;
|
|
||||||
import io.micrometer.core.instrument.stats.quantile.WindowSketchQuantiles;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.assertj.core.data.Percentage;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.jupiter.api.Assertions;
|
|
||||||
|
|
||||||
import com.netflix.spectator.atlas.AtlasConfig;
|
import com.netflix.spectator.atlas.AtlasConfig;
|
||||||
|
|
||||||
@ -55,7 +47,7 @@ public class MicrometerAtlasIntegrationTest {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Duration step() {
|
public Duration step() {
|
||||||
return Duration.ofSeconds(1);
|
return Duration.ofSeconds(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -77,9 +69,9 @@ public class MicrometerAtlasIntegrationTest {
|
|||||||
|
|
||||||
compositeRegistry.gauge("baeldung.heat", 90);
|
compositeRegistry.gauge("baeldung.heat", 90);
|
||||||
|
|
||||||
Optional<Gauge> oneGauge = oneSimpleMeter
|
Optional<Gauge> oneGauge = Optional.ofNullable(oneSimpleMeter
|
||||||
.find("baeldung.heat")
|
.find("baeldung.heat")
|
||||||
.gauge();
|
.gauge());
|
||||||
assertTrue(oneGauge.isPresent());
|
assertTrue(oneGauge.isPresent());
|
||||||
Iterator<Measurement> measurements = oneGauge
|
Iterator<Measurement> measurements = oneGauge
|
||||||
.get()
|
.get()
|
||||||
@ -91,9 +83,9 @@ public class MicrometerAtlasIntegrationTest {
|
|||||||
.next()
|
.next()
|
||||||
.getValue(), equalTo(90.00));
|
.getValue(), equalTo(90.00));
|
||||||
|
|
||||||
Optional<Gauge> atlasGauge = atlasMeterRegistry
|
Optional<Gauge> atlasGauge = Optional.ofNullable(atlasMeterRegistry
|
||||||
.find("baeldung.heat")
|
.find("baeldung.heat")
|
||||||
.gauge();
|
.gauge());
|
||||||
assertTrue(atlasGauge.isPresent());
|
assertTrue(atlasGauge.isPresent());
|
||||||
Iterator<Measurement> anotherMeasurements = atlasGauge
|
Iterator<Measurement> anotherMeasurements = atlasGauge
|
||||||
.get()
|
.get()
|
||||||
@ -122,14 +114,14 @@ public class MicrometerAtlasIntegrationTest {
|
|||||||
.increment();
|
.increment();
|
||||||
new CountedObject();
|
new CountedObject();
|
||||||
|
|
||||||
Optional<Counter> counterOptional = Metrics.globalRegistry
|
Optional<Counter> counterOptional = Optional.ofNullable(Metrics.globalRegistry
|
||||||
.find("objects.instance")
|
.find("objects.instance")
|
||||||
.counter();
|
.counter());
|
||||||
|
|
||||||
assertTrue(counterOptional.isPresent());
|
assertTrue(counterOptional.isPresent());
|
||||||
assertTrue(counterOptional
|
assertEquals(counterOptional
|
||||||
.get()
|
.get()
|
||||||
.count() == 2.0);
|
.count() , 2.0, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -142,10 +134,10 @@ public class MicrometerAtlasIntegrationTest {
|
|||||||
.register(registry);
|
.register(registry);
|
||||||
|
|
||||||
counter.increment(2.0);
|
counter.increment(2.0);
|
||||||
assertTrue(counter.count() == 2);
|
assertEquals(counter.count(), 2, 0);
|
||||||
|
|
||||||
counter.increment(-1);
|
counter.increment(-1);
|
||||||
assertTrue(counter.count() == 2);
|
assertEquals(counter.count(), 1, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -161,7 +153,7 @@ public class MicrometerAtlasIntegrationTest {
|
|||||||
|
|
||||||
timer.record(30, TimeUnit.MILLISECONDS);
|
timer.record(30, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
assertTrue(2 == timer.count());
|
assertEquals(2, timer.count(), 0);
|
||||||
|
|
||||||
assertThat(timer.totalTime(TimeUnit.MILLISECONDS)).isBetween(40.0, 55.0);
|
assertThat(timer.totalTime(TimeUnit.MILLISECONDS)).isBetween(40.0, 55.0);
|
||||||
}
|
}
|
||||||
@ -173,12 +165,12 @@ public class MicrometerAtlasIntegrationTest {
|
|||||||
.builder("3rdPartyService")
|
.builder("3rdPartyService")
|
||||||
.register(registry);
|
.register(registry);
|
||||||
|
|
||||||
long currentTaskId = longTaskTimer.start();
|
LongTaskTimer.Sample currentTaskId = longTaskTimer.start();
|
||||||
try {
|
try {
|
||||||
TimeUnit.MILLISECONDS.sleep(2);
|
TimeUnit.MILLISECONDS.sleep(2);
|
||||||
} catch (InterruptedException ignored) {
|
} catch (InterruptedException ignored) {
|
||||||
}
|
}
|
||||||
long timeElapsed = longTaskTimer.stop(currentTaskId);
|
long timeElapsed = currentTaskId.stop();
|
||||||
|
|
||||||
assertEquals(2L, timeElapsed/((int) 1e6),1L);
|
assertEquals(2L, timeElapsed/((int) 1e6),1L);
|
||||||
}
|
}
|
||||||
@ -191,10 +183,10 @@ public class MicrometerAtlasIntegrationTest {
|
|||||||
.builder("cache.size", list, List::size)
|
.builder("cache.size", list, List::size)
|
||||||
.register(registry);
|
.register(registry);
|
||||||
|
|
||||||
assertTrue(gauge.value() == 0.0);
|
assertEquals(gauge.value(), 0.0, 0.0);
|
||||||
|
|
||||||
list.add("1");
|
list.add("1");
|
||||||
assertTrue(gauge.value() == 1.0);
|
assertEquals(gauge.value(), 1.0, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -208,18 +200,17 @@ public class MicrometerAtlasIntegrationTest {
|
|||||||
distributionSummary.record(4);
|
distributionSummary.record(4);
|
||||||
distributionSummary.record(5);
|
distributionSummary.record(5);
|
||||||
|
|
||||||
assertTrue(3 == distributionSummary.count());
|
assertEquals(3, distributionSummary.count(), 0);
|
||||||
assertTrue(12 == distributionSummary.totalAmount());
|
assertEquals(12, distributionSummary.totalAmount(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void givenTimer_whenEnrichWithQuantile_thenQuantilesComputed() {
|
public void givenTimer_whenEnrichWithPercentile_thenPercentilesComputed() {
|
||||||
SimpleMeterRegistry registry = new SimpleMeterRegistry();
|
SimpleMeterRegistry registry = new SimpleMeterRegistry();
|
||||||
Timer timer = Timer
|
Timer timer = Timer
|
||||||
.builder("test.timer")
|
.builder("test.timer")
|
||||||
.quantiles(WindowSketchQuantiles
|
.publishPercentiles(0.3, 0.5, 0.95)
|
||||||
.quantiles(0.3, 0.5, 0.95)
|
.publishPercentileHistogram()
|
||||||
.create())
|
|
||||||
.register(registry);
|
.register(registry);
|
||||||
|
|
||||||
timer.record(2, TimeUnit.SECONDS);
|
timer.record(2, TimeUnit.SECONDS);
|
||||||
@ -229,27 +220,18 @@ public class MicrometerAtlasIntegrationTest {
|
|||||||
timer.record(8, TimeUnit.SECONDS);
|
timer.record(8, TimeUnit.SECONDS);
|
||||||
timer.record(13, TimeUnit.SECONDS);
|
timer.record(13, TimeUnit.SECONDS);
|
||||||
|
|
||||||
Map<String, Integer> quantileMap = extractTagValueMap(registry, Type.Gauge, 1e9);
|
Map<Double, Double> expectedMicrometer = new TreeMap<>();
|
||||||
assertThat(quantileMap, allOf(hasEntry("quantile=0.3", 2), hasEntry("quantile=0.5", 3), hasEntry("quantile=0.95", 8)));
|
expectedMicrometer.put(0.3, 1946.157056);
|
||||||
|
expectedMicrometer.put(0.5, 3019.89888);
|
||||||
|
expectedMicrometer.put(0.95, 13354.663936);
|
||||||
|
|
||||||
|
Map<Double, Double> actualMicrometer = new TreeMap<>();
|
||||||
|
ValueAtPercentile[] percentiles = timer.takeSnapshot().percentileValues();
|
||||||
|
for (ValueAtPercentile percentile : percentiles) {
|
||||||
|
actualMicrometer.put(percentile.percentile(), percentile.value(TimeUnit.MILLISECONDS));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Integer> extractTagValueMap(MeterRegistry registry, Type meterType, double valueDivisor) {
|
assertEquals(expectedMicrometer, actualMicrometer);
|
||||||
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)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -257,7 +239,7 @@ public class MicrometerAtlasIntegrationTest {
|
|||||||
SimpleMeterRegistry registry = new SimpleMeterRegistry();
|
SimpleMeterRegistry registry = new SimpleMeterRegistry();
|
||||||
DistributionSummary hist = DistributionSummary
|
DistributionSummary hist = DistributionSummary
|
||||||
.builder("summary")
|
.builder("summary")
|
||||||
.histogram(Histogram.linear(0, 10, 5))
|
.serviceLevelObjectives(1, 10, 5)
|
||||||
.register(registry);
|
.register(registry);
|
||||||
|
|
||||||
hist.record(3);
|
hist.record(3);
|
||||||
@ -267,17 +249,28 @@ public class MicrometerAtlasIntegrationTest {
|
|||||||
hist.record(13);
|
hist.record(13);
|
||||||
hist.record(26);
|
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
|
@Test
|
||||||
public void givenTimer_whenEnrichWithTimescaleHistogram_thenTimeScaleDataCollected() {
|
public void givenTimer_whenEnrichWithTimescaleHistogram_thenTimeScaleDataCollected() {
|
||||||
SimpleMeterRegistry registry = new SimpleMeterRegistry();
|
SimpleMeterRegistry registry = new SimpleMeterRegistry();
|
||||||
|
Duration[] durations = {Duration.ofMillis(25), Duration.ofMillis(300), Duration.ofMillis(600)};
|
||||||
Timer timer = Timer
|
Timer timer = Timer
|
||||||
.builder("timer")
|
.builder("timer")
|
||||||
.histogram(Histogram.linearTime(TimeUnit.MILLISECONDS, 0, 200, 3))
|
.sla(durations)
|
||||||
|
.publishPercentileHistogram()
|
||||||
.register(registry);
|
.register(registry);
|
||||||
|
|
||||||
timer.record(1000, TimeUnit.MILLISECONDS);
|
timer.record(1000, TimeUnit.MILLISECONDS);
|
||||||
@ -286,10 +279,18 @@ public class MicrometerAtlasIntegrationTest {
|
|||||||
timer.record(341, TimeUnit.MILLISECONDS);
|
timer.record(341, TimeUnit.MILLISECONDS);
|
||||||
timer.record(500, 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,7 @@
|
|||||||
<module>spring-boot-persistence</module>
|
<module>spring-boot-persistence</module>
|
||||||
<module>spring-boot-persistence-h2</module>
|
<module>spring-boot-persistence-h2</module>
|
||||||
<module>spring-boot-persistence-mongodb</module>
|
<module>spring-boot-persistence-mongodb</module>
|
||||||
|
<module>spring-data-arangodb</module>
|
||||||
<module>spring-data-cassandra</module>
|
<module>spring-data-cassandra</module>
|
||||||
<module>spring-data-cassandra-test</module>
|
<module>spring-data-cassandra-test</module>
|
||||||
<module>spring-data-cassandra-reactive</module>
|
<module>spring-data-cassandra-reactive</module>
|
||||||
|
6
persistence-modules/spring-data-arangodb/README.md
Normal file
6
persistence-modules/spring-data-arangodb/README.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
=========
|
||||||
|
|
||||||
|
## Spring Data ArangoDB
|
||||||
|
|
||||||
|
|
||||||
|
### Relevant Articles:
|
29
persistence-modules/spring-data-arangodb/pom.xml
Normal file
29
persistence-modules/spring-data-arangodb/pom.xml
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
package com.baeldung.arangodb.model;
|
||||||
|
|
||||||
|
import com.arangodb.springframework.annotation.Edge;
|
||||||
|
import com.arangodb.springframework.annotation.From;
|
||||||
|
import com.arangodb.springframework.annotation.To;
|
||||||
|
|
||||||
|
@Edge
|
||||||
|
public class ArticleLink {
|
||||||
|
|
||||||
|
@From
|
||||||
|
private Article article;
|
||||||
|
|
||||||
|
@To
|
||||||
|
private Author author;
|
||||||
|
|
||||||
|
public ArticleLink() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArticleLink(Article article, Author author) {
|
||||||
|
this.article = article;
|
||||||
|
this.author = author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Article getArticle() {
|
||||||
|
return article;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setArticle(Article article) {
|
||||||
|
this.article = article;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Author getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthor(Author author) {
|
||||||
|
this.author = author;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package com.baeldung.arangodb.model;
|
||||||
|
|
||||||
|
import com.arangodb.springframework.annotation.ArangoId;
|
||||||
|
import com.arangodb.springframework.annotation.Document;
|
||||||
|
import org.springframework.data.annotation.Id;
|
||||||
|
|
||||||
|
@Document("articles")
|
||||||
|
public class Author {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@ArangoId
|
||||||
|
private String arangoId;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public Author() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Author(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package com.baeldung.arangodb.repository;
|
||||||
|
|
||||||
|
import com.arangodb.springframework.annotation.Query;
|
||||||
|
import com.arangodb.springframework.repository.ArangoRepository;
|
||||||
|
import com.baeldung.arangodb.model.Article;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface ArticleRepository extends ArangoRepository<Article, String> {
|
||||||
|
|
||||||
|
Iterable<Article> findByAuthor(String author);
|
||||||
|
|
||||||
|
@Query("FOR a IN articles FILTER a.author == @author SORT a.publishDate ASC RETURN a")
|
||||||
|
Iterable<Article> getByAuthor(@Param("author") String author);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
arangodb.hosts=127.0.0.1:8529
|
||||||
|
arangodb.user=root
|
||||||
|
arangodb.password=password
|
@ -0,0 +1,113 @@
|
|||||||
|
package com.baeldung.arangodb;
|
||||||
|
|
||||||
|
import com.baeldung.arangodb.model.Article;
|
||||||
|
import com.baeldung.arangodb.repository.ArticleRepository;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
public class ArticleRepositoryIntegrationTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ArticleRepository articleRepository;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenNewArticle_whenSaveInArangoDb_thenDataIsCorrect() {
|
||||||
|
Article newArticle = new Article(
|
||||||
|
"ArangoDb with Spring Data",
|
||||||
|
"Baeldung Writer",
|
||||||
|
ZonedDateTime.now(),
|
||||||
|
"<html>Some HTML content</html>"
|
||||||
|
);
|
||||||
|
|
||||||
|
Article savedArticle = articleRepository.save(newArticle);
|
||||||
|
|
||||||
|
assertNotNull(savedArticle.getId());
|
||||||
|
assertNotNull(savedArticle.getArangoId());
|
||||||
|
|
||||||
|
assertEquals(savedArticle.getName(), newArticle.getName());
|
||||||
|
assertEquals(savedArticle.getAuthor(), newArticle.getAuthor());
|
||||||
|
assertEquals(savedArticle.getPublishDate(), newArticle.getPublishDate());
|
||||||
|
assertEquals(savedArticle.getHtmlContent(), newArticle.getHtmlContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenArticleId_whenReadFromArangoDb_thenDataIsCorrect() {
|
||||||
|
Article newArticle = new Article(
|
||||||
|
"ArangoDb with Spring Data",
|
||||||
|
"Baeldung Writer",
|
||||||
|
ZonedDateTime.now(),
|
||||||
|
"<html>Some HTML content</html>"
|
||||||
|
);
|
||||||
|
|
||||||
|
Article savedArticle = articleRepository.save(newArticle);
|
||||||
|
|
||||||
|
String articleId = savedArticle.getId();
|
||||||
|
|
||||||
|
Optional<Article> article = articleRepository.findById(articleId);
|
||||||
|
assertTrue(article.isPresent());
|
||||||
|
|
||||||
|
Article foundArticle = article.get();
|
||||||
|
|
||||||
|
assertEquals(foundArticle.getId(), articleId);
|
||||||
|
assertEquals(foundArticle.getArangoId(), savedArticle.getArangoId());
|
||||||
|
assertEquals(foundArticle.getName(), savedArticle.getName());
|
||||||
|
assertEquals(foundArticle.getAuthor(), savedArticle.getAuthor());
|
||||||
|
assertEquals(foundArticle.getPublishDate(), savedArticle.getPublishDate());
|
||||||
|
assertEquals(foundArticle.getHtmlContent(), savedArticle.getHtmlContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenArticleId_whenDeleteFromArangoDb_thenDataIsGone() {
|
||||||
|
Article newArticle = new Article(
|
||||||
|
"ArangoDb with Spring Data",
|
||||||
|
"Baeldung Writer",
|
||||||
|
ZonedDateTime.now(),
|
||||||
|
"<html>Some HTML content</html>"
|
||||||
|
);
|
||||||
|
|
||||||
|
Article savedArticle = articleRepository.save(newArticle);
|
||||||
|
|
||||||
|
String articleId = savedArticle.getId();
|
||||||
|
|
||||||
|
articleRepository.deleteById(articleId);
|
||||||
|
|
||||||
|
Optional<Article> article = articleRepository.findById(articleId);
|
||||||
|
assertFalse(article.isPresent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenAuthorName_whenGetByAuthor_thenListOfArticles() {
|
||||||
|
Article newArticle = new Article(
|
||||||
|
"ArangoDb with Spring Data",
|
||||||
|
"Baeldung Writer",
|
||||||
|
ZonedDateTime.now(),
|
||||||
|
"<html>Some HTML content</html>"
|
||||||
|
);
|
||||||
|
articleRepository.save(newArticle);
|
||||||
|
|
||||||
|
Iterable<Article> articlesByAuthor = articleRepository.findByAuthor(newArticle.getAuthor());
|
||||||
|
List<Article> articlesByAuthorList = new ArrayList<>();
|
||||||
|
articlesByAuthor.forEach(articlesByAuthorList::add);
|
||||||
|
|
||||||
|
assertEquals(1, articlesByAuthorList.size());
|
||||||
|
|
||||||
|
Article foundArticle = articlesByAuthorList.get(0);
|
||||||
|
assertEquals(foundArticle.getName(), newArticle.getName());
|
||||||
|
assertEquals(foundArticle.getAuthor(), newArticle.getAuthor());
|
||||||
|
assertEquals(foundArticle.getPublishDate(), newArticle.getPublishDate());
|
||||||
|
assertEquals(foundArticle.getHtmlContent(), newArticle.getHtmlContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
3
pom.xml
3
pom.xml
@ -472,6 +472,7 @@
|
|||||||
<module>jsoup</module>
|
<module>jsoup</module>
|
||||||
<module>jta</module>
|
<module>jta</module>
|
||||||
<module>kubernetes</module>
|
<module>kubernetes</module>
|
||||||
|
<module>ksqldb</module>
|
||||||
<!-- <module>lagom</module> --> <!-- Not a maven project -->
|
<!-- <module>lagom</module> --> <!-- Not a maven project -->
|
||||||
<module>language-interop</module>
|
<module>language-interop</module>
|
||||||
<module>libraries-2</module>
|
<module>libraries-2</module>
|
||||||
@ -940,6 +941,8 @@
|
|||||||
<module>jsoup</module>
|
<module>jsoup</module>
|
||||||
<module>jta</module>
|
<module>jta</module>
|
||||||
|
|
||||||
|
<module>ksqldb</module>
|
||||||
|
|
||||||
<!-- <module>lagom</module> --> <!-- Not a maven project -->
|
<!-- <module>lagom</module> --> <!-- Not a maven project -->
|
||||||
<module>libraries-2</module>
|
<module>libraries-2</module>
|
||||||
<module>libraries-3</module>
|
<module>libraries-3</module>
|
||||||
|
@ -9,10 +9,9 @@
|
|||||||
<version>0.0.1-SNAPSHOT</version>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>com.baeldung.spring-boot-modules</groupId>
|
||||||
<artifactId>spring-boot-starter-parent</artifactId>
|
<artifactId>spring-boot-modules</artifactId>
|
||||||
<version>2.3.2.RELEASE</version>
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
<relativePath /> <!-- lookup parent from repository -->
|
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
@ -77,7 +77,8 @@
|
|||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<keycloak-adapter-bom.version>13.0.1</keycloak-adapter-bom.version>
|
<keycloak-adapter-bom.version>13.0.1</keycloak-adapter-bom.version>
|
||||||
<spring-boot.version>2.4.7</spring-boot.version>
|
<!-- explicit version declaration to be removed when parent-boot-2 is upgraded to 2.5.3 or above -->
|
||||||
|
<spring-boot.version>2.5.3</spring-boot.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
</project>
|
</project>
|
@ -8,10 +8,9 @@
|
|||||||
<version>0.0.1-SNAPSHOT</version>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>com.baeldung.spring-boot-modules</groupId>
|
||||||
<artifactId>spring-boot-starter-parent</artifactId>
|
<artifactId>spring-boot-mvc-jersey</artifactId>
|
||||||
<version>2.4.2</version>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
<relativePath /> <!-- lookup parent from repository -->
|
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
@ -4,7 +4,7 @@ import org.junit.jupiter.api.Test;
|
|||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
class JerseyApplicationIntegrationTests {
|
class JerseyApplicationIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void contextLoads() {
|
void contextLoads() {
|
@ -8,10 +8,9 @@
|
|||||||
<version>0.0.1-SNAPSHOT</version>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>com.baeldung.spring-boot-modules</groupId>
|
||||||
<artifactId>spring-boot-starter-parent</artifactId>
|
<artifactId>spring-boot-mvc-jersey</artifactId>
|
||||||
<version>2.4.2</version>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
<relativePath /> <!-- lookup parent from repository -->
|
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
@ -4,7 +4,7 @@ import org.junit.jupiter.api.Test;
|
|||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
class MvcApplicationIntegrationTests {
|
class MvcApplicationIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void contextLoads() {
|
void contextLoads() {
|
@ -0,0 +1,15 @@
|
|||||||
|
package com.baeldung.annotations.globalmethod;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
@EnableWebSecurity
|
||||||
|
public class AnnotationSecuredApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(AnnotationSecuredApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package com.baeldung.annotations.globalmethod;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import javax.annotation.security.RolesAllowed;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@EnableGlobalMethodSecurity(jsr250Enabled = true, prePostEnabled = true)
|
||||||
|
public class AnnotationSecuredController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
DifferentClass differentClass;
|
||||||
|
|
||||||
|
@GetMapping("/public")
|
||||||
|
public String publicHello() {
|
||||||
|
return "Hello Public";
|
||||||
|
}
|
||||||
|
|
||||||
|
@RolesAllowed("ADMIN")
|
||||||
|
@GetMapping("/admin")
|
||||||
|
public String adminHello() {
|
||||||
|
return "Hello Admin";
|
||||||
|
}
|
||||||
|
|
||||||
|
@RolesAllowed("USER")
|
||||||
|
@GetMapping("/protected")
|
||||||
|
public String jsr250Hello() {
|
||||||
|
return "Hello Jsr250";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/indirect")
|
||||||
|
public String indirectHello() {
|
||||||
|
return jsr250Hello();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/differentclass")
|
||||||
|
public String differentClassHello() {
|
||||||
|
return differentClass.differentJsr250Hello();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasRole('USER')")
|
||||||
|
public String preAuthorizeHello() {
|
||||||
|
return "Hello PreAuthorize";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package com.baeldung.annotations.globalmethod;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
|
||||||
|
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.configuration.WebSecurityCustomizer;
|
||||||
|
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
public class AnnotationSecuredStaticResourceConfig extends WebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public WebSecurityCustomizer ignoreResources() {
|
||||||
|
return (webSecurity) -> webSecurity
|
||||||
|
.ignoring()
|
||||||
|
.antMatchers("/hello/*");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package com.baeldung.annotations.globalmethod;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.security.RolesAllowed;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class DifferentClass {
|
||||||
|
@RolesAllowed("USER")
|
||||||
|
public String differentJsr250Hello() {
|
||||||
|
return "Hello Jsr250";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.baeldung.annotations.websecurity;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
@EnableWebSecurity
|
||||||
|
public class ConfigSecuredApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(ConfigSecuredApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package com.baeldung.annotations.websecurity;
|
||||||
|
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||||
|
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@EnableWebSecurity
|
||||||
|
public class ConfigSecuredController {
|
||||||
|
|
||||||
|
@GetMapping("/public")
|
||||||
|
public String publicHello() {
|
||||||
|
return "Hello Public";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/protected")
|
||||||
|
public String protectedHello() {
|
||||||
|
return "Hello from protected";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/admin")
|
||||||
|
public String adminHello() {
|
||||||
|
return "Hello from admin";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package com.baeldung.annotations.websecurity;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.WebSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
public class CustomWebSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.authorizeRequests()
|
||||||
|
.antMatchers("/admin/**")
|
||||||
|
.hasRole("ADMIN")
|
||||||
|
.antMatchers("/protected/**")
|
||||||
|
.hasRole("USER");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(WebSecurity web) throws Exception {
|
||||||
|
web
|
||||||
|
.ignoring()
|
||||||
|
.antMatchers("/public/*");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
Hello From Baeldung
|
@ -0,0 +1,104 @@
|
|||||||
|
package com.baeldung.annotations.globalmethod;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
|
import org.springframework.security.test.context.support.WithAnonymousUser;
|
||||||
|
import org.springframework.security.test.context.support.WithMockUser;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
|
||||||
|
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@SpringBootTest(webEnvironment = RANDOM_PORT)
|
||||||
|
public class GlobalMethodSpringBootIntegrationTest {
|
||||||
|
public static final String HELLO_JSR_250 = "Hello Jsr250";
|
||||||
|
public static final String HELLO_PUBLIC = "Hello Public";
|
||||||
|
public static final String HELLO_PRE_AUTHORIZE = "Hello PreAuthorize";
|
||||||
|
public static final String PUBLIC_RESOURCE = "/hello/baeldung.txt";
|
||||||
|
public static final String HELLO_FROM_PUBLIC_RESOURCE = "Hello From Baeldung";
|
||||||
|
private static final String PROTECTED_METHOD = "/protected";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TestRestTemplate template;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AnnotationSecuredController api;
|
||||||
|
|
||||||
|
@WithMockUser(username="baeldung", roles = "USER")
|
||||||
|
@Test
|
||||||
|
public void givenUserWithRole_whenJsr250_thenOk() {
|
||||||
|
assertThat(api.jsr250Hello()).isEqualTo(HELLO_JSR_250);
|
||||||
|
}
|
||||||
|
|
||||||
|
@WithMockUser(username="baeldung", roles = "NOT-USER")
|
||||||
|
@Test(expected = AccessDeniedException.class)
|
||||||
|
public void givenWrongRole_whenJsr250_thenAccessDenied() {
|
||||||
|
api.jsr250Hello();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithAnonymousUser
|
||||||
|
public void givenAnonymousUser_whenPublic_thenOk() {
|
||||||
|
assertThat(api.publicHello()).isEqualTo(HELLO_PUBLIC);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = AccessDeniedException.class)
|
||||||
|
@WithAnonymousUser
|
||||||
|
public void givenAnonymousUser_whenJsr250_thenAccessDenied() {
|
||||||
|
api.jsr250Hello();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests for indirect calling of method
|
||||||
|
@Test
|
||||||
|
@WithAnonymousUser
|
||||||
|
public void givenAnonymousUser_whenIndirectCall_thenNoSecurity() {
|
||||||
|
assertThat(api.indirectHello()).isEqualTo(HELLO_JSR_250);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = AccessDeniedException.class)
|
||||||
|
@WithAnonymousUser
|
||||||
|
public void givenAnonymousUser_whenIndirectToDifferentClass_thenAccessDenied() {
|
||||||
|
api.differentClassHello();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests for static resource
|
||||||
|
@Test
|
||||||
|
public void givenPublicResource_whenGetViaWeb_thenOk() {
|
||||||
|
ResponseEntity<String> result = template.getForEntity(PUBLIC_RESOURCE, String.class);
|
||||||
|
assertEquals(HELLO_FROM_PUBLIC_RESOURCE, result.getBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenProtectedMethod_whenGetViaWeb_thenRedirectToLogin() {
|
||||||
|
ResponseEntity<String> result = template.getForEntity(PROTECTED_METHOD, String.class);
|
||||||
|
assertEquals(HttpStatus.FOUND, result.getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests for preAuthorize annotations
|
||||||
|
@WithMockUser(username="baeldung", roles = "USER")
|
||||||
|
@Test
|
||||||
|
public void givenUserWithRole_whenCallPreAuthorize_thenOk() {
|
||||||
|
assertThat(api.preAuthorizeHello()).isEqualTo(HELLO_PRE_AUTHORIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@WithMockUser(username="baeldung", roles = "NOT-USER")
|
||||||
|
@Test(expected = AccessDeniedException.class)
|
||||||
|
public void givenWrongRole_whenCallPreAuthorize_thenAccessDenied() {
|
||||||
|
api.preAuthorizeHello();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = AccessDeniedException.class)
|
||||||
|
@WithAnonymousUser
|
||||||
|
public void givenAnonymousUser_whenCallPreAuthorize_thenAccessDenied() {
|
||||||
|
api.preAuthorizeHello();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
package com.baeldung.annotations.websecurity;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
|
||||||
|
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@SpringBootTest(webEnvironment = RANDOM_PORT)
|
||||||
|
public class WebSecuritySpringBootIntegrationTest {
|
||||||
|
private static final String PUBLIC_RESOURCE = "/hello/baeldung.txt";
|
||||||
|
private static final String HELLO_FROM_PUBLIC_RESOURCE = "Hello From Baeldung";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ConfigSecuredController api;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TestRestTemplate template;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenCallPublicDirectly_thenOk() {
|
||||||
|
assertThat(api.publicHello()).isEqualTo("Hello Public");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenCallProtectedDirectly_thenNoSecurity() {
|
||||||
|
assertThat(api.protectedHello()).isEqualTo("Hello from protected");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenGetProtectedViaWeb_thenForbidden() {
|
||||||
|
ResponseEntity<String> result = template.getForEntity("/protected", String.class);
|
||||||
|
assertEquals(HttpStatus.FORBIDDEN, result.getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenGetAdminViaWeb_thenForbidden() {
|
||||||
|
ResponseEntity<String> result = template.getForEntity("/admin", String.class);
|
||||||
|
assertEquals(HttpStatus.FORBIDDEN, result.getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenGetPublicViaWeb_thenSuccess() {
|
||||||
|
ResponseEntity<String> result = template.getForEntity("/public", String.class);
|
||||||
|
assertEquals(HttpStatus.OK, result.getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenPublicResource_whenGetViaWeb_thenOk() {
|
||||||
|
ResponseEntity<String> result = template.getForEntity(PUBLIC_RESOURCE, String.class);
|
||||||
|
assertEquals(HELLO_FROM_PUBLIC_RESOURCE, result.getBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
3
spring-cloud/spring-cloud-dapr/README.md
Normal file
3
spring-cloud/spring-cloud-dapr/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
### Relevant Articles:
|
||||||
|
|
||||||
|
- [An Intro to Dapr with Spring Cloud Gateway](https://www.baeldung.com/dapr-spring-cloud-gateway)
|
@ -0,0 +1,5 @@
|
|||||||
|
apiVersion: dapr.io/v1alpha1
|
||||||
|
kind: Configuration
|
||||||
|
metadata:
|
||||||
|
name: daprConfig
|
||||||
|
spec: {}
|
@ -0,0 +1,9 @@
|
|||||||
|
apiVersion: dapr.io/v1alpha1
|
||||||
|
kind: Configuration
|
||||||
|
metadata:
|
||||||
|
name: daprConfig
|
||||||
|
spec:
|
||||||
|
nameResolution:
|
||||||
|
component: "consul"
|
||||||
|
configuration:
|
||||||
|
selfRegister: true
|
@ -0,0 +1,13 @@
|
|||||||
|
apiVersion: dapr.io/v1alpha1
|
||||||
|
kind: Configuration
|
||||||
|
metadata:
|
||||||
|
name: daprConfig
|
||||||
|
spec:
|
||||||
|
nameResolution:
|
||||||
|
component: "consul"
|
||||||
|
configuration:
|
||||||
|
selfRegister: true
|
||||||
|
tracing:
|
||||||
|
samplingRate: "1"
|
||||||
|
zipkin:
|
||||||
|
endpointAddress: "http://localhost:9411/api/v2/spans"
|
44
spring-cloud/spring-cloud-dapr/gateway/pom.xml
Normal file
44
spring-cloud/spring-cloud-dapr/gateway/pom.xml
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?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>
|
||||||
|
<groupId>com.baeldung.spring.cloud.spring-cloud-dapr</groupId>
|
||||||
|
<artifactId>gateway</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>2.5.2</version>
|
||||||
|
<relativePath/>
|
||||||
|
</parent>
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>11</maven.compiler.source>
|
||||||
|
<maven.compiler.target>11</maven.compiler.target>
|
||||||
|
</properties>
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-dependencies</artifactId>
|
||||||
|
<version>2020.0.3</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-gateway</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
@ -0,0 +1,11 @@
|
|||||||
|
package org.example.gateway;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class GatewayApp {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(GatewayApp.class, args);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
server:
|
||||||
|
port: 3000
|
||||||
|
|
||||||
|
spring:
|
||||||
|
cloud:
|
||||||
|
gateway:
|
||||||
|
routes:
|
||||||
|
- id: greeting-service
|
||||||
|
uri: http://localhost:3001/
|
||||||
|
predicates:
|
||||||
|
- Path=/**
|
||||||
|
filters:
|
||||||
|
- RewritePath=/?(?<segment>.*), /$\{segment}
|
||||||
|
|
@ -0,0 +1,13 @@
|
|||||||
|
server:
|
||||||
|
port: 3000
|
||||||
|
|
||||||
|
spring:
|
||||||
|
cloud:
|
||||||
|
gateway:
|
||||||
|
routes:
|
||||||
|
- id: greeting-service
|
||||||
|
uri: http://localhost:4000/
|
||||||
|
predicates:
|
||||||
|
- Path=/**
|
||||||
|
filters:
|
||||||
|
- RewritePath=//?(?<segment>.*), /v1.0/invoke/greeting/method/$\{segment}
|
33
spring-cloud/spring-cloud-dapr/greeting/pom.xml
Normal file
33
spring-cloud/spring-cloud-dapr/greeting/pom.xml
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?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>
|
||||||
|
<groupId>com.baeldung.spring.cloud.spring-cloud-dapr</groupId>
|
||||||
|
<artifactId>greeting</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>2.5.2</version>
|
||||||
|
<relativePath/>
|
||||||
|
</parent>
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>11</maven.compiler.source>
|
||||||
|
<maven.compiler.target>11</maven.compiler.target>
|
||||||
|
</properties>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
@ -0,0 +1,11 @@
|
|||||||
|
package org.example.hello;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class GreetingApp {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(GreetingApp.class, args);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package org.example.hello;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class GreetingController {
|
||||||
|
@GetMapping(value = "/")
|
||||||
|
public String getGreeting() {
|
||||||
|
return "Welcome to the greeting service.";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = "/hello")
|
||||||
|
public String getHello() {
|
||||||
|
return "Hello world!";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = "/goodbye")
|
||||||
|
public String getGoodbye() {
|
||||||
|
return "Goodbye, cruel world!";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
server:
|
||||||
|
port: 3001
|
19
spring-cloud/spring-cloud-dapr/pom.xml
Normal file
19
spring-cloud/spring-cloud-dapr/pom.xml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?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-cloud-dapr</artifactId>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.baeldung.spring.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<modules>
|
||||||
|
<module>gateway</module>
|
||||||
|
<module>greeting</module>
|
||||||
|
</modules>
|
||||||
|
</project>
|
Loading…
x
Reference in New Issue
Block a user