Merge branch 'master' of https://github.com/eugenp/tutorials into BAEL_3301_testing_@ConfigurationProperties
This commit is contained in:
commit
057cab32fc
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,148 @@
|
||||||
|
package com.baeldung.lock;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.nio.channels.FileLock;
|
||||||
|
import java.nio.channels.NonReadableChannelException;
|
||||||
|
import java.nio.channels.NonWritableChannelException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
|
||||||
|
public class FileLocks {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(FileLocks.class);
|
||||||
|
|
||||||
|
// Write locks
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trying to get an exclusive lock on a read-only FileChannel won't work.
|
||||||
|
*/
|
||||||
|
static void getExclusiveLockFromInputStream() throws IOException {
|
||||||
|
Path path = Files.createTempFile("foo", "txt");
|
||||||
|
try (FileInputStream fis = new FileInputStream(path.toFile());
|
||||||
|
FileLock lock = fis.getChannel()
|
||||||
|
.lock()) {
|
||||||
|
LOG.debug("This won't happen");
|
||||||
|
} catch (NonWritableChannelException e) {
|
||||||
|
LOG.error("The channel obtained through a FileInputStream isn't writable. You can't obtain an exclusive lock on it!");
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an exclusive lock from a RandomAccessFile. Works because the file is writable.
|
||||||
|
* @param from beginning of the locked region
|
||||||
|
* @param size how many bytes to lock
|
||||||
|
* @return A lock object representing the newly-acquired lock
|
||||||
|
* @throws IOException if there is a problem creating the temporary file
|
||||||
|
*/
|
||||||
|
static FileLock getExclusiveLockFromRandomAccessFile(long from, long size) throws IOException {
|
||||||
|
Path path = Files.createTempFile("foo", "txt");
|
||||||
|
try (RandomAccessFile file = new RandomAccessFile(path.toFile(), "rw");
|
||||||
|
FileLock lock = file.getChannel()
|
||||||
|
.lock(from, size, false)) {
|
||||||
|
if (lock.isValid()) {
|
||||||
|
LOG.debug("This is a valid exclusive lock");
|
||||||
|
return lock;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error(e.getMessage());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Acquires an exclusive lock on a file region.
|
||||||
|
* @param from beginning of the locked region
|
||||||
|
* @param size how many bytes to lock
|
||||||
|
* @return A lock object representing the newly-acquired lock
|
||||||
|
* @throws IOException if there is a problem creating the temporary file
|
||||||
|
*/
|
||||||
|
static FileLock getExclusiveLockFromFileChannelOpen(long from, long size) throws IOException {
|
||||||
|
Path path = Files.createTempFile("foo", "txt");
|
||||||
|
try (FileChannel channel = FileChannel.open(path, StandardOpenOption.APPEND); FileLock lock = channel.lock(from, size, false)) {
|
||||||
|
String text = "Hello, world.";
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(text.length() + System.lineSeparator()
|
||||||
|
.length());
|
||||||
|
buffer.put((text + System.lineSeparator()).getBytes(StandardCharsets.UTF_8));
|
||||||
|
buffer.flip();
|
||||||
|
while (buffer.hasRemaining()) {
|
||||||
|
channel.write(buffer, channel.size());
|
||||||
|
}
|
||||||
|
LOG.debug("This was written to the file");
|
||||||
|
Files.lines(path)
|
||||||
|
.forEach(LOG::debug);
|
||||||
|
return lock;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read locks
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trying to get a shared lock on a write-only FileChannel won't work.
|
||||||
|
*/
|
||||||
|
static void getReadLockFromOutputStream() throws IOException {
|
||||||
|
Path path = Files.createTempFile("foo", "txt");
|
||||||
|
try (FileOutputStream fis = new FileOutputStream(path.toFile());
|
||||||
|
FileLock lock = fis.getChannel()
|
||||||
|
.lock(0, Long.MAX_VALUE, true)) {
|
||||||
|
LOG.debug("This won't happen");
|
||||||
|
} catch (NonReadableChannelException e) {
|
||||||
|
LOG.error("The channel obtained through a FileOutputStream isn't readable. " + "You can't obtain an shared lock on it!");
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a lock from an <tt>InputStream</tt>.
|
||||||
|
* @param from beginning of the locked region
|
||||||
|
* @param size how many bytes to lock
|
||||||
|
* @return A lock object representing the newly-acquired lock
|
||||||
|
* @throws IOException if there is a problem creating the temporary file
|
||||||
|
*/
|
||||||
|
static FileLock getReadLockFromInputStream(long from, long size) throws IOException {
|
||||||
|
Path path = Files.createTempFile("foo", "txt");
|
||||||
|
try (FileInputStream fis = new FileInputStream(path.toFile());
|
||||||
|
FileLock lock = fis.getChannel()
|
||||||
|
.lock(from, size, true)) {
|
||||||
|
if (lock.isValid()) {
|
||||||
|
LOG.debug("This is a valid shared lock");
|
||||||
|
return lock;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an exclusive lock from a RandomAccessFile. Works because the file is readable.
|
||||||
|
* @param from beginning of the locked region
|
||||||
|
* @param size how many bytes to lock
|
||||||
|
* @return A lock object representing the newly-acquired lock
|
||||||
|
* @throws IOException if there is a problem creating the temporary file
|
||||||
|
*/
|
||||||
|
static FileLock getReadLockFromRandomAccessFile(long from, long size) throws IOException {
|
||||||
|
Path path = Files.createTempFile("foo", "txt");
|
||||||
|
try (RandomAccessFile file = new RandomAccessFile(path.toFile(), "r"); // could also be "rw", but "r" is sufficient for reading
|
||||||
|
FileLock lock = file.getChannel()
|
||||||
|
.lock(from, size, true)) {
|
||||||
|
if (lock.isValid()) {
|
||||||
|
LOG.debug("This is a valid shared lock");
|
||||||
|
return lock;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error(e.getMessage());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package com.baeldung.lock;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.channels.FileLock;
|
||||||
|
import java.nio.channels.NonReadableChannelException;
|
||||||
|
import java.nio.channels.NonWritableChannelException;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class FileLocksUnitTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenAnInputStream_whenGetWriteLock_thenThrowNonWritableChannelException() {
|
||||||
|
assertThrows(NonWritableChannelException.class, () -> FileLocks.getExclusiveLockFromInputStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenARandomAccessFileWithReadWriteAccess_whenGetWriteLock_thenLock() throws IOException {
|
||||||
|
FileLock lock = FileLocks.getExclusiveLockFromRandomAccessFile(0, 10);
|
||||||
|
assertNotNull(lock);
|
||||||
|
assertFalse(lock.isShared());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenAPath_whenGetExclusiveLock_thenLock() throws IOException {
|
||||||
|
FileLock lock = FileLocks.getExclusiveLockFromFileChannelOpen(0, 10);
|
||||||
|
assertNotNull(lock);
|
||||||
|
assertFalse(lock.isShared());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenAFileOutputStream_whenGetSharedLock_thenThrowNonReadableChannelException() {
|
||||||
|
assertThrows(NonReadableChannelException.class, FileLocks::getReadLockFromOutputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenAnInputStream_whenGetSharedLock_thenLock() throws IOException {
|
||||||
|
FileLock lock = FileLocks.getReadLockFromInputStream(0, 10);
|
||||||
|
assertNotNull(lock);
|
||||||
|
assertTrue(lock.isShared());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenARandomAccessFile_whenGetSharedLock_thenLock() throws IOException {
|
||||||
|
FileLock lock = FileLocks.getReadLockFromRandomAccessFile(0, 10);
|
||||||
|
assertNotNull(lock);
|
||||||
|
assertTrue(lock.isShared());
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,7 @@
|
||||||
- [Introduction to Java Serialization](http://www.baeldung.com/java-serialization)
|
- [Introduction to Java Serialization](http://www.baeldung.com/java-serialization)
|
||||||
- [Guide to UUID in Java](http://www.baeldung.com/java-uuid)
|
- [Guide to UUID in Java](http://www.baeldung.com/java-uuid)
|
||||||
- [Creating a Java Compiler Plugin](http://www.baeldung.com/java-build-compiler-plugin)
|
- [Creating a Java Compiler Plugin](http://www.baeldung.com/java-build-compiler-plugin)
|
||||||
- [Quick Guide to Java Stack](http://www.baeldung.com/java-stack)
|
- [Quick Guide to the Java Stack](http://www.baeldung.com/java-stack)
|
||||||
- [Compiling Java *.class Files with javac](http://www.baeldung.com/javac)
|
- [Compiling Java *.class Files with javac](http://www.baeldung.com/javac)
|
||||||
- [Introduction to Javadoc](http://www.baeldung.com/javadoc)
|
- [Introduction to Javadoc](http://www.baeldung.com/javadoc)
|
||||||
- [Guide to the Externalizable Interface in Java](http://www.baeldung.com/java-externalizable)
|
- [Guide to the Externalizable Interface in Java](http://www.baeldung.com/java-externalizable)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package com.baeldung;
|
package com.baeldung.timer;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
|
@ -5,4 +5,5 @@ This module contains articles about Kotlin core features.
|
||||||
### Relevant articles:
|
### Relevant articles:
|
||||||
- [Working with Dates in Kotlin](https://www.baeldung.com/kotlin-dates)
|
- [Working with Dates in Kotlin](https://www.baeldung.com/kotlin-dates)
|
||||||
- [Kotlin Ternary Conditional Operator](https://www.baeldung.com/kotlin-ternary-operator)
|
- [Kotlin Ternary Conditional Operator](https://www.baeldung.com/kotlin-ternary-operator)
|
||||||
|
- [Sequences in Kotlin](https://www.baeldung.com/kotlin/sequences)
|
||||||
- [[<-- Prev]](/core-kotlin-modules/core-kotlin)
|
- [[<-- Prev]](/core-kotlin-modules/core-kotlin)
|
||||||
|
|
|
@ -13,4 +13,7 @@ This module contains articles about Kotlin core features.
|
||||||
- [Implementing a Binary Tree in Kotlin](https://www.baeldung.com/kotlin-binary-tree)
|
- [Implementing a Binary Tree in Kotlin](https://www.baeldung.com/kotlin-binary-tree)
|
||||||
- [JUnit 5 for Kotlin Developers](https://www.baeldung.com/junit-5-kotlin)
|
- [JUnit 5 for Kotlin Developers](https://www.baeldung.com/junit-5-kotlin)
|
||||||
- [Converting Kotlin Data Class from JSON using GSON](https://www.baeldung.com/kotlin-json-convert-data-class)
|
- [Converting Kotlin Data Class from JSON using GSON](https://www.baeldung.com/kotlin-json-convert-data-class)
|
||||||
|
- [Fuel HTTP Library with Kotlin](https://www.baeldung.com/kotlin-fuel)
|
||||||
|
- [Introduction to Kovenant Library for Kotlin](https://www.baeldung.com/kotlin-kovenant)
|
||||||
|
- [Dependency Injection for Kotlin with Injekt](https://www.baeldung.com/kotlin-dependency-injection-with-injekt)
|
||||||
- [[More --> ]](/core-kotlin-modules/core-kotlin-2)
|
- [[More --> ]](/core-kotlin-modules/core-kotlin-2)
|
||||||
|
|
|
@ -5,3 +5,4 @@ This module contains articles about Scala's core features
|
||||||
### Relevant Articles:
|
### Relevant Articles:
|
||||||
|
|
||||||
- [Introduction to Scala](https://www.baeldung.com/scala-intro)
|
- [Introduction to Scala](https://www.baeldung.com/scala-intro)
|
||||||
|
- [Regular Expressions in Scala](https://www.baeldung.com/scala/regular-expressions)
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
package com.baeldung.scala
|
||||||
|
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
|
||||||
|
class HigherOrderFunctionsExamplesUnitTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
def whenCallingMapWithAnonymousFunction_thenTransformationIsApplied() = {
|
||||||
|
val expected = Seq("sir Alex Ferguson", "sir Bobby Charlton", "sir Frank Lampard")
|
||||||
|
|
||||||
|
val names = Seq("Alex Ferguson", "Bobby Charlton", "Frank Lampard")
|
||||||
|
val sirNames = names.map(name => "sir " + name)
|
||||||
|
|
||||||
|
assertEquals(expected, sirNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
def whenCallingMapWithDefined_thenTransformationIsApplied() = {
|
||||||
|
val expected = Seq("sir Alex Ferguson", "sir Bobby Charlton", "sir Frank Lampard")
|
||||||
|
|
||||||
|
val names = Seq("Alex Ferguson", "Bobby Charlton", "Frank Lampard")
|
||||||
|
|
||||||
|
def prefixWithSir(name: String) = "sir " + name
|
||||||
|
val sirNames = names.map(prefixWithSir)
|
||||||
|
|
||||||
|
assertEquals(expected, sirNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
def whenCallingFilter_thenUnecessaryElementsAreRemoved() = {
|
||||||
|
val expected = Seq("John O'Shea", "John Hartson")
|
||||||
|
|
||||||
|
val names = Seq("John O'Shea", "Aiden McGeady", "John Hartson")
|
||||||
|
val johns = names.filter(name => name.matches("^John .*"))
|
||||||
|
|
||||||
|
assertEquals(expected, johns)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
def whenCallingReduce_thenProperSumIsCalculated() = {
|
||||||
|
val expected = 2750
|
||||||
|
|
||||||
|
val earnings = Seq(1000, 1300, 450)
|
||||||
|
val sumEarnings = earnings.reduce((acc, x) => acc + x)
|
||||||
|
|
||||||
|
assertEquals(expected, sumEarnings)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
def whenCallingFold_thenNumberOfWordsShouldBeCalculated() = {
|
||||||
|
val expected = 6
|
||||||
|
|
||||||
|
val strings = Seq("bunch of words", "just me", "it")
|
||||||
|
val sumEarnings = strings.foldLeft(0)((acc, x) => acc + x.split(" ").size)
|
||||||
|
|
||||||
|
assertEquals(expected, sumEarnings)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
def whenCallingOwnHigherOrderFunction_thenProperFunctionIsReturned() = {
|
||||||
|
def mathOperation(name: String): (Int, Int) => Int = (x: Int, y: Int) => {
|
||||||
|
name match {
|
||||||
|
case "addition" => x + y
|
||||||
|
case "multiplication" => x * y
|
||||||
|
case "division" => x/y
|
||||||
|
case "subtraction" => x - y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def add: (Int, Int) => Int = mathOperation("addition")
|
||||||
|
def mul: (Int, Int) => Int = mathOperation("multiplication")
|
||||||
|
def div: (Int, Int) => Int = mathOperation("division")
|
||||||
|
def sub: (Int, Int) => Int = mathOperation("subtraction")
|
||||||
|
|
||||||
|
assertEquals(15, add(10, 5))
|
||||||
|
assertEquals(50, mul(10, 5))
|
||||||
|
assertEquals(2, div(10, 5))
|
||||||
|
assertEquals(5, sub(10, 5))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package com.baeldung.scala
|
package com.baeldung.scala.regex
|
||||||
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
|
@ -14,11 +14,28 @@
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>it.unimi.dsi</groupId>
|
<groupId>it.unimi.dsi</groupId>
|
||||||
<artifactId>dsiutils</artifactId>
|
<artifactId>dsiutils</artifactId>
|
||||||
<version>${dsiutils.version}</version>
|
<version>${dsiutils.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.vavr</groupId>
|
||||||
|
<artifactId>vavr</artifactId>
|
||||||
|
<version>${vavr.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-lang3</artifactId>
|
||||||
|
<version>${commons.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.assertj</groupId>
|
||||||
|
<artifactId>assertj-core</artifactId>
|
||||||
|
<version>${assertj.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
@ -33,6 +50,9 @@
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<dsiutils.version>2.6.0</dsiutils.version>
|
<dsiutils.version>2.6.0</dsiutils.version>
|
||||||
|
<vavr.version>0.10.2</vavr.version>
|
||||||
|
<commons.version>3.9</commons.version>
|
||||||
|
<assertj.version>3.6.1</assertj.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -0,0 +1,149 @@
|
||||||
|
package com.baeldung.parsedouble;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.math.NumberUtils;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.google.common.base.MoreObjects;
|
||||||
|
import com.google.common.primitives.Doubles;
|
||||||
|
|
||||||
|
import io.vavr.control.Try;
|
||||||
|
|
||||||
|
public class StringToDoubleParserUnitTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenNullValue_whenParseStringToDouble_thenDefaultNaNValueIsReturned() {
|
||||||
|
assertThat(parseStringToDouble(null)).isNaN();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenEmptyStringValue_whenParseStringToDouble_thenDefaultNaNValueIsReturned() {
|
||||||
|
assertThat(parseStringToDouble("")).isNaN();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenStringValue_whenParseStringToDouble_thenDoubleValueIsReturned() {
|
||||||
|
assertThat(parseStringToDouble("1")).isEqualTo(1.0d);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenStringValue_whenParseStringToDoubleWithDefault_thenDoubleValueIsReturned() {
|
||||||
|
assertThat(parseStringToDouble("1", 2.0d)).isEqualTo(1.0d);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenEmptyStringValue_whenParseStringToDoubleWithDefault_thenDefaultValueIsReturned() {
|
||||||
|
assertThat(parseStringToDouble("", 1.0d)).isEqualTo(1.0d);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenNullValue_whenParseStringToDoubleWithDefault_thenDefaultValueIsReturned() {
|
||||||
|
assertThat(parseStringToDouble(null, 1.0d)).isEqualTo(1.0d);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenStringValue_whenParseStringToOptionalDouble_thenOptionalValueIsReturned() {
|
||||||
|
assertThat(parseStringToOptionalDouble("1")).isEqualTo(Optional.of(1.0d));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenNullValue_whenParseStringToOptionalDouble_thenOptionalValueIsEmpty() {
|
||||||
|
assertThat(parseStringToOptionalDouble(null)).isEqualTo(Optional.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenEmptyStringValue_whenParseStringToOptionalDouble_thenOptionalValueIsEmpty() {
|
||||||
|
assertThat(parseStringToOptionalDouble("")).isEqualTo(Optional.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenEmptyStringValue_whenParseStringToOptionalDouble_thenDefaulOptionalValueIsReturned() {
|
||||||
|
assertThat(parseStringToOptionalDouble("").orElse(1.0d)).isEqualTo(1.0d);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenNullValue_whenParseStringToOptionalDouble_thenDefaulOptionalValueIsReturned() {
|
||||||
|
assertThat(parseStringToOptionalDouble(null).orElse(1.0d)).isEqualTo(1.0d);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenStringValue_whenTryStringToDouble_thenDoubleValueIsReturned() {
|
||||||
|
assertThat(tryStringToDouble("1", 2.0d)).isEqualTo(1.0d);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenNullValue_whenTryStringToDoubleWithDefault_thenDoubleValueIsReturned() {
|
||||||
|
assertThat(tryStringToDouble(null, 2.0d)).isEqualTo(2.0d);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenEmptyStringValue_whenTryStringToDoubleWithDefault_thenDoubleValueIsReturned() {
|
||||||
|
assertThat(tryStringToDouble("", 2.0d)).isEqualTo(2.0d);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenTwoStringValues_whenTryParseFirstNonNull_thenDoubleValueIsReturned() {
|
||||||
|
assertThat(Doubles.tryParse(MoreObjects.firstNonNull("1.0", "2.0"))).isEqualTo(1.0d);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenNullStringValue_whenTryParseFirstNonNull_thenSecondDoubleValueIsReturned() {
|
||||||
|
assertThat(Doubles.tryParse(MoreObjects.firstNonNull(null, "2.0"))).isEqualTo(2.0d);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenEmptyStringValue_whenTryParseFirstNonNull_thenNullIsReturned() {
|
||||||
|
assertThat(Doubles.tryParse(MoreObjects.firstNonNull("", "2.0"))).isEqualTo(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenStringValue_whenToDouble_thenDoubleValueIsReturned() {
|
||||||
|
assertThat(NumberUtils.toDouble("1.0")).isEqualTo(1.0d);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenNullValue_whenToDouble_thenLibraryDefaultDoubleValueIsReturned() {
|
||||||
|
String nullString = null;
|
||||||
|
assertThat(NumberUtils.toDouble(nullString)).isEqualTo(0.0d);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenEmptyStringValue_whenToDouble_thenLibraryDefaultDoubleValueIsReturned() {
|
||||||
|
assertThat(NumberUtils.toDouble("")).isEqualTo(0.0d);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenEmptyStringValue_whenToDoubleWithDefault_thenDoubleValueIsReturned() {
|
||||||
|
assertThat(NumberUtils.toDouble("", 2.0d)).isEqualTo(2.0d);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenNullValue_whenToDoubleWithDefault_thenDoubleValueIsReturned() {
|
||||||
|
String nullString = null;
|
||||||
|
assertThat(NumberUtils.toDouble(nullString, 2.0d)).isEqualTo(2.0d);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenStringValue_whenToDoubleWithDefault_thenDoubleValueIsReturned() {
|
||||||
|
assertThat(NumberUtils.toDouble("1.0", 2.0d)).isEqualTo(1.0d);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Optional<Double> parseStringToOptionalDouble(String value) {
|
||||||
|
return value == null || value.isEmpty() ? Optional.empty() : Optional.of(Double.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double parseStringToDouble(String value) {
|
||||||
|
return value == null || value.isEmpty() ? Double.NaN : Double.parseDouble(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double parseStringToDouble(String value, double defaultValue) {
|
||||||
|
return value == null || value.isEmpty() ? defaultValue : Double.parseDouble(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double tryStringToDouble(String value, double defaultValue) {
|
||||||
|
return Try.of(() -> Double.parseDouble(value)).getOrElse(defaultValue);
|
||||||
|
}
|
||||||
|
}
|
|
@ -73,6 +73,28 @@
|
||||||
<version>${cache2k.version}</version>
|
<version>${cache2k.version}</version>
|
||||||
<type>pom</type>
|
<type>pom</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup.moshi</groupId>
|
||||||
|
<artifactId>moshi</artifactId>
|
||||||
|
<version>${moshi.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup.moshi</groupId>
|
||||||
|
<artifactId>moshi-adapters</artifactId>
|
||||||
|
<version>${moshi.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.jcabi</groupId>
|
||||||
|
<artifactId>jcabi-aspects</artifactId>
|
||||||
|
<version>${jcabi-aspects.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.aspectj</groupId>
|
||||||
|
<artifactId>aspectjrt</artifactId>
|
||||||
|
<version>${aspectjrt.version}</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<repositories>
|
<repositories>
|
||||||
|
@ -82,6 +104,36 @@
|
||||||
</repository>
|
</repository>
|
||||||
</repositories>
|
</repositories>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<finalName>libraries-3</finalName>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>com.jcabi</groupId>
|
||||||
|
<artifactId>jcabi-maven-plugin</artifactId>
|
||||||
|
<version>${jcabi-maven-plugin.version}</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>ajc</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.aspectj</groupId>
|
||||||
|
<artifactId>aspectjtools</artifactId>
|
||||||
|
<version>${aspectjtools.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.aspectj</groupId>
|
||||||
|
<artifactId>aspectjweaver</artifactId>
|
||||||
|
<version>${aspectjweaver.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<jcommander.version>1.78</jcommander.version>
|
<jcommander.version>1.78</jcommander.version>
|
||||||
<lombok.version>1.18.6</lombok.version>
|
<lombok.version>1.18.6</lombok.version>
|
||||||
|
@ -93,5 +145,11 @@
|
||||||
<cactoos.version>0.43</cactoos.version>
|
<cactoos.version>0.43</cactoos.version>
|
||||||
<airline.version>2.7.2</airline.version>
|
<airline.version>2.7.2</airline.version>
|
||||||
<cache2k.version>1.2.3.Final</cache2k.version>
|
<cache2k.version>1.2.3.Final</cache2k.version>
|
||||||
|
<moshi.version>1.9.2</moshi.version>
|
||||||
|
<jcabi-aspects.version>0.22.6</jcabi-aspects.version>
|
||||||
|
<aspectjrt.version>1.9.2</aspectjrt.version>
|
||||||
|
<jcabi-maven-plugin.version>0.14.1</jcabi-maven-plugin.version>
|
||||||
|
<aspectjtools.version>1.9.2</aspectjtools.version>
|
||||||
|
<aspectjweaver.version>1.9.2</aspectjweaver.version>
|
||||||
</properties>
|
</properties>
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -4,38 +4,33 @@ import java.util.Objects;
|
||||||
|
|
||||||
import org.cache2k.Cache;
|
import org.cache2k.Cache;
|
||||||
import org.cache2k.Cache2kBuilder;
|
import org.cache2k.Cache2kBuilder;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
public class ProductHelper {
|
public class ProductHelper {
|
||||||
|
|
||||||
final Logger LOGGER = LoggerFactory.getLogger(ProductHelper.class);
|
|
||||||
|
|
||||||
private Cache<String, Integer> cachedDiscounts;
|
private Cache<String, Integer> cachedDiscounts;
|
||||||
|
|
||||||
|
private int cacheMissCount = 0;
|
||||||
|
|
||||||
public ProductHelper() {
|
public ProductHelper() {
|
||||||
cachedDiscounts = Cache2kBuilder.of(String.class, Integer.class)
|
cachedDiscounts = Cache2kBuilder.of(String.class, Integer.class)
|
||||||
.name("discount")
|
.name("discount")
|
||||||
.eternal(true)
|
.eternal(true)
|
||||||
.entryCapacity(100)
|
.entryCapacity(100)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
initDiscountCache("Sports", 20);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void initDiscountCache(String productType, Integer value) {
|
|
||||||
cachedDiscounts.put(productType, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getDiscount(String productType) {
|
public Integer getDiscount(String productType) {
|
||||||
Integer discount = cachedDiscounts.get(productType);
|
Integer discount = cachedDiscounts.get(productType);
|
||||||
if (Objects.isNull(discount)) {
|
if (Objects.isNull(discount)) {
|
||||||
LOGGER.info("Discount for {} not found.", productType);
|
cacheMissCount++;
|
||||||
discount = 0;
|
discount = "Sports".equalsIgnoreCase(productType) ? 20 : 10;
|
||||||
} else {
|
cachedDiscounts.put(productType, discount);
|
||||||
LOGGER.info("Discount for {} found.", productType);
|
|
||||||
}
|
}
|
||||||
return discount;
|
return discount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getCacheMissCount() {
|
||||||
|
return cacheMissCount;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package com.baeldung.cache2k;
|
package com.baeldung.cache2k;
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.cache2k.Cache;
|
import org.cache2k.Cache;
|
||||||
|
@ -14,28 +13,26 @@ public class ProductHelperUsingLoader {
|
||||||
|
|
||||||
private Cache<String, Integer> cachedDiscounts;
|
private Cache<String, Integer> cachedDiscounts;
|
||||||
|
|
||||||
|
private int cacheMissCount = 0;
|
||||||
|
|
||||||
public ProductHelperUsingLoader() {
|
public ProductHelperUsingLoader() {
|
||||||
cachedDiscounts = Cache2kBuilder.of(String.class, Integer.class)
|
cachedDiscounts = Cache2kBuilder.of(String.class, Integer.class)
|
||||||
.name("discount-loader")
|
.name("discount-loader")
|
||||||
.eternal(false)
|
|
||||||
.expireAfterWrite(10, TimeUnit.MILLISECONDS)
|
.expireAfterWrite(10, TimeUnit.MILLISECONDS)
|
||||||
.entryCapacity(100)
|
.entryCapacity(100)
|
||||||
.loader((key) -> {
|
.loader((key) -> {
|
||||||
LOGGER.info("Calculating discount for {}.", key);
|
cacheMissCount++;
|
||||||
return "Sports".equalsIgnoreCase(key) ? 20 : 10;
|
return "Sports".equalsIgnoreCase(key) ? 20 : 10;
|
||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getDiscount(String productType) {
|
public Integer getDiscount(String productType) {
|
||||||
Integer discount = cachedDiscounts.get(productType);
|
return cachedDiscounts.get(productType);
|
||||||
if (Objects.isNull(discount)) {
|
}
|
||||||
LOGGER.info("Discount for {} not found.", productType);
|
|
||||||
discount = 0;
|
public int getCacheMissCount() {
|
||||||
} else {
|
return cacheMissCount;
|
||||||
LOGGER.info("Discount for {} found.", productType);
|
|
||||||
}
|
|
||||||
return discount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package com.baeldung.cache2k;
|
package com.baeldung.cache2k;
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.cache2k.Cache;
|
import org.cache2k.Cache;
|
||||||
|
@ -16,14 +15,15 @@ public class ProductHelperWithEventListener {
|
||||||
|
|
||||||
private Cache<String, Integer> cachedDiscounts;
|
private Cache<String, Integer> cachedDiscounts;
|
||||||
|
|
||||||
|
private int cacheMissCount = 0;
|
||||||
|
|
||||||
public ProductHelperWithEventListener() {
|
public ProductHelperWithEventListener() {
|
||||||
cachedDiscounts = Cache2kBuilder.of(String.class, Integer.class)
|
cachedDiscounts = Cache2kBuilder.of(String.class, Integer.class)
|
||||||
.name("discount-listener")
|
.name("discount-listener")
|
||||||
.eternal(false)
|
|
||||||
.expireAfterWrite(10, TimeUnit.MILLISECONDS)
|
.expireAfterWrite(10, TimeUnit.MILLISECONDS)
|
||||||
.entryCapacity(100)
|
.entryCapacity(100)
|
||||||
.loader((key) -> {
|
.loader((key) -> {
|
||||||
LOGGER.info("Calculating discount for {}.", key);
|
cacheMissCount++;
|
||||||
return "Sports".equalsIgnoreCase(key) ? 20 : 10;
|
return "Sports".equalsIgnoreCase(key) ? 20 : 10;
|
||||||
})
|
})
|
||||||
.addListener(new CacheEntryCreatedListener<String, Integer>() {
|
.addListener(new CacheEntryCreatedListener<String, Integer>() {
|
||||||
|
@ -36,14 +36,11 @@ public class ProductHelperWithEventListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getDiscount(String productType) {
|
public Integer getDiscount(String productType) {
|
||||||
Integer discount = cachedDiscounts.get(productType);
|
return cachedDiscounts.get(productType);
|
||||||
if (Objects.isNull(discount)) {
|
}
|
||||||
LOGGER.info("Discount for {} not found.", productType);
|
|
||||||
discount = 0;
|
public int getCacheMissCount() {
|
||||||
} else {
|
return cacheMissCount;
|
||||||
LOGGER.info("Discount for {} found.", productType);
|
|
||||||
}
|
|
||||||
return discount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,39 +5,34 @@ import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.cache2k.Cache;
|
import org.cache2k.Cache;
|
||||||
import org.cache2k.Cache2kBuilder;
|
import org.cache2k.Cache2kBuilder;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
public class ProductHelperWithExpiry {
|
public class ProductHelperWithExpiry {
|
||||||
|
|
||||||
final Logger LOGGER = LoggerFactory.getLogger(ProductHelperWithExpiry.class);
|
|
||||||
|
|
||||||
private Cache<String, Integer> cachedDiscounts;
|
private Cache<String, Integer> cachedDiscounts;
|
||||||
|
|
||||||
|
private int cacheMissCount = 0;
|
||||||
|
|
||||||
public ProductHelperWithExpiry() {
|
public ProductHelperWithExpiry() {
|
||||||
cachedDiscounts = Cache2kBuilder.of(String.class, Integer.class)
|
cachedDiscounts = Cache2kBuilder.of(String.class, Integer.class)
|
||||||
.name("discount-expiry")
|
.name("discount-expiry")
|
||||||
.eternal(false)
|
|
||||||
.expireAfterWrite(5, TimeUnit.MILLISECONDS)
|
.expireAfterWrite(5, TimeUnit.MILLISECONDS)
|
||||||
.entryCapacity(100)
|
.entryCapacity(100)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
initDiscountCache("Sports", 20);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void initDiscountCache(String productType, Integer value) {
|
|
||||||
cachedDiscounts.put(productType, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getDiscount(String productType) {
|
public Integer getDiscount(String productType) {
|
||||||
Integer discount = cachedDiscounts.get(productType);
|
Integer discount = cachedDiscounts.get(productType);
|
||||||
if (Objects.isNull(discount)) {
|
if (Objects.isNull(discount)) {
|
||||||
LOGGER.info("Discount for {} not found.", productType);
|
cacheMissCount++;
|
||||||
discount = 0;
|
discount = "Sports".equalsIgnoreCase(productType) ? 20 : 10;
|
||||||
} else {
|
cachedDiscounts.put(productType, discount);
|
||||||
LOGGER.info("Discount for {} found.", productType);
|
|
||||||
}
|
}
|
||||||
return discount;
|
return discount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getCacheMissCount() {
|
||||||
|
return cacheMissCount;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,112 @@
|
||||||
|
package com.baeldung.jcabi;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import com.jcabi.aspects.Async;
|
||||||
|
import com.jcabi.aspects.Cacheable;
|
||||||
|
import com.jcabi.aspects.LogExceptions;
|
||||||
|
import com.jcabi.aspects.Loggable;
|
||||||
|
import com.jcabi.aspects.Quietly;
|
||||||
|
import com.jcabi.aspects.RetryOnFailure;
|
||||||
|
import com.jcabi.aspects.UnitedThrow;
|
||||||
|
|
||||||
|
public class JcabiAspectJ {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
try {
|
||||||
|
displayFactorial(10);
|
||||||
|
getFactorial(10).get();
|
||||||
|
|
||||||
|
String result = cacheExchangeRates();
|
||||||
|
if (result != cacheExchangeRates()) {
|
||||||
|
System.out.println(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
divideByZero();
|
||||||
|
} catch(Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
divideByZeroQuietly();
|
||||||
|
try {
|
||||||
|
processFile();
|
||||||
|
} catch(Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Loggable
|
||||||
|
@Async
|
||||||
|
public static void displayFactorial(int number) {
|
||||||
|
long result = factorial(number);
|
||||||
|
System.out.println(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Loggable
|
||||||
|
@Async
|
||||||
|
public static Future<Long> getFactorial(int number) {
|
||||||
|
Future<Long> factorialFuture = CompletableFuture.supplyAsync(() -> factorial(number));
|
||||||
|
return factorialFuture;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds factorial of a number
|
||||||
|
* @param number
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static long factorial(int number) {
|
||||||
|
long result = 1;
|
||||||
|
for(int i=number;i>0;i--) {
|
||||||
|
result *= i;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Loggable
|
||||||
|
@Cacheable(lifetime = 2, unit = TimeUnit.SECONDS)
|
||||||
|
public static String cacheExchangeRates() {
|
||||||
|
String result = null;
|
||||||
|
try {
|
||||||
|
URL exchangeRateUrl = new URL("https://api.exchangeratesapi.io/latest");
|
||||||
|
URLConnection con = exchangeRateUrl.openConnection();
|
||||||
|
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
|
||||||
|
result = in.readLine();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogExceptions
|
||||||
|
public static void divideByZero() {
|
||||||
|
int x = 1/0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RetryOnFailure(attempts = 2, types = {java.lang.NumberFormatException.class})
|
||||||
|
@Quietly
|
||||||
|
public static void divideByZeroQuietly() {
|
||||||
|
int x = 1/0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@UnitedThrow(IllegalStateException.class)
|
||||||
|
public static void processFile() throws IOException, InterruptedException {
|
||||||
|
BufferedReader reader = new BufferedReader(new FileReader("baeldung.txt"));
|
||||||
|
reader.readLine();
|
||||||
|
|
||||||
|
Thread thread = new Thread();
|
||||||
|
thread.wait(2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -6,12 +6,13 @@ import org.junit.Test;
|
||||||
|
|
||||||
public class ProductHelperUnitTest {
|
public class ProductHelperUnitTest {
|
||||||
|
|
||||||
ProductHelper productHelper = new ProductHelper();
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void whenInvokedGetDiscount_thenGetItFromCache() {
|
public void whenInvokedGetDiscountTwice_thenGetItFromCache() {
|
||||||
|
ProductHelper productHelper = new ProductHelper();
|
||||||
|
assertTrue(productHelper.getCacheMissCount() == 0);
|
||||||
assertTrue(productHelper.getDiscount("Sports") == 20);
|
assertTrue(productHelper.getDiscount("Sports") == 20);
|
||||||
assertTrue(productHelper.getDiscount("Electronics") == 0);
|
assertTrue(productHelper.getDiscount("Sports") == 20);
|
||||||
|
assertTrue(productHelper.getCacheMissCount() == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,16 @@ import org.junit.Test;
|
||||||
|
|
||||||
public class ProductHelperUsingLoaderUnitTest {
|
public class ProductHelperUsingLoaderUnitTest {
|
||||||
|
|
||||||
ProductHelperUsingLoader productHelper = new ProductHelperUsingLoader();
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void whenInvokedGetDiscount_thenPopulateCache() {
|
public void whenInvokedGetDiscount_thenPopulateCacheUsingLoader() {
|
||||||
|
ProductHelperUsingLoader productHelper = new ProductHelperUsingLoader();
|
||||||
|
assertTrue(productHelper.getCacheMissCount() == 0);
|
||||||
|
|
||||||
assertTrue(productHelper.getDiscount("Sports") == 20);
|
assertTrue(productHelper.getDiscount("Sports") == 20);
|
||||||
|
assertTrue(productHelper.getCacheMissCount() == 1);
|
||||||
|
|
||||||
assertTrue(productHelper.getDiscount("Electronics") == 10);
|
assertTrue(productHelper.getDiscount("Electronics") == 10);
|
||||||
|
assertTrue(productHelper.getCacheMissCount() == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,9 @@ import org.junit.Test;
|
||||||
|
|
||||||
public class ProductHelperWithEventListenerUnitTest {
|
public class ProductHelperWithEventListenerUnitTest {
|
||||||
|
|
||||||
ProductHelperWithEventListener productHelper = new ProductHelperWithEventListener();
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void whenEntryAddedInCache_thenEventListenerCalled() {
|
public void whenEntryAddedInCache_thenEventListenerCalled() {
|
||||||
|
ProductHelperWithEventListener productHelper = new ProductHelperWithEventListener();
|
||||||
assertTrue(productHelper.getDiscount("Sports") == 20);
|
assertTrue(productHelper.getDiscount("Sports") == 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,13 +6,17 @@ import org.junit.Test;
|
||||||
|
|
||||||
public class ProductHelperWithExpiryUnitTest {
|
public class ProductHelperWithExpiryUnitTest {
|
||||||
|
|
||||||
ProductHelperWithExpiry productHelper = new ProductHelperWithExpiry();
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void whenInvokedGetDiscountForExpiredProduct_thenNoDiscount() throws InterruptedException {
|
public void whenInvokedGetDiscountAfterExpiration_thenDiscountCalculatedAgain() throws InterruptedException {
|
||||||
|
ProductHelperWithExpiry productHelper = new ProductHelperWithExpiry();
|
||||||
|
assertTrue(productHelper.getCacheMissCount() == 0);
|
||||||
assertTrue(productHelper.getDiscount("Sports") == 20);
|
assertTrue(productHelper.getDiscount("Sports") == 20);
|
||||||
|
assertTrue(productHelper.getCacheMissCount() == 1);
|
||||||
|
|
||||||
Thread.sleep(20);
|
Thread.sleep(20);
|
||||||
assertTrue(productHelper.getDiscount("Sports") == 0);
|
|
||||||
|
assertTrue(productHelper.getDiscount("Sports") == 20);
|
||||||
|
assertTrue(productHelper.getCacheMissCount() == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
package com.baeldung.moshi;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import com.squareup.moshi.FromJson;
|
||||||
|
import com.squareup.moshi.JsonAdapter;
|
||||||
|
import com.squareup.moshi.JsonQualifier;
|
||||||
|
import com.squareup.moshi.Moshi;
|
||||||
|
import com.squareup.moshi.ToJson;
|
||||||
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class AlternativeAdapterUnitTest {
|
||||||
|
@Test
|
||||||
|
public void whenSerializing_thenAlternativeAdapterUsed() {
|
||||||
|
Moshi moshi = new Moshi.Builder()
|
||||||
|
.add(new EpochMillisAdapter())
|
||||||
|
.build();
|
||||||
|
JsonAdapter<Post> jsonAdapter = moshi.adapter(Post.class);
|
||||||
|
|
||||||
|
String json = jsonAdapter.toJson(new Post("Introduction to Moshi Json", "Baeldung", Instant.now()));
|
||||||
|
System.out.println(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenDeserializing_thenAlternativeAdapterUsed() throws IOException {
|
||||||
|
Moshi moshi = new Moshi.Builder()
|
||||||
|
.add(new EpochMillisAdapter())
|
||||||
|
.build();
|
||||||
|
JsonAdapter<Post> jsonAdapter = moshi.adapter(Post.class);
|
||||||
|
|
||||||
|
String json = "{\"author\":\"Baeldung\",\"posted\":1582095269204,\"title\":\"Introduction to Moshi Json\"}";
|
||||||
|
Post post = jsonAdapter.fromJson(json);
|
||||||
|
System.out.println(post);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Post {
|
||||||
|
String title;
|
||||||
|
String author;
|
||||||
|
@EpochMillis Instant posted;
|
||||||
|
|
||||||
|
public Post() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Post(String title, String author, Instant posted) {
|
||||||
|
this.title = title;
|
||||||
|
this.author = author;
|
||||||
|
this.posted = posted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthor(String author) {
|
||||||
|
this.author = author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getPosted() {
|
||||||
|
return posted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPosted(Instant posted) {
|
||||||
|
this.posted = posted;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return new ToStringBuilder(this).append("title", title).append("author", author).append("posted", posted)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
|
||||||
|
@JsonQualifier
|
||||||
|
public @interface EpochMillis {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class EpochMillisAdapter {
|
||||||
|
@ToJson
|
||||||
|
public Long toJson(@EpochMillis Instant input) {
|
||||||
|
return input.toEpochMilli();
|
||||||
|
}
|
||||||
|
@FromJson
|
||||||
|
@EpochMillis
|
||||||
|
public Instant fromJson(Long input) {
|
||||||
|
return Instant.ofEpochMilli(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package com.baeldung.moshi;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonAdapter;
|
||||||
|
import com.squareup.moshi.Moshi;
|
||||||
|
import com.squareup.moshi.Types;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class ArrayUnitTest {
|
||||||
|
@Test
|
||||||
|
public void whenSerializingList_thenJsonArrayProduced() {
|
||||||
|
Moshi moshi = new Moshi.Builder()
|
||||||
|
.build();
|
||||||
|
Type type = Types.newParameterizedType(List.class, String.class);
|
||||||
|
JsonAdapter<List<String>> jsonAdapter = moshi.adapter(type);
|
||||||
|
|
||||||
|
String json = jsonAdapter.toJson(Arrays.asList("One", "Two", "Three"));
|
||||||
|
System.out.println(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenDeserializingJsonArray_thenListProduced() throws IOException {
|
||||||
|
Moshi moshi = new Moshi.Builder()
|
||||||
|
.build();
|
||||||
|
Type type = Types.newParameterizedType(List.class, String.class);
|
||||||
|
JsonAdapter<List<String>> jsonAdapter = moshi.adapter(type);
|
||||||
|
|
||||||
|
String json = "[\"One\",\"Two\",\"Three\"]";
|
||||||
|
List<String> result = jsonAdapter.fromJson(json);
|
||||||
|
System.out.println(result);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
package com.baeldung.moshi;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
|
||||||
|
import com.squareup.moshi.FromJson;
|
||||||
|
import com.squareup.moshi.JsonAdapter;
|
||||||
|
import com.squareup.moshi.Moshi;
|
||||||
|
import com.squareup.moshi.ToJson;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class ComplexAdapterUnitTest {
|
||||||
|
@Test
|
||||||
|
public void whenSerializing_thenCorrectJsonProduced() {
|
||||||
|
Moshi moshi = new Moshi.Builder()
|
||||||
|
.add(new JsonDateTimeAdapter())
|
||||||
|
.build();
|
||||||
|
JsonAdapter<ZonedDateTime> jsonAdapter = moshi.adapter(ZonedDateTime.class);
|
||||||
|
|
||||||
|
String json = jsonAdapter.toJson(ZonedDateTime.now());
|
||||||
|
System.out.println(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenDeserializing_thenCorrectJsonConsumed() throws IOException {
|
||||||
|
Moshi moshi = new Moshi.Builder()
|
||||||
|
.add(new JsonDateTimeAdapter())
|
||||||
|
.build();
|
||||||
|
JsonAdapter<ZonedDateTime> jsonAdapter = moshi.adapter(ZonedDateTime.class);
|
||||||
|
|
||||||
|
String json = "{\"date\":\"2020-02-17\",\"time\":\"07:53:27.064\",\"timezone\":\"Europe/London\"}";
|
||||||
|
ZonedDateTime now = jsonAdapter.fromJson(json);
|
||||||
|
System.out.println(now);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class JsonDateTimeAdapter {
|
||||||
|
@ToJson
|
||||||
|
public JsonDateTime toJson(ZonedDateTime input) {
|
||||||
|
String date = input.toLocalDate().toString();
|
||||||
|
String time = input.toLocalTime().toString();
|
||||||
|
String timezone = input.getZone().toString();
|
||||||
|
return new JsonDateTime(date, time, timezone);
|
||||||
|
}
|
||||||
|
@FromJson
|
||||||
|
public ZonedDateTime fromJson(JsonDateTime input) {
|
||||||
|
LocalDate date = LocalDate.parse(input.getDate());
|
||||||
|
LocalTime time = LocalTime.parse(input.getTime());
|
||||||
|
ZoneId timezone = ZoneId.of(input.getTimezone());
|
||||||
|
return ZonedDateTime.of(date, time, timezone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static class JsonDateTime {
|
||||||
|
private String date;
|
||||||
|
private String time;
|
||||||
|
private String timezone;
|
||||||
|
|
||||||
|
public JsonDateTime() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonDateTime(String date, String time, String timezone) {
|
||||||
|
this.date = date;
|
||||||
|
this.time = time;
|
||||||
|
this.timezone = timezone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDate() {
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDate(String date) {
|
||||||
|
this.date = date;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTime() {
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTime(String time) {
|
||||||
|
this.time = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTimezone() {
|
||||||
|
return timezone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimezone(String timezone) {
|
||||||
|
this.timezone = timezone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package com.baeldung.moshi;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonAdapter;
|
||||||
|
import com.squareup.moshi.Moshi;
|
||||||
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class DefaultUnitTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenDeserializing_thenFieldsGetDefaultValues() throws IOException {
|
||||||
|
Moshi moshi = new Moshi.Builder()
|
||||||
|
.build();
|
||||||
|
JsonAdapter<Post> jsonAdapter = moshi.adapter(Post.class);
|
||||||
|
|
||||||
|
String json = "{\"title\":\"My Post\"}";
|
||||||
|
Post post = jsonAdapter.fromJson(json);
|
||||||
|
System.out.println(post);
|
||||||
|
}
|
||||||
|
public static class Post {
|
||||||
|
private String title;
|
||||||
|
private String author;
|
||||||
|
private String posted;
|
||||||
|
|
||||||
|
public Post() {
|
||||||
|
posted = Instant.now().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Post(String title, String author, String posted) {
|
||||||
|
this.title = title;
|
||||||
|
this.author = author;
|
||||||
|
this.posted = posted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthor(String author) {
|
||||||
|
this.author = author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPosted() {
|
||||||
|
return posted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPosted(String posted) {
|
||||||
|
this.posted = posted;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return new ToStringBuilder(this).append("title", title).append("author", author).append("posted", posted)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
package com.baeldung.moshi;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonAdapter;
|
||||||
|
import com.squareup.moshi.Moshi;
|
||||||
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class PrimitiveUnitTest {
|
||||||
|
@Test
|
||||||
|
public void whenSerializing_thenCorrectJsonProduced() {
|
||||||
|
Moshi moshi = new Moshi.Builder()
|
||||||
|
.build();
|
||||||
|
JsonAdapter<Post> jsonAdapter = moshi.adapter(Post.class);
|
||||||
|
|
||||||
|
Post post = new Post("My Post", "Baeldung", "This is my post");
|
||||||
|
String json = jsonAdapter.toJson(post);
|
||||||
|
System.out.println(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenDeserializing_thenCorrectJsonConsumed() throws IOException {
|
||||||
|
Moshi moshi = new Moshi.Builder()
|
||||||
|
.build();
|
||||||
|
JsonAdapter<Post> jsonAdapter = moshi.adapter(Post.class);
|
||||||
|
|
||||||
|
String json = "{\"author\":\"Baeldung\",\"text\":\"This is my post\",\"title\":\"My Post\"}";
|
||||||
|
Post post = jsonAdapter.fromJson(json);
|
||||||
|
System.out.println(post);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Post {
|
||||||
|
private String title;
|
||||||
|
private String author;
|
||||||
|
private String text;
|
||||||
|
|
||||||
|
public Post() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Post(String title, String author, String text) {
|
||||||
|
this.title = title;
|
||||||
|
this.author = author;
|
||||||
|
this.text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthor(String author) {
|
||||||
|
this.author = author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getText() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setText(String text) {
|
||||||
|
this.text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return new ToStringBuilder(this).append("title", title).append("author", author).append("text", text)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package com.baeldung.moshi;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json;
|
||||||
|
import com.squareup.moshi.JsonAdapter;
|
||||||
|
import com.squareup.moshi.Moshi;
|
||||||
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class RenameUnitTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenSerializing_thenFieldsGetRenamed() {
|
||||||
|
Moshi moshi = new Moshi.Builder()
|
||||||
|
.build();
|
||||||
|
JsonAdapter<Post> jsonAdapter = moshi.adapter(Post.class);
|
||||||
|
|
||||||
|
Post post = new Post("My Post", "Baeldung");
|
||||||
|
String json = jsonAdapter.toJson(post);
|
||||||
|
System.out.println(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenSerializing_thenRenamedFieldsGetConsumed() throws IOException {
|
||||||
|
Moshi moshi = new Moshi.Builder()
|
||||||
|
.build();
|
||||||
|
JsonAdapter<Post> jsonAdapter = moshi.adapter(Post.class);
|
||||||
|
|
||||||
|
String json = "{\"authored_by\":\"Baeldung\",\"title\":\"My Post\"}";
|
||||||
|
Post post = jsonAdapter.fromJson(json);
|
||||||
|
System.out.println(post);
|
||||||
|
}
|
||||||
|
public static class Post {
|
||||||
|
private String title;
|
||||||
|
@Json(name = "authored_by")
|
||||||
|
private String author;
|
||||||
|
|
||||||
|
public Post() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Post(String title, String author) {
|
||||||
|
this.title = title;
|
||||||
|
this.author = author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthor(String author) {
|
||||||
|
this.author = author;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return new ToStringBuilder(this).append("title", title).append("author", author).toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
package com.baeldung.moshi;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import com.squareup.moshi.FromJson;
|
||||||
|
import com.squareup.moshi.JsonAdapter;
|
||||||
|
import com.squareup.moshi.Moshi;
|
||||||
|
import com.squareup.moshi.ToJson;
|
||||||
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class SimpleAdapterUnitTest {
|
||||||
|
@Test
|
||||||
|
public void whenSerializing_thenAdapterUsed() {
|
||||||
|
Moshi moshi = new Moshi.Builder()
|
||||||
|
.add(new AuthorAdapter())
|
||||||
|
.build();
|
||||||
|
JsonAdapter<Post> jsonAdapter = moshi.adapter(Post.class);
|
||||||
|
|
||||||
|
Post post = new Post("My Post", new Author("Baeldung", "baeldung@example.com"), "This is my post");
|
||||||
|
String json = jsonAdapter.toJson(post);
|
||||||
|
System.out.println(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenDeserializing_thenAdapterUsed() throws IOException {
|
||||||
|
Moshi moshi = new Moshi.Builder()
|
||||||
|
.add(new AuthorAdapter())
|
||||||
|
.build();
|
||||||
|
JsonAdapter<Post> jsonAdapter = moshi.adapter(Post.class);
|
||||||
|
|
||||||
|
String json = "{\"author\":\"Baeldung <baeldung@example.com>\",\"text\":\"This is my post\",\"title\":\"My Post\"}";
|
||||||
|
Post post = jsonAdapter.fromJson(json);
|
||||||
|
System.out.println(post);
|
||||||
|
}
|
||||||
|
public static class AuthorAdapter {
|
||||||
|
private Pattern pattern = Pattern.compile("^(.*) <(.*)>$");
|
||||||
|
@ToJson
|
||||||
|
public String toJson(Author author) {
|
||||||
|
return author.name + " <" + author.email + ">";
|
||||||
|
}
|
||||||
|
|
||||||
|
@FromJson
|
||||||
|
public Author fromJson(String author) {
|
||||||
|
Matcher matcher = pattern.matcher(author);
|
||||||
|
return matcher.find() ? new Author(matcher.group(1), matcher.group(2)) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Author {
|
||||||
|
private String name;
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
public Author() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Author(String name, String email) {
|
||||||
|
this.name = name;
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return new ToStringBuilder(this).append("name", name).append("email", email).toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static class Post {
|
||||||
|
private String title;
|
||||||
|
private Author author;
|
||||||
|
private String text;
|
||||||
|
|
||||||
|
public Post() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Post(String title, Author author, String text) {
|
||||||
|
this.title = title;
|
||||||
|
this.author = author;
|
||||||
|
this.text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Author getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthor(Author author) {
|
||||||
|
this.author = author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getText() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setText(String text) {
|
||||||
|
this.text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return new ToStringBuilder(this).append("title", title).append("author", author).append("text", text)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package com.baeldung.moshi;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonAdapter;
|
||||||
|
import com.squareup.moshi.Moshi;
|
||||||
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class TransientUnitTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenSerializing_thenTransientFieldIgnored() {
|
||||||
|
Moshi moshi = new Moshi.Builder()
|
||||||
|
.build();
|
||||||
|
JsonAdapter<Post> jsonAdapter = moshi.adapter(Post.class);
|
||||||
|
|
||||||
|
Post post = new Post("My Post", "Baeldung");
|
||||||
|
String json = jsonAdapter.toJson(post);
|
||||||
|
System.out.println(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenDeserializing_thenTransientFieldIgnored() throws IOException {
|
||||||
|
Moshi moshi = new Moshi.Builder()
|
||||||
|
.build();
|
||||||
|
JsonAdapter<Post> jsonAdapter = moshi.adapter(Post.class);
|
||||||
|
|
||||||
|
String json = "{\"authored_by\":\"Baeldung\",\"title\":\"My Post\"}";
|
||||||
|
Post post = jsonAdapter.fromJson(json);
|
||||||
|
System.out.println(post);
|
||||||
|
}
|
||||||
|
public static class Post {
|
||||||
|
private String title;
|
||||||
|
private transient String author;
|
||||||
|
|
||||||
|
public Post() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Post(String title, String author) {
|
||||||
|
this.title = title;
|
||||||
|
this.author = author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthor(String author) {
|
||||||
|
this.author = author;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return new ToStringBuilder(this).append("title", title).append("author", author).toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -36,12 +36,12 @@
|
||||||
|
|
||||||
<profiles>
|
<profiles>
|
||||||
<profile>
|
<profile>
|
||||||
<id>default-tools.jar</id>
|
<id>default-profile</id>
|
||||||
<activation>
|
<activation>
|
||||||
<property>
|
<activeByDefault>true</activeByDefault>
|
||||||
<name>java.vendor</name>
|
<file>
|
||||||
<value>Oracle Corporation</value>
|
<exists>${java.home}/../lib/tools.jar</exists>
|
||||||
</property>
|
</file>
|
||||||
</activation>
|
</activation>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@ -53,6 +53,24 @@
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</profile>
|
</profile>
|
||||||
|
<profile>
|
||||||
|
<id>mac-profile</id>
|
||||||
|
<activation>
|
||||||
|
<activeByDefault>false</activeByDefault>
|
||||||
|
<file>
|
||||||
|
<exists>${java.home}/../Classes/classes.jar</exists>
|
||||||
|
</file>
|
||||||
|
</activation>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.sun</groupId>
|
||||||
|
<artifactId>tools</artifactId>
|
||||||
|
<version>${java.version}</version>
|
||||||
|
<scope>system</scope>
|
||||||
|
<systemPath>${java.home}/../Classes/classes.jar</systemPath>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</profile>
|
||||||
</profiles>
|
</profiles>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
package controllers;
|
||||||
|
|
||||||
|
import play.data.Form;
|
||||||
|
import play.data.FormFactory;
|
||||||
|
import play.mvc.Controller;
|
||||||
|
import play.mvc.Result;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
// Add the following to conf/routes
|
||||||
|
/*
|
||||||
|
GET /$model;format="camel"$ controllers.$model;format="Camel"$Controller.$model;format="camel"$Get
|
||||||
|
POST /$model;format="camel"$ controllers.$model;format="Camel"$Controller.$model;format="camel"$Post
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* $model;format="Camel"$ form controller for Play Java
|
||||||
|
*/
|
||||||
|
public class $model;format="Camel"$Controller extends Controller {
|
||||||
|
|
||||||
|
private final Form<$model;format="Camel"$Data> $model;format="camel"$Form;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public $model;format="Camel"$Controller(FormFactory formFactory) {
|
||||||
|
this.$model;format="camel"$Form = formFactory.form($model;format="Camel"$Data.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result $model;format="camel"$Get() {
|
||||||
|
return ok(views.html.$model;format="camel"$.form.render($model;format="camel"$Form));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result $model;format="camel"$Post() {
|
||||||
|
Form<$model;format="Camel"$Data> boundForm = $model;format="camel"$Form.bindFromRequest();
|
||||||
|
if (boundForm.hasErrors()) {
|
||||||
|
return badRequest(views.html.$model;format="camel"$.form.render(boundForm));
|
||||||
|
} else {
|
||||||
|
$model;format="Camel"$Data $model;format="camel"$ = boundForm.get();
|
||||||
|
flash("success", "$model;format="Camel"$ " + $model;format="camel"$);
|
||||||
|
return redirect(routes.$model;format="Camel"$Controller.$model;format="camel"$Get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package controllers;
|
||||||
|
|
||||||
|
import play.data.validation.Constraints;
|
||||||
|
|
||||||
|
public class $model;format="Camel"$Data {
|
||||||
|
|
||||||
|
@Constraints.Required
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Constraints.Required
|
||||||
|
private Integer age;
|
||||||
|
|
||||||
|
public $model;format="Camel"$Data() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getAge() {
|
||||||
|
return age;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAge(Integer age) {
|
||||||
|
this.age = age;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("$model;format="Camel"$Data(%s, %s)", name, age);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
@($model;format="camel"$Form: Form[$model;format="Camel"$Data])
|
||||||
|
|
||||||
|
<h1>$model;format="camel"$ form</h1>
|
||||||
|
|
||||||
|
@flash.getOrDefault("success", "")
|
||||||
|
|
||||||
|
@helper.form(action = routes.$model;format="Camel"$Controller.$model;format="camel"$Post()) {
|
||||||
|
@helper.CSRF.formField
|
||||||
|
@helper.inputText($model;format="camel"$Form("name"))
|
||||||
|
@helper.inputText($model;format="camel"$Form("age"))
|
||||||
|
<input type="submit" value="submit"/>
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
description = Generates a Controller with form handling
|
||||||
|
model = user
|
|
@ -0,0 +1 @@
|
||||||
|
Temporary file until g8-scaffold will generate "test" directory
|
|
@ -0,0 +1,50 @@
|
||||||
|
package controllers;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import play.Application;
|
||||||
|
import play.filters.csrf.*;
|
||||||
|
import play.inject.guice.GuiceApplicationBuilder;
|
||||||
|
import play.mvc.*;
|
||||||
|
import play.test.WithApplication;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static play.mvc.Http.RequestBuilder;
|
||||||
|
import static play.mvc.Http.Status.OK;
|
||||||
|
import static play.test.Helpers.*;
|
||||||
|
import static play.api.test.CSRFTokenHelper.*;
|
||||||
|
|
||||||
|
public class $model;format="Camel"$ControllerTest extends WithApplication {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Application provideApplication() {
|
||||||
|
return new GuiceApplicationBuilder().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test$model;format="Camel"$Get() {
|
||||||
|
RequestBuilder request = new RequestBuilder()
|
||||||
|
.method(GET)
|
||||||
|
.uri("/$model;format="camel"$");
|
||||||
|
|
||||||
|
Result result = route(app, request);
|
||||||
|
assertEquals(OK, result.status());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test$model;format="Camel"$Post() {
|
||||||
|
HashMap<String, String> formData = new HashMap<>();
|
||||||
|
formData.put("name", "play");
|
||||||
|
formData.put("age", "4");
|
||||||
|
RequestBuilder request = addCSRFToken(new RequestBuilder()
|
||||||
|
.header(Http.HeaderNames.HOST, "localhost")
|
||||||
|
.method(POST)
|
||||||
|
.bodyForm(formData)
|
||||||
|
.uri("/$model;format="camel"$"));
|
||||||
|
|
||||||
|
Result result = route(app, request);
|
||||||
|
assertEquals(SEE_OTHER, result.status());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
logs
|
||||||
|
target
|
||||||
|
/.idea
|
||||||
|
/.idea_modules
|
||||||
|
/.classpath
|
||||||
|
/.project
|
||||||
|
/.settings
|
||||||
|
/RUNNING_PID
|
|
@ -0,0 +1,38 @@
|
||||||
|
package controllers;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import play.mvc.Controller;
|
||||||
|
import play.mvc.Http;
|
||||||
|
import play.mvc.Result;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This controller contains an action to handle HTTP requests to the application's home page.
|
||||||
|
*/
|
||||||
|
public class HomeController extends Controller {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An action that renders an HTML page with a welcome message. The configuration in the
|
||||||
|
* <code>routes</code> file means that this method will be called when the application receives
|
||||||
|
* a
|
||||||
|
* <code>GET</code> request with a path of <code>/</code>.
|
||||||
|
*/
|
||||||
|
public Result index(Http.Request request) throws JsonProcessingException {
|
||||||
|
return ok(printStats(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String printStats(Http.Request request) throws JsonProcessingException {
|
||||||
|
Map<String, String[]> stringMap = request.body()
|
||||||
|
.asFormUrlEncoded();
|
||||||
|
Map<String, Object> map = ImmutableMap.of(
|
||||||
|
"Result", "ok",
|
||||||
|
"GetParams", request.queryString(),
|
||||||
|
"PostParams", stringMap == null ? Collections.emptyMap() : stringMap,
|
||||||
|
"Headers", request.getHeaders().toMap()
|
||||||
|
);
|
||||||
|
return new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(map);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
@()
|
||||||
|
|
||||||
|
@main("Welcome to Play") {
|
||||||
|
<h1>Welcome to Play!</h1>
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
@*
|
||||||
|
* This template is called from the `index` template. This template
|
||||||
|
* handles the rendering of the page header and body tags. It takes
|
||||||
|
* two arguments, a `String` for the title of the page and an `Html`
|
||||||
|
* object to insert into the body of the page.
|
||||||
|
*@
|
||||||
|
@(title: String)(content: Html)
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
@* Here's where we render the page title `String`. *@
|
||||||
|
<title>@title</title>
|
||||||
|
<link rel="stylesheet" media="screen" href="@routes.Assets.versioned("stylesheets/main.css")">
|
||||||
|
<link rel="shortcut icon" type="image/png" href="@routes.Assets.versioned("images/favicon.png")">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
@* And here's where we render the `Html` object containing
|
||||||
|
* the page content. *@
|
||||||
|
@content
|
||||||
|
|
||||||
|
<script src="@routes.Assets.versioned("javascripts/main.js")" type="text/javascript"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,12 @@
|
||||||
|
name := """async"""
|
||||||
|
organization := "com.example"
|
||||||
|
|
||||||
|
version := "1.0-SNAPSHOT"
|
||||||
|
|
||||||
|
lazy val root = (project in file(".")).enablePlugins(PlayJava)
|
||||||
|
|
||||||
|
scalaVersion := "2.13.1"
|
||||||
|
|
||||||
|
// comment out the original line
|
||||||
|
libraryDependencies += guice
|
||||||
|
libraryDependencies += javaWs
|
|
@ -0,0 +1,11 @@
|
||||||
|
# This is the main configuration file for the application.
|
||||||
|
# https://www.playframework.com/documentation/latest/ConfigFile
|
||||||
|
play.ws.followRedirects=false
|
||||||
|
play.ws.useragent=MyPlayApplication
|
||||||
|
play.ws.compressionEnabled=true
|
||||||
|
# time to wait for the connection to be established
|
||||||
|
play.ws.timeout.connection=30.seconds
|
||||||
|
# time to wait for data after the connection is open
|
||||||
|
play.ws.timeout.idle=30.seconds
|
||||||
|
# max time available to complete the request
|
||||||
|
play.ws.timeout.request=300.seconds
|
|
@ -0,0 +1,36 @@
|
||||||
|
<!-- https://www.playframework.com/documentation/latest/SettingsLogger -->
|
||||||
|
<configuration>
|
||||||
|
|
||||||
|
<conversionRule conversionWord="coloredLevel" converterClass="play.api.libs.logback.ColoredLevel" />
|
||||||
|
|
||||||
|
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
|
||||||
|
<file>${application.home:-.}/logs/application.log</file>
|
||||||
|
<encoder>
|
||||||
|
<pattern>%date [%level] from %logger in %thread - %message%n%xException</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%coloredLevel %logger{15} - %message%n%xException{10}</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name="ASYNCFILE" class="ch.qos.logback.classic.AsyncAppender">
|
||||||
|
<appender-ref ref="FILE" />
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name="ASYNCSTDOUT" class="ch.qos.logback.classic.AsyncAppender">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<logger name="play" level="INFO" />
|
||||||
|
<logger name="application" level="DEBUG" />
|
||||||
|
<logger name="controllers" level="DEBUG" />
|
||||||
|
|
||||||
|
<root level="WARN">
|
||||||
|
<appender-ref ref="ASYNCFILE" />
|
||||||
|
<appender-ref ref="ASYNCSTDOUT" />
|
||||||
|
</root>
|
||||||
|
|
||||||
|
</configuration>
|
|
@ -0,0 +1,10 @@
|
||||||
|
# Routes
|
||||||
|
# This file defines all application routes (Higher priority routes first)
|
||||||
|
# ~~~~
|
||||||
|
|
||||||
|
# An example controller showing a sample home page
|
||||||
|
GET / controllers.HomeController.index(request: Request)
|
||||||
|
POST / controllers.HomeController.index(request: Request)
|
||||||
|
|
||||||
|
# Map static resources from the /public folder to the /assets URL path
|
||||||
|
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
|
|
@ -0,0 +1 @@
|
||||||
|
sbt.version=1.3.3
|
|
@ -0,0 +1,7 @@
|
||||||
|
// The Play plugin
|
||||||
|
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.7.3")
|
||||||
|
|
||||||
|
// Defines scaffolding (found under .g8 folder)
|
||||||
|
// http://www.foundweekends.org/giter8/scaffolding.html
|
||||||
|
// sbt "g8Scaffold form"
|
||||||
|
addSbtPlugin("org.foundweekends.giter8" % "sbt-giter8-scaffold" % "0.11.0")
|
Binary file not shown.
After Width: | Height: | Size: 687 B |
|
@ -0,0 +1,232 @@
|
||||||
|
package controllers;
|
||||||
|
|
||||||
|
import static java.time.temporal.ChronoUnit.SECONDS;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static play.mvc.Http.Status.SERVICE_UNAVAILABLE;
|
||||||
|
|
||||||
|
import akka.Done;
|
||||||
|
import akka.actor.ActorSystem;
|
||||||
|
import akka.stream.ActorMaterializer;
|
||||||
|
import akka.stream.javadsl.Sink;
|
||||||
|
import akka.util.ByteString;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.OptionalInt;
|
||||||
|
import java.util.concurrent.CompletionStage;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
import org.apache.http.HttpStatus;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import play.Application;
|
||||||
|
import play.inject.guice.GuiceApplicationBuilder;
|
||||||
|
import play.libs.concurrent.Futures;
|
||||||
|
import play.libs.ws.WSClient;
|
||||||
|
import play.libs.ws.WSResponse;
|
||||||
|
import play.libs.ws.ahc.AhcCurlRequestLogger;
|
||||||
|
import play.mvc.Result;
|
||||||
|
import play.mvc.Results;
|
||||||
|
import play.test.WithServer;
|
||||||
|
|
||||||
|
public class HomeControllerTest extends WithServer {
|
||||||
|
|
||||||
|
private final Logger log = LoggerFactory.getLogger(HomeControllerTest.class);
|
||||||
|
private String url;
|
||||||
|
private int port;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Application provideApplication() {
|
||||||
|
return new GuiceApplicationBuilder().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
OptionalInt optHttpsPort = testServer.getRunningHttpsPort();
|
||||||
|
if (optHttpsPort.isPresent()) {
|
||||||
|
port = optHttpsPort.getAsInt();
|
||||||
|
url = "https://localhost:" + port;
|
||||||
|
} else {
|
||||||
|
port = testServer.getRunningHttpPort()
|
||||||
|
.getAsInt();
|
||||||
|
url = "http://localhost:" + port;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenASingleGetRequestWhenResponseThenBlockWithCompletableAndLog()
|
||||||
|
throws Exception {
|
||||||
|
WSClient ws = play.test.WSTestClient.newClient(port);
|
||||||
|
WSResponse wsResponse = ws.url(url)
|
||||||
|
.setRequestFilter(new AhcCurlRequestLogger())
|
||||||
|
.addHeader("key", "value")
|
||||||
|
.addQueryParameter("num", "" + 1)
|
||||||
|
.get()
|
||||||
|
.toCompletableFuture()
|
||||||
|
.get();
|
||||||
|
|
||||||
|
log.debug("Thread#" + Thread.currentThread()
|
||||||
|
.getId() + " Request complete: Response code = "
|
||||||
|
+ wsResponse.getStatus()
|
||||||
|
+ " | Response: " + wsResponse.getBody() + " | Current Time:"
|
||||||
|
+ System.currentTimeMillis());
|
||||||
|
assert (HttpStatus.SC_OK == wsResponse.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenASingleGetRequestWhenResponseThenLog() throws Exception {
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
WSClient ws = play.test.WSTestClient.newClient(port);
|
||||||
|
ws.url(url)
|
||||||
|
.setRequestFilter(new AhcCurlRequestLogger())
|
||||||
|
.addHeader("key", "value")
|
||||||
|
.addQueryParameter("num", "" + 1)
|
||||||
|
.get()
|
||||||
|
.thenAccept(r -> {
|
||||||
|
log.debug("Thread#" + Thread.currentThread()
|
||||||
|
.getId() + " Request complete: Response code = "
|
||||||
|
+ r.getStatus()
|
||||||
|
+ " | Response: " + r.getBody() + " | Current Time:" + System.currentTimeMillis());
|
||||||
|
latch.countDown();
|
||||||
|
});
|
||||||
|
|
||||||
|
log.debug(
|
||||||
|
"Waiting for requests to be completed. Current Time: " + System.currentTimeMillis());
|
||||||
|
latch.await(5, TimeUnit.SECONDS );
|
||||||
|
assertEquals(0, latch.getCount());
|
||||||
|
log.debug("All requests have been completed. Exiting test.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenASinglePostRequestWhenResponseThenLog() throws Exception {
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
WSClient ws = play.test.WSTestClient.newClient(port);
|
||||||
|
ws.url(url)
|
||||||
|
.setContentType("application/x-www-form-urlencoded")
|
||||||
|
.post("key1=value1&key2=value2")
|
||||||
|
.thenAccept(r -> {
|
||||||
|
log.debug("Thread#" + Thread.currentThread()
|
||||||
|
.getId() + " Request complete: Response code = "
|
||||||
|
+ r.getStatus()
|
||||||
|
+ " | Response: " + r.getBody() + " | Current Time:" + System.currentTimeMillis());
|
||||||
|
latch.countDown();
|
||||||
|
});
|
||||||
|
|
||||||
|
log.debug(
|
||||||
|
"Waiting for requests to be completed. Current Time: " + System.currentTimeMillis());
|
||||||
|
latch.await(5, TimeUnit.SECONDS );
|
||||||
|
assertEquals(0, latch.getCount());
|
||||||
|
log.debug("All requests have been completed. Exiting test.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenMultipleRequestsWhenResponseThenLog() throws Exception {
|
||||||
|
CountDownLatch latch = new CountDownLatch(100);
|
||||||
|
WSClient ws = play.test.WSTestClient.newClient(port);
|
||||||
|
IntStream.range(0, 100)
|
||||||
|
.parallel()
|
||||||
|
.forEach(num ->
|
||||||
|
ws.url(url)
|
||||||
|
.setRequestFilter(new AhcCurlRequestLogger())
|
||||||
|
.addHeader("key", "value")
|
||||||
|
.addQueryParameter("num", "" + num)
|
||||||
|
.get()
|
||||||
|
.thenAccept(r -> {
|
||||||
|
log.debug(
|
||||||
|
"Thread#" + num + " Request complete: Response code = " + r.getStatus()
|
||||||
|
+ " | Response: " + r.getBody() + " | Current Time:"
|
||||||
|
+ System.currentTimeMillis());
|
||||||
|
latch.countDown();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
log.debug(
|
||||||
|
"Waiting for requests to be completed. Current Time: " + System.currentTimeMillis());
|
||||||
|
latch.await(5, TimeUnit.SECONDS );
|
||||||
|
assertEquals(0, latch.getCount());
|
||||||
|
log.debug("All requests have been completed. Exiting test.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenLongResponseWhenTimeoutThenHandle() throws Exception {
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
WSClient ws = play.test.WSTestClient.newClient(port);
|
||||||
|
Futures futures = app.injector()
|
||||||
|
.instanceOf(Futures.class);
|
||||||
|
CompletionStage<Result> f = futures.timeout(
|
||||||
|
ws.url(url)
|
||||||
|
.setRequestTimeout(Duration.of(1, SECONDS))
|
||||||
|
.get()
|
||||||
|
.thenApply(result -> {
|
||||||
|
try {
|
||||||
|
Thread.sleep(2000L);
|
||||||
|
return Results.ok();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
return Results.status(
|
||||||
|
SERVICE_UNAVAILABLE);
|
||||||
|
}
|
||||||
|
}), 1L, TimeUnit.SECONDS
|
||||||
|
);
|
||||||
|
CompletionStage<Object> res = f.handleAsync((result, e) -> {
|
||||||
|
if (e != null) {
|
||||||
|
log.error("Exception thrown", e);
|
||||||
|
latch.countDown();
|
||||||
|
return e.getCause();
|
||||||
|
} else {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
res.thenAccept(result -> assertEquals(TimeoutException.class, result));
|
||||||
|
|
||||||
|
log.debug(
|
||||||
|
"Waiting for requests to be completed. Current Time: " + System.currentTimeMillis());
|
||||||
|
latch.await(5, TimeUnit.SECONDS );
|
||||||
|
assertEquals(0, latch.getCount());
|
||||||
|
log.debug("All requests have been completed. Exiting test.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenMultigigabyteResponseConsumeWithStreams() throws Exception {
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
final ActorSystem system = ActorSystem.create();
|
||||||
|
final ActorMaterializer materializer = ActorMaterializer.create(system);
|
||||||
|
final Path path = Files.createTempFile("tmp_", ".out");
|
||||||
|
|
||||||
|
WSClient ws = play.test.WSTestClient.newClient(port);
|
||||||
|
log.info("Starting test server on url: " + url);
|
||||||
|
ws.url(url)
|
||||||
|
.stream()
|
||||||
|
.thenAccept(
|
||||||
|
response -> {
|
||||||
|
try {
|
||||||
|
OutputStream outputStream = java.nio.file.Files.newOutputStream(path);
|
||||||
|
Sink<ByteString, CompletionStage<Done>> outputWriter =
|
||||||
|
Sink.foreach(bytes -> {
|
||||||
|
log.info("Reponse: " + bytes.utf8String());
|
||||||
|
outputStream.write(bytes.toArray());
|
||||||
|
});
|
||||||
|
|
||||||
|
response.getBodyAsSource()
|
||||||
|
.runWith(outputWriter, materializer);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("An error happened while opening the output stream", e);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.whenComplete((value, error) -> latch.countDown());
|
||||||
|
|
||||||
|
log.debug(
|
||||||
|
"Waiting for requests to be completed. Current Time: " + System.currentTimeMillis());
|
||||||
|
latch.await(5, TimeUnit.SECONDS );
|
||||||
|
assertEquals(0, latch.getCount());
|
||||||
|
log.debug("All requests have been completed. Exiting test.");
|
||||||
|
}
|
||||||
|
}
|
2
pom.xml
2
pom.xml
|
@ -639,7 +639,6 @@
|
||||||
<module>spring-batch</module>
|
<module>spring-batch</module>
|
||||||
<module>spring-bom</module>
|
<module>spring-bom</module>
|
||||||
<module>spring-boot-modules</module>
|
<module>spring-boot-modules</module>
|
||||||
<module>spring-boot-parent</module>
|
|
||||||
<module>spring-boot-rest</module>
|
<module>spring-boot-rest</module>
|
||||||
|
|
||||||
<module>spring-caching</module>
|
<module>spring-caching</module>
|
||||||
|
@ -1141,7 +1140,6 @@
|
||||||
<module>spring-batch</module>
|
<module>spring-batch</module>
|
||||||
<module>spring-bom</module>
|
<module>spring-bom</module>
|
||||||
<module>spring-boot-modules</module>
|
<module>spring-boot-modules</module>
|
||||||
<module>spring-boot-parent</module>
|
|
||||||
<module>spring-boot-rest</module>
|
<module>spring-boot-rest</module>
|
||||||
|
|
||||||
<module>spring-caching</module>
|
<module>spring-caching</module>
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.baeldung.springamqp.exponentialbackoff;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class ExponentialBackoffApp {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(ExponentialBackoffApp.class, args);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.baeldung.springamqp.exponentialbackoff;
|
||||||
|
|
||||||
|
import org.springframework.amqp.core.Message;
|
||||||
|
import org.springframework.amqp.rabbit.retry.RejectAndDontRequeueRecoverer;
|
||||||
|
|
||||||
|
public class ObservableRejectAndDontRequeueRecoverer extends RejectAndDontRequeueRecoverer {
|
||||||
|
private Runnable observer;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void recover(Message message, Throwable cause) {
|
||||||
|
if(observer != null) {
|
||||||
|
observer.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
super.recover(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setObserver(Runnable observer){
|
||||||
|
this.observer = observer;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,162 @@
|
||||||
|
package com.baeldung.springamqp.exponentialbackoff;
|
||||||
|
|
||||||
|
import org.aopalliance.aop.Advice;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.amqp.core.AmqpAdmin;
|
||||||
|
import org.springframework.amqp.core.Message;
|
||||||
|
import org.springframework.amqp.core.MessageProperties;
|
||||||
|
import org.springframework.amqp.core.Queue;
|
||||||
|
import org.springframework.amqp.core.QueueBuilder;
|
||||||
|
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
|
||||||
|
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||||
|
import org.springframework.amqp.rabbit.config.RetryInterceptorBuilder;
|
||||||
|
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
|
||||||
|
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
|
||||||
|
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||||
|
import org.springframework.amqp.rabbit.core.RabbitAdmin;
|
||||||
|
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.retry.interceptor.RetryOperationsInterceptor;
|
||||||
|
|
||||||
|
import com.rabbitmq.client.Channel;
|
||||||
|
|
||||||
|
@EnableRabbit
|
||||||
|
@Configuration
|
||||||
|
public class RabbitConfiguration {
|
||||||
|
|
||||||
|
private static Logger logger = LoggerFactory.getLogger(RabbitConfiguration.class);
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ConnectionFactory connectionFactory() {
|
||||||
|
return new CachingConnectionFactory("localhost");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AmqpAdmin amqpAdmin() {
|
||||||
|
return new RabbitAdmin(connectionFactory());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RabbitTemplate rabbitTemplate() {
|
||||||
|
return new RabbitTemplate(connectionFactory());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Queue blockingQueue() {
|
||||||
|
return QueueBuilder.nonDurable("blocking-queue")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Queue nonBlockingQueue() {
|
||||||
|
return QueueBuilder.nonDurable("non-blocking-queue")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Queue retryWaitEndedQueue() {
|
||||||
|
return QueueBuilder.nonDurable("retry-wait-ended-queue")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Queue retryQueue1() {
|
||||||
|
return QueueBuilder.nonDurable("retry-queue-1")
|
||||||
|
.deadLetterExchange("")
|
||||||
|
.deadLetterRoutingKey("retry-wait-ended-queue")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Queue retryQueue2() {
|
||||||
|
return QueueBuilder.nonDurable("retry-queue-2")
|
||||||
|
.deadLetterExchange("")
|
||||||
|
.deadLetterRoutingKey("retry-wait-ended-queue")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Queue retryQueue3() {
|
||||||
|
return QueueBuilder.nonDurable("retry-queue-3")
|
||||||
|
.deadLetterExchange("")
|
||||||
|
.deadLetterRoutingKey("retry-wait-ended-queue")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RetryQueues retryQueues() {
|
||||||
|
return new RetryQueues(1000, 3.0, 10000, retryQueue1(), retryQueue2(), retryQueue3());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ObservableRejectAndDontRequeueRecoverer observableRecoverer() {
|
||||||
|
return new ObservableRejectAndDontRequeueRecoverer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RetryOperationsInterceptor retryInterceptor() {
|
||||||
|
return RetryInterceptorBuilder.stateless()
|
||||||
|
.backOffOptions(1000, 3.0, 10000)
|
||||||
|
.maxAttempts(5)
|
||||||
|
.recoverer(observableRecoverer())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RetryQueuesInterceptor retryQueuesInterceptor(RabbitTemplate rabbitTemplate, RetryQueues retryQueues) {
|
||||||
|
return new RetryQueuesInterceptor(rabbitTemplate, retryQueues);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SimpleRabbitListenerContainerFactory defaultContainerFactory(ConnectionFactory connectionFactory) {
|
||||||
|
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
|
||||||
|
factory.setConnectionFactory(connectionFactory);
|
||||||
|
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SimpleRabbitListenerContainerFactory retryContainerFactory(ConnectionFactory connectionFactory, RetryOperationsInterceptor retryInterceptor) {
|
||||||
|
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
|
||||||
|
factory.setConnectionFactory(connectionFactory);
|
||||||
|
|
||||||
|
Advice[] adviceChain = { retryInterceptor };
|
||||||
|
factory.setAdviceChain(adviceChain);
|
||||||
|
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SimpleRabbitListenerContainerFactory retryQueuesContainerFactory(ConnectionFactory connectionFactory, RetryQueuesInterceptor retryInterceptor) {
|
||||||
|
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
|
||||||
|
factory.setConnectionFactory(connectionFactory);
|
||||||
|
|
||||||
|
Advice[] adviceChain = { retryInterceptor };
|
||||||
|
factory.setAdviceChain(adviceChain);
|
||||||
|
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RabbitListener(queues = "blocking-queue", containerFactory = "retryContainerFactory")
|
||||||
|
public void consumeBlocking(String payload) throws Exception {
|
||||||
|
logger.info("Processing message from blocking-queue: {}", payload);
|
||||||
|
|
||||||
|
throw new Exception("exception occured!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@RabbitListener(queues = "non-blocking-queue", containerFactory = "retryQueuesContainerFactory", ackMode = "MANUAL")
|
||||||
|
public void consumeNonBlocking(String payload) throws Exception {
|
||||||
|
logger.info("Processing message from non-blocking-queue: {}", payload);
|
||||||
|
|
||||||
|
throw new Exception("Error occured!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@RabbitListener(queues = "retry-wait-ended-queue", containerFactory = "defaultContainerFactory")
|
||||||
|
public void consumeRetryWaitEndedMessage(String payload, Message message, Channel channel) throws Exception {
|
||||||
|
MessageProperties props = message.getMessageProperties();
|
||||||
|
|
||||||
|
rabbitTemplate().convertAndSend(props.getHeader("x-original-exchange"), props.getHeader("x-original-routing-key"), message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.baeldung.springamqp.exponentialbackoff;
|
||||||
|
|
||||||
|
import org.springframework.amqp.core.Queue;
|
||||||
|
|
||||||
|
public class RetryQueues {
|
||||||
|
private Queue[] queues;
|
||||||
|
private long initialInterval;
|
||||||
|
private double factor;
|
||||||
|
private long maxWait;
|
||||||
|
|
||||||
|
public RetryQueues(long initialInterval, double factor, long maxWait, Queue... queues) {
|
||||||
|
this.queues = queues;
|
||||||
|
this.initialInterval = initialInterval;
|
||||||
|
this.factor = factor;
|
||||||
|
this.maxWait = maxWait;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean retriesExhausted(int retry) {
|
||||||
|
return retry >= queues.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getQueueName(int retry) {
|
||||||
|
return queues[retry].getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimeToWait(int retry) {
|
||||||
|
double time = initialInterval * Math.pow(factor, (double) retry);
|
||||||
|
if (time > maxWait) {
|
||||||
|
return maxWait;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (long) time;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
package com.baeldung.springamqp.exponentialbackoff;
|
||||||
|
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import com.rabbitmq.client.Channel;
|
||||||
|
|
||||||
|
import org.aopalliance.intercept.MethodInterceptor;
|
||||||
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
|
import org.springframework.amqp.core.Message;
|
||||||
|
import org.springframework.amqp.core.MessageProperties;
|
||||||
|
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||||
|
|
||||||
|
public class RetryQueuesInterceptor implements MethodInterceptor {
|
||||||
|
|
||||||
|
private RabbitTemplate rabbitTemplate;
|
||||||
|
|
||||||
|
private RetryQueues retryQueues;
|
||||||
|
|
||||||
|
private Runnable observer;
|
||||||
|
|
||||||
|
public RetryQueuesInterceptor(RabbitTemplate rabbitTemplate, RetryQueues retryQueues) {
|
||||||
|
this.rabbitTemplate = rabbitTemplate;
|
||||||
|
this.retryQueues = retryQueues;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object invoke(MethodInvocation invocation) throws Throwable {
|
||||||
|
return tryConsume(invocation, this::ack, (mac, e) -> {
|
||||||
|
try {
|
||||||
|
int retryCount = tryGetRetryCountOrFail(mac, e);
|
||||||
|
sendToNextRetryQueue(mac, retryCount);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
if (observer != null) {
|
||||||
|
observer.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeException(t);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void setObserver(Runnable observer) {
|
||||||
|
this.observer = observer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object tryConsume(MethodInvocation invocation, Consumer<MessageAndChannel> successHandler, BiConsumer<MessageAndChannel, Throwable> errorHandler) throws Throwable {
|
||||||
|
MessageAndChannel mac = new MessageAndChannel((Message) invocation.getArguments()[1], (Channel) invocation.getArguments()[0]);
|
||||||
|
Object ret = null;
|
||||||
|
try {
|
||||||
|
ret = invocation.proceed();
|
||||||
|
successHandler.accept(mac);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
errorHandler.accept(mac, e);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ack(MessageAndChannel mac) {
|
||||||
|
try {
|
||||||
|
mac.channel.basicAck(mac.message.getMessageProperties()
|
||||||
|
.getDeliveryTag(), false);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int tryGetRetryCountOrFail(MessageAndChannel mac, Throwable originalError) throws Throwable {
|
||||||
|
MessageProperties props = mac.message.getMessageProperties();
|
||||||
|
|
||||||
|
String xRetriedCountHeader = (String) props.getHeader("x-retried-count");
|
||||||
|
final int xRetriedCount = xRetriedCountHeader == null ? 0 : Integer.valueOf(xRetriedCountHeader);
|
||||||
|
|
||||||
|
if (retryQueues.retriesExhausted(xRetriedCount)) {
|
||||||
|
mac.channel.basicReject(props.getDeliveryTag(), false);
|
||||||
|
|
||||||
|
throw originalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
return xRetriedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendToNextRetryQueue(MessageAndChannel mac, int retryCount) throws Exception {
|
||||||
|
String retryQueueName = retryQueues.getQueueName(retryCount);
|
||||||
|
|
||||||
|
rabbitTemplate.convertAndSend(retryQueueName, mac.message, m -> {
|
||||||
|
MessageProperties props = m.getMessageProperties();
|
||||||
|
props.setExpiration(String.valueOf(retryQueues.getTimeToWait(retryCount)));
|
||||||
|
props.setHeader("x-retried-count", String.valueOf(retryCount + 1));
|
||||||
|
props.setHeader("x-original-exchange", props.getReceivedExchange());
|
||||||
|
props.setHeader("x-original-routing-key", props.getReceivedRoutingKey());
|
||||||
|
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
|
||||||
|
mac.channel.basicReject(mac.message.getMessageProperties()
|
||||||
|
.getDeliveryTag(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MessageAndChannel {
|
||||||
|
private Message message;
|
||||||
|
private Channel channel;
|
||||||
|
|
||||||
|
private MessageAndChannel(Message message, Channel channel) {
|
||||||
|
this.message = message;
|
||||||
|
this.channel = channel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package com.baeldung.springamqp.exponentialbackoff;
|
||||||
|
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This live test requires:
|
||||||
|
*
|
||||||
|
* - A running RabbitMQ instance on localhost (e.g. docker run -p 5672:5672 -p 15672:15672 --name rabbit rabbitmq:3-management)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@RunWith(SpringJUnit4ClassRunner.class)
|
||||||
|
@ContextConfiguration(classes = { RabbitConfiguration.class })
|
||||||
|
public class ExponentialBackoffLiveTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RabbitTemplate rabbitTemplate;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ObservableRejectAndDontRequeueRecoverer observableRecoverer;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RetryQueuesInterceptor retryQueues;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenSendToBlockingQueue_thenAllMessagesProcessed() throws Exception {
|
||||||
|
int nb = 2;
|
||||||
|
|
||||||
|
CountDownLatch latch = new CountDownLatch(nb);
|
||||||
|
observableRecoverer.setObserver(() -> latch.countDown());
|
||||||
|
|
||||||
|
for (int i = 1; i <= nb; i++) {
|
||||||
|
rabbitTemplate.convertAndSend("blocking-queue", "blocking message " + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
latch.await();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenSendToNonBlockingQueue_thenAllMessageProcessed() throws Exception {
|
||||||
|
int nb = 2;
|
||||||
|
|
||||||
|
CountDownLatch latch = new CountDownLatch(nb);
|
||||||
|
retryQueues.setObserver(() -> latch.countDown());
|
||||||
|
|
||||||
|
for (int i = 1; i <= nb; i++) {
|
||||||
|
rabbitTemplate.convertAndSend("non-blocking-queue", "non-blocking message " + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
latch.await();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration>
|
||||||
|
<include
|
||||||
|
resource="org/springframework/boot/logging/logback/base.xml" />
|
||||||
|
<logger name="org.springframework" level="INFO" />
|
||||||
|
</configuration>
|
|
@ -18,6 +18,8 @@ public class App {
|
||||||
final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||||
context.register(SpringConfig.class);
|
context.register(SpringConfig.class);
|
||||||
context.register(SpringBatchConfig.class);
|
context.register(SpringBatchConfig.class);
|
||||||
|
context.register(SpringBatchRetryConfig.class);
|
||||||
|
|
||||||
context.refresh();
|
context.refresh();
|
||||||
|
|
||||||
// Spring xml config
|
// Spring xml config
|
||||||
|
@ -26,6 +28,8 @@ public class App {
|
||||||
runJob(context, "firstBatchJob");
|
runJob(context, "firstBatchJob");
|
||||||
runJob(context, "skippingBatchJob");
|
runJob(context, "skippingBatchJob");
|
||||||
runJob(context, "skipPolicyBatchJob");
|
runJob(context, "skipPolicyBatchJob");
|
||||||
|
runJob(context, "retryBatchJob");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void runJob(AnnotationConfigApplicationContext context, String batchJobName) {
|
private static void runJob(AnnotationConfigApplicationContext context, String batchJobName) {
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
package org.baeldung.batch;
|
||||||
|
|
||||||
|
import org.apache.http.client.config.RequestConfig;
|
||||||
|
import org.apache.http.conn.ConnectTimeoutException;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.impl.client.HttpClientBuilder;
|
||||||
|
import org.baeldung.batch.model.Transaction;
|
||||||
|
import org.baeldung.batch.service.RecordFieldSetMapper;
|
||||||
|
import org.baeldung.batch.service.RetryItemProcessor;
|
||||||
|
import org.springframework.batch.core.Job;
|
||||||
|
import org.springframework.batch.core.Step;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
|
||||||
|
import org.springframework.batch.item.ItemProcessor;
|
||||||
|
import org.springframework.batch.item.ItemReader;
|
||||||
|
import org.springframework.batch.item.ItemWriter;
|
||||||
|
import org.springframework.batch.item.file.FlatFileItemReader;
|
||||||
|
import org.springframework.batch.item.file.mapping.DefaultLineMapper;
|
||||||
|
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
|
||||||
|
import org.springframework.batch.item.xml.StaxEventItemWriter;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.dao.DeadlockLoserDataAccessException;
|
||||||
|
import org.springframework.oxm.Marshaller;
|
||||||
|
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableBatchProcessing
|
||||||
|
public class SpringBatchRetryConfig {
|
||||||
|
|
||||||
|
private static final String[] tokens = { "username", "userid", "transactiondate", "amount" };
|
||||||
|
private static final int TWO_SECONDS = 2000;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobBuilderFactory jobBuilderFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private StepBuilderFactory stepBuilderFactory;
|
||||||
|
|
||||||
|
@Value("input/recordRetry.csv")
|
||||||
|
private Resource inputCsv;
|
||||||
|
|
||||||
|
@Value("file:xml/retryOutput.xml")
|
||||||
|
private Resource outputXml;
|
||||||
|
|
||||||
|
public ItemReader<Transaction> itemReader(Resource inputData) throws ParseException {
|
||||||
|
DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer();
|
||||||
|
tokenizer.setNames(tokens);
|
||||||
|
DefaultLineMapper<Transaction> lineMapper = new DefaultLineMapper<>();
|
||||||
|
lineMapper.setLineTokenizer(tokenizer);
|
||||||
|
lineMapper.setFieldSetMapper(new RecordFieldSetMapper());
|
||||||
|
FlatFileItemReader<Transaction> reader = new FlatFileItemReader<>();
|
||||||
|
reader.setResource(inputData);
|
||||||
|
reader.setLinesToSkip(1);
|
||||||
|
reader.setLineMapper(lineMapper);
|
||||||
|
return reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public CloseableHttpClient closeableHttpClient() {
|
||||||
|
final RequestConfig config = RequestConfig.custom()
|
||||||
|
.setConnectTimeout(TWO_SECONDS)
|
||||||
|
.build();
|
||||||
|
return HttpClientBuilder.create().setDefaultRequestConfig(config).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ItemProcessor<Transaction, Transaction> retryItemProcessor() {
|
||||||
|
return new RetryItemProcessor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ItemWriter<Transaction> itemWriter(Marshaller marshaller) {
|
||||||
|
StaxEventItemWriter<Transaction> itemWriter = new StaxEventItemWriter<>();
|
||||||
|
itemWriter.setMarshaller(marshaller);
|
||||||
|
itemWriter.setRootTagName("transactionRecord");
|
||||||
|
itemWriter.setResource(outputXml);
|
||||||
|
return itemWriter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Marshaller marshaller() {
|
||||||
|
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
|
||||||
|
marshaller.setClassesToBeBound(Transaction.class);
|
||||||
|
return marshaller;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Step retryStep(@Qualifier("retryItemProcessor") ItemProcessor<Transaction, Transaction> processor,
|
||||||
|
ItemWriter<Transaction> writer) throws ParseException {
|
||||||
|
return stepBuilderFactory.get("retryStep")
|
||||||
|
.<Transaction, Transaction>chunk(10)
|
||||||
|
.reader(itemReader(inputCsv))
|
||||||
|
.processor(processor)
|
||||||
|
.writer(writer)
|
||||||
|
.faultTolerant()
|
||||||
|
.retryLimit(3)
|
||||||
|
.retry(ConnectTimeoutException.class)
|
||||||
|
.retry(DeadlockLoserDataAccessException.class)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean(name = "retryBatchJob")
|
||||||
|
public Job retryJob(@Qualifier("retryStep") Step retryStep) {
|
||||||
|
return jobBuilderFactory
|
||||||
|
.get("retryBatchJob")
|
||||||
|
.start(retryStep)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,8 @@ import javax.xml.bind.annotation.XmlRootElement;
|
||||||
public class Transaction {
|
public class Transaction {
|
||||||
private String username;
|
private String username;
|
||||||
private int userId;
|
private int userId;
|
||||||
|
private int age;
|
||||||
|
private String postCode;
|
||||||
private Date transactionDate;
|
private Date transactionDate;
|
||||||
private double amount;
|
private double amount;
|
||||||
|
|
||||||
|
@ -46,9 +48,25 @@ public class Transaction {
|
||||||
this.amount = amount;
|
this.amount = amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getAge() {
|
||||||
|
return age;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAge(int age) {
|
||||||
|
this.age = age;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPostCode() {
|
||||||
|
return postCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPostCode(String postCode) {
|
||||||
|
this.postCode = postCode;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Transaction [username=" + username + ", userId=" + userId + ", transactionDate=" + transactionDate + ", amount=" + amount + "]";
|
return "Transaction [username=" + username + ", userId=" + userId + ", age=" + age + ", postCode=" + postCode + ", transactionDate=" + transactionDate + ", amount=" + amount + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
package org.baeldung.batch.service;
|
||||||
|
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.util.EntityUtils;
|
||||||
|
import org.baeldung.batch.model.Transaction;
|
||||||
|
import org.codehaus.jettison.json.JSONException;
|
||||||
|
import org.codehaus.jettison.json.JSONObject;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.batch.item.ItemProcessor;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class RetryItemProcessor implements ItemProcessor<Transaction, Transaction> {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(RetryItemProcessor.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CloseableHttpClient closeableHttpClient;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Transaction process(Transaction transaction) throws IOException, JSONException {
|
||||||
|
LOGGER.info("Attempting to process user with id={}", transaction.getUserId());
|
||||||
|
HttpResponse response = fetchMoreUserDetails(transaction.getUserId());
|
||||||
|
|
||||||
|
//parse user's age and postCode from response and update transaction
|
||||||
|
String result = EntityUtils.toString(response.getEntity());
|
||||||
|
JSONObject userObject = new JSONObject(result);
|
||||||
|
transaction.setAge(Integer.parseInt(userObject.getString("age")));
|
||||||
|
transaction.setPostCode(userObject.getString("postCode"));
|
||||||
|
|
||||||
|
return transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpResponse fetchMoreUserDetails(int id) throws IOException {
|
||||||
|
final HttpGet request = new HttpGet("http://www.baeldung.com:81/user/" + id);
|
||||||
|
return closeableHttpClient.execute(request);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
username, user_id, transaction_date, transaction_amount
|
||||||
|
sammy, 1234, 31/10/2015, 10000
|
||||||
|
john, 9999, 3/12/2015, 12321
|
|
|
@ -54,4 +54,19 @@
|
||||||
</batch:tasklet>
|
</batch:tasklet>
|
||||||
</batch:step>
|
</batch:step>
|
||||||
</batch:job>
|
</batch:job>
|
||||||
|
|
||||||
|
<batch:job id="retryBatchJob">
|
||||||
|
<batch:step id="retryStep">
|
||||||
|
<batch:tasklet>
|
||||||
|
<batch:chunk reader="itemReader" writer="itemWriter"
|
||||||
|
processor="retryItemProcessor" commit-interval="10"
|
||||||
|
retry-limit="3">
|
||||||
|
<batch:retryable-exception-classes>
|
||||||
|
<batch:include class="org.apache.http.conn.ConnectTimeoutException"/>
|
||||||
|
<batch:include class="org.springframework.dao.DeadlockLoserDataAccessException"/>
|
||||||
|
</batch:retryable-exception-classes>
|
||||||
|
</batch:chunk>
|
||||||
|
</batch:tasklet>
|
||||||
|
</batch:step>
|
||||||
|
</batch:job>
|
||||||
</beans>
|
</beans>
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
package org.baeldung.batch;
|
||||||
|
|
||||||
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
import org.apache.http.conn.ConnectTimeoutException;
|
||||||
|
import org.apache.http.entity.StringEntity;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.springframework.batch.core.ExitStatus;
|
||||||
|
import org.springframework.batch.core.JobExecution;
|
||||||
|
import org.springframework.batch.core.JobInstance;
|
||||||
|
import org.springframework.batch.core.JobParameters;
|
||||||
|
import org.springframework.batch.core.JobParametersBuilder;
|
||||||
|
import org.springframework.batch.test.AssertFile;
|
||||||
|
import org.springframework.batch.test.JobLauncherTestUtils;
|
||||||
|
import org.springframework.batch.test.context.SpringBatchTest;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||||
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
|
import org.springframework.core.io.FileSystemResource;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.containsString;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@SpringBatchTest
|
||||||
|
@EnableAutoConfiguration
|
||||||
|
@ContextConfiguration(classes = { SpringBatchRetryConfig.class })
|
||||||
|
public class SpringBatchRetryIntegrationTest {
|
||||||
|
|
||||||
|
private static final String TEST_OUTPUT = "xml/retryOutput.xml";
|
||||||
|
private static final String EXPECTED_OUTPUT = "src/test/resources/output/batchRetry/retryOutput.xml";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobLauncherTestUtils jobLauncherTestUtils;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private CloseableHttpClient closeableHttpClient;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CloseableHttpResponse httpResponse;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenEndpointAlwaysFail_thenJobFails() throws Exception {
|
||||||
|
when(closeableHttpClient.execute(any()))
|
||||||
|
.thenThrow(new ConnectTimeoutException("Endpoint is down"));
|
||||||
|
|
||||||
|
JobExecution jobExecution = jobLauncherTestUtils.launchJob(defaultJobParameters());
|
||||||
|
JobInstance actualJobInstance = jobExecution.getJobInstance();
|
||||||
|
ExitStatus actualJobExitStatus = jobExecution.getExitStatus();
|
||||||
|
|
||||||
|
assertThat(actualJobInstance.getJobName(), is("retryBatchJob"));
|
||||||
|
assertThat(actualJobExitStatus.getExitCode(), is("FAILED"));
|
||||||
|
assertThat(actualJobExitStatus.getExitDescription(), containsString("org.apache.http.conn.ConnectTimeoutException"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenEndpointFailsTwicePasses3rdTime_thenSuccess() throws Exception {
|
||||||
|
FileSystemResource expectedResult = new FileSystemResource(EXPECTED_OUTPUT);
|
||||||
|
FileSystemResource actualResult = new FileSystemResource(TEST_OUTPUT);
|
||||||
|
|
||||||
|
//fails for first two calls and passes third time onwards
|
||||||
|
when(httpResponse.getEntity())
|
||||||
|
.thenReturn(new StringEntity("{ \"age\":10, \"postCode\":\"430222\" }"));
|
||||||
|
when(closeableHttpClient.execute(any()))
|
||||||
|
.thenThrow(new ConnectTimeoutException("Timeout count 1"))
|
||||||
|
.thenThrow(new ConnectTimeoutException("Timeout count 2"))
|
||||||
|
.thenReturn(httpResponse);
|
||||||
|
|
||||||
|
JobExecution jobExecution = jobLauncherTestUtils.launchJob(defaultJobParameters());
|
||||||
|
JobInstance actualJobInstance = jobExecution.getJobInstance();
|
||||||
|
ExitStatus actualJobExitStatus = jobExecution.getExitStatus();
|
||||||
|
|
||||||
|
assertThat(actualJobInstance.getJobName(), is("retryBatchJob"));
|
||||||
|
assertThat(actualJobExitStatus.getExitCode(), is("COMPLETED"));
|
||||||
|
AssertFile.assertFileEquals(expectedResult, actualResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JobParameters defaultJobParameters() {
|
||||||
|
JobParametersBuilder paramsBuilder = new JobParametersBuilder();
|
||||||
|
paramsBuilder.addString("jobID", String.valueOf(System.currentTimeMillis()));
|
||||||
|
return paramsBuilder.toJobParameters();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?><transactionRecord><transactionRecord><age>10</age><amount>10000.0</amount><postCode>430222</postCode><transactionDate>2015-10-31T00:00:00+05:30</transactionDate><userId>1234</userId><username>sammy</username></transactionRecord><transactionRecord><age>10</age><amount>12321.0</amount><postCode>430222</postCode><transactionDate>2015-12-03T00:00:00+05:30</transactionDate><userId>9999</userId><username>john</username></transactionRecord></transactionRecord>
|
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?><transactionRecord><transactionRecord><age>10</age><amount>10000.0</amount><postCode>430222</postCode><transactionDate>2015-10-31T00:00:00+05:30</transactionDate><userId>1234</userId><username>sammy</username></transactionRecord><transactionRecord><age>10</age><amount>12321.0</amount><postCode>430222</postCode><transactionDate>2015-12-03T00:00:00+05:30</transactionDate><userId>9999</userId><username>john</username></transactionRecord></transactionRecord>
|
|
@ -43,6 +43,7 @@
|
||||||
<module>spring-boot-mvc-2</module>
|
<module>spring-boot-mvc-2</module>
|
||||||
<module>spring-boot-mvc-birt</module>
|
<module>spring-boot-mvc-birt</module>
|
||||||
<module>spring-boot-nashorn</module>
|
<module>spring-boot-nashorn</module>
|
||||||
|
<module>spring-boot-parent</module>
|
||||||
<module>spring-boot-performance</module>
|
<module>spring-boot-performance</module>
|
||||||
<module>spring-boot-properties</module>
|
<module>spring-boot-properties</module>
|
||||||
<module>spring-boot-property-exp</module>
|
<module>spring-boot-property-exp</module>
|
||||||
|
|
|
@ -16,11 +16,12 @@ import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
|
||||||
public class Swagger2Config {
|
public class Swagger2Config {
|
||||||
@Bean
|
@Bean
|
||||||
public Docket api() {
|
public Docket api() {
|
||||||
return new Docket(DocumentationType.SWAGGER_2).select()
|
return new Docket(DocumentationType.SWAGGER_2)
|
||||||
.apis(RequestHandlerSelectors.basePackage("com.baeldung.swagger2boot.controller"))
|
.select()
|
||||||
.paths(PathSelectors.regex("/.*"))
|
.apis(RequestHandlerSelectors.any())
|
||||||
.build()
|
.paths(PathSelectors.any())
|
||||||
.apiInfo(apiEndPointsInfo());
|
.build()
|
||||||
|
.apiInfo(apiEndPointsInfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
private ApiInfo apiEndPointsInfo() {
|
private ApiInfo apiEndPointsInfo() {
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
package com.baeldung.swagger2boot.controller;
|
||||||
|
|
||||||
|
import com.baeldung.swagger2boot.model.Foo;
|
||||||
|
import com.baeldung.swagger2boot.model.User;
|
||||||
|
import io.swagger.annotations.ApiImplicitParam;
|
||||||
|
import io.swagger.annotations.ApiImplicitParams;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import io.swagger.annotations.ApiParam;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.validation.Valid;
|
||||||
|
import javax.websocket.server.PathParam;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.RandomStringUtils.randomNumeric;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
public class UserController {
|
||||||
|
|
||||||
|
public UserController() {
|
||||||
|
super();
|
||||||
|
} //@formatter:off
|
||||||
|
|
||||||
|
@RequestMapping(method = RequestMethod.POST, value = "/createUser", produces = "application/json; charset=UTF-8")
|
||||||
|
@ResponseStatus(HttpStatus.CREATED)
|
||||||
|
@ResponseBody
|
||||||
|
@ApiOperation(value = "Create user",
|
||||||
|
notes = "This method creates a new user")
|
||||||
|
public User createUser(@ApiParam(
|
||||||
|
name = "firstName",
|
||||||
|
type = "String",
|
||||||
|
value = "First Name of the user",
|
||||||
|
example = "Vatsal",
|
||||||
|
required = true) @RequestParam String firstName) { //@formatter:on
|
||||||
|
User user = new User(firstName);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.baeldung.swagger2boot.model;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
|
||||||
|
@ApiModel
|
||||||
|
public class User {
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "first name of the user", name = "firstName", dataType = "String", example = "Vatsal")
|
||||||
|
String firstName;
|
||||||
|
|
||||||
|
public User() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public User(final String firstName) {
|
||||||
|
super();
|
||||||
|
this.firstName = firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFirstName() {
|
||||||
|
return firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFirstName(String firstName) {
|
||||||
|
this.firstName = firstName;
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,10 +10,9 @@
|
||||||
<description>spring-boot-parent</description>
|
<description>spring-boot-parent</description>
|
||||||
|
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.baeldung</groupId>
|
<groupId>com.baeldung.spring-boot-modules</groupId>
|
||||||
<artifactId>parent-modules</artifactId>
|
<artifactId>spring-boot-modules</artifactId>
|
||||||
<version>1.0.0-SNAPSHOT</version>
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
<relativePath>..</relativePath>
|
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
|
@ -1,11 +1,11 @@
|
||||||
package com.baeldung.requestresponsebody;
|
package com.baeldung.requestresponsebody;
|
||||||
|
|
||||||
import com.baeldung.services.ExampleService;
|
import com.baeldung.services.ExampleService;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
@ -34,4 +34,18 @@ public class ExamplePostController {
|
||||||
log.debug("POST received - serializing LoginForm: " + loginForm.getPassword() + " " + loginForm.getUsername());
|
log.debug("POST received - serializing LoginForm: " + loginForm.getPassword() + " " + loginForm.getUsername());
|
||||||
return new ResponseTransfer("Thanks For Posting!!!");
|
return new ResponseTransfer("Thanks For Posting!!!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = "/content", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
@ResponseBody
|
||||||
|
public ResponseTransfer postResponseJsonContent(@RequestBody LoginForm loginForm) {
|
||||||
|
log.debug("POST received - serializing LoginForm: " + loginForm.getPassword() + " " + loginForm.getUsername());
|
||||||
|
return new ResponseTransfer("JSON Content!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = "/content", produces = MediaType.APPLICATION_XML_VALUE)
|
||||||
|
@ResponseBody
|
||||||
|
public ResponseTransfer postResponseXmlContent(@RequestBody LoginForm loginForm) {
|
||||||
|
log.debug("POST received - serializing LoginForm: " + loginForm.getPassword() + " " + loginForm.getUsername());
|
||||||
|
return new ResponseTransfer("XML Content!");
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package org.baeldung;
|
package com.baeldung;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
|
@ -1,4 +1,4 @@
|
||||||
package org.baeldung;
|
package com.baeldung;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
|
@ -2,7 +2,7 @@
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
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>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.baeldung.spring.cloud</groupId>
|
<groupId>com.baeldung.spring.cloud</groupId>
|
||||||
<artifactId>batch-job</artifactId>
|
<artifactId>batch-job</artifactId>
|
||||||
<version>0.0.1-SNAPSHOT</version>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
<name>batch-job</name>
|
<name>batch-job</name>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package org.baeldung.spring.cloud;
|
package com.baeldung.spring.cloud;
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|
@ -1,4 +1,4 @@
|
||||||
package org.baeldung.spring.cloud;
|
package com.baeldung.spring.cloud;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
|
@ -1,6 +1,6 @@
|
||||||
package org.baeldung;
|
package com.baeldung;
|
||||||
|
|
||||||
import org.baeldung.spring.cloud.JobConfiguration;
|
import com.baeldung.spring.cloud.JobConfiguration;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
|
@ -1,4 +1,4 @@
|
||||||
package org.baeldung.spring.cloud;
|
package com.baeldung.spring.cloud;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
|
@ -1,4 +1,4 @@
|
||||||
package org.baeldung.spring.cloud;
|
package com.baeldung.spring.cloud;
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|
@ -1,6 +1,6 @@
|
||||||
package org.baeldung;
|
package com.baeldung;
|
||||||
|
|
||||||
import org.baeldung.spring.cloud.DataFlowServerApplication;
|
import com.baeldung.spring.cloud.DataFlowServerApplication;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
|
@ -1,4 +1,4 @@
|
||||||
package org.baeldung.spring.cloud;
|
package com.baeldung.spring.cloud;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
|
@ -1,4 +1,4 @@
|
||||||
package org.baeldung.spring.cloud;
|
package com.baeldung.spring.cloud;
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|
@ -1,6 +1,6 @@
|
||||||
package org.baeldung;
|
package com.baeldung;
|
||||||
|
|
||||||
import org.baeldung.spring.cloud.DataFlowShellApplication;
|
import com.baeldung.spring.cloud.DataFlowShellApplication;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
|
@ -1,4 +1,4 @@
|
||||||
package org.baeldung.spring.cloud;
|
package com.baeldung.spring.cloud;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
|
@ -1,6 +1,6 @@
|
||||||
package org.baeldung;
|
package com.baeldung;
|
||||||
|
|
||||||
import org.baeldung.spring.cloud.LogSinkApplication;
|
import com.baeldung.spring.cloud.LogSinkApplication;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
|
@ -1,4 +1,4 @@
|
||||||
package org.baeldung.spring.cloud;
|
package com.baeldung.spring.cloud;
|
||||||
|
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
|
@ -1,6 +1,6 @@
|
||||||
package org.baeldung;
|
package com.baeldung;
|
||||||
|
|
||||||
import org.baeldung.spring.cloud.TimeProcessorApplication;
|
import com.baeldung.spring.cloud.TimeProcessorApplication;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
|
@ -1,4 +1,4 @@
|
||||||
package org.baeldung.spring.cloud;
|
package com.baeldung.spring.cloud;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue