Compare commits
1 Commits
master
...
fix-claim-
Author | SHA1 | Date | |
---|---|---|---|
|
fcf3e5265e |
@ -1737,8 +1737,7 @@
|
|||||||
"avatar_url": "https://avatars.githubusercontent.com/u/47126749?v=4",
|
"avatar_url": "https://avatars.githubusercontent.com/u/47126749?v=4",
|
||||||
"profile": "http://no website",
|
"profile": "http://no website",
|
||||||
"contributions": [
|
"contributions": [
|
||||||
"review",
|
"review"
|
||||||
"code"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1750,60 +1749,6 @@
|
|||||||
"review",
|
"review",
|
||||||
"code"
|
"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"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "ShivanshCharak",
|
|
||||||
"name": "ShivanshCharak",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/96943825?v=4",
|
|
||||||
"profile": "https://github.com/ShivanshCharak",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "HattoriHenzo",
|
|
||||||
"name": "HattoriHenzo",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/5141285?v=4",
|
|
||||||
"profile": "https://github.com/HattoriHenzo",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contributorsPerLine": 7,
|
"contributorsPerLine": 7,
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -12,7 +12,6 @@ tmp/
|
|||||||
local.properties
|
local.properties
|
||||||
.loadpath
|
.loadpath
|
||||||
.recommenders
|
.recommenders
|
||||||
.DS_Store
|
|
||||||
|
|
||||||
####### Java annotation processor (APT) ########
|
####### Java annotation processor (APT) ########
|
||||||
.factorypath
|
.factorypath
|
||||||
|
39
.mvn/wrapper/maven-wrapper.properties
vendored
39
.mvn/wrapper/maven-wrapper.properties
vendored
@ -1,18 +1,25 @@
|
|||||||
# Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
# or more contributor license agreements. See the NOTICE file
|
|
||||||
# distributed with this work for additional information
|
|
||||||
# regarding copyright ownership. The ASF licenses this file
|
|
||||||
# to you under the Apache License, Version 2.0 (the
|
|
||||||
# "License"); you may not use this file except in compliance
|
|
||||||
# with the License. You may obtain a copy of the License at
|
|
||||||
#
|
#
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
# The MIT License
|
||||||
|
# Copyright © 2014-2021 Ilkka Seppälä
|
||||||
#
|
#
|
||||||
# Unless required by applicable law or agreed to in writing,
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
# software distributed under the License is distributed on an
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
# in the Software without restriction, including without limitation the rights
|
||||||
# KIND, either express or implied. See the License for the
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
# specific language governing permissions and limitations
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
# under the License.
|
# furnished to do so, subject to the following conditions:
|
||||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip
|
#
|
||||||
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
|
||||||
|
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
|
||||||
|
12
README.md
12
README.md
@ -10,7 +10,7 @@
|
|||||||
[](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
|
[](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)
|
[](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-BADGE:START - Do not remove or modify this section -->
|
||||||
[](#contributors-)
|
[](#contributors-)
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
@ -320,16 +320,8 @@ This project is licensed under the terms of the MIT license.
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<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="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> <a href="https://github.com/iluwatar/java-design-patterns/commits?author=Kevinyl3" title="Code">💻</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/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>
|
|
||||||
<tr>
|
|
||||||
<td align="center"><a href="https://github.com/ShivanshCharak"><img src="https://avatars.githubusercontent.com/u/96943825?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ShivanshCharak</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=ShivanshCharak" title="Code">💻</a></td>
|
|
||||||
<td align="center"><a href="https://github.com/HattoriHenzo"><img src="https://avatars.githubusercontent.com/u/5141285?v=4?s=100" width="100px;" alt=""/><br /><sub><b>HattoriHenzo</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=HattoriHenzo" title="Code">💻</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
* Hayes class implements its accept method.
|
* Hayes class implements its accept method.
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class Hayes implements Modem {
|
public class Hayes extends Modem {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accepts all visitors but honors only HayesVisitor.
|
* Accepts all visitors but honors only HayesVisitor.
|
||||||
|
@ -24,9 +24,8 @@
|
|||||||
package com.iluwatar.acyclicvisitor;
|
package com.iluwatar.acyclicvisitor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* //Modem abstract class.
|
* Modem abstract class.
|
||||||
* converted to an interface
|
|
||||||
*/
|
*/
|
||||||
public interface Modem {
|
public abstract class Modem {
|
||||||
void accept(ModemVisitor modemVisitor);
|
public abstract void accept(ModemVisitor modemVisitor);
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
* Zoom class implements its accept method.
|
* Zoom class implements its accept method.
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class Zoom implements Modem {
|
public class Zoom extends Modem {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accepts all visitors but honors only ZoomVisitor.
|
* Accepts all visitors but honors only ZoomVisitor.
|
||||||
|
@ -18,10 +18,10 @@ couldn't otherwise because of incompatible interfaces.
|
|||||||
|
|
||||||
## Explanation
|
## 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.
|
> 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 outlets.
|
> 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
|
> Yet another example would be a translator translating words spoken by one person to another
|
||||||
|
|
||||||
In plain words
|
In plain words
|
||||||
@ -36,7 +36,7 @@ Wikipedia says
|
|||||||
|
|
||||||
Consider a captain that can only use rowing boats and cannot sail at all.
|
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
|
```java
|
||||||
public interface RowingBoat {
|
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
|
```java
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@ -100,10 +100,10 @@ captain.row();
|
|||||||
## Applicability
|
## Applicability
|
||||||
Use the Adapter pattern when
|
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 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 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.
|
* 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.
|
* 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
|
## Tutorials
|
||||||
|
|
||||||
@ -114,17 +114,17 @@ Use the Adapter pattern when
|
|||||||
## Consequences
|
## Consequences
|
||||||
Class and object adapters have different trade-offs. A class adapter
|
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.
|
* 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.
|
* 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.
|
* introduces only one object, and no additional pointer indirection is needed to get to the adaptee.
|
||||||
|
|
||||||
An object adapter
|
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.
|
* 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 the Adapter refer to the subclass rather than the Adaptee itself.
|
* 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.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-)
|
* [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;
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for the adapter pattern.
|
* Test class
|
||||||
*/
|
*/
|
||||||
class AdapterPatternTest {
|
class AdapterPatternTest {
|
||||||
|
|
||||||
|
@ -33,7 +33,9 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
|||||||
class AppTest {
|
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.
|
* throws an exception.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -36,6 +36,9 @@ public class Wizard {
|
|||||||
private final Deque<Runnable> undoStack = new LinkedList<>();
|
private final Deque<Runnable> undoStack = new LinkedList<>();
|
||||||
private final Deque<Runnable> redoStack = new LinkedList<>();
|
private final Deque<Runnable> redoStack = new LinkedList<>();
|
||||||
|
|
||||||
|
public Wizard() {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cast spell.
|
* Cast spell.
|
||||||
*/
|
*/
|
||||||
|
@ -90,13 +90,6 @@ public class Commander {
|
|||||||
private static final Logger LOG = LoggerFactory.getLogger(Commander.class);
|
private static final Logger LOG = LoggerFactory.getLogger(Commander.class);
|
||||||
//we could also have another db where it stores all orders
|
//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,
|
Commander(EmployeeHandle empDb, PaymentService paymentService, ShippingService shippingService,
|
||||||
MessagingService messagingService, QueueDatabase qdb, int numOfRetries,
|
MessagingService messagingService, QueueDatabase qdb, int numOfRetries,
|
||||||
long retryDuration, long queueTime, long queueTaskTime, long paymentTime,
|
long retryDuration, long queueTime, long queueTaskTime, long paymentTime,
|
||||||
@ -125,17 +118,17 @@ public class Commander {
|
|||||||
Retry.Operation op = (l) -> {
|
Retry.Operation op = (l) -> {
|
||||||
if (!l.isEmpty()) {
|
if (!l.isEmpty()) {
|
||||||
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
|
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
|
||||||
LOG.debug(ORDER_ID + ": Error in connecting to shipping service, "
|
LOG.debug("Order " + order.id + ": Error in connecting to shipping service, "
|
||||||
+ "trying again..", order.id);
|
+ "trying again..");
|
||||||
} else {
|
} 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);
|
throw l.remove(0);
|
||||||
}
|
}
|
||||||
String transactionId = shippingService.receiveRequest(order.item, order.user.address);
|
String transactionId = shippingService.receiveRequest(order.item, order.user.address);
|
||||||
//could save this transaction id in a db too
|
//could save this transaction id in a db too
|
||||||
LOG.info(ORDER_ID + ": Shipping placed successfully, transaction id: {}",
|
LOG.info("Order " + order.id + ": Shipping placed successfully, transaction id: "
|
||||||
order.id, transactionId);
|
+ transactionId);
|
||||||
LOG.info("Order has been placed and will be shipped to you. Please wait while we make your"
|
LOG.info("Order has been placed and will be shipped to you. Please wait while we make your"
|
||||||
+ " payment... ");
|
+ " payment... ");
|
||||||
sendPaymentRequest(order);
|
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"
|
LOG.info("Shipping is currently not possible to your address. We are working on the problem"
|
||||||
+ " and will get back to you asap.");
|
+ " and will get back to you asap.");
|
||||||
finalSiteMsgShown = true;
|
finalSiteMsgShown = true;
|
||||||
LOG.info(ORDER_ID + ": Shipping not possible to address, trying to add problem "
|
LOG.info("Order " + order.id + ": Shipping not possible to address, trying to add problem "
|
||||||
+ "to employee db..", order.id);
|
+ "to employee db..");
|
||||||
employeeHandleIssue(o);
|
employeeHandleIssue(o);
|
||||||
} else if (ItemUnavailableException.class.isAssignableFrom(err.getClass())) {
|
} else if (ItemUnavailableException.class.isAssignableFrom(err.getClass())) {
|
||||||
LOG.info("This item is currently unavailable. We will inform you as soon as the item "
|
LOG.info("This item is currently unavailable. We will inform you as soon as the item "
|
||||||
+ "becomes available again.");
|
+ "becomes available again.");
|
||||||
finalSiteMsgShown = true;
|
finalSiteMsgShown = true;
|
||||||
LOG.info(ORDER_ID + ": Item {}" + " unavailable, trying to add "
|
LOG.info("Order " + order.id + ": Item " + order.item + " unavailable, trying to add "
|
||||||
+ "problem to employee handle..", order.id, order.item);
|
+ "problem to employee handle..");
|
||||||
employeeHandleIssue(o);
|
employeeHandleIssue(o);
|
||||||
} else {
|
} else {
|
||||||
LOG.info("Sorry, there was a problem in creating your order. Please try later.");
|
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;
|
finalSiteMsgShown = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -171,7 +164,7 @@ public class Commander {
|
|||||||
if (order.paid.equals(PaymentStatus.TRYING)) {
|
if (order.paid.equals(PaymentStatus.TRYING)) {
|
||||||
order.paid = PaymentStatus.NOT_DONE;
|
order.paid = PaymentStatus.NOT_DONE;
|
||||||
sendPaymentFailureMessage(order);
|
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
|
} //if succeeded or failed, would have been dequeued, no attempt to make payment
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -180,18 +173,17 @@ public class Commander {
|
|||||||
Retry.Operation op = (l) -> {
|
Retry.Operation op = (l) -> {
|
||||||
if (!l.isEmpty()) {
|
if (!l.isEmpty()) {
|
||||||
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
|
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
|
||||||
LOG.debug(ORDER_ID + ": Error in connecting to payment service,"
|
LOG.debug("Order " + order.id + ": Error in connecting to payment service,"
|
||||||
+ " trying again..", order.id);
|
+ " trying again..");
|
||||||
} else {
|
} 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);
|
throw l.remove(0);
|
||||||
}
|
}
|
||||||
if (order.paid.equals(PaymentStatus.TRYING)) {
|
if (order.paid.equals(PaymentStatus.TRYING)) {
|
||||||
var transactionId = paymentService.receiveRequest(order.price);
|
var transactionId = paymentService.receiveRequest(order.price);
|
||||||
order.paid = PaymentStatus.DONE;
|
order.paid = PaymentStatus.DONE;
|
||||||
LOG.info(ORDER_ID + ": Payment successful, transaction Id: {}",
|
LOG.info("Order " + order.id + ": Payment successful, transaction Id: " + transactionId);
|
||||||
order.id, transactionId);
|
|
||||||
if (!finalSiteMsgShown) {
|
if (!finalSiteMsgShown) {
|
||||||
LOG.info("Payment made successfully, thank you for shopping with us!!");
|
LOG.info("Payment made successfully, thank you for shopping with us!!");
|
||||||
finalSiteMsgShown = true;
|
finalSiteMsgShown = true;
|
||||||
@ -207,7 +199,7 @@ public class Commander {
|
|||||||
+ "Meanwhile, your order has been converted to COD and will be shipped.");
|
+ "Meanwhile, your order has been converted to COD and will be shipped.");
|
||||||
finalSiteMsgShown = true;
|
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;
|
o.paid = PaymentStatus.NOT_DONE;
|
||||||
sendPaymentFailureMessage(o);
|
sendPaymentFailureMessage(o);
|
||||||
} else {
|
} else {
|
||||||
@ -217,7 +209,7 @@ public class Commander {
|
|||||||
+ "asap. Don't worry, your order has been placed and will be shipped.");
|
+ "asap. Don't worry, your order has been placed and will be shipped.");
|
||||||
finalSiteMsgShown = true;
|
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);
|
sendPaymentPossibleErrorMsg(o);
|
||||||
}
|
}
|
||||||
if (o.paid.equals(PaymentStatus.TRYING) && System
|
if (o.paid.equals(PaymentStatus.TRYING) && System
|
||||||
@ -242,7 +234,7 @@ public class Commander {
|
|||||||
if (System.currentTimeMillis() - qt.order.createdTime >= this.queueTime) {
|
if (System.currentTimeMillis() - qt.order.createdTime >= this.queueTime) {
|
||||||
// since payment time is lesser than queuetime it would have already failed..
|
// since payment time is lesser than queuetime it would have already failed..
|
||||||
// additional check not needed
|
// 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;
|
return;
|
||||||
} else if (qt.taskType.equals(TaskType.PAYMENT) && !qt.order.paid.equals(PaymentStatus.TRYING)
|
} else if (qt.taskType.equals(TaskType.PAYMENT) && !qt.order.paid.equals(PaymentStatus.TRYING)
|
||||||
|| qt.taskType.equals(TaskType.MESSAGING) && (qt.messageType == 1
|
|| 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_FAIL)
|
||||||
|| qt.order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL))
|
|| qt.order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL))
|
||||||
|| qt.taskType.equals(TaskType.EMPLOYEE_DB) && qt.order.addedToEmployeeHandle) {
|
|| 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;
|
return;
|
||||||
}
|
}
|
||||||
var list = queue.exceptionsList;
|
var list = queue.exceptionsList;
|
||||||
Thread t = new Thread(() -> {
|
Thread t = new Thread(() -> {
|
||||||
Retry.Operation op = (list1) -> {
|
Retry.Operation op = (list1) -> {
|
||||||
if (!list1.isEmpty()) {
|
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);
|
throw list1.remove(0);
|
||||||
}
|
}
|
||||||
queue.add(qt);
|
queue.add(qt);
|
||||||
queueItems++;
|
queueItems++;
|
||||||
LOG.info(ORDER_ID + ": {}" + " task enqueued..", qt.order.id, qt.getType());
|
LOG.info("Order " + qt.order.id + ": " + qt.getType() + " task enqueued..");
|
||||||
tryDoingTasksInQueue();
|
tryDoingTasksInQueue();
|
||||||
};
|
};
|
||||||
Retry.HandleErrorIssue<QueueTask> handleError = (qt1, err) -> {
|
Retry.HandleErrorIssue<QueueTask> handleError = (qt1, err) -> {
|
||||||
if (qt1.taskType.equals(TaskType.PAYMENT)) {
|
if (qt1.taskType.equals(TaskType.PAYMENT)) {
|
||||||
qt1.order.paid = PaymentStatus.NOT_DONE;
|
qt1.order.paid = PaymentStatus.NOT_DONE;
|
||||||
sendPaymentFailureMessage(qt1.order);
|
sendPaymentFailureMessage(qt1.order);
|
||||||
LOG.error(ORDER_ID + ": Unable to enqueue payment task,"
|
LOG.error("Order " + qt1.order.id + ": Unable to enqueue payment task,"
|
||||||
+ " payment failed..", qt1.order.id);
|
+ " payment failed..");
|
||||||
}
|
}
|
||||||
LOG.error(ORDER_ID + ": Unable to enqueue task of type {}"
|
LOG.error("Order " + qt1.order.id + ": Unable to enqueue task of type " + qt1.getType()
|
||||||
+ ", trying to add to employee handle..", qt1.order.id, qt1.getType());
|
+ ", trying to add to employee handle..");
|
||||||
employeeHandleIssue(qt1.order);
|
employeeHandleIssue(qt1.order);
|
||||||
};
|
};
|
||||||
var r = new Retry<>(op, handleError, numOfRetries, retryDuration,
|
var r = new Retry<>(op, handleError, numOfRetries, retryDuration,
|
||||||
@ -336,7 +328,7 @@ public class Commander {
|
|||||||
|
|
||||||
private void sendSuccessMessage(Order order) {
|
private void sendSuccessMessage(Order order) {
|
||||||
if (System.currentTimeMillis() - order.createdTime >= this.messageTime) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
var list = messagingService.exceptionsList;
|
var list = messagingService.exceptionsList;
|
||||||
@ -362,8 +354,8 @@ public class Commander {
|
|||||||
&& System.currentTimeMillis() - o.createdTime < messageTime) {
|
&& System.currentTimeMillis() - o.createdTime < messageTime) {
|
||||||
var qt = new QueueTask(order, TaskType.MESSAGING, 2);
|
var qt = new QueueTask(order, TaskType.MESSAGING, 2);
|
||||||
updateQueue(qt);
|
updateQueue(qt);
|
||||||
LOG.info(ORDER_ID + ": Error in sending Payment Success message, trying to"
|
LOG.info("Order " + order.id + ": Error in sending Payment Success message, trying to"
|
||||||
+ " queue task and add to employee handle..", order.id);
|
+ " queue task and add to employee handle..");
|
||||||
employeeHandleIssue(order);
|
employeeHandleIssue(order);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -372,11 +364,11 @@ public class Commander {
|
|||||||
return (l) -> {
|
return (l) -> {
|
||||||
if (!l.isEmpty()) {
|
if (!l.isEmpty()) {
|
||||||
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
|
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
|
||||||
LOG.debug(ORDER_ID + ERROR_CONNECTING_MSG_SVC
|
LOG.debug("Order " + order.id + ": Error in connecting to messaging service "
|
||||||
+ "(Payment Success msg), trying again..", order.id);
|
+ "(Payment Success msg), trying again..");
|
||||||
} else {
|
} else {
|
||||||
LOG.debug(ORDER_ID + ": Error in creating Payment Success"
|
LOG.debug("Order " + order.id + ": Error in creating Payment Success"
|
||||||
+ " messaging request..", order.id);
|
+ " messaging request..");
|
||||||
}
|
}
|
||||||
throw l.remove(0);
|
throw l.remove(0);
|
||||||
}
|
}
|
||||||
@ -384,15 +376,15 @@ public class Commander {
|
|||||||
&& !order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) {
|
&& !order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) {
|
||||||
var requestId = messagingService.receiveRequest(2);
|
var requestId = messagingService.receiveRequest(2);
|
||||||
order.messageSent = MessageSent.PAYMENT_SUCCESSFUL;
|
order.messageSent = MessageSent.PAYMENT_SUCCESSFUL;
|
||||||
LOG.info(ORDER_ID + ": Payment Success message sent,"
|
LOG.info("Order " + order.id + ": Payment Success message sent,"
|
||||||
+ REQUEST_ID, order.id, requestId);
|
+ " request Id: " + requestId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendPaymentFailureMessage(Order order) {
|
private void sendPaymentFailureMessage(Order order) {
|
||||||
if (System.currentTimeMillis() - order.createdTime >= this.messageTime) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
var list = messagingService.exceptionsList;
|
var list = messagingService.exceptionsList;
|
||||||
@ -420,8 +412,8 @@ public class Commander {
|
|||||||
&& System.currentTimeMillis() - o.createdTime < messageTime) {
|
&& System.currentTimeMillis() - o.createdTime < messageTime) {
|
||||||
var qt = new QueueTask(order, TaskType.MESSAGING, 0);
|
var qt = new QueueTask(order, TaskType.MESSAGING, 0);
|
||||||
updateQueue(qt);
|
updateQueue(qt);
|
||||||
LOG.warn(ORDER_ID + ": Error in sending Payment Failure message, "
|
LOG.warn("Order " + order.id + ": Error in sending Payment Failure message, "
|
||||||
+ "trying to queue task and add to employee handle..", order.id);
|
+ "trying to queue task and add to employee handle..");
|
||||||
employeeHandleIssue(o);
|
employeeHandleIssue(o);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -429,11 +421,11 @@ public class Commander {
|
|||||||
private void handlePaymentFailureRetryOperation(Order order, List<Exception> l) throws Exception {
|
private void handlePaymentFailureRetryOperation(Order order, List<Exception> l) throws Exception {
|
||||||
if (!l.isEmpty()) {
|
if (!l.isEmpty()) {
|
||||||
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
|
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
|
||||||
LOG.debug(ORDER_ID + ERROR_CONNECTING_MSG_SVC
|
LOG.debug("Order " + order.id + ": Error in connecting to messaging service "
|
||||||
+ "(Payment Failure msg), trying again..", order.id);
|
+ "(Payment Failure msg), trying again..");
|
||||||
} else {
|
} else {
|
||||||
LOG.debug(ORDER_ID + ": Error in creating Payment Failure"
|
LOG.debug("Order " + order.id + ": Error in creating Payment Failure"
|
||||||
+ " message request..", order.id);
|
+ " message request..");
|
||||||
}
|
}
|
||||||
throw l.remove(0);
|
throw l.remove(0);
|
||||||
}
|
}
|
||||||
@ -441,8 +433,8 @@ public class Commander {
|
|||||||
&& !order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) {
|
&& !order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) {
|
||||||
var requestId = messagingService.receiveRequest(0);
|
var requestId = messagingService.receiveRequest(0);
|
||||||
order.messageSent = MessageSent.PAYMENT_FAIL;
|
order.messageSent = MessageSent.PAYMENT_FAIL;
|
||||||
LOG.info(ORDER_ID + ": Payment Failure message sent successfully,"
|
LOG.info("Order " + order.id + ": Payment Failure message sent successfully,"
|
||||||
+ REQUEST_ID, order.id, requestId);
|
+ " request Id: " + requestId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -486,11 +478,11 @@ public class Commander {
|
|||||||
throws Exception {
|
throws Exception {
|
||||||
if (!l.isEmpty()) {
|
if (!l.isEmpty()) {
|
||||||
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
|
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
|
||||||
LOG.debug(ORDER_ID + ERROR_CONNECTING_MSG_SVC
|
LOG.debug("Order " + order.id + ": Error in connecting to messaging service "
|
||||||
+ "(Payment Error msg), trying again..", order.id);
|
+ "(Payment Error msg), trying again..");
|
||||||
} else {
|
} else {
|
||||||
LOG.debug(ORDER_ID + ": Error in creating Payment Error"
|
LOG.debug("Order " + order.id + ": Error in creating Payment Error"
|
||||||
+ " messaging request..", order.id);
|
+ " messaging request..");
|
||||||
}
|
}
|
||||||
throw l.remove(0);
|
throw l.remove(0);
|
||||||
}
|
}
|
||||||
@ -498,28 +490,28 @@ public class Commander {
|
|||||||
.equals(MessageSent.NONE_SENT)) {
|
.equals(MessageSent.NONE_SENT)) {
|
||||||
var requestId = messagingService.receiveRequest(1);
|
var requestId = messagingService.receiveRequest(1);
|
||||||
order.messageSent = MessageSent.PAYMENT_TRYING;
|
order.messageSent = MessageSent.PAYMENT_TRYING;
|
||||||
LOG.info(ORDER_ID + ": Payment Error message sent successfully,"
|
LOG.info("Order " + order.id + ": Payment Error message sent successfully,"
|
||||||
+ REQUEST_ID, order.id, requestId);
|
+ " request Id: " + requestId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void employeeHandleIssue(Order order) {
|
private void employeeHandleIssue(Order order) {
|
||||||
if (System.currentTimeMillis() - order.createdTime >= this.employeeTime) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
var list = employeeDb.exceptionsList;
|
var list = employeeDb.exceptionsList;
|
||||||
var t = new Thread(() -> {
|
var t = new Thread(() -> {
|
||||||
Retry.Operation op = (l) -> {
|
Retry.Operation op = (l) -> {
|
||||||
if (!l.isEmpty()) {
|
if (!l.isEmpty()) {
|
||||||
LOG.warn(ORDER_ID + ": Error in connecting to employee handle,"
|
LOG.warn("Order " + order.id + ": Error in connecting to employee handle,"
|
||||||
+ " trying again..", order.id);
|
+ " trying again..");
|
||||||
throw l.remove(0);
|
throw l.remove(0);
|
||||||
}
|
}
|
||||||
if (!order.addedToEmployeeHandle) {
|
if (!order.addedToEmployeeHandle) {
|
||||||
employeeDb.receiveRequest(order);
|
employeeDb.receiveRequest(order);
|
||||||
order.addedToEmployeeHandle = true;
|
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) -> {
|
Retry.HandleErrorIssue<Order> handleError = (o, err) -> {
|
||||||
@ -527,8 +519,8 @@ public class Commander {
|
|||||||
.currentTimeMillis() - order.createdTime < employeeTime) {
|
.currentTimeMillis() - order.createdTime < employeeTime) {
|
||||||
var qt = new QueueTask(order, TaskType.EMPLOYEE_DB, -1);
|
var qt = new QueueTask(order, TaskType.EMPLOYEE_DB, -1);
|
||||||
updateQueue(qt);
|
updateQueue(qt);
|
||||||
LOG.warn(ORDER_ID + ": Error in adding to employee db,"
|
LOG.warn("Order " + order.id + ": Error in adding to employee db,"
|
||||||
+ " trying to queue task..", order.id);
|
+ " trying to queue task..");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var r = new Retry<>(op, handleError, numOfRetries, retryDuration,
|
var r = new Retry<>(op, handleError, numOfRetries, retryDuration,
|
||||||
@ -546,51 +538,51 @@ public class Commander {
|
|||||||
if (queueItems != 0) {
|
if (queueItems != 0) {
|
||||||
var qt = queue.peek(); //this should probably be cloned here
|
var qt = queue.peek(); //this should probably be cloned here
|
||||||
//this is why we have retry for doTasksInQueue
|
//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) {
|
if (qt.getFirstAttemptTime() == -1) {
|
||||||
qt.setFirstAttemptTime(System.currentTimeMillis());
|
qt.setFirstAttemptTime(System.currentTimeMillis());
|
||||||
}
|
}
|
||||||
if (System.currentTimeMillis() - qt.getFirstAttemptTime() >= queueTaskTime) {
|
if (System.currentTimeMillis() - qt.getFirstAttemptTime() >= queueTaskTime) {
|
||||||
tryDequeue();
|
tryDequeue();
|
||||||
LOG.trace(ORDER_ID + ": This queue task of type {}"
|
LOG.trace("Order " + qt.order.id + ": This queue task of type " + qt.getType()
|
||||||
+ " does not need to be done anymore (timeout), dequeue..", qt.order.id, qt.getType());
|
+ " does not need to be done anymore (timeout), dequeue..");
|
||||||
} else {
|
} else {
|
||||||
if (qt.taskType.equals(TaskType.PAYMENT)) {
|
if (qt.taskType.equals(TaskType.PAYMENT)) {
|
||||||
if (!qt.order.paid.equals(PaymentStatus.TRYING)) {
|
if (!qt.order.paid.equals(PaymentStatus.TRYING)) {
|
||||||
tryDequeue();
|
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 {
|
} else {
|
||||||
sendPaymentRequest(qt.order);
|
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)) {
|
} else if (qt.taskType.equals(TaskType.MESSAGING)) {
|
||||||
if (qt.order.messageSent.equals(MessageSent.PAYMENT_FAIL)
|
if (qt.order.messageSent.equals(MessageSent.PAYMENT_FAIL)
|
||||||
|| qt.order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) {
|
|| qt.order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) {
|
||||||
tryDequeue();
|
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)
|
} else if (qt.messageType == 1 && (!qt.order.messageSent.equals(MessageSent.NONE_SENT)
|
||||||
|| !qt.order.paid.equals(PaymentStatus.TRYING))) {
|
|| !qt.order.paid.equals(PaymentStatus.TRYING))) {
|
||||||
tryDequeue();
|
tryDequeue();
|
||||||
LOG.trace(ORDER_ID + ": This messaging task does not need to be done,"
|
LOG.trace("Order " + qt.order.id + ": This messaging task does not need to be done,"
|
||||||
+ " dequeue..", qt.order.id);
|
+ " dequeue..");
|
||||||
} else if (qt.messageType == 0) {
|
} else if (qt.messageType == 0) {
|
||||||
sendPaymentFailureMessage(qt.order);
|
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) {
|
} else if (qt.messageType == 1) {
|
||||||
sendPaymentPossibleErrorMsg(qt.order);
|
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) {
|
} else if (qt.messageType == 2) {
|
||||||
sendSuccessMessage(qt.order);
|
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)) {
|
} else if (qt.taskType.equals(TaskType.EMPLOYEE_DB)) {
|
||||||
if (qt.order.addedToEmployeeHandle) {
|
if (qt.order.addedToEmployeeHandle) {
|
||||||
tryDequeue();
|
tryDequeue();
|
||||||
LOG.trace(ORDER_ID + ": This employee handle task already done,"
|
LOG.trace("Order " + qt.order.id + ": This employee handle task already done,"
|
||||||
+ " dequeue..", qt.order.id);
|
+ " dequeue..");
|
||||||
} else {
|
} else {
|
||||||
employeeHandleIssue(qt.order);
|
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..");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,325 +0,0 @@
|
|||||||
---
|
|
||||||
layout: pattern
|
|
||||||
title: Composite View
|
|
||||||
folder: composite-view
|
|
||||||
permalink: /patterns/composite-view/
|
|
||||||
categories: Structural
|
|
||||||
language: en
|
|
||||||
tags:
|
|
||||||
- Enterprise Integration Pattern
|
|
||||||
- Presentation
|
|
||||||
---
|
|
||||||
|
|
||||||
## Name
|
|
||||||
**Composite View**
|
|
||||||
|
|
||||||
## Intent
|
|
||||||
The purpose of the Composite View Pattern is to increase re-usability and flexibility when creating views for websites/webapps.
|
|
||||||
This pattern seeks to decouple the content of the page from its layout, allowing changes to be made to either the content
|
|
||||||
or layout of the page without impacting the other. This pattern also allows content to be easily reused across different views easily.
|
|
||||||
|
|
||||||
## Explanation
|
|
||||||
Real World Example
|
|
||||||
> A news site wants to display the current date and news to different users
|
|
||||||
> based on that user's preferences. The news site will substitute in different news feed
|
|
||||||
> components depending on the user's interest, defaulting to local news.
|
|
||||||
|
|
||||||
In Plain Words
|
|
||||||
> Composite View Pattern is having a main view being composed of smaller subviews.
|
|
||||||
> The layout of this composite view is based on a template. A View-manager then decides which
|
|
||||||
> subviews to include in this template.
|
|
||||||
|
|
||||||
Wikipedia Says
|
|
||||||
> Composite views that are composed of multiple atomic subviews. Each component of
|
|
||||||
> the template may be included dynamically into the whole and the layout of the page may be managed independently of the content.
|
|
||||||
> This solution provides for the creation of a composite view based on the inclusion and substitution of
|
|
||||||
> modular dynamic and static template fragments.
|
|
||||||
> It promotes the reuse of atomic portions of the view by encouraging modular design.
|
|
||||||
|
|
||||||
**Programmatic Example**
|
|
||||||
|
|
||||||
Since this is a web development pattern, a server is required to demonstrate it.
|
|
||||||
This example uses Tomcat 10.0.13 to run the servlet, and this programmatic example will only work with Tomcat 10+.
|
|
||||||
|
|
||||||
Firstly there is `AppServlet` which is an `HttpServlet` that runs on Tomcat 10+.
|
|
||||||
```java
|
|
||||||
public class AppServlet extends HttpServlet {
|
|
||||||
private String msgPartOne = "<h1>This Server Doesn't Support";
|
|
||||||
private String msgPartTwo = "Requests</h1>\n"
|
|
||||||
+ "<h2>Use a GET request with boolean values for the following parameters<h2>\n"
|
|
||||||
+ "<h3>'name'</h3>\n<h3>'bus'</h3>\n<h3>'sports'</h3>\n<h3>'sci'</h3>\n<h3>'world'</h3>";
|
|
||||||
|
|
||||||
private String destination = "newsDisplay.jsp";
|
|
||||||
|
|
||||||
public AppServlet() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void doGet(HttpServletRequest req, HttpServletResponse resp)
|
|
||||||
throws ServletException, IOException {
|
|
||||||
RequestDispatcher requestDispatcher = req.getRequestDispatcher(destination);
|
|
||||||
ClientPropertiesBean reqParams = new ClientPropertiesBean(req);
|
|
||||||
req.setAttribute("properties", reqParams);
|
|
||||||
requestDispatcher.forward(req, resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void doPost(HttpServletRequest req, HttpServletResponse resp)
|
|
||||||
throws ServletException, IOException {
|
|
||||||
resp.setContentType("text/html");
|
|
||||||
PrintWriter out = resp.getWriter();
|
|
||||||
out.println(msgPartOne + " Post " + msgPartTwo);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void doDelete(HttpServletRequest req, HttpServletResponse resp)
|
|
||||||
throws ServletException, IOException {
|
|
||||||
resp.setContentType("text/html");
|
|
||||||
PrintWriter out = resp.getWriter();
|
|
||||||
out.println(msgPartOne + " Delete " + msgPartTwo);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void doPut(HttpServletRequest req, HttpServletResponse resp)
|
|
||||||
throws ServletException, IOException {
|
|
||||||
resp.setContentType("text/html");
|
|
||||||
PrintWriter out = resp.getWriter();
|
|
||||||
out.println(msgPartOne + " Put " + msgPartTwo);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
This servlet is not part of the pattern, and simply forwards GET requests to the correct JSP.
|
|
||||||
PUT, POST, and DELETE requests are not supported and will simply show an error message.
|
|
||||||
|
|
||||||
The view management in this example is done via a javabean class: `ClientPropertiesBean`, which stores user preferences.
|
|
||||||
```java
|
|
||||||
public class ClientPropertiesBean implements Serializable {
|
|
||||||
|
|
||||||
private static final String WORLD_PARAM = "world";
|
|
||||||
private static final String SCIENCE_PARAM = "sci";
|
|
||||||
private static final String SPORTS_PARAM = "sport";
|
|
||||||
private static final String BUSINESS_PARAM = "bus";
|
|
||||||
private static final String NAME_PARAM = "name";
|
|
||||||
|
|
||||||
private static final String DEFAULT_NAME = "DEFAULT_NAME";
|
|
||||||
private boolean worldNewsInterest;
|
|
||||||
private boolean sportsInterest;
|
|
||||||
private boolean businessInterest;
|
|
||||||
private boolean scienceNewsInterest;
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
public ClientPropertiesBean() {
|
|
||||||
worldNewsInterest = true;
|
|
||||||
sportsInterest = true;
|
|
||||||
businessInterest = true;
|
|
||||||
scienceNewsInterest = true;
|
|
||||||
name = DEFAULT_NAME;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientPropertiesBean(HttpServletRequest req) {
|
|
||||||
worldNewsInterest = Boolean.parseBoolean(req.getParameter(WORLD_PARAM));
|
|
||||||
sportsInterest = Boolean.parseBoolean(req.getParameter(SPORTS_PARAM));
|
|
||||||
businessInterest = Boolean.parseBoolean(req.getParameter(BUSINESS_PARAM));
|
|
||||||
scienceNewsInterest = Boolean.parseBoolean(req.getParameter(SCIENCE_PARAM));
|
|
||||||
String tempName = req.getParameter(NAME_PARAM);
|
|
||||||
if (tempName == null || tempName == "") {
|
|
||||||
tempName = DEFAULT_NAME;
|
|
||||||
}
|
|
||||||
name = tempName;
|
|
||||||
}
|
|
||||||
// getters and setters generated by Lombok
|
|
||||||
}
|
|
||||||
```
|
|
||||||
This javabean has a default constructor, and another that takes an `HttpServletRequest`.
|
|
||||||
This second constructor takes the request object, parses out the request parameters which contain the
|
|
||||||
user preferences for different types of news.
|
|
||||||
|
|
||||||
The template for the news page is in `newsDisplay.jsp`
|
|
||||||
```html
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<style>
|
|
||||||
h1 { text-align: center;}
|
|
||||||
h2 { text-align: center;}
|
|
||||||
h3 { text-align: center;}
|
|
||||||
.centerTable {
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
table {border: 1px solid black;}
|
|
||||||
tr {text-align: center;}
|
|
||||||
td {text-align: center;}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<%ClientPropertiesBean propertiesBean = (ClientPropertiesBean) request.getAttribute("properties");%>
|
|
||||||
<h1>Welcome <%= propertiesBean.getName()%></h1>
|
|
||||||
<jsp:include page="header.jsp"></jsp:include>
|
|
||||||
<table class="centerTable">
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td></td>
|
|
||||||
<% if(propertiesBean.isWorldNewsInterest()) { %>
|
|
||||||
<td><%@include file="worldNews.jsp"%></td>
|
|
||||||
<% } else { %>
|
|
||||||
<td><%@include file="localNews.jsp"%></td>
|
|
||||||
<% } %>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<% if(propertiesBean.isBusinessInterest()) { %>
|
|
||||||
<td><%@include file="businessNews.jsp"%></td>
|
|
||||||
<% } else { %>
|
|
||||||
<td><%@include file="localNews.jsp"%></td>
|
|
||||||
<% } %>
|
|
||||||
<td></td>
|
|
||||||
<% if(propertiesBean.isSportsInterest()) { %>
|
|
||||||
<td><%@include file="sportsNews.jsp"%></td>
|
|
||||||
<% } else { %>
|
|
||||||
<td><%@include file="localNews.jsp"%></td>
|
|
||||||
<% } %>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td></td>
|
|
||||||
<% if(propertiesBean.isScienceNewsInterest()) { %>
|
|
||||||
<td><%@include file="scienceNews.jsp"%></td>
|
|
||||||
<% } else { %>
|
|
||||||
<td><%@include file="localNews.jsp"%></td>
|
|
||||||
<% } %>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
This JSP page is the template. It declares a table with three rows, with one component in the first row,
|
|
||||||
two components in the second row, and one component in the third row.
|
|
||||||
|
|
||||||
The scriplets in the file are part of the
|
|
||||||
view management strategy that include different atomic subviews based on the user preferences in the Javabean.
|
|
||||||
|
|
||||||
Here are two examples of the mock atomic subviews used in the composite:
|
|
||||||
`businessNews.jsp`
|
|
||||||
```html
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<style>
|
|
||||||
h2 { text-align: center;}
|
|
||||||
table {border: 1px solid black;}
|
|
||||||
tr {text-align: center;}
|
|
||||||
td {text-align: center;}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h2>
|
|
||||||
Generic Business News
|
|
||||||
</h2>
|
|
||||||
<table style="margin-right: auto; margin-left: auto">
|
|
||||||
<tr>
|
|
||||||
<td>Stock prices up across the world</td>
|
|
||||||
<td>New tech companies to invest in</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Industry leaders unveil new project</td>
|
|
||||||
<td>Price fluctuations and what they mean</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
`localNews.jsp`
|
|
||||||
```html
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<div style="text-align: center">
|
|
||||||
<h3>
|
|
||||||
Generic Local News
|
|
||||||
</h3>
|
|
||||||
<ul style="list-style-type: none">
|
|
||||||
<li>
|
|
||||||
Mayoral elections coming up in 2 weeks
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
New parking meter rates downtown coming tomorrow
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Park renovations to finish by the next year
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Annual marathon sign ups available online
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
The results are as such:
|
|
||||||
|
|
||||||
1) The user has put their name as `Tammy` in the request parameters and no preferences:
|
|
||||||

|
|
||||||
2) The user has put their name as `Johnny` in the request parameters and has a preference for world, business, and science news:
|
|
||||||

|
|
||||||
|
|
||||||
The different subviews such as `worldNews.jsp`, `businessNews.jsp`, etc. are included conditionally
|
|
||||||
based on the request parameters.
|
|
||||||
|
|
||||||
**How To Use**
|
|
||||||
|
|
||||||
To try this example, make sure you have Tomcat 10+ installed.
|
|
||||||
Set up your IDE to build a WAR file from the module and deploy that file to the server
|
|
||||||
|
|
||||||
IntelliJ:
|
|
||||||
|
|
||||||
Under `Run` and `edit configurations` Make sure Tomcat server is one of the run configurations.
|
|
||||||
Go to the deployment tab, and make sure there is one artifact being built called `composite-view:war exploded`.
|
|
||||||
If not present, add one.
|
|
||||||
|
|
||||||
Ensure that the artifact is being built from the content of the `web` directory and the compilation results of the module.
|
|
||||||
Point the output of the artifact to a convenient place. Run the configuration and view the landing page,
|
|
||||||
follow instructions on that page to continue.
|
|
||||||
|
|
||||||
## Class diagram
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
The class diagram here displays the Javabean which is the view manager.
|
|
||||||
The views are JSP's held inside the web directory.
|
|
||||||
|
|
||||||
## Applicability
|
|
||||||
|
|
||||||
This pattern is applicable to most websites that require content to be displayed dynamically/conditionally.
|
|
||||||
If there are components that need to be re-used for multiple views, or if the project requires reusing a template,
|
|
||||||
or if it needs to include content depending on certain conditions, then this pattern is a good choice.
|
|
||||||
|
|
||||||
## Known uses
|
|
||||||
|
|
||||||
Most modern websites use composite views in some shape or form, as they have templates for views and small atomic components
|
|
||||||
that are included in the page dynamically. Most modern Javascript libraries, like React, support this design pattern
|
|
||||||
with components.
|
|
||||||
|
|
||||||
## Consequences
|
|
||||||
**Pros**
|
|
||||||
* Easy to re-use components
|
|
||||||
* Change layout/content without affecting the other
|
|
||||||
* Reduce code duplication
|
|
||||||
* Code is more maintainable and modular
|
|
||||||
|
|
||||||
**Cons**
|
|
||||||
* Overhead cost at runtime
|
|
||||||
* Slower response compared to directly embedding elements
|
|
||||||
* Increases potential for display errors
|
|
||||||
|
|
||||||
## Related patterns
|
|
||||||
* [Composite (GoF)](https://java-design-patterns.com/patterns/composite/)
|
|
||||||
* [View Helper](https://www.oracle.com/java/technologies/viewhelper.html)
|
|
||||||
|
|
||||||
## Credits
|
|
||||||
* [Core J2EE Patterns - Composite View](https://www.oracle.com/java/technologies/composite-view.html)
|
|
||||||
* [Composite View Design Pattern – Core J2EE Patterns](https://www.dineshonjava.com/composite-view-design-pattern/)
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
|||||||
@startuml
|
|
||||||
package com.iluwatar.compositeview {
|
|
||||||
class ClientPropertiesBean {
|
|
||||||
- BUSINESS_PARAM : String {static}
|
|
||||||
- DEFAULT_NAME : String {static}
|
|
||||||
- NAME_PARAM : String {static}
|
|
||||||
- SCIENCE_PARAM : String {static}
|
|
||||||
- SPORTS_PARAM : String {static}
|
|
||||||
- WORLD_PARAM : String {static}
|
|
||||||
- businessInterest : boolean
|
|
||||||
- name : String
|
|
||||||
- scienceNewsInterest : boolean
|
|
||||||
- sportsInterest : boolean
|
|
||||||
- worldNewsInterest : boolean
|
|
||||||
+ ClientPropertiesBean()
|
|
||||||
+ ClientPropertiesBean(req : HttpServletRequest)
|
|
||||||
+ getName() : String
|
|
||||||
+ isBusinessInterest() : boolean
|
|
||||||
+ isScienceNewsInterest() : boolean
|
|
||||||
+ isSportsInterest() : boolean
|
|
||||||
+ isWorldNewsInterest() : boolean
|
|
||||||
+ setBusinessInterest(businessInterest : boolean)
|
|
||||||
+ setName(name : String)
|
|
||||||
+ setScienceNewsInterest(scienceNewsInterest : boolean)
|
|
||||||
+ setSportsInterest(sportsInterest : boolean)
|
|
||||||
+ setWorldNewsInterest(worldNewsInterest : boolean)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@enduml
|
|
Binary file not shown.
Before Width: | Height: | Size: 19 KiB |
Binary file not shown.
Before Width: | Height: | Size: 71 KiB |
Binary file not shown.
Before Width: | Height: | Size: 77 KiB |
@ -1,80 +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>composite-view</artifactId>
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.junit.jupiter</groupId>
|
|
||||||
<artifactId>junit-jupiter-engine</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>junit</groupId>
|
|
||||||
<artifactId>junit</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>jakarta.servlet</groupId>
|
|
||||||
<artifactId>jakarta.servlet-api</artifactId>
|
|
||||||
<version>5.0.0</version>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.mockito</groupId>
|
|
||||||
<artifactId>mockito-core</artifactId>
|
|
||||||
<version>4.1.0</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-assembly-plugin</artifactId>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<configuration>
|
|
||||||
<archive>
|
|
||||||
<manifest>
|
|
||||||
<mainClass>com.iluwatar.compositeview.App</mainClass>
|
|
||||||
</manifest>
|
|
||||||
</archive>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
|
|
||||||
</project>
|
|
@ -1,64 +0,0 @@
|
|||||||
package com.iluwatar.compositeview;
|
|
||||||
|
|
||||||
import jakarta.servlet.RequestDispatcher;
|
|
||||||
import jakarta.servlet.ServletException;
|
|
||||||
import jakarta.servlet.http.HttpServlet;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A servlet object that extends HttpServlet.
|
|
||||||
* Runs on Tomcat 10 and handles Http requests
|
|
||||||
*/
|
|
||||||
|
|
||||||
public final class AppServlet extends HttpServlet {
|
|
||||||
private String msgPartOne = "<h1>This Server Doesn't Support";
|
|
||||||
private String msgPartTwo = "Requests</h1>\n"
|
|
||||||
+ "<h2>Use a GET request with boolean values for the following parameters<h2>\n"
|
|
||||||
+ "<h3>'name'</h3>\n<h3>'bus'</h3>\n<h3>'sports'</h3>\n<h3>'sci'</h3>\n<h3>'world'</h3>";
|
|
||||||
|
|
||||||
private String destination = "newsDisplay.jsp";
|
|
||||||
|
|
||||||
public AppServlet() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void doGet(HttpServletRequest req, HttpServletResponse resp)
|
|
||||||
throws ServletException, IOException {
|
|
||||||
RequestDispatcher requestDispatcher = req.getRequestDispatcher(destination);
|
|
||||||
ClientPropertiesBean reqParams = new ClientPropertiesBean(req);
|
|
||||||
req.setAttribute("properties", reqParams);
|
|
||||||
requestDispatcher.forward(req, resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void doPost(HttpServletRequest req, HttpServletResponse resp)
|
|
||||||
throws ServletException, IOException {
|
|
||||||
resp.setContentType("text/html");
|
|
||||||
try (PrintWriter out = resp.getWriter()) {
|
|
||||||
out.println(msgPartOne + " Post " + msgPartTwo);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void doDelete(HttpServletRequest req, HttpServletResponse resp)
|
|
||||||
throws ServletException, IOException {
|
|
||||||
resp.setContentType("text/html");
|
|
||||||
try (PrintWriter out = resp.getWriter()) {
|
|
||||||
out.println(msgPartOne + " Delete " + msgPartTwo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void doPut(HttpServletRequest req, HttpServletResponse resp)
|
|
||||||
throws ServletException, IOException {
|
|
||||||
resp.setContentType("text/html");
|
|
||||||
try (PrintWriter out = resp.getWriter()) {
|
|
||||||
out.println(msgPartOne + " Put " + msgPartTwo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,53 +0,0 @@
|
|||||||
package com.iluwatar.compositeview;
|
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Java beans class that parses a http request and stores parameters.
|
|
||||||
* Java beans used in JSP's to dynamically include elements in view.
|
|
||||||
* DEFAULT_NAME = a constant, default name to be used for the default constructor
|
|
||||||
* worldNewsInterest = whether current request has world news interest
|
|
||||||
* sportsInterest = whether current request has a sportsInterest
|
|
||||||
* businessInterest = whether current request has a businessInterest
|
|
||||||
* scienceNewsInterest = whether current request has a scienceNewsInterest
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@NoArgsConstructor
|
|
||||||
public class ClientPropertiesBean implements Serializable {
|
|
||||||
|
|
||||||
private static final String WORLD_PARAM = "world";
|
|
||||||
private static final String SCIENCE_PARAM = "sci";
|
|
||||||
private static final String SPORTS_PARAM = "sport";
|
|
||||||
private static final String BUSINESS_PARAM = "bus";
|
|
||||||
private static final String NAME_PARAM = "name";
|
|
||||||
|
|
||||||
private static final String DEFAULT_NAME = "DEFAULT_NAME";
|
|
||||||
private boolean worldNewsInterest = true;
|
|
||||||
private boolean sportsInterest = true;
|
|
||||||
private boolean businessInterest = true;
|
|
||||||
private boolean scienceNewsInterest = true;
|
|
||||||
private String name = DEFAULT_NAME;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor that parses an HttpServletRequest and stores all the request parameters.
|
|
||||||
*
|
|
||||||
* @param req the HttpServletRequest object that is passed in
|
|
||||||
*/
|
|
||||||
public ClientPropertiesBean(HttpServletRequest req) {
|
|
||||||
worldNewsInterest = Boolean.parseBoolean(req.getParameter(WORLD_PARAM));
|
|
||||||
sportsInterest = Boolean.parseBoolean(req.getParameter(SPORTS_PARAM));
|
|
||||||
businessInterest = Boolean.parseBoolean(req.getParameter(BUSINESS_PARAM));
|
|
||||||
scienceNewsInterest = Boolean.parseBoolean(req.getParameter(SCIENCE_PARAM));
|
|
||||||
String tempName = req.getParameter(NAME_PARAM);
|
|
||||||
if (tempName == null || tempName.equals("")) {
|
|
||||||
tempName = DEFAULT_NAME;
|
|
||||||
}
|
|
||||||
name = tempName;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
package com.iluwatar.compositeview;
|
|
||||||
|
|
||||||
import jakarta.servlet.RequestDispatcher;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.mockito.Mockito;
|
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
/* Written with reference from https://stackoverflow.com/questions/5434419/how-to-test-my-servlet-using-junit
|
|
||||||
and https://stackoverflow.com/questions/50211433/servlets-unit-testing
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class AppServletTest extends Mockito{
|
|
||||||
private String msgPartOne = "<h1>This Server Doesn't Support";
|
|
||||||
private String msgPartTwo = "Requests</h1>\n"
|
|
||||||
+ "<h2>Use a GET request with boolean values for the following parameters<h2>\n"
|
|
||||||
+ "<h3>'name'</h3>\n<h3>'bus'</h3>\n<h3>'sports'</h3>\n<h3>'sci'</h3>\n<h3>'world'</h3>";
|
|
||||||
private String destination = "newsDisplay.jsp";
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDoGet() throws Exception {
|
|
||||||
HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
|
|
||||||
HttpServletResponse mockResp = Mockito.mock(HttpServletResponse.class);
|
|
||||||
RequestDispatcher mockDispatcher = Mockito.mock(RequestDispatcher.class);
|
|
||||||
StringWriter stringWriter = new StringWriter();
|
|
||||||
PrintWriter printWriter = new PrintWriter(stringWriter);
|
|
||||||
when(mockResp.getWriter()).thenReturn(printWriter);
|
|
||||||
when(mockReq.getRequestDispatcher(destination)).thenReturn(mockDispatcher);
|
|
||||||
AppServlet curServlet = new AppServlet();
|
|
||||||
curServlet.doGet(mockReq, mockResp);
|
|
||||||
verify(mockReq, times(1)).getRequestDispatcher(destination);
|
|
||||||
verify(mockDispatcher).forward(mockReq, mockResp);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDoPost() throws Exception {
|
|
||||||
HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
|
|
||||||
HttpServletResponse mockResp = Mockito.mock(HttpServletResponse.class);
|
|
||||||
StringWriter stringWriter = new StringWriter();
|
|
||||||
PrintWriter printWriter = new PrintWriter(stringWriter);
|
|
||||||
when(mockResp.getWriter()).thenReturn(printWriter);
|
|
||||||
|
|
||||||
AppServlet curServlet = new AppServlet();
|
|
||||||
curServlet.doPost(mockReq, mockResp);
|
|
||||||
printWriter.flush();
|
|
||||||
assertTrue(stringWriter.toString().contains(msgPartOne + " Post " + msgPartTwo));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDoPut() throws Exception {
|
|
||||||
HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
|
|
||||||
HttpServletResponse mockResp = Mockito.mock(HttpServletResponse.class);
|
|
||||||
StringWriter stringWriter = new StringWriter();
|
|
||||||
PrintWriter printWriter = new PrintWriter(stringWriter);
|
|
||||||
when(mockResp.getWriter()).thenReturn(printWriter);
|
|
||||||
|
|
||||||
AppServlet curServlet = new AppServlet();
|
|
||||||
curServlet.doPut(mockReq, mockResp);
|
|
||||||
printWriter.flush();
|
|
||||||
assertTrue(stringWriter.toString().contains(msgPartOne + " Put " + msgPartTwo));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDoDelete() throws Exception {
|
|
||||||
HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
|
|
||||||
HttpServletResponse mockResp = Mockito.mock(HttpServletResponse.class);
|
|
||||||
StringWriter stringWriter = new StringWriter();
|
|
||||||
PrintWriter printWriter = new PrintWriter(stringWriter);
|
|
||||||
when(mockResp.getWriter()).thenReturn(printWriter);
|
|
||||||
|
|
||||||
AppServlet curServlet = new AppServlet();
|
|
||||||
curServlet.doDelete(mockReq, mockResp);
|
|
||||||
printWriter.flush();
|
|
||||||
assertTrue(stringWriter.toString().contains(msgPartOne + " Delete " + msgPartTwo));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
package com.iluwatar.compositeview;
|
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.mockito.Mockito;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
public class JavaBeansTest {
|
|
||||||
@Test
|
|
||||||
public void testDefaultConstructor() {
|
|
||||||
ClientPropertiesBean newBean = new ClientPropertiesBean();
|
|
||||||
assertEquals("DEFAULT_NAME", newBean.getName());
|
|
||||||
assertTrue(newBean.isBusinessInterest());
|
|
||||||
assertTrue(newBean.isScienceNewsInterest());
|
|
||||||
assertTrue(newBean.isSportsInterest());
|
|
||||||
assertTrue(newBean.isWorldNewsInterest());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testNameGetterSetter() {
|
|
||||||
ClientPropertiesBean newBean = new ClientPropertiesBean();
|
|
||||||
assertEquals("DEFAULT_NAME", newBean.getName());
|
|
||||||
newBean.setName("TEST_NAME_ONE");
|
|
||||||
assertEquals("TEST_NAME_ONE", newBean.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBusinessSetterGetter() {
|
|
||||||
ClientPropertiesBean newBean = new ClientPropertiesBean();
|
|
||||||
assertTrue(newBean.isBusinessInterest());
|
|
||||||
newBean.setBusinessInterest(false);
|
|
||||||
assertFalse(newBean.isBusinessInterest());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testScienceSetterGetter() {
|
|
||||||
ClientPropertiesBean newBean = new ClientPropertiesBean();
|
|
||||||
assertTrue(newBean.isScienceNewsInterest());
|
|
||||||
newBean.setScienceNewsInterest(false);
|
|
||||||
assertFalse(newBean.isScienceNewsInterest());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSportsSetterGetter() {
|
|
||||||
ClientPropertiesBean newBean = new ClientPropertiesBean();
|
|
||||||
assertTrue(newBean.isSportsInterest());
|
|
||||||
newBean.setSportsInterest(false);
|
|
||||||
assertFalse(newBean.isSportsInterest());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testWorldSetterGetter() {
|
|
||||||
ClientPropertiesBean newBean = new ClientPropertiesBean();
|
|
||||||
assertTrue(newBean.isWorldNewsInterest());
|
|
||||||
newBean.setWorldNewsInterest(false);
|
|
||||||
assertFalse(newBean.isWorldNewsInterest());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRequestConstructor(){
|
|
||||||
HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
|
|
||||||
ClientPropertiesBean newBean = new ClientPropertiesBean((mockReq));
|
|
||||||
assertEquals("DEFAULT_NAME", newBean.getName());
|
|
||||||
assertFalse(newBean.isWorldNewsInterest());
|
|
||||||
assertFalse(newBean.isBusinessInterest());
|
|
||||||
assertFalse(newBean.isScienceNewsInterest());
|
|
||||||
assertFalse(newBean.isSportsInterest());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
|
|
||||||
version="4.0">
|
|
||||||
<servlet>
|
|
||||||
<servlet-name>appServlet</servlet-name>
|
|
||||||
<servlet-class>com.iluwatar.compositeview.AppServlet</servlet-class>
|
|
||||||
</servlet>
|
|
||||||
<servlet-mapping>
|
|
||||||
<servlet-name>appServlet</servlet-name>
|
|
||||||
<url-pattern>/news</url-pattern>
|
|
||||||
</servlet-mapping>
|
|
||||||
</web-app>
|
|
@ -1,33 +0,0 @@
|
|||||||
<%--
|
|
||||||
Created by IntelliJ IDEA.
|
|
||||||
User: Kevin
|
|
||||||
Date: 11/29/2021
|
|
||||||
Time: 2:51 PM
|
|
||||||
To change this template use File | Settings | File Templates.
|
|
||||||
--%>
|
|
||||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<style>
|
|
||||||
h2 { text-align: center;}
|
|
||||||
table {border: 1px solid black;}
|
|
||||||
tr {text-align: center;}
|
|
||||||
td {text-align: center;}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h2>
|
|
||||||
Generic Business News
|
|
||||||
</h2>
|
|
||||||
<table style="margin-right: auto; margin-left: auto">
|
|
||||||
<tr>
|
|
||||||
<td>Stock prices up across the world</td>
|
|
||||||
<td>New tech companies to invest in</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Industry leaders unveil new project</td>
|
|
||||||
<td>Price fluctuations and what they mean</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,23 +0,0 @@
|
|||||||
<%--
|
|
||||||
Created by IntelliJ IDEA.
|
|
||||||
User: Kevin
|
|
||||||
Date: 11/29/2021
|
|
||||||
Time: 1:28 PM
|
|
||||||
To change this template use File | Settings | File Templates.
|
|
||||||
--%>
|
|
||||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
|
||||||
<%@ page import="java.util.Date"%>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<style>
|
|
||||||
h1 { text-align: center;}
|
|
||||||
h2 { text-align: center;}
|
|
||||||
h3 { text-align: center;}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<% String todayDateStr = (new Date().toString()); %>
|
|
||||||
<h1>Today's Personalized Frontpage</h1>
|
|
||||||
<h2><%=todayDateStr%></h2>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,20 +0,0 @@
|
|||||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<style>
|
|
||||||
h1 { text-align: center;}
|
|
||||||
h2 { text-align: center;}
|
|
||||||
h3 { text-align: center;}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Welcome To The Composite Patterns Mock News Site</h1>
|
|
||||||
<h2>Send a GET request to the "/news" path to see the composite view with mock news</h2>
|
|
||||||
<h2>Use the following parameters:</h2>
|
|
||||||
<h3>name: string name to be dynamically displayed</h3>
|
|
||||||
<h3>bus: boolean for whether you want to see the mock business news</h3>
|
|
||||||
<h3>world: boolean for whether you want to see the mock world news</h3>
|
|
||||||
<h3>sci: boolean for whether you want to see the mock world news</h3>
|
|
||||||
<h3>sport: boolean for whether you want to see the mock world news</h3>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,25 +0,0 @@
|
|||||||
|
|
||||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<div style="text-align: center">
|
|
||||||
<h3>
|
|
||||||
Generic Local News
|
|
||||||
</h3>
|
|
||||||
<ul style="list-style-type: none">
|
|
||||||
<li>
|
|
||||||
Mayoral elections coming up in 2 weeks
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
New parking meter rates downtown coming tomorrow
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Park renovations to finish by the next year
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Annual marathon sign ups available online
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,57 +0,0 @@
|
|||||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
|
||||||
<%@ page import="com.iluwatar.compositeview.ClientPropertiesBean"%>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<style>
|
|
||||||
h1 { text-align: center;}
|
|
||||||
h2 { text-align: center;}
|
|
||||||
h3 { text-align: center;}
|
|
||||||
.centerTable {
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
table {border: 1px solid black;}
|
|
||||||
tr {text-align: center;}
|
|
||||||
td {text-align: center;}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<%ClientPropertiesBean propertiesBean = (ClientPropertiesBean) request.getAttribute("properties");%>
|
|
||||||
<h1>Welcome <%= propertiesBean.getName()%></h1>
|
|
||||||
<jsp:include page="header.jsp"></jsp:include>
|
|
||||||
<table class="centerTable">
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td></td>
|
|
||||||
<% if(propertiesBean.isWorldNewsInterest()) { %>
|
|
||||||
<td><%@include file="worldNews.jsp"%></td>
|
|
||||||
<% } else { %>
|
|
||||||
<td><%@include file="localNews.jsp"%></td>
|
|
||||||
<% } %>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<% if(propertiesBean.isBusinessInterest()) { %>
|
|
||||||
<td><%@include file="businessNews.jsp"%></td>
|
|
||||||
<% } else { %>
|
|
||||||
<td><%@include file="localNews.jsp"%></td>
|
|
||||||
<% } %>
|
|
||||||
<td></td>
|
|
||||||
<% if(propertiesBean.isSportsInterest()) { %>
|
|
||||||
<td><%@include file="sportsNews.jsp"%></td>
|
|
||||||
<% } else { %>
|
|
||||||
<td><%@include file="localNews.jsp"%></td>
|
|
||||||
<% } %>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td></td>
|
|
||||||
<% if(propertiesBean.isScienceNewsInterest()) { %>
|
|
||||||
<td><%@include file="scienceNews.jsp"%></td>
|
|
||||||
<% } else { %>
|
|
||||||
<td><%@include file="localNews.jsp"%></td>
|
|
||||||
<% } %>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,34 +0,0 @@
|
|||||||
<%--
|
|
||||||
Created by IntelliJ IDEA.
|
|
||||||
User: Kevin
|
|
||||||
Date: 11/29/2021
|
|
||||||
Time: 4:18 PM
|
|
||||||
To change this template use File | Settings | File Templates.
|
|
||||||
--%>
|
|
||||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<div style="text-align: center">
|
|
||||||
<h3>
|
|
||||||
Generic Science News
|
|
||||||
</h3>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
New model of gravity proposed for dark matter
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Genetic modifying technique proved on bacteria
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Neurology study maps brain with new precision
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Survey of rainforest discovers 15 new species
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
New signalling pathway for immune system discovered
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,32 +0,0 @@
|
|||||||
<%--
|
|
||||||
Created by IntelliJ IDEA.
|
|
||||||
User: Kevin
|
|
||||||
Date: 11/29/2021
|
|
||||||
Time: 3:53 PM
|
|
||||||
To change this template use File | Settings | File Templates.
|
|
||||||
--%>
|
|
||||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<style>
|
|
||||||
h2 { text-align: center;}
|
|
||||||
table {border: 1px solid black;}
|
|
||||||
tr {text-align: center;}
|
|
||||||
td {text-align: center;}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h2>
|
|
||||||
Generic Sports News
|
|
||||||
</h2>
|
|
||||||
<div style="margin-left: auto; margin-right: auto; padding: 20px">
|
|
||||||
International football match delayed due to weather, will be held next week
|
|
||||||
</div>
|
|
||||||
<div style="margin-left: auto; margin-right: auto; padding: 20px">
|
|
||||||
New rising stars in winter sports, ten new athletes that will shake up the scene
|
|
||||||
</div>
|
|
||||||
<div style="margin-left: auto; margin-right: auto; padding: 20px">
|
|
||||||
Biggest upset in basketball history, upstart team sweeps competition
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,34 +0,0 @@
|
|||||||
<%--
|
|
||||||
Created by IntelliJ IDEA.
|
|
||||||
User: Kevin
|
|
||||||
Date: 11/29/2021
|
|
||||||
Time: 2:51 PM
|
|
||||||
To change this template use File | Settings | File Templates.
|
|
||||||
--%>
|
|
||||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<style>
|
|
||||||
h2 { text-align: center;}
|
|
||||||
table {border: 1px solid black;}
|
|
||||||
tr {text-align: center;}
|
|
||||||
td {text-align: center;}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h2>
|
|
||||||
Generic World News
|
|
||||||
</h2>
|
|
||||||
<table style="margin-right: auto; margin-left: auto">
|
|
||||||
<tr>
|
|
||||||
<td>New trade talks happening at UN on Thursday</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>European Union to announce new resolution next week</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>UN delivers report on world economic status</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -9,10 +9,6 @@ tags:
|
|||||||
- Reactive
|
- Reactive
|
||||||
---
|
---
|
||||||
|
|
||||||
## Name
|
|
||||||
|
|
||||||
Event Aggregator
|
|
||||||
|
|
||||||
## Intent
|
## Intent
|
||||||
A system with lots of objects can lead to complexities when a
|
A system with lots of objects can lead to complexities when a
|
||||||
client wants to subscribe to events. The client has to find and register for
|
client wants to subscribe to events. The client has to find and register for
|
||||||
@ -21,136 +17,6 @@ requires a separate subscription. An Event Aggregator acts as a single source
|
|||||||
of events for many objects. It registers for all the events of the many objects
|
of events for many objects. It registers for all the events of the many objects
|
||||||
allowing clients to register with just the aggregator.
|
allowing clients to register with just the aggregator.
|
||||||
|
|
||||||
## Explanation
|
|
||||||
|
|
||||||
Real-world example
|
|
||||||
|
|
||||||
> King Joffrey sits on the iron throne and rules the seven kingdoms of Westeros. He receives most
|
|
||||||
> of his critical information from King's Hand, the second in command. King's hand has many
|
|
||||||
> close advisors himself, feeding him with relevant information about events occurring in the
|
|
||||||
> kingdom.
|
|
||||||
|
|
||||||
In Plain Words
|
|
||||||
|
|
||||||
> Event Aggregator is an event mediator that collects events from multiple sources and delivers
|
|
||||||
> them to registered observers.
|
|
||||||
|
|
||||||
**Programmatic Example**
|
|
||||||
|
|
||||||
In our programmatic example, we demonstrate the implementation of an event aggregator pattern. Some of
|
|
||||||
the objects are event listeners, some are event emitters, and the event aggregator does both.
|
|
||||||
|
|
||||||
```java
|
|
||||||
public interface EventObserver {
|
|
||||||
void onEvent(Event e);
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract class EventEmitter {
|
|
||||||
|
|
||||||
private final Map<Event, List<EventObserver>> observerLists;
|
|
||||||
|
|
||||||
public EventEmitter() {
|
|
||||||
observerLists = new HashMap<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void registerObserver(EventObserver obs, Event e) {
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void notifyObservers(Event e) {
|
|
||||||
...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`KingJoffrey` is listening to events from `KingsHand`.
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Slf4j
|
|
||||||
public class KingJoffrey implements EventObserver {
|
|
||||||
@Override
|
|
||||||
public void onEvent(Event e) {
|
|
||||||
LOGGER.info("Received event from the King's Hand: {}", e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`KingsHand` is listening to events from his subordinates `LordBaelish`, `LordVarys`, and `Scout`.
|
|
||||||
Whatever he hears from them, he delivers to `KingJoffrey`.
|
|
||||||
|
|
||||||
```java
|
|
||||||
public class KingsHand extends EventEmitter implements EventObserver {
|
|
||||||
|
|
||||||
public KingsHand() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public KingsHand(EventObserver obs, Event e) {
|
|
||||||
super(obs, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEvent(Event e) {
|
|
||||||
notifyObservers(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
For example, `LordVarys` finds a traitor every Sunday and notifies the `KingsHand`.
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Slf4j
|
|
||||||
public class LordVarys extends EventEmitter implements EventObserver {
|
|
||||||
@Override
|
|
||||||
public void timePasses(Weekday day) {
|
|
||||||
if (day == Weekday.SATURDAY) {
|
|
||||||
notifyObservers(Event.TRAITOR_DETECTED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The following snippet demonstrates how the objects are constructed and wired together.
|
|
||||||
|
|
||||||
```java
|
|
||||||
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 emitters = List.of(
|
|
||||||
kingsHand,
|
|
||||||
baelish,
|
|
||||||
varys,
|
|
||||||
scout
|
|
||||||
);
|
|
||||||
|
|
||||||
Arrays.stream(Weekday.values())
|
|
||||||
.<Consumer<? super EventEmitter>>map(day -> emitter -> emitter.timePasses(day))
|
|
||||||
.forEachOrdered(emitters::forEach);
|
|
||||||
```
|
|
||||||
|
|
||||||
The console output after running the example.
|
|
||||||
|
|
||||||
```
|
|
||||||
18:21:52.955 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: Warships approaching
|
|
||||||
18:21:52.960 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: White walkers sighted
|
|
||||||
18:21:52.960 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: Stark sighted
|
|
||||||
18:21:52.960 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: Traitor detected
|
|
||||||
```
|
|
||||||
|
|
||||||
## Class diagram
|
## Class diagram
|
||||||

|

|
||||||
|
|
||||||
@ -160,13 +26,9 @@ Use the Event Aggregator pattern when
|
|||||||
* Event Aggregator is a good choice when you have lots of objects that are
|
* Event Aggregator is a good choice when you have lots of objects that are
|
||||||
potential event sources. Rather than have the observer deal with registering
|
potential event sources. Rather than have the observer deal with registering
|
||||||
with them all, you can centralize the registration logic to the Event
|
with them all, you can centralize the registration logic to the Event
|
||||||
Aggregator. As well as simplifying registration, an Event Aggregator also
|
Aggregator. As well as simplifying registration, a Event Aggregator also
|
||||||
simplifies the memory management issues in using observers.
|
simplifies the memory management issues in using observers.
|
||||||
|
|
||||||
## Related patterns
|
|
||||||
|
|
||||||
* [Observer](https://java-design-patterns.com/patterns/observer/)
|
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
* [Martin Fowler - Event Aggregator](http://martinfowler.com/eaaDev/EventAggregator.html)
|
* [Martin Fowler - Event Aggregator](http://martinfowler.com/eaaDev/EventAggregator.html)
|
||||||
|
@ -49,28 +49,13 @@ public class App {
|
|||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|
||||||
var kingJoffrey = new KingJoffrey();
|
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(
|
var emitters = List.of(
|
||||||
kingsHand,
|
kingsHand,
|
||||||
baelish,
|
new LordBaelish(kingsHand),
|
||||||
varys,
|
new LordVarys(kingsHand),
|
||||||
scout
|
new Scout(kingsHand)
|
||||||
);
|
);
|
||||||
|
|
||||||
Arrays.stream(Weekday.values())
|
Arrays.stream(Weekday.values())
|
||||||
|
@ -31,7 +31,6 @@ import lombok.RequiredArgsConstructor;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public enum Event {
|
public enum Event {
|
||||||
|
|
||||||
WHITE_WALKERS_SIGHTED("White walkers sighted"),
|
|
||||||
STARK_SIGHTED("Stark sighted"),
|
STARK_SIGHTED("Stark sighted"),
|
||||||
WARSHIPS_APPROACHING("Warships approaching"),
|
WARSHIPS_APPROACHING("Warships approaching"),
|
||||||
TRAITOR_DETECTED("Traitor detected");
|
TRAITOR_DETECTED("Traitor detected");
|
||||||
|
@ -23,48 +23,31 @@
|
|||||||
|
|
||||||
package com.iluwatar.event.aggregator;
|
package com.iluwatar.event.aggregator;
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* EventEmitter is the base class for event producers that can be observed.
|
* EventEmitter is the base class for event producers that can be observed.
|
||||||
*/
|
*/
|
||||||
public abstract class EventEmitter {
|
public abstract class EventEmitter {
|
||||||
|
|
||||||
private final Map<Event, List<EventObserver>> observerLists;
|
private final List<EventObserver> observers;
|
||||||
|
|
||||||
public EventEmitter() {
|
public EventEmitter() {
|
||||||
observerLists = new HashMap<>();
|
observers = new LinkedList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public EventEmitter(EventObserver obs, Event e) {
|
public EventEmitter(EventObserver obs) {
|
||||||
this();
|
this();
|
||||||
registerObserver(obs, e);
|
registerObserver(obs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public final void registerObserver(EventObserver obs) {
|
||||||
* Registers observer for specific event in the related list.
|
observers.add(obs);
|
||||||
*
|
|
||||||
* @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) {
|
protected void notifyObservers(Event e) {
|
||||||
if (observerLists.containsKey(e)) {
|
observers.forEach(obs -> obs.onEvent(e));
|
||||||
observerLists
|
|
||||||
.get(e)
|
|
||||||
.forEach(observer -> observer.onEvent(e));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void timePasses(Weekday day);
|
public abstract void timePasses(Weekday day);
|
||||||
|
@ -31,8 +31,8 @@ public class KingsHand extends EventEmitter implements EventObserver {
|
|||||||
public KingsHand() {
|
public KingsHand() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public KingsHand(EventObserver obs, Event e) {
|
public KingsHand(EventObserver obs) {
|
||||||
super(obs, e);
|
super(obs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -42,5 +42,6 @@ public class KingsHand extends EventEmitter implements EventObserver {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void timePasses(Weekday day) {
|
public void timePasses(Weekday day) {
|
||||||
|
// NOP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,8 +31,8 @@ public class LordBaelish extends EventEmitter {
|
|||||||
public LordBaelish() {
|
public LordBaelish() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public LordBaelish(EventObserver obs, Event e) {
|
public LordBaelish(EventObserver obs) {
|
||||||
super(obs, e);
|
super(obs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -23,19 +23,16 @@
|
|||||||
|
|
||||||
package com.iluwatar.event.aggregator;
|
package com.iluwatar.event.aggregator;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LordVarys produces events.
|
* LordVarys produces events.
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
public class LordVarys extends EventEmitter {
|
||||||
public class LordVarys extends EventEmitter implements EventObserver {
|
|
||||||
|
|
||||||
public LordVarys() {
|
public LordVarys() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public LordVarys(EventObserver obs, Event e) {
|
public LordVarys(EventObserver obs) {
|
||||||
super(obs, e);
|
super(obs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -44,10 +41,4 @@ public class LordVarys extends EventEmitter implements EventObserver {
|
|||||||
notifyObservers(Event.TRAITOR_DETECTED);
|
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() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Scout(EventObserver obs, Event e) {
|
public Scout(EventObserver obs) {
|
||||||
super(obs, e);
|
super(obs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -40,8 +40,5 @@ public class Scout extends EventEmitter {
|
|||||||
if (day == Weekday.TUESDAY) {
|
if (day == Weekday.TUESDAY) {
|
||||||
notifyObservers(Event.WARSHIPS_APPROACHING);
|
notifyObservers(Event.WARSHIPS_APPROACHING);
|
||||||
}
|
}
|
||||||
if (day == Weekday.WEDNESDAY) {
|
|
||||||
notifyObservers(Event.WHITE_WALKERS_SIGHTED);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,6 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
|
|||||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.function.BiFunction;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import org.junit.jupiter.api.Test;
|
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
|
* 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
|
* 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
|
* Create a new event emitter test, using the given test object factories, special day and event
|
||||||
*/
|
*/
|
||||||
EventEmitterTest(final Weekday specialDay, final Event event,
|
EventEmitterTest(final Weekday specialDay, final Event event,
|
||||||
final BiFunction<EventObserver, Event, E> factoryWithDefaultObserver,
|
final Function<EventObserver, E> factoryWithDefaultObserver,
|
||||||
final Supplier<E> factoryWithoutDefaultObserver) {
|
final Supplier<E> factoryWithoutDefaultObserver) {
|
||||||
|
|
||||||
this.specialDay = specialDay;
|
this.specialDay = specialDay;
|
||||||
@ -130,8 +129,8 @@ abstract class EventEmitterTest<E extends EventEmitter> {
|
|||||||
final var observer2 = mock(EventObserver.class);
|
final var observer2 = mock(EventObserver.class);
|
||||||
|
|
||||||
final var emitter = this.factoryWithoutDefaultObserver.get();
|
final var emitter = this.factoryWithoutDefaultObserver.get();
|
||||||
emitter.registerObserver(observer1, event);
|
emitter.registerObserver(observer1);
|
||||||
emitter.registerObserver(observer2, event);
|
emitter.registerObserver(observer2);
|
||||||
|
|
||||||
testAllDays(specialDay, event, emitter, observer1, 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 observer1 = mock(EventObserver.class);
|
||||||
final var observer2 = mock(EventObserver.class);
|
final var observer2 = mock(EventObserver.class);
|
||||||
|
|
||||||
final var emitter = this.factoryWithDefaultObserver.apply(defaultObserver, event);
|
final var emitter = this.factoryWithDefaultObserver.apply(defaultObserver);
|
||||||
emitter.registerObserver(observer1, event);
|
emitter.registerObserver(observer1);
|
||||||
emitter.registerObserver(observer2, event);
|
emitter.registerObserver(observer2);
|
||||||
|
|
||||||
testAllDays(specialDay, event, emitter, defaultObserver, observer1, observer2);
|
testAllDays(specialDay, event, emitter, defaultObserver, observer1, observer2);
|
||||||
}
|
}
|
||||||
|
@ -55,11 +55,7 @@ class KingsHandTest extends EventEmitterTest<KingsHand> {
|
|||||||
@Test
|
@Test
|
||||||
void testPassThrough() throws Exception {
|
void testPassThrough() throws Exception {
|
||||||
final var observer = mock(EventObserver.class);
|
final var observer = mock(EventObserver.class);
|
||||||
final var kingsHand = new KingsHand();
|
final var kingsHand = new KingsHand(observer);
|
||||||
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
|
// The kings hand should not pass any events before he received one
|
||||||
verifyZeroInteractions(observer);
|
verifyZeroInteractions(observer);
|
||||||
|
@ -34,9 +34,7 @@ class ScoutTest extends EventEmitterTest<Scout> {
|
|||||||
* Create a new test instance, using the correct object factory
|
* Create a new test instance, using the correct object factory
|
||||||
*/
|
*/
|
||||||
public ScoutTest() {
|
public ScoutTest() {
|
||||||
|
|
||||||
super(Weekday.TUESDAY, Event.WARSHIPS_APPROACHING, Scout::new, Scout::new);
|
super(Weekday.TUESDAY, Event.WARSHIPS_APPROACHING, Scout::new, Scout::new);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -17,10 +17,10 @@ the user to specify only what to do with the resource.
|
|||||||
|
|
||||||
## Explanation
|
## Explanation
|
||||||
|
|
||||||
Real-world example
|
Real world example
|
||||||
|
|
||||||
> A class needs to be provided for writing text strings to files. To make it easy for
|
> We need to provide a class that can be used to write text strings to files. To make it easy for
|
||||||
> the user, the service class opens and closes the file automatically. The user only has to
|
> the user we let our service class open and close the file automatically, the user only has to
|
||||||
> specify what is written into which file.
|
> specify what is written into which file.
|
||||||
|
|
||||||
In plain words
|
In plain words
|
||||||
@ -35,50 +35,35 @@ In plain words
|
|||||||
|
|
||||||
**Programmatic Example**
|
**Programmatic Example**
|
||||||
|
|
||||||
`SimpleFileWriter` class implements the Execute Around idiom. It takes `FileWriterAction` as a
|
Let's introduce our file writer class.
|
||||||
constructor argument allowing the user to specify what gets written into the file.
|
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface FileWriterAction {
|
public interface FileWriterAction {
|
||||||
|
|
||||||
void writeFile(FileWriter writer) throws IOException;
|
void writeFile(FileWriter writer) throws IOException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
public class SimpleFileWriter {
|
public class SimpleFileWriter {
|
||||||
|
|
||||||
public SimpleFileWriter(String filename, FileWriterAction action) throws IOException {
|
public SimpleFileWriter(String filename, FileWriterAction action) throws IOException {
|
||||||
LOGGER.info("Opening the file");
|
|
||||||
try (var writer = new FileWriter(filename)) {
|
try (var writer = new FileWriter(filename)) {
|
||||||
LOGGER.info("Executing the action");
|
|
||||||
action.writeFile(writer);
|
action.writeFile(writer);
|
||||||
LOGGER.info("Closing the file");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The following code demonstrates how `SimpleFileWriter` is used. `Scanner` is used to print the file
|
To utilize the file writer the following code is needed.
|
||||||
contents after the writing finishes.
|
|
||||||
|
|
||||||
```java
|
```java
|
||||||
FileWriterAction writeHello = writer -> {
|
FileWriterAction writeHello = writer -> {
|
||||||
writer.write("Gandalf was here");
|
writer.write("Hello");
|
||||||
};
|
writer.append(" ");
|
||||||
new SimpleFileWriter("testfile.txt", writeHello);
|
writer.append("there!");
|
||||||
|
};
|
||||||
var scanner = new Scanner(new File("testfile.txt"));
|
new SimpleFileWriter("testfile.txt", writeHello);
|
||||||
while (scanner.hasNextLine()) {
|
|
||||||
LOGGER.info(scanner.nextLine());
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Here's the console output.
|
|
||||||
|
|
||||||
```
|
|
||||||
21:18:07.185 [main] INFO com.iluwatar.execute.around.SimpleFileWriter - Opening the file
|
|
||||||
21:18:07.188 [main] INFO com.iluwatar.execute.around.SimpleFileWriter - Executing the action
|
|
||||||
21:18:07.189 [main] INFO com.iluwatar.execute.around.SimpleFileWriter - Closing the file
|
|
||||||
21:18:07.199 [main] INFO com.iluwatar.execute.around.App - Gandalf was here
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Class diagram
|
## Class diagram
|
||||||
@ -89,7 +74,8 @@ Here's the console output.
|
|||||||
|
|
||||||
Use the Execute Around idiom when
|
Use the Execute Around idiom when
|
||||||
|
|
||||||
* An API requires methods to be called in pairs such as open/close or allocate/deallocate.
|
* You use an API that requires methods to be called in pairs such as open/close or
|
||||||
|
allocate/deallocate.
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
|
@ -23,14 +23,10 @@
|
|||||||
|
|
||||||
package com.iluwatar.execute.around;
|
package com.iluwatar.execute.around;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Scanner;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Execute Around idiom specifies executable code before and after a method. Typically
|
* The Execute Around idiom specifies some code to be executed before and after a method. Typically
|
||||||
* the idiom is used when the API has methods to be executed in pairs, such as resource
|
* the idiom is used when the API has methods to be executed in pairs, such as resource
|
||||||
* allocation/deallocation or lock acquisition/release.
|
* allocation/deallocation or lock acquisition/release.
|
||||||
*
|
*
|
||||||
@ -38,7 +34,6 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
* the user. The user specifies only what to do with the file by providing the {@link
|
* the user. The user specifies only what to do with the file by providing the {@link
|
||||||
* FileWriterAction} implementation.
|
* FileWriterAction} implementation.
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
|
||||||
public class App {
|
public class App {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,16 +41,11 @@ public class App {
|
|||||||
*/
|
*/
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) throws IOException {
|
||||||
|
|
||||||
// create the file writer and execute the custom action
|
|
||||||
FileWriterAction writeHello = writer -> {
|
FileWriterAction writeHello = writer -> {
|
||||||
writer.write("Gandalf was here");
|
writer.write("Hello");
|
||||||
|
writer.append(" ");
|
||||||
|
writer.append("there!");
|
||||||
};
|
};
|
||||||
new SimpleFileWriter("testfile.txt", writeHello);
|
new SimpleFileWriter("testfile.txt", writeHello);
|
||||||
|
|
||||||
// print the file contents
|
|
||||||
var scanner = new Scanner(new File("testfile.txt"));
|
|
||||||
while (scanner.hasNextLine()) {
|
|
||||||
LOGGER.info(scanner.nextLine());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,24 +26,18 @@ package com.iluwatar.execute.around;
|
|||||||
import java.io.FileWriter;
|
import java.io.FileWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SimpleFileWriter handles opening and closing file for the user. The user only has to specify what
|
* SimpleFileWriter handles opening and closing file for the user. The user only has to specify what
|
||||||
* to do with the file resource through {@link FileWriterAction} parameter.
|
* to do with the file resource through {@link FileWriterAction} parameter.
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
|
||||||
public class SimpleFileWriter {
|
public class SimpleFileWriter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*/
|
*/
|
||||||
public SimpleFileWriter(String filename, FileWriterAction action) throws IOException {
|
public SimpleFileWriter(String filename, FileWriterAction action) throws IOException {
|
||||||
LOGGER.info("Opening the file");
|
|
||||||
try (var writer = new FileWriter(filename)) {
|
try (var writer = new FileWriter(filename)) {
|
||||||
LOGGER.info("Executing the action");
|
|
||||||
action.writeFile(writer);
|
action.writeFile(writer);
|
||||||
LOGGER.info("Closing the file");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,115 +10,19 @@ tags:
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Intent
|
## Intent
|
||||||
|
|
||||||
Define a factory of immutable content with separated builder and factory interfaces.
|
Define a factory of immutable content with separated builder and factory interfaces.
|
||||||
|
|
||||||
## Explanation
|
|
||||||
|
|
||||||
Real-world example
|
|
||||||
|
|
||||||
> Imagine a magical weapon factory that can create any type of weapon wished for. When the factory
|
|
||||||
> is unboxed, the master recites the weapon types needed to prepare it. After that, any of those
|
|
||||||
> weapon types can be summoned in an instant.
|
|
||||||
|
|
||||||
In plain words
|
|
||||||
|
|
||||||
> Factory kit is a configurable object builder.
|
|
||||||
|
|
||||||
**Programmatic Example**
|
|
||||||
|
|
||||||
Let's first define the simple `Weapon` hierarchy.
|
|
||||||
|
|
||||||
```java
|
|
||||||
public interface Weapon {
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum WeaponType {
|
|
||||||
SWORD,
|
|
||||||
AXE,
|
|
||||||
BOW,
|
|
||||||
SPEAR
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Sword implements Weapon {
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "Sword";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Axe, Bow, and Spear are defined similarly
|
|
||||||
```
|
|
||||||
|
|
||||||
Next, we define a functional interface that allows adding a builder with a name to the factory.
|
|
||||||
|
|
||||||
```java
|
|
||||||
public interface Builder {
|
|
||||||
void add(WeaponType name, Supplier<Weapon> supplier);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The meat of the example is the `WeaponFactory` interface that effectively implements the factory
|
|
||||||
kit pattern. The method `#factory` is used to configure the factory with the classes it needs to
|
|
||||||
be able to construct. The method `#create` is then used to create object instances.
|
|
||||||
|
|
||||||
```java
|
|
||||||
public interface WeaponFactory {
|
|
||||||
|
|
||||||
static WeaponFactory factory(Consumer<Builder> consumer) {
|
|
||||||
var map = new HashMap<WeaponType, Supplier<Weapon>>();
|
|
||||||
consumer.accept(map::put);
|
|
||||||
return name -> map.get(name).get();
|
|
||||||
}
|
|
||||||
|
|
||||||
Weapon create(WeaponType name);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Now, we can show how `WeaponFactory` can be used.
|
|
||||||
|
|
||||||
```java
|
|
||||||
var factory = WeaponFactory.factory(builder -> {
|
|
||||||
builder.add(WeaponType.SWORD, Sword::new);
|
|
||||||
builder.add(WeaponType.AXE, Axe::new);
|
|
||||||
builder.add(WeaponType.SPEAR, Spear::new);
|
|
||||||
builder.add(WeaponType.BOW, Bow::new);
|
|
||||||
});
|
|
||||||
var list = new ArrayList<Weapon>();
|
|
||||||
list.add(factory.create(WeaponType.AXE));
|
|
||||||
list.add(factory.create(WeaponType.SPEAR));
|
|
||||||
list.add(factory.create(WeaponType.SWORD));
|
|
||||||
list.add(factory.create(WeaponType.BOW));
|
|
||||||
list.stream().forEach(weapon -> LOGGER.info("{}", weapon.toString()));
|
|
||||||
```
|
|
||||||
|
|
||||||
Here is the console output when the example is run.
|
|
||||||
|
|
||||||
```
|
|
||||||
21:15:49.709 [main] INFO com.iluwatar.factorykit.App - Axe
|
|
||||||
21:15:49.713 [main] INFO com.iluwatar.factorykit.App - Spear
|
|
||||||
21:15:49.713 [main] INFO com.iluwatar.factorykit.App - Sword
|
|
||||||
21:15:49.713 [main] INFO com.iluwatar.factorykit.App - Bow
|
|
||||||
```
|
|
||||||
|
|
||||||
## Class diagram
|
## Class diagram
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Applicability
|
## Applicability
|
||||||
|
|
||||||
Use the Factory Kit pattern when
|
Use the Factory Kit pattern when
|
||||||
|
|
||||||
* The factory class can't anticipate the types of objects it must create
|
* a class can't anticipate the class of objects it must create
|
||||||
* A new instance of a custom builder is needed instead of a global one
|
* you just want a new instance of a custom builder instead of the global one
|
||||||
* The types of objects that the factory can build need to be defined outside the class
|
* you explicitly want to define types of objects, that factory can build
|
||||||
* The builder and creator interfaces need to be separated
|
* you want a separated builder and creator interface
|
||||||
|
|
||||||
## Related patterns
|
|
||||||
|
|
||||||
* [Builder](https://java-design-patterns.com/patterns/builder/)
|
|
||||||
* [Factory](https://java-design-patterns.com/patterns/factory/)
|
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
* [Design Pattern Reloaded by Remi Forax](https://www.youtube.com/watch?v=-k2X7guaArU)
|
* [Design Pattern Reloaded by Remi Forax: ](https://www.youtube.com/watch?v=-k2X7guaArU)
|
||||||
|
@ -23,16 +23,14 @@
|
|||||||
|
|
||||||
package com.iluwatar.factorykit;
|
package com.iluwatar.factorykit;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory kit is a creational pattern that defines a factory of immutable content with separated
|
* Factory-kit is a creational pattern which defines a factory of immutable content with separated
|
||||||
* builder and factory interfaces to deal with the problem of creating one of the objects specified
|
* builder and factory interfaces to deal with the problem of creating one of the objects specified
|
||||||
* directly in the factory kit instance.
|
* directly in the factory-kit instance.
|
||||||
*
|
*
|
||||||
* <p>In the given example {@link WeaponFactory} represents the factory kit, that contains four
|
* <p>In the given example {@link WeaponFactory} represents the factory-kit, that contains four
|
||||||
* {@link Builder}s for creating new objects of the classes implementing {@link Weapon} interface.
|
* {@link Builder}s for creating new objects of the classes implementing {@link Weapon} interface.
|
||||||
*
|
*
|
||||||
* <p>Each of them can be called with {@link WeaponFactory#create(WeaponType)} method, with
|
* <p>Each of them can be called with {@link WeaponFactory#create(WeaponType)} method, with
|
||||||
@ -54,11 +52,7 @@ public class App {
|
|||||||
builder.add(WeaponType.SPEAR, Spear::new);
|
builder.add(WeaponType.SPEAR, Spear::new);
|
||||||
builder.add(WeaponType.BOW, Bow::new);
|
builder.add(WeaponType.BOW, Bow::new);
|
||||||
});
|
});
|
||||||
var list = new ArrayList<Weapon>();
|
var axe = factory.create(WeaponType.AXE);
|
||||||
list.add(factory.create(WeaponType.AXE));
|
LOGGER.info(axe.toString());
|
||||||
list.add(factory.create(WeaponType.SPEAR));
|
|
||||||
list.add(factory.create(WeaponType.SWORD));
|
|
||||||
list.add(factory.create(WeaponType.BOW));
|
|
||||||
list.stream().forEach(weapon -> LOGGER.info("{}", weapon.toString()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 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 |
@ -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
|
|
@ -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>
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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[]{}));
|
|
||||||
}
|
|
||||||
}
|
|
@ -41,7 +41,7 @@
|
|||||||
<zk.version>9.0.0</zk.version>
|
<zk.version>9.0.0</zk.version>
|
||||||
<guava.version>19.0</guava.version>
|
<guava.version>19.0</guava.version>
|
||||||
<jetty-maven-plugin.version>9.4.28.v20200408</jetty-maven-plugin.version>
|
<jetty-maven-plugin.version>9.4.28.v20200408</jetty-maven-plugin.version>
|
||||||
<maven-war-plugin.version>3.3.2</maven-war-plugin.version>
|
<maven-war-plugin.version>2.1.1</maven-war-plugin.version>
|
||||||
<maven-assembly-plugin.version>2.2</maven-assembly-plugin.version>
|
<maven-assembly-plugin.version>2.2</maven-assembly-plugin.version>
|
||||||
<maven.build.timestamp.format>yyyy-MM-dd</maven.build.timestamp.format>
|
<maven.build.timestamp.format>yyyy-MM-dd</maven.build.timestamp.format>
|
||||||
<packname>-${project.version}-FL-${maven.build.timestamp}</packname>
|
<packname>-${project.version}-FL-${maven.build.timestamp}</packname>
|
||||||
|
@ -15,18 +15,18 @@ Registry
|
|||||||
|
|
||||||
## Intent
|
## Intent
|
||||||
|
|
||||||
Ensure a class only has a limited number of instances and provide a global point of access to them.
|
Ensure a class only has limited number of instances and provide a global point of access to them.
|
||||||
|
|
||||||
## Explanation
|
## Explanation
|
||||||
|
|
||||||
Real-world example
|
Real world example
|
||||||
|
|
||||||
> The Nazgûl, also called ringwraiths or the Nine Riders, are Sauron's most terrible servants. By
|
> The Nazgûl, also called ringwraiths or the Nine Riders, are Sauron's most terrible servants. By
|
||||||
> definition, there's always nine of them.
|
> definition there's always nine of them.
|
||||||
|
|
||||||
In plain words
|
In plain words
|
||||||
|
|
||||||
> Multiton pattern ensures there are a predefined amount of instances available globally.
|
> Multiton pattern ensures there's predefined amount of instances available globally.
|
||||||
|
|
||||||
Wikipedia says
|
Wikipedia says
|
||||||
|
|
||||||
@ -81,54 +81,29 @@ public final class Nazgul {
|
|||||||
And here's how we access the `Nazgul` instances.
|
And here's how we access the `Nazgul` instances.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
// eagerly initialized multiton
|
LOGGER.info("KHAMUL={}", Nazgul.getInstance(NazgulName.KHAMUL));
|
||||||
LOGGER.info("Printing out eagerly initialized multiton contents");
|
LOGGER.info("MURAZOR={}", Nazgul.getInstance(NazgulName.MURAZOR));
|
||||||
LOGGER.info("KHAMUL={}", Nazgul.getInstance(NazgulName.KHAMUL));
|
LOGGER.info("DWAR={}", Nazgul.getInstance(NazgulName.DWAR));
|
||||||
LOGGER.info("MURAZOR={}", Nazgul.getInstance(NazgulName.MURAZOR));
|
LOGGER.info("JI_INDUR={}", Nazgul.getInstance(NazgulName.JI_INDUR));
|
||||||
LOGGER.info("DWAR={}", Nazgul.getInstance(NazgulName.DWAR));
|
LOGGER.info("AKHORAHIL={}", Nazgul.getInstance(NazgulName.AKHORAHIL));
|
||||||
LOGGER.info("JI_INDUR={}", Nazgul.getInstance(NazgulName.JI_INDUR));
|
LOGGER.info("HOARMURATH={}", Nazgul.getInstance(NazgulName.HOARMURATH));
|
||||||
LOGGER.info("AKHORAHIL={}", Nazgul.getInstance(NazgulName.AKHORAHIL));
|
LOGGER.info("ADUNAPHEL={}", Nazgul.getInstance(NazgulName.ADUNAPHEL));
|
||||||
LOGGER.info("HOARMURATH={}", Nazgul.getInstance(NazgulName.HOARMURATH));
|
LOGGER.info("REN={}", Nazgul.getInstance(NazgulName.REN));
|
||||||
LOGGER.info("ADUNAPHEL={}", Nazgul.getInstance(NazgulName.ADUNAPHEL));
|
LOGGER.info("UVATHA={}", Nazgul.getInstance(NazgulName.UVATHA));
|
||||||
LOGGER.info("REN={}", Nazgul.getInstance(NazgulName.REN));
|
|
||||||
LOGGER.info("UVATHA={}", Nazgul.getInstance(NazgulName.UVATHA));
|
|
||||||
|
|
||||||
// enum multiton
|
|
||||||
LOGGER.info("Printing out enum-based multiton contents");
|
|
||||||
LOGGER.info("KHAMUL={}", NazgulEnum.KHAMUL);
|
|
||||||
LOGGER.info("MURAZOR={}", NazgulEnum.MURAZOR);
|
|
||||||
LOGGER.info("DWAR={}", NazgulEnum.DWAR);
|
|
||||||
LOGGER.info("JI_INDUR={}", NazgulEnum.JI_INDUR);
|
|
||||||
LOGGER.info("AKHORAHIL={}", NazgulEnum.AKHORAHIL);
|
|
||||||
LOGGER.info("HOARMURATH={}", NazgulEnum.HOARMURATH);
|
|
||||||
LOGGER.info("ADUNAPHEL={}", NazgulEnum.ADUNAPHEL);
|
|
||||||
LOGGER.info("REN={}", NazgulEnum.REN);
|
|
||||||
LOGGER.info("UVATHA={}", NazgulEnum.UVATHA);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Program output:
|
Program output:
|
||||||
|
|
||||||
```
|
```
|
||||||
20:35:07.413 [main] INFO com.iluwatar.multiton.App - Printing out eagerly initialized multiton contents
|
KHAMUL=com.iluwatar.multiton.Nazgul@2b214b94
|
||||||
20:35:07.417 [main] INFO com.iluwatar.multiton.App - KHAMUL=com.iluwatar.multiton.Nazgul@48cf768c
|
MURAZOR=com.iluwatar.multiton.Nazgul@17814b1c
|
||||||
20:35:07.419 [main] INFO com.iluwatar.multiton.App - MURAZOR=com.iluwatar.multiton.Nazgul@7960847b
|
DWAR=com.iluwatar.multiton.Nazgul@7ac9af2a
|
||||||
20:35:07.419 [main] INFO com.iluwatar.multiton.App - DWAR=com.iluwatar.multiton.Nazgul@6a6824be
|
JI_INDUR=com.iluwatar.multiton.Nazgul@7bb004b8
|
||||||
20:35:07.419 [main] INFO com.iluwatar.multiton.App - JI_INDUR=com.iluwatar.multiton.Nazgul@5c8da962
|
AKHORAHIL=com.iluwatar.multiton.Nazgul@78e89bfe
|
||||||
20:35:07.419 [main] INFO com.iluwatar.multiton.App - AKHORAHIL=com.iluwatar.multiton.Nazgul@512ddf17
|
HOARMURATH=com.iluwatar.multiton.Nazgul@652ce654
|
||||||
20:35:07.419 [main] INFO com.iluwatar.multiton.App - HOARMURATH=com.iluwatar.multiton.Nazgul@2c13da15
|
ADUNAPHEL=com.iluwatar.multiton.Nazgul@522ba524
|
||||||
20:35:07.419 [main] INFO com.iluwatar.multiton.App - ADUNAPHEL=com.iluwatar.multiton.Nazgul@77556fd
|
REN=com.iluwatar.multiton.Nazgul@29c5ee1d
|
||||||
20:35:07.419 [main] INFO com.iluwatar.multiton.App - REN=com.iluwatar.multiton.Nazgul@368239c8
|
UVATHA=com.iluwatar.multiton.Nazgul@15cea7b0
|
||||||
20:35:07.420 [main] INFO com.iluwatar.multiton.App - UVATHA=com.iluwatar.multiton.Nazgul@9e89d68
|
|
||||||
20:35:07.420 [main] INFO com.iluwatar.multiton.App - Printing out enum-based multiton contents
|
|
||||||
20:35:07.420 [main] INFO com.iluwatar.multiton.App - KHAMUL=KHAMUL
|
|
||||||
20:35:07.420 [main] INFO com.iluwatar.multiton.App - MURAZOR=MURAZOR
|
|
||||||
20:35:07.420 [main] INFO com.iluwatar.multiton.App - DWAR=DWAR
|
|
||||||
20:35:07.420 [main] INFO com.iluwatar.multiton.App - JI_INDUR=JI_INDUR
|
|
||||||
20:35:07.421 [main] INFO com.iluwatar.multiton.App - AKHORAHIL=AKHORAHIL
|
|
||||||
20:35:07.421 [main] INFO com.iluwatar.multiton.App - HOARMURATH=HOARMURATH
|
|
||||||
20:35:07.421 [main] INFO com.iluwatar.multiton.App - ADUNAPHEL=ADUNAPHEL
|
|
||||||
20:35:07.421 [main] INFO com.iluwatar.multiton.App - REN=REN
|
|
||||||
20:35:07.421 [main] INFO com.iluwatar.multiton.App - UVATHA=UVATHA
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Class diagram
|
## Class diagram
|
||||||
@ -139,5 +114,5 @@ Program output:
|
|||||||
|
|
||||||
Use the Multiton pattern when
|
Use the Multiton pattern when
|
||||||
|
|
||||||
* There must be a specific number of instances of a class, and they must be accessible to clients from
|
* There must be specific number of instances of a class, and they must be accessible to clients from
|
||||||
a well-known access point.
|
a well-known access point.
|
||||||
|
@ -26,13 +26,13 @@ package com.iluwatar.multiton;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whereas Singleton design pattern introduces single globally accessible object, the Multiton
|
* Whereas Singleton design pattern introduces single globally accessible object the Multiton
|
||||||
* pattern defines many globally accessible objects. The client asks for the correct instance from
|
* pattern defines many globally accessible objects. The client asks for the correct instance from
|
||||||
* the Multiton by passing an enumeration as a parameter.
|
* the Multiton by passing an enumeration as parameter.
|
||||||
*
|
*
|
||||||
* <p>There is more than one way to implement the multiton design pattern. In the first example
|
* <p>There is more than one way to implement the multiton design pattern. In the first example
|
||||||
* {@link Nazgul} is the Multiton and we can ask single {@link Nazgul} from it using {@link
|
* {@link Nazgul} is the Multiton and we can ask single {@link Nazgul} from it using {@link
|
||||||
* NazgulName}. The {@link Nazgul}s are statically initialized and stored in a concurrent hash map.
|
* NazgulName}. The {@link Nazgul}s are statically initialized and stored in concurrent hash map.
|
||||||
*
|
*
|
||||||
* <p>In the enum implementation {@link NazgulEnum} is the multiton. It is static and mutable
|
* <p>In the enum implementation {@link NazgulEnum} is the multiton. It is static and mutable
|
||||||
* because of the way java supports enums.
|
* because of the way java supports enums.
|
||||||
@ -47,7 +47,6 @@ public class App {
|
|||||||
*/
|
*/
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
// eagerly initialized multiton
|
// eagerly initialized multiton
|
||||||
LOGGER.info("Printing out eagerly initialized multiton contents");
|
|
||||||
LOGGER.info("KHAMUL={}", Nazgul.getInstance(NazgulName.KHAMUL));
|
LOGGER.info("KHAMUL={}", Nazgul.getInstance(NazgulName.KHAMUL));
|
||||||
LOGGER.info("MURAZOR={}", Nazgul.getInstance(NazgulName.MURAZOR));
|
LOGGER.info("MURAZOR={}", Nazgul.getInstance(NazgulName.MURAZOR));
|
||||||
LOGGER.info("DWAR={}", Nazgul.getInstance(NazgulName.DWAR));
|
LOGGER.info("DWAR={}", Nazgul.getInstance(NazgulName.DWAR));
|
||||||
@ -59,7 +58,6 @@ public class App {
|
|||||||
LOGGER.info("UVATHA={}", Nazgul.getInstance(NazgulName.UVATHA));
|
LOGGER.info("UVATHA={}", Nazgul.getInstance(NazgulName.UVATHA));
|
||||||
|
|
||||||
// enum multiton
|
// enum multiton
|
||||||
LOGGER.info("Printing out enum-based multiton contents");
|
|
||||||
LOGGER.info("KHAMUL={}", NazgulEnum.KHAMUL);
|
LOGGER.info("KHAMUL={}", NazgulEnum.KHAMUL);
|
||||||
LOGGER.info("MURAZOR={}", NazgulEnum.MURAZOR);
|
LOGGER.info("MURAZOR={}", NazgulEnum.MURAZOR);
|
||||||
LOGGER.info("DWAR={}", NazgulEnum.DWAR);
|
LOGGER.info("DWAR={}", NazgulEnum.DWAR);
|
||||||
|
@ -48,4 +48,5 @@ public class NazgulTest {
|
|||||||
assertEquals(name, nazgul.getName());
|
assertEquals(name, nazgul.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
18
mvnw
vendored
18
mvnw
vendored
@ -36,10 +36,6 @@
|
|||||||
|
|
||||||
if [ -z "$MAVEN_SKIP_RC" ] ; then
|
if [ -z "$MAVEN_SKIP_RC" ] ; then
|
||||||
|
|
||||||
if [ -f /usr/local/etc/mavenrc ] ; then
|
|
||||||
. /usr/local/etc/mavenrc
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f /etc/mavenrc ] ; then
|
if [ -f /etc/mavenrc ] ; then
|
||||||
. /etc/mavenrc
|
. /etc/mavenrc
|
||||||
fi
|
fi
|
||||||
@ -149,7 +145,7 @@ if [ -z "$JAVACMD" ] ; then
|
|||||||
JAVACMD="$JAVA_HOME/bin/java"
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD="`\\unset -f command; \\command -v java`"
|
JAVACMD="`which java`"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -216,9 +212,9 @@ else
|
|||||||
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
|
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
|
||||||
fi
|
fi
|
||||||
if [ -n "$MVNW_REPOURL" ]; then
|
if [ -n "$MVNW_REPOURL" ]; then
|
||||||
jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
|
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
|
||||||
else
|
else
|
||||||
jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
|
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
|
||||||
fi
|
fi
|
||||||
while IFS="=" read key value; do
|
while IFS="=" read key value; do
|
||||||
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
|
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
|
||||||
@ -237,9 +233,9 @@ else
|
|||||||
echo "Found wget ... using wget"
|
echo "Found wget ... using wget"
|
||||||
fi
|
fi
|
||||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
||||||
wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
|
wget "$jarUrl" -O "$wrapperJarPath"
|
||||||
else
|
else
|
||||||
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
|
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
|
||||||
fi
|
fi
|
||||||
elif command -v curl > /dev/null; then
|
elif command -v curl > /dev/null; then
|
||||||
if [ "$MVNW_VERBOSE" = true ]; then
|
if [ "$MVNW_VERBOSE" = true ]; then
|
||||||
@ -309,8 +305,6 @@ WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
|||||||
|
|
||||||
exec "$JAVACMD" \
|
exec "$JAVACMD" \
|
||||||
$MAVEN_OPTS \
|
$MAVEN_OPTS \
|
||||||
$MAVEN_DEBUG_OPTS \
|
|
||||||
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
|
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
|
||||||
"-Dmaven.home=${M2_HOME}" \
|
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
|
||||||
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
|
|
||||||
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
|
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
|
||||||
|
66
mvnw.cmd
vendored
66
mvnw.cmd
vendored
@ -1,21 +1,25 @@
|
|||||||
@REM ----------------------------------------------------------------------------
|
|
||||||
@REM Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
@REM or more contributor license agreements. See the NOTICE file
|
|
||||||
@REM distributed with this work for additional information
|
|
||||||
@REM regarding copyright ownership. The ASF licenses this file
|
|
||||||
@REM to you under the Apache License, Version 2.0 (the
|
|
||||||
@REM "License"); you may not use this file except in compliance
|
|
||||||
@REM with the License. You may obtain a copy of the License at
|
|
||||||
@REM
|
@REM
|
||||||
@REM http://www.apache.org/licenses/LICENSE-2.0
|
@REM The MIT License
|
||||||
|
@REM Copyright © 2014-2021 Ilkka Seppälä
|
||||||
|
@REM
|
||||||
|
@REM Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
@REM of this software and associated documentation files (the "Software"), to deal
|
||||||
|
@REM in the Software without restriction, including without limitation the rights
|
||||||
|
@REM to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
@REM copies of the Software, and to permit persons to whom the Software is
|
||||||
|
@REM furnished to do so, subject to the following conditions:
|
||||||
|
@REM
|
||||||
|
@REM The above copyright notice and this permission notice shall be included in
|
||||||
|
@REM all copies or substantial portions of the Software.
|
||||||
|
@REM
|
||||||
|
@REM THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
@REM IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
@REM FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
@REM AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
@REM LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
@REM OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
@REM THE SOFTWARE.
|
||||||
@REM
|
@REM
|
||||||
@REM Unless required by applicable law or agreed to in writing,
|
|
||||||
@REM software distributed under the License is distributed on an
|
|
||||||
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
@REM KIND, either express or implied. See the License for the
|
|
||||||
@REM specific language governing permissions and limitations
|
|
||||||
@REM under the License.
|
|
||||||
@REM ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
@REM ----------------------------------------------------------------------------
|
@REM ----------------------------------------------------------------------------
|
||||||
@REM Maven Start Up Batch script
|
@REM Maven Start Up Batch script
|
||||||
@ -46,8 +50,8 @@ if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
|
|||||||
@REM Execute a user defined script before this one
|
@REM Execute a user defined script before this one
|
||||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
|
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
|
||||||
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
|
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
|
||||||
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
|
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
|
||||||
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
|
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
|
||||||
:skipRcPre
|
:skipRcPre
|
||||||
|
|
||||||
@setlocal
|
@setlocal
|
||||||
@ -120,9 +124,9 @@ SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
|
|||||||
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
|
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
|
||||||
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||||
|
|
||||||
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
|
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
|
||||||
|
|
||||||
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
|
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
|
||||||
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
|
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -134,7 +138,7 @@ if exist %WRAPPER_JAR% (
|
|||||||
)
|
)
|
||||||
) else (
|
) else (
|
||||||
if not "%MVNW_REPOURL%" == "" (
|
if not "%MVNW_REPOURL%" == "" (
|
||||||
SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
|
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
|
||||||
)
|
)
|
||||||
if "%MVNW_VERBOSE%" == "true" (
|
if "%MVNW_VERBOSE%" == "true" (
|
||||||
echo Couldn't find %WRAPPER_JAR%, downloading it ...
|
echo Couldn't find %WRAPPER_JAR%, downloading it ...
|
||||||
@ -158,13 +162,7 @@ if exist %WRAPPER_JAR% (
|
|||||||
@REM work with both Windows and non-Windows executions.
|
@REM work with both Windows and non-Windows executions.
|
||||||
set MAVEN_CMD_LINE_ARGS=%*
|
set MAVEN_CMD_LINE_ARGS=%*
|
||||||
|
|
||||||
%MAVEN_JAVA_EXE% ^
|
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
|
||||||
%JVM_CONFIG_MAVEN_PROPS% ^
|
|
||||||
%MAVEN_OPTS% ^
|
|
||||||
%MAVEN_DEBUG_OPTS% ^
|
|
||||||
-classpath %WRAPPER_JAR% ^
|
|
||||||
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
|
|
||||||
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
|
|
||||||
if ERRORLEVEL 1 goto error
|
if ERRORLEVEL 1 goto error
|
||||||
goto end
|
goto end
|
||||||
|
|
||||||
@ -174,15 +172,15 @@ set ERROR_CODE=1
|
|||||||
:end
|
:end
|
||||||
@endlocal & set ERROR_CODE=%ERROR_CODE%
|
@endlocal & set ERROR_CODE=%ERROR_CODE%
|
||||||
|
|
||||||
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
|
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
|
||||||
@REM check for post script, once with legacy .bat ending and once with .cmd ending
|
@REM check for post script, once with legacy .bat ending and once with .cmd ending
|
||||||
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
|
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
|
||||||
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
|
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
|
||||||
:skipRcPost
|
:skipRcPost
|
||||||
|
|
||||||
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
|
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
|
||||||
if "%MAVEN_BATCH_PAUSE%"=="on" pause
|
if "%MAVEN_BATCH_PAUSE%" == "on" pause
|
||||||
|
|
||||||
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
|
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
|
||||||
|
|
||||||
cmd /C exit /B %ERROR_CODE%
|
exit /B %ERROR_CODE%
|
||||||
|
1
naked-objects/README
Normal file
1
naked-objects/README
Normal file
@ -0,0 +1 @@
|
|||||||
|
This is an Apache Isis application created with the SimpleApp archetype. The usage instructions can be found at http://isis.apache.org/guides/ug.html#_ug_getting-started_simpleapp-archetype
|
@ -150,7 +150,7 @@
|
|||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<artifactId>maven-war-plugin</artifactId>
|
<artifactId>maven-war-plugin</artifactId>
|
||||||
<version>3.3.2</version>
|
<version>2.4</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.mortbay.jetty</groupId>
|
<groupId>org.mortbay.jetty</groupId>
|
||||||
|
8
pom.xml
8
pom.xml
@ -75,7 +75,6 @@
|
|||||||
<license-maven-plugin.version>3.0</license-maven-plugin.version>
|
<license-maven-plugin.version>3.0</license-maven-plugin.version>
|
||||||
<urm-maven-plugin.version>1.4.8</urm-maven-plugin.version>
|
<urm-maven-plugin.version>1.4.8</urm-maven-plugin.version>
|
||||||
<commons-io.version>2.7</commons-io.version>
|
<commons-io.version>2.7</commons-io.version>
|
||||||
<istack-commons-runtime.version>4.0.1</istack-commons-runtime.version>
|
|
||||||
<!-- SonarCloud -->
|
<!-- SonarCloud -->
|
||||||
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
|
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
|
||||||
<sonar.organization>iluwatar</sonar.organization>
|
<sonar.organization>iluwatar</sonar.organization>
|
||||||
@ -228,8 +227,6 @@
|
|||||||
<module>lockable-object</module>
|
<module>lockable-object</module>
|
||||||
<module>fanout-fanin</module>
|
<module>fanout-fanin</module>
|
||||||
<module>domain-model</module>
|
<module>domain-model</module>
|
||||||
<module>composite-view</module>
|
|
||||||
<module>metadata-mapping</module>
|
|
||||||
</modules>
|
</modules>
|
||||||
<repositories>
|
<repositories>
|
||||||
<repository>
|
<repository>
|
||||||
@ -380,11 +377,6 @@
|
|||||||
<artifactId>commons-io</artifactId>
|
<artifactId>commons-io</artifactId>
|
||||||
<version>${commons-io.version}</version>
|
<version>${commons-io.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>com.sun.istack</groupId>
|
|
||||||
<artifactId>istack-commons-runtime</artifactId>
|
|
||||||
<version>${istack-commons-runtime.version}</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
@ -96,11 +96,9 @@ public class NioReactor {
|
|||||||
* @throws IOException if any I/O error occurs.
|
* @throws IOException if any I/O error occurs.
|
||||||
*/
|
*/
|
||||||
public void stop() throws InterruptedException, IOException {
|
public void stop() throws InterruptedException, IOException {
|
||||||
reactorMain.shutdown();
|
|
||||||
selector.wakeup();
|
|
||||||
if (!reactorMain.awaitTermination(4, TimeUnit.SECONDS)) {
|
|
||||||
reactorMain.shutdownNow();
|
reactorMain.shutdownNow();
|
||||||
}
|
selector.wakeup();
|
||||||
|
reactorMain.awaitTermination(4, TimeUnit.SECONDS);
|
||||||
selector.close();
|
selector.close();
|
||||||
LOGGER.info("Reactor stopped");
|
LOGGER.info("Reactor stopped");
|
||||||
}
|
}
|
||||||
|
@ -64,8 +64,6 @@ public class ThreadPoolDispatcher implements Dispatcher {
|
|||||||
@Override
|
@Override
|
||||||
public void stop() throws InterruptedException {
|
public void stop() throws InterruptedException {
|
||||||
executorService.shutdown();
|
executorService.shutdown();
|
||||||
if (executorService.awaitTermination(4, TimeUnit.SECONDS)) {
|
executorService.awaitTermination(4, TimeUnit.SECONDS);
|
||||||
executorService.shutdownNow();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,11 +34,11 @@ import java.util.HashMap;
|
|||||||
public class SpatialPartitionBubbles extends SpatialPartitionGeneric<Bubble> {
|
public class SpatialPartitionBubbles extends SpatialPartitionGeneric<Bubble> {
|
||||||
|
|
||||||
private final HashMap<Integer, Bubble> bubbles;
|
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.bubbles = bubbles;
|
||||||
this.bubblesQuadTree = bubblesQuadTree;
|
this.quadTree = quadTree;
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleCollisionsUsingQt(Bubble b) {
|
void handleCollisionsUsingQt(Bubble b) {
|
||||||
@ -46,7 +46,7 @@ public class SpatialPartitionBubbles extends SpatialPartitionGeneric<Bubble> {
|
|||||||
// centre of bubble and length = radius of bubble
|
// centre of bubble and length = radius of bubble
|
||||||
var rect = new Rect(b.coordinateX, b.coordinateY, 2D * b.radius, 2D * b.radius);
|
var rect = new Rect(b.coordinateX, b.coordinateY, 2D * b.radius, 2D * b.radius);
|
||||||
var quadTreeQueryResult = new ArrayList<Point>();
|
var quadTreeQueryResult = new ArrayList<Point>();
|
||||||
this.bubblesQuadTree.query(rect, quadTreeQueryResult);
|
this.quadTree.query(rect, quadTreeQueryResult);
|
||||||
//handling these collisions
|
//handling these collisions
|
||||||
b.handleCollision(quadTreeQueryResult, this.bubbles);
|
b.handleCollision(quadTreeQueryResult, this.bubbles);
|
||||||
}
|
}
|
||||||
|
@ -18,11 +18,11 @@ threads and eliminating the latency of creating new threads.
|
|||||||
|
|
||||||
## Explanation
|
## 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
|
> We have a large number of relatively short tasks at hand. We need to peel huge amounts of potatoes
|
||||||
> and serve a mighty amount of coffee cups. Creating a new thread for each task would be a waste so
|
> and serve mighty amount of coffee cups. Creating a new thread for each task would be a waste so we
|
||||||
> we establish a thread pool.
|
> establish a thread pool.
|
||||||
|
|
||||||
In plain words
|
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.
|
peeling and coffee making.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
|
@ -16,12 +16,10 @@ Ensure that a given client is not able to access service resources more than the
|
|||||||
|
|
||||||
## Explanation
|
## Explanation
|
||||||
|
|
||||||
Real-world example
|
Real world example
|
||||||
|
|
||||||
> A young human and an old dwarf walk into a bar. They start ordering beers from the bartender.
|
> A large multinational corporation offers API to its customers. The API is rate-limited and each
|
||||||
> The bartender immediately sees that the young human shouldn't consume too many drinks too fast
|
> customer can only make certain amount of calls per second.
|
||||||
> and refuses to serve if enough time has not passed. For the old dwarf, the serving rate can
|
|
||||||
> be higher.
|
|
||||||
|
|
||||||
In plain words
|
In plain words
|
||||||
|
|
||||||
@ -35,18 +33,15 @@ In plain words
|
|||||||
|
|
||||||
**Programmatic Example**
|
**Programmatic Example**
|
||||||
|
|
||||||
`BarCustomer` class presents the clients of the `Bartender` API. `CallsCount` tracks the number of
|
Tenant class presents the clients of the API. CallsCount tracks the number of API calls per tenant.
|
||||||
calls per `BarCustomer`.
|
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public class BarCustomer {
|
public class Tenant {
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final String name;
|
private final String name;
|
||||||
@Getter
|
|
||||||
private final int allowedCallsPerSecond;
|
private final int allowedCallsPerSecond;
|
||||||
|
|
||||||
public BarCustomer(String name, int allowedCallsPerSecond, CallsCount callsCount) {
|
public Tenant(String name, int allowedCallsPerSecond, CallsCount callsCount) {
|
||||||
if (allowedCallsPerSecond < 0) {
|
if (allowedCallsPerSecond < 0) {
|
||||||
throw new InvalidParameterException("Number of calls less than 0 not allowed");
|
throw new InvalidParameterException("Number of calls less than 0 not allowed");
|
||||||
}
|
}
|
||||||
@ -54,6 +49,14 @@ public class BarCustomer {
|
|||||||
this.allowedCallsPerSecond = allowedCallsPerSecond;
|
this.allowedCallsPerSecond = allowedCallsPerSecond;
|
||||||
callsCount.addTenant(name);
|
callsCount.addTenant(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAllowedCallsPerSecond() {
|
||||||
|
return allowedCallsPerSecond;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@ -73,14 +76,14 @@ public final class CallsCount {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
|
LOGGER.debug("Resetting the map.");
|
||||||
tenantCallsCount.replaceAll((k, v) -> new AtomicLong(0));
|
tenantCallsCount.replaceAll((k, v) -> new AtomicLong(0));
|
||||||
LOGGER.info("reset counters");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Next, the service that the tenants are calling is introduced. To track the call count, a throttler
|
Next we introduce the service that the tenants are calling. To track the call count we use the
|
||||||
timer is used.
|
throttler timer.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public interface Throttler {
|
public interface Throttler {
|
||||||
@ -108,31 +111,26 @@ public class ThrottleTimerImpl implements Throttler {
|
|||||||
}, 0, throttlePeriod);
|
}, 0, throttlePeriod);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
|
||||||
`Bartender` offers the `orderDrink` service to the `BarCustomer`s. The customers probably don't
|
class B2BService {
|
||||||
know that the beer serving rate is limited by their appearances.
|
|
||||||
|
|
||||||
```java
|
private static final Logger LOGGER = LoggerFactory.getLogger(B2BService.class);
|
||||||
class Bartender {
|
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(Bartender.class);
|
|
||||||
private final CallsCount callsCount;
|
private final CallsCount callsCount;
|
||||||
|
|
||||||
public Bartender(Throttler timer, CallsCount callsCount) {
|
public B2BService(Throttler timer, CallsCount callsCount) {
|
||||||
this.callsCount = callsCount;
|
this.callsCount = callsCount;
|
||||||
timer.start();
|
timer.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int orderDrink(BarCustomer barCustomer) {
|
public int dummyCustomerApi(Tenant tenant) {
|
||||||
var tenantName = barCustomer.getName();
|
var tenantName = tenant.getName();
|
||||||
var count = callsCount.getCount(tenantName);
|
var count = callsCount.getCount(tenantName);
|
||||||
if (count >= barCustomer.getAllowedCallsPerSecond()) {
|
LOGGER.debug("Counter for {} : {} ", tenant.getName(), count);
|
||||||
LOGGER.error("I'm sorry {}, you've had enough for today!", tenantName);
|
if (count >= tenant.getAllowedCallsPerSecond()) {
|
||||||
|
LOGGER.error("API access per second limit reached for: {}", tenantName);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
callsCount.incrementCount(tenantName);
|
callsCount.incrementCount(tenantName);
|
||||||
LOGGER.debug("Serving beer to {} : [{} consumed] ", barCustomer.getName(), count+1);
|
|
||||||
return getRandomCustomerId();
|
return getRandomCustomerId();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,69 +140,42 @@ class Bartender {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Now it is possible to see the full example in action. `BarCustomer` young human is rate-limited to 2
|
Now we are ready to see the full example in action. Tenant Adidas is rate-limited to 5 calls per
|
||||||
calls per second and the old dwarf to 4.
|
second and Nike to 6.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
var callsCount = new CallsCount();
|
var callsCount = new CallsCount();
|
||||||
var human = new BarCustomer("young human", 2, callsCount);
|
var adidas = new Tenant("Adidas", 5, callsCount);
|
||||||
var dwarf = new BarCustomer("dwarf soldier", 4, callsCount);
|
var nike = new Tenant("Nike", 6, callsCount);
|
||||||
|
|
||||||
var executorService = Executors.newFixedThreadPool(2);
|
var executorService = Executors.newFixedThreadPool(2);
|
||||||
|
executorService.execute(() -> makeServiceCalls(adidas, callsCount));
|
||||||
executorService.execute(() -> makeServiceCalls(human, callsCount));
|
executorService.execute(() -> makeServiceCalls(nike, callsCount));
|
||||||
executorService.execute(() -> makeServiceCalls(dwarf, callsCount));
|
|
||||||
|
|
||||||
executorService.shutdown();
|
executorService.shutdown();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
executorService.awaitTermination(10, TimeUnit.SECONDS);
|
executorService.awaitTermination(10, TimeUnit.SECONDS);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
LOGGER.error("Executor service terminated: {}", e.getMessage());
|
LOGGER.error("Executor Service terminated: {}", e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static void makeServiceCalls(BarCustomer barCustomer, CallsCount callsCount) {
|
private static void makeServiceCalls(Tenant tenant, CallsCount callsCount) {
|
||||||
var timer = new ThrottleTimerImpl(1000, callsCount);
|
var timer = new ThrottleTimerImpl(10, callsCount);
|
||||||
var service = new Bartender(timer, callsCount);
|
var service = new B2BService(timer, callsCount);
|
||||||
// Sleep is introduced to keep the output in check and easy to view and analyze the results.
|
// Sleep is introduced to keep the output in check and easy to view and analyze the results.
|
||||||
IntStream.range(0, 50).forEach(i -> {
|
IntStream.range(0, 20).forEach(i -> {
|
||||||
service.orderDrink(barCustomer);
|
service.dummyCustomerApi(tenant);
|
||||||
try {
|
try {
|
||||||
Thread.sleep(100);
|
Thread.sleep(1);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
LOGGER.error("Thread interrupted: {}", e.getMessage());
|
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
|
## Class diagram
|
||||||
|
|
||||||
@ -214,7 +185,7 @@ An excerpt from the example's console output:
|
|||||||
|
|
||||||
The Throttling pattern should be used:
|
The Throttling pattern should be used:
|
||||||
|
|
||||||
* When service access needs to be restricted not to have high impact on the performance of the service.
|
* When a service access needs to be restricted to not have high impacts 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.
|
* When multiple clients are consuming the same service resources and restriction has to be made according to the usage per client.
|
||||||
|
|
||||||
## Credits
|
## 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
|
* 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.
|
* and meet service level agreements, even when an increase in demand places load on resources.
|
||||||
* <p>
|
* <p>
|
||||||
* In this example there is a {@link Bartender} serving beer to {@link BarCustomer}s. This is a time
|
* In this example we have ({@link App}) as the initiating point of the service. This is a time
|
||||||
* based throttling, i.e. only a certain number of calls are allowed per second.
|
* based throttling, i.e. only a certain number of calls are allowed per second.
|
||||||
* </p>
|
* </p>
|
||||||
* ({@link BarCustomer}) is the service tenant class having a name and the number of calls allowed.
|
* ({@link Tenant}) is the Tenant POJO class with which many tenants can be created ({@link
|
||||||
* ({@link Bartender}) is the service which is consumed by the tenants and is throttled.
|
* B2BService}) is the service which is consumed by the tenants and is throttled.
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class App {
|
public class App {
|
||||||
@ -50,35 +50,33 @@ public class App {
|
|||||||
*/
|
*/
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
var callsCount = new CallsCount();
|
var callsCount = new CallsCount();
|
||||||
var human = new BarCustomer("young human", 2, callsCount);
|
var adidas = new Tenant("Adidas", 5, callsCount);
|
||||||
var dwarf = new BarCustomer("dwarf soldier", 4, callsCount);
|
var nike = new Tenant("Nike", 6, callsCount);
|
||||||
|
|
||||||
var executorService = Executors.newFixedThreadPool(2);
|
var executorService = Executors.newFixedThreadPool(2);
|
||||||
|
|
||||||
executorService.execute(() -> makeServiceCalls(human, callsCount));
|
executorService.execute(() -> makeServiceCalls(adidas, callsCount));
|
||||||
executorService.execute(() -> makeServiceCalls(dwarf, callsCount));
|
executorService.execute(() -> makeServiceCalls(nike, callsCount));
|
||||||
|
|
||||||
executorService.shutdown();
|
executorService.shutdown();
|
||||||
try {
|
try {
|
||||||
if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
|
executorService.awaitTermination(10, TimeUnit.SECONDS);
|
||||||
executorService.shutdownNow();
|
|
||||||
}
|
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
executorService.shutdownNow();
|
LOGGER.error("Executor Service terminated: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make calls to the bartender.
|
* Make calls to the B2BService dummy API.
|
||||||
*/
|
*/
|
||||||
private static void makeServiceCalls(BarCustomer barCustomer, CallsCount callsCount) {
|
private static void makeServiceCalls(Tenant tenant, CallsCount callsCount) {
|
||||||
var timer = new ThrottleTimerImpl(1000, callsCount);
|
var timer = new ThrottleTimerImpl(10, callsCount);
|
||||||
var service = new Bartender(timer, callsCount);
|
var service = new B2BService(timer, callsCount);
|
||||||
// Sleep is introduced to keep the output in check and easy to view and analyze the results.
|
// Sleep is introduced to keep the output in check and easy to view and analyze the results.
|
||||||
IntStream.range(0, 50).forEach(i -> {
|
IntStream.range(0, 20).forEach(i -> {
|
||||||
service.orderDrink(barCustomer);
|
service.dummyCustomerApi(tenant);
|
||||||
try {
|
try {
|
||||||
Thread.sleep(100);
|
Thread.sleep(1);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
LOGGER.error("Thread interrupted: {}", e.getMessage());
|
LOGGER.error("Thread interrupted: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
|
@ -29,32 +29,33 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bartender is a service which accepts a BarCustomer (tenant) and throttles
|
* A service which accepts a tenant and throttles the resource based on the time given to the
|
||||||
* the resource based on the time given to the tenant.
|
* tenant.
|
||||||
*/
|
*/
|
||||||
class Bartender {
|
class B2BService {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(Bartender.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(B2BService.class);
|
||||||
private final CallsCount callsCount;
|
private final CallsCount callsCount;
|
||||||
|
|
||||||
public Bartender(Throttler timer, CallsCount callsCount) {
|
public B2BService(Throttler timer, CallsCount callsCount) {
|
||||||
this.callsCount = callsCount;
|
this.callsCount = callsCount;
|
||||||
timer.start();
|
timer.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Orders a drink from the bartender.
|
* Calls dummy customer api.
|
||||||
|
*
|
||||||
* @return customer id which is randomly generated
|
* @return customer id which is randomly generated
|
||||||
*/
|
*/
|
||||||
public int orderDrink(BarCustomer barCustomer) {
|
public int dummyCustomerApi(Tenant tenant) {
|
||||||
var tenantName = barCustomer.getName();
|
var tenantName = tenant.getName();
|
||||||
var count = callsCount.getCount(tenantName);
|
var count = callsCount.getCount(tenantName);
|
||||||
if (count >= barCustomer.getAllowedCallsPerSecond()) {
|
LOGGER.debug("Counter for {} : {} ", tenant.getName(), count);
|
||||||
LOGGER.error("I'm sorry {}, you've had enough for today!", tenantName);
|
if (count >= tenant.getAllowedCallsPerSecond()) {
|
||||||
|
LOGGER.error("API access per second limit reached for: {}", tenantName);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
callsCount.incrementCount(tenantName);
|
callsCount.incrementCount(tenantName);
|
||||||
LOGGER.debug("Serving beer to {} : [{} consumed] ", barCustomer.getName(), count + 1);
|
|
||||||
return getRandomCustomerId();
|
return getRandomCustomerId();
|
||||||
}
|
}
|
||||||
|
|
@ -69,7 +69,7 @@ public final class CallsCount {
|
|||||||
* Resets the count of all the tenants in the map.
|
* Resets the count of all the tenants in the map.
|
||||||
*/
|
*/
|
||||||
public void reset() {
|
public void reset() {
|
||||||
|
LOGGER.debug("Resetting the map.");
|
||||||
tenantCallsCount.replaceAll((k, v) -> new AtomicLong(0));
|
tenantCallsCount.replaceAll((k, v) -> new AtomicLong(0));
|
||||||
LOGGER.info("reset counters");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,26 +25,22 @@ package com.iluwatar.throttling;
|
|||||||
|
|
||||||
import java.security.InvalidParameterException;
|
import java.security.InvalidParameterException;
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BarCustomer is a tenant with a name and a number of allowed calls per second.
|
* A Pojo class to create a basic Tenant with the allowed calls per second.
|
||||||
*/
|
*/
|
||||||
public class BarCustomer {
|
public class Tenant {
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final String name;
|
private final String name;
|
||||||
@Getter
|
|
||||||
private final int allowedCallsPerSecond;
|
private final int allowedCallsPerSecond;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
* @param name Name of the BarCustomer
|
* @param name Name of the tenant
|
||||||
* @param allowedCallsPerSecond The number of calls allowed for this particular tenant.
|
* @param allowedCallsPerSecond The number of calls allowed for a particular tenant.
|
||||||
* @throws InvalidParameterException If number of calls is less than 0, throws exception.
|
* @throws InvalidParameterException If number of calls is less than 0, throws exception.
|
||||||
*/
|
*/
|
||||||
public BarCustomer(String name, int allowedCallsPerSecond, CallsCount callsCount) {
|
public Tenant(String name, int allowedCallsPerSecond, CallsCount callsCount) {
|
||||||
if (allowedCallsPerSecond < 0) {
|
if (allowedCallsPerSecond < 0) {
|
||||||
throw new InvalidParameterException("Number of calls less than 0 not allowed");
|
throw new InvalidParameterException("Number of calls less than 0 not allowed");
|
||||||
}
|
}
|
||||||
@ -52,4 +48,12 @@ public class BarCustomer {
|
|||||||
this.allowedCallsPerSecond = allowedCallsPerSecond;
|
this.allowedCallsPerSecond = allowedCallsPerSecond;
|
||||||
callsCount.addTenant(name);
|
callsCount.addTenant(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAllowedCallsPerSecond() {
|
||||||
|
return allowedCallsPerSecond;
|
||||||
|
}
|
||||||
}
|
}
|
@ -32,18 +32,19 @@ import org.junit.jupiter.api.Test;
|
|||||||
/**
|
/**
|
||||||
* B2BServiceTest class to test the B2BService
|
* B2BServiceTest class to test the B2BService
|
||||||
*/
|
*/
|
||||||
public class BartenderTest {
|
public class B2BServiceTest {
|
||||||
|
|
||||||
private final CallsCount callsCount = new CallsCount();
|
private final CallsCount callsCount = new CallsCount();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void dummyCustomerApiTest() {
|
void dummyCustomerApiTest() {
|
||||||
var tenant = new BarCustomer("pirate", 2, callsCount);
|
var tenant = new Tenant("testTenant", 2, callsCount);
|
||||||
// In order to assure that throttling limits will not be reset, we use an empty throttling implementation
|
// In order to assure that throttling limits will not be reset, we use an empty throttling implementation
|
||||||
var timer = (Throttler) () -> {};
|
var timer = (Throttler) () -> {
|
||||||
var service = new Bartender(timer, callsCount);
|
};
|
||||||
|
var service = new B2BService(timer, callsCount);
|
||||||
|
|
||||||
IntStream.range(0, 5).mapToObj(i -> tenant).forEach(service::orderDrink);
|
IntStream.range(0, 5).mapToObj(i -> tenant).forEach(service::dummyCustomerApi);
|
||||||
var counter = callsCount.getCount(tenant.getName());
|
var counter = callsCount.getCount(tenant.getName());
|
||||||
assertEquals(2, counter, "Counter limit must be reached");
|
assertEquals(2, counter, "Counter limit must be reached");
|
||||||
}
|
}
|
@ -23,21 +23,20 @@
|
|||||||
|
|
||||||
package com.iluwatar.throttling;
|
package com.iluwatar.throttling;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
import java.security.InvalidParameterException;
|
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.
|
* TenantTest to test the creation of Tenant with valid parameters.
|
||||||
*/
|
*/
|
||||||
public class BarCustomerTest {
|
public class TenantTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void constructorTest() {
|
void constructorTest() {
|
||||||
assertThrows(InvalidParameterException.class, () -> {
|
assertThrows(InvalidParameterException.class, () -> {
|
||||||
new BarCustomer("sirBrave", -1, new CallsCount());
|
new Tenant("FailTenant", -1, new CallsCount());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -17,19 +17,19 @@ and to interleave the execution of functions without hard coding them together.
|
|||||||
## Explanation
|
## Explanation
|
||||||
|
|
||||||
Recursion is a frequently adopted technique for solving algorithmic problems in a divide and conquer
|
Recursion is a frequently adopted technique for solving algorithmic problems in a divide and conquer
|
||||||
style. For example, calculating Fibonacci accumulating sum and factorials. In these kinds of
|
style. For example calculating fibonacci accumulating sum and factorials. In these kinds of problems
|
||||||
problems, recursion is more straightforward than its loop counterpart. Furthermore, recursion may
|
recursion is more straightforward than their loop counterpart. Furthermore recursion may need less
|
||||||
need less code and looks more concise. There is a saying that every recursion problem can be solved
|
code and looks more concise. There is a saying that every recursion problem can be solved using
|
||||||
using a loop with the cost of writing code that is more difficult to understand.
|
a loop with the cost of writing code that is more difficult to understand.
|
||||||
|
|
||||||
However, recursion-type solutions have one big caveat. For each recursive call, it typically needs
|
However recursion type solutions have one big caveat. For each recursive call it typically needs
|
||||||
an intermediate value stored and there is a limited amount of stack memory available. Running out of
|
an intermediate value stored and there is a limited amount of stack memory available. Running out of
|
||||||
stack memory creates a stack overflow error and halts the program execution.
|
stack memory creates a stack overflow error and halts the program execution.
|
||||||
|
|
||||||
Trampoline pattern is a trick that allows defining recursive algorithms in Java without blowing the
|
Trampoline pattern is a trick that allows us define recursive algorithms in Java without blowing the
|
||||||
stack.
|
stack.
|
||||||
|
|
||||||
Real-world example
|
Real world example
|
||||||
|
|
||||||
> A recursive Fibonacci calculation without the stack overflow problem using the Trampoline pattern.
|
> A recursive Fibonacci calculation without the stack overflow problem using the Trampoline pattern.
|
||||||
|
|
||||||
@ -105,26 +105,24 @@ public interface Trampoline<T> {
|
|||||||
Using the `Trampoline` to get Fibonacci values.
|
Using the `Trampoline` to get Fibonacci values.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public static void main(String[] args) {
|
public static Trampoline<Integer> loop(int times, int prod) {
|
||||||
LOGGER.info("Start calculating war casualties");
|
|
||||||
var result = loop(10, 1).result();
|
|
||||||
LOGGER.info("The number of orcs perished in the war: {}", result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Trampoline<Integer> loop(int times, int prod) {
|
|
||||||
if (times == 0) {
|
if (times == 0) {
|
||||||
return Trampoline.done(prod);
|
return Trampoline.done(prod);
|
||||||
} else {
|
} else {
|
||||||
return Trampoline.more(() -> loop(times - 1, prod * times));
|
return Trampoline.more(() -> loop(times - 1, prod * times));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.info("start pattern");
|
||||||
|
var result = loop(10, 1).result();
|
||||||
|
log.info("result {}", result);
|
||||||
```
|
```
|
||||||
|
|
||||||
Program output:
|
Program output:
|
||||||
|
|
||||||
```
|
```
|
||||||
19:22:24.462 [main] INFO com.iluwatar.trampoline.TrampolineApp - Start calculating war casualties
|
start pattern
|
||||||
19:22:24.472 [main] INFO com.iluwatar.trampoline.TrampolineApp - The number of orcs perished in the war: 3628800
|
result 3628800
|
||||||
```
|
```
|
||||||
|
|
||||||
## Class diagram
|
## Class diagram
|
||||||
@ -135,8 +133,8 @@ Program output:
|
|||||||
|
|
||||||
Use the Trampoline pattern when
|
Use the Trampoline pattern when
|
||||||
|
|
||||||
* For implementing tail-recursive functions. This pattern allows to switch on a stackless operation.
|
* For implementing tail recursive function. This pattern allows to switch on a stackless operation.
|
||||||
* For interleaving execution of two or more functions on the same thread.
|
* For interleaving the execution of two or more functions on the same thread.
|
||||||
|
|
||||||
## Known uses
|
## Known uses
|
||||||
|
|
||||||
|
@ -107,4 +107,6 @@ public interface Trampoline<T> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -39,9 +39,9 @@ public class TrampolineApp {
|
|||||||
* Main program for showing pattern. It does loop with factorial function.
|
* Main program for showing pattern. It does loop with factorial function.
|
||||||
*/
|
*/
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
LOGGER.info("Start calculating war casualties");
|
LOGGER.info("start pattern");
|
||||||
var result = loop(10, 1).result();
|
var result = loop(10, 1).result();
|
||||||
LOGGER.info("The number of orcs perished in the war: {}", result);
|
LOGGER.info("result {}", result);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,4 +55,5 @@ public class TrampolineApp {
|
|||||||
return Trampoline.more(() -> loop(times - 1, prod * times));
|
return Trampoline.more(() -> loop(times - 1, prod * times));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -12,20 +12,20 @@ tags:
|
|||||||
|
|
||||||
## Intent
|
## 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.
|
persisted in one go to minimize database round-trips.
|
||||||
|
|
||||||
## Explanation
|
## Explanation
|
||||||
|
|
||||||
Real-world example
|
Real world example
|
||||||
|
|
||||||
> Arms dealer has a database containing weapon information. Merchants all over the town are
|
> We have a database containing student information. Administrators all over the country are
|
||||||
> constantly updating this information and it causes a high load on the database server. To make the
|
> 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.
|
> load more manageable we apply to Unit of Work pattern to send many small updates in batches.
|
||||||
|
|
||||||
In plain words
|
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.
|
> round-trips.
|
||||||
|
|
||||||
[MartinFowler.com](https://martinfowler.com/eaaCatalog/unitOfWork.html) says
|
[MartinFowler.com](https://martinfowler.com/eaaCatalog/unitOfWork.html) says
|
||||||
@ -35,20 +35,37 @@ In plain words
|
|||||||
|
|
||||||
**Programmatic Example**
|
**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
|
```java
|
||||||
@Getter
|
public class Student {
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class Weapon {
|
|
||||||
private final Integer id;
|
private final Integer id;
|
||||||
private final String name;
|
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
|
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
|
```java
|
||||||
public interface IUnitOfWork<T> {
|
public interface IUnitOfWork<T> {
|
||||||
@ -67,117 +84,96 @@ public interface IUnitOfWork<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RequiredArgsConstructor
|
public class StudentRepository implements IUnitOfWork<Student> {
|
||||||
public class ArmsDealer implements IUnitOfWork<Weapon> {
|
|
||||||
|
|
||||||
private final Map<String, List<Weapon>> context;
|
private final Map<String, List<Student>> context;
|
||||||
private final WeaponDatabase weaponDatabase;
|
private final StudentDatabase studentDatabase;
|
||||||
|
|
||||||
@Override
|
public StudentRepository(Map<String, List<Student>> context, StudentDatabase studentDatabase) {
|
||||||
public void registerNew(Weapon weapon) {
|
this.context = context;
|
||||||
LOGGER.info("Registering {} for insert in context.", weapon.getName());
|
this.studentDatabase = studentDatabase;
|
||||||
register(weapon, UnitActions.INSERT.getActionValue());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerModified(Weapon weapon) {
|
public void registerNew(Student student) {
|
||||||
LOGGER.info("Registering {} for modify in context.", weapon.getName());
|
LOGGER.info("Registering {} for insert in context.", student.getName());
|
||||||
register(weapon, UnitActions.MODIFY.getActionValue());
|
register(student, IUnitOfWork.INSERT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerModified(Student student) {
|
||||||
|
LOGGER.info("Registering {} for modify in context.", student.getName());
|
||||||
|
register(student, IUnitOfWork.MODIFY);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerDeleted(Weapon weapon) {
|
public void registerDeleted(Student student) {
|
||||||
LOGGER.info("Registering {} for delete in context.", weapon.getName());
|
LOGGER.info("Registering {} for delete in context.", student.getName());
|
||||||
register(weapon, UnitActions.DELETE.getActionValue());
|
register(student, IUnitOfWork.DELETE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void register(Weapon weapon, String operation) {
|
private void register(Student student, String operation) {
|
||||||
var weaponsToOperate = context.get(operation);
|
var studentsToOperate = context.get(operation);
|
||||||
if (weaponsToOperate == null) {
|
if (studentsToOperate == null) {
|
||||||
weaponsToOperate = new ArrayList<>();
|
studentsToOperate = new ArrayList<>();
|
||||||
}
|
}
|
||||||
weaponsToOperate.add(weapon);
|
studentsToOperate.add(student);
|
||||||
context.put(operation, weaponsToOperate);
|
context.put(operation, studentsToOperate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* All UnitOfWork operations are batched and executed together on commit only.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void commit() {
|
public void commit() {
|
||||||
if (context == null || context.size() == 0) {
|
if (context == null || context.size() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LOGGER.info("Commit started");
|
LOGGER.info("Commit started");
|
||||||
if (context.containsKey(UnitActions.INSERT.getActionValue())) {
|
if (context.containsKey(IUnitOfWork.INSERT)) {
|
||||||
commitInsert();
|
commitInsert();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.containsKey(UnitActions.MODIFY.getActionValue())) {
|
if (context.containsKey(IUnitOfWork.MODIFY)) {
|
||||||
commitModify();
|
commitModify();
|
||||||
}
|
}
|
||||||
if (context.containsKey(UnitActions.DELETE.getActionValue())) {
|
if (context.containsKey(IUnitOfWork.DELETE)) {
|
||||||
commitDelete();
|
commitDelete();
|
||||||
}
|
}
|
||||||
LOGGER.info("Commit finished.");
|
LOGGER.info("Commit finished.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void commitInsert() {
|
private void commitInsert() {
|
||||||
var weaponsToBeInserted = context.get(UnitActions.INSERT.getActionValue());
|
var studentsToBeInserted = context.get(IUnitOfWork.INSERT);
|
||||||
for (var weapon : weaponsToBeInserted) {
|
for (var student : studentsToBeInserted) {
|
||||||
LOGGER.info("Inserting a new weapon {} to sales rack.", weapon.getName());
|
LOGGER.info("Saving {} to database.", student.getName());
|
||||||
weaponDatabase.insert(weapon);
|
studentDatabase.insert(student);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void commitModify() {
|
private void commitModify() {
|
||||||
var modifiedWeapons = context.get(UnitActions.MODIFY.getActionValue());
|
var modifiedStudents = context.get(IUnitOfWork.MODIFY);
|
||||||
for (var weapon : modifiedWeapons) {
|
for (var student : modifiedStudents) {
|
||||||
LOGGER.info("Scheduling {} for modification work.", weapon.getName());
|
LOGGER.info("Modifying {} to database.", student.getName());
|
||||||
weaponDatabase.modify(weapon);
|
studentDatabase.modify(student);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void commitDelete() {
|
private void commitDelete() {
|
||||||
var deletedWeapons = context.get(UnitActions.DELETE.getActionValue());
|
var deletedStudents = context.get(IUnitOfWork.DELETE);
|
||||||
for (var weapon : deletedWeapons) {
|
for (var student : deletedStudents) {
|
||||||
LOGGER.info("Scrapping {}.", weapon.getName());
|
LOGGER.info("Deleting {} to database.", student.getName());
|
||||||
weaponDatabase.delete(weapon);
|
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
|
```java
|
||||||
// create some weapons
|
studentRepository.registerNew(ram);
|
||||||
var enchantedHammer = new Weapon(1, "enchanted hammer");
|
studentRepository.registerModified(shyam);
|
||||||
var brokenGreatSword = new Weapon(2, "broken great sword");
|
studentRepository.registerDeleted(gopi);
|
||||||
var silverTrident = new Weapon(3, "silver trident");
|
studentRepository.commit();
|
||||||
|
|
||||||
// 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.
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Class diagram
|
## Class diagram
|
||||||
@ -190,7 +186,7 @@ Use the Unit Of Work pattern when
|
|||||||
|
|
||||||
* To optimize the time taken for database transactions.
|
* 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 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
|
## Tutorials
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<class-diagram version="1.2.1" icons="true" always-add-relationships="false" generalizations="true" realizations="true"
|
<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">
|
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">
|
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"/>
|
<position height="-1" width="-1" x="170" y="406"/>
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
<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"/>
|
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
</display>
|
</display>
|
||||||
</class>
|
</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"
|
file="/unit-of-work/src/main/java/com/iluwatar/unitofwork/StudentRepository.java" binary="false"
|
||||||
corner="BOTTOM_RIGHT">
|
corner="BOTTOM_RIGHT">
|
||||||
<position height="-1" width="-1" x="377" y="166"/>
|
<position height="-1" width="-1" x="377" y="166"/>
|
||||||
@ -38,7 +38,7 @@
|
|||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
</display>
|
</display>
|
||||||
</class>
|
</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">
|
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"/>
|
<position height="-1" width="-1" x="696" y="130"/>
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||||
|
@ -27,7 +27,7 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link App} Application demonstrating unit of work pattern.
|
* {@link App} Application for managing student data.
|
||||||
*/
|
*/
|
||||||
public class App {
|
public class App {
|
||||||
/**
|
/**
|
||||||
@ -37,19 +37,17 @@ public class App {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
// create some weapons
|
var ram = new Student(1, "Ram", "Street 9, Cupertino");
|
||||||
var enchantedHammer = new Weapon(1, "enchanted hammer");
|
var shyam = new Student(2, "Shyam", "Z bridge, Pune");
|
||||||
var brokenGreatSword = new Weapon(2, "broken great sword");
|
var gopi = new Student(3, "Gopi", "Street 10, Mumbai");
|
||||||
var silverTrident = new Weapon(3, "silver trident");
|
|
||||||
|
|
||||||
// create repository
|
var context = new HashMap<String, List<Student>>();
|
||||||
var weaponRepository = new ArmsDealer(new HashMap<String, List<Weapon>>(),
|
var studentDatabase = new StudentDatabase();
|
||||||
new WeaponDatabase());
|
var studentRepository = new StudentRepository(context, studentDatabase);
|
||||||
|
|
||||||
// perform operations on the weapons
|
studentRepository.registerNew(ram);
|
||||||
weaponRepository.registerNew(enchantedHammer);
|
studentRepository.registerModified(shyam);
|
||||||
weaponRepository.registerModified(silverTrident);
|
studentRepository.registerDeleted(gopi);
|
||||||
weaponRepository.registerDeleted(brokenGreatSword);
|
studentRepository.commit();
|
||||||
weaponRepository.commit();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,12 +27,14 @@ import lombok.Getter;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link Weapon} is an entity.
|
* {@link Student} is an entity.
|
||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class Weapon {
|
public class Student {
|
||||||
|
|
||||||
private final Integer id;
|
private final Integer id;
|
||||||
private final String name;
|
private final String name;
|
||||||
|
private final String address;
|
||||||
|
|
||||||
}
|
}
|
@ -24,19 +24,19 @@
|
|||||||
package com.iluwatar.unitofwork;
|
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
|
//Some insert logic to DB
|
||||||
}
|
}
|
||||||
|
|
||||||
public void modify(Weapon weapon) {
|
public void modify(Student student) {
|
||||||
//Some modify logic to DB
|
//Some modify logic to DB
|
||||||
}
|
}
|
||||||
|
|
||||||
public void delete(Weapon weapon) {
|
public void delete(Student student) {
|
||||||
//Some delete logic to DB
|
//Some delete logic to DB
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -30,41 +30,41 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
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
|
@Slf4j
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class ArmsDealer implements IUnitOfWork<Weapon> {
|
public class StudentRepository implements IUnitOfWork<Student> {
|
||||||
|
|
||||||
private final Map<String, List<Weapon>> context;
|
private final Map<String, List<Student>> context;
|
||||||
private final WeaponDatabase weaponDatabase;
|
private final StudentDatabase studentDatabase;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerNew(Weapon weapon) {
|
public void registerNew(Student student) {
|
||||||
LOGGER.info("Registering {} for insert in context.", weapon.getName());
|
LOGGER.info("Registering {} for insert in context.", student.getName());
|
||||||
register(weapon, UnitActions.INSERT.getActionValue());
|
register(student, UnitActions.INSERT.getActionValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerModified(Weapon weapon) {
|
public void registerModified(Student student) {
|
||||||
LOGGER.info("Registering {} for modify in context.", weapon.getName());
|
LOGGER.info("Registering {} for modify in context.", student.getName());
|
||||||
register(weapon, UnitActions.MODIFY.getActionValue());
|
register(student, UnitActions.MODIFY.getActionValue());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerDeleted(Weapon weapon) {
|
public void registerDeleted(Student student) {
|
||||||
LOGGER.info("Registering {} for delete in context.", weapon.getName());
|
LOGGER.info("Registering {} for delete in context.", student.getName());
|
||||||
register(weapon, UnitActions.DELETE.getActionValue());
|
register(student, UnitActions.DELETE.getActionValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void register(Weapon weapon, String operation) {
|
private void register(Student student, String operation) {
|
||||||
var weaponsToOperate = context.get(operation);
|
var studentsToOperate = context.get(operation);
|
||||||
if (weaponsToOperate == null) {
|
if (studentsToOperate == null) {
|
||||||
weaponsToOperate = new ArrayList<>();
|
studentsToOperate = new ArrayList<>();
|
||||||
}
|
}
|
||||||
weaponsToOperate.add(weapon);
|
studentsToOperate.add(student);
|
||||||
context.put(operation, weaponsToOperate);
|
context.put(operation, studentsToOperate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -90,26 +90,26 @@ public class ArmsDealer implements IUnitOfWork<Weapon> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void commitInsert() {
|
private void commitInsert() {
|
||||||
var weaponsToBeInserted = context.get(UnitActions.INSERT.getActionValue());
|
var studentsToBeInserted = context.get(UnitActions.INSERT.getActionValue());
|
||||||
for (var weapon : weaponsToBeInserted) {
|
for (var student : studentsToBeInserted) {
|
||||||
LOGGER.info("Inserting a new weapon {} to sales rack.", weapon.getName());
|
LOGGER.info("Saving {} to database.", student.getName());
|
||||||
weaponDatabase.insert(weapon);
|
studentDatabase.insert(student);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void commitModify() {
|
private void commitModify() {
|
||||||
var modifiedWeapons = context.get(UnitActions.MODIFY.getActionValue());
|
var modifiedStudents = context.get(UnitActions.MODIFY.getActionValue());
|
||||||
for (var weapon : modifiedWeapons) {
|
for (var student : modifiedStudents) {
|
||||||
LOGGER.info("Scheduling {} for modification work.", weapon.getName());
|
LOGGER.info("Modifying {} to database.", student.getName());
|
||||||
weaponDatabase.modify(weapon);
|
studentDatabase.modify(student);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void commitDelete() {
|
private void commitDelete() {
|
||||||
var deletedWeapons = context.get(UnitActions.DELETE.getActionValue());
|
var deletedStudents = context.get(UnitActions.DELETE.getActionValue());
|
||||||
for (var weapon : deletedWeapons) {
|
for (var student : deletedStudents) {
|
||||||
LOGGER.info("Scrapping {}.", weapon.getName());
|
LOGGER.info("Deleting {} to database.", student.getName());
|
||||||
weaponDatabase.delete(weapon);
|
studentDatabase.delete(student);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -36,102 +36,102 @@ import java.util.Map;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* tests {@link ArmsDealer}
|
* tests {@link StudentRepository}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class ArmsDealerTest {
|
class StudentRepositoryTest {
|
||||||
private final Weapon weapon1 = new Weapon(1, "battle ram");
|
private final Student student1 = new Student(1, "Ram", "street 9, cupertino");
|
||||||
private final Weapon weapon2 = new Weapon(1, "wooden lance");
|
private final Student student2 = new Student(1, "Sham", "Z bridge, pune");
|
||||||
|
|
||||||
private final Map<String, List<Weapon>> context = new HashMap<>();
|
private final Map<String, List<Student>> context = new HashMap<>();
|
||||||
private final WeaponDatabase weaponDatabase = mock(WeaponDatabase.class);
|
private final StudentDatabase studentDatabase = mock(StudentDatabase.class);
|
||||||
private final ArmsDealer armsDealer = new ArmsDealer(context, weaponDatabase);;
|
private final StudentRepository studentRepository = new StudentRepository(context, studentDatabase);;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldSaveNewStudentWithoutWritingToDb() {
|
void shouldSaveNewStudentWithoutWritingToDb() {
|
||||||
armsDealer.registerNew(weapon1);
|
studentRepository.registerNew(student1);
|
||||||
armsDealer.registerNew(weapon2);
|
studentRepository.registerNew(student2);
|
||||||
|
|
||||||
assertEquals(2, context.get(UnitActions.INSERT.getActionValue()).size());
|
assertEquals(2, context.get(UnitActions.INSERT.getActionValue()).size());
|
||||||
verifyNoMoreInteractions(weaponDatabase);
|
verifyNoMoreInteractions(studentDatabase);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldSaveDeletedStudentWithoutWritingToDb() {
|
void shouldSaveDeletedStudentWithoutWritingToDb() {
|
||||||
armsDealer.registerDeleted(weapon1);
|
studentRepository.registerDeleted(student1);
|
||||||
armsDealer.registerDeleted(weapon2);
|
studentRepository.registerDeleted(student2);
|
||||||
|
|
||||||
assertEquals(2, context.get(UnitActions.DELETE.getActionValue()).size());
|
assertEquals(2, context.get(UnitActions.DELETE.getActionValue()).size());
|
||||||
verifyNoMoreInteractions(weaponDatabase);
|
verifyNoMoreInteractions(studentDatabase);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldSaveModifiedStudentWithoutWritingToDb() {
|
void shouldSaveModifiedStudentWithoutWritingToDb() {
|
||||||
armsDealer.registerModified(weapon1);
|
studentRepository.registerModified(student1);
|
||||||
armsDealer.registerModified(weapon2);
|
studentRepository.registerModified(student2);
|
||||||
|
|
||||||
assertEquals(2, context.get(UnitActions.MODIFY.getActionValue()).size());
|
assertEquals(2, context.get(UnitActions.MODIFY.getActionValue()).size());
|
||||||
verifyNoMoreInteractions(weaponDatabase);
|
verifyNoMoreInteractions(studentDatabase);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldSaveAllLocalChangesToDb() {
|
void shouldSaveAllLocalChangesToDb() {
|
||||||
context.put(UnitActions.INSERT.getActionValue(), List.of(weapon1));
|
context.put(UnitActions.INSERT.getActionValue(), List.of(student1));
|
||||||
context.put(UnitActions.MODIFY.getActionValue(), List.of(weapon1));
|
context.put(UnitActions.MODIFY.getActionValue(), List.of(student1));
|
||||||
context.put(UnitActions.DELETE.getActionValue(), List.of(weapon1));
|
context.put(UnitActions.DELETE.getActionValue(), List.of(student1));
|
||||||
|
|
||||||
armsDealer.commit();
|
studentRepository.commit();
|
||||||
|
|
||||||
verify(weaponDatabase, times(1)).insert(weapon1);
|
verify(studentDatabase, times(1)).insert(student1);
|
||||||
verify(weaponDatabase, times(1)).modify(weapon1);
|
verify(studentDatabase, times(1)).modify(student1);
|
||||||
verify(weaponDatabase, times(1)).delete(weapon1);
|
verify(studentDatabase, times(1)).delete(student1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldNotWriteToDbIfContextIsNull() {
|
void shouldNotWriteToDbIfContextIsNull() {
|
||||||
var weaponRepository = new ArmsDealer(null, weaponDatabase);
|
var studentRepository = new StudentRepository(null, studentDatabase);
|
||||||
|
|
||||||
weaponRepository.commit();
|
studentRepository.commit();
|
||||||
|
|
||||||
verifyNoMoreInteractions(weaponDatabase);
|
verifyNoMoreInteractions(studentDatabase);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldNotWriteToDbIfNothingToCommit() {
|
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
|
@Test
|
||||||
void shouldNotInsertToDbIfNoRegisteredStudentsToBeCommitted() {
|
void shouldNotInsertToDbIfNoRegisteredStudentsToBeCommitted() {
|
||||||
context.put(UnitActions.MODIFY.getActionValue(), List.of(weapon1));
|
context.put(UnitActions.MODIFY.getActionValue(), List.of(student1));
|
||||||
context.put(UnitActions.DELETE.getActionValue(), List.of(weapon1));
|
context.put(UnitActions.DELETE.getActionValue(), List.of(student1));
|
||||||
|
|
||||||
armsDealer.commit();
|
studentRepository.commit();
|
||||||
|
|
||||||
verify(weaponDatabase, never()).insert(weapon1);
|
verify(studentDatabase, never()).insert(student1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldNotModifyToDbIfNotRegisteredStudentsToBeCommitted() {
|
void shouldNotModifyToDbIfNotRegisteredStudentsToBeCommitted() {
|
||||||
context.put(UnitActions.INSERT.getActionValue(), List.of(weapon1));
|
context.put(UnitActions.INSERT.getActionValue(), List.of(student1));
|
||||||
context.put(UnitActions.DELETE.getActionValue(), List.of(weapon1));
|
context.put(UnitActions.DELETE.getActionValue(), List.of(student1));
|
||||||
|
|
||||||
armsDealer.commit();
|
studentRepository.commit();
|
||||||
|
|
||||||
verify(weaponDatabase, never()).modify(weapon1);
|
verify(studentDatabase, never()).modify(student1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldNotDeleteFromDbIfNotRegisteredStudentsToBeCommitted() {
|
void shouldNotDeleteFromDbIfNotRegisteredStudentsToBeCommitted() {
|
||||||
context.put(UnitActions.INSERT.getActionValue(), List.of(weapon1));
|
context.put(UnitActions.INSERT.getActionValue(), List.of(student1));
|
||||||
context.put(UnitActions.MODIFY.getActionValue(), List.of(weapon1));
|
context.put(UnitActions.MODIFY.getActionValue(), List.of(student1));
|
||||||
|
|
||||||
armsDealer.commit();
|
studentRepository.commit();
|
||||||
|
|
||||||
verify(weaponDatabase, never()).delete(weapon1);
|
verify(studentDatabase, never()).delete(student1);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -10,80 +10,19 @@ tags:
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Intent
|
## Intent
|
||||||
|
|
||||||
Provide objects which follow value semantics rather than reference semantics.
|
Provide objects which follow value semantics rather than reference semantics.
|
||||||
This means value objects' equality is not based on identity. Two value objects are
|
This means value objects' equality are not based on identity. Two value objects are
|
||||||
equal when they have the same value, not necessarily being the same object.
|
equal when they have the same value, not necessarily being the same object.
|
||||||
|
|
||||||
## Explanation
|
|
||||||
|
|
||||||
Real-world example
|
|
||||||
|
|
||||||
> There is a class for hero statistics in a role-playing game. The statistics contain attributes
|
|
||||||
> such as strength, intelligence, and luck. The statistics of different heroes should be equal
|
|
||||||
> when all the attributes are equal.
|
|
||||||
|
|
||||||
In plain words
|
|
||||||
|
|
||||||
> Value objects are equal when their attributes have the same value
|
|
||||||
|
|
||||||
Wikipedia says
|
|
||||||
|
|
||||||
> In computer science, a value object is a small object that represents a simple entity whose
|
|
||||||
> equality is not based on identity: i.e. two value objects are equal when they have the same
|
|
||||||
> value, not necessarily being the same object.
|
|
||||||
|
|
||||||
**Programmatic Example**
|
|
||||||
|
|
||||||
Here is the `HeroStat` class that is the value object. Notice the use of
|
|
||||||
[Lombok's `@Value`](https://projectlombok.org/features/Value) annotation.
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Value(staticConstructor = "valueOf")
|
|
||||||
class HeroStat {
|
|
||||||
|
|
||||||
int strength;
|
|
||||||
int intelligence;
|
|
||||||
int luck;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The example creates three different `HeroStat`s and compares their equality.
|
|
||||||
|
|
||||||
```java
|
|
||||||
var statA = HeroStat.valueOf(10, 5, 0);
|
|
||||||
var statB = HeroStat.valueOf(10, 5, 0);
|
|
||||||
var statC = HeroStat.valueOf(5, 1, 8);
|
|
||||||
|
|
||||||
LOGGER.info(statA.toString());
|
|
||||||
LOGGER.info(statB.toString());
|
|
||||||
LOGGER.info(statC.toString());
|
|
||||||
|
|
||||||
LOGGER.info("Is statA and statB equal : {}", statA.equals(statB));
|
|
||||||
LOGGER.info("Is statA and statC equal : {}", statA.equals(statC));
|
|
||||||
```
|
|
||||||
|
|
||||||
Here's the console output.
|
|
||||||
|
|
||||||
```
|
|
||||||
20:11:12.199 [main] INFO com.iluwatar.value.object.App - HeroStat(strength=10, intelligence=5, luck=0)
|
|
||||||
20:11:12.202 [main] INFO com.iluwatar.value.object.App - HeroStat(strength=10, intelligence=5, luck=0)
|
|
||||||
20:11:12.202 [main] INFO com.iluwatar.value.object.App - HeroStat(strength=5, intelligence=1, luck=8)
|
|
||||||
20:11:12.202 [main] INFO com.iluwatar.value.object.App - Is statA and statB equal : true
|
|
||||||
20:11:12.203 [main] INFO com.iluwatar.value.object.App - Is statA and statC equal : false
|
|
||||||
```
|
|
||||||
|
|
||||||
## Class diagram
|
## Class diagram
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Applicability
|
## Applicability
|
||||||
|
|
||||||
Use the Value Object when
|
Use the Value Object when
|
||||||
|
|
||||||
* The object's equality needs to be based on the object's value
|
* You need to measure the objects' equality based on the objects' value
|
||||||
|
|
||||||
## Known uses
|
## Real world examples
|
||||||
|
|
||||||
* [java.util.Optional](https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html)
|
* [java.util.Optional](https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html)
|
||||||
* [java.time.LocalDate](https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html)
|
* [java.time.LocalDate](https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html)
|
||||||
@ -92,7 +31,6 @@ Use the Value Object when
|
|||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
* [Patterns of Enterprise Application Architecture](http://www.martinfowler.com/books/eaa.html)
|
* [Patterns of Enterprise Application Architecture](http://www.martinfowler.com/books/eaa.html)
|
||||||
* [ValueObject](https://martinfowler.com/bliki/ValueObject.html)
|
|
||||||
* [VALJOs - Value Java Objects : Stephen Colebourne's blog](http://blog.joda.org/2014/03/valjos-value-java-objects.html)
|
* [VALJOs - Value Java Objects : Stephen Colebourne's blog](http://blog.joda.org/2014/03/valjos-value-java-objects.html)
|
||||||
* [Value Object : Wikipedia](https://en.wikipedia.org/wiki/Value_object)
|
* [Value Object : Wikipedia](https://en.wikipedia.org/wiki/Value_object)
|
||||||
* [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=f27d2644fbe5026ea448791a8ad09c94)
|
* [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=f27d2644fbe5026ea448791a8ad09c94)
|
||||||
|
@ -43,7 +43,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
public class App {
|
public class App {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This example creates three HeroStats (value objects) and checks equality between those.
|
* This practice creates three HeroStats(Value object) and checks equality between those.
|
||||||
*/
|
*/
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
var statA = HeroStat.valueOf(10, 5, 0);
|
var statA = HeroStat.valueOf(10, 5, 0);
|
||||||
@ -51,8 +51,6 @@ public class App {
|
|||||||
var statC = HeroStat.valueOf(5, 1, 8);
|
var statC = HeroStat.valueOf(5, 1, 8);
|
||||||
|
|
||||||
LOGGER.info(statA.toString());
|
LOGGER.info(statA.toString());
|
||||||
LOGGER.info(statB.toString());
|
|
||||||
LOGGER.info(statC.toString());
|
|
||||||
|
|
||||||
LOGGER.info("Is statA and statB equal : {}", statA.equals(statB));
|
LOGGER.info("Is statA and statB equal : {}", statA.equals(statB));
|
||||||
LOGGER.info("Is statA and statC equal : {}", statA.equals(statC));
|
LOGGER.info("Is statA and statC equal : {}", statA.equals(statC));
|
||||||
|
@ -23,7 +23,10 @@
|
|||||||
|
|
||||||
package com.iluwatar.value.object;
|
package com.iluwatar.value.object;
|
||||||
|
|
||||||
import lombok.Value;
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HeroStat is a value object.
|
* HeroStat is a value object.
|
||||||
@ -32,10 +35,23 @@ import lombok.Value;
|
|||||||
* http://docs.oracle.com/javase/8/docs/api/java/lang/doc-files/ValueBased.html
|
* http://docs.oracle.com/javase/8/docs/api/java/lang/doc-files/ValueBased.html
|
||||||
* </a>
|
* </a>
|
||||||
*/
|
*/
|
||||||
@Value(staticConstructor = "valueOf")
|
@Getter
|
||||||
class HeroStat {
|
@ToString
|
||||||
|
@EqualsAndHashCode
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class HeroStat {
|
||||||
|
|
||||||
|
// Stats for a hero
|
||||||
|
|
||||||
|
private final int strength;
|
||||||
|
private final int intelligence;
|
||||||
|
private final int luck;
|
||||||
|
|
||||||
|
// Static factory method to create new instances.
|
||||||
|
public static HeroStat valueOf(int strength, int intelligence, int luck) {
|
||||||
|
return new HeroStat(strength, intelligence, luck);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The clone() method should not be public. Just don't override it.
|
||||||
|
|
||||||
int strength;
|
|
||||||
int intelligence;
|
|
||||||
int luck;
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user