Compare commits
4 Commits
all-contri
...
code-clean
Author | SHA1 | Date | |
---|---|---|---|
fdab5318f3 | |||
a68edf0f3d | |||
0f35681674 | |||
981985531d |
@ -1038,117 +1038,6 @@
|
||||
"contributions": [
|
||||
"translation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "charlesfinley",
|
||||
"name": "Matt Dolan",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/6307904?v=4",
|
||||
"profile": "https://github.com/charlesfinley",
|
||||
"contributions": [
|
||||
"code",
|
||||
"review"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "MananS77",
|
||||
"name": "Manan",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/21033516?v=4",
|
||||
"profile": "https://github.com/MananS77",
|
||||
"contributions": [
|
||||
"review"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "nishant",
|
||||
"name": "Nishant Arora",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/15331971?v=4",
|
||||
"profile": "https://github.com/nishant",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "raja-peeyush-kumar-singh",
|
||||
"name": "Peeyush",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/5496024?v=4",
|
||||
"profile": "https://github.com/raja-peeyush-kumar-singh",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ravening",
|
||||
"name": "Rakesh",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/10645273?v=4",
|
||||
"profile": "https://github.com/ravening",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "vINCENT8888801",
|
||||
"name": "Wei Seng",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/8037883?v=4",
|
||||
"profile": "https://github.com/vINCENT8888801",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ashishtrivedi16",
|
||||
"name": "Ashish Trivedi",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/23194128?v=4",
|
||||
"profile": "https://www.linkedin.com/in/ashish-trivedi-218379135/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "RayYH",
|
||||
"name": "洪月阳",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/41055099?v=4",
|
||||
"profile": "https://rayyounghong.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "xdvrx1",
|
||||
"name": "xdvrx1",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/47092464?v=4",
|
||||
"profile": "https://xdvrx1.github.io/",
|
||||
"contributions": [
|
||||
"review",
|
||||
"ideas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ohbus",
|
||||
"name": "Subhrodip Mohanta",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/13291222?v=4",
|
||||
"profile": "http://subho.xyz",
|
||||
"contributions": [
|
||||
"code",
|
||||
"review"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "nahteb",
|
||||
"name": "Bethan Palmer",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/13121570?v=4",
|
||||
"profile": "https://github.com/nahteb",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ToxicDreamz",
|
||||
"name": "Toxic Dreamz",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/45225562?v=4",
|
||||
"profile": "https://github.com/ToxicDreamz",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 4,
|
||||
|
57
.github/workflows/maven-pr-builder.yml
vendored
57
.github/workflows/maven-pr-builder.yml
vendored
@ -1,57 +0,0 @@
|
||||
#
|
||||
# The MIT License
|
||||
# Copyright © 2014-2019 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.
|
||||
#
|
||||
|
||||
# This workflow will build a Java project with Maven
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
|
||||
|
||||
name: Java PR Builder
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-18.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-maven-
|
||||
# Some tests need screen access
|
||||
- name: Install xvfb
|
||||
run: sudo apt-get install xvfb
|
||||
# SonarQube scan does not work for forked repositories
|
||||
# See https://jira.sonarsource.com/browse/MMF-1371
|
||||
- name: Build with Maven
|
||||
if: github.ref != 'refs/heads/master'
|
||||
run: xvfb-run mvn clean verify
|
@ -24,11 +24,13 @@
|
||||
# This workflow will build a Java project with Maven
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
|
||||
|
||||
name: Java CI
|
||||
name: Java CI with Maven
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@ -41,12 +43,6 @@ jobs:
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-maven-
|
||||
# Some tests need screen access
|
||||
- name: Install xvfb
|
||||
run: sudo apt-get install xvfb
|
20
README.md
20
README.md
@ -9,7 +9,7 @@
|
||||
[](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
[](#contributors-)
|
||||
[](#contributors-)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
|
||||
# Introduction
|
||||
@ -239,24 +239,6 @@ This project is licensed under the terms of the MIT license.
|
||||
<tr>
|
||||
<td align="center"><a href="https://webpro.nl"><img src="https://avatars1.githubusercontent.com/u/456426?v=4" width="100px;" alt=""/><br /><sub><b>Lars Kappert</b></sub></a><br /><a href="#content-webpro" title="Content">🖋</a></td>
|
||||
<td align="center"><a href="https://xiaod.info"><img src="https://avatars2.githubusercontent.com/u/21277644?v=4" width="100px;" alt=""/><br /><sub><b>Mike Liu</b></sub></a><br /><a href="#translation-xiaod-dev" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/charlesfinley"><img src="https://avatars1.githubusercontent.com/u/6307904?v=4" width="100px;" alt=""/><br /><sub><b>Matt Dolan</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=charlesfinley" title="Code">💻</a> <a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3Acharlesfinley" title="Reviewed Pull Requests">👀</a></td>
|
||||
<td align="center"><a href="https://github.com/MananS77"><img src="https://avatars3.githubusercontent.com/u/21033516?v=4" width="100px;" alt=""/><br /><sub><b>Manan</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3AMananS77" title="Reviewed Pull Requests">👀</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/nishant"><img src="https://avatars2.githubusercontent.com/u/15331971?v=4" width="100px;" alt=""/><br /><sub><b>Nishant Arora</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=nishant" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/raja-peeyush-kumar-singh"><img src="https://avatars0.githubusercontent.com/u/5496024?v=4" width="100px;" alt=""/><br /><sub><b>Peeyush</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=raja-peeyush-kumar-singh" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/ravening"><img src="https://avatars1.githubusercontent.com/u/10645273?v=4" width="100px;" alt=""/><br /><sub><b>Rakesh</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=ravening" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/vINCENT8888801"><img src="https://avatars0.githubusercontent.com/u/8037883?v=4" width="100px;" alt=""/><br /><sub><b>Wei Seng</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=vINCENT8888801" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/ashish-trivedi-218379135/"><img src="https://avatars3.githubusercontent.com/u/23194128?v=4" width="100px;" alt=""/><br /><sub><b>Ashish Trivedi</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=ashishtrivedi16" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://rayyounghong.com"><img src="https://avatars1.githubusercontent.com/u/41055099?v=4" width="100px;" alt=""/><br /><sub><b>洪月阳</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=RayYH" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://xdvrx1.github.io/"><img src="https://avatars0.githubusercontent.com/u/47092464?v=4" width="100px;" alt=""/><br /><sub><b>xdvrx1</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3Axdvrx1" title="Reviewed Pull Requests">👀</a> <a href="#ideas-xdvrx1" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center"><a href="http://subho.xyz"><img src="https://avatars0.githubusercontent.com/u/13291222?v=4" width="100px;" alt=""/><br /><sub><b>Subhrodip Mohanta</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=ohbus" title="Code">💻</a> <a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3Aohbus" title="Reviewed Pull Requests">👀</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/nahteb"><img src="https://avatars3.githubusercontent.com/u/13121570?v=4" width="100px;" alt=""/><br /><sub><b>Bethan Palmer</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=nahteb" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/ToxicDreamz"><img src="https://avatars0.githubusercontent.com/u/45225562?v=4" width="100px;" alt=""/><br /><sub><b>Toxic Dreamz</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=ToxicDreamz" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
@ -9,182 +9,21 @@ tags:
|
||||
---
|
||||
|
||||
## Intent
|
||||
|
||||
Use dynamic properties and achieve flexibility of untyped languages while keeping type-safety.
|
||||
|
||||
## Explanation
|
||||
|
||||
The Abstract Document pattern enables handling additional, non-static properties. This pattern
|
||||
uses concept of traits to enable type safety and separate properties of different classes into
|
||||
set of interfaces.
|
||||
|
||||
Real world example
|
||||
|
||||
> Consider a car that consists of multiple parts. However we don't know if the specific car really has all the parts, or just some of them. Our cars are dynamic and extremely flexible.
|
||||
|
||||
In plain words
|
||||
|
||||
> Abstract Document pattern allows attaching properties to objects without them knowing about it.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> An object-oriented structural design pattern for organizing objects in loosely typed key-value stores and exposing
|
||||
the data using typed views. The purpose of the pattern is to achieve a high degree of flexibility between components
|
||||
in a strongly typed language where new properties can be added to the object-tree on the fly, without losing the
|
||||
support of type-safety. The pattern makes use of traits to separate different properties of a class into different
|
||||
interfaces.
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
Let's first define the base classes `Document` and `AbstractDocument`. They basically make the object hold a property
|
||||
map and any amount of child objects.
|
||||
|
||||
```java
|
||||
public interface Document {
|
||||
|
||||
Void put(String key, Object value);
|
||||
|
||||
Object get(String key);
|
||||
|
||||
<T> Stream<T> children(String key, Function<Map<String, Object>, T> constructor);
|
||||
}
|
||||
|
||||
public abstract class AbstractDocument implements Document {
|
||||
|
||||
private final Map<String, Object> properties;
|
||||
|
||||
protected AbstractDocument(Map<String, Object> properties) {
|
||||
Objects.requireNonNull(properties, "properties map is required");
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void put(String key, Object value) {
|
||||
properties.put(key, value);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(String key) {
|
||||
return properties.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Stream<T> children(String key, Function<Map<String, Object>, T> constructor) {
|
||||
return Stream.ofNullable(get(key))
|
||||
.filter(Objects::nonNull)
|
||||
.map(el -> (List<Map<String, Object>>) el)
|
||||
.findAny()
|
||||
.stream()
|
||||
.flatMap(Collection::stream)
|
||||
.map(constructor);
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
Next we define an enum `Property` and a set of interfaces for type, price, model and parts. This allows us to create
|
||||
static looking interface to our `Car` class.
|
||||
|
||||
```java
|
||||
public enum Property {
|
||||
|
||||
PARTS, TYPE, PRICE, MODEL
|
||||
}
|
||||
|
||||
public interface HasType extends Document {
|
||||
|
||||
default Optional<String> getType() {
|
||||
return Optional.ofNullable((String) get(Property.TYPE.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
public interface HasPrice extends Document {
|
||||
|
||||
default Optional<Number> getPrice() {
|
||||
return Optional.ofNullable((Number) get(Property.PRICE.toString()));
|
||||
}
|
||||
}
|
||||
public interface HasModel extends Document {
|
||||
|
||||
default Optional<String> getModel() {
|
||||
return Optional.ofNullable((String) get(Property.MODEL.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
public interface HasParts extends Document {
|
||||
|
||||
default Stream<Part> getParts() {
|
||||
return children(Property.PARTS.toString(), Part::new);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now we are ready to introduce the `Car`.
|
||||
|
||||
```java
|
||||
public class Car extends AbstractDocument implements HasModel, HasPrice, HasParts {
|
||||
|
||||
public Car(Map<String, Object> properties) {
|
||||
super(properties);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And finally here's how we construct and use the `Car` in a full example.
|
||||
|
||||
```java
|
||||
LOGGER.info("Constructing parts and car");
|
||||
|
||||
var wheelProperties = Map.of(
|
||||
Property.TYPE.toString(), "wheel",
|
||||
Property.MODEL.toString(), "15C",
|
||||
Property.PRICE.toString(), 100L);
|
||||
|
||||
var doorProperties = Map.of(
|
||||
Property.TYPE.toString(), "door",
|
||||
Property.MODEL.toString(), "Lambo",
|
||||
Property.PRICE.toString(), 300L);
|
||||
|
||||
var carProperties = Map.of(
|
||||
Property.MODEL.toString(), "300SL",
|
||||
Property.PRICE.toString(), 10000L,
|
||||
Property.PARTS.toString(), List.of(wheelProperties, doorProperties));
|
||||
|
||||
var car = new Car(carProperties);
|
||||
|
||||
LOGGER.info("Here is our car:");
|
||||
LOGGER.info("-> model: {}", car.getModel().orElseThrow());
|
||||
LOGGER.info("-> price: {}", car.getPrice().orElseThrow());
|
||||
LOGGER.info("-> parts: ");
|
||||
car.getParts().forEach(p -> LOGGER.info("\t{}/{}/{}",
|
||||
p.getType().orElse(null),
|
||||
p.getModel().orElse(null),
|
||||
p.getPrice().orElse(null))
|
||||
);
|
||||
|
||||
// Constructing parts and car
|
||||
// Here is our car:
|
||||
// model: 300SL
|
||||
// price: 10000
|
||||
// parts:
|
||||
// wheel/15C/100
|
||||
// door/Lambo/300
|
||||
```
|
||||
Achieve flexibility of untyped languages and keep the type-safety
|
||||
|
||||
## Class diagram
|
||||
|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
## Applicability
|
||||
Use the Abstract Document Pattern when
|
||||
|
||||
* There is a need to add new properties on the fly
|
||||
* You want a flexible way to organize domain in tree like structure
|
||||
* You want more loosely coupled system
|
||||
* there is a need to add new properties on the fly
|
||||
* you want a flexible way to organize domain in tree like structure
|
||||
* you want more loosely coupled system
|
||||
|
||||
|
||||
## Credits
|
||||
|
||||
* [Wikipedia: Abstract Document Pattern](https://en.wikipedia.org/wiki/Abstract_Document_Pattern)
|
||||
* [Martin Fowler: Dealing with properties](http://martinfowler.com/apsupp/properties.pdf)
|
||||
* [Pattern-Oriented Software Architecture Volume 4: A Pattern Language for Distributed Computing (v. 4)](https://www.amazon.com/gp/product/0470059028/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0470059028&linkId=e3aacaea7017258acf184f9f3283b492)
|
||||
|
@ -43,11 +43,9 @@ public class App {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
||||
|
||||
/**
|
||||
* Program entry point.
|
||||
*
|
||||
* @param args command line args
|
||||
* Executes the App.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
public App() {
|
||||
LOGGER.info("Constructing parts and car");
|
||||
|
||||
var wheelProperties = Map.of(
|
||||
@ -77,4 +75,14 @@ public class App {
|
||||
p.getPrice().orElse(null))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Program entry point.
|
||||
*
|
||||
* @param args command line args
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
new App();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ import java.util.stream.Stream;
|
||||
*/
|
||||
public interface HasParts extends Document {
|
||||
|
||||
|
||||
default Stream<Part> getParts() {
|
||||
return children(Property.PARTS.toString(), Part::new);
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ import java.util.Optional;
|
||||
*/
|
||||
public interface HasPrice extends Document {
|
||||
|
||||
|
||||
default Optional<Number> getPrice() {
|
||||
return Optional.ofNullable((Number) get(Property.PRICE.toString()));
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ import java.util.Optional;
|
||||
*/
|
||||
public interface HasType extends Document {
|
||||
|
||||
|
||||
default Optional<String> getType() {
|
||||
return Optional.ofNullable((String) get(Property.TYPE.toString()));
|
||||
}
|
||||
|
@ -21,20 +21,6 @@
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.unitofwork;
|
||||
|
||||
public enum UnitActions {
|
||||
INSERT("INSERT"),
|
||||
DELETE("DELETE"),
|
||||
MODIFY("MODIFY");
|
||||
|
||||
private final String actionValue;
|
||||
|
||||
UnitActions(String actionValue) {
|
||||
this.actionValue = actionValue;
|
||||
}
|
||||
|
||||
public String getActionValue() {
|
||||
return actionValue;
|
||||
}
|
||||
}
|
||||
module com.iluwatar.abstractdocument {
|
||||
requires org.slf4j;
|
||||
}
|
@ -40,7 +40,7 @@ public class AbstractDocumentTest {
|
||||
private static final String KEY = "key";
|
||||
private static final String VALUE = "value";
|
||||
|
||||
private static class DocumentImplementation extends AbstractDocument {
|
||||
private class DocumentImplementation extends AbstractDocument {
|
||||
|
||||
DocumentImplementation(Map<String, Object> properties) {
|
||||
super(properties);
|
||||
|
@ -25,23 +25,14 @@ package com.iluwatar.abstractdocument;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* Simple App test
|
||||
*/
|
||||
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}
|
||||
* throws an exception.
|
||||
*/
|
||||
public class AppTest {
|
||||
|
||||
@Test
|
||||
void shouldExecuteAppWithoutException() {
|
||||
assertDoesNotThrow(() -> App.main(null));
|
||||
public void shouldExecuteAppWithoutException() {
|
||||
App.main(null);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,19 +9,16 @@ tags:
|
||||
---
|
||||
|
||||
## Also known as
|
||||
|
||||
Kit
|
||||
|
||||
## Intent
|
||||
|
||||
Provide an interface for creating families of related or dependent
|
||||
objects without specifying their concrete classes.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real world example
|
||||
|
||||
> To create a kingdom we need objects with a common theme. Elven kingdom needs an Elven king, Elven castle and Elven army whereas Orcish kingdom needs an Orcish king, Orcish castle and Orcish army. There is a dependency between the objects in the kingdom.
|
||||
> To create a kingdom we need objects with common theme. Elven kingdom needs an Elven king, Elven castle and Elven army whereas Orcish kingdom needs an Orcish king, Orcish castle and Orcish army. There is a dependency between the objects in the kingdom.
|
||||
|
||||
In plain words
|
||||
|
||||
@ -33,18 +30,15 @@ Wikipedia says
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
Translating the kingdom example above. First of all we have some interfaces and implementation for the objects in the
|
||||
kingdom.
|
||||
Translating the kingdom example above. First of all we have some interfaces and implementation for the objects in the kingdom
|
||||
|
||||
```java
|
||||
public interface Castle {
|
||||
String getDescription();
|
||||
}
|
||||
|
||||
public interface King {
|
||||
String getDescription();
|
||||
}
|
||||
|
||||
public interface Army {
|
||||
String getDescription();
|
||||
}
|
||||
@ -72,7 +66,7 @@ public class ElfArmy implements Army {
|
||||
}
|
||||
}
|
||||
|
||||
// Orcish implementations similarly -> ...
|
||||
// Orcish implementations similarly...
|
||||
|
||||
```
|
||||
|
||||
@ -118,17 +112,9 @@ var castle = factory.createCastle();
|
||||
var king = factory.createKing();
|
||||
var army = factory.createArmy();
|
||||
|
||||
castle.getDescription();
|
||||
king.getDescription();
|
||||
army.getDescription();
|
||||
```
|
||||
|
||||
Program output:
|
||||
|
||||
```java
|
||||
This is the Elven castle!
|
||||
This is the Elven king!
|
||||
This is the Elven Army!
|
||||
castle.getDescription(); // Output: This is the Elven castle!
|
||||
king.getDescription(); // Output: This is the Elven king!
|
||||
army.getDescription(); // Output: This is the Elven Army!
|
||||
```
|
||||
|
||||
Now, we can design a factory for our different kingdom factories. In this example, we created FactoryMaker, responsible for returning an instance of either ElfKingdomFactory or OrcKingdomFactory.
|
||||
@ -170,52 +156,46 @@ public static void main(String[] args) {
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
|
||||

|
||||
|
||||
|
||||
## Applicability
|
||||
|
||||
Use the Abstract Factory pattern when
|
||||
|
||||
* The system should be independent of how its products are created, composed and represented
|
||||
* The system should be configured with one of multiple families of products
|
||||
* The family of related product objects is designed to be used together, and you need to enforce this constraint
|
||||
* You want to provide a class library of products, and you want to reveal just their interfaces, not their implementations
|
||||
* The lifetime of the dependency is conceptually shorter than the lifetime of the consumer.
|
||||
* You need a run-time value to construct a particular dependency
|
||||
* You want to decide which product to call from a family at runtime.
|
||||
* You need to supply one or more parameters only known at run-time before you can resolve a dependency.
|
||||
* When you need consistency among products
|
||||
* You don’t want to change existing code when adding new products or families of products to the program.
|
||||
* a system should be independent of how its products are created, composed and represented
|
||||
* a system should be configured with one of multiple families of products
|
||||
* a family of related product objects is designed to be used together, and you need to enforce this constraint
|
||||
* you want to provide a class library of products, and you want to reveal just their interfaces, not their implementations
|
||||
* the lifetime of the dependency is conceptually shorter than the lifetime of the consumer.
|
||||
* you need a run-time value to construct a particular dependency
|
||||
* you want to decide which product to call from a family at runtime.
|
||||
* you need to supply one or more parameters only known at run-time before you can resolve a dependency.
|
||||
* when you need consistency among products
|
||||
* you don’t want to change existing code when adding new products or families of products to the program.
|
||||
|
||||
Example use cases
|
||||
## Use Cases:
|
||||
|
||||
* Selecting to call to the appropriate implementation of FileSystemAcmeService or DatabaseAcmeService or NetworkAcmeService at runtime.
|
||||
* Unit test case writing becomes much easier
|
||||
* Selecting to call the appropriate implementation of FileSystemAcmeService or DatabaseAcmeService or NetworkAcmeService at runtime.
|
||||
* Unit test case writing becomes much easier
|
||||
* UI tools for different OS
|
||||
|
||||
## Consequences:
|
||||
|
||||
* Dependency injection in java hides the service class dependencies that can lead to runtime errors that would have been caught at compile time.
|
||||
* Dependency injection in java hides the service class dependencies that can lead to runtime errors that would have been caught at compile time.
|
||||
* While the pattern is great when creating predefined objects, adding the new ones might be challenging.
|
||||
* The code becomes more complicated than it should be, since a lot of new interfaces and classes are introduced along with the pattern.
|
||||
* The code may become more complicated than it should be, since a lot of new interfaces and classes are introduced along with the pattern.
|
||||
|
||||
|
||||
## Tutorial
|
||||
|
||||
* [Abstract Factory Pattern Tutorial](https://www.journaldev.com/1418/abstract-factory-design-pattern-in-java)
|
||||
|
||||
## Known uses
|
||||
|
||||
## Real world examples
|
||||
|
||||
* [javax.xml.parsers.DocumentBuilderFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/parsers/DocumentBuilderFactory.html)
|
||||
* [javax.xml.transform.TransformerFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/transform/TransformerFactory.html#newInstance--)
|
||||
* [javax.xml.xpath.XPathFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/xpath/XPathFactory.html#newInstance--)
|
||||
|
||||
## Related patterns
|
||||
|
||||
[Factory Method](https://java-design-patterns.com/patterns/factory-method/)
|
||||
[Factory Kit](https://java-design-patterns.com/patterns/factory-kit/)
|
||||
|
||||
## Credits
|
||||
|
||||
* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59)
|
||||
|
@ -21,19 +21,6 @@
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.transactionscript;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* Tests that Transaction script example runs without errors.
|
||||
*/
|
||||
class AppTest {
|
||||
|
||||
@Test
|
||||
void shouldExecuteWithoutException() {
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
}
|
||||
}
|
||||
module com.iluwatar.abstractfactory {
|
||||
requires org.slf4j;
|
||||
}
|
@ -25,23 +25,12 @@ package com.iluwatar.abstractfactory;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* Tests that Abstract Factory 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}
|
||||
* throws an exception.
|
||||
*/
|
||||
|
||||
public class AppTest {
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
public void test() {
|
||||
App.main(new String[]{});
|
||||
}
|
||||
}
|
||||
|
@ -9,126 +9,12 @@ tags:
|
||||
---
|
||||
|
||||
## Intent
|
||||
|
||||
Allow new functions to be added to existing class hierarchies without affecting those hierarchies, and without creating
|
||||
the troublesome dependency cycles that are inherent to the GoF Visitor Pattern.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real world example
|
||||
|
||||
> We have a hierarchy of modem classes. The modems in this hierarchy need to be visited by an external algorithm based
|
||||
> on filtering criteria (is it Unix or DOS compatible modem).
|
||||
|
||||
In plain words
|
||||
|
||||
> Acyclic Visitor allows functions to be added to existing class hierarchies without modifying the hierarchies.
|
||||
|
||||
[WikiWikiWeb](https://wiki.c2.com/?AcyclicVisitor) says
|
||||
|
||||
> The Acyclic Visitor pattern allows new functions to be added to existing class hierarchies without affecting those
|
||||
> hierarchies, and without creating the dependency cycles that are inherent to the GangOfFour VisitorPattern.
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
Here's the `Modem` hierarchy.
|
||||
|
||||
```java
|
||||
public abstract class Modem {
|
||||
public abstract void accept(ModemVisitor modemVisitor);
|
||||
}
|
||||
|
||||
public class Zoom extends Modem {
|
||||
...
|
||||
@Override
|
||||
public void accept(ModemVisitor modemVisitor) {
|
||||
if (modemVisitor instanceof ZoomVisitor) {
|
||||
((ZoomVisitor) modemVisitor).visit(this);
|
||||
} else {
|
||||
LOGGER.info("Only ZoomVisitor is allowed to visit Zoom modem");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Hayes extends Modem {
|
||||
...
|
||||
@Override
|
||||
public void accept(ModemVisitor modemVisitor) {
|
||||
if (modemVisitor instanceof HayesVisitor) {
|
||||
((HayesVisitor) modemVisitor).visit(this);
|
||||
} else {
|
||||
LOGGER.info("Only HayesVisitor is allowed to visit Hayes modem");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Next we introduce the `ModemVisitor` hierarchy.
|
||||
|
||||
```java
|
||||
public interface ModemVisitor {
|
||||
}
|
||||
|
||||
public interface HayesVisitor extends ModemVisitor {
|
||||
void visit(Hayes hayes);
|
||||
}
|
||||
|
||||
public interface ZoomVisitor extends ModemVisitor {
|
||||
void visit(Zoom zoom);
|
||||
}
|
||||
|
||||
public interface AllModemVisitor extends ZoomVisitor, HayesVisitor {
|
||||
}
|
||||
|
||||
public class ConfigureForDosVisitor implements AllModemVisitor {
|
||||
...
|
||||
@Override
|
||||
public void visit(Hayes hayes) {
|
||||
LOGGER.info(hayes + " used with Dos configurator.");
|
||||
}
|
||||
@Override
|
||||
public void visit(Zoom zoom) {
|
||||
LOGGER.info(zoom + " used with Dos configurator.");
|
||||
}
|
||||
}
|
||||
|
||||
public class ConfigureForUnixVisitor implements ZoomVisitor {
|
||||
...
|
||||
@Override
|
||||
public void visit(Zoom zoom) {
|
||||
LOGGER.info(zoom + " used with Unix configurator.");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Finally, here are the visitors in action.
|
||||
|
||||
```java
|
||||
var conUnix = new ConfigureForUnixVisitor();
|
||||
var conDos = new ConfigureForDosVisitor();
|
||||
var zoom = new Zoom();
|
||||
var hayes = new Hayes();
|
||||
hayes.accept(conDos);
|
||||
zoom.accept(conDos);
|
||||
hayes.accept(conUnix);
|
||||
zoom.accept(conUnix);
|
||||
```
|
||||
|
||||
Program output:
|
||||
|
||||
```
|
||||
// Hayes modem used with Dos configurator.
|
||||
// Zoom modem used with Dos configurator.
|
||||
// Only HayesVisitor is allowed to visit Hayes modem
|
||||
// Zoom modem used with Unix configurator.
|
||||
```
|
||||
Allow new functions to be added to existing class hierarchies without affecting those hierarchies, and without creating the troublesome dependency cycles that are inherent to the GOF VISITOR Pattern.
|
||||
|
||||
## Class diagram
|
||||
|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
This pattern can be used:
|
||||
|
||||
* When you need to add a new function to an existing hierarchy without the need to alter or affect that hierarchy.
|
||||
@ -138,7 +24,6 @@ This pattern can be used:
|
||||
* When the recompilation, relinking, retesting or redistribution of the derivatives of Element is very expensive.
|
||||
|
||||
## Consequences
|
||||
|
||||
The good:
|
||||
|
||||
* No dependency cycles between class hierarchies.
|
||||
@ -147,14 +32,11 @@ The good:
|
||||
|
||||
The bad:
|
||||
|
||||
* Violates [Liskov's Substitution Principle](https://java-design-patterns.com/principles/#liskov-substitution-principle) by showing that it can accept all visitors but actually only being interested in particular visitors.
|
||||
* Violates the principle of least surprise or Liskov's Substitution principle by showing that it can accept all visitors but actually only being interested in particular visitors.
|
||||
* Parallel hierarchy of visitors has to be created for all members in visitable class hierarchy.
|
||||
|
||||
## Related patterns
|
||||
|
||||
* [Visitor Pattern](https://java-design-patterns.com/patterns/visitor/)
|
||||
* [Visitor Pattern](../visitor/)
|
||||
|
||||
## Credits
|
||||
|
||||
* [Acyclic Visitor by Robert C. Martin](http://condor.depaul.edu/dmumaugh/OOT/Design-Principles/acv.pdf)
|
||||
* [Acyclic Visitor in WikiWikiWeb](https://wiki.c2.com/?AcyclicVisitor)
|
||||
* [Acyclic Visitor](http://condor.depaul.edu/dmumaugh/OOT/Design-Principles/acv.pdf)
|
||||
|
115
acyclic-visitor/etc/Acyclic Visitor.ucls
Normal file
115
acyclic-visitor/etc/Acyclic Visitor.ucls
Normal file
@ -0,0 +1,115 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<class-diagram version="1.2.2" icons="true" always-add-relationships="false" generalizations="true" realizations="true"
|
||||
associations="true" dependencies="false" nesting-relationships="true" router="FAN">
|
||||
<interface id="1" language="java" name="com.iluwatar.acyclicvisitor.ModemVisitor" project="acyclic-visitor"
|
||||
file="/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ModemVisitor.java" binary="false"
|
||||
corner="BOTTOM_RIGHT">
|
||||
<position height="-1" width="-1" x="860" y="67"/>
|
||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
sort-features="false" accessors="true" visibility="true">
|
||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</display>
|
||||
</interface>
|
||||
<class id="2" language="java" name="com.iluwatar.acyclicvisitor.Modem" project="acyclic-visitor"
|
||||
file="/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Modem.java" binary="false" corner="BOTTOM_RIGHT">
|
||||
<position height="-1" width="-1" x="327" y="77"/>
|
||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
sort-features="false" accessors="true" visibility="true">
|
||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</display>
|
||||
</class>
|
||||
<class id="3" language="java" name="com.iluwatar.acyclicvisitor.ConfigureForUnixVisitor" project="acyclic-visitor"
|
||||
file="/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ConfigureForUnixVisitor.java" binary="false"
|
||||
corner="BOTTOM_RIGHT">
|
||||
<position height="124" width="196" x="647" y="225"/>
|
||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
sort-features="false" accessors="true" visibility="true">
|
||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</display>
|
||||
</class>
|
||||
<class id="4" language="java" name="com.iluwatar.acyclicvisitor.Zoom" project="acyclic-visitor"
|
||||
file="/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Zoom.java" binary="false" corner="BOTTOM_RIGHT">
|
||||
<position height="-1" width="-1" x="203" y="305"/>
|
||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
sort-features="false" accessors="true" visibility="true">
|
||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</display>
|
||||
</class>
|
||||
<interface id="5" language="java" name="com.iluwatar.acyclicvisitor.HayesVisitor" project="acyclic-visitor"
|
||||
file="/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/HayesVisitor.java" binary="false"
|
||||
corner="BOTTOM_RIGHT">
|
||||
<position height="-1" width="-1" x="1019" y="468"/>
|
||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
sort-features="false" accessors="true" visibility="true">
|
||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</display>
|
||||
</interface>
|
||||
<interface id="6" language="java" name="com.iluwatar.acyclicvisitor.ZoomVisitor" project="acyclic-visitor"
|
||||
file="/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ZoomVisitor.java" binary="false"
|
||||
corner="BOTTOM_RIGHT">
|
||||
<position height="-1" width="-1" x="758" y="467"/>
|
||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
sort-features="false" accessors="true" visibility="true">
|
||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</display>
|
||||
</interface>
|
||||
<class id="7" language="java" name="com.iluwatar.acyclicvisitor.Hayes" project="acyclic-visitor"
|
||||
file="/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Hayes.java" binary="false" corner="BOTTOM_RIGHT">
|
||||
<position height="-1" width="-1" x="479" y="307"/>
|
||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
sort-features="false" accessors="true" visibility="true">
|
||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</display>
|
||||
</class>
|
||||
<class id="8" language="java" name="com.iluwatar.acyclicvisitor.ConfigureForDosVisitor" project="acyclic-visitor"
|
||||
file="/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ConfigureForDosVisitor.java" binary="false"
|
||||
corner="BOTTOM_RIGHT">
|
||||
<position height="142" width="192" x="883" y="225"/>
|
||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
sort-features="false" accessors="true" visibility="true">
|
||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</display>
|
||||
</class>
|
||||
<generalization id="9">
|
||||
<end type="SOURCE" refId="7"/>
|
||||
<end type="TARGET" refId="2"/>
|
||||
</generalization>
|
||||
<realization id="10">
|
||||
<end type="SOURCE" refId="8"/>
|
||||
<end type="TARGET" refId="6"/>
|
||||
</realization>
|
||||
<realization id="11">
|
||||
<end type="SOURCE" refId="8"/>
|
||||
<end type="TARGET" refId="5"/>
|
||||
</realization>
|
||||
<realization id="12">
|
||||
<end type="SOURCE" refId="3"/>
|
||||
<end type="TARGET" refId="6"/>
|
||||
</realization>
|
||||
<realization id="13">
|
||||
<end type="SOURCE" refId="3"/>
|
||||
<end type="TARGET" refId="1"/>
|
||||
</realization>
|
||||
<realization id="14">
|
||||
<end type="SOURCE" refId="8"/>
|
||||
<end type="TARGET" refId="1"/>
|
||||
</realization>
|
||||
<generalization id="15">
|
||||
<end type="SOURCE" refId="4"/>
|
||||
<end type="TARGET" refId="2"/>
|
||||
</generalization>
|
||||
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
sort-features="false" accessors="true" visibility="true">
|
||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</classifier-display>
|
||||
<association-display labels="true" multiplicity="true"/>
|
||||
</class-diagram>
|
Binary file not shown.
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 26 KiB |
@ -21,20 +21,6 @@
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.transactionscript;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public interface HotelDao {
|
||||
|
||||
Stream<Room> getAll() throws Exception;
|
||||
|
||||
Optional<Room> getById(int id) throws Exception;
|
||||
|
||||
Boolean add(Room room) throws Exception;
|
||||
|
||||
Boolean update(Room room) throws Exception;
|
||||
|
||||
Boolean delete(Room room) throws Exception;
|
||||
}
|
||||
module com.iluwatar.acyclicvisitor {
|
||||
requires org.slf4j;
|
||||
}
|
@ -25,23 +25,13 @@ package com.iluwatar.acyclicvisitor;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* Tests that the Acyclic Visitor 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}
|
||||
* throws an exception.
|
||||
*/
|
||||
public class AppTest {
|
||||
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
public void test() {
|
||||
App.main(new String[]{});
|
||||
}
|
||||
}
|
@ -12,8 +12,9 @@ tags:
|
||||
Wrapper
|
||||
|
||||
## Intent
|
||||
Convert the interface of a class into another interface the clients expect. Adapter lets classes work together that
|
||||
couldn't otherwise because of incompatible interfaces.
|
||||
Convert the interface of a class into another interface the clients
|
||||
expect. Adapter lets classes work together that couldn't otherwise because of
|
||||
incompatible interfaces.
|
||||
|
||||
## Explanation
|
||||
|
||||
|
@ -25,23 +25,12 @@ package com.iluwatar.adapter;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* Tests that Adapter 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}
|
||||
* throws an exception.
|
||||
*/
|
||||
|
||||
public class AppTest {
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
public void test() {
|
||||
App.main(new String[]{});
|
||||
}
|
||||
}
|
||||
|
@ -6,97 +6,20 @@ permalink: /patterns/aggregator-microservices/
|
||||
categories: Architectural
|
||||
tags:
|
||||
- Cloud distributed
|
||||
- Decoupling
|
||||
- Microservices
|
||||
---
|
||||
|
||||
## Intent
|
||||
|
||||
The user makes a single call to the aggregator service, and the aggregator then calls each relevant microservice.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real world example
|
||||
|
||||
> Our web marketplace needs information about products and their current inventory. It makes a call to an aggregator
|
||||
> service which in turn calls the product information microservice and product inventory microservice returning the
|
||||
> combined information.
|
||||
|
||||
In plain words
|
||||
|
||||
> Aggregator Microservice collects pieces of data from various microservices and returns an aggregate for processing.
|
||||
|
||||
Stack Overflow says
|
||||
|
||||
> Aggregator Microservice invokes multiple services to achieve the functionality required by the application.
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
Let's start from the data model. Here's our `Product`.
|
||||
|
||||
```java
|
||||
public class Product {
|
||||
private String title;
|
||||
private int productInventories;
|
||||
// getters and setters ->
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Next we can introduce our `Aggregator` microservice. It contains clients `ProductInformationClient` and
|
||||
`ProductInventoryClient` for calling respective microservices.
|
||||
|
||||
```java
|
||||
@RestController
|
||||
public class Aggregator {
|
||||
|
||||
@Resource
|
||||
private ProductInformationClient informationClient;
|
||||
|
||||
@Resource
|
||||
private ProductInventoryClient inventoryClient;
|
||||
|
||||
@RequestMapping(path = "/product", method = RequestMethod.GET)
|
||||
public Product getProduct() {
|
||||
|
||||
var product = new Product();
|
||||
var productTitle = informationClient.getProductTitle();
|
||||
var productInventory = inventoryClient.getProductInventories();
|
||||
|
||||
//Fallback to error message
|
||||
product.setTitle(requireNonNullElse(productTitle, "Error: Fetching Product Title Failed"));
|
||||
|
||||
//Fallback to default error inventory
|
||||
product.setProductInventories(requireNonNullElse(productInventory, -1));
|
||||
|
||||
return product;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here's the essence of information microservice implementation. Inventory microservice is similar, it just returns
|
||||
inventory counts.
|
||||
|
||||
```java
|
||||
@RestController
|
||||
public class InformationController {
|
||||
@RequestMapping(value = "/information", method = RequestMethod.GET)
|
||||
public String getProductTitle() {
|
||||
return "The Product Title.";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now calling our `Aggregator` REST API returns the product information.
|
||||
|
||||
```bash
|
||||
curl http://localhost:50004/product
|
||||
{"title":"The Product Title.","productInventories":5}
|
||||
```
|
||||
The user makes a single call to the Aggregator, and the aggregator then calls each relevant microservice and collects
|
||||
the data, apply business logic to it, and further publish is as a REST Endpoint.
|
||||
More variations of the aggregator are:
|
||||
- Proxy Microservice Design Pattern: A different microservice is called upon the business need.
|
||||
- Chained Microservice Design Pattern: In this case each microservice is dependent/ chained to a series
|
||||
of other microservices.
|
||||
|
||||
## Class diagram
|
||||
|
||||

|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
@ -105,5 +28,3 @@ Use the Aggregator Microservices pattern when you need a unified API for various
|
||||
## Credits
|
||||
|
||||
* [Microservice Design Patterns](http://web.archive.org/web/20190705163602/http://blog.arungupta.me/microservice-design-patterns/)
|
||||
* [Microservices Patterns: With examples in Java](https://www.amazon.com/gp/product/1617294543/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617294543&linkId=8b4e570267bc5fb8b8189917b461dc60)
|
||||
* [Architectural Patterns: Uncover essential patterns in the most indispensable realm of enterprise architecture](https://www.amazon.com/gp/product/B077T7V8RC/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=B077T7V8RC&linkId=c34d204bfe1b277914b420189f09c1a4)
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 40 KiB |
BIN
aggregator-microservices/etc/aggregator-microservice.png
Normal file
BIN
aggregator-microservices/etc/aggregator-microservice.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 40 KiB |
@ -10,37 +10,28 @@ tags:
|
||||
---
|
||||
|
||||
## Intent
|
||||
|
||||
Provide a helper service instance on a client and offload common functionality away from a shared resource.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real world example
|
||||
|
||||
> A remote service has many clients accessing a function it provides. The service is a legacy application and is
|
||||
> impossible to update. Large numbers of requests from users are causing connectivity issues. New rules for request
|
||||
> frequency should be implemented along with latency checks and client-side logging.
|
||||
> A remote service has many clients accessing a function it provides. The service is a legacy application and is impossible to update. Large numbers of requests from users are causing connectivity issues. New rules for request frequency should be implemented along with latency checks and client-side logging.
|
||||
|
||||
In plain words
|
||||
|
||||
> With the Ambassador pattern, we can implement less-frequent polling from clients along with latency checks and
|
||||
> logging.
|
||||
> Using the ambassador pattern, we can implement less-frequent polling from clients along with latency checks and logging.
|
||||
|
||||
Microsoft documentation states
|
||||
|
||||
> An ambassador service can be thought of as an out-of-process proxy which is co-located with the client. This pattern
|
||||
> can be useful for offloading common client connectivity tasks such as monitoring, logging, routing,
|
||||
> security (such as TLS), and resiliency patterns in a language agnostic way. It is often used with legacy applications,
|
||||
> or other applications that are difficult to modify, in order to extend their networking capabilities. It can also
|
||||
> enable a specialized team to implement those features.
|
||||
> An ambassador service can be thought of as an out-of-process proxy that is co-located with the client. This pattern can be useful for offloading common client connectivity tasks such as monitoring, logging, routing, security (such as TLS), and resiliency patterns in a language agnostic way. It is often used with legacy applications, or other applications that are difficult to modify, in order to extend their networking capabilities. It can also enable a specialized team to implement those features.
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
With the above introduction in mind we will imitate the functionality in this example. We have an interface implemented
|
||||
by the remote service as well as the ambassador service:
|
||||
With the above example in mind we will imitate the functionality in a simple manner. We have an interface implemented by the remote service as well as the ambassador service:
|
||||
|
||||
```java
|
||||
interface RemoteServiceInterface {
|
||||
|
||||
long doRemoteFunction(int value) throws Exception;
|
||||
}
|
||||
```
|
||||
@ -145,7 +136,7 @@ public class Client {
|
||||
}
|
||||
```
|
||||
|
||||
Here are two clients using the service.
|
||||
And here are two clients using the service.
|
||||
|
||||
```java
|
||||
public class App {
|
||||
@ -158,29 +149,13 @@ public class App {
|
||||
}
|
||||
```
|
||||
|
||||
Here's the output for running the example:
|
||||
|
||||
```java
|
||||
Time taken (ms): 111
|
||||
Service result: 120
|
||||
Time taken (ms): 931
|
||||
Failed to reach remote: (1)
|
||||
Time taken (ms): 665
|
||||
Failed to reach remote: (2)
|
||||
Time taken (ms): 538
|
||||
Failed to reach remote: (3)
|
||||
Service result: -1
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
Ambassador is applicable when working with a legacy remote service which cannot be modified or would be extremely
|
||||
difficult to modify. Connectivity features can be implemented on the client avoiding the need for changes on the remote
|
||||
service.
|
||||
Ambassador is applicable when working with a legacy remote service that cannot
|
||||
be modified or would be extremely difficult to modify. Connectivity features can
|
||||
be implemented on the client avoiding the need for changes on the remote service.
|
||||
|
||||
* Ambassador provides a local interface for a remote service.
|
||||
* Ambassador provides logging, circuit breaking, retries and security on the client.
|
||||
@ -193,14 +168,10 @@ service.
|
||||
* Offload remote service tasks
|
||||
* Facilitate network connection
|
||||
|
||||
## Known uses
|
||||
## Real world examples
|
||||
|
||||
* [Kubernetes-native API gateway for microservices](https://github.com/datawire/ambassador)
|
||||
|
||||
## Related patterns
|
||||
|
||||
* [Proxy](https://java-design-patterns.com/patterns/proxy/)
|
||||
|
||||
## Credits
|
||||
|
||||
* [Ambassador pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/ambassador)
|
||||
|
@ -62,7 +62,7 @@ public class RemoteService implements RemoteServiceInterface {
|
||||
*
|
||||
* @param value integer value to be multiplied.
|
||||
* @return if waitTime is less than {@link RemoteService#THRESHOLD}, it returns value * 10,
|
||||
* otherwise {@link RemoteServiceStatus#FAILURE}.
|
||||
* otherwise {@link RemoteServiceInterface#FAILURE}.
|
||||
*/
|
||||
@Override
|
||||
public long doRemoteFunction(int value) {
|
||||
@ -74,7 +74,6 @@ public class RemoteService implements RemoteServiceInterface {
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Thread sleep state interrupted", e);
|
||||
}
|
||||
return waitTime <= THRESHOLD ? value * 10
|
||||
: RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue();
|
||||
return waitTime <= THRESHOLD ? value * 10 : FAILURE;
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ package com.iluwatar.ambassador;
|
||||
* Interface shared by ({@link RemoteService}) and ({@link ServiceAmbassador}).
|
||||
*/
|
||||
interface RemoteServiceInterface {
|
||||
int FAILURE = -1;
|
||||
|
||||
long doRemoteFunction(int value);
|
||||
long doRemoteFunction(int value) throws Exception;
|
||||
}
|
||||
|
@ -1,48 +0,0 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 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.
|
||||
*/
|
||||
|
||||
package com.iluwatar.ambassador;
|
||||
|
||||
/**
|
||||
* Holds information regarding the status of the Remote Service.
|
||||
*
|
||||
* <p> This Enum replaces the integer value previously
|
||||
* stored in {@link RemoteServiceInterface} as SonarCloud was identifying
|
||||
* it as an issue. All test cases have been checked after changes,
|
||||
* without failures. </p>
|
||||
*/
|
||||
|
||||
public enum RemoteServiceStatus {
|
||||
FAILURE(-1)
|
||||
;
|
||||
|
||||
private final long remoteServiceStatusValue;
|
||||
|
||||
RemoteServiceStatus(long remoteServiceStatusValue) {
|
||||
this.remoteServiceStatusValue = remoteServiceStatusValue;
|
||||
}
|
||||
|
||||
public long getRemoteServiceStatusValue() {
|
||||
return remoteServiceStatusValue;
|
||||
}
|
||||
}
|
@ -23,7 +23,6 @@
|
||||
|
||||
package com.iluwatar.ambassador;
|
||||
|
||||
import static com.iluwatar.ambassador.RemoteServiceStatus.FAILURE;
|
||||
import static java.lang.Thread.sleep;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@ -59,14 +58,14 @@ public class ServiceAmbassador implements RemoteServiceInterface {
|
||||
|
||||
private long safeCall(int value) {
|
||||
var retries = 0;
|
||||
var result = FAILURE.getRemoteServiceStatusValue();
|
||||
var result = (long) FAILURE;
|
||||
|
||||
for (int i = 0; i < RETRIES; i++) {
|
||||
if (retries >= RETRIES) {
|
||||
return FAILURE.getRemoteServiceStatusValue();
|
||||
return FAILURE;
|
||||
}
|
||||
|
||||
if ((result = checkLatency(value)) == FAILURE.getRemoteServiceStatusValue()) {
|
||||
if ((result = checkLatency(value)) == FAILURE) {
|
||||
LOGGER.info("Failed to reach remote: (" + (i + 1) + ")");
|
||||
retries++;
|
||||
try {
|
||||
|
@ -25,23 +25,13 @@ package com.iluwatar.ambassador;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* Application test
|
||||
*/
|
||||
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}
|
||||
* throws an exception.
|
||||
*/
|
||||
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
void test() {
|
||||
App.main(new String[]{});
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,6 @@ class ClientTest {
|
||||
Client client = new Client();
|
||||
var result = client.useService(10);
|
||||
|
||||
assertTrue(result == 100 || result == RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue());
|
||||
assertTrue(result == 100 || result == RemoteService.FAILURE);
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ class RemoteServiceTest {
|
||||
void testFailedCall() {
|
||||
var remoteService = new RemoteService(new StaticRandomProvider(0.21));
|
||||
var result = remoteService.doRemoteFunction(10);
|
||||
assertEquals(RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue(), result);
|
||||
assertEquals(RemoteServiceInterface.FAILURE, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -35,6 +35,6 @@ class ServiceAmbassadorTest {
|
||||
@Test
|
||||
void test() {
|
||||
long result = new ServiceAmbassador().doRemoteFunction(10);
|
||||
assertTrue(result == 100 || result == RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue());
|
||||
assertTrue(result == 100 || result == RemoteServiceInterface.FAILURE);
|
||||
}
|
||||
}
|
||||
|
@ -12,53 +12,49 @@ tags:
|
||||
|
||||
## Intent
|
||||
|
||||
Aggregate calls to microservices in a single location, the API Gateway. The user makes a single call
|
||||
to the API Gateway, and the API Gateway then calls each relevant microservice.
|
||||
Aggregate calls to microservices in a single location: the API Gateway. The user makes a single call to the API Gateway,
|
||||
and the API Gateway then calls each relevant microservice.
|
||||
|
||||
## Explanation
|
||||
|
||||
With the Microservices pattern, a client may need data from multiple different microservices. If the
|
||||
client called each microservice directly, that could contribute to longer load times, since the
|
||||
client would have to make a network request for each microservice called. Moreover, having the
|
||||
client call each microservice directly ties the client to that microservice - if the internal
|
||||
implementations of the microservices change (for example, if two microservices are combined sometime
|
||||
in the future) or if the location (host and port) of a microservice changes, then every client that
|
||||
With the Microservices pattern, a client may need data from multiple different microservices. If the client called each
|
||||
microservice directly, that could contribute to longer load times, since the client would have to make a network request
|
||||
for each microservice called. Moreover, having the client call each microservice directly ties the client to that
|
||||
microservice - if the internal implementations of the microservices change (for example, if two microservices are
|
||||
combined sometime in the future) or if the location (host and port) of a microservice changes, then every client that
|
||||
makes use of those microservices must be updated.
|
||||
|
||||
The intent of the API Gateway pattern is to alleviate some of these issues. In the API Gateway
|
||||
pattern, an additional entity (the API Gateway) is placed between the client and the microservices.
|
||||
The job of the API Gateway is to aggregate the calls to the microservices. Rather than the client
|
||||
calling each microservice individually, the client calls the API Gateway a single time. The API
|
||||
Gateway then calls each of the microservices that the client needs.
|
||||
The intent of the API Gateway pattern is to alleviate some of these issues. In the API Gateway pattern, an additional
|
||||
entity (the API Gateway) is placed between the client and the microservices. The job of the API Gateway is to aggregate
|
||||
the calls to the microservices. Rather than the client calling each microservice individually, the client calls the
|
||||
API Gateway a single time. The API Gateway then calls each of the microservices that the client needs.
|
||||
|
||||
Real world example
|
||||
|
||||
> We are implementing microservices and API Gateway pattern for an e-commerce site. In this system
|
||||
> the API Gateway makes calls to the Image and Price microservices.
|
||||
> We are implementing microservices and API Gateway pattern for an e-commerce site. In this system the API Gateway makes
|
||||
calls to the Image and Price microservices.
|
||||
|
||||
In plain words
|
||||
|
||||
> For a system implemented using microservices architecture, API Gateway is the single entry point
|
||||
> that aggregates the calls to the individual microservices.
|
||||
> For a system implemented using microservices architecture, API Gateway is the single entry point that aggregates the
|
||||
calls to the individual microservices.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> API Gateway is a server that acts as an API front-end, receives API requests, enforces throttling
|
||||
> and security policies, passes requests to the back-end service and then passes the response back
|
||||
> to the requester. A gateway often includes a transformation engine to orchestrate and modify the
|
||||
> requests and responses on the fly. A gateway can also provide functionality such as collecting
|
||||
> analytics data and providing caching. The gateway can provide functionality to support
|
||||
> authentication, authorization, security, audit and regulatory compliance.
|
||||
> API Gateway is a server that acts as an API front-end, receives API requests, enforces throttling and security
|
||||
policies, passes requests to the back-end service and then passes the response back to the requester. A gateway often
|
||||
includes a transformation engine to orchestrate and modify the requests and responses on the fly. A gateway can also
|
||||
provide functionality such as collecting analytics data and providing caching. The gateway can provide functionality to
|
||||
support authentication, authorization, security, audit and regulatory compliance.
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
This implementation shows what the API Gateway pattern could look like for an e-commerce site. The
|
||||
`ApiGateway` makes calls to the Image and Price microservices using the `ImageClientImpl` and
|
||||
`PriceClientImpl` respectively. Customers viewing the site on a desktop device can see both price
|
||||
information and an image of a product, so the `ApiGateway` calls both of the microservices and
|
||||
aggregates the data in the `DesktopProduct` model. However, mobile users only see price information;
|
||||
they do not see a product image. For mobile users, the `ApiGateway` only retrieves price
|
||||
information, which it uses to populate the `MobileProduct`.
|
||||
This implementation shows what the API Gateway pattern could look like for an e-commerce site. The `ApiGateway` makes
|
||||
calls to the Image and Price microservices using the `ImageClientImpl` and `PriceClientImpl` respectively. Customers
|
||||
viewing the site on a desktop device can see both price information and an image of a product, so the `ApiGateway` calls
|
||||
both of the microservices and aggregates the data in the `DesktopProduct` model. However, mobile users only see price
|
||||
information; they do not see a product image. For mobile users, the `ApiGateway` only retrieves price information, which
|
||||
it uses to populate the `MobileProduct`.
|
||||
|
||||
Here's the Image microservice implementation.
|
||||
|
||||
@ -68,6 +64,7 @@ public interface ImageClient {
|
||||
}
|
||||
|
||||
public class ImageClientImpl implements ImageClient {
|
||||
|
||||
@Override
|
||||
public String getImagePath() {
|
||||
var httpClient = HttpClient.newHttpClient();
|
||||
@ -117,7 +114,7 @@ public class PriceClientImpl implements PriceClient {
|
||||
}
|
||||
```
|
||||
|
||||
Here we can see how API Gateway maps the requests to the microservices.
|
||||
And here we can see how API Gateway maps the requests to the microservices.
|
||||
|
||||
```java
|
||||
public class ApiGateway {
|
||||
|
@ -23,16 +23,11 @@
|
||||
|
||||
package com.iluwatar.api.gateway;
|
||||
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.net.http.HttpResponse.BodyHandlers;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
@ -40,8 +35,6 @@ import org.springframework.stereotype.Component;
|
||||
*/
|
||||
@Component
|
||||
public class ImageClientImpl implements ImageClient {
|
||||
private static final Logger LOGGER = getLogger(ImageClientImpl.class);
|
||||
|
||||
/**
|
||||
* Makes a simple HTTP Get request to the Image microservice.
|
||||
*
|
||||
@ -56,26 +49,12 @@ public class ImageClientImpl implements ImageClient {
|
||||
.build();
|
||||
|
||||
try {
|
||||
LOGGER.info("Sending request to fetch image path");
|
||||
var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString());
|
||||
logResponse(httpResponse);
|
||||
return httpResponse.body();
|
||||
} catch (IOException | InterruptedException e) {
|
||||
LOGGER.error("Failure occurred while getting image path", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void logResponse(HttpResponse<String> httpResponse) {
|
||||
if (isSuccessResponse(httpResponse.statusCode())) {
|
||||
LOGGER.info("Image path received successfully");
|
||||
} else {
|
||||
LOGGER.warn("Image path request failed");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSuccessResponse(int responseCode) {
|
||||
return responseCode >= 200 && responseCode <= 299;
|
||||
}
|
||||
}
|
||||
|
@ -23,26 +23,18 @@
|
||||
|
||||
package com.iluwatar.api.gateway;
|
||||
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.net.http.HttpResponse.BodyHandlers;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
||||
/**
|
||||
* An adapter to communicate with the Price microservice.
|
||||
*/
|
||||
@Component
|
||||
public class PriceClientImpl implements PriceClient {
|
||||
private static final Logger LOGGER = getLogger(PriceClientImpl.class);
|
||||
|
||||
/**
|
||||
* Makes a simple HTTP Get request to the Price microservice.
|
||||
*
|
||||
@ -57,26 +49,12 @@ public class PriceClientImpl implements PriceClient {
|
||||
.build();
|
||||
|
||||
try {
|
||||
LOGGER.info("Sending request to fetch price info");
|
||||
var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString());
|
||||
logResponse(httpResponse);
|
||||
return httpResponse.body();
|
||||
} catch (IOException | InterruptedException e) {
|
||||
LOGGER.error("Failure occurred while getting price info", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void logResponse(HttpResponse<String> httpResponse) {
|
||||
if (isSuccessResponse(httpResponse.statusCode())) {
|
||||
LOGGER.info("Price info received successfully");
|
||||
} else {
|
||||
LOGGER.warn("Price info request failed");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSuccessResponse(int responseCode) {
|
||||
return responseCode >= 200 && responseCode <= 299;
|
||||
}
|
||||
}
|
||||
|
@ -23,20 +23,15 @@
|
||||
|
||||
package com.iluwatar.image.microservice;
|
||||
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
|
||||
/**
|
||||
* Exposes the Image microservice's endpoints.
|
||||
*/
|
||||
@RestController
|
||||
public class ImageController {
|
||||
private static final Logger LOGGER = getLogger(ImageController.class);
|
||||
|
||||
/**
|
||||
* An endpoint for a user to retrieve an image path.
|
||||
@ -45,7 +40,6 @@ public class ImageController {
|
||||
*/
|
||||
@RequestMapping(value = "/image-path", method = RequestMethod.GET)
|
||||
public String getImagePath() {
|
||||
LOGGER.info("Successfully found image path");
|
||||
return "/product-image.png";
|
||||
}
|
||||
}
|
||||
|
@ -23,20 +23,15 @@
|
||||
|
||||
package com.iluwatar.price.microservice;
|
||||
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
|
||||
/**
|
||||
* Exposes the Price microservice's endpoints.
|
||||
*/
|
||||
@RestController
|
||||
public class PriceController {
|
||||
private static final Logger LOGGER = getLogger(PriceController.class);
|
||||
|
||||
/**
|
||||
* An endpoint for a user to retrieve a product's price.
|
||||
@ -45,7 +40,6 @@ public class PriceController {
|
||||
*/
|
||||
@RequestMapping(value = "/price", method = RequestMethod.GET)
|
||||
public String getPrice() {
|
||||
LOGGER.info("Successfully found price info");
|
||||
return "20";
|
||||
}
|
||||
}
|
||||
|
@ -9,137 +9,22 @@ tags:
|
||||
---
|
||||
|
||||
## Also known as
|
||||
|
||||
Given/When/Then
|
||||
|
||||
## Intent
|
||||
|
||||
Arrange/Act/Assert (AAA) is a pattern for organizing unit tests.
|
||||
The Arrange/Act/Assert (AAA) is a pattern for organizing unit tests.
|
||||
It breaks tests down into three clear and distinct steps:
|
||||
|
||||
1. Arrange: Perform the setup and initialization required for the test.
|
||||
2. Act: Take action(s) required for the test.
|
||||
3. Assert: Verify the outcome(s) of the test.
|
||||
|
||||
## Explanation
|
||||
|
||||
This pattern has several significant benefits. It creates a clear separation between a test's
|
||||
setup, operations, and results. This structure makes the code easier to read and understand. If
|
||||
you place the steps in order and format your code to separate them, you can scan a test and
|
||||
quickly comprehend what it does.
|
||||
|
||||
It also enforces a certain degree of discipline when you write your tests. You have to think
|
||||
clearly about the three steps your test will perform. It makes tests more natural to write at
|
||||
the same time since you already have an outline.
|
||||
|
||||
Real world example
|
||||
|
||||
> We need to write comprehensive and clear unit test suite for a class.
|
||||
|
||||
In plain words
|
||||
|
||||
> Arrange/Act/Assert is a testing pattern that organizes tests into three clear steps for easy
|
||||
> maintenance.
|
||||
|
||||
WikiWikiWeb says
|
||||
|
||||
> Arrange/Act/Assert is a pattern for arranging and formatting code in UnitTest methods.
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
Let's first introduce our `Cash` class to be unit tested.
|
||||
|
||||
```java
|
||||
public class Cash {
|
||||
|
||||
private int amount;
|
||||
|
||||
Cash(int amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
void plus(int addend) {
|
||||
amount += addend;
|
||||
}
|
||||
|
||||
boolean minus(int subtrahend) {
|
||||
if (amount >= subtrahend) {
|
||||
amount -= subtrahend;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int count() {
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then we write our unit tests according to Arrange/Act/Assert pattern. Notice the clearly
|
||||
separated steps for each unit test.
|
||||
|
||||
```java
|
||||
public class CashAAATest {
|
||||
|
||||
@Test
|
||||
public void testPlus() {
|
||||
//Arrange
|
||||
var cash = new Cash(3);
|
||||
//Act
|
||||
cash.plus(4);
|
||||
//Assert
|
||||
assertEquals(7, cash.count());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMinus() {
|
||||
//Arrange
|
||||
var cash = new Cash(8);
|
||||
//Act
|
||||
var result = cash.minus(5);
|
||||
//Assert
|
||||
assertTrue(result);
|
||||
assertEquals(3, cash.count());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsufficientMinus() {
|
||||
//Arrange
|
||||
var cash = new Cash(1);
|
||||
//Act
|
||||
var result = cash.minus(6);
|
||||
//Assert
|
||||
assertFalse(result);
|
||||
assertEquals(1, cash.count());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdate() {
|
||||
//Arrange
|
||||
var cash = new Cash(5);
|
||||
//Act
|
||||
cash.plus(6);
|
||||
var result = cash.minus(3);
|
||||
//Assert
|
||||
assertTrue(result);
|
||||
assertEquals(8, cash.count());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Applicability
|
||||
|
||||
Use Arrange/Act/Assert pattern when
|
||||
|
||||
* You need to structure your unit tests so that they're easier to read, maintain, and enhance.
|
||||
* you need to structure your unit tests so they're easier to read, maintain, and enhance.
|
||||
|
||||
## Credits
|
||||
|
||||
* [Arrange, Act, Assert: What is AAA Testing?](https://blog.ncrunch.net/post/arrange-act-assert-aaa-testing.aspx)
|
||||
* [Bill Wake: 3A – Arrange, Act, Assert](https://xp123.com/articles/3a-arrange-act-assert/)
|
||||
* [Martin Fowler: GivenWhenThen](https://martinfowler.com/bliki/GivenWhenThen.html)
|
||||
* [xUnit Test Patterns: Refactoring Test Code](https://www.amazon.com/gp/product/0131495054/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0131495054&linkId=99701e8f4af2f7e8dd50d720c9b63dbf)
|
||||
* [Unit Testing Principles, Practices, and Patterns](https://www.amazon.com/gp/product/1617296279/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617296279&linkId=74c75cf22a63c3e4758ae08aa0a0cc35)
|
||||
* [Test Driven Development: By Example](https://www.amazon.com/gp/product/0321146530/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0321146530&linkId=5c63a93d8c1175b84ca5087472ef0e05)
|
||||
|
@ -60,7 +60,7 @@ public class CashAAATest {
|
||||
//Act
|
||||
cash.plus(4);
|
||||
//Assert
|
||||
assertEquals(7, cash.count());
|
||||
assertEquals(cash.count(), 7);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -71,7 +71,7 @@ public class CashAAATest {
|
||||
var result = cash.minus(5);
|
||||
//Assert
|
||||
assertTrue(result);
|
||||
assertEquals(3, cash.count());
|
||||
assertEquals(cash.count(), 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -82,7 +82,7 @@ public class CashAAATest {
|
||||
var result = cash.minus(6);
|
||||
//Assert
|
||||
assertFalse(result);
|
||||
assertEquals(1, cash.count());
|
||||
assertEquals(cash.count(), 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -94,6 +94,6 @@ public class CashAAATest {
|
||||
var result = cash.minus(3);
|
||||
//Assert
|
||||
assertTrue(result);
|
||||
assertEquals(8, cash.count());
|
||||
assertEquals(cash.count(), 8);
|
||||
}
|
||||
}
|
||||
|
@ -44,16 +44,16 @@ public class CashAntiAAATest {
|
||||
var cash = new Cash(3);
|
||||
//test plus
|
||||
cash.plus(4);
|
||||
assertEquals(7, cash.count());
|
||||
assertEquals(cash.count(), 7);
|
||||
//test minus
|
||||
cash = new Cash(8);
|
||||
assertTrue(cash.minus(5));
|
||||
assertEquals(3, cash.count());
|
||||
assertEquals(cash.count(), 3);
|
||||
assertFalse(cash.minus(6));
|
||||
assertEquals(3, cash.count());
|
||||
assertEquals(cash.count(), 3);
|
||||
//test update
|
||||
cash.plus(5);
|
||||
assertTrue(cash.minus(5));
|
||||
assertEquals(3, cash.count());
|
||||
assertEquals(cash.count(), 3);
|
||||
}
|
||||
}
|
||||
|
@ -25,24 +25,12 @@ package com.iluwatar.async.method.invocation;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* Application test
|
||||
*/
|
||||
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}
|
||||
* throws an exception.
|
||||
*/
|
||||
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
|
||||
void test() throws Exception {
|
||||
App.main(new String[]{});
|
||||
}
|
||||
}
|
||||
|
@ -24,26 +24,15 @@
|
||||
package com.iluwatar.balking;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.function.Executable;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
|
||||
/**
|
||||
* Application test
|
||||
*/
|
||||
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}
|
||||
* throws an exception.
|
||||
*/
|
||||
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
assertDoesNotThrow((Executable) App::main);
|
||||
void main() {
|
||||
App.main();
|
||||
}
|
||||
|
||||
}
|
@ -25,22 +25,12 @@ package com.iluwatar.bridge;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* Application test
|
||||
*/
|
||||
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}
|
||||
* throws an exception.
|
||||
*/
|
||||
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
void test() {
|
||||
App.main(new String[]{});
|
||||
}
|
||||
}
|
||||
|
@ -25,23 +25,12 @@ package com.iluwatar.builder;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* Application test
|
||||
*/
|
||||
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}
|
||||
* throws an exception.
|
||||
*/
|
||||
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
void test() {
|
||||
App.main(new String[]{});
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 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.
|
||||
*/
|
||||
|
||||
module com.iluwatar.business.delegate {
|
||||
requires org.slf4j;
|
||||
}
|
@ -27,23 +27,13 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* Tests that Business Delegate 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}
|
||||
* throws an exception.
|
||||
*/
|
||||
|
||||
public class AppTest {
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
public void test() throws IOException {
|
||||
String[] args = {};
|
||||
App.main(args);
|
||||
}
|
||||
}
|
||||
|
@ -58,14 +58,12 @@ public class App {
|
||||
var vm = new VirtualMachine();
|
||||
vm.getWizards()[0] = wizard;
|
||||
|
||||
String literal = "LITERAL 0";
|
||||
|
||||
interpretInstruction(literal, vm);
|
||||
interpretInstruction(literal, vm);
|
||||
interpretInstruction("LITERAL 0", vm);
|
||||
interpretInstruction("LITERAL 0", vm);
|
||||
interpretInstruction("GET_HEALTH", vm);
|
||||
interpretInstruction(literal, vm);
|
||||
interpretInstruction("LITERAL 0", vm);
|
||||
interpretInstruction("GET_AGILITY", vm);
|
||||
interpretInstruction(literal, vm);
|
||||
interpretInstruction("LITERAL 0", vm);
|
||||
interpretInstruction("GET_WISDOM ", vm);
|
||||
interpretInstruction("ADD", vm);
|
||||
interpretInstruction("LITERAL 2", vm);
|
||||
|
@ -25,22 +25,13 @@ package com.iluwatar.bytecode;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* Application test
|
||||
*/
|
||||
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}
|
||||
* throws an exception.
|
||||
*/
|
||||
public class AppTest {
|
||||
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
public void test() {
|
||||
App.main(new String[]{});
|
||||
}
|
||||
}
|
||||
|
@ -27,23 +27,12 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* Tests that Caching 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}
|
||||
* throws an exception.
|
||||
*/
|
||||
|
||||
public class AppTest {
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
public void test() {
|
||||
App.main(new String[]{});
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 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.
|
||||
*/
|
||||
|
||||
module com.iluwatar.callback {
|
||||
requires org.slf4j;
|
||||
}
|
@ -25,23 +25,12 @@ package com.iluwatar.callback;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* Tests that Callback 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}
|
||||
* throws an exception.
|
||||
*/
|
||||
|
||||
public class AppTest {
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
public void test() {
|
||||
App.main(new String[]{});
|
||||
}
|
||||
}
|
||||
|
26
chain/src/main/java/com/iluwatar/chain/module-info.java
Normal file
26
chain/src/main/java/com/iluwatar/chain/module-info.java
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 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.
|
||||
*/
|
||||
|
||||
module com.iluwatar.chain {
|
||||
requires org.slf4j;
|
||||
}
|
@ -25,23 +25,13 @@ package com.iluwatar.chain;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* Application test
|
||||
*/
|
||||
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}
|
||||
* throws an exception.
|
||||
*/
|
||||
public class AppTest {
|
||||
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
public void test() {
|
||||
App.main(new String[]{});
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 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.
|
||||
*/
|
||||
|
||||
module com.iluwatar.collectionpipeline {
|
||||
requires org.slf4j;
|
||||
}
|
@ -39,12 +39,6 @@
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
@ -25,19 +25,12 @@ package com.iluwatar.combinator;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class CombinatorAppTest {
|
||||
|
||||
/**
|
||||
* Issue: Add at least one assertion to this test case.
|
||||
*
|
||||
* Solution: Inserted assertion to check whether the execution of the main method in {@link CombinatorApp#main(String[])}
|
||||
* throws an exception.
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void shouldExecuteApplicationWithoutException() {
|
||||
assertDoesNotThrow(() -> CombinatorApp.main(new String[]{}));
|
||||
public void main() {
|
||||
CombinatorApp.main(new String[]{});
|
||||
}
|
||||
}
|
@ -75,18 +75,19 @@ public class Wizard {
|
||||
Next we present the spell hierarchy.
|
||||
|
||||
```java
|
||||
public interface Command {
|
||||
public abstract class Command {
|
||||
|
||||
void execute(Target target);
|
||||
public abstract void execute(Target target);
|
||||
|
||||
void undo();
|
||||
public abstract void undo();
|
||||
|
||||
void redo();
|
||||
public abstract void redo();
|
||||
|
||||
String toString();
|
||||
@Override
|
||||
public abstract String toString();
|
||||
}
|
||||
|
||||
public class InvisibilitySpell implements Command {
|
||||
public class InvisibilitySpell extends Command {
|
||||
|
||||
private Target target;
|
||||
|
||||
@ -116,7 +117,7 @@ public class InvisibilitySpell implements Command {
|
||||
}
|
||||
}
|
||||
|
||||
public class ShrinkSpell implements Command {
|
||||
public class ShrinkSpell extends Command {
|
||||
|
||||
private Size oldSize;
|
||||
private Target target;
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 27 KiB |
@ -4,7 +4,7 @@ package com.iluwatar.command {
|
||||
+ App()
|
||||
+ main(args : String[]) {static}
|
||||
}
|
||||
interface Command {
|
||||
abstract class Command {
|
||||
+ Command()
|
||||
+ execute(Target) {abstract}
|
||||
+ redo() {abstract}
|
||||
@ -77,7 +77,7 @@ ShrinkSpell --> "-oldSize" Size
|
||||
InvisibilitySpell --> "-target" Target
|
||||
ShrinkSpell --> "-target" Target
|
||||
Target --> "-visibility" Visibility
|
||||
Goblin --|> Target
|
||||
InvisibilitySpell ..|> Command
|
||||
ShrinkSpell ..|> Command
|
||||
@enduml
|
||||
Goblin --|> Target
|
||||
InvisibilitySpell --|> Command
|
||||
ShrinkSpell --|> Command
|
||||
@enduml
|
@ -26,12 +26,15 @@ package com.iluwatar.command;
|
||||
/**
|
||||
* Interface for Commands.
|
||||
*/
|
||||
public interface Command {
|
||||
void execute(Target target);
|
||||
public abstract class Command {
|
||||
|
||||
void undo();
|
||||
public abstract void execute(Target target);
|
||||
|
||||
void redo();
|
||||
public abstract void undo();
|
||||
|
||||
public abstract void redo();
|
||||
|
||||
@Override
|
||||
public abstract String toString();
|
||||
|
||||
String toString();
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ package com.iluwatar.command;
|
||||
/**
|
||||
* InvisibilitySpell is a concrete command.
|
||||
*/
|
||||
public class InvisibilitySpell implements Command {
|
||||
public class InvisibilitySpell extends Command {
|
||||
|
||||
private Target target;
|
||||
|
||||
|
@ -26,7 +26,7 @@ package com.iluwatar.command;
|
||||
/**
|
||||
* ShrinkSpell is a concrete command.
|
||||
*/
|
||||
public class ShrinkSpell implements Command {
|
||||
public class ShrinkSpell extends Command {
|
||||
|
||||
private Size oldSize;
|
||||
private Target target;
|
||||
|
26
command/src/main/java/com/iluwatar/command/module-info.java
Normal file
26
command/src/main/java/com/iluwatar/command/module-info.java
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 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.
|
||||
*/
|
||||
|
||||
module com.iluwatar.command {
|
||||
requires org.slf4j;
|
||||
}
|
@ -25,21 +25,12 @@ package com.iluwatar.command;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* Tests that Command 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.
|
||||
*/
|
||||
public class AppTest {
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
public void test() {
|
||||
App.main(new String[]{});
|
||||
}
|
||||
}
|
||||
|
@ -36,11 +36,9 @@ import com.iluwatar.commander.queue.QueueDatabase;
|
||||
import com.iluwatar.commander.queue.QueueTask;
|
||||
import com.iluwatar.commander.queue.QueueTask.TaskType;
|
||||
import com.iluwatar.commander.shippingservice.ShippingService;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
/**
|
||||
* <p>Commander pattern is used to handle all issues that can come up while making a
|
||||
* distributed transaction. The idea is to have a commander, which coordinates the execution of all
|
||||
@ -161,8 +159,8 @@ public class Commander {
|
||||
|
||||
private void sendPaymentRequest(Order order) {
|
||||
if (System.currentTimeMillis() - order.createdTime >= this.paymentTime) {
|
||||
if (order.paid.equals(PaymentStatus.TRYING)) {
|
||||
order.paid = PaymentStatus.NOT_DONE;
|
||||
if (order.paid.equals(PaymentStatus.Trying)) {
|
||||
order.paid = PaymentStatus.NotDone;
|
||||
sendPaymentFailureMessage(order);
|
||||
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
|
||||
@ -174,15 +172,15 @@ public class Commander {
|
||||
if (!l.isEmpty()) {
|
||||
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
|
||||
LOG.debug("Order " + order.id + ": Error in connecting to payment service,"
|
||||
+ " trying again..");
|
||||
+ " trying again..");
|
||||
} else {
|
||||
LOG.debug("Order " + order.id + ": Error in creating payment request..");
|
||||
}
|
||||
throw l.remove(0);
|
||||
}
|
||||
if (order.paid.equals(PaymentStatus.TRYING)) {
|
||||
if (order.paid.equals(PaymentStatus.Trying)) {
|
||||
var transactionId = paymentService.receiveRequest(order.price);
|
||||
order.paid = PaymentStatus.DONE;
|
||||
order.paid = PaymentStatus.Done;
|
||||
LOG.info("Order " + order.id + ": Payment successful, transaction Id: " + transactionId);
|
||||
if (!finalSiteMsgShown) {
|
||||
LOG.info("Payment made successfully, thank you for shopping with us!!");
|
||||
@ -195,26 +193,26 @@ public class Commander {
|
||||
if (PaymentDetailsErrorException.class.isAssignableFrom(err.getClass())) {
|
||||
if (!finalSiteMsgShown) {
|
||||
LOG.info("There was an error in payment. Your account/card details "
|
||||
+ "may have been incorrect. "
|
||||
+ "Meanwhile, your order has been converted to COD and will be shipped.");
|
||||
+ "may have been incorrect. "
|
||||
+ "Meanwhile, your order has been converted to COD and will be shipped.");
|
||||
finalSiteMsgShown = true;
|
||||
}
|
||||
LOG.error("Order " + order.id + ": Payment details incorrect, failed..");
|
||||
o.paid = PaymentStatus.NOT_DONE;
|
||||
o.paid = PaymentStatus.NotDone;
|
||||
sendPaymentFailureMessage(o);
|
||||
} else {
|
||||
if (o.messageSent.equals(MessageSent.NONE_SENT)) {
|
||||
if (o.messageSent.equals(MessageSent.NoneSent)) {
|
||||
if (!finalSiteMsgShown) {
|
||||
LOG.info("There was an error in payment. We are on it, and will get back to you "
|
||||
+ "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;
|
||||
}
|
||||
LOG.warn("Order " + order.id + ": Payment error, going to queue..");
|
||||
sendPaymentPossibleErrorMsg(o);
|
||||
}
|
||||
if (o.paid.equals(PaymentStatus.TRYING) && System
|
||||
.currentTimeMillis() - o.createdTime < paymentTime) {
|
||||
var qt = new QueueTask(o, TaskType.PAYMENT, -1);
|
||||
if (o.paid.equals(PaymentStatus.Trying) && System
|
||||
.currentTimeMillis() - o.createdTime < paymentTime) {
|
||||
var qt = new QueueTask(o, TaskType.Payment, -1);
|
||||
updateQueue(qt);
|
||||
}
|
||||
}
|
||||
@ -236,12 +234,12 @@ public class Commander {
|
||||
// additional check not needed
|
||||
LOG.trace("Order " + qt.order.id + ": Queue time for order over, failed..");
|
||||
return;
|
||||
} else if (qt.taskType.equals(TaskType.PAYMENT) && !qt.order.paid.equals(PaymentStatus.TRYING)
|
||||
|| qt.taskType.equals(TaskType.MESSAGING) && (qt.messageType == 1
|
||||
&& !qt.order.messageSent.equals(MessageSent.NONE_SENT)
|
||||
|| qt.order.messageSent.equals(MessageSent.PAYMENT_FAIL)
|
||||
|| qt.order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL))
|
||||
|| qt.taskType.equals(TaskType.EMPLOYEE_DB) && qt.order.addedToEmployeeHandle) {
|
||||
} else if (qt.taskType.equals(TaskType.Payment) && !qt.order.paid.equals(PaymentStatus.Trying)
|
||||
|| qt.taskType.equals(TaskType.Messaging) && (qt.messageType == 1
|
||||
&& !qt.order.messageSent.equals(MessageSent.NoneSent)
|
||||
|| qt.order.messageSent.equals(MessageSent.PaymentFail)
|
||||
|| qt.order.messageSent.equals(MessageSent.PaymentSuccessful))
|
||||
|| qt.taskType.equals(TaskType.EmployeeDb) && qt.order.addedToEmployeeHandle) {
|
||||
LOG.trace("Order " + qt.order.id + ": Not queueing task since task already done..");
|
||||
return;
|
||||
}
|
||||
@ -258,8 +256,8 @@ public class Commander {
|
||||
tryDoingTasksInQueue();
|
||||
};
|
||||
Retry.HandleErrorIssue<QueueTask> handleError = (qt1, err) -> {
|
||||
if (qt1.taskType.equals(TaskType.PAYMENT)) {
|
||||
qt1.order.paid = PaymentStatus.NOT_DONE;
|
||||
if (qt1.taskType.equals(TaskType.Payment)) {
|
||||
qt1.order.paid = PaymentStatus.NotDone;
|
||||
sendPaymentFailureMessage(qt1.order);
|
||||
LOG.error("Order " + qt1.order.id + ": Unable to enqueue payment task,"
|
||||
+ " payment failed..");
|
||||
@ -333,9 +331,35 @@ public class Commander {
|
||||
}
|
||||
var list = messagingService.exceptionsList;
|
||||
Thread t = new Thread(() -> {
|
||||
Retry.Operation op = handleSuccessMessageRetryOperation(order);
|
||||
Retry.Operation op = (l) -> {
|
||||
if (!l.isEmpty()) {
|
||||
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
|
||||
LOG.debug("Order " + order.id + ": Error in connecting to messaging service "
|
||||
+ "(Payment Success msg), trying again..");
|
||||
} else {
|
||||
LOG.debug("Order " + order.id + ": Error in creating Payment Success"
|
||||
+ " messaging request..");
|
||||
}
|
||||
throw l.remove(0);
|
||||
}
|
||||
if (!order.messageSent.equals(MessageSent.PaymentFail)
|
||||
&& !order.messageSent.equals(MessageSent.PaymentSuccessful)) {
|
||||
var requestId = messagingService.receiveRequest(2);
|
||||
order.messageSent = MessageSent.PaymentSuccessful;
|
||||
LOG.info("Order " + order.id + ": Payment Success message sent,"
|
||||
+ " request Id: " + requestId);
|
||||
}
|
||||
};
|
||||
Retry.HandleErrorIssue<Order> handleError = (o, err) -> {
|
||||
handleSuccessMessageErrorIssue(order, o);
|
||||
if ((o.messageSent.equals(MessageSent.NoneSent) || o.messageSent
|
||||
.equals(MessageSent.PaymentTrying))
|
||||
&& System.currentTimeMillis() - o.createdTime < messageTime) {
|
||||
var qt = new QueueTask(order, TaskType.Messaging, 2);
|
||||
updateQueue(qt);
|
||||
LOG.info("Order " + order.id + ": Error in sending Payment Success message, trying to"
|
||||
+ " queue task and add to employee handle..");
|
||||
employeeHandleIssue(order);
|
||||
}
|
||||
};
|
||||
var r = new Retry<>(op, handleError, numOfRetries, retryDuration,
|
||||
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
|
||||
@ -348,40 +372,6 @@ public class Commander {
|
||||
t.start();
|
||||
}
|
||||
|
||||
private void handleSuccessMessageErrorIssue(Order order, Order o) {
|
||||
if ((o.messageSent.equals(MessageSent.NONE_SENT) || o.messageSent
|
||||
.equals(MessageSent.PAYMENT_TRYING))
|
||||
&& System.currentTimeMillis() - o.createdTime < messageTime) {
|
||||
var qt = new QueueTask(order, TaskType.MESSAGING, 2);
|
||||
updateQueue(qt);
|
||||
LOG.info("Order " + order.id + ": Error in sending Payment Success message, trying to"
|
||||
+ " queue task and add to employee handle..");
|
||||
employeeHandleIssue(order);
|
||||
}
|
||||
}
|
||||
|
||||
private Retry.Operation handleSuccessMessageRetryOperation(Order order) {
|
||||
return (l) -> {
|
||||
if (!l.isEmpty()) {
|
||||
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
|
||||
LOG.debug("Order " + order.id + ": Error in connecting to messaging service "
|
||||
+ "(Payment Success msg), trying again..");
|
||||
} else {
|
||||
LOG.debug("Order " + order.id + ": Error in creating Payment Success"
|
||||
+ " messaging request..");
|
||||
}
|
||||
throw l.remove(0);
|
||||
}
|
||||
if (!order.messageSent.equals(MessageSent.PAYMENT_FAIL)
|
||||
&& !order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) {
|
||||
var requestId = messagingService.receiveRequest(2);
|
||||
order.messageSent = MessageSent.PAYMENT_SUCCESSFUL;
|
||||
LOG.info("Order " + order.id + ": Payment Success message sent,"
|
||||
+ " request Id: " + requestId);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void sendPaymentFailureMessage(Order order) {
|
||||
if (System.currentTimeMillis() - order.createdTime >= this.messageTime) {
|
||||
LOG.trace("Order " + order.id + ": Message time for order over, returning..");
|
||||
@ -390,10 +380,34 @@ public class Commander {
|
||||
var list = messagingService.exceptionsList;
|
||||
var t = new Thread(() -> {
|
||||
Retry.Operation op = (l) -> {
|
||||
handlePaymentFailureRetryOperation(order, l);
|
||||
if (!l.isEmpty()) {
|
||||
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
|
||||
LOG.debug("Order " + order.id + ": Error in connecting to messaging service "
|
||||
+ "(Payment Failure msg), trying again..");
|
||||
} else {
|
||||
LOG.debug("Order " + order.id + ": Error in creating Payment Failure"
|
||||
+ " message request..");
|
||||
}
|
||||
throw l.remove(0);
|
||||
}
|
||||
if (!order.messageSent.equals(MessageSent.PaymentFail)
|
||||
&& !order.messageSent.equals(MessageSent.PaymentSuccessful)) {
|
||||
var requestId = messagingService.receiveRequest(0);
|
||||
order.messageSent = MessageSent.PaymentFail;
|
||||
LOG.info("Order " + order.id + ": Payment Failure message sent successfully,"
|
||||
+ " request Id: " + requestId);
|
||||
}
|
||||
};
|
||||
Retry.HandleErrorIssue<Order> handleError = (o, err) -> {
|
||||
handlePaymentErrorIssue(order, o);
|
||||
if ((o.messageSent.equals(MessageSent.NoneSent) || o.messageSent
|
||||
.equals(MessageSent.PaymentTrying))
|
||||
&& System.currentTimeMillis() - o.createdTime < messageTime) {
|
||||
var qt = new QueueTask(order, TaskType.Messaging, 0);
|
||||
updateQueue(qt);
|
||||
LOG.warn("Order " + order.id + ": Error in sending Payment Failure message, "
|
||||
+ "trying to queue task and add to employee handle..");
|
||||
employeeHandleIssue(o);
|
||||
}
|
||||
};
|
||||
var r = new Retry<>(op, handleError, numOfRetries, retryDuration,
|
||||
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
|
||||
@ -406,38 +420,6 @@ public class Commander {
|
||||
t.start();
|
||||
}
|
||||
|
||||
private void handlePaymentErrorIssue(Order order, Order o) {
|
||||
if ((o.messageSent.equals(MessageSent.NONE_SENT) || o.messageSent
|
||||
.equals(MessageSent.PAYMENT_TRYING))
|
||||
&& System.currentTimeMillis() - o.createdTime < messageTime) {
|
||||
var qt = new QueueTask(order, TaskType.MESSAGING, 0);
|
||||
updateQueue(qt);
|
||||
LOG.warn("Order " + order.id + ": Error in sending Payment Failure message, "
|
||||
+ "trying to queue task and add to employee handle..");
|
||||
employeeHandleIssue(o);
|
||||
}
|
||||
}
|
||||
|
||||
private void handlePaymentFailureRetryOperation(Order order, List<Exception> l) throws Exception {
|
||||
if (!l.isEmpty()) {
|
||||
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
|
||||
LOG.debug("Order " + order.id + ": Error in connecting to messaging service "
|
||||
+ "(Payment Failure msg), trying again..");
|
||||
} else {
|
||||
LOG.debug("Order " + order.id + ": Error in creating Payment Failure"
|
||||
+ " message request..");
|
||||
}
|
||||
throw l.remove(0);
|
||||
}
|
||||
if (!order.messageSent.equals(MessageSent.PAYMENT_FAIL)
|
||||
&& !order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) {
|
||||
var requestId = messagingService.receiveRequest(0);
|
||||
order.messageSent = MessageSent.PAYMENT_FAIL;
|
||||
LOG.info("Order " + order.id + ": Payment Failure message sent successfully,"
|
||||
+ " request Id: " + requestId);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendPaymentPossibleErrorMsg(Order order) {
|
||||
if (System.currentTimeMillis() - order.createdTime >= this.messageTime) {
|
||||
LOG.trace("Message time for order over, returning..");
|
||||
@ -446,10 +428,34 @@ public class Commander {
|
||||
var list = messagingService.exceptionsList;
|
||||
var t = new Thread(() -> {
|
||||
Retry.Operation op = (l) -> {
|
||||
handlePaymentPossibleErrorMsgRetryOperation(order, l);
|
||||
if (!l.isEmpty()) {
|
||||
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
|
||||
LOG.debug("Order " + order.id + ": Error in connecting to messaging service "
|
||||
+ "(Payment Error msg), trying again..");
|
||||
} else {
|
||||
LOG.debug("Order " + order.id + ": Error in creating Payment Error"
|
||||
+ " messaging request..");
|
||||
}
|
||||
throw l.remove(0);
|
||||
}
|
||||
if (order.paid.equals(PaymentStatus.Trying) && order.messageSent
|
||||
.equals(MessageSent.NoneSent)) {
|
||||
var requestId = messagingService.receiveRequest(1);
|
||||
order.messageSent = MessageSent.PaymentTrying;
|
||||
LOG.info("Order " + order.id + ": Payment Error message sent successfully,"
|
||||
+ " request Id: " + requestId);
|
||||
}
|
||||
};
|
||||
Retry.HandleErrorIssue<Order> handleError = (o, err) -> {
|
||||
handlePaymentPossibleErrorMsgErrorIssue(order, o);
|
||||
if (o.messageSent.equals(MessageSent.NoneSent) && order.paid
|
||||
.equals(PaymentStatus.Trying)
|
||||
&& System.currentTimeMillis() - o.createdTime < messageTime) {
|
||||
var qt = new QueueTask(order, TaskType.Messaging, 1);
|
||||
updateQueue(qt);
|
||||
LOG.warn("Order " + order.id + ": Error in sending Payment Error message, "
|
||||
+ "trying to queue task and add to employee handle..");
|
||||
employeeHandleIssue(o);
|
||||
}
|
||||
};
|
||||
var r = new Retry<>(op, handleError, numOfRetries, retryDuration,
|
||||
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
|
||||
@ -462,39 +468,6 @@ public class Commander {
|
||||
t.start();
|
||||
}
|
||||
|
||||
private void handlePaymentPossibleErrorMsgErrorIssue(Order order, Order o) {
|
||||
if (o.messageSent.equals(MessageSent.NONE_SENT) && order.paid
|
||||
.equals(PaymentStatus.TRYING)
|
||||
&& System.currentTimeMillis() - o.createdTime < messageTime) {
|
||||
var qt = new QueueTask(order, TaskType.MESSAGING, 1);
|
||||
updateQueue(qt);
|
||||
LOG.warn("Order " + order.id + ": Error in sending Payment Error message, "
|
||||
+ "trying to queue task and add to employee handle..");
|
||||
employeeHandleIssue(o);
|
||||
}
|
||||
}
|
||||
|
||||
private void handlePaymentPossibleErrorMsgRetryOperation(Order order, List<Exception> l)
|
||||
throws Exception {
|
||||
if (!l.isEmpty()) {
|
||||
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
|
||||
LOG.debug("Order " + order.id + ": Error in connecting to messaging service "
|
||||
+ "(Payment Error msg), trying again..");
|
||||
} else {
|
||||
LOG.debug("Order " + order.id + ": Error in creating Payment Error"
|
||||
+ " messaging request..");
|
||||
}
|
||||
throw l.remove(0);
|
||||
}
|
||||
if (order.paid.equals(PaymentStatus.TRYING) && order.messageSent
|
||||
.equals(MessageSent.NONE_SENT)) {
|
||||
var requestId = messagingService.receiveRequest(1);
|
||||
order.messageSent = MessageSent.PAYMENT_TRYING;
|
||||
LOG.info("Order " + order.id + ": Payment Error message sent successfully,"
|
||||
+ " request Id: " + requestId);
|
||||
}
|
||||
}
|
||||
|
||||
private void employeeHandleIssue(Order order) {
|
||||
if (System.currentTimeMillis() - order.createdTime >= this.employeeTime) {
|
||||
LOG.trace("Order " + order.id + ": Employee handle time for order over, returning..");
|
||||
@ -517,7 +490,7 @@ public class Commander {
|
||||
Retry.HandleErrorIssue<Order> handleError = (o, err) -> {
|
||||
if (!o.addedToEmployeeHandle && System
|
||||
.currentTimeMillis() - order.createdTime < employeeTime) {
|
||||
var qt = new QueueTask(order, TaskType.EMPLOYEE_DB, -1);
|
||||
var qt = new QueueTask(order, TaskType.EmployeeDb, -1);
|
||||
updateQueue(qt);
|
||||
LOG.warn("Order " + order.id + ": Error in adding to employee db,"
|
||||
+ " trying to queue task..");
|
||||
@ -547,21 +520,21 @@ public class Commander {
|
||||
LOG.trace("Order " + qt.order.id + ": This queue task of type " + qt.getType()
|
||||
+ " does not need to be done anymore (timeout), dequeue..");
|
||||
} else {
|
||||
if (qt.taskType.equals(TaskType.PAYMENT)) {
|
||||
if (!qt.order.paid.equals(PaymentStatus.TRYING)) {
|
||||
if (qt.taskType.equals(TaskType.Payment)) {
|
||||
if (!qt.order.paid.equals(PaymentStatus.Trying)) {
|
||||
tryDequeue();
|
||||
LOG.trace("Order " + qt.order.id + ": This payment task already done, dequeueing..");
|
||||
} else {
|
||||
sendPaymentRequest(qt.order);
|
||||
LOG.debug("Order " + qt.order.id + ": Trying to connect to payment service..");
|
||||
}
|
||||
} else if (qt.taskType.equals(TaskType.MESSAGING)) {
|
||||
if (qt.order.messageSent.equals(MessageSent.PAYMENT_FAIL)
|
||||
|| qt.order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) {
|
||||
} else if (qt.taskType.equals(TaskType.Messaging)) {
|
||||
if (qt.order.messageSent.equals(MessageSent.PaymentFail)
|
||||
|| qt.order.messageSent.equals(MessageSent.PaymentSuccessful)) {
|
||||
tryDequeue();
|
||||
LOG.trace("Order " + qt.order.id + ": This messaging task already done, dequeue..");
|
||||
} else if (qt.messageType == 1 && (!qt.order.messageSent.equals(MessageSent.NONE_SENT)
|
||||
|| !qt.order.paid.equals(PaymentStatus.TRYING))) {
|
||||
} else if (qt.messageType == 1 && (!qt.order.messageSent.equals(MessageSent.NoneSent)
|
||||
|| !qt.order.paid.equals(PaymentStatus.Trying))) {
|
||||
tryDequeue();
|
||||
LOG.trace("Order " + qt.order.id + ": This messaging task does not need to be done,"
|
||||
+ " dequeue..");
|
||||
@ -575,7 +548,7 @@ public class Commander {
|
||||
sendSuccessMessage(qt.order);
|
||||
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.EmployeeDb)) {
|
||||
if (qt.order.addedToEmployeeHandle) {
|
||||
tryDequeue();
|
||||
LOG.trace("Order " + qt.order.id + ": This employee handle task already done,"
|
||||
|
@ -33,11 +33,11 @@ import java.util.Random;
|
||||
public class Order { //can store all transactions ids also
|
||||
|
||||
enum PaymentStatus {
|
||||
NOT_DONE, TRYING, DONE
|
||||
NotDone, Trying, Done
|
||||
}
|
||||
|
||||
enum MessageSent {
|
||||
NONE_SENT, PAYMENT_FAIL, PAYMENT_TRYING, PAYMENT_SUCCESSFUL
|
||||
NoneSent, PaymentFail, PaymentTrying, PaymentSuccessful
|
||||
}
|
||||
|
||||
final User user;
|
||||
@ -65,8 +65,8 @@ public class Order { //can store all transactions ids also
|
||||
}
|
||||
this.id = id;
|
||||
USED_IDS.put(this.id, true);
|
||||
this.paid = PaymentStatus.TRYING;
|
||||
this.messageSent = MessageSent.NONE_SENT;
|
||||
this.paid = PaymentStatus.Trying;
|
||||
this.messageSent = MessageSent.NoneSent;
|
||||
this.addedToEmployeeHandle = false;
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ public class MessagingService extends Service {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(MessagingService.class);
|
||||
|
||||
enum MessageToSend {
|
||||
PAYMENT_FAIL, PAYMENT_TRYING, PAYMENT_SUCCESSFUL
|
||||
PaymentFail, PaymentTrying, PaymentSuccessful
|
||||
}
|
||||
|
||||
class MessageRequest {
|
||||
@ -63,11 +63,11 @@ public class MessagingService extends Service {
|
||||
var id = generateId();
|
||||
MessageToSend msg;
|
||||
if (messageToSend == 0) {
|
||||
msg = MessageToSend.PAYMENT_FAIL;
|
||||
msg = MessageToSend.PaymentFail;
|
||||
} else if (messageToSend == 1) {
|
||||
msg = MessageToSend.PAYMENT_TRYING;
|
||||
msg = MessageToSend.PaymentTrying;
|
||||
} else { //messageToSend == 2
|
||||
msg = MessageToSend.PAYMENT_SUCCESSFUL;
|
||||
msg = MessageToSend.PaymentSuccessful;
|
||||
}
|
||||
var req = new MessageRequest(id, msg);
|
||||
return updateDb(req);
|
||||
@ -84,10 +84,10 @@ public class MessagingService extends Service {
|
||||
}
|
||||
|
||||
String sendMessage(MessageToSend m) {
|
||||
if (m.equals(MessageToSend.PAYMENT_SUCCESSFUL)) {
|
||||
if (m.equals(MessageToSend.PaymentSuccessful)) {
|
||||
return "Msg: Your order has been placed and paid for successfully!"
|
||||
+ " Thank you for shopping with us!";
|
||||
} else if (m.equals(MessageToSend.PAYMENT_TRYING)) {
|
||||
} else if (m.equals(MessageToSend.PaymentTrying)) {
|
||||
return "Msg: There was an error in your payment process,"
|
||||
+ " we are working on it and will return back to you shortly."
|
||||
+ " Meanwhile, your order has been placed and will be shipped.";
|
||||
|
@ -36,7 +36,7 @@ public class QueueTask {
|
||||
*/
|
||||
|
||||
public enum TaskType {
|
||||
MESSAGING, PAYMENT, EMPLOYEE_DB
|
||||
Messaging, Payment, EmployeeDb
|
||||
}
|
||||
|
||||
public Order order;
|
||||
@ -68,7 +68,7 @@ public class QueueTask {
|
||||
* @return String representing type of task
|
||||
*/
|
||||
public String getType() {
|
||||
if (!this.taskType.equals(TaskType.MESSAGING)) {
|
||||
if (!this.taskType.equals(TaskType.Messaging)) {
|
||||
return this.taskType.toString();
|
||||
} else {
|
||||
if (this.messageType == 0) {
|
||||
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 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.
|
||||
*/
|
||||
|
||||
module com.iluwatar.composite {
|
||||
requires org.slf4j;
|
||||
}
|
@ -23,23 +23,15 @@
|
||||
|
||||
package com.iluwatar.composite;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Application test
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public class AppTest {
|
||||
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
Assertions.assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
public void test() {
|
||||
App.main(new String[]{});
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 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.
|
||||
*/
|
||||
|
||||
module com.iluwatar.converter {
|
||||
requires org.slf4j;
|
||||
}
|
@ -25,24 +25,14 @@ package com.iluwatar.converter;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* App running test
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public class AppTest {
|
||||
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
public void testMain() {
|
||||
App.main(new String[]{});
|
||||
}
|
||||
|
||||
}
|
||||
|
29
dao/src/main/java/com/iluwatar/dao/module-info.java
Normal file
29
dao/src/main/java/com/iluwatar/dao/module-info.java
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 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.
|
||||
*/
|
||||
|
||||
module com.iluwatar.dao {
|
||||
requires org.slf4j;
|
||||
requires java.sql;
|
||||
requires h2;
|
||||
requires java.naming;
|
||||
}
|
@ -25,22 +25,12 @@ package com.iluwatar.dao;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* Tests that DAO 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.
|
||||
*/
|
||||
|
||||
public class AppTest {
|
||||
@Test
|
||||
void shouldExecuteDaoWithoutException() {
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
public void test() throws Exception {
|
||||
App.main(new String[]{});
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +43,6 @@ public class AiComponent implements Component {
|
||||
|
||||
@Override
|
||||
public void render() {
|
||||
// Do Nothing.
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ public class AiComponentManager {
|
||||
|
||||
private final int numEntities;
|
||||
|
||||
private final Component[] aiComponents = new AiComponent[MAX_ENTITIES];
|
||||
private static final Component[] AI_COMPONENTS = new AiComponent[MAX_ENTITIES];
|
||||
|
||||
public AiComponentManager(int numEntities) {
|
||||
this.numEntities = numEntities;
|
||||
@ -51,7 +51,7 @@ public class AiComponentManager {
|
||||
*/
|
||||
public void start() {
|
||||
LOGGER.info("Start AI Game Component");
|
||||
IntStream.range(0, numEntities).forEach(i -> aiComponents[i] = new AiComponent());
|
||||
IntStream.range(0, numEntities).forEach(i -> AI_COMPONENTS[i] = new AiComponent());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -60,7 +60,7 @@ public class AiComponentManager {
|
||||
public void update() {
|
||||
LOGGER.info("Update AI Game Component");
|
||||
IntStream.range(0, numEntities)
|
||||
.filter(i -> aiComponents.length > i && aiComponents[i] != null)
|
||||
.forEach(i -> aiComponents[i].update());
|
||||
.filter(i -> AI_COMPONENTS.length > i && AI_COMPONENTS[i] != null)
|
||||
.forEach(i -> AI_COMPONENTS[i].update());
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ public class PhysicsComponentManager {
|
||||
|
||||
private final int numEntities;
|
||||
|
||||
private final Component[] physicsComponents = new PhysicsComponent[MAX_ENTITIES];
|
||||
private static final Component[] PHYSICS_COMPONENTS = new PhysicsComponent[MAX_ENTITIES];
|
||||
|
||||
public PhysicsComponentManager(int numEntities) {
|
||||
this.numEntities = numEntities;
|
||||
@ -51,7 +51,7 @@ public class PhysicsComponentManager {
|
||||
*/
|
||||
public void start() {
|
||||
LOGGER.info("Start Physics Game Component ");
|
||||
IntStream.range(0, numEntities).forEach(i -> physicsComponents[i] = new PhysicsComponent());
|
||||
IntStream.range(0, numEntities).forEach(i -> PHYSICS_COMPONENTS[i] = new PhysicsComponent());
|
||||
}
|
||||
|
||||
|
||||
@ -62,7 +62,7 @@ public class PhysicsComponentManager {
|
||||
LOGGER.info("Update Physics Game Component ");
|
||||
// Process physics.
|
||||
IntStream.range(0, numEntities)
|
||||
.filter(i -> physicsComponents.length > i && physicsComponents[i] != null)
|
||||
.forEach(i -> physicsComponents[i].update());
|
||||
.filter(i -> PHYSICS_COMPONENTS.length > i && PHYSICS_COMPONENTS[i] != null)
|
||||
.forEach(i -> PHYSICS_COMPONENTS[i].update());
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ public class RenderComponentManager {
|
||||
|
||||
private final int numEntities;
|
||||
|
||||
private final Component[] renderComponents = new RenderComponent[MAX_ENTITIES];
|
||||
private static final Component[] RENDER_COMPONENTS = new RenderComponent[MAX_ENTITIES];
|
||||
|
||||
public RenderComponentManager(int numEntities) {
|
||||
this.numEntities = numEntities;
|
||||
@ -51,7 +51,7 @@ public class RenderComponentManager {
|
||||
*/
|
||||
public void start() {
|
||||
LOGGER.info("Start Render Game Component ");
|
||||
IntStream.range(0, numEntities).forEach(i -> renderComponents[i] = new RenderComponent());
|
||||
IntStream.range(0, numEntities).forEach(i -> RENDER_COMPONENTS[i] = new RenderComponent());
|
||||
}
|
||||
|
||||
|
||||
@ -62,7 +62,7 @@ public class RenderComponentManager {
|
||||
LOGGER.info("Update Render Game Component ");
|
||||
// Process Render.
|
||||
IntStream.range(0, numEntities)
|
||||
.filter(i -> renderComponents.length > i && renderComponents[i] != null)
|
||||
.forEach(i -> renderComponents[i].render());
|
||||
.filter(i -> RENDER_COMPONENTS.length > i && RENDER_COMPONENTS[i] != null)
|
||||
.forEach(i -> RENDER_COMPONENTS[i].render());
|
||||
}
|
||||
}
|
||||
|
@ -26,22 +26,16 @@ package com.iluwatar.data.locality;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* Test Game Application
|
||||
*/
|
||||
class ApplicationTest {
|
||||
|
||||
/**
|
||||
* Issue: Add at least one assertion to this test case.
|
||||
*
|
||||
* Solution: Inserted assertion to check whether the execution of the main method in {@link Application#main(String[])}
|
||||
* throws an exception.
|
||||
* Test run
|
||||
*/
|
||||
|
||||
@Test
|
||||
void shouldExecuteGameApplicationWithoutException() {
|
||||
assertDoesNotThrow(() -> Application.main(new String[] {}));
|
||||
void main() {
|
||||
Application.main(new String[] {});
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 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.
|
||||
*/
|
||||
|
||||
module com.iluwatar.datamapper {
|
||||
requires org.slf4j;
|
||||
}
|
@ -24,25 +24,14 @@
|
||||
package com.iluwatar.datamapper;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.function.Executable;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* Tests that Data-Mapper example runs without errors.
|
||||
*/
|
||||
final 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.
|
||||
*/
|
||||
public final class AppTest {
|
||||
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
|
||||
assertDoesNotThrow((Executable) App::main);
|
||||
public void test() {
|
||||
App.main();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 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.
|
||||
*/
|
||||
|
||||
module com.iluwatar.datatransfer {
|
||||
requires org.slf4j;
|
||||
}
|
@ -25,19 +25,9 @@ package com.iluwatar.datatransfer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
class AppTest {
|
||||
|
||||
/**
|
||||
* Issue: Add at least one assertion to this test case.
|
||||
*
|
||||
* Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])}
|
||||
* throws an exception.
|
||||
*/
|
||||
|
||||
public class AppTest {
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
public void test() throws Exception {
|
||||
App.main(new String[]{});
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 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.
|
||||
*/
|
||||
|
||||
module com.iluwatar.decorator {
|
||||
requires org.slf4j;
|
||||
}
|
@ -25,22 +25,13 @@ package com.iluwatar.decorator;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* Application test
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public class AppTest {
|
||||
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
public void test() {
|
||||
App.main(new String[]{});
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 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.
|
||||
*/
|
||||
|
||||
module com.iluwatar.delegation {
|
||||
requires org.slf4j;
|
||||
}
|
@ -25,23 +25,14 @@ package com.iluwatar.delegation.simple;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* Application Test Entry
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public class AppTest {
|
||||
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
public void test() {
|
||||
App.main(new String[]{});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -25,22 +25,13 @@ package com.iluwatar.dependency.injection;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* Application test
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public class AppTest {
|
||||
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
public void test() {
|
||||
App.main(new String[]{});
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@
|
||||
<artifactId>java-design-patterns</artifactId>
|
||||
<version>1.23.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<groupId>com.iluwatar</groupId>
|
||||
<artifactId>dirty-flag</artifactId>
|
||||
<version>1.23.0-SNAPSHOT</version>
|
||||
<name>dirty-flag</name>
|
||||
@ -44,12 +45,6 @@
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-junit-jupiter</artifactId>
|
||||
<version>3.5.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 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.
|
||||
*/
|
||||
|
||||
module com.iluwatar.dirtyflag {
|
||||
requires org.slf4j;
|
||||
}
|
@ -26,22 +26,12 @@ package org.dirty.flag;
|
||||
import com.iluwatar.dirtyflag.App;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* Tests that Dirty-Flag 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.
|
||||
*/
|
||||
|
||||
public class AppTest {
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
public void test() {
|
||||
App.main(new String[]{});
|
||||
}
|
||||
}
|
||||
|
@ -23,27 +23,29 @@
|
||||
|
||||
package org.dirty.flag;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import com.iluwatar.dirtyflag.DataFetcher;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Application test
|
||||
*/
|
||||
class DirtyFlagTest {
|
||||
public class DirtyFlagTest {
|
||||
|
||||
@Test
|
||||
void testIsDirty() {
|
||||
public void testIsDirty() {
|
||||
var df = new DataFetcher();
|
||||
var countries = df.fetch();
|
||||
Assertions.assertFalse(countries.isEmpty());
|
||||
assertFalse(countries.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsNotDirty() {
|
||||
public void testIsNotDirty() {
|
||||
var df = new DataFetcher();
|
||||
df.fetch();
|
||||
var countries = df.fetch();
|
||||
Assertions.assertTrue(countries.isEmpty());
|
||||
assertTrue(countries.isEmpty());
|
||||
}
|
||||
}
|
||||
|
@ -44,11 +44,6 @@
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
|
@ -25,23 +25,14 @@ package com.iluwatar.doublebuffer;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* App unit test.
|
||||
*/
|
||||
public 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
|
||||
public void shouldExecuteApplicationWithoutException() {
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
public void testMain() {
|
||||
App.main(new String[]{});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 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.
|
||||
*/
|
||||
|
||||
module com.iluwatar.doublecheckedlocking {
|
||||
requires org.slf4j;
|
||||
}
|
@ -25,23 +25,13 @@ package com.iluwatar.doublechecked.locking;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* Application test
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
|
||||
public class AppTest {
|
||||
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
public void test() {
|
||||
App.main(new String[]{});
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 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.
|
||||
*/
|
||||
|
||||
module com.iluwatar.doubledispatch {
|
||||
requires org.slf4j;
|
||||
}
|
@ -25,23 +25,13 @@ package com.iluwatar.doubledispatch;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* Application test
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
|
||||
public class AppTest {
|
||||
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
public void test() {
|
||||
App.main(new String[]{});
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user