Merge branch 'eugenp:master' into JAVA-18149
This commit is contained in:
commit
3eb4c1f078
|
@ -67,6 +67,8 @@ ethereum/logs/
|
||||||
jmeter/src/main/resources/*-JMeter.csv
|
jmeter/src/main/resources/*-JMeter.csv
|
||||||
jmeter/src/main/resources/*-Basic*.csv
|
jmeter/src/main/resources/*-Basic*.csv
|
||||||
jmeter/src/main/resources/*-JMeter*.csv
|
jmeter/src/main/resources/*-JMeter*.csv
|
||||||
|
jmeter/src/main/resources/*ReportsDashboard*.csv
|
||||||
|
jmeter/src/main/resources/dashboard/*ReportsDashboard*.csv
|
||||||
|
|
||||||
ninja/devDb.mv.db
|
ninja/devDb.mv.db
|
||||||
|
|
||||||
|
|
|
@ -80,16 +80,20 @@ public class BaeldungIntegrationTest {
|
||||||
private void marshalCourseRepo(CourseRepo courseRepo) throws Exception {
|
private void marshalCourseRepo(CourseRepo courseRepo) throws Exception {
|
||||||
AegisWriter<XMLStreamWriter> writer = context.createXMLStreamWriter();
|
AegisWriter<XMLStreamWriter> writer = context.createXMLStreamWriter();
|
||||||
AegisType aegisType = context.getTypeMapping().getType(CourseRepo.class);
|
AegisType aegisType = context.getTypeMapping().getType(CourseRepo.class);
|
||||||
XMLStreamWriter xmlWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(new FileOutputStream(fileName));
|
final FileOutputStream stream = new FileOutputStream(fileName);
|
||||||
|
XMLStreamWriter xmlWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(stream);
|
||||||
writer.write(courseRepo, new QName("http://aegis.cxf.baeldung.com", "baeldung"), false, xmlWriter, aegisType);
|
writer.write(courseRepo, new QName("http://aegis.cxf.baeldung.com", "baeldung"), false, xmlWriter, aegisType);
|
||||||
xmlWriter.close();
|
xmlWriter.close();
|
||||||
|
stream.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private CourseRepo unmarshalCourseRepo() throws Exception {
|
private CourseRepo unmarshalCourseRepo() throws Exception {
|
||||||
AegisReader<XMLStreamReader> reader = context.createXMLStreamReader();
|
AegisReader<XMLStreamReader> reader = context.createXMLStreamReader();
|
||||||
XMLStreamReader xmlReader = XMLInputFactory.newInstance().createXMLStreamReader(new FileInputStream(fileName));
|
final FileInputStream stream = new FileInputStream(fileName);
|
||||||
|
XMLStreamReader xmlReader = XMLInputFactory.newInstance().createXMLStreamReader(stream);
|
||||||
CourseRepo courseRepo = (CourseRepo) reader.read(xmlReader, context.getTypeMapping().getType(CourseRepo.class));
|
CourseRepo courseRepo = (CourseRepo) reader.read(xmlReader, context.getTypeMapping().getType(CourseRepo.class));
|
||||||
xmlReader.close();
|
xmlReader.close();
|
||||||
|
stream.close();
|
||||||
return courseRepo;
|
return courseRepo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,7 +101,7 @@ public class BaeldungIntegrationTest {
|
||||||
public void cleanup(){
|
public void cleanup(){
|
||||||
File testFile = new File(fileName);
|
File testFile = new File(fileName);
|
||||||
if (testFile.exists()) {
|
if (testFile.exists()) {
|
||||||
testFile.delete();
|
testFile.deleteOnExit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -63,6 +63,7 @@
|
||||||
<org.apache.httpcomponents.version>4.5.2</org.apache.httpcomponents.version>
|
<org.apache.httpcomponents.version>4.5.2</org.apache.httpcomponents.version>
|
||||||
<velocity-version>1.7</velocity-version>
|
<velocity-version>1.7</velocity-version>
|
||||||
<velocity-tools-version>2.0</velocity-tools-version>
|
<velocity-tools-version>2.0</velocity-tools-version>
|
||||||
|
<maven-war-plugin.version>3.3.2</maven-war-plugin.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
</project>
|
</project>
|
|
@ -62,10 +62,10 @@
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<asciidoctor-maven-plugin.version>1.5.6</asciidoctor-maven-plugin.version>
|
<asciidoctor-maven-plugin.version>2.2.2</asciidoctor-maven-plugin.version>
|
||||||
<asciidoctorj.version>1.5.6</asciidoctorj.version>
|
<asciidoctorj.version>2.5.7</asciidoctorj.version>
|
||||||
<asciidoctorj-pdf.version>1.5.0-alpha.15</asciidoctorj-pdf.version>
|
<asciidoctorj-pdf.version>2.3.4</asciidoctorj-pdf.version>
|
||||||
<asciidoctorj-pdf.plugin.version>1.5.0-alpha.15</asciidoctorj-pdf.plugin.version>
|
<asciidoctorj-pdf.plugin.version>2.3.4</asciidoctorj-pdf.plugin.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
</project>
|
</project>
|
|
@ -77,6 +77,7 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>${lombok.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
@ -92,6 +93,7 @@
|
||||||
<properties>
|
<properties>
|
||||||
<spring.version>2.2.1.RELEASE</spring.version>
|
<spring.version>2.2.1.RELEASE</spring.version>
|
||||||
<awssdk.version>2.17.283</awssdk.version>
|
<awssdk.version>2.17.283</awssdk.version>
|
||||||
|
<lombok.version>1.18.20</lombok.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
</project>
|
</project>
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.baeldung.argsVsvarargs;
|
||||||
|
|
||||||
|
public class StringArrayAndVarargs {
|
||||||
|
public static void capitalizeNames(String[] args) {
|
||||||
|
for(int i = 0; i < args.length; i++){
|
||||||
|
args[i] = args[i].toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String[] firstLetterOfWords(String... args) {
|
||||||
|
String[] firstLetter = new String[args.length];
|
||||||
|
for(int i = 0; i < args.length; i++){
|
||||||
|
firstLetter[i] = String.valueOf(args[i].charAt(0));
|
||||||
|
}
|
||||||
|
return firstLetter;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.baeldung.argsVsvarargs;
|
||||||
|
|
||||||
|
import static com.baeldung.argsVsvarargs.StringArrayAndVarargs.capitalizeNames;
|
||||||
|
import static com.baeldung.argsVsvarargs.StringArrayAndVarargs.firstLetterOfWords;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class StringArrayAndVarargsUnitTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenCheckingArgumentClassName_thenNameShouldBeStringArray() {
|
||||||
|
String[] names = {"john", "ade", "kofi", "imo"};
|
||||||
|
assertNotNull(names);
|
||||||
|
assertEquals("java.lang.String[]", names.getClass().getTypeName());
|
||||||
|
capitalizeNames(names);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenCheckingReturnedObjectClass_thenClassShouldBeStringArray() {
|
||||||
|
assertEquals(String[].class, firstLetterOfWords("football", "basketball", "volleyball").getClass());
|
||||||
|
assertEquals(3, firstLetterOfWords("football", "basketball", "volleyball").length);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package com.baeldung.array.isobjectarray;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class CheckObjectIsArrayUnitTest {
|
||||||
|
private static final Object ARRAY_INT = new int[] { 1, 2, 3, 4, 5 };
|
||||||
|
private static final Object ARRAY_PERSON = new Person[] { new Person("Jackie Chan", "Hong Kong"), new Person("Tom Hanks", "United States") };
|
||||||
|
|
||||||
|
boolean isArray(Object obj) {
|
||||||
|
return obj instanceof Object[] || obj instanceof boolean[] || obj instanceof byte[] || obj instanceof short[] || obj instanceof char[] || obj instanceof int[] || obj instanceof long[] || obj instanceof float[] || obj instanceof double[];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenAnArrayObject_whenUsingInstanceof_getExpectedResult() {
|
||||||
|
assertTrue(ARRAY_PERSON instanceof Object[]);
|
||||||
|
assertFalse(ARRAY_INT instanceof Object[]);
|
||||||
|
assertTrue(ARRAY_INT instanceof int[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenAnArrayObject_whenUsingOurIsArray_getExpectedResult() {
|
||||||
|
assertTrue(isArray(ARRAY_PERSON));
|
||||||
|
assertTrue(isArray(ARRAY_INT));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenAnArrayObject_whenUsingClassIsArray_getExpectedResult() {
|
||||||
|
assertTrue(ARRAY_INT.getClass()
|
||||||
|
.isArray());
|
||||||
|
assertTrue(ARRAY_PERSON.getClass()
|
||||||
|
.isArray());
|
||||||
|
|
||||||
|
assertEquals(Person.class, ARRAY_PERSON.getClass()
|
||||||
|
.getComponentType());
|
||||||
|
assertEquals(int.class, ARRAY_INT.getClass()
|
||||||
|
.getComponentType());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenAnArrayObject_whenUsingArrayGet_getExpectedElement() {
|
||||||
|
if (ARRAY_PERSON.getClass()
|
||||||
|
.isArray() && ARRAY_PERSON.getClass()
|
||||||
|
.getComponentType() == Person.class) {
|
||||||
|
Person person = (Person) Array.get(ARRAY_PERSON, 1);
|
||||||
|
assertEquals("Tom Hanks", person.getName());
|
||||||
|
}
|
||||||
|
if (ARRAY_INT.getClass()
|
||||||
|
.isArray() && ARRAY_INT.getClass()
|
||||||
|
.getComponentType() == int.class) {
|
||||||
|
assertEquals(2, ((int) Array.get(ARRAY_INT, 1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Person {
|
||||||
|
private String name;
|
||||||
|
private String Location;
|
||||||
|
|
||||||
|
public Person(String name, String location) {
|
||||||
|
this.name = name;
|
||||||
|
this.Location = location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLocation() {
|
||||||
|
return Location;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
package com.baeldung.concurrent.atomic;
|
package com.baeldung.concurrent.atomic;
|
||||||
|
|
||||||
public class SafeCounterWithLock {
|
public class SafeCounterWithLock {
|
||||||
private volatile int counter;
|
private int counter;
|
||||||
|
|
||||||
int getValue() {
|
int getValue() {
|
||||||
return counter;
|
return counter;
|
||||||
|
|
|
@ -10,12 +10,6 @@ public class SafeCounterWithoutLock {
|
||||||
}
|
}
|
||||||
|
|
||||||
void increment() {
|
void increment() {
|
||||||
while(true) {
|
counter.incrementAndGet();
|
||||||
int existingValue = getValue();
|
|
||||||
int newValue = existingValue + 1;
|
|
||||||
if(counter.compareAndSet(existingValue, newValue)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,8 @@ public class ThreadSafeCounterIntegrationTest {
|
||||||
SafeCounterWithLock safeCounter = new SafeCounterWithLock();
|
SafeCounterWithLock safeCounter = new SafeCounterWithLock();
|
||||||
|
|
||||||
IntStream.range(0, 1000)
|
IntStream.range(0, 1000)
|
||||||
.forEach(count -> service.submit(safeCounter::increment));
|
.forEach(count -> service.execute(safeCounter::increment));
|
||||||
service.awaitTermination(100, TimeUnit.MILLISECONDS);
|
shutdownAndAwaitTermination(service);
|
||||||
|
|
||||||
assertEquals(1000, safeCounter.getValue());
|
assertEquals(1000, safeCounter.getValue());
|
||||||
}
|
}
|
||||||
|
@ -29,10 +29,30 @@ public class ThreadSafeCounterIntegrationTest {
|
||||||
SafeCounterWithoutLock safeCounter = new SafeCounterWithoutLock();
|
SafeCounterWithoutLock safeCounter = new SafeCounterWithoutLock();
|
||||||
|
|
||||||
IntStream.range(0, 1000)
|
IntStream.range(0, 1000)
|
||||||
.forEach(count -> service.submit(safeCounter::increment));
|
.forEach(count -> service.execute(safeCounter::increment));
|
||||||
service.awaitTermination(100, TimeUnit.MILLISECONDS);
|
shutdownAndAwaitTermination(service);
|
||||||
|
|
||||||
assertEquals(1000, safeCounter.getValue());
|
assertEquals(1000, safeCounter.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void shutdownAndAwaitTermination(ExecutorService pool) {
|
||||||
|
// Disable new tasks from being submitted
|
||||||
|
pool.shutdown();
|
||||||
|
try {
|
||||||
|
// Wait a while for existing tasks to terminate
|
||||||
|
if (!pool.awaitTermination(100, TimeUnit.MILLISECONDS)) {
|
||||||
|
// Cancel currently executing tasks forcefully
|
||||||
|
pool.shutdownNow();
|
||||||
|
// Wait a while for tasks to respond to being cancelled
|
||||||
|
if (!pool.awaitTermination(100, TimeUnit.MILLISECONDS))
|
||||||
|
System.err.println("Pool did not terminate");
|
||||||
|
}
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
// (Re-)Cancel if current thread also interrupted
|
||||||
|
pool.shutdownNow();
|
||||||
|
// Preserve interrupt status
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?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>core-java-hex</artifactId>
|
||||||
|
<version>0.1.0-SNAPSHOT</version>
|
||||||
|
<name>core-java-hex</name>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.baeldung.core-java-modules</groupId>
|
||||||
|
<artifactId>core-java-modules</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.baeldung.hextorgb;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class HexToRgbUnitTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenHexCode_whenConvertedToRgb_thenCorrectRgbValuesAreReturned() {
|
||||||
|
String hexCode = "FF9933";
|
||||||
|
int red = 255;
|
||||||
|
int green = 153;
|
||||||
|
int blue = 51;
|
||||||
|
|
||||||
|
int resultRed = Integer.valueOf(hexCode.substring(0, 2), 16);
|
||||||
|
int resultGreen = Integer.valueOf(hexCode.substring(2, 4), 16);
|
||||||
|
int resultBlue = Integer.valueOf(hexCode.substring(4, 6), 16);
|
||||||
|
|
||||||
|
assertEquals(red, resultRed);
|
||||||
|
assertEquals(green, resultGreen);
|
||||||
|
assertEquals(blue, resultBlue);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.baeldung.firstocurrenceofaninteger;
|
||||||
|
|
||||||
|
public class FirstOccurrenceOfAnInteger {
|
||||||
|
|
||||||
|
static Integer findFirstInteger(String s) {
|
||||||
|
int i = 0;
|
||||||
|
while (i < s.length() && !Character.isDigit(s.charAt(i))) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
int j = i;
|
||||||
|
while (j < s.length() && Character.isDigit(s.charAt(j))) {
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
return Integer.parseInt(s.substring(i, j));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
package com.baeldung.firstocurrenceofaninteger;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Scanner;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
class FirstOccurrenceOfAnIntegerUnitTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenUsingPatternMatcher_findFirstInteger() {
|
||||||
|
String s = "ba31dung123";
|
||||||
|
Matcher matcher = Pattern.compile("\\d+").matcher(s);
|
||||||
|
matcher.find();
|
||||||
|
int i = Integer.parseInt(matcher.group());
|
||||||
|
Assertions.assertEquals(31, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenUsingScanner_findFirstInteger() {
|
||||||
|
int i = new Scanner("ba31dung123").useDelimiter("\\D+").nextInt();
|
||||||
|
Assertions.assertEquals(31, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenUsingSplit_findFirstInteger() {
|
||||||
|
String str = "ba31dung123";
|
||||||
|
List<String> tokens = Arrays.stream(str.split("\\D+"))
|
||||||
|
.filter(s -> s.length() > 0).collect(Collectors.toList());
|
||||||
|
Assertions.assertEquals(31, Integer.parseInt(tokens.get(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenUsingCustomMethod_findFirstInteger() {
|
||||||
|
String str = "ba31dung123";
|
||||||
|
Integer i = FirstOccurrenceOfAnInteger.findFirstInteger(str);
|
||||||
|
Assertions.assertEquals(31, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -62,6 +62,7 @@
|
||||||
<module>core-java-exceptions-4</module>
|
<module>core-java-exceptions-4</module>
|
||||||
<module>core-java-function</module>
|
<module>core-java-function</module>
|
||||||
<module>core-java-functional</module>
|
<module>core-java-functional</module>
|
||||||
|
<module>core-java-hex</module>
|
||||||
<module>core-java-io</module>
|
<module>core-java-io</module>
|
||||||
<module>core-java-io-2</module>
|
<module>core-java-io-2</module>
|
||||||
<module>core-java-io-3</module>
|
<module>core-java-io-3</module>
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<guice.version>4.1.0</guice.version>
|
<guice.version>5.0.1</guice.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
</project>
|
</project>
|
|
@ -136,7 +136,7 @@
|
||||||
<resultsDirectory>${project.basedir}/src/main/resources/dashboard</resultsDirectory>
|
<resultsDirectory>${project.basedir}/src/main/resources/dashboard</resultsDirectory>
|
||||||
<generateReports>true</generateReports>
|
<generateReports>true</generateReports>
|
||||||
<ignoreResultFailures>true</ignoreResultFailures>
|
<ignoreResultFailures>true</ignoreResultFailures>
|
||||||
<testResultsTimestamp>false</testResultsTimestamp>
|
<testResultsTimestamp>true</testResultsTimestamp>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
<?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">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>rethinkdb</artifactId>
|
||||||
|
<name>rethinkdb</name>
|
||||||
|
<description>Code snippets for RethinkDB articles</description>
|
||||||
|
|
||||||
|
<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-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.rethinkdb</groupId>
|
||||||
|
<artifactId>rethinkdb-driver</artifactId>
|
||||||
|
<version>2.4.4</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- utils -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>17</java.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,62 @@
|
||||||
|
package com.baeldung.rethinkdb;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static com.rethinkdb.RethinkDB.r;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some tests demonstrating inserting data.
|
||||||
|
*/
|
||||||
|
public class InsertIntegrationTest extends TestBase {
|
||||||
|
/**
|
||||||
|
* Create a table for the tests.
|
||||||
|
*/
|
||||||
|
@BeforeEach
|
||||||
|
public void createTable() {
|
||||||
|
r.db(DB_NAME).tableCreate(tableName).run(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert a single simple record into the database.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void insertSimpleRecord() {
|
||||||
|
r.db(DB_NAME).table(tableName)
|
||||||
|
.insert(
|
||||||
|
r.hashMap()
|
||||||
|
.with("name", "Baeldung")
|
||||||
|
)
|
||||||
|
.run(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void insertMap() {
|
||||||
|
r.db(DB_NAME).table(tableName)
|
||||||
|
.insert(
|
||||||
|
Map.of("name", "Baeldung")
|
||||||
|
)
|
||||||
|
.run(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void insertComplex() {
|
||||||
|
r.db(DB_NAME).table(tableName)
|
||||||
|
.insert(
|
||||||
|
r.hashMap()
|
||||||
|
.with("name", "Baeldung")
|
||||||
|
.with("articles", r.array(
|
||||||
|
r.hashMap()
|
||||||
|
.with("name", "String Interpolation in Java")
|
||||||
|
.with("url", "https://www.baeldung.com/java-string-interpolation"),
|
||||||
|
r.hashMap()
|
||||||
|
.with("name", "Access HTTPS REST Service Using Spring RestTemplate")
|
||||||
|
.with("url", "https://www.baeldung.com/spring-resttemplate-secure-https-service")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.run(conn);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
package com.baeldung.rethinkdb;
|
||||||
|
|
||||||
|
import com.rethinkdb.net.Result;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static com.rethinkdb.RethinkDB.r;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some tests demonstrating querying data.
|
||||||
|
*/
|
||||||
|
public class QueryIntegrationTest extends TestBase {
|
||||||
|
/**
|
||||||
|
* Create a table for the tests.
|
||||||
|
*/
|
||||||
|
@BeforeEach
|
||||||
|
public void createTable() {
|
||||||
|
r.db(DB_NAME).tableCreate(tableName).run(conn);
|
||||||
|
|
||||||
|
r.db(DB_NAME).table(tableName)
|
||||||
|
.insert(
|
||||||
|
r.hashMap()
|
||||||
|
.with("id", "article1")
|
||||||
|
.with("name", "String Interpolation in Java")
|
||||||
|
.with("url", "https://www.baeldung.com/java-string-interpolation")
|
||||||
|
).run(conn);
|
||||||
|
r.db(DB_NAME).table(tableName)
|
||||||
|
.insert(
|
||||||
|
r.hashMap()
|
||||||
|
.with("id", "article2")
|
||||||
|
.with("name", "Access HTTPS REST Service Using Spring RestTemplate")
|
||||||
|
.with("url", "https://www.baeldung.com/spring-resttemplate-secure-https-service")
|
||||||
|
).run(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void listAll() {
|
||||||
|
Result<Map> results = r.db(DB_NAME).table(tableName).run(conn, Map.class);
|
||||||
|
|
||||||
|
// We can't ensure the order the results come back in.
|
||||||
|
Set<String> expected = new HashSet<>(Set.of(
|
||||||
|
"String Interpolation in Java",
|
||||||
|
"Access HTTPS REST Service Using Spring RestTemplate"
|
||||||
|
));
|
||||||
|
|
||||||
|
for (Map result : results) {
|
||||||
|
assertTrue(expected.remove(result.get("name")));
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(expected.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void listSome() {
|
||||||
|
Result<Map> results = r.db(DB_NAME)
|
||||||
|
.table(tableName)
|
||||||
|
.filter(r -> r.g("name").eq("String Interpolation in Java"))
|
||||||
|
.run(conn, Map.class);
|
||||||
|
|
||||||
|
// We can't ensure the order the results come back in.
|
||||||
|
Set<String> expected = Set.of("https://www.baeldung.com/java-string-interpolation");
|
||||||
|
|
||||||
|
assertEquals(expected, results.stream()
|
||||||
|
.map(r -> r.get("url"))
|
||||||
|
.collect(Collectors.toSet()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getByKey() {
|
||||||
|
Result<Map> results = r.db(DB_NAME).table(tableName).get("article1").run(conn, Map.class);
|
||||||
|
|
||||||
|
assertEquals("String Interpolation in Java", results.first().get("name"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
package com.baeldung.rethinkdb;
|
||||||
|
|
||||||
|
import com.rethinkdb.net.Result;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static com.rethinkdb.RethinkDB.r;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some tests demonstrating streaming live changes to data.
|
||||||
|
*/
|
||||||
|
public class StreamingIntegrationTest extends TestBase {
|
||||||
|
@Test
|
||||||
|
public void getLiveInserts() throws InterruptedException {
|
||||||
|
ExecutorService executorService = Executors.newCachedThreadPool();
|
||||||
|
|
||||||
|
r.db(DB_NAME).tableCreate(tableName).run(conn);
|
||||||
|
|
||||||
|
r.db(DB_NAME).table(tableName).insert(r.hashMap().with("index", 0)).run(conn);
|
||||||
|
|
||||||
|
executorService.submit(() -> {
|
||||||
|
Result<Map> cursor = r.db(DB_NAME).table(tableName).changes().run(conn, Map.class);
|
||||||
|
|
||||||
|
cursor.stream().forEach(record -> System.out.println("Record: " + record));
|
||||||
|
});
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
r.db(DB_NAME).table(tableName).insert(r.hashMap().with("index", i)).run(conn);
|
||||||
|
TimeUnit.MILLISECONDS.sleep(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
executorService.shutdownNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getSomeLiveInserts() throws InterruptedException {
|
||||||
|
ExecutorService executorService = Executors.newCachedThreadPool();
|
||||||
|
|
||||||
|
r.db(DB_NAME).tableCreate(tableName).run(conn);
|
||||||
|
|
||||||
|
r.db(DB_NAME).table(tableName).insert(r.hashMap().with("index", 0)).run(conn);
|
||||||
|
|
||||||
|
executorService.submit(() -> {
|
||||||
|
Result<Map> cursor = r.db(DB_NAME).table(tableName)
|
||||||
|
.filter(r -> r.g("index").eq(5))
|
||||||
|
.changes()
|
||||||
|
.run(conn, Map.class);
|
||||||
|
|
||||||
|
cursor.stream().forEach(record -> System.out.println("Record: " + record));
|
||||||
|
});
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
r.db(DB_NAME).table(tableName).insert(r.hashMap().with("index", i)).run(conn);
|
||||||
|
TimeUnit.MILLISECONDS.sleep(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
executorService.shutdownNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getLiveUpdates() throws InterruptedException {
|
||||||
|
ExecutorService executorService = Executors.newCachedThreadPool();
|
||||||
|
|
||||||
|
r.db(DB_NAME).tableCreate(tableName).run(conn);
|
||||||
|
|
||||||
|
r.db(DB_NAME).table(tableName).insert(r.hashMap().with("index", 0)).run(conn);
|
||||||
|
|
||||||
|
executorService.submit(() -> {
|
||||||
|
Result<Map> cursor = r.db(DB_NAME).table(tableName).changes().run(conn, Map.class);
|
||||||
|
|
||||||
|
cursor.stream().forEach(record -> System.out.println("Record: " + record));
|
||||||
|
});
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
r.db(DB_NAME).table(tableName).update(r.hashMap().with("index", i)).run(conn);
|
||||||
|
TimeUnit.MILLISECONDS.sleep(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
executorService.shutdownNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getLiveDeletes() throws InterruptedException {
|
||||||
|
ExecutorService executorService = Executors.newCachedThreadPool();
|
||||||
|
|
||||||
|
r.db(DB_NAME).tableCreate(tableName).run(conn);
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
r.db(DB_NAME).table(tableName).insert(r.hashMap().with("index", i)).run(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
executorService.submit(() -> {
|
||||||
|
Result<Map> cursor = r.db(DB_NAME).table(tableName).changes().run(conn, Map.class);
|
||||||
|
|
||||||
|
cursor.stream().forEach(record -> System.out.println("Record: " + record));
|
||||||
|
});
|
||||||
|
|
||||||
|
r.db(DB_NAME).table(tableName)
|
||||||
|
.filter(r -> r.g("index").eq(1))
|
||||||
|
.delete()
|
||||||
|
.run(conn);
|
||||||
|
r.db(DB_NAME).table(tableName)
|
||||||
|
.filter(r -> r.g("index").eq(3))
|
||||||
|
.delete()
|
||||||
|
.run(conn);
|
||||||
|
r.db(DB_NAME).table(tableName)
|
||||||
|
.filter(r -> r.g("index").eq(5))
|
||||||
|
.delete()
|
||||||
|
.run(conn);
|
||||||
|
|
||||||
|
executorService.shutdownNow();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package com.baeldung.rethinkdb;
|
||||||
|
|
||||||
|
import com.rethinkdb.gen.exc.ReqlOpFailedError;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.rethinkdb.RethinkDB.r;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some tests demonstrating working with tables.
|
||||||
|
*/
|
||||||
|
public class TablesIntegrationTest extends TestBase {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createTable() {
|
||||||
|
r.db(DB_NAME).tableCreate(tableName).run(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createTableTwice() {
|
||||||
|
r.db(DB_NAME).tableCreate(tableName).run(conn);
|
||||||
|
|
||||||
|
Assertions.assertThrows(ReqlOpFailedError.class, () -> {
|
||||||
|
r.db(DB_NAME).tableCreate(tableName).run(conn);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void listTables() {
|
||||||
|
r.db(DB_NAME).tableCreate(tableName).run(conn);
|
||||||
|
|
||||||
|
List<String> tables = r.db(DB_NAME).tableList().run(conn, List.class).first();
|
||||||
|
|
||||||
|
assertTrue(tables.contains(tableName));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package com.baeldung.rethinkdb;
|
||||||
|
|
||||||
|
import com.rethinkdb.net.Connection;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static com.rethinkdb.RethinkDB.r;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for RethinkDB tests.
|
||||||
|
*/
|
||||||
|
public class TestBase {
|
||||||
|
/** The database name to work with */
|
||||||
|
protected static final String DB_NAME = "test";
|
||||||
|
|
||||||
|
/** A randomly generated table name so they never collide */
|
||||||
|
protected final String tableName = UUID.randomUUID().toString().replaceAll("-","");
|
||||||
|
|
||||||
|
/** A database connection */
|
||||||
|
protected Connection conn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to the database for each test
|
||||||
|
*/
|
||||||
|
@BeforeEach
|
||||||
|
public void connect() {
|
||||||
|
conn = r.connection()
|
||||||
|
.hostname("localhost")
|
||||||
|
.port(28015)
|
||||||
|
.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect from the database after each test
|
||||||
|
*/
|
||||||
|
@AfterEach
|
||||||
|
public void disconnect() {
|
||||||
|
if (this.conn != null) {
|
||||||
|
conn.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package com.baeldung.rethinkdb;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static com.rethinkdb.RethinkDB.r;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some tests demonstrating updating data.
|
||||||
|
*/
|
||||||
|
public class UpdateIntegrationTest extends TestBase {
|
||||||
|
/**
|
||||||
|
* Create a table for the tests.
|
||||||
|
*/
|
||||||
|
@BeforeEach
|
||||||
|
public void createTable() {
|
||||||
|
r.db(DB_NAME).tableCreate(tableName).run(conn);
|
||||||
|
|
||||||
|
r.db(DB_NAME).table(tableName)
|
||||||
|
.insert(
|
||||||
|
r.hashMap()
|
||||||
|
.with("id", "article1")
|
||||||
|
.with("name", "String Interpolation in Java")
|
||||||
|
.with("url", "https://www.baeldung.com/java-string-interpolation")
|
||||||
|
).run(conn);
|
||||||
|
r.db(DB_NAME).table(tableName)
|
||||||
|
.insert(
|
||||||
|
r.hashMap()
|
||||||
|
.with("id", "article2")
|
||||||
|
.with("name", "Access HTTPS REST Service Using Spring RestTemplate")
|
||||||
|
.with("url", "https://www.baeldung.com/spring-resttemplate-secure-https-service")
|
||||||
|
).run(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateAll() {
|
||||||
|
r.db(DB_NAME).table(tableName).update(r.hashMap().with("site", "Baeldung")).run(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateSome() {
|
||||||
|
r.db(DB_NAME).table(tableName)
|
||||||
|
.filter(r -> r.g("name").eq("String Interpolation in Java"))
|
||||||
|
.update(r.hashMap().with("category", "java"))
|
||||||
|
.run(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void delete() {
|
||||||
|
r.db(DB_NAME).table(tableName)
|
||||||
|
.filter(r -> r.g("name").eq("String Interpolation in Java"))
|
||||||
|
.delete()
|
||||||
|
.run(conn);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,8 +4,6 @@ import org.bson.BsonBinary;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
import com.mongodb.client.vault.ClientEncryption;
|
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class EncryptionConfig {
|
public class EncryptionConfig {
|
||||||
|
|
||||||
|
@ -21,18 +19,8 @@ public class EncryptionConfig {
|
||||||
@Value("${com.baeldung.csfle.auto-decryption:false}")
|
@Value("${com.baeldung.csfle.auto-decryption:false}")
|
||||||
private Boolean autoDecryption;
|
private Boolean autoDecryption;
|
||||||
|
|
||||||
private ClientEncryption encryption;
|
|
||||||
|
|
||||||
private BsonBinary dataKeyId;
|
private BsonBinary dataKeyId;
|
||||||
|
|
||||||
public void setEncryption(ClientEncryption encryption) {
|
|
||||||
this.encryption = encryption;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientEncryption getEncryption() {
|
|
||||||
return encryption;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDataKeyId(BsonBinary dataKeyId) {
|
public void setDataKeyId(BsonBinary dataKeyId) {
|
||||||
this.dataKeyId = dataKeyId;
|
this.dataKeyId = dataKeyId;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,12 +11,12 @@ import org.bson.Document;
|
||||||
import org.bson.conversions.Bson;
|
import org.bson.conversions.Bson;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration;
|
import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration;
|
||||||
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
|
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
|
||||||
|
|
||||||
import com.baeldung.boot.csfle.config.converter.IntegerConverter;
|
import com.baeldung.boot.csfle.config.converter.BinaryConverter;
|
||||||
import com.baeldung.boot.csfle.config.converter.StringConverter;
|
|
||||||
import com.mongodb.AutoEncryptionSettings;
|
import com.mongodb.AutoEncryptionSettings;
|
||||||
import com.mongodb.ClientEncryptionSettings;
|
import com.mongodb.ClientEncryptionSettings;
|
||||||
import com.mongodb.ConnectionString;
|
import com.mongodb.ConnectionString;
|
||||||
|
@ -52,16 +52,17 @@ public class MongoClientConfig extends AbstractMongoClientConfiguration {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MongoCustomConversions customConversions() {
|
public MongoCustomConversions customConversions() {
|
||||||
return new MongoCustomConversions(Arrays.asList(new StringConverter(encryptionConfig), new IntegerConverter(encryptionConfig)));
|
return new MongoCustomConversions(Arrays.asList(new BinaryConverter()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
@Override
|
@Override
|
||||||
public MongoClient mongoClient() {
|
public MongoClient mongoClient() {
|
||||||
MongoClient client;
|
MongoClient client;
|
||||||
try {
|
try {
|
||||||
client = MongoClients.create(clientSettings());
|
client = MongoClients.create(clientSettings());
|
||||||
|
|
||||||
ClientEncryption encryption = createClientEncryption();
|
ClientEncryption encryption = clientEncryption();
|
||||||
encryptionConfig.setDataKeyId(createOrRetrieveDataKey(client, encryption));
|
encryptionConfig.setDataKeyId(createOrRetrieveDataKey(client, encryption));
|
||||||
|
|
||||||
return client;
|
return client;
|
||||||
|
@ -70,6 +71,19 @@ public class MongoClientConfig extends AbstractMongoClientConfiguration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ClientEncryption clientEncryption() throws FileNotFoundException, IOException {
|
||||||
|
Map<String, Map<String, Object>> kmsProviders = LocalKmsUtils.providersMap(encryptionConfig.getMasterKeyPath());
|
||||||
|
|
||||||
|
ClientEncryptionSettings encryptionSettings = ClientEncryptionSettings.builder()
|
||||||
|
.keyVaultMongoClientSettings(clientSettings())
|
||||||
|
.keyVaultNamespace(encryptionConfig.getKeyVaultNamespace())
|
||||||
|
.kmsProviders(kmsProviders)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return ClientEncryptions.create(encryptionSettings);
|
||||||
|
}
|
||||||
|
|
||||||
private BsonBinary createOrRetrieveDataKey(MongoClient client, ClientEncryption encryption) {
|
private BsonBinary createOrRetrieveDataKey(MongoClient client, ClientEncryption encryption) {
|
||||||
MongoNamespace namespace = new MongoNamespace(encryptionConfig.getKeyVaultNamespace());
|
MongoNamespace namespace = new MongoNamespace(encryptionConfig.getKeyVaultNamespace());
|
||||||
MongoCollection<Document> keyVault = client.getDatabase(namespace.getDatabaseName())
|
MongoCollection<Document> keyVault = client.getDatabase(namespace.getDatabaseName())
|
||||||
|
@ -92,19 +106,6 @@ public class MongoClientConfig extends AbstractMongoClientConfiguration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClientEncryption createClientEncryption() throws FileNotFoundException, IOException {
|
|
||||||
Map<String, Map<String, Object>> kmsProviders = LocalKmsUtils.providersMap(encryptionConfig.getMasterKeyPath());
|
|
||||||
|
|
||||||
ClientEncryptionSettings encryptionSettings = ClientEncryptionSettings.builder()
|
|
||||||
.keyVaultMongoClientSettings(clientSettings())
|
|
||||||
.keyVaultNamespace(encryptionConfig.getKeyVaultNamespace())
|
|
||||||
.kmsProviders(kmsProviders)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
encryptionConfig.setEncryption(ClientEncryptions.create(encryptionSettings));
|
|
||||||
return encryptionConfig.getEncryption();
|
|
||||||
}
|
|
||||||
|
|
||||||
private MongoClientSettings clientSettings() throws FileNotFoundException, IOException {
|
private MongoClientSettings clientSettings() throws FileNotFoundException, IOException {
|
||||||
Builder settings = MongoClientSettings.builder()
|
Builder settings = MongoClientSettings.builder()
|
||||||
.applyConnectionString(new ConnectionString(uri));
|
.applyConnectionString(new ConnectionString(uri));
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.baeldung.boot.csfle.config.converter;
|
||||||
|
|
||||||
|
import org.bson.BsonBinary;
|
||||||
|
import org.bson.types.Binary;
|
||||||
|
import org.springframework.core.convert.converter.Converter;
|
||||||
|
|
||||||
|
public class BinaryConverter implements Converter<Binary, BsonBinary> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BsonBinary convert(Binary source) {
|
||||||
|
return new BsonBinary(source.getType(), source.getData());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,27 +0,0 @@
|
||||||
package com.baeldung.boot.csfle.config.converter;
|
|
||||||
|
|
||||||
import org.bson.BsonBinary;
|
|
||||||
import org.bson.BsonValue;
|
|
||||||
import org.bson.types.Binary;
|
|
||||||
import org.springframework.core.convert.converter.Converter;
|
|
||||||
|
|
||||||
import com.baeldung.boot.csfle.config.EncryptionConfig;
|
|
||||||
|
|
||||||
public class IntegerConverter implements Converter<Binary, Integer> {
|
|
||||||
|
|
||||||
private EncryptionConfig encryptionConfig;
|
|
||||||
|
|
||||||
public IntegerConverter(EncryptionConfig config) {
|
|
||||||
this.encryptionConfig = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Integer convert(Binary source) {
|
|
||||||
BsonBinary bin = new BsonBinary(source.getType(), source.getData());
|
|
||||||
BsonValue value = encryptionConfig.getEncryption()
|
|
||||||
.decrypt(bin);
|
|
||||||
|
|
||||||
return value.asInt32()
|
|
||||||
.getValue();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
package com.baeldung.boot.csfle.config.converter;
|
|
||||||
|
|
||||||
import org.bson.BsonBinary;
|
|
||||||
import org.bson.BsonValue;
|
|
||||||
import org.bson.types.Binary;
|
|
||||||
import org.springframework.core.convert.converter.Converter;
|
|
||||||
|
|
||||||
import com.baeldung.boot.csfle.config.EncryptionConfig;
|
|
||||||
|
|
||||||
public class StringConverter implements Converter<Binary, String> {
|
|
||||||
|
|
||||||
private EncryptionConfig encryptionConfig;
|
|
||||||
|
|
||||||
public StringConverter(EncryptionConfig config) {
|
|
||||||
this.encryptionConfig = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String convert(Binary source) {
|
|
||||||
BsonBinary bin = new BsonBinary(source.getType(), source.getData());
|
|
||||||
BsonValue value = encryptionConfig.getEncryption()
|
|
||||||
.decrypt(bin);
|
|
||||||
|
|
||||||
return value.asString()
|
|
||||||
.getValue();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.baeldung.boot.csfle.service;
|
package com.baeldung.boot.csfle.service;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.bson.BsonBinary;
|
import org.bson.BsonBinary;
|
||||||
import org.bson.BsonInt32;
|
import org.bson.BsonInt32;
|
||||||
|
@ -16,6 +17,7 @@ import com.baeldung.boot.csfle.config.EncryptionConfig;
|
||||||
import com.baeldung.boot.csfle.data.Citizen;
|
import com.baeldung.boot.csfle.data.Citizen;
|
||||||
import com.baeldung.boot.csfle.data.EncryptedCitizen;
|
import com.baeldung.boot.csfle.data.EncryptedCitizen;
|
||||||
import com.mongodb.client.model.vault.EncryptOptions;
|
import com.mongodb.client.model.vault.EncryptOptions;
|
||||||
|
import com.mongodb.client.vault.ClientEncryption;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class CitizenService {
|
public class CitizenService {
|
||||||
|
@ -29,6 +31,9 @@ public class CitizenService {
|
||||||
@Autowired
|
@Autowired
|
||||||
private EncryptionConfig encryptionConfig;
|
private EncryptionConfig encryptionConfig;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ClientEncryption clientEncryption;
|
||||||
|
|
||||||
public EncryptedCitizen save(Citizen citizen) {
|
public EncryptedCitizen save(Citizen citizen) {
|
||||||
EncryptedCitizen encryptedCitizen = new EncryptedCitizen(citizen);
|
EncryptedCitizen encryptedCitizen = new EncryptedCitizen(citizen);
|
||||||
encryptedCitizen.setEmail(encrypt(citizen.getEmail(), DETERMINISTIC_ALGORITHM));
|
encryptedCitizen.setEmail(encrypt(citizen.getEmail(), DETERMINISTIC_ALGORITHM));
|
||||||
|
@ -38,26 +43,68 @@ public class CitizenService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Citizen> findAll() {
|
public List<Citizen> findAll() {
|
||||||
return mongo.findAll(Citizen.class);
|
if (!encryptionConfig.getAutoDecryption()) {
|
||||||
|
List<EncryptedCitizen> allEncrypted = mongo.findAll(EncryptedCitizen.class);
|
||||||
|
|
||||||
|
return allEncrypted.stream()
|
||||||
|
.map(this::decrypt)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
} else {
|
||||||
|
return mongo.findAll(Citizen.class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Citizen findByEmail(String email) {
|
public Citizen findByEmail(String email) {
|
||||||
Query byEmail = new Query(Criteria.where("email")
|
Query byEmail = new Query(Criteria.where("email")
|
||||||
.is(encrypt(email, DETERMINISTIC_ALGORITHM)));
|
.is(encrypt(email, DETERMINISTIC_ALGORITHM)));
|
||||||
return mongo.findOne(byEmail, Citizen.class);
|
if (!encryptionConfig.getAutoDecryption()) {
|
||||||
|
EncryptedCitizen encryptedCitizen = mongo.findOne(byEmail, EncryptedCitizen.class);
|
||||||
|
return decrypt(encryptedCitizen);
|
||||||
|
} else {
|
||||||
|
return mongo.findOne(byEmail, Citizen.class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public BsonBinary encrypt(Object value, String algorithm) {
|
public BsonBinary encrypt(Object value, String algorithm) {
|
||||||
if (value == null)
|
if (value == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
BsonValue bsonValue = value instanceof Integer
|
BsonValue bsonValue;
|
||||||
? new BsonInt32((Integer) value)
|
if (value instanceof Integer) {
|
||||||
: new BsonString(value.toString());
|
bsonValue = new BsonInt32((Integer) value);
|
||||||
|
} else if (value instanceof String) {
|
||||||
|
bsonValue = new BsonString((String) value);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("unsupported type: " + value.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
EncryptOptions options = new EncryptOptions(algorithm);
|
EncryptOptions options = new EncryptOptions(algorithm);
|
||||||
options.keyId(encryptionConfig.getDataKeyId());
|
options.keyId(encryptionConfig.getDataKeyId());
|
||||||
return encryptionConfig.getEncryption()
|
return clientEncryption.encrypt(bsonValue, options);
|
||||||
.encrypt(bsonValue, options);
|
}
|
||||||
|
|
||||||
|
public BsonValue decryptProperty(BsonBinary value) {
|
||||||
|
if (value == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return clientEncryption.decrypt(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Citizen decrypt(EncryptedCitizen encrypted) {
|
||||||
|
Citizen citizen = new Citizen(encrypted);
|
||||||
|
|
||||||
|
BsonValue decryptedBirthYear = decryptProperty(encrypted.getBirthYear());
|
||||||
|
if (decryptedBirthYear != null) {
|
||||||
|
citizen.setBirthYear(decryptedBirthYear.asInt32()
|
||||||
|
.intValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
BsonValue decryptedEmail = decryptProperty(encrypted.getEmail());
|
||||||
|
if (decryptedEmail != null) {
|
||||||
|
citizen.setEmail(decryptedEmail.asString()
|
||||||
|
.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
return citizen;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<artifactId>spring-data-jpa-query-3</artifactId>
|
<artifactId>spring-data-jpa-query-3</artifactId>
|
||||||
<name>spring-data-jpa-query-3</name>
|
<name>spring-data-jpa-query-3</name>
|
||||||
|
<properties>
|
||||||
|
<javafaker.version>0.15</javafaker.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.baeldung</groupId>
|
<groupId>com.baeldung</groupId>
|
||||||
|
@ -22,6 +25,11 @@
|
||||||
<groupId>com.h2database</groupId>
|
<groupId>com.h2database</groupId>
|
||||||
<artifactId>h2</artifactId>
|
<artifactId>h2</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.javafaker</groupId>
|
||||||
|
<artifactId>javafaker</artifactId>
|
||||||
|
<version>${javafaker.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-test</artifactId>
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.baeldung.spring.data.jpa.collectionsvsstream;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class ListVsStreamQueryApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(ListVsStreamQueryApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
package com.baeldung.spring.data.jpa.collectionsvsstream;
|
||||||
|
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "_user")
|
||||||
|
public class User {
|
||||||
|
private String firstName;
|
||||||
|
private String lastName;
|
||||||
|
private int age;
|
||||||
|
@Id
|
||||||
|
private int id;
|
||||||
|
|
||||||
|
public User() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public User(String firstName, String lastName, int age) {
|
||||||
|
this.firstName = firstName;
|
||||||
|
this.lastName = lastName;
|
||||||
|
this.age = age;
|
||||||
|
}
|
||||||
|
|
||||||
|
public User(String firstName, String lastName, int age, int id) {
|
||||||
|
this(firstName, lastName, age);
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFirstName() {
|
||||||
|
return firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFirstName(String firstName) {
|
||||||
|
this.firstName = firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastName() {
|
||||||
|
return lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastName(String lastName) {
|
||||||
|
this.lastName = lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAge() {
|
||||||
|
return age;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAge(int age) {
|
||||||
|
this.age = age;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.baeldung.spring.data.jpa.collectionsvsstream;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface UserRepository extends JpaRepository<User, String> {
|
||||||
|
Stream<User> findAllByAgeGreaterThan(int age);
|
||||||
|
|
||||||
|
List<User> findByAgeGreaterThan(int age);
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package com.baeldung.spring.data.jpa.collectionsvsstream;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
||||||
|
|
||||||
|
import com.github.javafaker.Faker;
|
||||||
|
|
||||||
|
@DataJpaTest
|
||||||
|
class UserRepositoryUnitTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserRepository userRepository;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setup() {
|
||||||
|
Faker faker = new Faker();
|
||||||
|
List<User> people = IntStream.range(1, 100)
|
||||||
|
.parallel()
|
||||||
|
.mapToObj(i -> new User(faker.name()
|
||||||
|
.firstName(), faker.name()
|
||||||
|
.lastName(), faker.number()
|
||||||
|
.numberBetween(1, 100), i))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
userRepository.saveAll(people);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void tearDown() {
|
||||||
|
userRepository.deleteAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenAgeIs20_thenItShouldReturnAllUsersWhoseAgeIsGreaterThan20InAList() {
|
||||||
|
List<User> users = userRepository.findByAgeGreaterThan(20);
|
||||||
|
assertThat(users).isNotEmpty();
|
||||||
|
assertThat(users.stream()
|
||||||
|
.map(User::getAge)
|
||||||
|
.allMatch(age -> age > 20)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenAgeIs20_thenItShouldReturnAllUsersWhoseAgeIsGreaterThan20InAStream() {
|
||||||
|
Stream<User> users = userRepository.findAllByAgeGreaterThan(20);
|
||||||
|
assertThat(users).isNotNull();
|
||||||
|
assertThat(users.map(User::getAge)
|
||||||
|
.allMatch(age -> age > 20)).isTrue();
|
||||||
|
}
|
||||||
|
}
|
25
pom.xml
25
pom.xml
|
@ -332,12 +332,6 @@
|
||||||
|
|
||||||
<module>apache-cxf-modules</module>
|
<module>apache-cxf-modules</module>
|
||||||
<module>apache-libraries</module>
|
<module>apache-libraries</module>
|
||||||
<module>apache-poi</module>
|
|
||||||
<module>apache-velocity</module>
|
|
||||||
<module>di-modules</module>
|
|
||||||
<module>asciidoctor</module>
|
|
||||||
|
|
||||||
<module>aws-modules</module>
|
|
||||||
|
|
||||||
<module>azure</module>
|
<module>azure</module>
|
||||||
<module>checker-plugin</module>
|
<module>checker-plugin</module>
|
||||||
|
@ -614,12 +608,6 @@
|
||||||
|
|
||||||
<module>apache-cxf-modules</module>
|
<module>apache-cxf-modules</module>
|
||||||
<module>apache-libraries</module>
|
<module>apache-libraries</module>
|
||||||
<module>apache-poi</module>
|
|
||||||
<module>apache-velocity</module>
|
|
||||||
<module>di-modules</module>
|
|
||||||
<module>asciidoctor</module>
|
|
||||||
|
|
||||||
<module>aws-modules</module>
|
|
||||||
|
|
||||||
<module>azure</module>
|
<module>azure</module>
|
||||||
<module>checker-plugin</module>
|
<module>checker-plugin</module>
|
||||||
|
@ -907,6 +895,11 @@
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
<module>algorithms-modules</module>
|
<module>algorithms-modules</module>
|
||||||
|
<module>apache-poi</module>
|
||||||
|
<module>apache-velocity</module>
|
||||||
|
<module>di-modules</module>
|
||||||
|
<module>asciidoctor</module>
|
||||||
|
<module>aws-modules</module>
|
||||||
<module>core-java-modules/core-java-9</module>
|
<module>core-java-modules/core-java-9</module>
|
||||||
<module>core-java-modules/core-java-9-improvements</module>
|
<module>core-java-modules/core-java-9-improvements</module>
|
||||||
<module>core-java-modules/core-java-9-jigsaw</module>
|
<module>core-java-modules/core-java-9-jigsaw</module>
|
||||||
|
@ -963,6 +956,7 @@
|
||||||
<module>spring-boot-modules/spring-boot-3</module>
|
<module>spring-boot-modules/spring-boot-3</module>
|
||||||
<module>spring-boot-modules/spring-boot-3-native</module>
|
<module>spring-boot-modules/spring-boot-3-native</module>
|
||||||
<module>spring-boot-modules/spring-boot-3-observation</module>
|
<module>spring-boot-modules/spring-boot-3-observation</module>
|
||||||
|
<module>spring-boot-modules/spring-boot-3-test-pitfalls</module>
|
||||||
<module>spring-swagger-codegen/custom-validations-opeanpi-codegen</module>
|
<module>spring-swagger-codegen/custom-validations-opeanpi-codegen</module>
|
||||||
<module>testing-modules/testing-assertions</module>
|
<module>testing-modules/testing-assertions</module>
|
||||||
<module>persistence-modules/fauna</module>
|
<module>persistence-modules/fauna</module>
|
||||||
|
@ -1106,6 +1100,12 @@
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
<module>algorithms-modules</module>
|
<module>algorithms-modules</module>
|
||||||
|
<module>apache-poi</module>
|
||||||
|
<module>apache-velocity</module>
|
||||||
|
<module>di-modules</module>
|
||||||
|
<module>asciidoctor</module>
|
||||||
|
<module>aws-modules</module>
|
||||||
|
|
||||||
<module>core-java-modules/core-java-9</module>
|
<module>core-java-modules/core-java-9</module>
|
||||||
<module>core-java-modules/core-java-9-improvements</module>
|
<module>core-java-modules/core-java-9-improvements</module>
|
||||||
<module>core-java-modules/core-java-9-jigsaw</module>
|
<module>core-java-modules/core-java-9-jigsaw</module>
|
||||||
|
@ -1162,6 +1162,7 @@
|
||||||
<module>spring-boot-modules/spring-boot-3</module>
|
<module>spring-boot-modules/spring-boot-3</module>
|
||||||
<module>spring-boot-modules/spring-boot-3-native</module>
|
<module>spring-boot-modules/spring-boot-3-native</module>
|
||||||
<module>spring-boot-modules/spring-boot-3-observation</module>
|
<module>spring-boot-modules/spring-boot-3-observation</module>
|
||||||
|
<module>spring-boot-modules/spring-boot-3-test-pitfalls</module>
|
||||||
<module>spring-swagger-codegen/custom-validations-opeanpi-codegen</module>
|
<module>spring-swagger-codegen/custom-validations-opeanpi-codegen</module>
|
||||||
<module>testing-modules/testing-assertions</module>
|
<module>testing-modules/testing-assertions</module>
|
||||||
<module>persistence-modules/fauna</module>
|
<module>persistence-modules/fauna</module>
|
||||||
|
|
|
@ -16,6 +16,11 @@
|
||||||
<quarkus.platform.version>2.16.0.Final</quarkus.platform.version>
|
<quarkus.platform.version>2.16.0.Final</quarkus.platform.version>
|
||||||
<surefire-plugin.version>3.0.0-M7</surefire-plugin.version>
|
<surefire-plugin.version>3.0.0-M7</surefire-plugin.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.baeldung</groupId>
|
||||||
|
<artifactId>quarkus-modules</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
<?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">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>spring-boot-3-test-pitfalls</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<name>spring-boot-3-test-pitfalls</name>
|
||||||
|
<description>Demo project for Spring Boot Testing Pitfalls</description>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.baeldung</groupId>
|
||||||
|
<artifactId>parent-boot-3</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<relativePath>../../parent-boot-3</relativePath>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-devtools</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mapstruct</groupId>
|
||||||
|
<artifactId>mapstruct</artifactId>
|
||||||
|
<version>${org.mapstruct.version}</version>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<pluginManagement>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>${lombok.version}</version>
|
||||||
|
</path>
|
||||||
|
<path>
|
||||||
|
<groupId>org.mapstruct</groupId>
|
||||||
|
<artifactId>mapstruct-processor</artifactId>
|
||||||
|
<version>${org.mapstruct.version}</version>
|
||||||
|
</path>
|
||||||
|
<path>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok-mapstruct-binding</artifactId>
|
||||||
|
<version>0.2.0</version>
|
||||||
|
</path>
|
||||||
|
</annotationProcessorPaths>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</pluginManagement>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<org.mapstruct.version>1.5.3.Final</org.mapstruct.version>
|
||||||
|
<maven-surefire-plugin.version>3.0.0-M7</maven-surefire-plugin.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.baeldung.sample.pets;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class PetsApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(PetsApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.baeldung.sample.pets.boundary;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class PetDto {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.baeldung.sample.pets.boundary;
|
||||||
|
|
||||||
|
import com.baeldung.sample.pets.domain.Pet;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
|
||||||
|
@Mapper(componentModel = "spring")
|
||||||
|
public interface PetDtoMapper {
|
||||||
|
|
||||||
|
PetDto map(Pet source);
|
||||||
|
|
||||||
|
Pet map(PetDto source);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.baeldung.sample.pets.boundary;
|
||||||
|
|
||||||
|
import com.baeldung.sample.pets.domain.PetService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/pets")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class PetsController {
|
||||||
|
|
||||||
|
private final PetService service;
|
||||||
|
private final PetDtoMapper mapper;
|
||||||
|
|
||||||
|
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public Collection<PetDto> readAll() {
|
||||||
|
return service.getPets().stream()
|
||||||
|
.map(mapper::map)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
package com.baeldung.sample.pets.domain;
|
||||||
|
|
||||||
|
public record Pet(String name) {
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.baeldung.sample.pets.domain;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.experimental.Delegate;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class PetService {
|
||||||
|
|
||||||
|
@Delegate
|
||||||
|
private final PetServiceRepository repo;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.baeldung.sample.pets.domain;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
public interface PetServiceRepository {
|
||||||
|
|
||||||
|
boolean add(Pet pet);
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
Collection<Pet> getPets();
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package com.baeldung.sample.pets.domain;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class PetServiceRepositoryImpl implements PetServiceRepository {
|
||||||
|
|
||||||
|
private final Set<Pet> pets = new HashSet<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Pet> getPets() {
|
||||||
|
return Collections.unmodifiableSet(pets);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean add(Pet pet) {
|
||||||
|
return this.pets.add(pet);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
this.pets.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
package com.baeldung.sample.pets.boundary;
|
||||||
|
|
||||||
|
import com.baeldung.sample.pets.domain.PetService;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.mock.mockito.MockReset;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
@ExtendWith(SpringExtension.class)
|
||||||
|
public class PetDtoMapperIntegrationTest {
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ComponentScan(basePackageClasses = PetDtoMapper.class)
|
||||||
|
static class PetDtoMapperTestConfig {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This would be necessary because the controller is also initialized
|
||||||
|
* and needs the service, although we do not want to test it here.
|
||||||
|
*
|
||||||
|
* Solutions:
|
||||||
|
* - place the mapper into a separate sub package
|
||||||
|
* - do not test the mapper separately, test it integrated within the controller
|
||||||
|
* (recommended)
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
PetService createServiceMock() {
|
||||||
|
return mock(PetService.class, MockReset.withSettings(MockReset.AFTER));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
PetDtoMapper mapper;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExist() { // simply test correct test setup
|
||||||
|
assertThat(mapper).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.baeldung.sample.pets.boundary;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Just an interface to use for compiler-checked component scanning during tests.
|
||||||
|
* @see ComponentScan#basePackageClasses()
|
||||||
|
*/
|
||||||
|
public interface PetsBoundaryLayer {
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package com.baeldung.sample.pets.boundary;
|
||||||
|
|
||||||
|
import com.baeldung.sample.pets.domain.PetService;
|
||||||
|
import com.baeldung.sample.test.slices.PetsBoundaryTest;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
@PetsBoundaryTest
|
||||||
|
class PetsControllerMvcIntegrationTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
MockMvc mvc;
|
||||||
|
@Autowired
|
||||||
|
PetService service;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnEmptyArrayWhenGetPets() throws Exception {
|
||||||
|
when(service.getPets()).thenReturn(Collections.emptyList());
|
||||||
|
mvc.perform(
|
||||||
|
get("/pets")
|
||||||
|
.accept(MediaType.APPLICATION_JSON)
|
||||||
|
)
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(content().string("[]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.baeldung.sample.pets.domain;
|
||||||
|
|
||||||
|
import com.baeldung.sample.test.slices.PetsDomainTest;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@PetsDomainTest
|
||||||
|
class PetServiceIntegrationTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
PetService service;
|
||||||
|
@Autowired // Mock
|
||||||
|
PetServiceRepository repository;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldAddPetWhenNotAlreadyExisting() {
|
||||||
|
var pet = new Pet("Dog");
|
||||||
|
when(repository.add(pet)).thenReturn(true);
|
||||||
|
var result = service.add(pet);
|
||||||
|
assertThat(result).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package com.baeldung.sample.pets.domain;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class PetServiceUnitTest {
|
||||||
|
|
||||||
|
PetService service = new PetService(new PetServiceRepositoryImpl());
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldAddPetWhenNotAlreadyExisting() {
|
||||||
|
var pet = new Pet("Dog");
|
||||||
|
var result = service.add(pet);
|
||||||
|
assertThat(result).isTrue();
|
||||||
|
assertThat(service.getPets()).hasSize(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotAddPetWhenAlreadyExisting() {
|
||||||
|
var pet = new Pet("Cat");
|
||||||
|
var result = service.add(pet);
|
||||||
|
assertThat(result).isTrue();
|
||||||
|
// try a second time
|
||||||
|
result = service.add(pet);
|
||||||
|
assertThat(result).isFalse();
|
||||||
|
assertThat(service.getPets()).hasSize(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.baeldung.sample.pets.domain;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Just an interface to use for compiler-checked component scanning during tests.
|
||||||
|
* @see ComponentScan#basePackageClasses()
|
||||||
|
*/
|
||||||
|
public interface PetsDomainLayer {
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package com.baeldung.sample.test.slices;
|
||||||
|
|
||||||
|
import com.baeldung.sample.pets.boundary.PetsBoundaryLayer;
|
||||||
|
import com.baeldung.sample.pets.boundary.PetsController;
|
||||||
|
import com.baeldung.sample.pets.domain.PetService;
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||||
|
import org.springframework.boot.test.context.TestConfiguration;
|
||||||
|
import org.springframework.boot.test.mock.mockito.MockReset;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.context.annotation.Primary;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Inherited;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
@Documented
|
||||||
|
@Inherited
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@WebMvcTest(controllers = PetsController.class)
|
||||||
|
@ComponentScan(basePackageClasses = PetsBoundaryLayer.class)
|
||||||
|
@Import(PetsBoundaryTest.PetBoundaryTestConfiguration.class)
|
||||||
|
// further features that can help to configure and execute tests
|
||||||
|
@ActiveProfiles({ "test", "boundary-test" })
|
||||||
|
@Tag("integration-test")
|
||||||
|
@Tag("boundary-test")
|
||||||
|
public @interface PetsBoundaryTest {
|
||||||
|
|
||||||
|
@TestConfiguration
|
||||||
|
class PetBoundaryTestConfiguration {
|
||||||
|
|
||||||
|
@Primary
|
||||||
|
@Bean
|
||||||
|
PetService createPetServiceMock() {
|
||||||
|
return mock(
|
||||||
|
PetService.class,
|
||||||
|
MockReset.withSettings(MockReset.AFTER)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package com.baeldung.sample.test.slices;
|
||||||
|
|
||||||
|
import com.baeldung.sample.pets.domain.PetServiceRepository;
|
||||||
|
import com.baeldung.sample.pets.domain.PetsDomainLayer;
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.springframework.boot.test.context.TestConfiguration;
|
||||||
|
import org.springframework.boot.test.mock.mockito.MockReset;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.context.annotation.Primary;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
|
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Inherited;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
@Documented
|
||||||
|
@Inherited
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@ExtendWith(SpringExtension.class)
|
||||||
|
@ComponentScan(basePackageClasses = PetsDomainLayer.class)
|
||||||
|
@Import(PetsDomainTest.PetServiceTestConfiguration.class)
|
||||||
|
// further features that can help to configure and execute tests
|
||||||
|
@ActiveProfiles({"test", "domain-test"})
|
||||||
|
@Tag("integration-test")
|
||||||
|
@Tag("domain-test")
|
||||||
|
public @interface PetsDomainTest {
|
||||||
|
|
||||||
|
@TestConfiguration
|
||||||
|
class PetServiceTestConfiguration {
|
||||||
|
|
||||||
|
@Primary
|
||||||
|
@Bean
|
||||||
|
PetServiceRepository createPetsRepositoryMock() {
|
||||||
|
return mock(
|
||||||
|
PetServiceRepository.class,
|
||||||
|
MockReset.withSettings(MockReset.AFTER)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
root: info
|
||||||
|
org:
|
||||||
|
springframework:
|
||||||
|
test:
|
||||||
|
context:
|
||||||
|
cache: DEBUG
|
||||||
|
spring:
|
||||||
|
main:
|
||||||
|
allow-bean-definition-overriding: true
|
|
@ -0,0 +1,29 @@
|
||||||
|
package com.baeldung.sample.singleton;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Scope;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class SingletonBeanConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
|
||||||
|
public SingletonBean singletonBean() {
|
||||||
|
return new SingletonBean();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
|
||||||
|
public SingletonBean anotherSingletonBean() {
|
||||||
|
return new SingletonBean();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class SingletonBean {
|
||||||
|
public String getValue() {
|
||||||
|
return "test";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.baeldung.sample.singleton;
|
||||||
|
|
||||||
|
public final class ThreadSafeSingleInstance {
|
||||||
|
|
||||||
|
private static volatile ThreadSafeSingleInstance instance = null;
|
||||||
|
|
||||||
|
private ThreadSafeSingleInstance() {}
|
||||||
|
|
||||||
|
public static ThreadSafeSingleInstance getInstance() {
|
||||||
|
if (instance == null) {
|
||||||
|
synchronized(ThreadSafeSingleInstance.class) {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new ThreadSafeSingleInstance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return "test";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package com.baeldung.sample.singleton;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
class SingletonBeanUnitTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Qualifier("singletonBean")
|
||||||
|
private SingletonBeanConfig.SingletonBean beanOne;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Qualifier("singletonBean")
|
||||||
|
private SingletonBeanConfig.SingletonBean beanTwo;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Qualifier("anotherSingletonBean")
|
||||||
|
private SingletonBeanConfig.SingletonBean beanThree;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenTwoBeansWithSameId_whenInjectingThem_thenSameInstancesAreReturned() {
|
||||||
|
assertSame(beanOne, beanTwo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenTwoBeansWithDifferentId_whenInjectingThem_thenDifferentInstancesAreReturned() {
|
||||||
|
assertNotSame(beanOne, beanThree);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.baeldung.sample.singleton;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||||
|
|
||||||
|
class ThreadSafeSingleInstanceUnitTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenTwoSingletonInstances_whenGettingThem_thenSameInstancesAreReturned() {
|
||||||
|
ThreadSafeSingleInstance instanceOne = ThreadSafeSingleInstance.getInstance();
|
||||||
|
ThreadSafeSingleInstance instanceTwo = ThreadSafeSingleInstance.getInstance();
|
||||||
|
assertSame(instanceOne, instanceTwo);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,3 +1,7 @@
|
||||||
|
### Spring Reactive Articles that are also part of the e-book
|
||||||
|
|
||||||
|
This module contains articles about Spring Reactive that are also part of an Ebook.
|
||||||
|
|
||||||
## Spring Reactive
|
## Spring Reactive
|
||||||
|
|
||||||
This module contains articles describing reactive processing in Spring.
|
This module contains articles describing reactive processing in Spring.
|
||||||
|
@ -14,3 +18,7 @@ This module contains articles describing reactive processing in Spring.
|
||||||
- [Handling Errors in Spring WebFlux](https://www.baeldung.com/spring-webflux-errors)
|
- [Handling Errors in Spring WebFlux](https://www.baeldung.com/spring-webflux-errors)
|
||||||
- [Spring Security 5 for Reactive Applications](https://www.baeldung.com/spring-security-5-reactive)
|
- [Spring Security 5 for Reactive Applications](https://www.baeldung.com/spring-security-5-reactive)
|
||||||
- [Concurrency in Spring WebFlux](https://www.baeldung.com/spring-webflux-concurrency)
|
- [Concurrency in Spring WebFlux](https://www.baeldung.com/spring-webflux-concurrency)
|
||||||
|
|
||||||
|
### NOTE:
|
||||||
|
|
||||||
|
Since this is a module tied to an e-book, it should **not** be moved or used to store the code for any further article.
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.baeldung.junit5.nested;
|
||||||
|
|
||||||
|
public class Article {
|
||||||
|
private String name;
|
||||||
|
private Membership articleLevel;
|
||||||
|
|
||||||
|
public Article(String name, Membership articleLevel) {
|
||||||
|
this.name = name;
|
||||||
|
this.articleLevel = articleLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Membership getArticleLevel() {
|
||||||
|
return articleLevel;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.baeldung.junit5.nested;
|
||||||
|
|
||||||
|
public enum Membership {
|
||||||
|
FREE(0), SILVER(10), GOLD(20);
|
||||||
|
|
||||||
|
private final int level;
|
||||||
|
|
||||||
|
Membership(int level) {
|
||||||
|
this.level = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int compare(Membership other) {
|
||||||
|
return level - other.level;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package com.baeldung.junit5.nested;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class Publication {
|
||||||
|
private final List<Article> articles;
|
||||||
|
|
||||||
|
public Publication(List<Article> articles) {
|
||||||
|
this.articles = articles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getReadableArticles(User user) {
|
||||||
|
return articles.stream()
|
||||||
|
.filter(a -> a.getArticleLevel()
|
||||||
|
.compare(user.getMembership()) <= 0)
|
||||||
|
.map(Article::getName)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getLockedArticles(User user) {
|
||||||
|
return articles.stream()
|
||||||
|
.filter(a -> a.getArticleLevel()
|
||||||
|
.compare(user.getMembership()) > 0)
|
||||||
|
.map(Article::getName)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Article> getArticles() {
|
||||||
|
return articles;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.baeldung.junit5.nested;
|
||||||
|
|
||||||
|
public class User {
|
||||||
|
private String name;
|
||||||
|
private Membership membership;
|
||||||
|
|
||||||
|
public User(String name, Membership membership) {
|
||||||
|
this.name = name;
|
||||||
|
this.membership = membership;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Membership getMembership() {
|
||||||
|
return membership;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package com.baeldung.junit5.nested;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class NestedUnitTest {
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void beforeEach() {
|
||||||
|
System.out.println("NestedUnitTest.beforeEach()");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class FirstNestedClass {
|
||||||
|
@BeforeEach
|
||||||
|
void beforeEach() {
|
||||||
|
System.out.println("FirstNestedClass.beforeEach()");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void test() {
|
||||||
|
System.out.println("FirstNestedClass.test()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class SecondNestedClass {
|
||||||
|
@BeforeEach
|
||||||
|
void beforeEach() {
|
||||||
|
System.out.println("SecondNestedClass.beforeEach()");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void test() {
|
||||||
|
System.out.println("SecondNestedClass.test()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
package com.baeldung.junit5.nested;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
@DisplayName("given a article publication with three articles")
|
||||||
|
class OnlinePublicationUnitTest {
|
||||||
|
private Publication publication;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setupArticlesAndPublication() {
|
||||||
|
Article freeArticle = new Article("free article", Membership.FREE);
|
||||||
|
Article silverArticle = new Article("silver level article", Membership.SILVER);
|
||||||
|
Article goldArticle = new Article("gold level article", Membership.GOLD);
|
||||||
|
publication = new Publication(Arrays.asList(freeArticle, silverArticle, goldArticle));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("then 3 articles are available")
|
||||||
|
void shouldHaveThreeArticlesInTotal() {
|
||||||
|
List<Article> allArticles = publication.getArticles();
|
||||||
|
assertThat(allArticles).hasSize(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
@DisplayName("when a user with a 'free' membership logs in")
|
||||||
|
class UserWithAFreeMembership {
|
||||||
|
User freeFreya = new User("Freya", Membership.FREE);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("then he should be able to read the 'free' articles")
|
||||||
|
void shouldOnlyReadFreeArticles() {
|
||||||
|
List<String> articles = publication.getReadableArticles(freeFreya);
|
||||||
|
assertThat(articles).containsExactly("free article");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("then he shouldn't be able to read the 'silver' and 'gold' articles")
|
||||||
|
void shouldSeeSilverAndGoldLevelArticlesAsLocked() {
|
||||||
|
List<String> articles = publication.getLockedArticles(freeFreya);
|
||||||
|
assertThat(articles).containsExactlyInAnyOrder("silver level article", "gold level article");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
@DisplayName("when a user with a 'silver' membership logs in")
|
||||||
|
class UserWithSilverMembership {
|
||||||
|
User silverSilvester = new User("Silvester", Membership.SILVER);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("then he should be able to read the 'free' and 'silver' level articles")
|
||||||
|
void shouldOnlyReadFreeAndSilverLevelArticles() {
|
||||||
|
List<String> articles = publication.getReadableArticles(silverSilvester);
|
||||||
|
assertThat(articles).containsExactlyInAnyOrder("free article", "silver level article");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("then he should see the 'gold' level articles as locked")
|
||||||
|
void shouldSeeGoldLevelArticlesAsLocked() {
|
||||||
|
List<String> articles = publication.getLockedArticles(silverSilvester);
|
||||||
|
assertThat(articles).containsExactlyInAnyOrder("gold level article");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
@DisplayName("when a user with a 'gold' membership logs in")
|
||||||
|
class UserWithGoldMembership {
|
||||||
|
User goldenGeorge = new User("George", Membership.GOLD);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("then he should be able to read all the articles")
|
||||||
|
void shouldSeeAllArticles() {
|
||||||
|
List<String> articles = publication.getReadableArticles(goldenGeorge);
|
||||||
|
assertThat(articles).containsExactlyInAnyOrder("free article", "silver level article", "gold level article");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("then he should not see any article as locked")
|
||||||
|
void shouldNotHaveHiddenArticles() {
|
||||||
|
List<String> articles = publication.getLockedArticles(goldenGeorge);
|
||||||
|
assertThat(articles).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue