Merge branch 'master' of https://github.com/eugenp/tutorials
This commit is contained in:
commit
08506092e8
|
@ -0,0 +1,57 @@
|
||||||
|
package com.baeldung.algorithms.suffixtree;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Node {
|
||||||
|
private String text;
|
||||||
|
private List<Node> children;
|
||||||
|
private int position;
|
||||||
|
|
||||||
|
public Node(String word, int position) {
|
||||||
|
this.text = word;
|
||||||
|
this.position = position;
|
||||||
|
this.children = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getText() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setText(String text) {
|
||||||
|
this.text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPosition() {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPosition(int position) {
|
||||||
|
this.position = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Node> getChildren() {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChildren(List<Node> children) {
|
||||||
|
this.children = children;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String printTree(String depthIndicator) {
|
||||||
|
String str = "";
|
||||||
|
String positionStr = position > -1 ? "[" + String.valueOf(position) + "]" : "";
|
||||||
|
str += depthIndicator + text + positionStr + "\n";
|
||||||
|
|
||||||
|
for (int i = 0; i < children.size(); i++) {
|
||||||
|
str += children.get(i)
|
||||||
|
.printTree(depthIndicator + "\t");
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return printTree("");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,175 @@
|
||||||
|
package com.baeldung.algorithms.suffixtree;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class SuffixTree {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(SuffixTree.class);
|
||||||
|
private static final String WORD_TERMINATION = "$";
|
||||||
|
private static final int POSITION_UNDEFINED = -1;
|
||||||
|
private Node root;
|
||||||
|
private String fullText;
|
||||||
|
|
||||||
|
public SuffixTree(String text) {
|
||||||
|
root = new Node("", POSITION_UNDEFINED);
|
||||||
|
for (int i = 0; i < text.length(); i++) {
|
||||||
|
addSuffix(text.substring(i) + WORD_TERMINATION, i);
|
||||||
|
}
|
||||||
|
fullText = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> searchText(String pattern) {
|
||||||
|
LOGGER.info("Searching for pattern \"{}\"", pattern);
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
List<Node> nodes = getAllNodesInTraversePath(pattern, root, false);
|
||||||
|
|
||||||
|
if (nodes.size() > 0) {
|
||||||
|
Node lastNode = nodes.get(nodes.size() - 1);
|
||||||
|
if (lastNode != null) {
|
||||||
|
List<Integer> positions = getPositions(lastNode);
|
||||||
|
positions = positions.stream()
|
||||||
|
.sorted()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
positions.forEach(m -> result.add((markPatternInText(m, pattern))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addSuffix(String suffix, int position) {
|
||||||
|
LOGGER.info(">>>>>>>>>>>> Adding new suffix {}", suffix);
|
||||||
|
List<Node> nodes = getAllNodesInTraversePath(suffix, root, true);
|
||||||
|
if (nodes.size() == 0) {
|
||||||
|
addChildNode(root, suffix, position);
|
||||||
|
LOGGER.info("{}", printTree());
|
||||||
|
} else {
|
||||||
|
Node lastNode = nodes.remove(nodes.size() - 1);
|
||||||
|
String newText = suffix;
|
||||||
|
if (nodes.size() > 0) {
|
||||||
|
String existingSuffixUptoLastNode = nodes.stream()
|
||||||
|
.map(a -> a.getText())
|
||||||
|
.reduce("", String::concat);
|
||||||
|
|
||||||
|
// Remove prefix from newText already included in parent
|
||||||
|
newText = newText.substring(existingSuffixUptoLastNode.length());
|
||||||
|
}
|
||||||
|
extendNode(lastNode, newText, position);
|
||||||
|
LOGGER.info("{}", printTree());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Integer> getPositions(Node node) {
|
||||||
|
List<Integer> positions = new ArrayList<>();
|
||||||
|
if (node.getText()
|
||||||
|
.endsWith(WORD_TERMINATION)) {
|
||||||
|
positions.add(node.getPosition());
|
||||||
|
}
|
||||||
|
for (int i = 0; i < node.getChildren()
|
||||||
|
.size(); i++) {
|
||||||
|
positions.addAll(getPositions(node.getChildren()
|
||||||
|
.get(i)));
|
||||||
|
}
|
||||||
|
return positions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String markPatternInText(Integer startPosition, String pattern) {
|
||||||
|
String matchingTextLHS = fullText.substring(0, startPosition);
|
||||||
|
String matchingText = fullText.substring(startPosition, startPosition + pattern.length());
|
||||||
|
String matchingTextRHS = fullText.substring(startPosition + pattern.length());
|
||||||
|
return matchingTextLHS + "[" + matchingText + "]" + matchingTextRHS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addChildNode(Node parentNode, String text, int position) {
|
||||||
|
parentNode.getChildren()
|
||||||
|
.add(new Node(text, position));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void extendNode(Node node, String newText, int position) {
|
||||||
|
String currentText = node.getText();
|
||||||
|
String commonPrefix = getLongestCommonPrefix(currentText, newText);
|
||||||
|
|
||||||
|
if (commonPrefix != currentText) {
|
||||||
|
String parentText = currentText.substring(0, commonPrefix.length());
|
||||||
|
String childText = currentText.substring(commonPrefix.length());
|
||||||
|
splitNodeToParentAndChild(node, parentText, childText);
|
||||||
|
}
|
||||||
|
|
||||||
|
String remainingText = newText.substring(commonPrefix.length());
|
||||||
|
addChildNode(node, remainingText, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void splitNodeToParentAndChild(Node parentNode, String parentNewText, String childNewText) {
|
||||||
|
Node childNode = new Node(childNewText, parentNode.getPosition());
|
||||||
|
|
||||||
|
if (parentNode.getChildren()
|
||||||
|
.size() > 0) {
|
||||||
|
while (parentNode.getChildren()
|
||||||
|
.size() > 0) {
|
||||||
|
childNode.getChildren()
|
||||||
|
.add(parentNode.getChildren()
|
||||||
|
.remove(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parentNode.getChildren()
|
||||||
|
.add(childNode);
|
||||||
|
parentNode.setText(parentNewText);
|
||||||
|
parentNode.setPosition(POSITION_UNDEFINED);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getLongestCommonPrefix(String str1, String str2) {
|
||||||
|
int compareLength = Math.min(str1.length(), str2.length());
|
||||||
|
for (int i = 0; i < compareLength; i++) {
|
||||||
|
if (str1.charAt(i) != str2.charAt(i)) {
|
||||||
|
return str1.substring(0, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str1.substring(0, compareLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Node> getAllNodesInTraversePath(String pattern, Node startNode, boolean isAllowPartialMatch) {
|
||||||
|
List<Node> nodes = new ArrayList<>();
|
||||||
|
for (int i = 0; i < startNode.getChildren()
|
||||||
|
.size(); i++) {
|
||||||
|
Node currentNode = startNode.getChildren()
|
||||||
|
.get(i);
|
||||||
|
String nodeText = currentNode.getText();
|
||||||
|
if (pattern.charAt(0) == nodeText.charAt(0)) {
|
||||||
|
if (isAllowPartialMatch && pattern.length() <= nodeText.length()) {
|
||||||
|
nodes.add(currentNode);
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
int compareLength = Math.min(nodeText.length(), pattern.length());
|
||||||
|
for (int j = 1; j < compareLength; j++) {
|
||||||
|
if (pattern.charAt(j) != nodeText.charAt(j)) {
|
||||||
|
if (isAllowPartialMatch) {
|
||||||
|
nodes.add(currentNode);
|
||||||
|
}
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes.add(currentNode);
|
||||||
|
if (pattern.length() > compareLength) {
|
||||||
|
List<Node> nodes2 = getAllNodesInTraversePath(pattern.substring(compareLength), currentNode, isAllowPartialMatch);
|
||||||
|
if (nodes2.size() > 0) {
|
||||||
|
nodes.addAll(nodes2);
|
||||||
|
} else if (!isAllowPartialMatch) {
|
||||||
|
nodes.add(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String printTree() {
|
||||||
|
return root.printTree("");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
package com.baeldung.algorithms.suffixtree;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class SuffixTreeUnitTest {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(SuffixTreeUnitTest.class);
|
||||||
|
|
||||||
|
private static SuffixTree suffixTree;
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void setUp() {
|
||||||
|
suffixTree = new SuffixTree("havanabanana");
|
||||||
|
printTree();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenSuffixTree_whenSearchingForA_thenReturn6Matches() {
|
||||||
|
List<String> matches = suffixTree.searchText("a");
|
||||||
|
matches.stream()
|
||||||
|
.forEach(m -> LOGGER.info(m));
|
||||||
|
Assert.assertArrayEquals(new String[] { "h[a]vanabanana", "hav[a]nabanana", "havan[a]banana", "havanab[a]nana", "havanaban[a]na", "havanabanan[a]" }, matches.toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenSuffixTree_whenSearchingForNab_thenReturn1Match() {
|
||||||
|
List<String> matches = suffixTree.searchText("nab");
|
||||||
|
matches.stream()
|
||||||
|
.forEach(m -> LOGGER.info(m));
|
||||||
|
Assert.assertArrayEquals(new String[] { "hava[nab]anana" }, matches.toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenSuffixTree_whenSearchingForNag_thenReturnNoMatches() {
|
||||||
|
List<String> matches = suffixTree.searchText("nag");
|
||||||
|
matches.stream()
|
||||||
|
.forEach(m -> LOGGER.info(m));
|
||||||
|
Assert.assertArrayEquals(new String[] {}, matches.toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenSuffixTree_whenSearchingForBanana_thenReturn2Matches() {
|
||||||
|
List<String> matches = suffixTree.searchText("ana");
|
||||||
|
matches.stream()
|
||||||
|
.forEach(m -> LOGGER.info(m));
|
||||||
|
Assert.assertArrayEquals(new String[] { "hav[ana]banana", "havanab[ana]na", "havanaban[ana]" }, matches.toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenSuffixTree_whenSearchingForNa_thenReturn4Matches() {
|
||||||
|
List<String> matches = suffixTree.searchText("na");
|
||||||
|
matches.stream()
|
||||||
|
.forEach(m -> LOGGER.info(m));
|
||||||
|
Assert.assertArrayEquals(new String[] { "hava[na]banana", "havanaba[na]na", "havanabana[na]" }, matches.toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenSuffixTree_whenSearchingForX_thenReturnNoMatches() {
|
||||||
|
List<String> matches = suffixTree.searchText("x");
|
||||||
|
matches.stream()
|
||||||
|
.forEach(m -> LOGGER.info(m));
|
||||||
|
Assert.assertArrayEquals(new String[] {}, matches.toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void printTree() {
|
||||||
|
suffixTree.printTree();
|
||||||
|
|
||||||
|
LOGGER.info("\n" + suffixTree.printTree());
|
||||||
|
LOGGER.info("==============================================");
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,5 +11,5 @@ This module contains articles about core Java input and output (IO)
|
||||||
- [List Files in a Directory in Java](https://www.baeldung.com/java-list-directory-files)
|
- [List Files in a Directory in Java](https://www.baeldung.com/java-list-directory-files)
|
||||||
- [Java – Append Data to a File](https://www.baeldung.com/java-append-to-file)
|
- [Java – Append Data to a File](https://www.baeldung.com/java-append-to-file)
|
||||||
- [How to Copy a File with Java](https://www.baeldung.com/java-copy-file)
|
- [How to Copy a File with Java](https://www.baeldung.com/java-copy-file)
|
||||||
- [Create a Directory in Java](https://www.baeldung.com/java-create-directory)
|
- [Create a Directory in Java](https://www.baeldung.com/java-create-directory)
|
||||||
- [[<-- Prev]](/core-java-modules/core-java-io)
|
- [[<-- Prev]](/core-java-modules/core-java-io)
|
||||||
|
|
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
|
|
@ -0,0 +1 @@
|
||||||
|
## Relevant Articles
|
|
@ -0,0 +1,53 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>com.baeldung.dddmodules.infrastructure</groupId>
|
||||||
|
<artifactId>infrastructure</artifactId>
|
||||||
|
<version>1.0</version>
|
||||||
|
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.baeldung.dddmodules</groupId>
|
||||||
|
<artifactId>dddmodules</artifactId>
|
||||||
|
<version>1.0</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baeldung.dddmodules.shippingcontext</groupId>
|
||||||
|
<artifactId>shippingcontext</artifactId>
|
||||||
|
<version>${appmodules.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baeldung.dddmodules.ordercontext</groupId>
|
||||||
|
<artifactId>ordercontext</artifactId>
|
||||||
|
<version>${appmodules.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baeldung.dddmodules.sharedkernel</groupId>
|
||||||
|
<artifactId>sharedkernel</artifactId>
|
||||||
|
<version>${appmodules.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>${source.version}</source>
|
||||||
|
<target>${target.version}</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<source.version>9</source.version>
|
||||||
|
<target.version>9</target.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,79 @@
|
||||||
|
package com.baeldung.dddmodules.infrastructure.db;
|
||||||
|
|
||||||
|
import com.baeldung.dddmodules.ordercontext.model.CustomerOrder;
|
||||||
|
import com.baeldung.dddmodules.ordercontext.repository.CustomerOrderRepository;
|
||||||
|
import com.baeldung.dddmodules.shippingcontext.model.PackageItem;
|
||||||
|
import com.baeldung.dddmodules.shippingcontext.model.ShippableOrder;
|
||||||
|
import com.baeldung.dddmodules.shippingcontext.repository.ShippingOrderRepository;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class InMemoryOrderStore implements CustomerOrderRepository, ShippingOrderRepository {
|
||||||
|
private Map<Integer, PersistenceOrder> ordersDb = new HashMap<>();
|
||||||
|
private volatile static InMemoryOrderStore instance = new InMemoryOrderStore();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveCustomerOrder(CustomerOrder order) {
|
||||||
|
this.ordersDb.put(order.getOrderId(), new PersistenceOrder(order.getOrderId(),
|
||||||
|
order.getPaymentMethod(),
|
||||||
|
order.getAddress(),
|
||||||
|
order
|
||||||
|
.getOrderItems()
|
||||||
|
.stream()
|
||||||
|
.map(orderItem ->
|
||||||
|
new PersistenceOrder.OrderItem(orderItem.getProductId(),
|
||||||
|
orderItem.getQuantity(),
|
||||||
|
orderItem.getUnitWeight(),
|
||||||
|
orderItem.getUnitPrice()))
|
||||||
|
.collect(Collectors.toList())
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<ShippableOrder> findShippableOrder(int orderId) {
|
||||||
|
if (!this.ordersDb.containsKey(orderId)) return Optional.empty();
|
||||||
|
PersistenceOrder orderRecord = this.ordersDb.get(orderId);
|
||||||
|
return Optional.of(
|
||||||
|
new ShippableOrder(orderRecord.orderId, orderRecord.orderItems
|
||||||
|
.stream().map(orderItem -> new PackageItem(orderItem.productId,
|
||||||
|
orderItem.itemWeight,
|
||||||
|
orderItem.quantity * orderItem.unitPrice)
|
||||||
|
).collect(Collectors.toList())));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static InMemoryOrderStore provider() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PersistenceOrder {
|
||||||
|
public int orderId;
|
||||||
|
public String paymentMethod;
|
||||||
|
public String address;
|
||||||
|
public List<OrderItem> orderItems;
|
||||||
|
|
||||||
|
public PersistenceOrder(int orderId, String paymentMethod, String address, List<OrderItem> orderItems) {
|
||||||
|
this.orderId = orderId;
|
||||||
|
this.paymentMethod = paymentMethod;
|
||||||
|
this.address = address;
|
||||||
|
this.orderItems = orderItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class OrderItem {
|
||||||
|
public int productId;
|
||||||
|
public float unitPrice;
|
||||||
|
public float itemWeight;
|
||||||
|
public int quantity;
|
||||||
|
|
||||||
|
public OrderItem(int productId, int quantity, float unitWeight, float unitPrice) {
|
||||||
|
this.itemWeight = unitWeight;
|
||||||
|
this.quantity = quantity;
|
||||||
|
this.unitPrice = unitPrice;
|
||||||
|
this.productId = productId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package com.baeldung.dddmodules.infrastructure.events;
|
||||||
|
|
||||||
|
import com.baeldung.dddmodules.sharedkernel.events.ApplicationEvent;
|
||||||
|
import com.baeldung.dddmodules.sharedkernel.events.EventBus;
|
||||||
|
import com.baeldung.dddmodules.sharedkernel.events.EventSubscriber;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
|
|
||||||
|
public class SimpleEventBus implements EventBus {
|
||||||
|
private final Map<String, Set<EventSubscriber>> subscribers = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <E extends ApplicationEvent> void publish(E event) {
|
||||||
|
if (subscribers.containsKey(event.getType())) {
|
||||||
|
subscribers.get(event.getType())
|
||||||
|
.forEach(subscriber -> subscriber.onEvent(event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <E extends ApplicationEvent> void subscribe(String eventType, EventSubscriber subscriber) {
|
||||||
|
Set<EventSubscriber> eventSubscribers = subscribers.get(eventType);
|
||||||
|
if (eventSubscribers == null) {
|
||||||
|
eventSubscribers = new CopyOnWriteArraySet<>();
|
||||||
|
subscribers.put(eventType, eventSubscribers);
|
||||||
|
}
|
||||||
|
eventSubscribers.add(subscriber);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <E extends ApplicationEvent> void unsubscribe(String eventType, EventSubscriber subscriber) {
|
||||||
|
if (subscribers.containsKey(eventType)) {
|
||||||
|
subscribers.get(eventType).remove(subscriber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
module com.baeldung.dddmodules.infrastructure {
|
||||||
|
requires transitive com.baeldung.dddmodules.sharedkernel;
|
||||||
|
requires transitive com.baeldung.dddmodules.ordercontext;
|
||||||
|
requires transitive com.baeldung.dddmodules.shippingcontext;
|
||||||
|
provides com.baeldung.dddmodules.sharedkernel.events.EventBus
|
||||||
|
with com.baeldung.dddmodules.infrastructure.events.SimpleEventBus;
|
||||||
|
provides com.baeldung.dddmodules.ordercontext.repository.CustomerOrderRepository
|
||||||
|
with com.baeldung.dddmodules.infrastructure.db.InMemoryOrderStore;
|
||||||
|
provides com.baeldung.dddmodules.shippingcontext.repository.ShippingOrderRepository
|
||||||
|
with com.baeldung.dddmodules.infrastructure.db.InMemoryOrderStore;
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>com.baeldung.dddmodules.mainapp</groupId>
|
||||||
|
<artifactId>mainapp</artifactId>
|
||||||
|
<version>1.0</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.baeldung.dddmodules</groupId>
|
||||||
|
<artifactId>dddmodules</artifactId>
|
||||||
|
<version>1.0</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baeldung.dddmodules.infrastructure</groupId>
|
||||||
|
<artifactId>infrastructure</artifactId>
|
||||||
|
<version>${appmodules.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>2.16</version>
|
||||||
|
<configuration>
|
||||||
|
<enableAssertions>true</enableAssertions>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>${compiler.plugin.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<source>${source.version}</source>
|
||||||
|
<target>${target.version}</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<source.version>9</source.version>
|
||||||
|
<target.version>9</target.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,54 @@
|
||||||
|
package com.baeldung.dddmodules.mainapp;
|
||||||
|
|
||||||
|
import com.baeldung.dddmodules.ordercontext.model.CustomerOrder;
|
||||||
|
import com.baeldung.dddmodules.ordercontext.model.OrderItem;
|
||||||
|
import com.baeldung.dddmodules.ordercontext.repository.CustomerOrderRepository;
|
||||||
|
import com.baeldung.dddmodules.ordercontext.service.OrderService;
|
||||||
|
import com.baeldung.dddmodules.sharedkernel.events.EventBus;
|
||||||
|
import com.baeldung.dddmodules.shippingcontext.repository.ShippingOrderRepository;
|
||||||
|
import com.baeldung.dddmodules.shippingcontext.service.ShippingService;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class Application {
|
||||||
|
|
||||||
|
public static void main(String args[]) {
|
||||||
|
Map<Class<?>, Object> container = createContainer();
|
||||||
|
OrderService orderService = (OrderService) container.get(OrderService.class);
|
||||||
|
ShippingService shippingService = (ShippingService) container.get(ShippingService.class);
|
||||||
|
shippingService.listenToOrderEvents();
|
||||||
|
|
||||||
|
CustomerOrder customerOrder = new CustomerOrder();
|
||||||
|
int orderId = 1;
|
||||||
|
customerOrder.setOrderId(orderId);
|
||||||
|
List<OrderItem> orderItems = new ArrayList<OrderItem>();
|
||||||
|
orderItems.add(new OrderItem(1, 2, 3, 1));
|
||||||
|
orderItems.add(new OrderItem(2, 1, 1, 1));
|
||||||
|
orderItems.add(new OrderItem(3, 4, 11, 21));
|
||||||
|
customerOrder.setOrderItems(orderItems);
|
||||||
|
customerOrder.setPaymentMethod("PayPal");
|
||||||
|
customerOrder.setAddress("Full address here");
|
||||||
|
orderService.placeOrder(customerOrder);
|
||||||
|
|
||||||
|
if (orderId == shippingService.getParcelByOrderId(orderId).get().getOrderId()) {
|
||||||
|
System.out.println("Order has been processed and shipped successfully");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<Class<?>, Object> createContainer() {
|
||||||
|
EventBus eventBus = ServiceLoader.load(EventBus.class).findFirst().get();
|
||||||
|
CustomerOrderRepository customerOrderRepository = ServiceLoader.load(CustomerOrderRepository.class).findFirst().get();
|
||||||
|
ShippingOrderRepository shippingOrderRepository = ServiceLoader.load(ShippingOrderRepository.class).findFirst().get();
|
||||||
|
ShippingService shippingService = ServiceLoader.load(ShippingService.class).findFirst().get();
|
||||||
|
shippingService.setEventBus(eventBus);
|
||||||
|
shippingService.setOrderRepository(shippingOrderRepository);
|
||||||
|
OrderService orderService = ServiceLoader.load(OrderService.class).findFirst().get();
|
||||||
|
orderService.setEventBus(eventBus);
|
||||||
|
orderService.setOrderRepository(customerOrderRepository);
|
||||||
|
HashMap<Class<?>, Object> container = new HashMap<>();
|
||||||
|
container.put(OrderService.class, orderService);
|
||||||
|
container.put(ShippingService.class, shippingService);
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
module com.baeldung.dddmodules.mainapp {
|
||||||
|
uses com.baeldung.dddmodules.sharedkernel.events.EventBus;
|
||||||
|
uses com.baeldung.dddmodules.ordercontext.service.OrderService;
|
||||||
|
uses com.baeldung.dddmodules.ordercontext.repository.CustomerOrderRepository;
|
||||||
|
uses com.baeldung.dddmodules.shippingcontext.repository.ShippingOrderRepository;
|
||||||
|
uses com.baeldung.dddmodules.shippingcontext.service.ShippingService;
|
||||||
|
requires transitive com.baeldung.dddmodules.infrastructure;
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>com.baeldung.dddmodules.ordercontext</groupId>
|
||||||
|
<artifactId>ordercontext</artifactId>
|
||||||
|
<version>1.0</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.baeldung.dddmodules</groupId>
|
||||||
|
<artifactId>dddmodules</artifactId>
|
||||||
|
<version>1.0</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baeldung.dddmodules.sharedkernel</groupId>
|
||||||
|
<artifactId>sharedkernel</artifactId>
|
||||||
|
<version>${appmodules.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>${source.version}</source>
|
||||||
|
<target>${target.version}</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<source.version>9</source.version>
|
||||||
|
<target.version>9</target.version>
|
||||||
|
<entitymodule.version>1.0</entitymodule.version>
|
||||||
|
<daomodule.version>1.0</daomodule.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,51 @@
|
||||||
|
package com.baeldung.dddmodules.ordercontext.model;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class CustomerOrder {
|
||||||
|
private int orderId;
|
||||||
|
private String paymentMethod;
|
||||||
|
private String address;
|
||||||
|
private List<OrderItem> orderItems;
|
||||||
|
|
||||||
|
public CustomerOrder() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public float calculateTotalPrice() {
|
||||||
|
return orderItems.stream().map(OrderItem::getTotalPrice)
|
||||||
|
.reduce(0F, Float::sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOrderItems(List<OrderItem> orderItems) {
|
||||||
|
this.orderItems = orderItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOrderId() {
|
||||||
|
return orderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<OrderItem> getOrderItems() {
|
||||||
|
return orderItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOrderId(int orderId) {
|
||||||
|
this.orderId = orderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPaymentMethod() {
|
||||||
|
return paymentMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPaymentMethod(String paymentMethod) {
|
||||||
|
this.paymentMethod = paymentMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAddress() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAddress(String address) {
|
||||||
|
this.address = address;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package com.baeldung.dddmodules.ordercontext.model;
|
||||||
|
|
||||||
|
public class OrderItem {
|
||||||
|
private int productId;
|
||||||
|
private int quantity;
|
||||||
|
private float unitPrice;
|
||||||
|
private float unitWeight;
|
||||||
|
|
||||||
|
public OrderItem(int productId, int quantity, float unitPrice, float unitWeight) {
|
||||||
|
this.productId = productId;
|
||||||
|
this.quantity = quantity;
|
||||||
|
this.unitPrice = unitPrice;
|
||||||
|
this.unitWeight = unitWeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getProductId() {
|
||||||
|
return productId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProductId(int productId) {
|
||||||
|
this.productId = productId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getQuantity() {
|
||||||
|
return quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQuantity(int quantity) {
|
||||||
|
this.quantity = quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getTotalPrice() {
|
||||||
|
return this.quantity * this.unitPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUnitPrice(float unitPrice) {
|
||||||
|
this.unitPrice = unitPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getUnitWeight() {
|
||||||
|
return unitWeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getUnitPrice() {
|
||||||
|
return unitPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUnitWeight(float unitWeight) {
|
||||||
|
this.unitWeight = unitWeight;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.baeldung.dddmodules.ordercontext.repository;
|
||||||
|
|
||||||
|
import com.baeldung.dddmodules.ordercontext.model.CustomerOrder;
|
||||||
|
|
||||||
|
public interface CustomerOrderRepository {
|
||||||
|
void saveCustomerOrder(CustomerOrder order);
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package com.baeldung.dddmodules.ordercontext.service;
|
||||||
|
|
||||||
|
import com.baeldung.dddmodules.ordercontext.model.CustomerOrder;
|
||||||
|
import com.baeldung.dddmodules.ordercontext.repository.CustomerOrderRepository;
|
||||||
|
import com.baeldung.dddmodules.sharedkernel.events.ApplicationEvent;
|
||||||
|
import com.baeldung.dddmodules.sharedkernel.events.EventBus;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class CustomerOrderService implements OrderService {
|
||||||
|
public static final String EVENT_ORDER_READY_FOR_SHIPMENT = "OrderReadyForShipmentEvent";
|
||||||
|
|
||||||
|
private CustomerOrderRepository orderRepository;
|
||||||
|
private EventBus eventBus;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void placeOrder(CustomerOrder order) {
|
||||||
|
this.orderRepository.saveCustomerOrder(order);
|
||||||
|
Map<String, String> payload = new HashMap<>();
|
||||||
|
payload.put("order_id", String.valueOf(order.getOrderId()));
|
||||||
|
ApplicationEvent event = new ApplicationEvent(payload) {
|
||||||
|
@Override
|
||||||
|
public String getType() {
|
||||||
|
return EVENT_ORDER_READY_FOR_SHIPMENT;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.eventBus.publish(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventBus getEventBus() {
|
||||||
|
return eventBus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOrderRepository(CustomerOrderRepository orderRepository) {
|
||||||
|
this.orderRepository = orderRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEventBus(EventBus eventBus) {
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.baeldung.dddmodules.ordercontext.service;
|
||||||
|
|
||||||
|
import com.baeldung.dddmodules.ordercontext.model.CustomerOrder;
|
||||||
|
import com.baeldung.dddmodules.ordercontext.repository.CustomerOrderRepository;
|
||||||
|
import com.baeldung.dddmodules.sharedkernel.service.ApplicationService;
|
||||||
|
|
||||||
|
public interface OrderService extends ApplicationService {
|
||||||
|
void placeOrder(CustomerOrder order);
|
||||||
|
|
||||||
|
void setOrderRepository(CustomerOrderRepository orderRepository);
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
module com.baeldung.dddmodules.ordercontext {
|
||||||
|
requires com.baeldung.dddmodules.sharedkernel;
|
||||||
|
exports com.baeldung.dddmodules.ordercontext.service;
|
||||||
|
exports com.baeldung.dddmodules.ordercontext.model;
|
||||||
|
exports com.baeldung.dddmodules.ordercontext.repository;
|
||||||
|
provides com.baeldung.dddmodules.ordercontext.service.OrderService
|
||||||
|
with com.baeldung.dddmodules.ordercontext.service.CustomerOrderService;
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>com.baeldung.dddmodules</groupId>
|
||||||
|
<artifactId>dddmodules</artifactId>
|
||||||
|
<version>1.0</version>
|
||||||
|
<name>ddd-modules</name>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.baeldung</groupId>
|
||||||
|
<artifactId>parent-modules</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
<relativePath>../</relativePath>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<modules>
|
||||||
|
<module>sharedkernel</module>
|
||||||
|
<module>infrastructure</module>
|
||||||
|
<module>shippingcontext</module>
|
||||||
|
<module>ordercontext</module>
|
||||||
|
<module>mainapp</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>${junit.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.assertj</groupId>
|
||||||
|
<artifactId>assertj-core</artifactId>
|
||||||
|
<version>${assertj-core.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<pluginManagement>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>${compiler.plugin.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<source>${source.version}</source>
|
||||||
|
<target>${target.version}</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</pluginManagement>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<compiler.plugin.version>3.8.1</compiler.plugin.version>
|
||||||
|
<source.version>9</source.version>
|
||||||
|
<target.version>9</target.version>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<assertj-core.version>3.12.2</assertj-core.version>
|
||||||
|
<appmodules.version>1.0</appmodules.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>com.baeldung.dddmodules.sharedkernel</groupId>
|
||||||
|
<artifactId>sharedkernel</artifactId>
|
||||||
|
<version>1.0</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.baeldung.dddmodules</groupId>
|
||||||
|
<artifactId>dddmodules</artifactId>
|
||||||
|
<version>1.0</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>${source.version}</source>
|
||||||
|
<target>${target.version}</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<source.version>9</source.version>
|
||||||
|
<target.version>9</target.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.baeldung.dddmodules.sharedkernel.events;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public abstract class ApplicationEvent {
|
||||||
|
protected Map<String, String> payload;
|
||||||
|
|
||||||
|
public abstract String getType();
|
||||||
|
|
||||||
|
public String getPayloadValue(String key) {
|
||||||
|
if (this.payload.containsKey(key)) {
|
||||||
|
return this.payload.get(key);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApplicationEvent(Map<String, String> payload) {
|
||||||
|
this.payload = payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.baeldung.dddmodules.sharedkernel.events;
|
||||||
|
|
||||||
|
public interface EventBus {
|
||||||
|
<E extends ApplicationEvent> void publish(E event);
|
||||||
|
|
||||||
|
<E extends ApplicationEvent> void subscribe(String eventType, EventSubscriber subscriber);
|
||||||
|
|
||||||
|
<E extends ApplicationEvent> void unsubscribe(String eventType, EventSubscriber subscriber);
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.baeldung.dddmodules.sharedkernel.events;
|
||||||
|
|
||||||
|
public interface EventSubscriber {
|
||||||
|
<E extends ApplicationEvent> void onEvent(E event);
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package com.baeldung.dddmodules.sharedkernel.service;
|
||||||
|
|
||||||
|
import com.baeldung.dddmodules.sharedkernel.events.ApplicationEvent;
|
||||||
|
import com.baeldung.dddmodules.sharedkernel.events.EventBus;
|
||||||
|
import com.baeldung.dddmodules.sharedkernel.events.EventSubscriber;
|
||||||
|
|
||||||
|
public interface ApplicationService {
|
||||||
|
|
||||||
|
default <E extends ApplicationEvent> void publishEvent(E event) {
|
||||||
|
EventBus eventBus = getEventBus();
|
||||||
|
if (eventBus != null) {
|
||||||
|
eventBus.publish(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default <E extends ApplicationEvent> void subscribe(String eventType, EventSubscriber subscriber) {
|
||||||
|
EventBus eventBus = getEventBus();
|
||||||
|
if (eventBus != null) {
|
||||||
|
eventBus.subscribe(eventType, subscriber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default <E extends ApplicationEvent> void unsubscribe(String eventType, EventSubscriber subscriber) {
|
||||||
|
EventBus eventBus = getEventBus();
|
||||||
|
if (eventBus != null) {
|
||||||
|
eventBus.unsubscribe(eventType, subscriber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EventBus getEventBus();
|
||||||
|
|
||||||
|
void setEventBus(EventBus eventBus);
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
module com.baeldung.dddmodules.sharedkernel {
|
||||||
|
exports com.baeldung.dddmodules.sharedkernel.events;
|
||||||
|
exports com.baeldung.dddmodules.sharedkernel.service;
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>com.baeldung.dddmodules.shippingcontext</groupId>
|
||||||
|
<artifactId>shippingcontext</artifactId>
|
||||||
|
<version>1.0</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.baeldung.dddmodules</groupId>
|
||||||
|
<artifactId>dddmodules</artifactId>
|
||||||
|
<version>1.0</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baeldung.dddmodules.sharedkernel</groupId>
|
||||||
|
<artifactId>sharedkernel</artifactId>
|
||||||
|
<version>${appmodules.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>${source.version}</source>
|
||||||
|
<target>${target.version}</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<source.version>9</source.version>
|
||||||
|
<target.version>9</target.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,37 @@
|
||||||
|
package com.baeldung.dddmodules.shippingcontext.model;
|
||||||
|
|
||||||
|
public class PackageItem {
|
||||||
|
private int productId;
|
||||||
|
private float weight;
|
||||||
|
private float estimatedValue;
|
||||||
|
|
||||||
|
public PackageItem(int productId, float weight, float estimatedValue) {
|
||||||
|
this.productId = productId;
|
||||||
|
this.weight = weight;
|
||||||
|
this.estimatedValue = estimatedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getProductId() {
|
||||||
|
return productId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProductId(int productId) {
|
||||||
|
this.productId = productId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getWeight() {
|
||||||
|
return weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWeight(float weight) {
|
||||||
|
this.weight = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getEstimatedValue() {
|
||||||
|
return estimatedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEstimatedValue(float estimatedValue) {
|
||||||
|
this.estimatedValue = estimatedValue;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package com.baeldung.dddmodules.shippingcontext.model;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Parcel {
|
||||||
|
private int orderId;
|
||||||
|
private String address;
|
||||||
|
private String trackingId;
|
||||||
|
private List<PackageItem> packageItems;
|
||||||
|
|
||||||
|
public Parcel(int orderId, String address, List<PackageItem> packageItems) {
|
||||||
|
this.orderId = orderId;
|
||||||
|
this.address = address;
|
||||||
|
this.packageItems = packageItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float calculateTotalWeight() {
|
||||||
|
return packageItems.stream().map(PackageItem::getWeight)
|
||||||
|
.reduce(0F, Float::sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isTaxable() {
|
||||||
|
return calculateEstimatedValue() > 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float calculateEstimatedValue() {
|
||||||
|
return packageItems.stream().map(PackageItem::getWeight)
|
||||||
|
.reduce(0F, Float::sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOrderId() {
|
||||||
|
return orderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOrderId(int orderId) {
|
||||||
|
this.orderId = orderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTrackingId() {
|
||||||
|
return trackingId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTrackingId(String trackingId) {
|
||||||
|
this.trackingId = trackingId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAddress() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAddress(String address) {
|
||||||
|
this.address = address;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package com.baeldung.dddmodules.shippingcontext.model;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ShippableOrder {
|
||||||
|
private int orderId;
|
||||||
|
private String address;
|
||||||
|
private List<PackageItem> packageItems;
|
||||||
|
|
||||||
|
public ShippableOrder(int orderId, List<PackageItem> packageItems) {
|
||||||
|
this.orderId = orderId;
|
||||||
|
this.packageItems = packageItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOrderId() {
|
||||||
|
return orderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOrderId(int orderId) {
|
||||||
|
this.orderId = orderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PackageItem> getPackageItems() {
|
||||||
|
return packageItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPackageItems(List<PackageItem> packageItems) {
|
||||||
|
this.packageItems = packageItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAddress() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAddress(String address) {
|
||||||
|
this.address = address;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.baeldung.dddmodules.shippingcontext.repository;
|
||||||
|
|
||||||
|
import com.baeldung.dddmodules.shippingcontext.model.ShippableOrder;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface ShippingOrderRepository {
|
||||||
|
Optional<ShippableOrder> findShippableOrder(int orderId);
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package com.baeldung.dddmodules.shippingcontext.service;
|
||||||
|
|
||||||
|
import com.baeldung.dddmodules.sharedkernel.events.ApplicationEvent;
|
||||||
|
import com.baeldung.dddmodules.sharedkernel.events.EventBus;
|
||||||
|
import com.baeldung.dddmodules.sharedkernel.events.EventSubscriber;
|
||||||
|
import com.baeldung.dddmodules.shippingcontext.model.Parcel;
|
||||||
|
import com.baeldung.dddmodules.shippingcontext.model.ShippableOrder;
|
||||||
|
import com.baeldung.dddmodules.shippingcontext.repository.ShippingOrderRepository;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class ParcelShippingService implements ShippingService {
|
||||||
|
public static final String EVENT_ORDER_READY_FOR_SHIPMENT = "OrderReadyForShipmentEvent";
|
||||||
|
private ShippingOrderRepository orderRepository;
|
||||||
|
private EventBus eventBus;
|
||||||
|
private Map<Integer, Parcel> shippedParcels = new HashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shipOrder(int orderId) {
|
||||||
|
Optional<ShippableOrder> order = this.orderRepository.findShippableOrder(orderId);
|
||||||
|
order.ifPresent(completedOrder -> {
|
||||||
|
Parcel parcel = new Parcel(completedOrder.getOrderId(), completedOrder.getAddress(), completedOrder.getPackageItems());
|
||||||
|
if (parcel.isTaxable()) {
|
||||||
|
// Calculate additional taxes
|
||||||
|
}
|
||||||
|
// Ship parcel
|
||||||
|
this.shippedParcels.put(completedOrder.getOrderId(), parcel);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void listenToOrderEvents() {
|
||||||
|
this.eventBus.subscribe(EVENT_ORDER_READY_FOR_SHIPMENT, new EventSubscriber() {
|
||||||
|
@Override
|
||||||
|
public <E extends ApplicationEvent> void onEvent(E event) {
|
||||||
|
shipOrder(Integer.parseInt(event.getPayloadValue("order_id")));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Parcel> getParcelByOrderId(int orderId) {
|
||||||
|
return Optional.ofNullable(this.shippedParcels.get(orderId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOrderRepository(ShippingOrderRepository orderRepository) {
|
||||||
|
this.orderRepository = orderRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventBus getEventBus() {
|
||||||
|
return eventBus;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEventBus(EventBus eventBus) {
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.baeldung.dddmodules.shippingcontext.service;
|
||||||
|
|
||||||
|
import com.baeldung.dddmodules.sharedkernel.service.ApplicationService;
|
||||||
|
import com.baeldung.dddmodules.shippingcontext.model.Parcel;
|
||||||
|
import com.baeldung.dddmodules.shippingcontext.repository.ShippingOrderRepository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface ShippingService extends ApplicationService {
|
||||||
|
void shipOrder(int orderId);
|
||||||
|
|
||||||
|
void listenToOrderEvents();
|
||||||
|
|
||||||
|
Optional<Parcel> getParcelByOrderId(int orderId);
|
||||||
|
|
||||||
|
void setOrderRepository(ShippingOrderRepository orderRepository);
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
module com.baeldung.dddmodules.shippingcontext {
|
||||||
|
requires com.baeldung.dddmodules.sharedkernel;
|
||||||
|
exports com.baeldung.dddmodules.shippingcontext.service;
|
||||||
|
exports com.baeldung.dddmodules.shippingcontext.model;
|
||||||
|
exports com.baeldung.dddmodules.shippingcontext.repository;
|
||||||
|
provides com.baeldung.dddmodules.shippingcontext.service.ShippingService
|
||||||
|
with com.baeldung.dddmodules.shippingcontext.service.ParcelShippingService;
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'java'
|
||||||
|
apply plugin: 'maven'
|
||||||
|
|
||||||
|
group = 'com.baeldung'
|
||||||
|
|
||||||
|
// by default, pom's artifactId is taken from the directory name
|
||||||
|
|
||||||
|
version = '0.0.1-SNAPSHOT'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile('org.slf4j:slf4j-api')
|
||||||
|
testCompile('junit:junit')
|
||||||
|
}
|
||||||
|
|
||||||
|
install {
|
||||||
|
repositories {
|
||||||
|
mavenInstaller {
|
||||||
|
pom.version = '0.0.1-maven-SNAPSHOT'
|
||||||
|
pom.groupId = 'com.baeldung.sample'
|
||||||
|
pom.artifactId = 'gradle-maven-converter'
|
||||||
|
pom.project {
|
||||||
|
inceptionYear '2020'
|
||||||
|
licenses {
|
||||||
|
license {
|
||||||
|
name 'My License'
|
||||||
|
url 'http://www.mycompany.com/licenses/license.txt'
|
||||||
|
distribution 'repo'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pom.whenConfigured {pom ->
|
||||||
|
pom.dependencies.find {dep -> dep.groupId == 'junit' && dep.artifactId == 'junit' }.optional = true
|
||||||
|
}
|
||||||
|
pom.writeTo("${mavenPomDir}/${project.group}/${project.name}/pom.xml")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +0,0 @@
|
||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="Remote Debugger" type="Remote">
|
|
||||||
<option name="USE_SOCKET_TRANSPORT" value="true" />
|
|
||||||
<option name="SERVER_MODE" value="false" />
|
|
||||||
<option name="SHMEM_ADDRESS" />
|
|
||||||
<option name="HOST" value="localhost" />
|
|
||||||
<option name="PORT" value="5005" />
|
|
||||||
<option name="AUTO_RESTART" value="false" />
|
|
||||||
<RunnerSettings RunnerId="Debug">
|
|
||||||
<option name="DEBUG_PORT" value="5005" />
|
|
||||||
<option name="LOCAL" value="false" />
|
|
||||||
</RunnerSettings>
|
|
||||||
<method v="2" />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
|
@ -1,2 +0,0 @@
|
||||||
If you have not previously done so, please fill out and
|
|
||||||
submit the https://cla.pivotal.io/sign/spring[Contributor License Agreement].
|
|
|
@ -1,16 +0,0 @@
|
||||||
All code in this repository is:
|
|
||||||
=======================================================================
|
|
||||||
Copyright (c) 2013 GoPivotal, Inc. All Rights Reserved
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Except where otherwise noted, this work is licensed under https://creativecommons.org/licenses/by-nd/3.0/
|
|
|
@ -1,3 +0,0 @@
|
||||||
### Relevant Articles:
|
|
||||||
|
|
||||||
- [Remote Debugging with IntelliJ IDEA](https://www.baeldung.com/intellij-remote-debugging)
|
|
|
@ -1,50 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<groupId>com.baeldung</groupId>
|
|
||||||
<artifactId>gs-scheduling-tasks</artifactId>
|
|
||||||
<version>0.1.0</version>
|
|
||||||
<name>gs-scheduling-tasks</name>
|
|
||||||
|
|
||||||
<parent>
|
|
||||||
<groupId>com.baeldung</groupId>
|
|
||||||
<artifactId>parent-boot-2</artifactId>
|
|
||||||
<version>0.0.1-SNAPSHOT</version>
|
|
||||||
<relativePath>../../parent-boot-2</relativePath>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<java.version>1.8</java.version>
|
|
||||||
<awaitility.version>3.1.2</awaitility.version>
|
|
||||||
<spring-boot.version>2.1.6.RELEASE</spring-boot.version>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-test</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.awaitility</groupId>
|
|
||||||
<artifactId>awaitility</artifactId>
|
|
||||||
<version>${awaitility.version}</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</project>
|
|
|
@ -1,38 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2012-2015 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package hello;
|
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class ScheduledTasks {
|
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);
|
|
||||||
|
|
||||||
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
|
|
||||||
|
|
||||||
@Scheduled(fixedRate = 5000)
|
|
||||||
public void reportCurrentTime() {
|
|
||||||
log.info("The time is now {}", dateFormat.format(new Date()));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,3 +11,4 @@ Remember, for advanced libraries like [Jackson](/jackson) and [JUnit](/testing-m
|
||||||
- [Parsing Command-Line Parameters with JCommander](https://www.baeldung.com/jcommander-parsing-command-line-parameters)
|
- [Parsing Command-Line Parameters with JCommander](https://www.baeldung.com/jcommander-parsing-command-line-parameters)
|
||||||
- [Guide to the Cactoos Library](https://www.baeldung.com/java-cactoos)
|
- [Guide to the Cactoos Library](https://www.baeldung.com/java-cactoos)
|
||||||
- [Parsing Command-Line Parameters with Airline](https://www.baeldung.com/java-airline)
|
- [Parsing Command-Line Parameters with Airline](https://www.baeldung.com/java-airline)
|
||||||
|
- [Introduction to cache2k](https://www.baeldung.com/java-cache2k)
|
||||||
|
|
|
@ -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>
|
||||||
|
@ -81,6 +103,36 @@
|
||||||
<url>https://jitpack.io</url>
|
<url>https://jitpack.io</url>
|
||||||
</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>
|
||||||
|
@ -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>
|
||||||
|
|
|
@ -23,6 +23,16 @@
|
||||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-jdbc</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-java</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.h2database</groupId>
|
<groupId>com.h2database</groupId>
|
||||||
<artifactId>h2</artifactId>
|
<artifactId>h2</artifactId>
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
package hello;
|
package com.baeldung.storedprocedure;
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@EnableScheduling
|
public class StoredProcedureApplication {
|
||||||
public class Application {
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(Application.class);
|
SpringApplication.run(StoredProcedureApplication.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package com.baeldung.storedprocedure.controller;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import com.baeldung.storedprocedure.entity.Car;
|
||||||
|
import com.baeldung.storedprocedure.service.CarService;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class CarController {
|
||||||
|
@Autowired
|
||||||
|
private CarService carService;
|
||||||
|
|
||||||
|
@GetMapping(path = "/modelcount")
|
||||||
|
public long getTotalCarsByModel(@RequestParam("model") String model) {
|
||||||
|
return carService.getTotalCarsByModel(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/modelcountP")
|
||||||
|
public long getTotalCarsByModelProcedureName(@RequestParam("model") String model) {
|
||||||
|
return carService.getTotalCarsByModelProcedureName(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/modelcountV")
|
||||||
|
public long getTotalCarsByModelVaue(@RequestParam("model") String model) {
|
||||||
|
return carService.getTotalCarsByModelValue(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/modelcountEx")
|
||||||
|
public long getTotalCarsByModelExplicit(@RequestParam("model") String model) {
|
||||||
|
return carService.getTotalCarsByModelExplicit(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/modelcountEn")
|
||||||
|
public long getTotalCarsByModelEntity(@RequestParam("model") String model) {
|
||||||
|
return carService.getTotalCarsByModelEntity(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/carsafteryear")
|
||||||
|
public List<Car> findCarsAfterYear(@RequestParam("year") Integer year) {
|
||||||
|
return carService.findCarsAfterYear(year);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package com.baeldung.storedprocedure.entity;
|
||||||
|
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.GenerationType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.NamedStoredProcedureQuery;
|
||||||
|
import javax.persistence.StoredProcedureParameter;
|
||||||
|
import javax.persistence.ParameterMode;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@NamedStoredProcedureQuery(name = "Car.getTotalCardsbyModelEntity", procedureName = "GET_TOTAL_CARS_BY_MODEL", parameters = {
|
||||||
|
@StoredProcedureParameter(mode = ParameterMode.IN, name = "model_in", type = String.class),
|
||||||
|
@StoredProcedureParameter(mode = ParameterMode.OUT, name = "count_out", type = Integer.class) })
|
||||||
|
|
||||||
|
public class Car {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
@Column
|
||||||
|
private long id;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String model;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private Integer year;
|
||||||
|
|
||||||
|
public long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getModel() {
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getYear() {
|
||||||
|
return year;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.baeldung.storedprocedure.repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.jpa.repository.query.Procedure;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import com.baeldung.storedprocedure.entity.Car;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface CarRepository extends JpaRepository<Car, Integer> {
|
||||||
|
|
||||||
|
@Procedure
|
||||||
|
int GET_TOTAL_CARS_BY_MODEL(String model);
|
||||||
|
|
||||||
|
@Procedure("GET_TOTAL_CARS_BY_MODEL")
|
||||||
|
int getTotalCarsByModel(String model);
|
||||||
|
|
||||||
|
@Procedure(procedureName = "GET_TOTAL_CARS_BY_MODEL")
|
||||||
|
int getTotalCarsByModelProcedureName(String model);
|
||||||
|
|
||||||
|
@Procedure(value = "GET_TOTAL_CARS_BY_MODEL")
|
||||||
|
int getTotalCarsByModelValue(String model);
|
||||||
|
|
||||||
|
@Procedure(name = "Car.getTotalCardsbyModelEntity")
|
||||||
|
int getTotalCarsByModelEntiy(@Param("model_in") String model);
|
||||||
|
|
||||||
|
@Query(value = "CALL FIND_CARS_AFTER_YEAR(:year_in);", nativeQuery = true)
|
||||||
|
List<Car> findCarsAfterYear(@Param("year_in") Integer year_in);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package com.baeldung.storedprocedure.service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.baeldung.storedprocedure.entity.Car;
|
||||||
|
import com.baeldung.storedprocedure.repository.CarRepository;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class CarService {
|
||||||
|
@Autowired
|
||||||
|
private CarRepository carRepository;
|
||||||
|
|
||||||
|
public int getTotalCarsByModel(String model) {
|
||||||
|
return carRepository.getTotalCarsByModel(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalCarsByModelProcedureName(String model) {
|
||||||
|
return carRepository.getTotalCarsByModelProcedureName(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalCarsByModelValue(String model) {
|
||||||
|
return carRepository.getTotalCarsByModelValue(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalCarsByModelExplicit(String model) {
|
||||||
|
return carRepository.GET_TOTAL_CARS_BY_MODEL(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalCarsByModelEntity(String model) {
|
||||||
|
return carRepository.getTotalCarsByModelEntiy(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Car> findCarsAfterYear(Integer year) {
|
||||||
|
return carRepository.findCarsAfterYear(year);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +1,5 @@
|
||||||
spring.jpa.show-sql=true
|
spring.jpa.show-sql=true
|
||||||
|
#MySql
|
||||||
|
spring.datasource.url=jdbc:mysql://localhost:3306/baeldung
|
||||||
|
spring.datasource.username=baeldung
|
||||||
|
spring.datasource.password=baeldung
|
|
@ -0,0 +1,27 @@
|
||||||
|
DROP TABLE IF EXISTS car;
|
||||||
|
|
||||||
|
CREATE TABLE car (id int(10) NOT NULL AUTO_INCREMENT,
|
||||||
|
model varchar(50) NOT NULL,
|
||||||
|
year int(4) NOT NULL,
|
||||||
|
PRIMARY KEY (id));
|
||||||
|
|
||||||
|
INSERT INTO car (model, year) VALUES ('BMW', 2000);
|
||||||
|
INSERT INTO car (model, year) VALUES ('BENZ', 2010);
|
||||||
|
INSERT INTO car (model, year) VALUES ('PORCHE', 2005);
|
||||||
|
INSERT INTO car (model, year) VALUES ('PORCHE', 2004);
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
|
||||||
|
DROP PROCEDURE IF EXISTS FIND_CARS_AFTER_YEAR$$
|
||||||
|
CREATE PROCEDURE FIND_CARS_AFTER_YEAR(IN year_in INT)
|
||||||
|
BEGIN
|
||||||
|
SELECT * FROM car WHERE year >= year_in ORDER BY year;
|
||||||
|
END$$
|
||||||
|
|
||||||
|
DROP PROCEDURE IF EXISTS GET_TOTAL_CARS_BY_MODEL$$
|
||||||
|
CREATE PROCEDURE GET_TOTAL_CARS_BY_MODEL(IN model_in VARCHAR(50), OUT count_out INT)
|
||||||
|
BEGIN
|
||||||
|
SELECT COUNT(*) into count_out from car WHERE model = model_in;
|
||||||
|
END$$
|
||||||
|
|
||||||
|
DELIMITER ;
|
|
@ -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")
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue