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: | ||||
| 
 | ||||
| - [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"?> | ||||
| <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||
|          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||||
| <project xmlns="http://maven.apache.org/POM/4.0.0" | ||||
|     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||
|     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||||
|     <modelVersion>4.0.0</modelVersion> | ||||
|     <parent> | ||||
|         <groupId>org.springframework.boot</groupId> | ||||
|         <artifactId>spring-boot-starter-parent</artifactId> | ||||
|         <version>2.4.2</version> | ||||
|         <relativePath/> <!-- lookup parent from repository --> | ||||
|     </parent> | ||||
|     <groupId>com.baeldung.docker</groupId> | ||||
|     <artifactId>heap-sizing</artifactId> | ||||
|     <version>0.0.1-SNAPSHOT</version> | ||||
|     <name>heap-sizing</name> | ||||
|     <description>Demo project for Spring Boot</description> | ||||
|     <properties> | ||||
|         <java.version>11</java.version> | ||||
|     </properties> | ||||
| 
 | ||||
|     <parent> | ||||
|         <groupId>com.baeldung</groupId> | ||||
|         <artifactId>parent-boot-2</artifactId> | ||||
|         <version>0.0.1-SNAPSHOT</version> | ||||
|         <relativePath>../../parent-boot-2</relativePath> | ||||
|     </parent> | ||||
| 
 | ||||
|     <dependencies> | ||||
|         <dependency> | ||||
|             <groupId>org.springframework.boot</groupId> | ||||
| @ -58,4 +58,8 @@ | ||||
|         </plugins> | ||||
|     </build> | ||||
| 
 | ||||
|     <properties> | ||||
|         <java.version>11</java.version> | ||||
|     </properties> | ||||
| 
 | ||||
| </project> | ||||
|  | ||||
							
								
								
									
										74
									
								
								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> | ||||
|             <scope>test</scope> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>com.netflix.spectator</groupId> | ||||
|             <artifactId>spectator-api</artifactId> | ||||
|             <version>0.132.0</version> | ||||
|         </dependency> | ||||
|     </dependencies> | ||||
| 
 | ||||
|     <properties> | ||||
|         <dep.ver.metrics>3.1.2</dep.ver.metrics> | ||||
|         <dep.ver.servlet>3.1.0</dep.ver.servlet> | ||||
|         <netflix.servo.ver>0.12.17</netflix.servo.ver> | ||||
|         <micrometer.ver>0.12.0.RELEASE</micrometer.ver> | ||||
|         <micrometer.ver>1.7.1</micrometer.ver> | ||||
|         <!-- <fasterxml.jackson.version>2.9.1</fasterxml.jackson.version> --> | ||||
|         <spring-boot-starter-web.version>2.0.7.RELEASE</spring-boot-starter-web.version> | ||||
|         <assertj-core.version>3.11.1</assertj-core.version> | ||||
|  | ||||
| @ -1,10 +1,10 @@ | ||||
| package com.baeldung.metrics.micrometer; | ||||
| 
 | ||||
| import io.micrometer.core.instrument.binder.jvm.JvmThreadMetrics; | ||||
| import org.springframework.boot.SpringApplication; | ||||
| import org.springframework.boot.autoconfigure.SpringBootApplication; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| 
 | ||||
| import io.micrometer.core.instrument.binder.JvmThreadMetrics; | ||||
| 
 | ||||
| @SpringBootApplication | ||||
| public class MicrometerApp { | ||||
| @ -14,7 +14,7 @@ public class MicrometerApp { | ||||
|         return new JvmThreadMetrics(); | ||||
|     } | ||||
| 
 | ||||
|     public static void main(String[] args) throws Exception { | ||||
|     public static void main(String[] args) { | ||||
|         SpringApplication.run(MicrometerApp.class, args); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -1,11 +1,7 @@ | ||||
| package com.baeldung.metrics.micrometer; | ||||
| 
 | ||||
| import static org.assertj.core.api.Assertions.assertThat; | ||||
| import static org.assertj.core.api.Assertions.within; | ||||
| import static org.assertj.core.api.Assertions.withinPercentage; | ||||
| import static org.hamcrest.CoreMatchers.allOf; | ||||
| import static org.hamcrest.CoreMatchers.equalTo; | ||||
| import static org.hamcrest.collection.IsMapContaining.hasEntry; | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertThat; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| @ -16,29 +12,25 @@ import io.micrometer.core.instrument.DistributionSummary; | ||||
| import io.micrometer.core.instrument.Gauge; | ||||
| import io.micrometer.core.instrument.LongTaskTimer; | ||||
| import io.micrometer.core.instrument.Measurement; | ||||
| import io.micrometer.core.instrument.Meter.Type; | ||||
| import io.micrometer.core.instrument.MeterRegistry; | ||||
| import io.micrometer.core.instrument.Metrics; | ||||
| import io.micrometer.core.instrument.Tag; | ||||
| import io.micrometer.core.instrument.Timer; | ||||
| import io.micrometer.core.instrument.composite.CompositeMeterRegistry; | ||||
| import io.micrometer.core.instrument.distribution.HistogramSnapshot; | ||||
| import io.micrometer.core.instrument.distribution.ValueAtPercentile; | ||||
| import io.micrometer.core.instrument.simple.SimpleMeterRegistry; | ||||
| import io.micrometer.core.instrument.stats.hist.Histogram; | ||||
| import io.micrometer.core.instrument.stats.quantile.WindowSketchQuantiles; | ||||
| 
 | ||||
| import java.time.Duration; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Iterator; | ||||
| import java.util.List; | ||||
| import java.util.Arrays; | ||||
| import java.util.Map; | ||||
| import java.util.TreeMap; | ||||
| import java.util.Optional; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
| import org.assertj.core.data.Percentage; | ||||
| import org.junit.Before; | ||||
| import org.junit.Test; | ||||
| import org.junit.jupiter.api.Assertions; | ||||
| 
 | ||||
| import com.netflix.spectator.atlas.AtlasConfig; | ||||
| 
 | ||||
| @ -55,7 +47,7 @@ public class MicrometerAtlasIntegrationTest { | ||||
| 
 | ||||
|             @Override | ||||
|             public Duration step() { | ||||
|                 return Duration.ofSeconds(1); | ||||
|                 return Duration.ofSeconds(10); | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
| @ -77,9 +69,9 @@ public class MicrometerAtlasIntegrationTest { | ||||
| 
 | ||||
|         compositeRegistry.gauge("baeldung.heat", 90); | ||||
| 
 | ||||
|         Optional<Gauge> oneGauge = oneSimpleMeter | ||||
|         Optional<Gauge> oneGauge = Optional.ofNullable(oneSimpleMeter | ||||
|           .find("baeldung.heat") | ||||
|           .gauge(); | ||||
|           .gauge()); | ||||
|         assertTrue(oneGauge.isPresent()); | ||||
|         Iterator<Measurement> measurements = oneGauge | ||||
|           .get() | ||||
| @ -91,9 +83,9 @@ public class MicrometerAtlasIntegrationTest { | ||||
|           .next() | ||||
|           .getValue(), equalTo(90.00)); | ||||
| 
 | ||||
|         Optional<Gauge> atlasGauge = atlasMeterRegistry | ||||
|         Optional<Gauge> atlasGauge = Optional.ofNullable(atlasMeterRegistry | ||||
|           .find("baeldung.heat") | ||||
|           .gauge(); | ||||
|           .gauge()); | ||||
|         assertTrue(atlasGauge.isPresent()); | ||||
|         Iterator<Measurement> anotherMeasurements = atlasGauge | ||||
|           .get() | ||||
| @ -122,14 +114,14 @@ public class MicrometerAtlasIntegrationTest { | ||||
|           .increment(); | ||||
|         new CountedObject(); | ||||
| 
 | ||||
|         Optional<Counter> counterOptional = Metrics.globalRegistry | ||||
|         Optional<Counter> counterOptional = Optional.ofNullable(Metrics.globalRegistry | ||||
|           .find("objects.instance") | ||||
|           .counter(); | ||||
|           .counter()); | ||||
| 
 | ||||
|         assertTrue(counterOptional.isPresent()); | ||||
|         assertTrue(counterOptional | ||||
|         assertEquals(counterOptional | ||||
|           .get() | ||||
|           .count() == 2.0); | ||||
|           .count() , 2.0, 0.0); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
| @ -142,10 +134,10 @@ public class MicrometerAtlasIntegrationTest { | ||||
|           .register(registry); | ||||
| 
 | ||||
|         counter.increment(2.0); | ||||
|         assertTrue(counter.count() == 2); | ||||
|         assertEquals(counter.count(), 2, 0); | ||||
| 
 | ||||
|         counter.increment(-1); | ||||
|         assertTrue(counter.count() == 2); | ||||
|         assertEquals(counter.count(), 1, 0); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
| @ -161,8 +153,8 @@ public class MicrometerAtlasIntegrationTest { | ||||
| 
 | ||||
|         timer.record(30, TimeUnit.MILLISECONDS); | ||||
| 
 | ||||
|         assertTrue(2 == timer.count()); | ||||
|          | ||||
|         assertEquals(2, timer.count(), 0); | ||||
| 
 | ||||
|         assertThat(timer.totalTime(TimeUnit.MILLISECONDS)).isBetween(40.0, 55.0); | ||||
|     } | ||||
| 
 | ||||
| @ -173,12 +165,12 @@ public class MicrometerAtlasIntegrationTest { | ||||
|           .builder("3rdPartyService") | ||||
|           .register(registry); | ||||
| 
 | ||||
|         long currentTaskId = longTaskTimer.start(); | ||||
|         LongTaskTimer.Sample currentTaskId = longTaskTimer.start(); | ||||
|         try { | ||||
|             TimeUnit.MILLISECONDS.sleep(2); | ||||
|         } catch (InterruptedException ignored) { | ||||
|         } | ||||
|         long timeElapsed = longTaskTimer.stop(currentTaskId); | ||||
|         long timeElapsed = currentTaskId.stop(); | ||||
| 
 | ||||
|         assertEquals(2L, timeElapsed/((int) 1e6),1L); | ||||
|     } | ||||
| @ -191,10 +183,10 @@ public class MicrometerAtlasIntegrationTest { | ||||
|           .builder("cache.size", list, List::size) | ||||
|           .register(registry); | ||||
| 
 | ||||
|         assertTrue(gauge.value() == 0.0); | ||||
|         assertEquals(gauge.value(), 0.0, 0.0); | ||||
| 
 | ||||
|         list.add("1"); | ||||
|         assertTrue(gauge.value() == 1.0); | ||||
|         assertEquals(gauge.value(), 1.0, 0.0); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
| @ -208,18 +200,17 @@ public class MicrometerAtlasIntegrationTest { | ||||
|         distributionSummary.record(4); | ||||
|         distributionSummary.record(5); | ||||
| 
 | ||||
|         assertTrue(3 == distributionSummary.count()); | ||||
|         assertTrue(12 == distributionSummary.totalAmount()); | ||||
|         assertEquals(3, distributionSummary.count(), 0); | ||||
|         assertEquals(12, distributionSummary.totalAmount(), 0); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void givenTimer_whenEnrichWithQuantile_thenQuantilesComputed() { | ||||
|     public void givenTimer_whenEnrichWithPercentile_thenPercentilesComputed() { | ||||
|         SimpleMeterRegistry registry = new SimpleMeterRegistry(); | ||||
|         Timer timer = Timer | ||||
|           .builder("test.timer") | ||||
|           .quantiles(WindowSketchQuantiles | ||||
|             .quantiles(0.3, 0.5, 0.95) | ||||
|             .create()) | ||||
|           .publishPercentiles(0.3, 0.5, 0.95) | ||||
|           .publishPercentileHistogram() | ||||
|           .register(registry); | ||||
| 
 | ||||
|         timer.record(2, TimeUnit.SECONDS); | ||||
| @ -229,27 +220,18 @@ public class MicrometerAtlasIntegrationTest { | ||||
|         timer.record(8, TimeUnit.SECONDS); | ||||
|         timer.record(13, TimeUnit.SECONDS); | ||||
| 
 | ||||
|         Map<String, Integer> quantileMap = extractTagValueMap(registry, Type.Gauge, 1e9); | ||||
|         assertThat(quantileMap, allOf(hasEntry("quantile=0.3", 2), hasEntry("quantile=0.5", 3), hasEntry("quantile=0.95", 8))); | ||||
|     } | ||||
|         Map<Double, Double> expectedMicrometer = new TreeMap<>(); | ||||
|         expectedMicrometer.put(0.3, 1946.157056); | ||||
|         expectedMicrometer.put(0.5, 3019.89888); | ||||
|         expectedMicrometer.put(0.95, 13354.663936); | ||||
| 
 | ||||
|     private Map<String, Integer> extractTagValueMap(MeterRegistry registry, Type meterType, double valueDivisor) { | ||||
|         return registry | ||||
|           .getMeters() | ||||
|           .stream() | ||||
|           .filter(meter -> meter.getType() == meterType) | ||||
|           .collect(Collectors.toMap(meter -> { | ||||
|               Tag tag = meter | ||||
|                 .getId() | ||||
|                 .getTags() | ||||
|                 .iterator() | ||||
|                 .next(); | ||||
|               return tag.getKey() + "=" + tag.getValue(); | ||||
|           }, meter -> (int) (meter | ||||
|             .measure() | ||||
|             .iterator() | ||||
|             .next() | ||||
|             .getValue() / valueDivisor))); | ||||
|         Map<Double, Double> actualMicrometer = new TreeMap<>(); | ||||
|         ValueAtPercentile[] percentiles = timer.takeSnapshot().percentileValues(); | ||||
|         for (ValueAtPercentile percentile : percentiles) { | ||||
|             actualMicrometer.put(percentile.percentile(), percentile.value(TimeUnit.MILLISECONDS)); | ||||
|         } | ||||
| 
 | ||||
|         assertEquals(expectedMicrometer, actualMicrometer); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
| @ -257,7 +239,7 @@ public class MicrometerAtlasIntegrationTest { | ||||
|         SimpleMeterRegistry registry = new SimpleMeterRegistry(); | ||||
|         DistributionSummary hist = DistributionSummary | ||||
|           .builder("summary") | ||||
|           .histogram(Histogram.linear(0, 10, 5)) | ||||
|           .serviceLevelObjectives(1, 10, 5) | ||||
|           .register(registry); | ||||
| 
 | ||||
|         hist.record(3); | ||||
| @ -267,17 +249,28 @@ public class MicrometerAtlasIntegrationTest { | ||||
|         hist.record(13); | ||||
|         hist.record(26); | ||||
| 
 | ||||
|         Map<String, Integer> histograms = extractTagValueMap(registry, Type.Counter, 1.0); | ||||
|         Map<Integer, Double> expectedMicrometer = new TreeMap<>(); | ||||
|         expectedMicrometer.put(1,0D); | ||||
|         expectedMicrometer.put(10,2D); | ||||
|         expectedMicrometer.put(5,1D); | ||||
| 
 | ||||
|         assertThat(histograms, allOf(hasEntry("bucket=0.0", 0), hasEntry("bucket=10.0", 2), hasEntry("bucket=20.0", 2), hasEntry("bucket=30.0", 1), hasEntry("bucket=40.0", 1), hasEntry("bucket=Infinity", 0))); | ||||
|         Map<Integer, Double> actualMicrometer = new TreeMap<>(); | ||||
|         HistogramSnapshot snapshot = hist.takeSnapshot(); | ||||
|         Arrays.stream(snapshot.histogramCounts()).forEach(p -> { | ||||
|             actualMicrometer.put((Integer.valueOf((int) p.bucket())), p.count()); | ||||
|         }); | ||||
| 
 | ||||
|        assertEquals(expectedMicrometer, actualMicrometer); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void givenTimer_whenEnrichWithTimescaleHistogram_thenTimeScaleDataCollected() { | ||||
|         SimpleMeterRegistry registry = new SimpleMeterRegistry(); | ||||
|         Duration[] durations = {Duration.ofMillis(25), Duration.ofMillis(300), Duration.ofMillis(600)}; | ||||
|         Timer timer = Timer | ||||
|           .builder("timer") | ||||
|           .histogram(Histogram.linearTime(TimeUnit.MILLISECONDS, 0, 200, 3)) | ||||
|           .sla(durations) | ||||
|            .publishPercentileHistogram() | ||||
|           .register(registry); | ||||
| 
 | ||||
|         timer.record(1000, TimeUnit.MILLISECONDS); | ||||
| @ -286,10 +279,18 @@ public class MicrometerAtlasIntegrationTest { | ||||
|         timer.record(341, TimeUnit.MILLISECONDS); | ||||
|         timer.record(500, TimeUnit.MILLISECONDS); | ||||
| 
 | ||||
|         Map<String, Integer> histograms = extractTagValueMap(registry, Type.Counter, 1.0); | ||||
|         Map<Double, Double> expectedMicrometer = new TreeMap<>(); | ||||
|         expectedMicrometer.put(2.5E7,1D); | ||||
|         expectedMicrometer.put(3.0E8,1D); | ||||
|         expectedMicrometer.put(6.0E8,4D); | ||||
| 
 | ||||
|         assertThat(histograms, allOf(hasEntry("bucket=0.0", 0), hasEntry("bucket=2.0E8", 1), hasEntry("bucket=4.0E8", 1), hasEntry("bucket=Infinity", 3))); | ||||
|         Map<Double, Double> actualMicrometer = new TreeMap<>(); | ||||
|         HistogramSnapshot snapshot = timer.takeSnapshot(); | ||||
|         Arrays.stream(snapshot.histogramCounts()).forEach(p -> { | ||||
|           actualMicrometer.put((Double.valueOf((int) p.bucket())), p.count()); | ||||
|         }); | ||||
| 
 | ||||
|         assertEquals(expectedMicrometer, actualMicrometer); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -57,6 +57,7 @@ | ||||
|         <module>spring-boot-persistence</module> | ||||
|         <module>spring-boot-persistence-h2</module> | ||||
|         <module>spring-boot-persistence-mongodb</module> | ||||
|         <module>spring-data-arangodb</module> | ||||
|         <module>spring-data-cassandra</module> | ||||
|         <module>spring-data-cassandra-test</module> | ||||
|         <module>spring-data-cassandra-reactive</module> | ||||
|  | ||||
							
								
								
									
										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>jta</module> | ||||
|                 <module>kubernetes</module> | ||||
|                 <module>ksqldb</module> | ||||
|                 <!-- <module>lagom</module> --> <!-- Not a maven project --> | ||||
| 		<module>language-interop</module> | ||||
| 		<module>libraries-2</module> | ||||
| @ -940,6 +941,8 @@ | ||||
|                 <module>jsoup</module> | ||||
|                 <module>jta</module> | ||||
| 
 | ||||
|                 <module>ksqldb</module> | ||||
| 
 | ||||
|                 <!-- <module>lagom</module> --> <!-- Not a maven project --> | ||||
|                 <module>libraries-2</module> | ||||
|                 <module>libraries-3</module> | ||||
|  | ||||
| @ -9,10 +9,9 @@ | ||||
|     <version>0.0.1-SNAPSHOT</version> | ||||
| 
 | ||||
|     <parent> | ||||
|         <groupId>org.springframework.boot</groupId> | ||||
|         <artifactId>spring-boot-starter-parent</artifactId> | ||||
|         <version>2.3.2.RELEASE</version> | ||||
|         <relativePath /> <!-- lookup parent from repository --> | ||||
|         <groupId>com.baeldung.spring-boot-modules</groupId> | ||||
|         <artifactId>spring-boot-modules</artifactId> | ||||
|         <version>1.0.0-SNAPSHOT</version> | ||||
|     </parent> | ||||
| 
 | ||||
|     <dependencies> | ||||
|  | ||||
| @ -77,7 +77,8 @@ | ||||
| 
 | ||||
|     <properties> | ||||
|         <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> | ||||
| 
 | ||||
| </project> | ||||
| @ -8,10 +8,9 @@ | ||||
|     <version>0.0.1-SNAPSHOT</version> | ||||
| 
 | ||||
|     <parent> | ||||
|         <groupId>org.springframework.boot</groupId> | ||||
|         <artifactId>spring-boot-starter-parent</artifactId> | ||||
|         <version>2.4.2</version> | ||||
|         <relativePath /> <!-- lookup parent from repository --> | ||||
|         <groupId>com.baeldung.spring-boot-modules</groupId> | ||||
|         <artifactId>spring-boot-mvc-jersey</artifactId> | ||||
|         <version>0.0.1-SNAPSHOT</version> | ||||
|     </parent> | ||||
| 
 | ||||
|     <dependencies> | ||||
|  | ||||
| @ -4,7 +4,7 @@ import org.junit.jupiter.api.Test; | ||||
| import org.springframework.boot.test.context.SpringBootTest; | ||||
| 
 | ||||
| @SpringBootTest | ||||
| class JerseyApplicationIntegrationTests { | ||||
| class JerseyApplicationIntegrationTest { | ||||
| 
 | ||||
|     @Test | ||||
|     void contextLoads() { | ||||
| @ -8,10 +8,9 @@ | ||||
|     <version>0.0.1-SNAPSHOT</version> | ||||
| 
 | ||||
|     <parent> | ||||
|         <groupId>org.springframework.boot</groupId> | ||||
|         <artifactId>spring-boot-starter-parent</artifactId> | ||||
|         <version>2.4.2</version> | ||||
|         <relativePath /> <!-- lookup parent from repository --> | ||||
|         <groupId>com.baeldung.spring-boot-modules</groupId> | ||||
|         <artifactId>spring-boot-mvc-jersey</artifactId> | ||||
|         <version>0.0.1-SNAPSHOT</version> | ||||
|     </parent> | ||||
| 
 | ||||
|     <dependencies> | ||||
|  | ||||
| @ -4,7 +4,7 @@ import org.junit.jupiter.api.Test; | ||||
| import org.springframework.boot.test.context.SpringBootTest; | ||||
| 
 | ||||
| @SpringBootTest | ||||
| class MvcApplicationIntegrationTests { | ||||
| class MvcApplicationIntegrationTest { | ||||
| 
 | ||||
|     @Test | ||||
|     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