Compare commits

..

2 Commits

42 changed files with 329 additions and 1619 deletions

View File

@ -1749,42 +1749,6 @@
"review",
"code"
]
},
{
"login": "interactwithankush",
"name": "interactwithankush",
"avatar_url": "https://avatars.githubusercontent.com/u/18613127?v=4",
"profile": "https://github.com/interactwithankush",
"contributions": [
"code"
]
},
{
"login": "yuhangbin",
"name": "CharlieYu",
"avatar_url": "https://avatars.githubusercontent.com/u/17566866?v=4",
"profile": "https://github.com/yuhangbin",
"contributions": [
"code"
]
},
{
"login": "Leisterbecker",
"name": "Leisterbecker",
"avatar_url": "https://avatars.githubusercontent.com/u/20650323?v=4",
"profile": "https://github.com/Leisterbecker",
"contributions": [
"code"
]
},
{
"login": "castleKing1997",
"name": "DragonDreamer",
"avatar_url": "https://avatars.githubusercontent.com/u/35420129?v=4",
"profile": "http://rosaecrucis.cn",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,

View File

@ -10,7 +10,7 @@
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
[![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-196-orange.svg?style=flat-square)](#contributors-)
[![All Contributors](https://img.shields.io/badge/all_contributors-192-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
<br/>
@ -322,10 +322,6 @@ This project is licensed under the terms of the MIT license.
<td align="center"><a href="https://www.linkedin.com/in/abhinav-vashisth-06613b208/"><img src="https://avatars.githubusercontent.com/u/89785800?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Abhinav Vashisth</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=vashisthabhinav" title="Documentation">📖</a></td>
<td align="center"><a href="http://no website"><img src="https://avatars.githubusercontent.com/u/47126749?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kevin</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3AKevinyl3" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://github.com/Shrirang97"><img src="https://avatars.githubusercontent.com/u/28738668?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Shrirang</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3AShrirang97" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/iluwatar/java-design-patterns/commits?author=Shrirang97" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/interactwithankush"><img src="https://avatars.githubusercontent.com/u/18613127?v=4?s=100" width="100px;" alt=""/><br /><sub><b>interactwithankush</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=interactwithankush" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/yuhangbin"><img src="https://avatars.githubusercontent.com/u/17566866?v=4?s=100" width="100px;" alt=""/><br /><sub><b>CharlieYu</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=yuhangbin" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/Leisterbecker"><img src="https://avatars.githubusercontent.com/u/20650323?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Leisterbecker</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=Leisterbecker" title="Code">💻</a></td>
<td align="center"><a href="http://rosaecrucis.cn"><img src="https://avatars.githubusercontent.com/u/35420129?v=4?s=100" width="100px;" alt=""/><br /><sub><b>DragonDreamer</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=castleKing1997" title="Code">💻</a></td>
</tr>
</table>

View File

@ -18,10 +18,10 @@ couldn't otherwise because of incompatible interfaces.
## Explanation
Real-world example
Real world example
> Consider that you have some pictures on your memory card and you need to transfer them to your computer. To transfer them, you need some kind of adapter that is compatible with your computer ports so that you can attach a memory card to your computer. In this case card reader is an adapter.
> Another example would be the famous power adapter; a three-legged plug can't be connected to a two-pronged outlet, it needs to use a power adapter that makes it compatible with the two-pronged outlets.
> Consider that you have some pictures in your memory card and you need to transfer them to your computer. In order to transfer them you need some kind of adapter that is compatible with your computer ports so that you can attach memory card to your computer. In this case card reader is an adapter.
> Another example would be the famous power adapter; a three legged plug can't be connected to a two pronged outlet, it needs to use a power adapter that makes it compatible with the two pronged outlet.
> Yet another example would be a translator translating words spoken by one person to another
In plain words
@ -36,7 +36,7 @@ Wikipedia says
Consider a captain that can only use rowing boats and cannot sail at all.
First, we have interfaces `RowingBoat` and `FishingBoat`
First we have interfaces `RowingBoat` and `FishingBoat`
```java
public interface RowingBoat {
@ -68,7 +68,7 @@ public class Captain {
}
```
Now let's say the pirates are coming and our captain needs to escape but there is only a fishing boat available. We need to create an adapter that allows the captain to operate the fishing boat with his rowing boat skills.
Now let's say the pirates are coming and our captain needs to escape but there is only fishing boat available. We need to create an adapter that allows the captain to operate the fishing boat with his rowing boat skills.
```java
@Slf4j
@ -100,10 +100,10 @@ captain.row();
## Applicability
Use the Adapter pattern when
* You want to use an existing class, and its interface does not match the one you need
* You want to create a reusable class that cooperates with unrelated or unforeseen classes, that is, classes that don't necessarily have compatible interfaces
* You need to use several existing subclasses, but it's impractical to adapt their interface by subclassing everyone. An object adapter can adapt the interface of its parent class.
* Most of the applications using third-party libraries use adapters as a middle layer between the application and the 3rd party library to decouple the application from the library. If another library has to be used only an adapter for the new library is required without having to change the application code.
* you want to use an existing class, and its interface does not match the one you need
* you want to create a reusable class that cooperates with unrelated or unforeseen classes, that is, classes that don't necessarily have compatible interfaces
* you need to use several existing subclasses, but it's impractical to adapt their interface by subclassing every one. An object adapter can adapt the interface of its parent class.
* most of the applications using third party libraries use adapters as a middle layer between the application and the 3rd party library to decouple the application from the library. If another library has to be used only an adapter for the new library is required without having to change the application code.
## Tutorials
@ -114,17 +114,17 @@ Use the Adapter pattern when
## Consequences
Class and object adapters have different trade-offs. A class adapter
* Adapts Adaptee to Target by committing to a concrete Adaptee class. As a consequence, a class adapter wont work when we want to adapt a class and all its subclasses.
* Lets Adapter override some of Adaptees behavior since Adapter is a subclass of Adaptee.
* Introduces only one object, and no additional pointer indirection is needed to get to the adaptee.
* adapts Adaptee to Target by committing to a concrete Adaptee class. As a consequence, a class adapter wont work when we want to adapt a class and all its subclasses.
* lets Adapter override some of Adaptees behavior, since Adapter is a subclass of Adaptee.
* introduces only one object, and no additional pointer indirection is needed to get to the adaptee.
An object adapter
* Lets a single Adapter work with many Adaptees—that is, the Adaptee itself and all of its subclasses (if any). The Adapter can also add functionality to all Adaptees at once.
* Makes it harder to override Adaptee behavior. It will require subclassing Adaptee and making the Adapter refer to the subclass rather than the Adaptee itself.
* lets a single Adapter work with many Adaptees—that is, the Adaptee itself and all of its subclasses (if any). The Adapter can also add functionality to all Adaptees at once.
* makes it harder to override Adaptee behavior. It will require subclassing Adaptee and making Adapter refer to the subclass rather than the Adaptee itself.
## Real-world examples
## Known uses
* [java.util.Arrays#asList()](http://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#asList%28T...%29)
* [java.util.Collections#list()](https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#list-java.util.Enumeration-)

View File

@ -33,7 +33,7 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
/**
* Tests for the adapter pattern.
* Test class
*/
class AdapterPatternTest {

View File

@ -33,7 +33,9 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
class AppTest {
/**
* Check whether the execution of the main method in {@link App}
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
* throws an exception.
*/

View File

@ -4,7 +4,6 @@ title: Claim Check Pattern
folder: cloud-claim-check-pattern
permalink: /patterns/cloud-claim-check-pattern/
categories: Cloud
language: en
tags:
- Cloud distributed
- Microservices

View File

@ -36,6 +36,9 @@ public class Wizard {
private final Deque<Runnable> undoStack = new LinkedList<>();
private final Deque<Runnable> redoStack = new LinkedList<>();
public Wizard() {
}
/**
* Cast spell.
*/

View File

@ -90,13 +90,6 @@ public class Commander {
private static final Logger LOG = LoggerFactory.getLogger(Commander.class);
//we could also have another db where it stores all orders
private static final String ORDER_ID = "Order {}";
private static final String REQUEST_ID = " request Id: {}";
private static final String ERROR_CONNECTING_MSG_SVC =
": Error in connecting to messaging service ";
private static final String TRY_CONNECTING_MSG_SVC =
": Trying to connect to messaging service..";
Commander(EmployeeHandle empDb, PaymentService paymentService, ShippingService shippingService,
MessagingService messagingService, QueueDatabase qdb, int numOfRetries,
long retryDuration, long queueTime, long queueTaskTime, long paymentTime,
@ -125,17 +118,17 @@ public class Commander {
Retry.Operation op = (l) -> {
if (!l.isEmpty()) {
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
LOG.debug(ORDER_ID + ": Error in connecting to shipping service, "
+ "trying again..", order.id);
LOG.debug("Order " + order.id + ": Error in connecting to shipping service, "
+ "trying again..");
} else {
LOG.debug(ORDER_ID + ": Error in creating shipping request..", order.id);
LOG.debug("Order " + order.id + ": Error in creating shipping request..");
}
throw l.remove(0);
}
String transactionId = shippingService.receiveRequest(order.item, order.user.address);
//could save this transaction id in a db too
LOG.info(ORDER_ID + ": Shipping placed successfully, transaction id: {}",
order.id, transactionId);
LOG.info("Order " + order.id + ": Shipping placed successfully, transaction id: "
+ transactionId);
LOG.info("Order has been placed and will be shipped to you. Please wait while we make your"
+ " payment... ");
sendPaymentRequest(order);
@ -145,19 +138,19 @@ public class Commander {
LOG.info("Shipping is currently not possible to your address. We are working on the problem"
+ " and will get back to you asap.");
finalSiteMsgShown = true;
LOG.info(ORDER_ID + ": Shipping not possible to address, trying to add problem "
+ "to employee db..", order.id);
LOG.info("Order " + order.id + ": Shipping not possible to address, trying to add problem "
+ "to employee db..");
employeeHandleIssue(o);
} else if (ItemUnavailableException.class.isAssignableFrom(err.getClass())) {
LOG.info("This item is currently unavailable. We will inform you as soon as the item "
+ "becomes available again.");
finalSiteMsgShown = true;
LOG.info(ORDER_ID + ": Item {}" + " unavailable, trying to add "
+ "problem to employee handle..", order.id, order.item);
LOG.info("Order " + order.id + ": Item " + order.item + " unavailable, trying to add "
+ "problem to employee handle..");
employeeHandleIssue(o);
} else {
LOG.info("Sorry, there was a problem in creating your order. Please try later.");
LOG.error(ORDER_ID + ": Shipping service unavailable, order not placed..", order.id);
LOG.error("Order " + order.id + ": Shipping service unavailable, order not placed..");
finalSiteMsgShown = true;
}
};
@ -171,7 +164,7 @@ public class Commander {
if (order.paid.equals(PaymentStatus.TRYING)) {
order.paid = PaymentStatus.NOT_DONE;
sendPaymentFailureMessage(order);
LOG.error(ORDER_ID + ": Payment time for order over, failed and returning..", order.id);
LOG.error("Order " + order.id + ": Payment time for order over, failed and returning..");
} //if succeeded or failed, would have been dequeued, no attempt to make payment
return;
}
@ -180,18 +173,17 @@ public class Commander {
Retry.Operation op = (l) -> {
if (!l.isEmpty()) {
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
LOG.debug(ORDER_ID + ": Error in connecting to payment service,"
+ " trying again..", order.id);
LOG.debug("Order " + order.id + ": Error in connecting to payment service,"
+ " trying again..");
} else {
LOG.debug(ORDER_ID + ": Error in creating payment request..", order.id);
LOG.debug("Order " + order.id + ": Error in creating payment request..");
}
throw l.remove(0);
}
if (order.paid.equals(PaymentStatus.TRYING)) {
var transactionId = paymentService.receiveRequest(order.price);
order.paid = PaymentStatus.DONE;
LOG.info(ORDER_ID + ": Payment successful, transaction Id: {}",
order.id, transactionId);
LOG.info("Order " + order.id + ": Payment successful, transaction Id: " + transactionId);
if (!finalSiteMsgShown) {
LOG.info("Payment made successfully, thank you for shopping with us!!");
finalSiteMsgShown = true;
@ -207,7 +199,7 @@ public class Commander {
+ "Meanwhile, your order has been converted to COD and will be shipped.");
finalSiteMsgShown = true;
}
LOG.error(ORDER_ID + ": Payment details incorrect, failed..", order.id);
LOG.error("Order " + order.id + ": Payment details incorrect, failed..");
o.paid = PaymentStatus.NOT_DONE;
sendPaymentFailureMessage(o);
} else {
@ -217,7 +209,7 @@ public class Commander {
+ "asap. Don't worry, your order has been placed and will be shipped.");
finalSiteMsgShown = true;
}
LOG.warn(ORDER_ID + ": Payment error, going to queue..", order.id);
LOG.warn("Order " + order.id + ": Payment error, going to queue..");
sendPaymentPossibleErrorMsg(o);
}
if (o.paid.equals(PaymentStatus.TRYING) && System
@ -242,7 +234,7 @@ public class Commander {
if (System.currentTimeMillis() - qt.order.createdTime >= this.queueTime) {
// since payment time is lesser than queuetime it would have already failed..
// additional check not needed
LOG.trace(ORDER_ID + ": Queue time for order over, failed..", qt.order.id);
LOG.trace("Order " + qt.order.id + ": Queue time for order over, failed..");
return;
} else if (qt.taskType.equals(TaskType.PAYMENT) && !qt.order.paid.equals(PaymentStatus.TRYING)
|| qt.taskType.equals(TaskType.MESSAGING) && (qt.messageType == 1
@ -250,30 +242,30 @@ public class Commander {
|| qt.order.messageSent.equals(MessageSent.PAYMENT_FAIL)
|| qt.order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL))
|| qt.taskType.equals(TaskType.EMPLOYEE_DB) && qt.order.addedToEmployeeHandle) {
LOG.trace(ORDER_ID + ": Not queueing task since task already done..", qt.order.id);
LOG.trace("Order " + qt.order.id + ": Not queueing task since task already done..");
return;
}
var list = queue.exceptionsList;
Thread t = new Thread(() -> {
Retry.Operation op = (list1) -> {
if (!list1.isEmpty()) {
LOG.warn(ORDER_ID + ": Error in connecting to queue db, trying again..", qt.order.id);
LOG.warn("Order " + qt.order.id + ": Error in connecting to queue db, trying again..");
throw list1.remove(0);
}
queue.add(qt);
queueItems++;
LOG.info(ORDER_ID + ": {}" + " task enqueued..", qt.order.id, qt.getType());
LOG.info("Order " + qt.order.id + ": " + qt.getType() + " task enqueued..");
tryDoingTasksInQueue();
};
Retry.HandleErrorIssue<QueueTask> handleError = (qt1, err) -> {
if (qt1.taskType.equals(TaskType.PAYMENT)) {
qt1.order.paid = PaymentStatus.NOT_DONE;
sendPaymentFailureMessage(qt1.order);
LOG.error(ORDER_ID + ": Unable to enqueue payment task,"
+ " payment failed..", qt1.order.id);
LOG.error("Order " + qt1.order.id + ": Unable to enqueue payment task,"
+ " payment failed..");
}
LOG.error(ORDER_ID + ": Unable to enqueue task of type {}"
+ ", trying to add to employee handle..", qt1.order.id, qt1.getType());
LOG.error("Order " + qt1.order.id + ": Unable to enqueue task of type " + qt1.getType()
+ ", trying to add to employee handle..");
employeeHandleIssue(qt1.order);
};
var r = new Retry<>(op, handleError, numOfRetries, retryDuration,
@ -336,7 +328,7 @@ public class Commander {
private void sendSuccessMessage(Order order) {
if (System.currentTimeMillis() - order.createdTime >= this.messageTime) {
LOG.trace(ORDER_ID + ": Message time for order over, returning..", order.id);
LOG.trace("Order " + order.id + ": Message time for order over, returning..");
return;
}
var list = messagingService.exceptionsList;
@ -362,8 +354,8 @@ public class Commander {
&& System.currentTimeMillis() - o.createdTime < messageTime) {
var qt = new QueueTask(order, TaskType.MESSAGING, 2);
updateQueue(qt);
LOG.info(ORDER_ID + ": Error in sending Payment Success message, trying to"
+ " queue task and add to employee handle..", order.id);
LOG.info("Order " + order.id + ": Error in sending Payment Success message, trying to"
+ " queue task and add to employee handle..");
employeeHandleIssue(order);
}
}
@ -372,11 +364,11 @@ public class Commander {
return (l) -> {
if (!l.isEmpty()) {
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
LOG.debug(ORDER_ID + ERROR_CONNECTING_MSG_SVC
+ "(Payment Success msg), trying again..", order.id);
LOG.debug("Order " + order.id + ": Error in connecting to messaging service "
+ "(Payment Success msg), trying again..");
} else {
LOG.debug(ORDER_ID + ": Error in creating Payment Success"
+ " messaging request..", order.id);
LOG.debug("Order " + order.id + ": Error in creating Payment Success"
+ " messaging request..");
}
throw l.remove(0);
}
@ -384,15 +376,15 @@ public class Commander {
&& !order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) {
var requestId = messagingService.receiveRequest(2);
order.messageSent = MessageSent.PAYMENT_SUCCESSFUL;
LOG.info(ORDER_ID + ": Payment Success message sent,"
+ REQUEST_ID, order.id, requestId);
LOG.info("Order " + order.id + ": Payment Success message sent,"
+ " request Id: " + requestId);
}
};
}
private void sendPaymentFailureMessage(Order order) {
if (System.currentTimeMillis() - order.createdTime >= this.messageTime) {
LOG.trace(ORDER_ID + ": Message time for order over, returning..", order.id);
LOG.trace("Order " + order.id + ": Message time for order over, returning..");
return;
}
var list = messagingService.exceptionsList;
@ -420,8 +412,8 @@ public class Commander {
&& System.currentTimeMillis() - o.createdTime < messageTime) {
var qt = new QueueTask(order, TaskType.MESSAGING, 0);
updateQueue(qt);
LOG.warn(ORDER_ID + ": Error in sending Payment Failure message, "
+ "trying to queue task and add to employee handle..", order.id);
LOG.warn("Order " + order.id + ": Error in sending Payment Failure message, "
+ "trying to queue task and add to employee handle..");
employeeHandleIssue(o);
}
}
@ -429,11 +421,11 @@ public class Commander {
private void handlePaymentFailureRetryOperation(Order order, List<Exception> l) throws Exception {
if (!l.isEmpty()) {
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
LOG.debug(ORDER_ID + ERROR_CONNECTING_MSG_SVC
+ "(Payment Failure msg), trying again..", order.id);
LOG.debug("Order " + order.id + ": Error in connecting to messaging service "
+ "(Payment Failure msg), trying again..");
} else {
LOG.debug(ORDER_ID + ": Error in creating Payment Failure"
+ " message request..", order.id);
LOG.debug("Order " + order.id + ": Error in creating Payment Failure"
+ " message request..");
}
throw l.remove(0);
}
@ -441,8 +433,8 @@ public class Commander {
&& !order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) {
var requestId = messagingService.receiveRequest(0);
order.messageSent = MessageSent.PAYMENT_FAIL;
LOG.info(ORDER_ID + ": Payment Failure message sent successfully,"
+ REQUEST_ID, order.id, requestId);
LOG.info("Order " + order.id + ": Payment Failure message sent successfully,"
+ " request Id: " + requestId);
}
}
@ -477,7 +469,7 @@ public class Commander {
var qt = new QueueTask(order, TaskType.MESSAGING, 1);
updateQueue(qt);
LOG.warn("Order " + order.id + ": Error in sending Payment Error message, "
+ "trying to queue task and add to employee handle..");
+ "trying to queue task and add to employee handle..");
employeeHandleIssue(o);
}
}
@ -486,11 +478,11 @@ public class Commander {
throws Exception {
if (!l.isEmpty()) {
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
LOG.debug(ORDER_ID + ERROR_CONNECTING_MSG_SVC
+ "(Payment Error msg), trying again..", order.id);
LOG.debug("Order " + order.id + ": Error in connecting to messaging service "
+ "(Payment Error msg), trying again..");
} else {
LOG.debug(ORDER_ID + ": Error in creating Payment Error"
+ " messaging request..", order.id);
LOG.debug("Order " + order.id + ": Error in creating Payment Error"
+ " messaging request..");
}
throw l.remove(0);
}
@ -498,28 +490,28 @@ public class Commander {
.equals(MessageSent.NONE_SENT)) {
var requestId = messagingService.receiveRequest(1);
order.messageSent = MessageSent.PAYMENT_TRYING;
LOG.info(ORDER_ID + ": Payment Error message sent successfully,"
+ REQUEST_ID, order.id, requestId);
LOG.info("Order " + order.id + ": Payment Error message sent successfully,"
+ " request Id: " + requestId);
}
}
private void employeeHandleIssue(Order order) {
if (System.currentTimeMillis() - order.createdTime >= this.employeeTime) {
LOG.trace(ORDER_ID + ": Employee handle time for order over, returning..", order.id);
LOG.trace("Order " + order.id + ": Employee handle time for order over, returning..");
return;
}
var list = employeeDb.exceptionsList;
var t = new Thread(() -> {
Retry.Operation op = (l) -> {
if (!l.isEmpty()) {
LOG.warn(ORDER_ID + ": Error in connecting to employee handle,"
+ " trying again..", order.id);
LOG.warn("Order " + order.id + ": Error in connecting to employee handle,"
+ " trying again..");
throw l.remove(0);
}
if (!order.addedToEmployeeHandle) {
employeeDb.receiveRequest(order);
order.addedToEmployeeHandle = true;
LOG.info(ORDER_ID + ": Added order to employee database", order.id);
LOG.info("Order " + order.id + ": Added order to employee database");
}
};
Retry.HandleErrorIssue<Order> handleError = (o, err) -> {
@ -527,8 +519,8 @@ public class Commander {
.currentTimeMillis() - order.createdTime < employeeTime) {
var qt = new QueueTask(order, TaskType.EMPLOYEE_DB, -1);
updateQueue(qt);
LOG.warn(ORDER_ID + ": Error in adding to employee db,"
+ " trying to queue task..", order.id);
LOG.warn("Order " + order.id + ": Error in adding to employee db,"
+ " trying to queue task..");
}
};
var r = new Retry<>(op, handleError, numOfRetries, retryDuration,
@ -546,51 +538,51 @@ public class Commander {
if (queueItems != 0) {
var qt = queue.peek(); //this should probably be cloned here
//this is why we have retry for doTasksInQueue
LOG.trace(ORDER_ID + ": Started doing task of type {}", qt.order.id, qt.getType());
LOG.trace("Order " + qt.order.id + ": Started doing task of type " + qt.getType());
if (qt.getFirstAttemptTime() == -1) {
qt.setFirstAttemptTime(System.currentTimeMillis());
}
if (System.currentTimeMillis() - qt.getFirstAttemptTime() >= queueTaskTime) {
tryDequeue();
LOG.trace(ORDER_ID + ": This queue task of type {}"
+ " does not need to be done anymore (timeout), dequeue..", qt.order.id, qt.getType());
LOG.trace("Order " + qt.order.id + ": This queue task of type " + qt.getType()
+ " does not need to be done anymore (timeout), dequeue..");
} else {
if (qt.taskType.equals(TaskType.PAYMENT)) {
if (!qt.order.paid.equals(PaymentStatus.TRYING)) {
tryDequeue();
LOG.trace(ORDER_ID + ": This payment task already done, dequeueing..", qt.order.id);
LOG.trace("Order " + qt.order.id + ": This payment task already done, dequeueing..");
} else {
sendPaymentRequest(qt.order);
LOG.debug(ORDER_ID + ": Trying to connect to payment service..", qt.order.id);
LOG.debug("Order " + qt.order.id + ": Trying to connect to payment service..");
}
} else if (qt.taskType.equals(TaskType.MESSAGING)) {
if (qt.order.messageSent.equals(MessageSent.PAYMENT_FAIL)
|| qt.order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) {
tryDequeue();
LOG.trace(ORDER_ID + ": This messaging task already done, dequeue..", qt.order.id);
LOG.trace("Order " + qt.order.id + ": This messaging task already done, dequeue..");
} else if (qt.messageType == 1 && (!qt.order.messageSent.equals(MessageSent.NONE_SENT)
|| !qt.order.paid.equals(PaymentStatus.TRYING))) {
tryDequeue();
LOG.trace(ORDER_ID + ": This messaging task does not need to be done,"
+ " dequeue..", qt.order.id);
LOG.trace("Order " + qt.order.id + ": This messaging task does not need to be done,"
+ " dequeue..");
} else if (qt.messageType == 0) {
sendPaymentFailureMessage(qt.order);
LOG.debug(ORDER_ID + TRY_CONNECTING_MSG_SVC, qt.order.id);
LOG.debug("Order " + qt.order.id + ": Trying to connect to messaging service..");
} else if (qt.messageType == 1) {
sendPaymentPossibleErrorMsg(qt.order);
LOG.debug(ORDER_ID + TRY_CONNECTING_MSG_SVC, qt.order.id);
LOG.debug("Order " + qt.order.id + ": Trying to connect to messaging service..");
} else if (qt.messageType == 2) {
sendSuccessMessage(qt.order);
LOG.debug(ORDER_ID + TRY_CONNECTING_MSG_SVC, qt.order.id);
LOG.debug("Order " + qt.order.id + ": Trying to connect to messaging service..");
}
} else if (qt.taskType.equals(TaskType.EMPLOYEE_DB)) {
if (qt.order.addedToEmployeeHandle) {
tryDequeue();
LOG.trace(ORDER_ID + ": This employee handle task already done,"
+ " dequeue..", qt.order.id);
LOG.trace("Order " + qt.order.id + ": This employee handle task already done,"
+ " dequeue..");
} else {
employeeHandleIssue(qt.order);
LOG.debug(ORDER_ID + ": Trying to connect to employee handle..", qt.order.id);
LOG.debug("Order " + qt.order.id + ": Trying to connect to employee handle..");
}
}
}

View File

@ -1,526 +0,0 @@
package com.iluwatar.commander;
import com.iluwatar.commander.employeehandle.EmployeeDatabase;
import com.iluwatar.commander.employeehandle.EmployeeHandle;
import com.iluwatar.commander.exceptions.DatabaseUnavailableException;
import com.iluwatar.commander.exceptions.ItemUnavailableException;
import com.iluwatar.commander.exceptions.PaymentDetailsErrorException;
import com.iluwatar.commander.exceptions.ShippingNotPossibleException;
import com.iluwatar.commander.messagingservice.MessagingDatabase;
import com.iluwatar.commander.messagingservice.MessagingService;
import com.iluwatar.commander.paymentservice.PaymentDatabase;
import com.iluwatar.commander.paymentservice.PaymentService;
import com.iluwatar.commander.queue.QueueDatabase;
import com.iluwatar.commander.shippingservice.ShippingDatabase;
import com.iluwatar.commander.shippingservice.ShippingService;
import org.junit.jupiter.api.Test;
import org.junit.platform.commons.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertFalse;
class CommanderTest {
private final int numOfRetries = 1;
private final long retryDuration = 1_000;
private long queueTime = 1_00;
private long queueTaskTime = 1_000;
private long paymentTime = 6_000;
private long messageTime = 5_000;
private long employeeTime = 2_000;
private static final List<Exception> exceptionList = new ArrayList<>();
static {
exceptionList.add(new DatabaseUnavailableException());
exceptionList.add(new ShippingNotPossibleException());
exceptionList.add(new ItemUnavailableException());
exceptionList.add(new PaymentDetailsErrorException());
exceptionList.add(new IllegalStateException());
}
private Commander buildCommanderObject() {
return buildCommanderObject(false);
}
private Commander buildCommanderObject(boolean nonPaymentException) {
PaymentService paymentService = new PaymentService
(new PaymentDatabase(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException());
ShippingService shippingService;
MessagingService messagingService;
if (nonPaymentException) {
shippingService = new ShippingService(new ShippingDatabase(), new DatabaseUnavailableException());
messagingService = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException());
} else {
shippingService = new ShippingService(new ShippingDatabase(), new DatabaseUnavailableException());
messagingService = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException());
}
var employeeHandle = new EmployeeHandle
(new EmployeeDatabase(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var qdb = new QueueDatabase
(new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException());
return new Commander(employeeHandle, paymentService, shippingService,
messagingService, qdb, numOfRetries, retryDuration,
queueTime, queueTaskTime, paymentTime, messageTime, employeeTime);
}
private Commander buildCommanderObjectVanilla() {
PaymentService paymentService = new PaymentService
(new PaymentDatabase(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var shippingService = new ShippingService(new ShippingDatabase());
var messagingService = new MessagingService(new MessagingDatabase());
var employeeHandle = new EmployeeHandle
(new EmployeeDatabase(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var qdb = new QueueDatabase
(new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException());
return new Commander(employeeHandle, paymentService, shippingService,
messagingService, qdb, numOfRetries, retryDuration,
queueTime, queueTaskTime, paymentTime, messageTime, employeeTime);
}
private Commander buildCommanderObjectUnknownException() {
PaymentService paymentService = new PaymentService
(new PaymentDatabase(), new IllegalStateException());
var shippingService = new ShippingService(new ShippingDatabase());
var messagingService = new MessagingService(new MessagingDatabase());
var employeeHandle = new EmployeeHandle
(new EmployeeDatabase(), new IllegalStateException());
var qdb = new QueueDatabase
(new DatabaseUnavailableException(), new IllegalStateException());
return new Commander(employeeHandle, paymentService, shippingService,
messagingService, qdb, numOfRetries, retryDuration,
queueTime, queueTaskTime, paymentTime, messageTime, employeeTime);
}
private Commander buildCommanderObjectNoPaymentException1() {
PaymentService paymentService = new PaymentService
(new PaymentDatabase());
var shippingService = new ShippingService(new ShippingDatabase());
var messagingService = new MessagingService(new MessagingDatabase());
var employeeHandle = new EmployeeHandle
(new EmployeeDatabase(), new IllegalStateException());
var qdb = new QueueDatabase
(new DatabaseUnavailableException(), new IllegalStateException());
return new Commander(employeeHandle, paymentService, shippingService,
messagingService, qdb, numOfRetries, retryDuration,
queueTime, queueTaskTime, paymentTime, messageTime, employeeTime);
}
private Commander buildCommanderObjectNoPaymentException2() {
PaymentService paymentService = new PaymentService
(new PaymentDatabase());
var shippingService = new ShippingService(new ShippingDatabase());
var messagingService = new MessagingService(new MessagingDatabase(), new IllegalStateException());
var employeeHandle = new EmployeeHandle
(new EmployeeDatabase(), new IllegalStateException());
var qdb = new QueueDatabase
(new DatabaseUnavailableException(), new IllegalStateException());
return new Commander(employeeHandle, paymentService, shippingService,
messagingService, qdb, numOfRetries, retryDuration,
queueTime, queueTaskTime, paymentTime, messageTime, employeeTime);
}
private Commander buildCommanderObjectNoPaymentException3() {
PaymentService paymentService = new PaymentService
(new PaymentDatabase());
var shippingService = new ShippingService(new ShippingDatabase());
var messagingService = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException());
var employeeHandle = new EmployeeHandle
(new EmployeeDatabase(), new IllegalStateException());
var qdb = new QueueDatabase
(new DatabaseUnavailableException(), new IllegalStateException());
return new Commander(employeeHandle, paymentService, shippingService,
messagingService, qdb, numOfRetries, retryDuration,
queueTime, queueTaskTime, paymentTime, messageTime, employeeTime);
}
private Commander buildCommanderObjectWithDB() {
return buildCommanderObjectWithoutDB(false, false, new IllegalStateException());
}
private Commander buildCommanderObjectWithDB(boolean includeException, boolean includeDBException, Exception e) {
var l = includeDBException ? new DatabaseUnavailableException() : e;
PaymentService paymentService;
ShippingService shippingService;
MessagingService messagingService;
EmployeeHandle employeeHandle;
if (includeException) {
paymentService = new PaymentService
(new PaymentDatabase(), l);
shippingService = new ShippingService(new ShippingDatabase(), l);
messagingService = new MessagingService(new MessagingDatabase(), l);
employeeHandle = new EmployeeHandle
(new EmployeeDatabase(), l);
} else {
paymentService = new PaymentService
(null);
shippingService = new ShippingService(null);
messagingService = new MessagingService(null);
employeeHandle = new EmployeeHandle
(null);
}
return new Commander(employeeHandle, paymentService, shippingService,
messagingService, null, numOfRetries, retryDuration,
queueTime, queueTaskTime, paymentTime, messageTime, employeeTime);
}
private Commander buildCommanderObjectWithoutDB() {
return buildCommanderObjectWithoutDB(false, false, new IllegalStateException());
}
private Commander buildCommanderObjectWithoutDB(boolean includeException, boolean includeDBException, Exception e) {
var l = includeDBException ? new DatabaseUnavailableException() : e;
PaymentService paymentService;
ShippingService shippingService;
MessagingService messagingService;
EmployeeHandle employeeHandle;
if (includeException) {
paymentService = new PaymentService
(null, l);
shippingService = new ShippingService(null, l);
messagingService = new MessagingService(null, l);
employeeHandle = new EmployeeHandle
(null, l);
} else {
paymentService = new PaymentService
(null);
shippingService = new ShippingService(null);
messagingService = new MessagingService(null);
employeeHandle = new EmployeeHandle
(null);
}
return new Commander(employeeHandle, paymentService, shippingService,
messagingService, null, numOfRetries, retryDuration,
queueTime, queueTaskTime, paymentTime, messageTime, employeeTime);
}
@Test
void testPlaceOrderVanilla() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
Commander c = buildCommanderObjectVanilla();
var order = new Order(new User("K", "J"), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
@Test
void testPlaceOrder() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
Commander c = buildCommanderObject(true);
var order = new Order(new User("K", "J"), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
@Test
void testPlaceOrder2() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
Commander c = buildCommanderObject(false);
var order = new Order(new User("K", "J"), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
@Test
void testPlaceOrderNoException1() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
Commander c = buildCommanderObjectNoPaymentException1();
var order = new Order(new User("K", "J"), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
@Test
void testPlaceOrderNoException2() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
Commander c = buildCommanderObjectNoPaymentException2();
var order = new Order(new User("K", "J"), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
@Test
void testPlaceOrderNoException3() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
Commander c = buildCommanderObjectNoPaymentException3();
var order = new Order(new User("K", "J"), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
@Test
void testPlaceOrderNoException4() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
Commander c = buildCommanderObjectNoPaymentException3();
var order = new Order(new User("K", "J"), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
c.placeOrder(order);
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
@Test
void testPlaceOrderUnknownException() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
messageTime *= d;
employeeTime *= d;
queueTime *= d;
Commander c = buildCommanderObjectUnknownException();
var order = new Order(new User("K", "J"), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
@Test
void testPlaceOrderShortDuration() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
messageTime *= d;
employeeTime *= d;
queueTime *= d;
Commander c = buildCommanderObject(true);
var order = new Order(new User("K", "J"), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
@Test
void testPlaceOrderShortDuration2() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
messageTime *= d;
employeeTime *= d;
queueTime *= d;
Commander c = buildCommanderObject(false);
var order = new Order(new User("K", "J"), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
@Test
void testPlaceOrderNoExceptionShortMsgDuration() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
messageTime *= d;
employeeTime *= d;
queueTime *= d;
Commander c = buildCommanderObjectNoPaymentException1();
var order = new Order(new User("K", "J"), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
@Test
void testPlaceOrderNoExceptionShortQueueDuration() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
messageTime *= d;
employeeTime *= d;
queueTime *= d;
Commander c = buildCommanderObjectUnknownException();
var order = new Order(new User("K", "J"), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
@Test
void testPlaceOrderWithDatabase() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
messageTime *= d;
employeeTime *= d;
queueTime *= d;
Commander c = buildCommanderObjectWithDB();
var order = new Order(new User("K", null), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
@Test
void testPlaceOrderWithDatabaseAndExceptions() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
messageTime *= d;
employeeTime *= d;
queueTime *= d;
for (Exception e : exceptionList) {
Commander c = buildCommanderObjectWithDB(true, true, e);
var order = new Order(new User("K", null), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
c = buildCommanderObjectWithDB(true, false, e);
order = new Order(new User("K", null), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
c = buildCommanderObjectWithDB(false, false, e);
order = new Order(new User("K", null), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
c = buildCommanderObjectWithDB(false, true, e);
order = new Order(new User("K", null), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
}
@Test
void testPlaceOrderWithoutDatabase() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
messageTime *= d;
employeeTime *= d;
queueTime *= d;
Commander c = buildCommanderObjectWithoutDB();
var order = new Order(new User("K", null), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
@Test
void testPlaceOrderWithoutDatabaseAndExceptions() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
messageTime *= d;
employeeTime *= d;
queueTime *= d;
for (Exception e : exceptionList) {
Commander c = buildCommanderObjectWithoutDB(true, true, e);
var order = new Order(new User("K", null), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
c = buildCommanderObjectWithoutDB(true, false, e);
order = new Order(new User("K", null), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
c = buildCommanderObjectWithoutDB(false, false, e);
order = new Order(new User("K", null), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
c = buildCommanderObjectWithoutDB(false, true, e);
order = new Order(new User("K", null), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
}
}

View File

@ -49,28 +49,13 @@ public class App {
public static void main(String[] args) {
var kingJoffrey = new KingJoffrey();
var kingsHand = new KingsHand();
kingsHand.registerObserver(kingJoffrey, Event.TRAITOR_DETECTED);
kingsHand.registerObserver(kingJoffrey, Event.STARK_SIGHTED);
kingsHand.registerObserver(kingJoffrey, Event.WARSHIPS_APPROACHING);
kingsHand.registerObserver(kingJoffrey, Event.WHITE_WALKERS_SIGHTED);
var varys = new LordVarys();
varys.registerObserver(kingsHand, Event.TRAITOR_DETECTED);
varys.registerObserver(kingsHand, Event.WHITE_WALKERS_SIGHTED);
var scout = new Scout();
scout.registerObserver(kingsHand, Event.WARSHIPS_APPROACHING);
scout.registerObserver(varys, Event.WHITE_WALKERS_SIGHTED);
var baelish = new LordBaelish(kingsHand, Event.STARK_SIGHTED);
var kingsHand = new KingsHand(kingJoffrey);
var emitters = List.of(
kingsHand,
baelish,
varys,
scout
new LordBaelish(kingsHand),
new LordVarys(kingsHand),
new Scout(kingsHand)
);
Arrays.stream(Weekday.values())

View File

@ -31,7 +31,6 @@ import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public enum Event {
WHITE_WALKERS_SIGHTED("White walkers sighted"),
STARK_SIGHTED("Stark sighted"),
WARSHIPS_APPROACHING("Warships approaching"),
TRAITOR_DETECTED("Traitor detected");

View File

@ -23,48 +23,31 @@
package com.iluwatar.event.aggregator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* EventEmitter is the base class for event producers that can be observed.
*/
public abstract class EventEmitter {
private final Map<Event, List<EventObserver>> observerLists;
private final List<EventObserver> observers;
public EventEmitter() {
observerLists = new HashMap<>();
observers = new LinkedList<>();
}
public EventEmitter(EventObserver obs, Event e) {
public EventEmitter(EventObserver obs) {
this();
registerObserver(obs, e);
registerObserver(obs);
}
/**
* Registers observer for specific event in the related list.
*
* @param obs the observer that observers this emitter
* @param e the specific event for that observation occurs
* */
public final void registerObserver(EventObserver obs, Event e) {
if (!observerLists.containsKey(e)) {
observerLists.put(e, new LinkedList<>());
}
if (!observerLists.get(e).contains(obs)) {
observerLists.get(e).add(obs);
}
public final void registerObserver(EventObserver obs) {
observers.add(obs);
}
protected void notifyObservers(Event e) {
if (observerLists.containsKey(e)) {
observerLists
.get(e)
.forEach(observer -> observer.onEvent(e));
}
observers.forEach(obs -> obs.onEvent(e));
}
public abstract void timePasses(Weekday day);

View File

@ -31,8 +31,8 @@ public class KingsHand extends EventEmitter implements EventObserver {
public KingsHand() {
}
public KingsHand(EventObserver obs, Event e) {
super(obs, e);
public KingsHand(EventObserver obs) {
super(obs);
}
@Override
@ -42,5 +42,6 @@ public class KingsHand extends EventEmitter implements EventObserver {
@Override
public void timePasses(Weekday day) {
// NOP
}
}

View File

@ -31,8 +31,8 @@ public class LordBaelish extends EventEmitter {
public LordBaelish() {
}
public LordBaelish(EventObserver obs, Event e) {
super(obs, e);
public LordBaelish(EventObserver obs) {
super(obs);
}
@Override

View File

@ -23,19 +23,16 @@
package com.iluwatar.event.aggregator;
import lombok.extern.slf4j.Slf4j;
/**
* LordVarys produces events.
*/
@Slf4j
public class LordVarys extends EventEmitter implements EventObserver {
public class LordVarys extends EventEmitter {
public LordVarys() {
}
public LordVarys(EventObserver obs, Event e) {
super(obs, e);
public LordVarys(EventObserver obs) {
super(obs);
}
@Override
@ -44,10 +41,4 @@ public class LordVarys extends EventEmitter implements EventObserver {
notifyObservers(Event.TRAITOR_DETECTED);
}
}
@Override
public void onEvent(Event e) {
notifyObservers(e);
}
}

View File

@ -31,8 +31,8 @@ public class Scout extends EventEmitter {
public Scout() {
}
public Scout(EventObserver obs, Event e) {
super(obs, e);
public Scout(EventObserver obs) {
super(obs);
}
@Override
@ -40,8 +40,5 @@ public class Scout extends EventEmitter {
if (day == Weekday.TUESDAY) {
notifyObservers(Event.WARSHIPS_APPROACHING);
}
if (day == Weekday.WEDNESDAY) {
notifyObservers(Event.WHITE_WALKERS_SIGHTED);
}
}
}

View File

@ -31,7 +31,6 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import org.junit.jupiter.api.Test;
@ -47,7 +46,7 @@ abstract class EventEmitterTest<E extends EventEmitter> {
/**
* Factory used to create a new instance of the test object with a default observer
*/
private final BiFunction<EventObserver, Event, E> factoryWithDefaultObserver;
private final Function<EventObserver, E> factoryWithDefaultObserver;
/**
* Factory used to create a new instance of the test object without passing a default observer
@ -68,7 +67,7 @@ abstract class EventEmitterTest<E extends EventEmitter> {
* Create a new event emitter test, using the given test object factories, special day and event
*/
EventEmitterTest(final Weekday specialDay, final Event event,
final BiFunction<EventObserver, Event, E> factoryWithDefaultObserver,
final Function<EventObserver, E> factoryWithDefaultObserver,
final Supplier<E> factoryWithoutDefaultObserver) {
this.specialDay = specialDay;
@ -130,8 +129,8 @@ abstract class EventEmitterTest<E extends EventEmitter> {
final var observer2 = mock(EventObserver.class);
final var emitter = this.factoryWithoutDefaultObserver.get();
emitter.registerObserver(observer1, event);
emitter.registerObserver(observer2, event);
emitter.registerObserver(observer1);
emitter.registerObserver(observer2);
testAllDays(specialDay, event, emitter, observer1, observer2);
}
@ -147,9 +146,9 @@ abstract class EventEmitterTest<E extends EventEmitter> {
final var observer1 = mock(EventObserver.class);
final var observer2 = mock(EventObserver.class);
final var emitter = this.factoryWithDefaultObserver.apply(defaultObserver, event);
emitter.registerObserver(observer1, event);
emitter.registerObserver(observer2, event);
final var emitter = this.factoryWithDefaultObserver.apply(defaultObserver);
emitter.registerObserver(observer1);
emitter.registerObserver(observer2);
testAllDays(specialDay, event, emitter, defaultObserver, observer1, observer2);
}

View File

@ -55,11 +55,7 @@ class KingsHandTest extends EventEmitterTest<KingsHand> {
@Test
void testPassThrough() throws Exception {
final var observer = mock(EventObserver.class);
final var kingsHand = new KingsHand();
kingsHand.registerObserver(observer, Event.STARK_SIGHTED);
kingsHand.registerObserver(observer, Event.WARSHIPS_APPROACHING);
kingsHand.registerObserver(observer, Event.TRAITOR_DETECTED);
kingsHand.registerObserver(observer, Event.WHITE_WALKERS_SIGHTED);
final var kingsHand = new KingsHand(observer);
// The kings hand should not pass any events before he received one
verifyZeroInteractions(observer);

View File

@ -34,9 +34,7 @@ class ScoutTest extends EventEmitterTest<Scout> {
* Create a new test instance, using the correct object factory
*/
public ScoutTest() {
super(Weekday.TUESDAY, Event.WARSHIPS_APPROACHING, Scout::new, Scout::new);
super(Weekday.TUESDAY, Event.WARSHIPS_APPROACHING, Scout::new, Scout::new);
}
}

View File

@ -1,182 +0,0 @@
---
layout: pattern
title: Metadata Mapping
folder: metadata-mapping
permalink: /patterns/metadata-mapping/
categories: Architectural
language: en
tags:
- Data access
---
## Intent
Holds details of object-relational mapping in the metadata.
## Explanation
Real world example
> Hibernate ORM Tool uses Metadata Mapping Pattern to specify the mapping between classes and tables either using XML or annotations in code.
In plain words
> Metadata Mapping specifies the mapping between classes and tables so that we could treat a table of any database like a Java class.
Wikipedia says
> Create a "virtual [object database](https://en.wikipedia.org/wiki/Object_database)" that can be used from within the programming language.
**Programmatic Example**
We give an example about visiting the information of `USER` table in `h2` database. Firstly, we create `USER` table with `h2`:
```java
@Slf4j
public class DatabaseUtil {
private static final String DB_URL = "jdbc:h2:mem:metamapping";
private static final String CREATE_SCHEMA_SQL = "DROP TABLE IF EXISTS `user`;"
+ "CREATE TABLE `user` (\n"
+ " `id` int(11) NOT NULL AUTO_INCREMENT,\n"
+ " `username` varchar(255) NOT NULL,\n"
+ " `password` varchar(255) NOT NULL,\n"
+ " PRIMARY KEY (`id`)\n"
+ ");";
/**
* Create database.
*/
static {
LOGGER.info("create h2 database");
var source = new JdbcDataSource();
source.setURL(DB_URL);
try (var statement = source.getConnection().createStatement()) {
statement.execute(CREATE_SCHEMA_SQL);
} catch (SQLException e) {
LOGGER.error("unable to create h2 data source", e);
}
}
}
```
Correspondingly, here's the basic `User` entity.
```java
@Setter
@Getter
@ToString
public class User {
private Integer id;
private String username;
private String password;
/**
* Get a user.
* @param username user name
* @param password user password
*/
public User(String username, String password) {
this.username = username;
this.password = password;
}
}
```
Then we write a `xml` file to show the mapping between the table and the object:
```xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.iluwatar.metamapping.model.User" table="user">
<id name="id" type="java.lang.Integer" column="id">
<generator class="native"/>
</id>
<property name="username" column="username" type="java.lang.String"/>
<property name="password" column="password" type="java.lang.String"/>
</class>
</hibernate-mapping>
```
We use `Hibernate` to resolve the mapping and connect to our database, here's its configuration:
```xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- JDBC Database connection settings -->
<property name="connection.url">jdbc:h2:mem:metamapping</property>
<property name="connection.driver_class">org.h2.Driver</property>
<!-- JDBC connection pool settings ... using built-in test pool -->
<property name="connection.pool_size">1</property>
<!-- Select our SQL dialect -->
<property name="dialect">org.hibernate.dialect.H2Dialect</property>
<!-- Echo the SQL to stdout -->
<property name="show_sql">false</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">create-drop</property>
<mapping resource="com/iluwatar/metamapping/model/User.hbm.xml" />
</session-factory>
</hibernate-configuration>
```
Then we can get access to the table just like an object with `Hibernate`, here's some CRUDs:
```java
@Slf4j
public class UserService {
private static final SessionFactory factory = HibernateUtil.getSessionFactory();
/**
* List all users.
* @return list of users
*/
public List<User> listUser() {
LOGGER.info("list all users.");
List<User> users = new ArrayList<>();
try (var session = factory.openSession()) {
var tx = session.beginTransaction();
List<User> userIter = session.createQuery("FROM User").list();
for (var iterator = userIter.iterator(); iterator.hasNext();) {
users.add(iterator.next());
}
tx.commit();
} catch (HibernateException e) {
LOGGER.debug("fail to get users", e);
}
return users;
}
// other CRUDs ->
...
public void close() {
HibernateUtil.shutdown();
}
}
```
## Class diagram
![metamapping](etc/metamapping.png)
## Applicability
Use the Metadata Mapping when:
- you want reduce the amount of work needed to handle database mapping.
## Known uses
[Hibernate](https://hibernate.org/), [EclipseLink](https://www.eclipse.org/eclipselink/), [MyBatis](https://blog.mybatis.org/)......
## Credits
- [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

View File

@ -1,32 +0,0 @@
@startuml
interface com.iluwatar.metamapping.service.UserService {
+ List<User> listUser()
+ int createUser(User)
+ void updateUser(Integer,User)
+ void deleteUser(Integer)
+ User getUser(Integer)
+ void close()
}
class com.iluwatar.metamapping.utils.DatabaseUtil {
+ {static} void createDataSource()
}
class com.iluwatar.metamapping.model.User {
- Integer id
- String username
- String password
+ User(String username, String password)
}
class com.iluwatar.metamapping.utils.HibernateUtil {
+ {static} SessionFactory getSessionFactory()
+ {static} void shutdown()
}
class com.iluwatar.metamapping.App {
+ {static} void main(String[])
+ {static} List<User> generateSampleUsers()
}
com.iluwatar.metamapping.service.UserService <.. com.iluwatar.metamapping.App
com.iluwatar.metamapping.model.User <.. com.iluwatar.metamapping.service.UserService
com.iluwatar.metamapping.utils.HibernateUtil <.. com.iluwatar.metamapping.service.UserService
com.iluwatar.metamapping.utils.DatabaseUtil <-- com.iluwatar.metamapping.utils.HibernateUtil
@enduml

View File

@ -1,87 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
The MIT License
Copyright © 2014-2021 Ilkka Seppälä
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<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">
<parent>
<artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.26.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>metadata-mapping</artifactId>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
</dependency>
<dependency>
<groupId>com.sun.istack</groupId>
<artifactId>istack-commons-runtime</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<configuration>
<archive>
<manifest>
<mainClass>com.iluwatar.metamapping.App</mainClass>
</manifest>
</archive>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,72 +0,0 @@
package com.iluwatar.metamapping;
import com.iluwatar.metamapping.model.User;
import com.iluwatar.metamapping.service.UserService;
import com.iluwatar.metamapping.utils.DatabaseUtil;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.service.ServiceRegistry;
/**
* Metadata Mapping specifies the mapping
* between classes and tables so that
* we could treat a table of any database like a Java class.
*
* <p>With hibernate, we achieve list/create/update/delete/get operations:
* 1)Create the H2 Database in {@link DatabaseUtil}.
* 2)Hibernate resolve hibernate.cfg.xml and generate service like save/list/get/delete.
* For learning metadata mapping pattern, we go deeper into Hibernate here:
* a)read properties from hibernate.cfg.xml and mapping from *.hbm.xml
* b)create session factory to generate session interacting with database
* c)generate session with factory pattern
* d)create query object or use basic api with session,
* hibernate will convert all query to database query according to metadata
* 3)We encapsulate hibernate service in {@link UserService} for our use.
* @see org.hibernate.cfg.Configuration#configure(String)
* @see org.hibernate.cfg.Configuration#buildSessionFactory(ServiceRegistry)
* @see org.hibernate.internal.SessionFactoryImpl#openSession()
*/
@Slf4j
public class App {
/**
* Program entry point.
*
* @param args command line args.
* @throws Exception if any error occurs.
*/
public static void main(String[] args) throws Exception {
// get service
var userService = new UserService();
// use create service to add users
for (var user: generateSampleUsers()) {
var id = userService.createUser(user);
LOGGER.info("Add user" + user + "at" + id + ".");
}
// use list service to get users
var users = userService.listUser();
LOGGER.info(String.valueOf(users));
// use get service to get a user
var user = userService.getUser(1);
LOGGER.info(String.valueOf(user));
// change password of user 1
user.setPassword("new123");
// use update service to update user 1
userService.updateUser(1, user);
// use delete service to delete user 2
userService.deleteUser(2);
// close service
userService.close();
}
/**
* Generate users.
*
* @return list of users.
*/
public static List<User> generateSampleUsers() {
final var user1 = new User("ZhangSan", "zhs123");
final var user2 = new User("LiSi", "ls123");
final var user3 = new User("WangWu", "ww123");
return List.of(user1, user2, user3);
}
}

View File

@ -1,29 +0,0 @@
package com.iluwatar.metamapping.model;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* User Entity.
*/
@Setter
@Getter
@ToString
public class User {
private Integer id;
private String username;
private String password;
public User() {}
/**
* Get a user.
* @param username user name
* @param password user password
*/
public User(String username, String password) {
this.username = username;
this.password = password;
}
}

View File

@ -1,114 +0,0 @@
package com.iluwatar.metamapping.service;
import com.iluwatar.metamapping.model.User;
import com.iluwatar.metamapping.utils.HibernateUtil;
import java.util.ArrayList;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.HibernateException;
import org.hibernate.SessionFactory;
/**
* Service layer for user.
*/
@Slf4j
public class UserService {
private static final SessionFactory factory = HibernateUtil.getSessionFactory();
/**
* List all users.
* @return list of users
*/
public List<User> listUser() {
LOGGER.info("list all users.");
List<User> users = new ArrayList<>();
try (var session = factory.openSession()) {
var tx = session.beginTransaction();
List<User> userIter = session.createQuery("FROM User").list();
for (var iterator = userIter.iterator(); iterator.hasNext();) {
users.add(iterator.next());
}
tx.commit();
} catch (HibernateException e) {
LOGGER.debug("fail to get users", e);
}
return users;
}
/**
* Add a user.
* @param user user entity
* @return user id
*/
public int createUser(User user) {
LOGGER.info("create user: " + user.getUsername());
var id = -1;
try (var session = factory.openSession()) {
var tx = session.beginTransaction();
id = (Integer) session.save(user);
tx.commit();
} catch (HibernateException e) {
LOGGER.debug("fail to create user", e);
}
LOGGER.info("create user " + user.getUsername() + " at " + id);
return id;
}
/**
* Update user.
* @param id user id
* @param user new user entity
*/
public void updateUser(Integer id, User user) {
LOGGER.info("update user at " + id);
try (var session = factory.openSession()) {
var tx = session.beginTransaction();
user.setId(id);
session.update(user);
tx.commit();
} catch (HibernateException e) {
LOGGER.debug("fail to update user", e);
}
}
/**
* Delete user.
* @param id user id
*/
public void deleteUser(Integer id) {
LOGGER.info("delete user at: " + id);
try (var session = factory.openSession()) {
var tx = session.beginTransaction();
var user = session.get(User.class, id);
session.delete(user);
tx.commit();
} catch (HibernateException e) {
LOGGER.debug("fail to delete user", e);
}
}
/**
* Get user.
* @param id user id
* @return deleted user
*/
public User getUser(Integer id) {
LOGGER.info("get user at: " + id);
User user = null;
try (var session = factory.openSession()) {
var tx = session.beginTransaction();
user = session.get(User.class, id);
tx.commit();
} catch (HibernateException e) {
LOGGER.debug("fail to get user", e);
}
return user;
}
/**
* Close hibernate.
*/
public void close() {
HibernateUtil.shutdown();
}
}

View File

@ -1,39 +0,0 @@
package com.iluwatar.metamapping.utils;
import java.sql.SQLException;
import lombok.extern.slf4j.Slf4j;
import org.h2.jdbcx.JdbcDataSource;
/**
* Create h2 database.
*/
@Slf4j
public class DatabaseUtil {
private static final String DB_URL = "jdbc:h2:mem:metamapping";
private static final String CREATE_SCHEMA_SQL = "DROP TABLE IF EXISTS `user`;"
+ "CREATE TABLE `user` (\n"
+ " `id` int(11) NOT NULL AUTO_INCREMENT,\n"
+ " `username` varchar(255) NOT NULL,\n"
+ " `password` varchar(255) NOT NULL,\n"
+ " PRIMARY KEY (`id`)\n"
+ ");";
/**
* Hide constructor.
*/
private DatabaseUtil() {}
/**
* Create database.
*/
static {
LOGGER.info("create h2 database");
var source = new JdbcDataSource();
source.setURL(DB_URL);
try (var statement = source.getConnection().createStatement()) {
statement.execute(CREATE_SCHEMA_SQL);
} catch (SQLException e) {
LOGGER.error("unable to create h2 data source", e);
}
}
}

View File

@ -1,45 +0,0 @@
package com.iluwatar.metamapping.utils;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
/**
* Manage hibernate.
*/
@Slf4j
public class HibernateUtil {
private static final SessionFactory sessionFactory = buildSessionFactory();
/**
* Hide constructor.
*/
private HibernateUtil() {}
/**
* Build session factory.
* @return session factory
*/
private static SessionFactory buildSessionFactory() {
// Create the SessionFactory from hibernate.cfg.xml
return new Configuration().configure().buildSessionFactory();
}
/**
* Get session factory.
* @return session factory
*/
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
/**
* Close session factory.
*/
public static void shutdown() {
// Close caches and connection pools
getSessionFactory().close();
}
}

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.iluwatar.metamapping.model.User" table="user">
<id name="id" type="java.lang.Integer" column="id">
<generator class="native"/>
</id>
<property name="username" column="username" type="java.lang.String"/>
<property name="password" column="password" type="java.lang.String"/>
</class>
</hibernate-mapping>

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- JDBC Database connection settings -->
<property name="connection.url">jdbc:h2:mem:metamapping</property>
<property name="connection.driver_class">org.h2.Driver</property>
<!-- JDBC connection pool settings ... using built-in test pool -->
<property name="connection.pool_size">1</property>
<!-- Select our SQL dialect -->
<property name="dialect">org.hibernate.dialect.H2Dialect</property>
<!-- Echo the SQL to stdout -->
<property name="show_sql">false</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">create-drop</property>
<mapping resource="com/iluwatar/metamapping/model/User.hbm.xml" />
</session-factory>
</hibernate-configuration>

View File

@ -1,20 +0,0 @@
package com.iluwatar.metamapping;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/**
* Tests that metadata mapping example runs without errors.
*/
class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])}
* throws an exception.
*/
@Test
void shouldExecuteMetaMappingWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{}));
}
}

View File

@ -75,7 +75,6 @@
<license-maven-plugin.version>3.0</license-maven-plugin.version>
<urm-maven-plugin.version>1.4.8</urm-maven-plugin.version>
<commons-io.version>2.7</commons-io.version>
<istack-commons-runtime.version>4.0.1</istack-commons-runtime.version>
<!-- SonarCloud -->
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
<sonar.organization>iluwatar</sonar.organization>
@ -228,7 +227,6 @@
<module>lockable-object</module>
<module>fanout-fanin</module>
<module>domain-model</module>
<module>metadata-mapping</module>
</modules>
<repositories>
<repository>
@ -379,11 +377,6 @@
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
<dependency>
<groupId>com.sun.istack</groupId>
<artifactId>istack-commons-runtime</artifactId>
<version>${istack-commons-runtime.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>

View File

@ -96,11 +96,9 @@ public class NioReactor {
* @throws IOException if any I/O error occurs.
*/
public void stop() throws InterruptedException, IOException {
reactorMain.shutdown();
reactorMain.shutdownNow();
selector.wakeup();
if (!reactorMain.awaitTermination(4, TimeUnit.SECONDS)) {
reactorMain.shutdownNow();
}
reactorMain.awaitTermination(4, TimeUnit.SECONDS);
selector.close();
LOGGER.info("Reactor stopped");
}

View File

@ -64,8 +64,6 @@ public class ThreadPoolDispatcher implements Dispatcher {
@Override
public void stop() throws InterruptedException {
executorService.shutdown();
if (executorService.awaitTermination(4, TimeUnit.SECONDS)) {
executorService.shutdownNow();
}
executorService.awaitTermination(4, TimeUnit.SECONDS);
}
}

View File

@ -34,11 +34,11 @@ import java.util.HashMap;
public class SpatialPartitionBubbles extends SpatialPartitionGeneric<Bubble> {
private final HashMap<Integer, Bubble> bubbles;
private final QuadTree bubblesQuadTree;
private final QuadTree quadTree;
SpatialPartitionBubbles(HashMap<Integer, Bubble> bubbles, QuadTree bubblesQuadTree) {
SpatialPartitionBubbles(HashMap<Integer, Bubble> bubbles, QuadTree quadTree) {
this.bubbles = bubbles;
this.bubblesQuadTree = bubblesQuadTree;
this.quadTree = quadTree;
}
void handleCollisionsUsingQt(Bubble b) {
@ -46,7 +46,7 @@ public class SpatialPartitionBubbles extends SpatialPartitionGeneric<Bubble> {
// centre of bubble and length = radius of bubble
var rect = new Rect(b.coordinateX, b.coordinateY, 2D * b.radius, 2D * b.radius);
var quadTreeQueryResult = new ArrayList<Point>();
this.bubblesQuadTree.query(rect, quadTreeQueryResult);
this.quadTree.query(rect, quadTreeQueryResult);
//handling these collisions
b.handleCollision(quadTreeQueryResult, this.bubbles);
}

View File

@ -12,20 +12,20 @@ tags:
## Intent
When a business transaction is completed, all the updates are sent as one big unit of work to be
When a business transaction is completed, all the the updates are sent as one big unit of work to be
persisted in one go to minimize database round-trips.
## Explanation
Real-world example
Real world example
> Arms dealer has a database containing weapon information. Merchants all over the town are
> constantly updating this information and it causes a high load on the database server. To make the
> We have a database containing student information. Administrators all over the country are
> constantly updating this information and it causes high load on the database server. To make the
> load more manageable we apply to Unit of Work pattern to send many small updates in batches.
In plain words
> Unit of Work merges many small database updates in a single batch to optimize the number of
> Unit of Work merges many small database updates in single batch to optimize the number of
> round-trips.
[MartinFowler.com](https://martinfowler.com/eaaCatalog/unitOfWork.html) says
@ -35,20 +35,37 @@ In plain words
**Programmatic Example**
Here's the `Weapon` entity that is being persisted in the database.
Here's the `Student` entity that is being persisted to the database.
```java
@Getter
@RequiredArgsConstructor
public class Weapon {
private final Integer id;
private final String name;
public class Student {
private final Integer id;
private final String name;
private final String address;
public Student(Integer id, String name, String address) {
this.id = id;
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public Integer getId() {
return id;
}
public String getAddress() {
return address;
}
}
```
The essence of the implementation is the `ArmsDealer` implementing the Unit of Work pattern.
The essence of the implementation is the `StudentRepository` implementing the Unit of Work pattern.
It maintains a map of database operations (`context`) that need to be done and when `commit` is
called it applies them in a single batch.
called it applies them in single batch.
```java
public interface IUnitOfWork<T> {
@ -67,117 +84,96 @@ public interface IUnitOfWork<T> {
}
@Slf4j
@RequiredArgsConstructor
public class ArmsDealer implements IUnitOfWork<Weapon> {
public class StudentRepository implements IUnitOfWork<Student> {
private final Map<String, List<Weapon>> context;
private final WeaponDatabase weaponDatabase;
private final Map<String, List<Student>> context;
private final StudentDatabase studentDatabase;
@Override
public void registerNew(Weapon weapon) {
LOGGER.info("Registering {} for insert in context.", weapon.getName());
register(weapon, UnitActions.INSERT.getActionValue());
public StudentRepository(Map<String, List<Student>> context, StudentDatabase studentDatabase) {
this.context = context;
this.studentDatabase = studentDatabase;
}
@Override
public void registerNew(Student student) {
LOGGER.info("Registering {} for insert in context.", student.getName());
register(student, IUnitOfWork.INSERT);
}
@Override
public void registerModified(Student student) {
LOGGER.info("Registering {} for modify in context.", student.getName());
register(student, IUnitOfWork.MODIFY);
}
@Override
public void registerDeleted(Student student) {
LOGGER.info("Registering {} for delete in context.", student.getName());
register(student, IUnitOfWork.DELETE);
}
private void register(Student student, String operation) {
var studentsToOperate = context.get(operation);
if (studentsToOperate == null) {
studentsToOperate = new ArrayList<>();
}
studentsToOperate.add(student);
context.put(operation, studentsToOperate);
}
@Override
public void commit() {
if (context == null || context.size() == 0) {
return;
}
LOGGER.info("Commit started");
if (context.containsKey(IUnitOfWork.INSERT)) {
commitInsert();
}
@Override
public void registerModified(Weapon weapon) {
LOGGER.info("Registering {} for modify in context.", weapon.getName());
register(weapon, UnitActions.MODIFY.getActionValue());
if (context.containsKey(IUnitOfWork.MODIFY)) {
commitModify();
}
@Override
public void registerDeleted(Weapon weapon) {
LOGGER.info("Registering {} for delete in context.", weapon.getName());
register(weapon, UnitActions.DELETE.getActionValue());
if (context.containsKey(IUnitOfWork.DELETE)) {
commitDelete();
}
LOGGER.info("Commit finished.");
}
private void register(Weapon weapon, String operation) {
var weaponsToOperate = context.get(operation);
if (weaponsToOperate == null) {
weaponsToOperate = new ArrayList<>();
}
weaponsToOperate.add(weapon);
context.put(operation, weaponsToOperate);
private void commitInsert() {
var studentsToBeInserted = context.get(IUnitOfWork.INSERT);
for (var student : studentsToBeInserted) {
LOGGER.info("Saving {} to database.", student.getName());
studentDatabase.insert(student);
}
}
/**
* All UnitOfWork operations are batched and executed together on commit only.
*/
@Override
public void commit() {
if (context == null || context.size() == 0) {
return;
}
LOGGER.info("Commit started");
if (context.containsKey(UnitActions.INSERT.getActionValue())) {
commitInsert();
}
if (context.containsKey(UnitActions.MODIFY.getActionValue())) {
commitModify();
}
if (context.containsKey(UnitActions.DELETE.getActionValue())) {
commitDelete();
}
LOGGER.info("Commit finished.");
private void commitModify() {
var modifiedStudents = context.get(IUnitOfWork.MODIFY);
for (var student : modifiedStudents) {
LOGGER.info("Modifying {} to database.", student.getName());
studentDatabase.modify(student);
}
}
private void commitInsert() {
var weaponsToBeInserted = context.get(UnitActions.INSERT.getActionValue());
for (var weapon : weaponsToBeInserted) {
LOGGER.info("Inserting a new weapon {} to sales rack.", weapon.getName());
weaponDatabase.insert(weapon);
}
}
private void commitModify() {
var modifiedWeapons = context.get(UnitActions.MODIFY.getActionValue());
for (var weapon : modifiedWeapons) {
LOGGER.info("Scheduling {} for modification work.", weapon.getName());
weaponDatabase.modify(weapon);
}
}
private void commitDelete() {
var deletedWeapons = context.get(UnitActions.DELETE.getActionValue());
for (var weapon : deletedWeapons) {
LOGGER.info("Scrapping {}.", weapon.getName());
weaponDatabase.delete(weapon);
}
private void commitDelete() {
var deletedStudents = context.get(IUnitOfWork.DELETE);
for (var student : deletedStudents) {
LOGGER.info("Deleting {} to database.", student.getName());
studentDatabase.delete(student);
}
}
}
```
Here is how the whole app is put together.
Finally, here's how we use the `StudentRepository` and `commit` the transaction.
```java
// create some weapons
var enchantedHammer = new Weapon(1, "enchanted hammer");
var brokenGreatSword = new Weapon(2, "broken great sword");
var silverTrident = new Weapon(3, "silver trident");
// create repository
var weaponRepository = new ArmsDealer(new HashMap<String, List<Weapon>>(), new WeaponDatabase());
// perform operations on the weapons
weaponRepository.registerNew(enchantedHammer);
weaponRepository.registerModified(silverTrident);
weaponRepository.registerDeleted(brokenGreatSword);
weaponRepository.commit();
```
Here is the console output.
```
21:39:21.984 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Registering enchanted hammer for insert in context.
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Registering silver trident for modify in context.
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Registering broken great sword for delete in context.
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Commit started
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Inserting a new weapon enchanted hammer to sales rack.
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Scheduling silver trident for modification work.
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Scrapping broken great sword.
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Commit finished.
studentRepository.registerNew(ram);
studentRepository.registerModified(shyam);
studentRepository.registerDeleted(gopi);
studentRepository.commit();
```
## Class diagram
@ -190,7 +186,7 @@ Use the Unit Of Work pattern when
* To optimize the time taken for database transactions.
* To send changes to database as a unit of work which ensures atomicity of the transaction.
* To reduce the number of database calls.
* To reduce number of database calls.
## Tutorials

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<class-diagram version="1.2.1" icons="true" always-add-relationships="false" generalizations="true" realizations="true"
associations="true" dependencies="false" nesting-relationships="true" router="FAN">
<class id="1" language="java" name="com.iluwatar.unitofwork.WeaponDatabase" project="unit-of-work"
<class id="1" language="java" name="com.iluwatar.unitofwork.StudentDatabase" project="unit-of-work"
file="/unit-of-work/src/main/java/com/iluwatar/unitofwork/StudentDatabase.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="170" y="406"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
@ -28,7 +28,7 @@
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="4" language="java" name="com.iluwatar.unitofwork.ArmsDealer" project="unit-of-work"
<class id="4" language="java" name="com.iluwatar.unitofwork.StudentRepository" project="unit-of-work"
file="/unit-of-work/src/main/java/com/iluwatar/unitofwork/StudentRepository.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="377" y="166"/>
@ -38,7 +38,7 @@
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="5" language="java" name="com.iluwatar.unitofwork.Weapon" project="unit-of-work"
<class id="5" language="java" name="com.iluwatar.unitofwork.Student" project="unit-of-work"
file="/unit-of-work/src/main/java/com/iluwatar/unitofwork/Student.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="696" y="130"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"

View File

@ -27,7 +27,7 @@ import java.util.HashMap;
import java.util.List;
/**
* {@link App} Application demonstrating unit of work pattern.
* {@link App} Application for managing student data.
*/
public class App {
/**
@ -37,19 +37,17 @@ public class App {
*/
public static void main(String[] args) {
// create some weapons
var enchantedHammer = new Weapon(1, "enchanted hammer");
var brokenGreatSword = new Weapon(2, "broken great sword");
var silverTrident = new Weapon(3, "silver trident");
var ram = new Student(1, "Ram", "Street 9, Cupertino");
var shyam = new Student(2, "Shyam", "Z bridge, Pune");
var gopi = new Student(3, "Gopi", "Street 10, Mumbai");
// create repository
var weaponRepository = new ArmsDealer(new HashMap<String, List<Weapon>>(),
new WeaponDatabase());
var context = new HashMap<String, List<Student>>();
var studentDatabase = new StudentDatabase();
var studentRepository = new StudentRepository(context, studentDatabase);
// perform operations on the weapons
weaponRepository.registerNew(enchantedHammer);
weaponRepository.registerModified(silverTrident);
weaponRepository.registerDeleted(brokenGreatSword);
weaponRepository.commit();
studentRepository.registerNew(ram);
studentRepository.registerModified(shyam);
studentRepository.registerDeleted(gopi);
studentRepository.commit();
}
}

View File

@ -27,12 +27,14 @@ import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* {@link Weapon} is an entity.
* {@link Student} is an entity.
*/
@Getter
@RequiredArgsConstructor
public class Weapon {
public class Student {
private final Integer id;
private final String name;
private final String address;
}

View File

@ -24,19 +24,19 @@
package com.iluwatar.unitofwork;
/**
* Act as database for weapon records.
* Act as Database for student records.
*/
public class WeaponDatabase {
public class StudentDatabase {
public void insert(Weapon weapon) {
public void insert(Student student) {
//Some insert logic to DB
}
public void modify(Weapon weapon) {
public void modify(Student student) {
//Some modify logic to DB
}
public void delete(Weapon weapon) {
public void delete(Student student) {
//Some delete logic to DB
}
}

View File

@ -30,41 +30,41 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* {@link ArmsDealer} Weapon repository that supports unit of work for weapons.
* {@link StudentRepository} Student database repository. supports unit of work for student data.
*/
@Slf4j
@RequiredArgsConstructor
public class ArmsDealer implements IUnitOfWork<Weapon> {
public class StudentRepository implements IUnitOfWork<Student> {
private final Map<String, List<Weapon>> context;
private final WeaponDatabase weaponDatabase;
private final Map<String, List<Student>> context;
private final StudentDatabase studentDatabase;
@Override
public void registerNew(Weapon weapon) {
LOGGER.info("Registering {} for insert in context.", weapon.getName());
register(weapon, UnitActions.INSERT.getActionValue());
public void registerNew(Student student) {
LOGGER.info("Registering {} for insert in context.", student.getName());
register(student, UnitActions.INSERT.getActionValue());
}
@Override
public void registerModified(Weapon weapon) {
LOGGER.info("Registering {} for modify in context.", weapon.getName());
register(weapon, UnitActions.MODIFY.getActionValue());
public void registerModified(Student student) {
LOGGER.info("Registering {} for modify in context.", student.getName());
register(student, UnitActions.MODIFY.getActionValue());
}
@Override
public void registerDeleted(Weapon weapon) {
LOGGER.info("Registering {} for delete in context.", weapon.getName());
register(weapon, UnitActions.DELETE.getActionValue());
public void registerDeleted(Student student) {
LOGGER.info("Registering {} for delete in context.", student.getName());
register(student, UnitActions.DELETE.getActionValue());
}
private void register(Weapon weapon, String operation) {
var weaponsToOperate = context.get(operation);
if (weaponsToOperate == null) {
weaponsToOperate = new ArrayList<>();
private void register(Student student, String operation) {
var studentsToOperate = context.get(operation);
if (studentsToOperate == null) {
studentsToOperate = new ArrayList<>();
}
weaponsToOperate.add(weapon);
context.put(operation, weaponsToOperate);
studentsToOperate.add(student);
context.put(operation, studentsToOperate);
}
/**
@ -90,26 +90,26 @@ public class ArmsDealer implements IUnitOfWork<Weapon> {
}
private void commitInsert() {
var weaponsToBeInserted = context.get(UnitActions.INSERT.getActionValue());
for (var weapon : weaponsToBeInserted) {
LOGGER.info("Inserting a new weapon {} to sales rack.", weapon.getName());
weaponDatabase.insert(weapon);
var studentsToBeInserted = context.get(UnitActions.INSERT.getActionValue());
for (var student : studentsToBeInserted) {
LOGGER.info("Saving {} to database.", student.getName());
studentDatabase.insert(student);
}
}
private void commitModify() {
var modifiedWeapons = context.get(UnitActions.MODIFY.getActionValue());
for (var weapon : modifiedWeapons) {
LOGGER.info("Scheduling {} for modification work.", weapon.getName());
weaponDatabase.modify(weapon);
var modifiedStudents = context.get(UnitActions.MODIFY.getActionValue());
for (var student : modifiedStudents) {
LOGGER.info("Modifying {} to database.", student.getName());
studentDatabase.modify(student);
}
}
private void commitDelete() {
var deletedWeapons = context.get(UnitActions.DELETE.getActionValue());
for (var weapon : deletedWeapons) {
LOGGER.info("Scrapping {}.", weapon.getName());
weaponDatabase.delete(weapon);
var deletedStudents = context.get(UnitActions.DELETE.getActionValue());
for (var student : deletedStudents) {
LOGGER.info("Deleting {} to database.", student.getName());
studentDatabase.delete(student);
}
}
}

View File

@ -36,102 +36,102 @@ import java.util.Map;
import org.junit.jupiter.api.Test;
/**
* tests {@link ArmsDealer}
* tests {@link StudentRepository}
*/
class ArmsDealerTest {
private final Weapon weapon1 = new Weapon(1, "battle ram");
private final Weapon weapon2 = new Weapon(1, "wooden lance");
class StudentRepositoryTest {
private final Student student1 = new Student(1, "Ram", "street 9, cupertino");
private final Student student2 = new Student(1, "Sham", "Z bridge, pune");
private final Map<String, List<Weapon>> context = new HashMap<>();
private final WeaponDatabase weaponDatabase = mock(WeaponDatabase.class);
private final ArmsDealer armsDealer = new ArmsDealer(context, weaponDatabase);;
private final Map<String, List<Student>> context = new HashMap<>();
private final StudentDatabase studentDatabase = mock(StudentDatabase.class);
private final StudentRepository studentRepository = new StudentRepository(context, studentDatabase);;
@Test
void shouldSaveNewStudentWithoutWritingToDb() {
armsDealer.registerNew(weapon1);
armsDealer.registerNew(weapon2);
studentRepository.registerNew(student1);
studentRepository.registerNew(student2);
assertEquals(2, context.get(UnitActions.INSERT.getActionValue()).size());
verifyNoMoreInteractions(weaponDatabase);
verifyNoMoreInteractions(studentDatabase);
}
@Test
void shouldSaveDeletedStudentWithoutWritingToDb() {
armsDealer.registerDeleted(weapon1);
armsDealer.registerDeleted(weapon2);
studentRepository.registerDeleted(student1);
studentRepository.registerDeleted(student2);
assertEquals(2, context.get(UnitActions.DELETE.getActionValue()).size());
verifyNoMoreInteractions(weaponDatabase);
verifyNoMoreInteractions(studentDatabase);
}
@Test
void shouldSaveModifiedStudentWithoutWritingToDb() {
armsDealer.registerModified(weapon1);
armsDealer.registerModified(weapon2);
studentRepository.registerModified(student1);
studentRepository.registerModified(student2);
assertEquals(2, context.get(UnitActions.MODIFY.getActionValue()).size());
verifyNoMoreInteractions(weaponDatabase);
verifyNoMoreInteractions(studentDatabase);
}
@Test
void shouldSaveAllLocalChangesToDb() {
context.put(UnitActions.INSERT.getActionValue(), List.of(weapon1));
context.put(UnitActions.MODIFY.getActionValue(), List.of(weapon1));
context.put(UnitActions.DELETE.getActionValue(), List.of(weapon1));
context.put(UnitActions.INSERT.getActionValue(), List.of(student1));
context.put(UnitActions.MODIFY.getActionValue(), List.of(student1));
context.put(UnitActions.DELETE.getActionValue(), List.of(student1));
armsDealer.commit();
studentRepository.commit();
verify(weaponDatabase, times(1)).insert(weapon1);
verify(weaponDatabase, times(1)).modify(weapon1);
verify(weaponDatabase, times(1)).delete(weapon1);
verify(studentDatabase, times(1)).insert(student1);
verify(studentDatabase, times(1)).modify(student1);
verify(studentDatabase, times(1)).delete(student1);
}
@Test
void shouldNotWriteToDbIfContextIsNull() {
var weaponRepository = new ArmsDealer(null, weaponDatabase);
var studentRepository = new StudentRepository(null, studentDatabase);
weaponRepository.commit();
studentRepository.commit();
verifyNoMoreInteractions(weaponDatabase);
verifyNoMoreInteractions(studentDatabase);
}
@Test
void shouldNotWriteToDbIfNothingToCommit() {
var weaponRepository = new ArmsDealer(new HashMap<>(), weaponDatabase);
var studentRepository = new StudentRepository(new HashMap<>(), studentDatabase);
weaponRepository.commit();
studentRepository.commit();
verifyNoMoreInteractions(weaponDatabase);
verifyNoMoreInteractions(studentDatabase);
}
@Test
void shouldNotInsertToDbIfNoRegisteredStudentsToBeCommitted() {
context.put(UnitActions.MODIFY.getActionValue(), List.of(weapon1));
context.put(UnitActions.DELETE.getActionValue(), List.of(weapon1));
context.put(UnitActions.MODIFY.getActionValue(), List.of(student1));
context.put(UnitActions.DELETE.getActionValue(), List.of(student1));
armsDealer.commit();
studentRepository.commit();
verify(weaponDatabase, never()).insert(weapon1);
verify(studentDatabase, never()).insert(student1);
}
@Test
void shouldNotModifyToDbIfNotRegisteredStudentsToBeCommitted() {
context.put(UnitActions.INSERT.getActionValue(), List.of(weapon1));
context.put(UnitActions.DELETE.getActionValue(), List.of(weapon1));
context.put(UnitActions.INSERT.getActionValue(), List.of(student1));
context.put(UnitActions.DELETE.getActionValue(), List.of(student1));
armsDealer.commit();
studentRepository.commit();
verify(weaponDatabase, never()).modify(weapon1);
verify(studentDatabase, never()).modify(student1);
}
@Test
void shouldNotDeleteFromDbIfNotRegisteredStudentsToBeCommitted() {
context.put(UnitActions.INSERT.getActionValue(), List.of(weapon1));
context.put(UnitActions.MODIFY.getActionValue(), List.of(weapon1));
context.put(UnitActions.INSERT.getActionValue(), List.of(student1));
context.put(UnitActions.MODIFY.getActionValue(), List.of(student1));
armsDealer.commit();
studentRepository.commit();
verify(weaponDatabase, never()).delete(weapon1);
verify(studentDatabase, never()).delete(student1);
}
}