Compare commits
51 Commits
fix-claim-
...
updateThro
Author | SHA1 | Date | |
---|---|---|---|
68a5df1cc9 | |||
1c70cdc333 | |||
55f4ecc234 | |||
3855f0bc15 | |||
d71d2a4abd | |||
67f0a201e2 | |||
3f08aebcd3 | |||
8604e65fce | |||
618b90147a | |||
405495f0a8 | |||
f9ca7f752e | |||
b2a22e506e | |||
1fb1073436 | |||
c28b8edf9e | |||
35ad973730 | |||
a4d77e65f8 | |||
0d1e226258 | |||
dc73d57f3e | |||
4f2becf96f | |||
6fed7cbbb2 | |||
c7c3cb8e19 | |||
6bd6f07bc2 | |||
b0d17a00cd | |||
18b3fa312f | |||
fc75edc7ca | |||
c78d5a3220 | |||
11dcf5a4aa | |||
df84a3cc74 | |||
6523047361 | |||
4a4c136757 | |||
1b5da55d45 | |||
e9b20f674e | |||
23162f474f | |||
1610876fdd | |||
be55b8ebb8 | |||
72aaea5333 | |||
e62a412318 | |||
ff955b4d95 | |||
a7650013a9 | |||
876d2a5466 | |||
636a826bb7 | |||
2230c55582 | |||
f003844c25 | |||
f670ae547b | |||
df73d80365 | |||
4588e09939 | |||
69883196d2 | |||
4dcc20b733 | |||
600227d2e4 | |||
fee898cd27 | |||
b1242629c8 |
@ -1749,6 +1749,33 @@
|
||||
"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"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
@ -10,7 +10,7 @@
|
||||
[](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
|
||||
[](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 -->
|
||||
[](#contributors-)
|
||||
[](#contributors-)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
|
||||
<br/>
|
||||
@ -322,6 +322,9 @@ 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>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
@ -18,10 +18,10 @@ couldn't otherwise because of incompatible interfaces.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real world example
|
||||
Real-world example
|
||||
|
||||
> 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.
|
||||
> 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.
|
||||
> 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 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 a 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 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.
|
||||
* 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.
|
||||
|
||||
## 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 won’t work when we want to adapt a class and all its subclasses.
|
||||
* let’s Adapter override some of Adaptee’s 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 won’t work when we want to adapt a class and all its subclasses.
|
||||
* Let’s Adapter override some of Adaptee’s 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
|
||||
|
||||
* let’s 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.
|
||||
* 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.
|
||||
|
||||
|
||||
## Known uses
|
||||
## Real-world examples
|
||||
|
||||
* [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-)
|
||||
|
@ -33,7 +33,7 @@ import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Test class
|
||||
* Tests for the adapter pattern.
|
||||
*/
|
||||
class AdapterPatternTest {
|
||||
|
||||
|
@ -33,9 +33,7 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
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}
|
||||
* Check whether the execution of the main method in {@link App}
|
||||
* throws an exception.
|
||||
*/
|
||||
|
||||
|
@ -4,6 +4,7 @@ title: Claim Check Pattern
|
||||
folder: cloud-claim-check-pattern
|
||||
permalink: /patterns/cloud-claim-check-pattern/
|
||||
categories: Cloud
|
||||
language: en
|
||||
tags:
|
||||
- Cloud distributed
|
||||
- Microservices
|
||||
|
@ -36,9 +36,6 @@ public class Wizard {
|
||||
private final Deque<Runnable> undoStack = new LinkedList<>();
|
||||
private final Deque<Runnable> redoStack = new LinkedList<>();
|
||||
|
||||
public Wizard() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast spell.
|
||||
*/
|
||||
|
@ -90,6 +90,13 @@ 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,
|
||||
@ -118,17 +125,17 @@ public class Commander {
|
||||
Retry.Operation op = (l) -> {
|
||||
if (!l.isEmpty()) {
|
||||
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
|
||||
LOG.debug("Order " + order.id + ": Error in connecting to shipping service, "
|
||||
+ "trying again..");
|
||||
LOG.debug(ORDER_ID + ": Error in connecting to shipping service, "
|
||||
+ "trying again..", order.id);
|
||||
} else {
|
||||
LOG.debug("Order " + order.id + ": Error in creating shipping request..");
|
||||
LOG.debug(ORDER_ID + ": Error in creating shipping request..", order.id);
|
||||
}
|
||||
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 " + order.id + ": Shipping placed successfully, transaction id: "
|
||||
+ transactionId);
|
||||
LOG.info(ORDER_ID + ": Shipping placed successfully, transaction id: {}",
|
||||
order.id, transactionId);
|
||||
LOG.info("Order has been placed and will be shipped to you. Please wait while we make your"
|
||||
+ " payment... ");
|
||||
sendPaymentRequest(order);
|
||||
@ -138,19 +145,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 " + order.id + ": Shipping not possible to address, trying to add problem "
|
||||
+ "to employee db..");
|
||||
LOG.info(ORDER_ID + ": Shipping not possible to address, trying to add problem "
|
||||
+ "to employee db..", order.id);
|
||||
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 " + order.id + ": Item " + order.item + " unavailable, trying to add "
|
||||
+ "problem to employee handle..");
|
||||
LOG.info(ORDER_ID + ": Item {}" + " unavailable, trying to add "
|
||||
+ "problem to employee handle..", order.id, order.item);
|
||||
employeeHandleIssue(o);
|
||||
} else {
|
||||
LOG.info("Sorry, there was a problem in creating your order. Please try later.");
|
||||
LOG.error("Order " + order.id + ": Shipping service unavailable, order not placed..");
|
||||
LOG.error(ORDER_ID + ": Shipping service unavailable, order not placed..", order.id);
|
||||
finalSiteMsgShown = true;
|
||||
}
|
||||
};
|
||||
@ -164,7 +171,7 @@ public class Commander {
|
||||
if (order.paid.equals(PaymentStatus.TRYING)) {
|
||||
order.paid = PaymentStatus.NOT_DONE;
|
||||
sendPaymentFailureMessage(order);
|
||||
LOG.error("Order " + order.id + ": Payment time for order over, failed and returning..");
|
||||
LOG.error(ORDER_ID + ": Payment time for order over, failed and returning..", order.id);
|
||||
} //if succeeded or failed, would have been dequeued, no attempt to make payment
|
||||
return;
|
||||
}
|
||||
@ -173,17 +180,18 @@ public class Commander {
|
||||
Retry.Operation op = (l) -> {
|
||||
if (!l.isEmpty()) {
|
||||
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
|
||||
LOG.debug("Order " + order.id + ": Error in connecting to payment service,"
|
||||
+ " trying again..");
|
||||
LOG.debug(ORDER_ID + ": Error in connecting to payment service,"
|
||||
+ " trying again..", order.id);
|
||||
} else {
|
||||
LOG.debug("Order " + order.id + ": Error in creating payment request..");
|
||||
LOG.debug(ORDER_ID + ": Error in creating payment request..", order.id);
|
||||
}
|
||||
throw l.remove(0);
|
||||
}
|
||||
if (order.paid.equals(PaymentStatus.TRYING)) {
|
||||
var transactionId = paymentService.receiveRequest(order.price);
|
||||
order.paid = PaymentStatus.DONE;
|
||||
LOG.info("Order " + order.id + ": Payment successful, transaction Id: " + transactionId);
|
||||
LOG.info(ORDER_ID + ": Payment successful, transaction Id: {}",
|
||||
order.id, transactionId);
|
||||
if (!finalSiteMsgShown) {
|
||||
LOG.info("Payment made successfully, thank you for shopping with us!!");
|
||||
finalSiteMsgShown = true;
|
||||
@ -199,7 +207,7 @@ public class Commander {
|
||||
+ "Meanwhile, your order has been converted to COD and will be shipped.");
|
||||
finalSiteMsgShown = true;
|
||||
}
|
||||
LOG.error("Order " + order.id + ": Payment details incorrect, failed..");
|
||||
LOG.error(ORDER_ID + ": Payment details incorrect, failed..", order.id);
|
||||
o.paid = PaymentStatus.NOT_DONE;
|
||||
sendPaymentFailureMessage(o);
|
||||
} else {
|
||||
@ -209,7 +217,7 @@ public class Commander {
|
||||
+ "asap. Don't worry, your order has been placed and will be shipped.");
|
||||
finalSiteMsgShown = true;
|
||||
}
|
||||
LOG.warn("Order " + order.id + ": Payment error, going to queue..");
|
||||
LOG.warn(ORDER_ID + ": Payment error, going to queue..", order.id);
|
||||
sendPaymentPossibleErrorMsg(o);
|
||||
}
|
||||
if (o.paid.equals(PaymentStatus.TRYING) && System
|
||||
@ -234,7 +242,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 " + qt.order.id + ": Queue time for order over, failed..");
|
||||
LOG.trace(ORDER_ID + ": Queue time for order over, failed..", qt.order.id);
|
||||
return;
|
||||
} else if (qt.taskType.equals(TaskType.PAYMENT) && !qt.order.paid.equals(PaymentStatus.TRYING)
|
||||
|| qt.taskType.equals(TaskType.MESSAGING) && (qt.messageType == 1
|
||||
@ -242,30 +250,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 " + qt.order.id + ": Not queueing task since task already done..");
|
||||
LOG.trace(ORDER_ID + ": Not queueing task since task already done..", qt.order.id);
|
||||
return;
|
||||
}
|
||||
var list = queue.exceptionsList;
|
||||
Thread t = new Thread(() -> {
|
||||
Retry.Operation op = (list1) -> {
|
||||
if (!list1.isEmpty()) {
|
||||
LOG.warn("Order " + qt.order.id + ": Error in connecting to queue db, trying again..");
|
||||
LOG.warn(ORDER_ID + ": Error in connecting to queue db, trying again..", qt.order.id);
|
||||
throw list1.remove(0);
|
||||
}
|
||||
queue.add(qt);
|
||||
queueItems++;
|
||||
LOG.info("Order " + qt.order.id + ": " + qt.getType() + " task enqueued..");
|
||||
LOG.info(ORDER_ID + ": {}" + " task enqueued..", qt.order.id, qt.getType());
|
||||
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 " + qt1.order.id + ": Unable to enqueue payment task,"
|
||||
+ " payment failed..");
|
||||
LOG.error(ORDER_ID + ": Unable to enqueue payment task,"
|
||||
+ " payment failed..", qt1.order.id);
|
||||
}
|
||||
LOG.error("Order " + qt1.order.id + ": Unable to enqueue task of type " + qt1.getType()
|
||||
+ ", trying to add to employee handle..");
|
||||
LOG.error(ORDER_ID + ": Unable to enqueue task of type {}"
|
||||
+ ", trying to add to employee handle..", qt1.order.id, qt1.getType());
|
||||
employeeHandleIssue(qt1.order);
|
||||
};
|
||||
var r = new Retry<>(op, handleError, numOfRetries, retryDuration,
|
||||
@ -328,7 +336,7 @@ public class Commander {
|
||||
|
||||
private void sendSuccessMessage(Order order) {
|
||||
if (System.currentTimeMillis() - order.createdTime >= this.messageTime) {
|
||||
LOG.trace("Order " + order.id + ": Message time for order over, returning..");
|
||||
LOG.trace(ORDER_ID + ": Message time for order over, returning..", order.id);
|
||||
return;
|
||||
}
|
||||
var list = messagingService.exceptionsList;
|
||||
@ -354,8 +362,8 @@ public class Commander {
|
||||
&& System.currentTimeMillis() - o.createdTime < messageTime) {
|
||||
var qt = new QueueTask(order, TaskType.MESSAGING, 2);
|
||||
updateQueue(qt);
|
||||
LOG.info("Order " + order.id + ": Error in sending Payment Success message, trying to"
|
||||
+ " queue task and add to employee handle..");
|
||||
LOG.info(ORDER_ID + ": Error in sending Payment Success message, trying to"
|
||||
+ " queue task and add to employee handle..", order.id);
|
||||
employeeHandleIssue(order);
|
||||
}
|
||||
}
|
||||
@ -364,11 +372,11 @@ public class Commander {
|
||||
return (l) -> {
|
||||
if (!l.isEmpty()) {
|
||||
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
|
||||
LOG.debug("Order " + order.id + ": Error in connecting to messaging service "
|
||||
+ "(Payment Success msg), trying again..");
|
||||
LOG.debug(ORDER_ID + ERROR_CONNECTING_MSG_SVC
|
||||
+ "(Payment Success msg), trying again..", order.id);
|
||||
} else {
|
||||
LOG.debug("Order " + order.id + ": Error in creating Payment Success"
|
||||
+ " messaging request..");
|
||||
LOG.debug(ORDER_ID + ": Error in creating Payment Success"
|
||||
+ " messaging request..", order.id);
|
||||
}
|
||||
throw l.remove(0);
|
||||
}
|
||||
@ -376,15 +384,15 @@ public class Commander {
|
||||
&& !order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) {
|
||||
var requestId = messagingService.receiveRequest(2);
|
||||
order.messageSent = MessageSent.PAYMENT_SUCCESSFUL;
|
||||
LOG.info("Order " + order.id + ": Payment Success message sent,"
|
||||
+ " request Id: " + requestId);
|
||||
LOG.info(ORDER_ID + ": Payment Success message sent,"
|
||||
+ REQUEST_ID, order.id, requestId);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void sendPaymentFailureMessage(Order order) {
|
||||
if (System.currentTimeMillis() - order.createdTime >= this.messageTime) {
|
||||
LOG.trace("Order " + order.id + ": Message time for order over, returning..");
|
||||
LOG.trace(ORDER_ID + ": Message time for order over, returning..", order.id);
|
||||
return;
|
||||
}
|
||||
var list = messagingService.exceptionsList;
|
||||
@ -412,8 +420,8 @@ public class Commander {
|
||||
&& System.currentTimeMillis() - o.createdTime < messageTime) {
|
||||
var qt = new QueueTask(order, TaskType.MESSAGING, 0);
|
||||
updateQueue(qt);
|
||||
LOG.warn("Order " + order.id + ": Error in sending Payment Failure message, "
|
||||
+ "trying to queue task and add to employee handle..");
|
||||
LOG.warn(ORDER_ID + ": Error in sending Payment Failure message, "
|
||||
+ "trying to queue task and add to employee handle..", order.id);
|
||||
employeeHandleIssue(o);
|
||||
}
|
||||
}
|
||||
@ -421,11 +429,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 " + order.id + ": Error in connecting to messaging service "
|
||||
+ "(Payment Failure msg), trying again..");
|
||||
LOG.debug(ORDER_ID + ERROR_CONNECTING_MSG_SVC
|
||||
+ "(Payment Failure msg), trying again..", order.id);
|
||||
} else {
|
||||
LOG.debug("Order " + order.id + ": Error in creating Payment Failure"
|
||||
+ " message request..");
|
||||
LOG.debug(ORDER_ID + ": Error in creating Payment Failure"
|
||||
+ " message request..", order.id);
|
||||
}
|
||||
throw l.remove(0);
|
||||
}
|
||||
@ -433,8 +441,8 @@ public class Commander {
|
||||
&& !order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) {
|
||||
var requestId = messagingService.receiveRequest(0);
|
||||
order.messageSent = MessageSent.PAYMENT_FAIL;
|
||||
LOG.info("Order " + order.id + ": Payment Failure message sent successfully,"
|
||||
+ " request Id: " + requestId);
|
||||
LOG.info(ORDER_ID + ": Payment Failure message sent successfully,"
|
||||
+ REQUEST_ID, order.id, requestId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -469,7 +477,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);
|
||||
}
|
||||
}
|
||||
@ -478,11 +486,11 @@ public class Commander {
|
||||
throws Exception {
|
||||
if (!l.isEmpty()) {
|
||||
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
|
||||
LOG.debug("Order " + order.id + ": Error in connecting to messaging service "
|
||||
+ "(Payment Error msg), trying again..");
|
||||
LOG.debug(ORDER_ID + ERROR_CONNECTING_MSG_SVC
|
||||
+ "(Payment Error msg), trying again..", order.id);
|
||||
} else {
|
||||
LOG.debug("Order " + order.id + ": Error in creating Payment Error"
|
||||
+ " messaging request..");
|
||||
LOG.debug(ORDER_ID + ": Error in creating Payment Error"
|
||||
+ " messaging request..", order.id);
|
||||
}
|
||||
throw l.remove(0);
|
||||
}
|
||||
@ -490,28 +498,28 @@ public class Commander {
|
||||
.equals(MessageSent.NONE_SENT)) {
|
||||
var requestId = messagingService.receiveRequest(1);
|
||||
order.messageSent = MessageSent.PAYMENT_TRYING;
|
||||
LOG.info("Order " + order.id + ": Payment Error message sent successfully,"
|
||||
+ " request Id: " + requestId);
|
||||
LOG.info(ORDER_ID + ": Payment Error message sent successfully,"
|
||||
+ REQUEST_ID, order.id, requestId);
|
||||
}
|
||||
}
|
||||
|
||||
private void employeeHandleIssue(Order order) {
|
||||
if (System.currentTimeMillis() - order.createdTime >= this.employeeTime) {
|
||||
LOG.trace("Order " + order.id + ": Employee handle time for order over, returning..");
|
||||
LOG.trace(ORDER_ID + ": Employee handle time for order over, returning..", order.id);
|
||||
return;
|
||||
}
|
||||
var list = employeeDb.exceptionsList;
|
||||
var t = new Thread(() -> {
|
||||
Retry.Operation op = (l) -> {
|
||||
if (!l.isEmpty()) {
|
||||
LOG.warn("Order " + order.id + ": Error in connecting to employee handle,"
|
||||
+ " trying again..");
|
||||
LOG.warn(ORDER_ID + ": Error in connecting to employee handle,"
|
||||
+ " trying again..", order.id);
|
||||
throw l.remove(0);
|
||||
}
|
||||
if (!order.addedToEmployeeHandle) {
|
||||
employeeDb.receiveRequest(order);
|
||||
order.addedToEmployeeHandle = true;
|
||||
LOG.info("Order " + order.id + ": Added order to employee database");
|
||||
LOG.info(ORDER_ID + ": Added order to employee database", order.id);
|
||||
}
|
||||
};
|
||||
Retry.HandleErrorIssue<Order> handleError = (o, err) -> {
|
||||
@ -519,8 +527,8 @@ public class Commander {
|
||||
.currentTimeMillis() - order.createdTime < employeeTime) {
|
||||
var qt = new QueueTask(order, TaskType.EMPLOYEE_DB, -1);
|
||||
updateQueue(qt);
|
||||
LOG.warn("Order " + order.id + ": Error in adding to employee db,"
|
||||
+ " trying to queue task..");
|
||||
LOG.warn(ORDER_ID + ": Error in adding to employee db,"
|
||||
+ " trying to queue task..", order.id);
|
||||
}
|
||||
};
|
||||
var r = new Retry<>(op, handleError, numOfRetries, retryDuration,
|
||||
@ -538,51 +546,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 " + qt.order.id + ": Started doing task of type " + qt.getType());
|
||||
LOG.trace(ORDER_ID + ": Started doing task of type {}", qt.order.id, qt.getType());
|
||||
if (qt.getFirstAttemptTime() == -1) {
|
||||
qt.setFirstAttemptTime(System.currentTimeMillis());
|
||||
}
|
||||
if (System.currentTimeMillis() - qt.getFirstAttemptTime() >= queueTaskTime) {
|
||||
tryDequeue();
|
||||
LOG.trace("Order " + qt.order.id + ": This queue task of type " + qt.getType()
|
||||
+ " does not need to be done anymore (timeout), dequeue..");
|
||||
LOG.trace(ORDER_ID + ": This queue task of type {}"
|
||||
+ " does not need to be done anymore (timeout), dequeue..", qt.order.id, qt.getType());
|
||||
} else {
|
||||
if (qt.taskType.equals(TaskType.PAYMENT)) {
|
||||
if (!qt.order.paid.equals(PaymentStatus.TRYING)) {
|
||||
tryDequeue();
|
||||
LOG.trace("Order " + qt.order.id + ": This payment task already done, dequeueing..");
|
||||
LOG.trace(ORDER_ID + ": This payment task already done, dequeueing..", qt.order.id);
|
||||
} else {
|
||||
sendPaymentRequest(qt.order);
|
||||
LOG.debug("Order " + qt.order.id + ": Trying to connect to payment service..");
|
||||
LOG.debug(ORDER_ID + ": Trying to connect to payment service..", qt.order.id);
|
||||
}
|
||||
} 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 " + qt.order.id + ": This messaging task already done, dequeue..");
|
||||
LOG.trace(ORDER_ID + ": This messaging task already done, dequeue..", qt.order.id);
|
||||
} else if (qt.messageType == 1 && (!qt.order.messageSent.equals(MessageSent.NONE_SENT)
|
||||
|| !qt.order.paid.equals(PaymentStatus.TRYING))) {
|
||||
tryDequeue();
|
||||
LOG.trace("Order " + qt.order.id + ": This messaging task does not need to be done,"
|
||||
+ " dequeue..");
|
||||
LOG.trace(ORDER_ID + ": This messaging task does not need to be done,"
|
||||
+ " dequeue..", qt.order.id);
|
||||
} else if (qt.messageType == 0) {
|
||||
sendPaymentFailureMessage(qt.order);
|
||||
LOG.debug("Order " + qt.order.id + ": Trying to connect to messaging service..");
|
||||
LOG.debug(ORDER_ID + TRY_CONNECTING_MSG_SVC, qt.order.id);
|
||||
} else if (qt.messageType == 1) {
|
||||
sendPaymentPossibleErrorMsg(qt.order);
|
||||
LOG.debug("Order " + qt.order.id + ": Trying to connect to messaging service..");
|
||||
LOG.debug(ORDER_ID + TRY_CONNECTING_MSG_SVC, qt.order.id);
|
||||
} else if (qt.messageType == 2) {
|
||||
sendSuccessMessage(qt.order);
|
||||
LOG.debug("Order " + qt.order.id + ": Trying to connect to messaging service..");
|
||||
LOG.debug(ORDER_ID + TRY_CONNECTING_MSG_SVC, qt.order.id);
|
||||
}
|
||||
} else if (qt.taskType.equals(TaskType.EMPLOYEE_DB)) {
|
||||
if (qt.order.addedToEmployeeHandle) {
|
||||
tryDequeue();
|
||||
LOG.trace("Order " + qt.order.id + ": This employee handle task already done,"
|
||||
+ " dequeue..");
|
||||
LOG.trace(ORDER_ID + ": This employee handle task already done,"
|
||||
+ " dequeue..", qt.order.id);
|
||||
} else {
|
||||
employeeHandleIssue(qt.order);
|
||||
LOG.debug("Order " + qt.order.id + ": Trying to connect to employee handle..");
|
||||
LOG.debug(ORDER_ID + ": Trying to connect to employee handle..", qt.order.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,526 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -49,13 +49,28 @@ public class App {
|
||||
public static void main(String[] args) {
|
||||
|
||||
var kingJoffrey = new KingJoffrey();
|
||||
var kingsHand = new KingsHand(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 emitters = List.of(
|
||||
kingsHand,
|
||||
new LordBaelish(kingsHand),
|
||||
new LordVarys(kingsHand),
|
||||
new Scout(kingsHand)
|
||||
baelish,
|
||||
varys,
|
||||
scout
|
||||
);
|
||||
|
||||
Arrays.stream(Weekday.values())
|
||||
|
@ -31,6 +31,7 @@ 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");
|
||||
|
@ -23,31 +23,48 @@
|
||||
|
||||
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 List<EventObserver> observers;
|
||||
private final Map<Event, List<EventObserver>> observerLists;
|
||||
|
||||
public EventEmitter() {
|
||||
observers = new LinkedList<>();
|
||||
observerLists = new HashMap<>();
|
||||
}
|
||||
|
||||
public EventEmitter(EventObserver obs) {
|
||||
public EventEmitter(EventObserver obs, Event e) {
|
||||
this();
|
||||
registerObserver(obs);
|
||||
registerObserver(obs, e);
|
||||
}
|
||||
|
||||
public final void registerObserver(EventObserver obs) {
|
||||
observers.add(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);
|
||||
}
|
||||
}
|
||||
|
||||
protected void notifyObservers(Event e) {
|
||||
observers.forEach(obs -> obs.onEvent(e));
|
||||
if (observerLists.containsKey(e)) {
|
||||
observerLists
|
||||
.get(e)
|
||||
.forEach(observer -> observer.onEvent(e));
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void timePasses(Weekday day);
|
||||
|
@ -31,8 +31,8 @@ public class KingsHand extends EventEmitter implements EventObserver {
|
||||
public KingsHand() {
|
||||
}
|
||||
|
||||
public KingsHand(EventObserver obs) {
|
||||
super(obs);
|
||||
public KingsHand(EventObserver obs, Event e) {
|
||||
super(obs, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -42,6 +42,5 @@ public class KingsHand extends EventEmitter implements EventObserver {
|
||||
|
||||
@Override
|
||||
public void timePasses(Weekday day) {
|
||||
// NOP
|
||||
}
|
||||
}
|
||||
|
@ -31,8 +31,8 @@ public class LordBaelish extends EventEmitter {
|
||||
public LordBaelish() {
|
||||
}
|
||||
|
||||
public LordBaelish(EventObserver obs) {
|
||||
super(obs);
|
||||
public LordBaelish(EventObserver obs, Event e) {
|
||||
super(obs, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -23,16 +23,19 @@
|
||||
|
||||
package com.iluwatar.event.aggregator;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* LordVarys produces events.
|
||||
*/
|
||||
public class LordVarys extends EventEmitter {
|
||||
@Slf4j
|
||||
public class LordVarys extends EventEmitter implements EventObserver {
|
||||
|
||||
public LordVarys() {
|
||||
}
|
||||
|
||||
public LordVarys(EventObserver obs) {
|
||||
super(obs);
|
||||
public LordVarys(EventObserver obs, Event e) {
|
||||
super(obs, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -41,4 +44,10 @@ public class LordVarys extends EventEmitter {
|
||||
notifyObservers(Event.TRAITOR_DETECTED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onEvent(Event e) {
|
||||
notifyObservers(e);
|
||||
}
|
||||
}
|
||||
|
@ -31,8 +31,8 @@ public class Scout extends EventEmitter {
|
||||
public Scout() {
|
||||
}
|
||||
|
||||
public Scout(EventObserver obs) {
|
||||
super(obs);
|
||||
public Scout(EventObserver obs, Event e) {
|
||||
super(obs, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -40,5 +40,8 @@ public class Scout extends EventEmitter {
|
||||
if (day == Weekday.TUESDAY) {
|
||||
notifyObservers(Event.WARSHIPS_APPROACHING);
|
||||
}
|
||||
if (day == Weekday.WEDNESDAY) {
|
||||
notifyObservers(Event.WHITE_WALKERS_SIGHTED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ 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;
|
||||
@ -46,7 +47,7 @@ abstract class EventEmitterTest<E extends EventEmitter> {
|
||||
/**
|
||||
* Factory used to create a new instance of the test object with a default observer
|
||||
*/
|
||||
private final Function<EventObserver, E> factoryWithDefaultObserver;
|
||||
private final BiFunction<EventObserver, Event, E> factoryWithDefaultObserver;
|
||||
|
||||
/**
|
||||
* Factory used to create a new instance of the test object without passing a default observer
|
||||
@ -67,7 +68,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 Function<EventObserver, E> factoryWithDefaultObserver,
|
||||
final BiFunction<EventObserver, Event, E> factoryWithDefaultObserver,
|
||||
final Supplier<E> factoryWithoutDefaultObserver) {
|
||||
|
||||
this.specialDay = specialDay;
|
||||
@ -129,8 +130,8 @@ abstract class EventEmitterTest<E extends EventEmitter> {
|
||||
final var observer2 = mock(EventObserver.class);
|
||||
|
||||
final var emitter = this.factoryWithoutDefaultObserver.get();
|
||||
emitter.registerObserver(observer1);
|
||||
emitter.registerObserver(observer2);
|
||||
emitter.registerObserver(observer1, event);
|
||||
emitter.registerObserver(observer2, event);
|
||||
|
||||
testAllDays(specialDay, event, emitter, observer1, observer2);
|
||||
}
|
||||
@ -146,9 +147,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);
|
||||
emitter.registerObserver(observer1);
|
||||
emitter.registerObserver(observer2);
|
||||
final var emitter = this.factoryWithDefaultObserver.apply(defaultObserver, event);
|
||||
emitter.registerObserver(observer1, event);
|
||||
emitter.registerObserver(observer2, event);
|
||||
|
||||
testAllDays(specialDay, event, emitter, defaultObserver, observer1, observer2);
|
||||
}
|
||||
|
@ -55,7 +55,11 @@ class KingsHandTest extends EventEmitterTest<KingsHand> {
|
||||
@Test
|
||||
void testPassThrough() throws Exception {
|
||||
final var observer = mock(EventObserver.class);
|
||||
final var kingsHand = new KingsHand(observer);
|
||||
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);
|
||||
|
||||
// The kings hand should not pass any events before he received one
|
||||
verifyZeroInteractions(observer);
|
||||
|
@ -34,7 +34,9 @@ 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);
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -96,9 +96,11 @@ public class NioReactor {
|
||||
* @throws IOException if any I/O error occurs.
|
||||
*/
|
||||
public void stop() throws InterruptedException, IOException {
|
||||
reactorMain.shutdownNow();
|
||||
reactorMain.shutdown();
|
||||
selector.wakeup();
|
||||
reactorMain.awaitTermination(4, TimeUnit.SECONDS);
|
||||
if (!reactorMain.awaitTermination(4, TimeUnit.SECONDS)) {
|
||||
reactorMain.shutdownNow();
|
||||
}
|
||||
selector.close();
|
||||
LOGGER.info("Reactor stopped");
|
||||
}
|
||||
|
@ -64,6 +64,8 @@ public class ThreadPoolDispatcher implements Dispatcher {
|
||||
@Override
|
||||
public void stop() throws InterruptedException {
|
||||
executorService.shutdown();
|
||||
executorService.awaitTermination(4, TimeUnit.SECONDS);
|
||||
if (executorService.awaitTermination(4, TimeUnit.SECONDS)) {
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,11 +34,11 @@ import java.util.HashMap;
|
||||
public class SpatialPartitionBubbles extends SpatialPartitionGeneric<Bubble> {
|
||||
|
||||
private final HashMap<Integer, Bubble> bubbles;
|
||||
private final QuadTree quadTree;
|
||||
private final QuadTree bubblesQuadTree;
|
||||
|
||||
SpatialPartitionBubbles(HashMap<Integer, Bubble> bubbles, QuadTree quadTree) {
|
||||
SpatialPartitionBubbles(HashMap<Integer, Bubble> bubbles, QuadTree bubblesQuadTree) {
|
||||
this.bubbles = bubbles;
|
||||
this.quadTree = quadTree;
|
||||
this.bubblesQuadTree = bubblesQuadTree;
|
||||
}
|
||||
|
||||
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.quadTree.query(rect, quadTreeQueryResult);
|
||||
this.bubblesQuadTree.query(rect, quadTreeQueryResult);
|
||||
//handling these collisions
|
||||
b.handleCollision(quadTreeQueryResult, this.bubbles);
|
||||
}
|
||||
|
@ -18,11 +18,11 @@ threads and eliminating the latency of creating new threads.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real world example
|
||||
Real-world example
|
||||
|
||||
> We have a large number of relatively short tasks at hand. We need to peel huge amounts of potatoes
|
||||
> and serve mighty amount of coffee cups. Creating a new thread for each task would be a waste so we
|
||||
> establish a thread pool.
|
||||
> and serve a mighty amount of coffee cups. Creating a new thread for each task would be a waste so
|
||||
> we establish a thread pool.
|
||||
|
||||
In plain words
|
||||
|
||||
@ -99,7 +99,7 @@ public class PotatoPeelingTask extends Task {
|
||||
}
|
||||
```
|
||||
|
||||
Next we present a runnable `Worker` class that the thread pool will utilize to handle all the potato
|
||||
Next, we present a runnable `Worker` class that the thread pool will utilize to handle all the potato
|
||||
peeling and coffee making.
|
||||
|
||||
```java
|
||||
|
@ -16,10 +16,12 @@ Ensure that a given client is not able to access service resources more than the
|
||||
|
||||
## Explanation
|
||||
|
||||
Real world example
|
||||
Real-world example
|
||||
|
||||
> A large multinational corporation offers API to its customers. The API is rate-limited and each
|
||||
> customer can only make certain amount of calls per second.
|
||||
> A young human and an old dwarf walk into a bar. They start ordering beers from the bartender.
|
||||
> The bartender immediately sees that the young human shouldn't consume too many drinks too fast
|
||||
> and refuses to serve if enough time has not passed. For the old dwarf, the serving rate can
|
||||
> be higher.
|
||||
|
||||
In plain words
|
||||
|
||||
@ -33,30 +35,25 @@ In plain words
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
Tenant class presents the clients of the API. CallsCount tracks the number of API calls per tenant.
|
||||
`BarCustomer` class presents the clients of the `Bartender` API. `CallsCount` tracks the number of
|
||||
calls per `BarCustomer`.
|
||||
|
||||
```java
|
||||
public class Tenant {
|
||||
public class BarCustomer {
|
||||
|
||||
private final String name;
|
||||
private final int allowedCallsPerSecond;
|
||||
@Getter
|
||||
private final String name;
|
||||
@Getter
|
||||
private final int allowedCallsPerSecond;
|
||||
|
||||
public Tenant(String name, int allowedCallsPerSecond, CallsCount callsCount) {
|
||||
if (allowedCallsPerSecond < 0) {
|
||||
throw new InvalidParameterException("Number of calls less than 0 not allowed");
|
||||
public BarCustomer(String name, int allowedCallsPerSecond, CallsCount callsCount) {
|
||||
if (allowedCallsPerSecond < 0) {
|
||||
throw new InvalidParameterException("Number of calls less than 0 not allowed");
|
||||
}
|
||||
this.name = name;
|
||||
this.allowedCallsPerSecond = allowedCallsPerSecond;
|
||||
callsCount.addTenant(name);
|
||||
}
|
||||
this.name = name;
|
||||
this.allowedCallsPerSecond = allowedCallsPerSecond;
|
||||
callsCount.addTenant(name);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getAllowedCallsPerSecond() {
|
||||
return allowedCallsPerSecond;
|
||||
}
|
||||
}
|
||||
|
||||
@Slf4j
|
||||
@ -76,14 +73,14 @@ public final class CallsCount {
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
LOGGER.debug("Resetting the map.");
|
||||
tenantCallsCount.replaceAll((k, v) -> new AtomicLong(0));
|
||||
LOGGER.info("reset counters");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Next we introduce the service that the tenants are calling. To track the call count we use the
|
||||
throttler timer.
|
||||
Next, the service that the tenants are calling is introduced. To track the call count, a throttler
|
||||
timer is used.
|
||||
|
||||
```java
|
||||
public interface Throttler {
|
||||
@ -111,71 +108,103 @@ public class ThrottleTimerImpl implements Throttler {
|
||||
}, 0, throttlePeriod);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
class B2BService {
|
||||
`Bartender` offers the `orderDrink` service to the `BarCustomer`s. The customers probably don't
|
||||
know that the beer serving rate is limited by their appearances.
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(B2BService.class);
|
||||
private final CallsCount callsCount;
|
||||
```java
|
||||
class Bartender {
|
||||
|
||||
public B2BService(Throttler timer, CallsCount callsCount) {
|
||||
this.callsCount = callsCount;
|
||||
timer.start();
|
||||
}
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Bartender.class);
|
||||
private final CallsCount callsCount;
|
||||
|
||||
public int dummyCustomerApi(Tenant tenant) {
|
||||
var tenantName = tenant.getName();
|
||||
var count = callsCount.getCount(tenantName);
|
||||
LOGGER.debug("Counter for {} : {} ", tenant.getName(), count);
|
||||
if (count >= tenant.getAllowedCallsPerSecond()) {
|
||||
LOGGER.error("API access per second limit reached for: {}", tenantName);
|
||||
return -1;
|
||||
public Bartender(Throttler timer, CallsCount callsCount) {
|
||||
this.callsCount = callsCount;
|
||||
timer.start();
|
||||
}
|
||||
callsCount.incrementCount(tenantName);
|
||||
return getRandomCustomerId();
|
||||
}
|
||||
|
||||
private int getRandomCustomerId() {
|
||||
return ThreadLocalRandom.current().nextInt(1, 10000);
|
||||
}
|
||||
public int orderDrink(BarCustomer barCustomer) {
|
||||
var tenantName = barCustomer.getName();
|
||||
var count = callsCount.getCount(tenantName);
|
||||
if (count >= barCustomer.getAllowedCallsPerSecond()) {
|
||||
LOGGER.error("I'm sorry {}, you've had enough for today!", tenantName);
|
||||
return -1;
|
||||
}
|
||||
callsCount.incrementCount(tenantName);
|
||||
LOGGER.debug("Serving beer to {} : [{} consumed] ", barCustomer.getName(), count+1);
|
||||
return getRandomCustomerId();
|
||||
}
|
||||
|
||||
private int getRandomCustomerId() {
|
||||
return ThreadLocalRandom.current().nextInt(1, 10000);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now we are ready to see the full example in action. Tenant Adidas is rate-limited to 5 calls per
|
||||
second and Nike to 6.
|
||||
Now it is possible to see the full example in action. `BarCustomer` young human is rate-limited to 2
|
||||
calls per second and the old dwarf to 4.
|
||||
|
||||
```java
|
||||
public static void main(String[] args) {
|
||||
public static void main(String[] args) {
|
||||
var callsCount = new CallsCount();
|
||||
var adidas = new Tenant("Adidas", 5, callsCount);
|
||||
var nike = new Tenant("Nike", 6, callsCount);
|
||||
var human = new BarCustomer("young human", 2, callsCount);
|
||||
var dwarf = new BarCustomer("dwarf soldier", 4, callsCount);
|
||||
|
||||
var executorService = Executors.newFixedThreadPool(2);
|
||||
executorService.execute(() -> makeServiceCalls(adidas, callsCount));
|
||||
executorService.execute(() -> makeServiceCalls(nike, callsCount));
|
||||
executorService.shutdown();
|
||||
|
||||
try {
|
||||
executorService.awaitTermination(10, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Executor Service terminated: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void makeServiceCalls(Tenant tenant, CallsCount callsCount) {
|
||||
var timer = new ThrottleTimerImpl(10, callsCount);
|
||||
var service = new B2BService(timer, callsCount);
|
||||
executorService.execute(() -> makeServiceCalls(human, callsCount));
|
||||
executorService.execute(() -> makeServiceCalls(dwarf, callsCount));
|
||||
|
||||
executorService.shutdown();
|
||||
try {
|
||||
executorService.awaitTermination(10, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Executor service terminated: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void makeServiceCalls(BarCustomer barCustomer, CallsCount callsCount) {
|
||||
var timer = new ThrottleTimerImpl(1000, callsCount);
|
||||
var service = new Bartender(timer, callsCount);
|
||||
// Sleep is introduced to keep the output in check and easy to view and analyze the results.
|
||||
IntStream.range(0, 20).forEach(i -> {
|
||||
service.dummyCustomerApi(tenant);
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Thread interrupted: {}", e.getMessage());
|
||||
}
|
||||
IntStream.range(0, 50).forEach(i -> {
|
||||
service.orderDrink(barCustomer);
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Thread interrupted: {}", e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
An excerpt from the example's console output:
|
||||
|
||||
```
|
||||
18:46:36.218 [Timer-0] INFO com.iluwatar.throttling.CallsCount - reset counters
|
||||
18:46:36.218 [Timer-1] INFO com.iluwatar.throttling.CallsCount - reset counters
|
||||
18:46:36.242 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [1 consumed]
|
||||
18:46:36.242 [pool-1-thread-1] DEBUG com.iluwatar.throttling.Bartender - Serving beer to young human : [1 consumed]
|
||||
18:46:36.342 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [2 consumed]
|
||||
18:46:36.342 [pool-1-thread-1] DEBUG com.iluwatar.throttling.Bartender - Serving beer to young human : [2 consumed]
|
||||
18:46:36.443 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:36.443 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [3 consumed]
|
||||
18:46:36.544 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:36.544 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [4 consumed]
|
||||
18:46:36.645 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
|
||||
18:46:36.645 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:36.745 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:36.745 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
|
||||
18:46:36.846 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:36.846 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
|
||||
18:46:36.947 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
|
||||
18:46:36.947 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:37.048 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
|
||||
18:46:37.048 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:37.148 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:37.148 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
|
||||
@ -185,7 +214,7 @@ second and Nike to 6.
|
||||
|
||||
The Throttling pattern should be used:
|
||||
|
||||
* When a service access needs to be restricted to not have high impacts on the performance of the service.
|
||||
* When service access needs to be restricted not to have high impact on the performance of the service.
|
||||
* When multiple clients are consuming the same service resources and restriction has to be made according to the usage per client.
|
||||
|
||||
## Credits
|
||||
|
@ -34,11 +34,11 @@ import lombok.extern.slf4j.Slf4j;
|
||||
* complete service by users or a particular tenant. This can allow systems to continue to function
|
||||
* and meet service level agreements, even when an increase in demand places load on resources.
|
||||
* <p>
|
||||
* In this example we have ({@link App}) as the initiating point of the service. This is a time
|
||||
* In this example there is a {@link Bartender} serving beer to {@link BarCustomer}s. This is a time
|
||||
* based throttling, i.e. only a certain number of calls are allowed per second.
|
||||
* </p>
|
||||
* ({@link Tenant}) is the Tenant POJO class with which many tenants can be created ({@link
|
||||
* B2BService}) is the service which is consumed by the tenants and is throttled.
|
||||
* ({@link BarCustomer}) is the service tenant class having a name and the number of calls allowed.
|
||||
* ({@link Bartender}) is the service which is consumed by the tenants and is throttled.
|
||||
*/
|
||||
@Slf4j
|
||||
public class App {
|
||||
@ -50,33 +50,35 @@ public class App {
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
var callsCount = new CallsCount();
|
||||
var adidas = new Tenant("Adidas", 5, callsCount);
|
||||
var nike = new Tenant("Nike", 6, callsCount);
|
||||
var human = new BarCustomer("young human", 2, callsCount);
|
||||
var dwarf = new BarCustomer("dwarf soldier", 4, callsCount);
|
||||
|
||||
var executorService = Executors.newFixedThreadPool(2);
|
||||
|
||||
executorService.execute(() -> makeServiceCalls(adidas, callsCount));
|
||||
executorService.execute(() -> makeServiceCalls(nike, callsCount));
|
||||
executorService.execute(() -> makeServiceCalls(human, callsCount));
|
||||
executorService.execute(() -> makeServiceCalls(dwarf, callsCount));
|
||||
|
||||
executorService.shutdown();
|
||||
try {
|
||||
executorService.awaitTermination(10, TimeUnit.SECONDS);
|
||||
if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Executor Service terminated: {}", e.getMessage());
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make calls to the B2BService dummy API.
|
||||
* Make calls to the bartender.
|
||||
*/
|
||||
private static void makeServiceCalls(Tenant tenant, CallsCount callsCount) {
|
||||
var timer = new ThrottleTimerImpl(10, callsCount);
|
||||
var service = new B2BService(timer, callsCount);
|
||||
private static void makeServiceCalls(BarCustomer barCustomer, CallsCount callsCount) {
|
||||
var timer = new ThrottleTimerImpl(1000, callsCount);
|
||||
var service = new Bartender(timer, callsCount);
|
||||
// Sleep is introduced to keep the output in check and easy to view and analyze the results.
|
||||
IntStream.range(0, 20).forEach(i -> {
|
||||
service.dummyCustomerApi(tenant);
|
||||
IntStream.range(0, 50).forEach(i -> {
|
||||
service.orderDrink(barCustomer);
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Thread interrupted: {}", e.getMessage());
|
||||
}
|
||||
|
@ -25,22 +25,26 @@ package com.iluwatar.throttling;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
|
||||
/**
|
||||
* A Pojo class to create a basic Tenant with the allowed calls per second.
|
||||
*/
|
||||
public class Tenant {
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* BarCustomer is a tenant with a name and a number of allowed calls per second.
|
||||
*/
|
||||
public class BarCustomer {
|
||||
|
||||
@Getter
|
||||
private final String name;
|
||||
@Getter
|
||||
private final int allowedCallsPerSecond;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param name Name of the tenant
|
||||
* @param allowedCallsPerSecond The number of calls allowed for a particular tenant.
|
||||
* @param name Name of the BarCustomer
|
||||
* @param allowedCallsPerSecond The number of calls allowed for this particular tenant.
|
||||
* @throws InvalidParameterException If number of calls is less than 0, throws exception.
|
||||
*/
|
||||
public Tenant(String name, int allowedCallsPerSecond, CallsCount callsCount) {
|
||||
public BarCustomer(String name, int allowedCallsPerSecond, CallsCount callsCount) {
|
||||
if (allowedCallsPerSecond < 0) {
|
||||
throw new InvalidParameterException("Number of calls less than 0 not allowed");
|
||||
}
|
||||
@ -48,12 +52,4 @@ public class Tenant {
|
||||
this.allowedCallsPerSecond = allowedCallsPerSecond;
|
||||
callsCount.addTenant(name);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getAllowedCallsPerSecond() {
|
||||
return allowedCallsPerSecond;
|
||||
}
|
||||
}
|
@ -29,33 +29,32 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A service which accepts a tenant and throttles the resource based on the time given to the
|
||||
* tenant.
|
||||
* Bartender is a service which accepts a BarCustomer (tenant) and throttles
|
||||
* the resource based on the time given to the tenant.
|
||||
*/
|
||||
class B2BService {
|
||||
class Bartender {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(B2BService.class);
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Bartender.class);
|
||||
private final CallsCount callsCount;
|
||||
|
||||
public B2BService(Throttler timer, CallsCount callsCount) {
|
||||
public Bartender(Throttler timer, CallsCount callsCount) {
|
||||
this.callsCount = callsCount;
|
||||
timer.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls dummy customer api.
|
||||
*
|
||||
* Orders a drink from the bartender.
|
||||
* @return customer id which is randomly generated
|
||||
*/
|
||||
public int dummyCustomerApi(Tenant tenant) {
|
||||
var tenantName = tenant.getName();
|
||||
public int orderDrink(BarCustomer barCustomer) {
|
||||
var tenantName = barCustomer.getName();
|
||||
var count = callsCount.getCount(tenantName);
|
||||
LOGGER.debug("Counter for {} : {} ", tenant.getName(), count);
|
||||
if (count >= tenant.getAllowedCallsPerSecond()) {
|
||||
LOGGER.error("API access per second limit reached for: {}", tenantName);
|
||||
if (count >= barCustomer.getAllowedCallsPerSecond()) {
|
||||
LOGGER.error("I'm sorry {}, you've had enough for today!", tenantName);
|
||||
return -1;
|
||||
}
|
||||
callsCount.incrementCount(tenantName);
|
||||
LOGGER.debug("Serving beer to {} : [{} consumed] ", barCustomer.getName(), count + 1);
|
||||
return getRandomCustomerId();
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ public final class CallsCount {
|
||||
* Resets the count of all the tenants in the map.
|
||||
*/
|
||||
public void reset() {
|
||||
LOGGER.debug("Resetting the map.");
|
||||
tenantCallsCount.replaceAll((k, v) -> new AtomicLong(0));
|
||||
LOGGER.info("reset counters");
|
||||
}
|
||||
}
|
||||
|
@ -23,20 +23,21 @@
|
||||
|
||||
package com.iluwatar.throttling;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
/**
|
||||
* TenantTest to test the creation of Tenant with valid parameters.
|
||||
*/
|
||||
public class TenantTest {
|
||||
public class BarCustomerTest {
|
||||
|
||||
@Test
|
||||
void constructorTest() {
|
||||
assertThrows(InvalidParameterException.class, () -> {
|
||||
new Tenant("FailTenant", -1, new CallsCount());
|
||||
new BarCustomer("sirBrave", -1, new CallsCount());
|
||||
});
|
||||
}
|
||||
}
|
@ -32,19 +32,18 @@ import org.junit.jupiter.api.Test;
|
||||
/**
|
||||
* B2BServiceTest class to test the B2BService
|
||||
*/
|
||||
public class B2BServiceTest {
|
||||
public class BartenderTest {
|
||||
|
||||
private final CallsCount callsCount = new CallsCount();
|
||||
|
||||
@Test
|
||||
void dummyCustomerApiTest() {
|
||||
var tenant = new Tenant("testTenant", 2, callsCount);
|
||||
var tenant = new BarCustomer("pirate", 2, callsCount);
|
||||
// In order to assure that throttling limits will not be reset, we use an empty throttling implementation
|
||||
var timer = (Throttler) () -> {
|
||||
};
|
||||
var service = new B2BService(timer, callsCount);
|
||||
var timer = (Throttler) () -> {};
|
||||
var service = new Bartender(timer, callsCount);
|
||||
|
||||
IntStream.range(0, 5).mapToObj(i -> tenant).forEach(service::dummyCustomerApi);
|
||||
IntStream.range(0, 5).mapToObj(i -> tenant).forEach(service::orderDrink);
|
||||
var counter = callsCount.getCount(tenant.getName());
|
||||
assertEquals(2, counter, "Counter limit must be reached");
|
||||
}
|
Reference in New Issue
Block a user