diff --git a/.gitignore b/.gitignore index 589d3fb13..fd0bb7810 100644 --- a/.gitignore +++ b/.gitignore @@ -1,19 +1,20 @@ -target -.metadata -.settings -.classpath -.project -*.class -# Package Files # -*.jar -*.war -*.ear -.idea -*.iml -*.swp +target +.metadata +.settings +.classpath +.project +*.class +# Package Files # +*.jar +*.war +*.ear +.idea +*.iml +*.swp datanucleus.log /bin/ /bin/ /bin/ - -data-mapper/src/main/resources/log4j.xml \ No newline at end of file +*.log +data-mapper/src/main/resources/log4j.xml +event-sourcing/Journal.json diff --git a/.travis.yml b/.travis.yml index deb436cd2..85f53bcd3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,13 +6,34 @@ env: global: - GH_REF: github.com/iluwatar/java-design-patterns.git - secure: LxTDuNS/rBWIvKkaEqr79ImZAe48mCdoYCF41coxNXgNoippo4GIBArknqtv+XvdkiuRZ1yGyj6pn8GU33c/yn+krddTUkVCwTbVatbalW5jhQjDbHYym/JcxaK9ZS/3JTeGcWrBgiPqHEEDhCf26vPZsXoMSeVCEORVKTp1BSg= + - secure: "eoWlW9GyTJY04P8K3pxayXwU9/hmptQg/LfirispQkV9YvmziCfSzXnatnBhNfud98sCzY8BScXnb+OWLTnjLKpId4rtEqb0aJ40Jc32cUKzgzFAUn7cNcDAbUIfyPAGVqyQqfj/11wYSADwWMMOPlW97ExUtoyiH2WenXuRHso=" before_install: - export DISPLAY=:99.0 - sh -e /etc/init.d/xvfb start +# default install command is just "mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V" +install: +- mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V -e + after_success: -- mvn clean test jacoco:report coveralls:report +- mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent package sonar:sonar -Dsonar.host.url=https://sonarcloud.io -Dsonar.login=$SONAR_TOKEN - bash update-ghpages.sh -sudo: false # route the build to the container-based infrastructure for a faster build +# use latest java version available instead of travis default +addons: + apt: + packages: + - oracle-java8-installer + +notifications: + email: + - iluwatar@gmail.com + webhooks: + urls: + - https://webhooks.gitter.im/e/3319623945358a093a6f + on_success: change # options: [always|never|change] default: always + on_failure: always # options: [always|never|change] default: always + on_start: never # options: [always|never|change] default: always + +sudo: required diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..097054fe5 --- /dev/null +++ b/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,15 @@ + +Pull request title + +- Clearly and concisely describes what it does +- Refer to the issue that it solves, if available + + +Pull request description + +- Describes the main changes that come with the pull request +- Any relevant additional information is provided + + + +> For detailed contributing instructions see https://github.com/iluwatar/java-design-patterns/wiki/01.-How-to-contribute diff --git a/README.md b/README.md index 811d6a17a..03aea2bf6 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,10 @@ # Design patterns implemented in Java [![Build status](https://travis-ci.org/iluwatar/java-design-patterns.svg?branch=master)](https://travis-ci.org/iluwatar/java-design-patterns) -[![Coverage Status](https://coveralls.io/repos/iluwatar/java-design-patterns/badge.svg?branch=master)](https://coveralls.io/r/iluwatar/java-design-patterns?branch=master) [![License MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/iluwatar/java-design-patterns/master/LICENSE.md) [![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Quality Gate](https://sonarcloud.io/api/badges/gate?key=com.iluwatar%3Ajava-design-patterns)](https://sonarcloud.io/dashboard/index/com.iluwatar%3Ajava-design-patterns) +[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/1503/badge)](https://bestpractices.coreinfrastructure.org/projects/1503) # Introduction @@ -24,7 +25,7 @@ are familiar with the patterns. # Getting started Before you dive into the material, you should be familiar with various -[Programming/Software Design Principles](http://webpro.github.io/programming-principles/). +Programming/Software Design Principles. All designs should be as simple as possible. You should start with KISS, YAGNI, and Do The Simplest Thing That Could Possibly Work principles. Complexity and diff --git a/abstract-document/README.md b/abstract-document/README.md index bf28ff999..2adb3732b 100644 --- a/abstract-document/README.md +++ b/abstract-document/README.md @@ -12,8 +12,6 @@ tags: ## Intent Achieve flexibility of untyped languages and keep the type-safety -![alt text](./etc/abstract-document-base.png "Abstract Document Base") - ![alt text](./etc/abstract-document.png "Abstract Document Traits and Domain") diff --git a/abstract-document/etc/abstract-document-base.png b/abstract-document/etc/abstract-document-base.png deleted file mode 100644 index 13345dbb8..000000000 Binary files a/abstract-document/etc/abstract-document-base.png and /dev/null differ diff --git a/abstract-document/etc/abstract-document.png b/abstract-document/etc/abstract-document.png index 98d186f7e..6bc0b29a4 100644 Binary files a/abstract-document/etc/abstract-document.png and b/abstract-document/etc/abstract-document.png differ diff --git a/abstract-document/etc/abstract-document.ucls b/abstract-document/etc/abstract-document.ucls index ad97457fd..f555394d0 100644 --- a/abstract-document/etc/abstract-document.ucls +++ b/abstract-document/etc/abstract-document.ucls @@ -1,91 +1,137 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + - - - - - - - + + + - - - + + + - + - + + + + + + + + + + + + + + + + + - - - + + + + + + + diff --git a/abstract-document/pom.xml b/abstract-document/pom.xml index b7a348d26..a249f0d07 100644 --- a/abstract-document/pom.xml +++ b/abstract-document/pom.xml @@ -2,7 +2,7 @@ + + + + Design Patterns - Abstract Factory Presentation + + + + + + + + + \ No newline at end of file diff --git a/abstract-factory/pom.xml b/abstract-factory/pom.xml index ec0f700a5..073879acc 100644 --- a/abstract-factory/pom.xml +++ b/abstract-factory/pom.xml @@ -2,7 +2,7 @@ + + + java-design-patterns + com.iluwatar + 1.19.0-SNAPSHOT + + 4.0.0 + + balking + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + \ No newline at end of file diff --git a/balking/src/main/java/com/iluwatar/balking/App.java b/balking/src/main/java/com/iluwatar/balking/App.java new file mode 100644 index 000000000..47b10cd46 --- /dev/null +++ b/balking/src/main/java/com/iluwatar/balking/App.java @@ -0,0 +1,65 @@ +/** + * The MIT License + * Copyright (c) 2014 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.balking; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +/** + * In Balking Design Pattern if an object’s method is invoked when it is in an inappropriate state, + * then the method will return without doing anything. Objects that use this pattern are generally only in a + * state that is prone to balking temporarily but for an unknown amount of time + * + * In this example implementation WashingMachine is an object that has two states + * in which it can be: ENABLED and WASHING. If the machine is ENABLED + * the state is changed into WASHING that any other thread can't invoke this action on this and then do the job. + * On the other hand if it have been already washing and any other thread execute wash() + * it can't do that once again and returns doing nothing. + */ + +public class App { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + /** + * @param args the command line arguments - not used + */ + public static void main(String... args) { + final WashingMachine washingMachine = new WashingMachine(); + ExecutorService executorService = Executors.newFixedThreadPool(3); + for (int i = 0; i < 3; i++) { + executorService.execute(washingMachine::wash); + } + executorService.shutdown(); + try { + executorService.awaitTermination(10, TimeUnit.SECONDS); + } catch (InterruptedException ie) { + LOGGER.error("ERROR: Waiting on executor service shutdown!"); + } + } + +} diff --git a/balking/src/main/java/com/iluwatar/balking/WashingMachine.java b/balking/src/main/java/com/iluwatar/balking/WashingMachine.java new file mode 100644 index 000000000..e4f1259ad --- /dev/null +++ b/balking/src/main/java/com/iluwatar/balking/WashingMachine.java @@ -0,0 +1,76 @@ +/** + * The MIT License + * Copyright (c) 2014 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.balking; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Washing machine class + */ +public class WashingMachine { + + private static final Logger LOGGER = LoggerFactory.getLogger(WashingMachine.class); + + private WashingMachineState washingMachineState; + + public WashingMachine() { + washingMachineState = WashingMachineState.ENABLED; + } + + public WashingMachineState getWashingMachineState() { + return washingMachineState; + } + + /** + * Method responsible for washing + * if the object is in appropriate state + */ + public void wash() { + synchronized (this) { + LOGGER.info("{}: Actual machine state: {}", Thread.currentThread().getName(), getWashingMachineState()); + if (washingMachineState == WashingMachineState.WASHING) { + LOGGER.error("ERROR: Cannot wash if the machine has been already washing!"); + return; + } + washingMachineState = WashingMachineState.WASHING; + } + LOGGER.info("{}: Doing the washing", Thread.currentThread().getName()); + try { + Thread.sleep(50); + } catch (InterruptedException ie) { + ie.printStackTrace(); + } + endOfWashing(); + } + + /** + * Method responsible of ending the washing + * by changing machine state + */ + public synchronized void endOfWashing() { + washingMachineState = WashingMachineState.ENABLED; + LOGGER.info("{}: Washing completed.", Thread.currentThread().getId()); + } + +} diff --git a/balking/src/main/java/com/iluwatar/balking/WashingMachineState.java b/balking/src/main/java/com/iluwatar/balking/WashingMachineState.java new file mode 100644 index 000000000..40a5b2f38 --- /dev/null +++ b/balking/src/main/java/com/iluwatar/balking/WashingMachineState.java @@ -0,0 +1,32 @@ +/** + * The MIT License + * Copyright (c) 2014 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.balking; + +/** + * WashingMachineState enum describes in which state machine is, + * it can be enabled and ready to work as well as during washing + */ + +public enum WashingMachineState { + ENABLED, WASHING +} diff --git a/balking/src/test/java/com/iluwatar/balking/AppTest.java b/balking/src/test/java/com/iluwatar/balking/AppTest.java new file mode 100644 index 000000000..87941dc27 --- /dev/null +++ b/balking/src/test/java/com/iluwatar/balking/AppTest.java @@ -0,0 +1,39 @@ +/** + * The MIT License + * Copyright (c) 2014 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.balking; + +import org.junit.jupiter.api.Test; + +/** + * Application test + */ +public class AppTest { + + @Test + public void main() throws Exception { + String[] args = {}; + App.main(args); + } + +} \ No newline at end of file diff --git a/balking/src/test/java/com/iluwatar/balking/WashingMachineTest.java b/balking/src/test/java/com/iluwatar/balking/WashingMachineTest.java new file mode 100644 index 000000000..47957835d --- /dev/null +++ b/balking/src/test/java/com/iluwatar/balking/WashingMachineTest.java @@ -0,0 +1,65 @@ +/** + * The MIT License + * Copyright (c) 2014 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.balking; + +import org.junit.jupiter.api.Test; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Tests for {@link WashingMachine} + */ +public class WashingMachineTest { + + private volatile WashingMachineState machineStateGlobal; + + @Test + public void wash() throws Exception { + WashingMachine washingMachine = new WashingMachine(); + ExecutorService executorService = Executors.newFixedThreadPool(2); + executorService.execute(washingMachine::wash); + executorService.execute(() -> { + washingMachine.wash(); + machineStateGlobal = washingMachine.getWashingMachineState(); + }); + executorService.shutdown(); + try { + executorService.awaitTermination(10, TimeUnit.SECONDS); + } catch (InterruptedException ie) { + ie.printStackTrace(); + } + assertEquals(WashingMachineState.WASHING, machineStateGlobal); + } + + @Test + public void endOfWashing() throws Exception { + WashingMachine washingMachine = new WashingMachine(); + washingMachine.wash(); + assertEquals(WashingMachineState.ENABLED, washingMachine.getWashingMachineState()); + } + +} \ No newline at end of file diff --git a/bridge/README.md b/bridge/README.md index 49dad14e4..882640725 100644 --- a/bridge/README.md +++ b/bridge/README.md @@ -14,10 +14,170 @@ tags: Handle/Body ## Intent -Decouple an abstraction from its implementation so that the two can -vary independently. +Decouple an abstraction from its implementation so that the two can vary independently. -![alt text](./etc/bridge.png "Bridge") +## Explanation + +Real world example + +> Consider you have a weapon with different enchantments and you are supposed to allow mixing different weapons with different enchantments. What would you do? Create multiple copies of each of the weapons for each of the enchantments or would you just create separate enchantment and set it for the weapon as needed? Bridge pattern allows you to do the second. + +In Plain Words + +> Bridge pattern is about preferring composition over inheritance. Implementation details are pushed from a hierarchy to another object with a separate hierarchy. + +Wikipedia says + +> The bridge pattern is a design pattern used in software engineering that is meant to "decouple an abstraction from its implementation so that the two can vary independently" + +**Programmatic Example** + +Translating our weapon example from above. Here we have the `Weapon` hierarchy + +``` +public interface Weapon { + void wield(); + void swing(); + void unwield(); + Enchantment getEnchantment(); +} + +public class Sword implements Weapon { + + private final Enchantment enchantment; + + public Sword(Enchantment enchantment) { + this.enchantment = enchantment; + } + + @Override + public void wield() { + LOGGER.info("The sword is wielded."); + enchantment.onActivate(); + } + + @Override + public void swing() { + LOGGER.info("The sword is swinged."); + enchantment.apply(); + } + + @Override + public void unwield() { + LOGGER.info("The sword is unwielded."); + enchantment.onDeactivate(); + } + + @Override + public Enchantment getEnchantment() { + return enchantment; + } +} + +public class Hammer implements Weapon { + + private final Enchantment enchantment; + + public Hammer(Enchantment enchantment) { + this.enchantment = enchantment; + } + + @Override + public void wield() { + LOGGER.info("The hammer is wielded."); + enchantment.onActivate(); + } + + @Override + public void swing() { + LOGGER.info("The hammer is swinged."); + enchantment.apply(); + } + + @Override + public void unwield() { + LOGGER.info("The hammer is unwielded."); + enchantment.onDeactivate(); + } + + @Override + public Enchantment getEnchantment() { + return enchantment; + } +} +``` + +And the separate enchantment hierarchy + +``` +public interface Enchantment { + void onActivate(); + void apply(); + void onDeactivate(); +} + +public class FlyingEnchantment implements Enchantment { + + @Override + public void onActivate() { + LOGGER.info("The item begins to glow faintly."); + } + + @Override + public void apply() { + LOGGER.info("The item flies and strikes the enemies finally returning to owner's hand."); + } + + @Override + public void onDeactivate() { + LOGGER.info("The item's glow fades."); + } +} + +public class SoulEatingEnchantment implements Enchantment { + + @Override + public void onActivate() { + LOGGER.info("The item spreads bloodlust."); + } + + @Override + public void apply() { + LOGGER.info("The item eats the soul of enemies."); + } + + @Override + public void onDeactivate() { + LOGGER.info("Bloodlust slowly disappears."); + } +} +``` + +And both the hierarchies in action + +``` +Sword enchantedSword = new Sword(new SoulEatingEnchantment()); +enchantedSword.wield(); +enchantedSword.swing(); +enchantedSword.unwield(); +// The sword is wielded. +// The item spreads bloodlust. +// The sword is swinged. +// The item eats the soul of enemies. +// The sword is unwielded. +// Bloodlust slowly disappears. + +Hammer hammer = new Hammer(new FlyingEnchantment()); +hammer.wield(); +hammer.swing(); +hammer.unwield(); +// The hammer is wielded. +// The item begins to glow faintly. +// The hammer is swinged. +// The item flies and strikes the enemies finally returning to owner's hand. +// The hammer is unwielded. +// The item's glow fades. +``` ## Applicability Use the Bridge pattern when diff --git a/bridge/etc/bridge.png b/bridge/etc/bridge.png deleted file mode 100644 index 00d3e611c..000000000 Binary files a/bridge/etc/bridge.png and /dev/null differ diff --git a/bridge/etc/bridge.ucls b/bridge/etc/bridge.ucls deleted file mode 100644 index 2eda6e1d1..000000000 --- a/bridge/etc/bridge.ucls +++ /dev/null @@ -1,157 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/bridge/pom.xml b/bridge/pom.xml index a7a3883c8..0cef80258 100644 --- a/bridge/pom.xml +++ b/bridge/pom.xml @@ -2,7 +2,7 @@ - + @@ -77,7 +77,6 @@ - @@ -86,9 +85,7 @@ value="LITERAL_TRY, LITERAL_CATCH, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/> - - - + @@ -120,7 +117,7 @@ - + @@ -182,9 +179,13 @@ - + + + + + + + + + Design Patterns - Command Presentation + + + + + + + + + \ No newline at end of file diff --git a/command/pom.xml b/command/pom.xml index a27a56e54..9a819bb6c 100644 --- a/command/pom.xml +++ b/command/pom.xml @@ -2,7 +2,7 @@ + + + java-design-patterns + com.iluwatar + 1.19.0-SNAPSHOT + + 4.0.0 + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + com.google.guava + guava + + + converter + + + diff --git a/converter/src/main/java/com/iluwatar/converter/App.java b/converter/src/main/java/com/iluwatar/converter/App.java new file mode 100644 index 000000000..6e436706d --- /dev/null +++ b/converter/src/main/java/com/iluwatar/converter/App.java @@ -0,0 +1,60 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 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.converter; + + +import com.google.common.collect.Lists; + +import java.util.ArrayList; +import java.util.List; + +/** + * The Converter pattern is a behavioral design pattern which allows a common way of bidirectional + * conversion between corresponding types (e.g. DTO and domain representations of the logically + * isomorphic types). Moreover, the pattern introduces a common way of converting a collection of + * objects between types. + */ +public class App { + /** + * Program entry point + * + * @param args command line args + */ + public static void main(String[] args) { + Converter userConverter = new UserConverter(); + + UserDto dtoUser = new UserDto("John", "Doe", true, "whatever[at]wherever.com"); + User user = userConverter.convertFromDto(dtoUser); + System.out.println("Entity converted from DTO:" + user); + + ArrayList users = Lists.newArrayList(new User("Camile", "Tough", false, "124sad"), + new User("Marti", "Luther", true, "42309fd"), new User("Kate", "Smith", true, "if0243")); + System.out.println("Domain entities:"); + users.forEach(System.out::println); + + System.out.println("DTO entities converted from domain:"); + List dtoEntities = userConverter.createFromEntities(users); + dtoEntities.forEach(System.out::println); + + } +} diff --git a/converter/src/main/java/com/iluwatar/converter/Converter.java b/converter/src/main/java/com/iluwatar/converter/Converter.java new file mode 100644 index 000000000..918d2d503 --- /dev/null +++ b/converter/src/main/java/com/iluwatar/converter/Converter.java @@ -0,0 +1,86 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 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.converter; + +import java.util.Collection; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Generic converter, thanks to Java8 features not only provides a way of generic bidirectional + * conversion between coresponding types, but also a common way of converting a collection of objects + * of the same type, reducing boilerplate code to the absolute minimum. + * @param DTO representation's type + * @param Domain representation's type + */ +public class Converter { + + private final Function fromDto; + private final Function fromEntity; + + /** + * @param fromDto Function that converts given dto entity into the domain entity. + * @param fromEntity Function that converts given domain entity into the dto entity. + */ + public Converter(final Function fromDto, final Function fromEntity) { + this.fromDto = fromDto; + this.fromEntity = fromEntity; + } + + /** + * @param userDto DTO entity + * @return The domain representation - the result of the converting function application on dto entity. + */ + public final U convertFromDto(final T userDto) { + return fromDto.apply(userDto); + } + + /** + * @param user domain entity + * @return The DTO representation - the result of the converting function application on domain entity. + */ + public final T convertFromEntity(final U user) { + return fromEntity.apply(user); + } + + /** + * @param dtoUsers collection of DTO entities + * @return List of domain representation of provided entities retrieved by + * mapping each of them with the conversion function + */ + public final List createFromDtos(final Collection dtoUsers) { + return dtoUsers.stream().map(this::convertFromDto).collect(Collectors.toList()); + } + + /** + * @param users collection of domain entities + * @return List of domain representation of provided entities retrieved by + * mapping each of them with the conversion function + */ + public final List createFromEntities(final Collection users) { + return users.stream().map(this::convertFromEntity).collect(Collectors.toList()); + } + +} diff --git a/converter/src/main/java/com/iluwatar/converter/User.java b/converter/src/main/java/com/iluwatar/converter/User.java new file mode 100644 index 000000000..62903a931 --- /dev/null +++ b/converter/src/main/java/com/iluwatar/converter/User.java @@ -0,0 +1,86 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 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.converter; + +import java.util.Objects; + +/** + * User class + */ +public class User { + private String firstName; + private String lastName; + private boolean isActive; + private String userId; + + /** + * @param firstName user's first name + * @param lastName user's last name + * @param isActive flag indicating whether the user is active + * @param userId user's identificator + */ + public User(String firstName, String lastName, boolean isActive, String userId) { + this.firstName = firstName; + this.lastName = lastName; + this.isActive = isActive; + this.userId = userId; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public boolean isActive() { + return isActive; + } + + public String getUserId() { + return userId; + } + + @Override public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + User user = (User) o; + return isActive == user.isActive && Objects.equals(firstName, user.firstName) && Objects + .equals(lastName, user.lastName) && Objects.equals(userId, user.userId); + } + + @Override public int hashCode() { + return Objects.hash(firstName, lastName, isActive, userId); + } + + @Override public String toString() { + return "User{" + "firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + + ", isActive=" + isActive + ", userId='" + userId + '\'' + '}'; + } +} diff --git a/converter/src/main/java/com/iluwatar/converter/UserConverter.java b/converter/src/main/java/com/iluwatar/converter/UserConverter.java new file mode 100644 index 000000000..9ef1d03c2 --- /dev/null +++ b/converter/src/main/java/com/iluwatar/converter/UserConverter.java @@ -0,0 +1,40 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 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.converter; + +/** + * Example implementation of the simple User converter. + */ +public class UserConverter extends Converter { + + /** + * Constructor. + */ + public UserConverter() { + super(userDto -> new User(userDto.getFirstName(), userDto.getLastName(), userDto.isActive(), + userDto.getEmail()), + user -> new UserDto(user.getFirstName(), user.getLastName(), user.isActive(), + user.getUserId())); + } +} diff --git a/converter/src/main/java/com/iluwatar/converter/UserDto.java b/converter/src/main/java/com/iluwatar/converter/UserDto.java new file mode 100644 index 000000000..8eacc0b3b --- /dev/null +++ b/converter/src/main/java/com/iluwatar/converter/UserDto.java @@ -0,0 +1,88 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 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.converter; + + +import java.util.Objects; + +/** + * User DTO class + */ +public class UserDto { + + private String firstName; + private String lastName; + private boolean isActive; + private String email; + + /** + * @param firstName user's first name + * @param lastName user's last name + * @param isActive flag indicating whether the user is active + * @param email user's email address + */ + public UserDto(String firstName, String lastName, boolean isActive, String email) { + this.firstName = firstName; + this.lastName = lastName; + this.isActive = isActive; + this.email = email; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public boolean isActive() { + return isActive; + } + + public String getEmail() { + return email; + } + + @Override public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + UserDto userDto = (UserDto) o; + return isActive == userDto.isActive && Objects.equals(firstName, userDto.firstName) && Objects + .equals(lastName, userDto.lastName) && Objects.equals(email, userDto.email); + } + + @Override public int hashCode() { + return Objects.hash(firstName, lastName, isActive, email); + } + + @Override public String toString() { + return "UserDto{" + "firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + + ", isActive=" + isActive + ", email='" + email + '\'' + '}'; + } +} diff --git a/converter/src/test/java/com/iluwatar/converter/AppTest.java b/converter/src/test/java/com/iluwatar/converter/AppTest.java new file mode 100644 index 000000000..1617beeb4 --- /dev/null +++ b/converter/src/test/java/com/iluwatar/converter/AppTest.java @@ -0,0 +1,38 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 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.converter; + +import org.junit.jupiter.api.Test; + +/** + * App running test + */ +public class AppTest { + + @Test + public void testMain() { + String[] args = {}; + App.main(args); + } + +} diff --git a/converter/src/test/java/com/iluwatar/converter/ConverterTest.java b/converter/src/test/java/com/iluwatar/converter/ConverterTest.java new file mode 100644 index 000000000..f5c41256d --- /dev/null +++ b/converter/src/test/java/com/iluwatar/converter/ConverterTest.java @@ -0,0 +1,88 @@ +/** + * The MIT License + * Copyright (c) 2014 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.converter; + +import com.google.common.collect.Lists; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Tests for {@link Converter} + */ +public class ConverterTest { + + private UserConverter userConverter = new UserConverter(); + + /** + * Tests whether a converter created of opposite functions holds equality as a bijection. + */ + @Test + public void testConversionsStartingFromDomain() { + User u1 = new User("Tom", "Hanks", true, "tom@hanks.com"); + User u2 = userConverter.convertFromDto(userConverter.convertFromEntity(u1)); + assertEquals(u1, u2); + } + + /** + * Tests whether a converter created of opposite functions holds equality as a bijection. + */ + @Test + public void testConversionsStartingFromDto() { + UserDto u1 = new UserDto("Tom", "Hanks", true, "tom@hanks.com"); + UserDto u2 = userConverter.convertFromEntity(userConverter.convertFromDto(u1)); + assertEquals(u1, u2); + } + + /** + * Tests the custom users converter. Thanks to Java8 lambdas, converter can be easily and + * cleanly instantiated allowing various different conversion strategies to be implemented. + */ + @Test + public void testCustomConverter() { + Converter converter = new Converter<>( + userDto -> new User(userDto.getFirstName(), userDto.getLastName(), userDto.isActive(), + String.valueOf(new Random().nextInt())), + user -> new UserDto(user.getFirstName(), user.getLastName(), user.isActive(), + user.getFirstName().toLowerCase() + user.getLastName().toLowerCase() + "@whatever.com")); + User u1 = new User("John", "Doe", false, "12324"); + UserDto userDto = converter.convertFromEntity(u1); + assertEquals(userDto.getEmail(), "johndoe@whatever.com"); + } + + /** + * Test whether converting a collection of Users to DTO Users and then converting them back to domain + * users returns an equal collection. + */ + @Test + public void testCollectionConversion() { + ArrayList users = Lists.newArrayList(new User("Camile", "Tough", false, "124sad"), + new User("Marti", "Luther", true, "42309fd"), new User("Kate", "Smith", true, "if0243")); + List fromDtos = userConverter.createFromDtos(userConverter.createFromEntities(users)); + assertEquals(fromDtos, users); + } +} diff --git a/cqrs/README.md b/cqrs/README.md new file mode 100644 index 000000000..111c1ccd4 --- /dev/null +++ b/cqrs/README.md @@ -0,0 +1,28 @@ +--- +layout: pattern +title: CQRS +folder: cqrs +permalink: /patterns/cqrs/ +categories: Architectural +tags: + - Java + - Difficulty-Intermediate +--- + +## Intent +CQRS Command Query Responsibility Segregation - Separate the query side from the command side. + +![alt text](./etc/cqrs.png "CQRS") + +## Applicability +Use the CQRS pattern when + +* you want to scale the queries and commands independently. +* you want to use different data models for queries and commands. Useful when dealing with complex domains. +* you want to use architectures like event sourcing or task based UI. + +## Credits + +* [Greg Young - CQRS, Task Based UIs, Event Sourcing agh!](http://codebetter.com/gregyoung/2010/02/16/cqrs-task-based-uis-event-sourcing-agh/) +* [Martin Fowler - CQRS](https://martinfowler.com/bliki/CQRS.html) +* [Oliver Wolf - CQRS for Great Good](https://www.youtube.com/watch?v=Ge53swja9Dw) diff --git a/cqrs/etc/cqrs.png b/cqrs/etc/cqrs.png new file mode 100644 index 000000000..28174bc9a Binary files /dev/null and b/cqrs/etc/cqrs.png differ diff --git a/cqrs/etc/cqrs.ucls b/cqrs/etc/cqrs.ucls new file mode 100644 index 000000000..6cdfebbb2 --- /dev/null +++ b/cqrs/etc/cqrs.ucls @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cqrs/pom.xml b/cqrs/pom.xml new file mode 100644 index 000000000..2ed0809f6 --- /dev/null +++ b/cqrs/pom.xml @@ -0,0 +1,47 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.19.0-SNAPSHOT + + cqrs + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + com.h2database + h2 + + + org.hibernate + hibernate-core + + + diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/app/App.java b/cqrs/src/main/java/com/iluwatar/cqrs/app/App.java new file mode 100644 index 000000000..a943a7f88 --- /dev/null +++ b/cqrs/src/main/java/com/iluwatar/cqrs/app/App.java @@ -0,0 +1,95 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 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.cqrs.app; + +import java.math.BigInteger; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.iluwatar.cqrs.commandes.CommandServiceImpl; +import com.iluwatar.cqrs.commandes.ICommandService; +import com.iluwatar.cqrs.dto.Author; +import com.iluwatar.cqrs.dto.Book; +import com.iluwatar.cqrs.queries.IQueryService; +import com.iluwatar.cqrs.queries.QueryServiceImpl; +import com.iluwatar.cqrs.util.HibernateUtil; + +/** + * CQRS : Command Query Responsibility Segregation. A pattern used to separate query services from commands or writes + * services. The pattern is very simple but it has many consequences. For example, it can be used to tackle down a + * complex domain, or to use other architectures that were hard to implement with the classical way. + * + * This implementation is an example of managing books and authors in a library. The persistence of books and authors is + * done according to the CQRS architecture. A command side that deals with a data model to persist(insert,update,delete) + * objects to a database. And a query side that uses native queries to get data from the database and return objects as + * DTOs (Data transfer Objects). + * + */ +public class App { + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + /** + * Program entry point + * + * @param args + * command line args + */ + public static void main(String[] args) { + ICommandService commands = new CommandServiceImpl(); + + // Create Authors and Books using CommandService + commands.authorCreated("eEvans", "Eric Evans", "eEvans@email.com"); + commands.authorCreated("jBloch", "Joshua Bloch", "jBloch@email.com"); + commands.authorCreated("mFowler", "Martin Fowler", "mFowler@email.com"); + + commands.bookAddedToAuthor("Domain-Driven Design", 60.08, "eEvans"); + commands.bookAddedToAuthor("Effective Java", 40.54, "jBloch"); + commands.bookAddedToAuthor("Java Puzzlers", 39.99, "jBloch"); + commands.bookAddedToAuthor("Java Concurrency in Practice", 29.40, "jBloch"); + commands.bookAddedToAuthor("Patterns of Enterprise Application Architecture", 54.01, "mFowler"); + commands.bookAddedToAuthor("Domain Specific Languages", 48.89, "mFowler"); + commands.authorNameUpdated("eEvans", "Eric J. Evans"); + + IQueryService queries = new QueryServiceImpl(); + + // Query the database using QueryService + Author nullAuthor = queries.getAuthorByUsername("username"); + Author eEvans = queries.getAuthorByUsername("eEvans"); + BigInteger jBlochBooksCount = queries.getAuthorBooksCount("jBloch"); + BigInteger authorsCount = queries.getAuthorsCount(); + Book dddBook = queries.getBook("Domain-Driven Design"); + List jBlochBooks = queries.getAuthorBooks("jBloch"); + + LOGGER.info("Author username : {}", nullAuthor); + LOGGER.info("Author eEvans : {}", eEvans); + LOGGER.info("jBloch number of books : {}", jBlochBooksCount); + LOGGER.info("Number of authors : {}", authorsCount); + LOGGER.info("DDD book : {}", dddBook); + LOGGER.info("jBloch books : {}", jBlochBooks); + + HibernateUtil.getSessionFactory().close(); + } + +} diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/commandes/CommandServiceImpl.java b/cqrs/src/main/java/com/iluwatar/cqrs/commandes/CommandServiceImpl.java new file mode 100644 index 000000000..a15f8a457 --- /dev/null +++ b/cqrs/src/main/java/com/iluwatar/cqrs/commandes/CommandServiceImpl.java @@ -0,0 +1,145 @@ +/** + * The MIT License + * Copyright (c) 2014 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.cqrs.commandes; + +import org.hibernate.Query; +import org.hibernate.Session; +import org.hibernate.SessionFactory; + +import com.iluwatar.cqrs.domain.model.Author; +import com.iluwatar.cqrs.domain.model.Book; +import com.iluwatar.cqrs.util.HibernateUtil; + +/** + * This class is an implementation of {@link ICommandService} interface. It uses Hibernate as an api for persistence. + * + */ +public class CommandServiceImpl implements ICommandService { + + private SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); + + private Author getAuthorByUsername(String username) { + Author author = null; + try (Session session = sessionFactory.openSession()) { + Query query = session.createQuery("from Author where username=:username"); + query.setParameter("username", username); + author = (Author) query.uniqueResult(); + } + if (author == null) { + HibernateUtil.getSessionFactory().close(); + throw new NullPointerException("Author " + username + " doesn't exist!"); + } + return author; + } + + private Book getBookByTitle(String title) { + Book book = null; + try (Session session = sessionFactory.openSession()) { + Query query = session.createQuery("from Book where title=:title"); + query.setParameter("title", title); + book = (Book) query.uniqueResult(); + } + if (book == null) { + HibernateUtil.getSessionFactory().close(); + throw new NullPointerException("Book " + title + " doesn't exist!"); + } + return book; + } + + @Override + public void authorCreated(String username, String name, String email) { + Author author = new Author(username, name, email); + try (Session session = sessionFactory.openSession()) { + session.beginTransaction(); + session.save(author); + session.getTransaction().commit(); + } + } + + @Override + public void bookAddedToAuthor(String title, double price, String username) { + Author author = getAuthorByUsername(username); + Book book = new Book(title, price, author); + try (Session session = sessionFactory.openSession()) { + session.beginTransaction(); + session.save(book); + session.getTransaction().commit(); + } + } + + @Override + public void authorNameUpdated(String username, String name) { + Author author = getAuthorByUsername(username); + author.setName(name); + try (Session session = sessionFactory.openSession()) { + session.beginTransaction(); + session.update(author); + session.getTransaction().commit(); + } + } + + @Override + public void authorUsernameUpdated(String oldUsername, String newUsername) { + Author author = getAuthorByUsername(oldUsername); + author.setUsername(newUsername); + try (Session session = sessionFactory.openSession()) { + session.beginTransaction(); + session.update(author); + session.getTransaction().commit(); + } + } + + @Override + public void authorEmailUpdated(String username, String email) { + Author author = getAuthorByUsername(username); + author.setEmail(email); + try (Session session = sessionFactory.openSession()) { + session.beginTransaction(); + session.update(author); + session.getTransaction().commit(); + } + } + + @Override + public void bookTitleUpdated(String oldTitle, String newTitle) { + Book book = getBookByTitle(oldTitle); + book.setTitle(newTitle); + try (Session session = sessionFactory.openSession()) { + session.beginTransaction(); + session.update(book); + session.getTransaction().commit(); + } + } + + @Override + public void bookPriceUpdated(String title, double price) { + Book book = getBookByTitle(title); + book.setPrice(price); + try (Session session = sessionFactory.openSession()) { + session.beginTransaction(); + session.update(book); + session.getTransaction().commit(); + } + } + +} diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/commandes/ICommandService.java b/cqrs/src/main/java/com/iluwatar/cqrs/commandes/ICommandService.java new file mode 100644 index 000000000..1da3f6c42 --- /dev/null +++ b/cqrs/src/main/java/com/iluwatar/cqrs/commandes/ICommandService.java @@ -0,0 +1,45 @@ +/** + * The MIT License + * Copyright (c) 2014 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.cqrs.commandes; + +/** + * This interface represents the commands of the CQRS pattern + * + */ +public interface ICommandService { + + void authorCreated(String username, String name, String email); + + void bookAddedToAuthor(String title, double price, String username); + + void authorNameUpdated(String username, String name); + + void authorUsernameUpdated(String oldUsername, String newUsername); + + void authorEmailUpdated(String username, String email); + + void bookTitleUpdated(String oldTitle, String newTitle); + + void bookPriceUpdated(String title, double price); + +} diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/domain/model/Author.java b/cqrs/src/main/java/com/iluwatar/cqrs/domain/model/Author.java new file mode 100644 index 000000000..9825de9f7 --- /dev/null +++ b/cqrs/src/main/java/com/iluwatar/cqrs/domain/model/Author.java @@ -0,0 +1,98 @@ +/** + * The MIT License + * Copyright (c) 2014 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.cqrs.domain.model; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +/** + * This is an Author entity. It is used by Hibernate for persistence. + * + */ +@Entity +public class Author { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + private String username; + private String name; + private String email; + + /** + * + * @param username + * username of the author + * @param name + * name of the author + * @param email + * email of the author + */ + public Author(String username, String name, String email) { + this.username = username; + this.name = name; + this.email = email; + } + + protected Author() { + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + @Override + public String toString() { + return "Author [name=" + name + ", email=" + email + "]"; + } + +} diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/domain/model/Book.java b/cqrs/src/main/java/com/iluwatar/cqrs/domain/model/Book.java new file mode 100644 index 000000000..8a11fcdd4 --- /dev/null +++ b/cqrs/src/main/java/com/iluwatar/cqrs/domain/model/Book.java @@ -0,0 +1,100 @@ +/** + * The MIT License + * Copyright (c) 2014 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.cqrs.domain.model; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +/** + * This is a Book entity. It is used by Hibernate for persistence. Many books can be written by one {@link Author} + * + */ +@Entity +public class Book { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + private String title; + private double price; + @ManyToOne + private Author author; + + /** + * + * @param title + * title of the book + * @param price + * price of the book + * @param author + * author of the book + */ + public Book(String title, double price, Author author) { + this.title = title; + this.price = price; + this.author = author; + } + + protected Book() { + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public double getPrice() { + return price; + } + + public void setPrice(double price) { + this.price = price; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + + @Override + public String toString() { + return "Book [title=" + title + ", price=" + price + ", author=" + author + "]"; + } + +} diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/dto/Author.java b/cqrs/src/main/java/com/iluwatar/cqrs/dto/Author.java new file mode 100644 index 000000000..c5473354d --- /dev/null +++ b/cqrs/src/main/java/com/iluwatar/cqrs/dto/Author.java @@ -0,0 +1,91 @@ +/** + * The MIT License + * Copyright (c) 2014 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.cqrs.dto; + +import java.util.Objects; + +/** + * + * This is a DTO (Data Transfer Object) author, contains only useful information to be returned + * + */ +public class Author { + + private String name; + private String email; + private String username; + + /** + * + * @param name + * name of the author + * @param email + * email of the author + * @param username + * username of the author + */ + public Author(String name, String email, String username) { + this.name = name; + this.email = email; + this.username = username; + } + + public Author() { + } + + public String getName() { + return name; + } + + public String getEmail() { + return email; + } + + public String getUsername() { + return username; + } + + @Override + public String toString() { + return "AuthorDTO [name=" + name + ", email=" + email + ", username=" + username + "]"; + } + + @Override + public int hashCode() { + return Objects.hash(username, name, email); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Author)) { + return false; + } + Author other = (Author) obj; + return username.equals(other.getUsername()) && email.equals(other.getEmail()) && name.equals(other.getName()); + + } + +} diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/dto/Book.java b/cqrs/src/main/java/com/iluwatar/cqrs/dto/Book.java new file mode 100644 index 000000000..f121a2ca7 --- /dev/null +++ b/cqrs/src/main/java/com/iluwatar/cqrs/dto/Book.java @@ -0,0 +1,82 @@ +/** + * The MIT License + * Copyright (c) 2014 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.cqrs.dto; + +import java.util.Objects; + +/** + * + * This is a DTO (Data Transfer Object) book, contains only useful information to be returned + * + */ +public class Book { + + private String title; + private double price; + + /** + * + * @param title + * title of the book + * @param price + * price of the book + */ + public Book(String title, double price) { + this.title = title; + this.price = price; + } + + public Book() { + } + + public String getTitle() { + return title; + } + + public double getPrice() { + return price; + } + + @Override + public String toString() { + return "BookDTO [title=" + title + ", price=" + price + "]"; + } + + @Override + public int hashCode() { + return Objects.hash(title, price); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Book)) { + return false; + } + Book book = (Book) obj; + return title.equals(book.getTitle()) && price == book.getPrice(); + } + +} diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/queries/IQueryService.java b/cqrs/src/main/java/com/iluwatar/cqrs/queries/IQueryService.java new file mode 100644 index 000000000..9c0252b0a --- /dev/null +++ b/cqrs/src/main/java/com/iluwatar/cqrs/queries/IQueryService.java @@ -0,0 +1,48 @@ +/** + * The MIT License + * Copyright (c) 2014 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.cqrs.queries; + +import java.math.BigInteger; +import java.util.List; + +import com.iluwatar.cqrs.dto.Author; +import com.iluwatar.cqrs.dto.Book; + +/** + * + * This interface represents the query methods of the CQRS pattern + * + */ +public interface IQueryService { + + Author getAuthorByUsername(String username); + + Book getBook(String title); + + List getAuthorBooks(String username); + + BigInteger getAuthorBooksCount(String username); + + BigInteger getAuthorsCount(); + +} diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/queries/QueryServiceImpl.java b/cqrs/src/main/java/com/iluwatar/cqrs/queries/QueryServiceImpl.java new file mode 100644 index 000000000..dc44d0f1b --- /dev/null +++ b/cqrs/src/main/java/com/iluwatar/cqrs/queries/QueryServiceImpl.java @@ -0,0 +1,105 @@ +/** + * The MIT License + * Copyright (c) 2014 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.cqrs.queries; + +import java.math.BigInteger; +import java.util.List; + +import org.hibernate.SQLQuery; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.transform.Transformers; + +import com.iluwatar.cqrs.dto.Author; +import com.iluwatar.cqrs.dto.Book; +import com.iluwatar.cqrs.util.HibernateUtil; + +/** + * This class is an implementation of {@link IQueryService}. It uses Hibernate native queries to return DTOs from the + * database. + * + */ +public class QueryServiceImpl implements IQueryService { + + private SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); + + @Override + public Author getAuthorByUsername(String username) { + Author authorDTo = null; + try (Session session = sessionFactory.openSession()) { + SQLQuery sqlQuery = session + .createSQLQuery("SELECT a.username as \"username\", a.name as \"name\", a.email as \"email\"" + + "FROM Author a where a.username=:username"); + sqlQuery.setParameter("username", username); + authorDTo = (Author) sqlQuery.setResultTransformer(Transformers.aliasToBean(Author.class)).uniqueResult(); + } + return authorDTo; + } + + @Override + public Book getBook(String title) { + Book bookDTo = null; + try (Session session = sessionFactory.openSession()) { + SQLQuery sqlQuery = session + .createSQLQuery("SELECT b.title as \"title\", b.price as \"price\"" + " FROM Book b where b.title=:title"); + sqlQuery.setParameter("title", title); + bookDTo = (Book) sqlQuery.setResultTransformer(Transformers.aliasToBean(Book.class)).uniqueResult(); + } + return bookDTo; + } + + @Override + public List getAuthorBooks(String username) { + List bookDTos = null; + try (Session session = sessionFactory.openSession()) { + SQLQuery sqlQuery = session.createSQLQuery("SELECT b.title as \"title\", b.price as \"price\"" + + " FROM Author a , Book b where b.author_id = a.id and a.username=:username"); + sqlQuery.setParameter("username", username); + bookDTos = sqlQuery.setResultTransformer(Transformers.aliasToBean(Book.class)).list(); + } + return bookDTos; + } + + @Override + public BigInteger getAuthorBooksCount(String username) { + BigInteger bookcount = null; + try (Session session = sessionFactory.openSession()) { + SQLQuery sqlQuery = session.createSQLQuery( + "SELECT count(b.title)" + " FROM Book b, Author a where b.author_id = a.id and a.username=:username"); + sqlQuery.setParameter("username", username); + bookcount = (BigInteger) sqlQuery.uniqueResult(); + } + return bookcount; + } + + @Override + public BigInteger getAuthorsCount() { + BigInteger authorcount = null; + try (Session session = sessionFactory.openSession()) { + SQLQuery sqlQuery = session.createSQLQuery("SELECT count(id) from Author"); + authorcount = (BigInteger) sqlQuery.uniqueResult(); + } + return authorcount; + } + +} diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/util/HibernateUtil.java b/cqrs/src/main/java/com/iluwatar/cqrs/util/HibernateUtil.java new file mode 100644 index 000000000..a5b59e20d --- /dev/null +++ b/cqrs/src/main/java/com/iluwatar/cqrs/util/HibernateUtil.java @@ -0,0 +1,58 @@ +/** + * The MIT License + * Copyright (c) 2014 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.cqrs.util; + +import org.hibernate.SessionFactory; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class simply returns one instance of {@link SessionFactory} initialized when the application is started + * + */ +public class HibernateUtil { + + private static final SessionFactory SESSIONFACTORY = buildSessionFactory(); + private static final Logger LOGGER = LoggerFactory.getLogger(HibernateUtil.class); + + private static SessionFactory buildSessionFactory() { + + // configures settings from hibernate.cfg.xml + final StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure().build(); + try { + return new MetadataSources(registry).buildMetadata().buildSessionFactory(); + } catch (Exception ex) { + StandardServiceRegistryBuilder.destroy(registry); + LOGGER.error("Initial SessionFactory creation failed." + ex); + throw new ExceptionInInitializerError(ex); + } + } + + public static SessionFactory getSessionFactory() { + return SESSIONFACTORY; + } + +} diff --git a/cqrs/src/main/resources/hibernate.cfg.xml b/cqrs/src/main/resources/hibernate.cfg.xml new file mode 100644 index 000000000..4ea142166 --- /dev/null +++ b/cqrs/src/main/resources/hibernate.cfg.xml @@ -0,0 +1,39 @@ + + + + + + + org.hibernate.dialect.H2Dialect + org.h2.Driver + jdbc:h2:mem:test + sa + create + + + + \ No newline at end of file diff --git a/cqrs/src/main/resources/logback.xml b/cqrs/src/main/resources/logback.xml new file mode 100644 index 000000000..e9694c41c --- /dev/null +++ b/cqrs/src/main/resources/logback.xml @@ -0,0 +1,37 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + diff --git a/cqrs/src/test/java/com/iluwatar/cqrs/IntegrationTest.java b/cqrs/src/test/java/com/iluwatar/cqrs/IntegrationTest.java new file mode 100644 index 000000000..536418cbe --- /dev/null +++ b/cqrs/src/test/java/com/iluwatar/cqrs/IntegrationTest.java @@ -0,0 +1,116 @@ +/** + * The MIT License + * Copyright (c) 2014 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.cqrs; + +import java.math.BigInteger; +import java.util.List; + +import com.iluwatar.cqrs.commandes.CommandServiceImpl; +import com.iluwatar.cqrs.commandes.ICommandService; +import com.iluwatar.cqrs.dto.Author; +import com.iluwatar.cqrs.dto.Book; +import com.iluwatar.cqrs.queries.IQueryService; +import com.iluwatar.cqrs.queries.QueryServiceImpl; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Integration test of IQueryService and ICommandService with h2 data + * + */ +public class IntegrationTest { + + private static IQueryService queryService; + private static ICommandService commandService; + + @BeforeAll + public static void initializeAndPopulateDatabase() { + commandService = new CommandServiceImpl(); + queryService = new QueryServiceImpl(); + + // create first author1 + commandService.authorCreated("username1", "name1", "email1"); + + // create author1 and update all its data + commandService.authorCreated("username2", "name2", "email2"); + commandService.authorEmailUpdated("username2", "new_email2"); + commandService.authorNameUpdated("username2", "new_name2"); + commandService.authorUsernameUpdated("username2", "new_username2"); + + // add book1 to author1 + commandService.bookAddedToAuthor("title1", 10, "username1"); + + // add book2 to author1 and update all its data + commandService.bookAddedToAuthor("title2", 20, "username1"); + commandService.bookPriceUpdated("title2", 30); + commandService.bookTitleUpdated("title2", "new_title2"); + + } + + @Test + public void testGetAuthorByUsername() { + Author author = queryService.getAuthorByUsername("username1"); + assertEquals("username1", author.getUsername()); + assertEquals("name1", author.getName()); + assertEquals("email1", author.getEmail()); + } + + @Test + public void testGetUpdatedAuthorByUsername() { + Author author = queryService.getAuthorByUsername("new_username2"); + Author expectedAuthor = new Author("new_name2", "new_email2", "new_username2"); + assertEquals(expectedAuthor, author); + + } + + @Test + public void testGetBook() { + Book book = queryService.getBook("title1"); + assertEquals("title1", book.getTitle()); + assertEquals(10, book.getPrice(), 0.01); + } + + @Test + public void testGetAuthorBooks() { + List books = queryService.getAuthorBooks("username1"); + assertTrue(books.size() == 2); + assertTrue(books.contains(new Book("title1", 10))); + assertTrue(books.contains(new Book("new_title2", 30))); + } + + @Test + public void testGetAuthorBooksCount() { + BigInteger bookCount = queryService.getAuthorBooksCount("username1"); + assertEquals(new BigInteger("2"), bookCount); + } + + @Test + public void testGetAuthorsCount() { + BigInteger authorCount = queryService.getAuthorsCount(); + assertEquals(new BigInteger("2"), authorCount); + } + +} diff --git a/cqrs/src/test/resources/hibernate.cfg.xml b/cqrs/src/test/resources/hibernate.cfg.xml new file mode 100644 index 000000000..4ea142166 --- /dev/null +++ b/cqrs/src/test/resources/hibernate.cfg.xml @@ -0,0 +1,39 @@ + + + + + + + org.hibernate.dialect.H2Dialect + org.h2.Driver + jdbc:h2:mem:test + sa + create + + + + \ No newline at end of file diff --git a/cqrs/src/test/resources/logback.xml b/cqrs/src/test/resources/logback.xml new file mode 100644 index 000000000..e9694c41c --- /dev/null +++ b/cqrs/src/test/resources/logback.xml @@ -0,0 +1,37 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + diff --git a/dao/pom.xml b/dao/pom.xml index f64eff8bc..621af6baf 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -2,7 +2,7 @@ + + 4.0.0 + + 1.16.14 + + + com.iluwatar + java-design-patterns + 1.19.0-SNAPSHOT + + data-bus + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.mockito + mockito-core + test + + + diff --git a/data-bus/src/main/java/com/iluwatar/databus/AbstractDataType.java b/data-bus/src/main/java/com/iluwatar/databus/AbstractDataType.java new file mode 100644 index 000000000..d57e4a014 --- /dev/null +++ b/data-bus/src/main/java/com/iluwatar/databus/AbstractDataType.java @@ -0,0 +1,67 @@ +/** + * The MIT License + * Copyright (c) 2014 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. + */ +/* +The MIT License (MIT) + +Copyright (c) 2016 Paul Campbell + +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.databus; + +/** + * Base for data to send via the Data-Bus. + * + * @author Paul Campbell (pcampbell@kemitix.net) + */ +public class AbstractDataType implements DataType { + + private DataBus dataBus; + + @Override + public DataBus getDataBus() { + return dataBus; + } + + @Override + public void setDataBus(DataBus dataBus) { + this.dataBus = dataBus; + } +} diff --git a/data-bus/src/main/java/com/iluwatar/databus/App.java b/data-bus/src/main/java/com/iluwatar/databus/App.java new file mode 100644 index 000000000..748bb6af0 --- /dev/null +++ b/data-bus/src/main/java/com/iluwatar/databus/App.java @@ -0,0 +1,79 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 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.databus; + +import com.iluwatar.databus.data.MessageData; +import com.iluwatar.databus.data.StartingData; +import com.iluwatar.databus.data.StoppingData; +import com.iluwatar.databus.members.MessageCollectorMember; +import com.iluwatar.databus.members.StatusMember; + +import java.time.LocalDateTime; + +/** + * The Data Bus pattern + *

+ *

{@see http://wiki.c2.com/?DataBusPattern}

+ *

+ *

The Data-Bus pattern provides a method where different parts of an application may + * pass messages between each other without needing to be aware of the other's existence.

+ *

Similar to the {@code ObserverPattern}, members register themselves with the {@link DataBus} + * and may then receive each piece of data that is published to the Data-Bus. The member + * may react to any given message or not.

+ *

It allows for Many-to-Many distribution of data, as there may be any number of + * publishers to a Data-Bus, and any number of members receiving the data. All members + * will receive the same data, the order each receives a given piece of data, is an + * implementation detail.

+ *

Members may unsubscribe from the Data-Bus to stop receiving data.

+ *

This example of the pattern implements a Synchronous Data-Bus, meaning that + * when data is published to the Data-Bus, the publish method will not return until + * all members have received the data and returned.

+ *

The {@link DataBus} class is a Singleton.

+ *

Members of the Data-Bus must implement the {@link Member} interface.

+ *

Data to be published via the Data-Bus must implement the {@link DataType} interface.

+ *

The {@code data} package contains example {@link DataType} implementations.

+ *

The {@code members} package contains example {@link Member} implementations.

+ *

The {@link StatusMember} demonstrates using the DataBus to publish a message + * to the Data-Bus when it receives a message.

+ * + * @author Paul Campbell (pcampbell@kemitix.net) + */ +class App { + + public static void main(String[] args) { + final DataBus bus = DataBus.getInstance(); + bus.subscribe(new StatusMember(1)); + bus.subscribe(new StatusMember(2)); + final MessageCollectorMember foo = new MessageCollectorMember("Foo"); + final MessageCollectorMember bar = new MessageCollectorMember("Bar"); + bus.subscribe(foo); + bus.publish(StartingData.of(LocalDateTime.now())); + bus.publish(MessageData.of("Only Foo should see this")); + bus.subscribe(bar); + bus.publish(MessageData.of("Foo and Bar should see this")); + bus.unsubscribe(foo); + bus.publish(MessageData.of("Only Bar should see this")); + bus.publish(StoppingData.of(LocalDateTime.now())); + } +} diff --git a/data-bus/src/main/java/com/iluwatar/databus/DataBus.java b/data-bus/src/main/java/com/iluwatar/databus/DataBus.java new file mode 100644 index 000000000..edaefe623 --- /dev/null +++ b/data-bus/src/main/java/com/iluwatar/databus/DataBus.java @@ -0,0 +1,73 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 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.databus; + +import java.util.HashSet; +import java.util.Set; + +/** + * The Data-Bus implementation. + * + *

This implementation uses a Singleton.

+ * + * @author Paul Campbell (pcampbell@kemitix.net) + */ +public class DataBus { + + private static final DataBus INSTANCE = new DataBus(); + + private final Set listeners = new HashSet<>(); + + public static DataBus getInstance() { + return INSTANCE; + } + + /** + * Register a member with the data-bus to start receiving events. + * + * @param member The member to register + */ + public void subscribe(final Member member) { + this.listeners.add(member); + } + + /** + * Deregister a member to stop receiving events. + * + * @param member The member to deregister + */ + public void unsubscribe(final Member member) { + this.listeners.remove(member); + } + + /** + * Publish and event to all members. + * + * @param event The event + */ + public void publish(final DataType event) { + event.setDataBus(this); + listeners.forEach(listener -> listener.accept(event)); + } +} diff --git a/data-bus/src/main/java/com/iluwatar/databus/DataType.java b/data-bus/src/main/java/com/iluwatar/databus/DataType.java new file mode 100644 index 000000000..a3dd32870 --- /dev/null +++ b/data-bus/src/main/java/com/iluwatar/databus/DataType.java @@ -0,0 +1,70 @@ +/** + * The MIT License + * Copyright (c) 2014 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. + */ +/* +The MIT License (MIT) + +Copyright (c) 2016 Paul Campbell + +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.databus; + +/** + * Events are sent via the Data-Bus. + * + * @author Paul Campbell (pcampbell@kemitix.net) + */ + +public interface DataType { + + /** + * Returns the data-bus the event is being sent on. + * + * @return The data-bus + */ + DataBus getDataBus(); + + /** + * Set the data-bus the event will be sent on. + * + * @param dataBus The data-bus + */ + void setDataBus(DataBus dataBus); +} diff --git a/data-bus/src/main/java/com/iluwatar/databus/Member.java b/data-bus/src/main/java/com/iluwatar/databus/Member.java new file mode 100644 index 000000000..c2ab7de32 --- /dev/null +++ b/data-bus/src/main/java/com/iluwatar/databus/Member.java @@ -0,0 +1,59 @@ +/** + * The MIT License + * Copyright (c) 2014 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. + */ +/* +The MIT License (MIT) + +Copyright (c) 2016 Paul Campbell + +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.databus; + +import java.util.function.Consumer; + +/** + * Members receive events from the Data-Bus. + * + * @author Paul Campbell (pcampbell@kemitix.net) + */ +public interface Member extends Consumer { + + void accept(DataType event); +} diff --git a/data-bus/src/main/java/com/iluwatar/databus/data/MessageData.java b/data-bus/src/main/java/com/iluwatar/databus/data/MessageData.java new file mode 100644 index 000000000..ac541cf20 --- /dev/null +++ b/data-bus/src/main/java/com/iluwatar/databus/data/MessageData.java @@ -0,0 +1,49 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 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.databus.data; + +import com.iluwatar.databus.AbstractDataType; +import com.iluwatar.databus.DataType; + +/** + * An event raised when a string message is sent. + * + * @author Paul Campbell (pcampbell@kemitix.net) + */ +public class MessageData extends AbstractDataType { + + private final String message; + + public MessageData(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public static DataType of(final String message) { + return new MessageData(message); + } +} diff --git a/data-bus/src/main/java/com/iluwatar/databus/data/StartingData.java b/data-bus/src/main/java/com/iluwatar/databus/data/StartingData.java new file mode 100644 index 000000000..ac7391f1a --- /dev/null +++ b/data-bus/src/main/java/com/iluwatar/databus/data/StartingData.java @@ -0,0 +1,51 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 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.databus.data; + +import com.iluwatar.databus.AbstractDataType; +import com.iluwatar.databus.DataType; + +import java.time.LocalDateTime; + +/** + * An event raised when applications starts, containing the start time of the application. + * + * @author Paul Campbell (pcampbell@kemitix.net) + */ +public class StartingData extends AbstractDataType { + + private final LocalDateTime when; + + public StartingData(LocalDateTime when) { + this.when = when; + } + + public LocalDateTime getWhen() { + return when; + } + + public static DataType of(final LocalDateTime when) { + return new StartingData(when); + } +} diff --git a/data-bus/src/main/java/com/iluwatar/databus/data/StoppingData.java b/data-bus/src/main/java/com/iluwatar/databus/data/StoppingData.java new file mode 100644 index 000000000..976fdc4e9 --- /dev/null +++ b/data-bus/src/main/java/com/iluwatar/databus/data/StoppingData.java @@ -0,0 +1,51 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 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.databus.data; + +import com.iluwatar.databus.AbstractDataType; +import com.iluwatar.databus.DataType; + +import java.time.LocalDateTime; + +/** + * An event raised when applications stops, containing the stop time of the application. + * + * @author Paul Campbell (pcampbell@kemitix.net) + */ +public class StoppingData extends AbstractDataType { + + private final LocalDateTime when; + + public StoppingData(LocalDateTime when) { + this.when = when; + } + + public LocalDateTime getWhen() { + return when; + } + + public static DataType of(final LocalDateTime when) { + return new StoppingData(when); + } +} diff --git a/data-bus/src/main/java/com/iluwatar/databus/members/MessageCollectorMember.java b/data-bus/src/main/java/com/iluwatar/databus/members/MessageCollectorMember.java new file mode 100644 index 000000000..a8ee94ad5 --- /dev/null +++ b/data-bus/src/main/java/com/iluwatar/databus/members/MessageCollectorMember.java @@ -0,0 +1,67 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 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.databus.members; + +import com.iluwatar.databus.DataType; +import com.iluwatar.databus.Member; +import com.iluwatar.databus.data.MessageData; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.logging.Logger; + +/** + * Receiver of Data-Bus events that collects the messages from each {@link MessageData}. + * + * @author Paul Campbell (pcampbell@kemitix.net) + */ +public class MessageCollectorMember implements Member { + + private static final Logger LOGGER = Logger.getLogger(MessageCollectorMember.class.getName()); + + private final String name; + + private List messages = new ArrayList<>(); + + public MessageCollectorMember(String name) { + this.name = name; + } + + @Override + public void accept(final DataType data) { + if (data instanceof MessageData) { + handleEvent((MessageData) data); + } + } + + private void handleEvent(MessageData data) { + LOGGER.info(String.format("%s sees message %s", name, data.getMessage())); + messages.add(data.getMessage()); + } + + public List getMessages() { + return Collections.unmodifiableList(messages); + } +} diff --git a/data-bus/src/main/java/com/iluwatar/databus/members/StatusMember.java b/data-bus/src/main/java/com/iluwatar/databus/members/StatusMember.java new file mode 100644 index 000000000..803e2df20 --- /dev/null +++ b/data-bus/src/main/java/com/iluwatar/databus/members/StatusMember.java @@ -0,0 +1,82 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 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.databus.members; + +import com.iluwatar.databus.DataType; +import com.iluwatar.databus.Member; +import com.iluwatar.databus.data.MessageData; +import com.iluwatar.databus.data.StartingData; +import com.iluwatar.databus.data.StoppingData; + +import java.time.LocalDateTime; +import java.util.logging.Logger; + +/** + * Receiver of Data-Bus events. + * + * @author Paul Campbell (pcampbell@kemitix.net) + */ +public class StatusMember implements Member { + + private static final Logger LOGGER = Logger.getLogger(StatusMember.class.getName()); + + private final int id; + + private LocalDateTime started; + + private LocalDateTime stopped; + + public StatusMember(int id) { + this.id = id; + } + + @Override + public void accept(final DataType data) { + if (data instanceof StartingData) { + handleEvent((StartingData) data); + } else if (data instanceof StoppingData) { + handleEvent((StoppingData) data); + } + } + + private void handleEvent(StartingData data) { + started = data.getWhen(); + LOGGER.info(String.format("Receiver #%d sees application started at %s", id, started)); + } + + private void handleEvent(StoppingData data) { + stopped = data.getWhen(); + LOGGER.info(String.format("Receiver #%d sees application stopping at %s", id, stopped)); + LOGGER.info(String.format("Receiver #%d sending goodbye message", id)); + data.getDataBus().publish(MessageData.of(String.format("Goodbye cruel world from #%d!", id))); + } + + public LocalDateTime getStarted() { + return started; + } + + public LocalDateTime getStopped() { + return stopped; + } +} diff --git a/data-bus/src/test/java/com/iluwatar/databus/DataBusTest.java b/data-bus/src/test/java/com/iluwatar/databus/DataBusTest.java new file mode 100644 index 000000000..83d5386ac --- /dev/null +++ b/data-bus/src/test/java/com/iluwatar/databus/DataBusTest.java @@ -0,0 +1,74 @@ +/** + * The MIT License + * Copyright (c) 2014 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.databus; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.never; + +/** + * Tests for {@link DataBus}. + * + * @author Paul Campbell (pcampbell@kemitix.net) + */ +public class DataBusTest { + + @Mock + private Member member; + + @Mock + private DataType event; + + @BeforeEach + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void publishedEventIsReceivedBySubscribedMember() { + //given + final DataBus dataBus = DataBus.getInstance(); + dataBus.subscribe(member); + //when + dataBus.publish(event); + //then + then(member).should().accept(event); + } + + @Test + public void publishedEventIsNotReceivedByMemberAfterUnsubscribing() { + //given + final DataBus dataBus = DataBus.getInstance(); + dataBus.subscribe(member); + dataBus.unsubscribe(member); + //when + dataBus.publish(event); + //then + then(member).should(never()).accept(event); + } + +} diff --git a/data-bus/src/test/java/com/iluwatar/databus/members/MessageCollectorMemberTest.java b/data-bus/src/test/java/com/iluwatar/databus/members/MessageCollectorMemberTest.java new file mode 100644 index 000000000..be705d6e7 --- /dev/null +++ b/data-bus/src/test/java/com/iluwatar/databus/members/MessageCollectorMemberTest.java @@ -0,0 +1,64 @@ +/** + * The MIT License + * Copyright (c) 2014 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.databus.members; + +import com.iluwatar.databus.data.MessageData; +import com.iluwatar.databus.data.StartingData; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests for {@link MessageCollectorMember}. + * + * @author Paul Campbell (pcampbell@kemitix.net) + */ +public class MessageCollectorMemberTest { + + @Test + public void collectMessageFromMessageData() { + //given + final String message = "message"; + final MessageData messageData = new MessageData(message); + final MessageCollectorMember collector = new MessageCollectorMember("collector"); + //when + collector.accept(messageData); + //then + assertTrue(collector.getMessages().contains(message)); + } + + @Test + public void collectIgnoresMessageFromOtherDataTypes() { + //given + final StartingData startingData = new StartingData(LocalDateTime.now()); + final MessageCollectorMember collector = new MessageCollectorMember("collector"); + //when + collector.accept(startingData); + //then + assertEquals(0, collector.getMessages().size()); + } + +} diff --git a/data-bus/src/test/java/com/iluwatar/databus/members/StatusMemberTest.java b/data-bus/src/test/java/com/iluwatar/databus/members/StatusMemberTest.java new file mode 100644 index 000000000..41c0fccca --- /dev/null +++ b/data-bus/src/test/java/com/iluwatar/databus/members/StatusMemberTest.java @@ -0,0 +1,81 @@ +/** + * The MIT License + * Copyright (c) 2014 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.databus.members; + +import com.iluwatar.databus.DataBus; +import com.iluwatar.databus.data.MessageData; +import com.iluwatar.databus.data.StartingData; +import com.iluwatar.databus.data.StoppingData; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; +import java.time.Month; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * Tests for {@link StatusMember}. + * + * @author Paul Campbell (pcampbell@kemitix.net) + */ +public class StatusMemberTest { + + @Test + public void statusRecordsTheStartTime() { + //given + final LocalDateTime startTime = LocalDateTime.of(2017, Month.APRIL, 1, 19, 9); + final StartingData startingData = new StartingData(startTime); + final StatusMember statusMember = new StatusMember(1); + //when + statusMember.accept(startingData); + //then + assertEquals(startTime, statusMember.getStarted()); + } + + @Test + public void statusRecordsTheStopTime() { + //given + final LocalDateTime stop = LocalDateTime.of(2017, Month.APRIL, 1, 19, 12); + final StoppingData stoppingData = new StoppingData(stop); + stoppingData.setDataBus(DataBus.getInstance()); + final StatusMember statusMember = new StatusMember(1); + //when + statusMember.accept(stoppingData); + //then + assertEquals(stop, statusMember.getStopped()); + } + + @Test + public void statusIgnoresMessageData() { + //given + final MessageData messageData = new MessageData("message"); + final StatusMember statusMember = new StatusMember(1); + //when + statusMember.accept(messageData); + //then + assertNull(statusMember.getStarted()); + assertNull(statusMember.getStopped()); + } + +} diff --git a/data-mapper/index.md b/data-mapper/README.md similarity index 100% rename from data-mapper/index.md rename to data-mapper/README.md diff --git a/data-mapper/pom.xml b/data-mapper/pom.xml index 7d1b83469..ac0d78c33 100644 --- a/data-mapper/pom.xml +++ b/data-mapper/pom.xml @@ -28,13 +28,18 @@ com.iluwatar java-design-patterns - 1.13.0-SNAPSHOT + 1.19.0-SNAPSHOT data-mapper - junit - junit + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine test diff --git a/data-mapper/src/main/java/com/iluwatar/datamapper/Student.java b/data-mapper/src/main/java/com/iluwatar/datamapper/Student.java index 0164533c8..d9c3d389f 100644 --- a/data-mapper/src/main/java/com/iluwatar/datamapper/Student.java +++ b/data-mapper/src/main/java/com/iluwatar/datamapper/Student.java @@ -21,6 +21,9 @@ package com.iluwatar.datamapper; import java.io.Serializable; +/** + * Class defining Student + */ public final class Student implements Serializable { private static final long serialVersionUID = 1L; @@ -32,21 +35,19 @@ public final class Student implements Serializable { /** * Use this constructor to create a Student with all details - * + * * @param studentId as unique student id * @param name as student name * @param grade as respective grade of student */ public Student(final int studentId, final String name, final char grade) { - super(); - this.studentId = studentId; this.name = name; this.grade = grade; } /** - * + * * @return the student id */ public int getStudentId() { @@ -54,7 +55,7 @@ public final class Student implements Serializable { } /** - * + * * @param studentId as unique student id */ public void setStudentId(final int studentId) { @@ -62,7 +63,7 @@ public final class Student implements Serializable { } /** - * + * * @return name of student */ public String getName() { @@ -70,7 +71,7 @@ public final class Student implements Serializable { } /** - * + * * @param name as 'name' of student */ public void setName(final String name) { @@ -78,7 +79,7 @@ public final class Student implements Serializable { } /** - * + * * @return grade of student */ public char getGrade() { @@ -86,7 +87,7 @@ public final class Student implements Serializable { } /** - * + * * @param grade as 'grade of student' */ public void setGrade(final char grade) { @@ -94,7 +95,7 @@ public final class Student implements Serializable { } /** - * + * */ @Override public boolean equals(final Object inputObject) { @@ -120,7 +121,7 @@ public final class Student implements Serializable { } /** - * + * */ @Override public int hashCode() { @@ -130,7 +131,7 @@ public final class Student implements Serializable { } /** - * + * */ @Override public String toString() { diff --git a/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapper.java b/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapper.java index 40f0c5c72..cb93f4697 100644 --- a/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapper.java +++ b/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapper.java @@ -20,6 +20,9 @@ package com.iluwatar.datamapper; import java.util.Optional; +/** + * Interface lists out the possible behaviour for all possible student mappers + */ public interface StudentDataMapper { Optional find(int studentId); diff --git a/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapperImpl.java b/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapperImpl.java index 7ecd9e7f8..685a439ac 100644 --- a/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapperImpl.java +++ b/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapperImpl.java @@ -22,6 +22,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +/** + * Implementation of Actions on Students Data + */ public final class StudentDataMapperImpl implements StudentDataMapper { /* Note: Normally this would be in the form of an actual database */ diff --git a/data-mapper/src/main/resources/log4j.xml b/data-mapper/src/main/resources/log4j.xml index b591c17e1..14f043ff7 100644 --- a/data-mapper/src/main/resources/log4j.xml +++ b/data-mapper/src/main/resources/log4j.xml @@ -2,7 +2,7 @@ + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.19.0-SNAPSHOT + + data-transfer-object + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + log4j + log4j + + + diff --git a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/CustomerClientApp.java b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/CustomerClientApp.java new file mode 100644 index 000000000..f5fcebe03 --- /dev/null +++ b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/CustomerClientApp.java @@ -0,0 +1,84 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Gopinath Langote + * + * 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.datatransfer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +/** + * The Data Transfer Object pattern is a design pattern in which an data transfer object is used to serve related + * information together to avoid multiple call for each piece of information. + *

+ * In this example, ({@link CustomerClientApp}) as as customer details consumer i.e. client to request for + * customer details to server. + *

+ * CustomerResource ({@link CustomerResource}) act as server to serve customer information. + * And The CustomerDto ({@link CustomerDto} is data transfer object to share customer information. + */ +public class CustomerClientApp { + + private static final Logger LOGGER = LoggerFactory.getLogger(CustomerClientApp.class); + + /** + * Method as act client and request to server for details. + * + * @param args program argument. + */ + public static void main(String[] args) { + List customers = new ArrayList<>(); + CustomerDto customerOne = new CustomerDto("1", "Kelly", "Brown"); + CustomerDto customerTwo = new CustomerDto("2", "Alfonso", "Bass"); + customers.add(customerOne); + customers.add(customerTwo); + + CustomerResource customerResource = new CustomerResource(customers); + + LOGGER.info("All customers:-"); + List allCustomers = customerResource.getAllCustomers(); + printCustomerDetails(allCustomers); + + LOGGER.info("----------------------------------------------------------"); + + LOGGER.info("Deleting customer with id {1}"); + customerResource.delete(customerOne.getId()); + allCustomers = customerResource.getAllCustomers(); + printCustomerDetails(allCustomers); + + LOGGER.info("----------------------------------------------------------"); + + LOGGER.info("Adding customer three}"); + CustomerDto customerThree = new CustomerDto("3", "Lynda", "Blair"); + customerResource.save(customerThree); + allCustomers = customerResource.getAllCustomers(); + printCustomerDetails(allCustomers); + } + + private static void printCustomerDetails(List allCustomers) { + allCustomers.forEach(customer -> LOGGER.info(customer.getFirstName())); + } +} diff --git a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/CustomerDto.java b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/CustomerDto.java new file mode 100644 index 000000000..7dedf891c --- /dev/null +++ b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/CustomerDto.java @@ -0,0 +1,60 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Gopinath Langote + * + * 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.datatransfer; + +/** + * {@link CustomerDto} is a data transfer object POJO. Instead of sending individual information to client + * We can send related information together in POJO. + *

+ * Dto will not have any business logic in it. + */ +public class CustomerDto { + private final String id; + private final String firstName; + private final String lastName; + + /** + * @param id customer id + * @param firstName customer first name + * @param lastName customer last name + */ + public CustomerDto(String id, String firstName, String lastName) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + } + + public String getId() { + return id; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } +} diff --git a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/CustomerResource.java b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/CustomerResource.java new file mode 100644 index 000000000..a4926d08c --- /dev/null +++ b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/CustomerResource.java @@ -0,0 +1,63 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Gopinath Langote + * + * 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.datatransfer; + +import java.util.List; + +/** + * The resource class which serves customer information. + * This class act as server in the demo. Which has all customer details. + */ +public class CustomerResource { + private List customers; + + /** + * @param customers initialize resource with existing customers. Act as database. + */ + public CustomerResource(List customers) { + this.customers = customers; + } + + /** + * @return : all customers in list. + */ + public List getAllCustomers() { + return customers; + } + + /** + * @param customer save new customer to list. + */ + public void save(CustomerDto customer) { + customers.add(customer); + } + + /** + * @param customerId delete customer with id {@code customerId} + */ + public void delete(String customerId) { + customers.removeIf(customer -> customer.getId().equals(customerId)); + } +} \ No newline at end of file diff --git a/data-transfer-object/src/test/java/com/iluwatar/datatransfer/CustomerResourceTest.java b/data-transfer-object/src/test/java/com/iluwatar/datatransfer/CustomerResourceTest.java new file mode 100644 index 000000000..db669b785 --- /dev/null +++ b/data-transfer-object/src/test/java/com/iluwatar/datatransfer/CustomerResourceTest.java @@ -0,0 +1,81 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Gopinath Langote + * + * 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.datatransfer; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * tests {@link CustomerResource}. + */ +public class CustomerResourceTest { + @Test + public void shouldGetAllCustomers() { + CustomerDto customer = new CustomerDto("1", "Melody", "Yates"); + List customers = new ArrayList<>(); + customers.add(customer); + + CustomerResource customerResource = new CustomerResource(customers); + + List allCustomers = customerResource.getAllCustomers(); + + assertEquals(allCustomers.size(), 1); + assertEquals(allCustomers.get(0).getId(), "1"); + assertEquals(allCustomers.get(0).getFirstName(), "Melody"); + assertEquals(allCustomers.get(0).getLastName(), "Yates"); + } + + @Test + public void shouldSaveCustomer() { + CustomerDto customer = new CustomerDto("1", "Rita", "Reynolds"); + CustomerResource customerResource = new CustomerResource(new ArrayList<>()); + + customerResource.save(customer); + + List allCustomers = customerResource.getAllCustomers(); + assertEquals(allCustomers.get(0).getId(), "1"); + assertEquals(allCustomers.get(0).getFirstName(), "Rita"); + assertEquals(allCustomers.get(0).getLastName(), "Reynolds"); + } + + @Test + public void shouldDeleteCustomer() { + CustomerDto customer = new CustomerDto("1", "Terry", "Nguyen"); + List customers = new ArrayList<>(); + customers.add(customer); + + CustomerResource customerResource = new CustomerResource(customers); + + customerResource.delete(customer.getId()); + + List allCustomers = customerResource.getAllCustomers(); + assertEquals(allCustomers.size(), 0); + } + +} \ No newline at end of file diff --git a/decorator/README.md b/decorator/README.md index 63795114c..4b6bfe61f 100644 --- a/decorator/README.md +++ b/decorator/README.md @@ -18,14 +18,111 @@ Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality. -![alt text](./etc/decorator.png "Decorator") +## Explanation + +Real world example + +> There is an angry troll living in the nearby hills. Usually it goes bare handed but sometimes it has a weapon. To arm the troll it's not necessary to create a new troll but to decorate it dynamically with a suitable weapon. + +In plain words + +> Decorator pattern lets you dynamically change the behavior of an object at run time by wrapping them in an object of a decorator class. + +Wikipedia says + +> In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. The decorator pattern is often useful for adhering to the Single Responsibility Principle, as it allows functionality to be divided between classes with unique areas of concern. + +**Programmatic Example** + +Let's take the troll example. First of all we have a simple troll implementing the troll interface + +``` +public interface Troll { + void attack(); + int getAttackPower(); + void fleeBattle(); +} + +public class SimpleTroll implements Troll { + + private static final Logger LOGGER = LoggerFactory.getLogger(SimpleTroll.class); + + @Override + public void attack() { + LOGGER.info("The troll tries to grab you!"); + } + + @Override + public int getAttackPower() { + return 10; + } + + @Override + public void fleeBattle() { + LOGGER.info("The troll shrieks in horror and runs away!"); + } +} +``` + +Next we want to add club for the troll. We can do it dynamically by using a decorator + +``` +public class ClubbedTroll implements Troll { + + private static final Logger LOGGER = LoggerFactory.getLogger(ClubbedTroll.class); + + private Troll decorated; + + public ClubbedTroll(Troll decorated) { + this.decorated = decorated; + } + + @Override + public void attack() { + decorated.attack(); + LOGGER.info("The troll swings at you with a club!"); + } + + @Override + public int getAttackPower() { + return decorated.getAttackPower() + 10; + } + + @Override + public void fleeBattle() { + decorated.fleeBattle(); + } +} +``` + +Here's the troll in action + +``` +// simple troll +Troll troll = new SimpleTroll(); +troll.attack(); // The troll tries to grab you! +troll.fleeBattle(); // The troll shrieks in horror and runs away! + +// change the behavior of the simple troll by adding a decorator +troll = new ClubbedTroll(troll); +troll.attack(); // The troll tries to grab you! The troll swings at you with a club! +troll.fleeBattle(); // The troll shrieks in horror and runs away! +``` ## Applicability Use Decorator -* to add responsibilities to individual objects dynamically and transparently, that is, without affecting other objects -* for responsibilities that can be withdrawn -* when extension by subclassing is impractical. Sometimes a large number of independent extensions are possible and would produce an explosion of subclasses to support every combination. Or a class definition may be hidden or otherwise unavailable for subclassing +* To add responsibilities to individual objects dynamically and transparently, that is, without affecting other objects +* For responsibilities that can be withdrawn +* When extension by subclassing is impractical. Sometimes a large number of independent extensions are possible and would produce an explosion of subclasses to support every combination. Or a class definition may be hidden or otherwise unavailable for subclassing + +## Real world examples + * [java.io.InputStream](http://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html), [java.io.OutputStream](http://docs.oracle.com/javase/8/docs/api/java/io/OutputStream.html), + [java.io.Reader](http://docs.oracle.com/javase/8/docs/api/java/io/Reader.html) and [java.io.Writer](http://docs.oracle.com/javase/8/docs/api/java/io/Writer.html) + * [java.util.Collections#synchronizedXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#synchronizedCollection-java.util.Collection-) + * [java.util.Collections#unmodifiableXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#unmodifiableCollection-java.util.Collection-) + * [java.util.Collections#checkedXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#checkedCollection-java.util.Collection-java.lang.Class-) + ## Credits diff --git a/decorator/etc/decorator.png b/decorator/etc/decorator.png deleted file mode 100644 index 47a87b20b..000000000 Binary files a/decorator/etc/decorator.png and /dev/null differ diff --git a/decorator/pom.xml b/decorator/pom.xml index 7ba2a4ee8..687de3ad8 100644 --- a/decorator/pom.xml +++ b/decorator/pom.xml @@ -2,7 +2,7 @@ + + + double-checked-locking.log + + double-checked-locking-%d.log + 5 + + + %-5p [%d{ISO8601,UTC}] %c: %m%n + + + + + + %-5p [%d{ISO8601,UTC}] %c: %m%n + + + + + + + + + + + + + diff --git a/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/AppTest.java b/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/AppTest.java index 748c66c6a..649afa58f 100644 --- a/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/AppTest.java +++ b/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/AppTest.java @@ -1,6 +1,6 @@ /** * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright (c) 2014-2016 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 @@ -22,7 +22,7 @@ */ package com.iluwatar.doublechecked.locking; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * diff --git a/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/InventoryTest.java b/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/InventoryTest.java index 485c9573e..2929afc5e 100644 --- a/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/InventoryTest.java +++ b/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/InventoryTest.java @@ -1,6 +1,6 @@ /** * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright (c) 2014-2016 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 @@ -22,24 +22,25 @@ */ package com.iluwatar.doublechecked.locking; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.AppenderBase; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.LoggerFactory; -import java.io.PrintStream; +import java.util.LinkedList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import static junit.framework.Assert.assertTrue; -import static junit.framework.TestCase.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; +import static java.time.Duration.ofMillis; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTimeout; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Date: 12/10/15 - 9:34 PM @@ -48,31 +49,16 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; */ public class InventoryTest { - /** - * The mocked standard out {@link PrintStream}, used to verify a steady increasing size of the - * {@link Inventory} while adding items from multiple threads concurrently - */ - private final PrintStream stdOutMock = mock(PrintStream.class); + private InMemoryAppender appender; - /** - * Keep the original std-out so it can be restored after the test - */ - private final PrintStream stdOutOrig = System.out; - - /** - * Inject the mocked std-out {@link PrintStream} into the {@link System} class before each test - */ - @Before + @BeforeEach public void setUp() { - System.setOut(this.stdOutMock); + appender = new InMemoryAppender(Inventory.class); } - /** - * Removed the mocked std-out {@link PrintStream} again from the {@link System} class - */ - @After + @AfterEach public void tearDown() { - System.setOut(this.stdOutOrig); + appender.stop(); } /** @@ -92,41 +78,54 @@ public class InventoryTest { * of order, it means that the locking is not ok, increasing the risk of going over the inventory * item limit. */ - @Test(timeout = 10000) + @Test public void testAddItem() throws Exception { - // Create a new inventory with a limit of 1000 items and put some load on the add method - final Inventory inventory = new Inventory(INVENTORY_SIZE); - final ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT); - for (int i = 0; i < THREAD_COUNT; i++) { - executorService.execute(() -> { - while (inventory.addItem(new Item())) {}; - }); - } + assertTimeout(ofMillis(10000), () -> { + // Create a new inventory with a limit of 1000 items and put some load on the add method + final Inventory inventory = new Inventory(INVENTORY_SIZE); + final ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT); + for (int i = 0; i < THREAD_COUNT; i++) { + executorService.execute(() -> { + while (inventory.addItem(new Item())) {}; + }); + } - // Wait until all threads have finished - executorService.shutdown(); - executorService.awaitTermination(5, TimeUnit.SECONDS); + // Wait until all threads have finished + executorService.shutdown(); + executorService.awaitTermination(5, TimeUnit.SECONDS); - // Check the number of items in the inventory. It should not have exceeded the allowed maximum - final List items = inventory.getItems(); - assertNotNull(items); - assertEquals(INVENTORY_SIZE, items.size()); + // Check the number of items in the inventory. It should not have exceeded the allowed maximum + final List items = inventory.getItems(); + assertNotNull(items); + assertEquals(INVENTORY_SIZE, items.size()); - // Capture all stdOut messages ... - final ArgumentCaptor stdOutCaptor = ArgumentCaptor.forClass(String.class); - verify(this.stdOutMock, times(INVENTORY_SIZE)).println(stdOutCaptor.capture()); + assertEquals(INVENTORY_SIZE, appender.getLogSize()); - // ... verify if we got all 1000 - final List values = stdOutCaptor.getAllValues(); - assertEquals(INVENTORY_SIZE, values.size()); - - // ... and check if the inventory size is increasing continuously - for (int i = 0; i < values.size(); i++) { - assertNotNull(values.get(i)); - assertTrue(values.get(i).contains("items.size()=" + (i + 1))); - } - - verifyNoMoreInteractions(this.stdOutMock); + // ... and check if the inventory size is increasing continuously + for (int i = 0; i < items.size(); i++) { + assertTrue(appender.log.get(i).getFormattedMessage().contains("items.size()=" + (i + 1))); + } + }); } -} \ No newline at end of file + + + private class InMemoryAppender extends AppenderBase { + private List log = new LinkedList<>(); + + public InMemoryAppender(Class clazz) { + ((Logger) LoggerFactory.getLogger(clazz)).addAppender(this); + start(); + } + + @Override + protected void append(ILoggingEvent eventObject) { + log.add(eventObject); + } + + public int getLogSize() { + return log.size(); + } + } + +} diff --git a/double-dispatch/pom.xml b/double-dispatch/pom.xml index 4f31b2e7e..85a85d831 100644 --- a/double-dispatch/pom.xml +++ b/double-dispatch/pom.xml @@ -2,7 +2,7 @@ + + 4.0.0 + eip-aggregator + + com.iluwatar + java-design-patterns + 1.19.0-SNAPSHOT + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.apache.camel + camel-core + ${camel.version} + + + + org.apache.camel + camel-spring-boot + ${camel.version} + + + + + com.github.sbrannen + spring-test-junit5 + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.springframework.boot + spring-boot-starter-test + + + + org.apache.camel + camel-test-spring + ${camel.version} + + + + \ No newline at end of file diff --git a/eip-aggregator/src/main/java/com/iluwatar/eip/aggregator/App.java b/eip-aggregator/src/main/java/com/iluwatar/eip/aggregator/App.java new file mode 100644 index 000000000..d55f24e3d --- /dev/null +++ b/eip-aggregator/src/main/java/com/iluwatar/eip/aggregator/App.java @@ -0,0 +1,73 @@ +/** + * The MIT License + * Copyright (c) 2014 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.eip.aggregator; + +import org.apache.camel.CamelContext; +import org.apache.camel.builder.RouteBuilder; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * Sometimes in enterprise systems there is a need to group incoming data in order to process it as a whole. For example + * you may need to gather offers and after defined number of offers has been received you would like to choose the one + * with the best parameters. + * + *

+ * Aggregator allows you to merge messages based on defined criteria and parameters. It gathers original messages, + * applies aggregation strategy and upon fulfilling given criteria, releasing merged messages. + *

+ * + */ +@SpringBootApplication +public class App { + + /** + * Program entry point. It starts Spring Boot application and using Apache Camel it auto-configures routes. + * + * @param args command line args + */ + public static void main(String[] args) throws Exception { + // Run Spring Boot application and obtain ApplicationContext + ConfigurableApplicationContext context = SpringApplication.run(App.class, args); + + // Get CamelContext from ApplicationContext + CamelContext camelContext = (CamelContext) context.getBean("camelContext"); + + // Add a new routes that will handle endpoints form SplitterRoute class. + camelContext.addRoutes(new RouteBuilder() { + + @Override + public void configure() throws Exception { + from("{{endpoint}}").log("ENDPOINT: ${body}"); + } + + }); + + // Add producer that will send test message to an entry point in WireTapRoute + String[] stringArray = {"Test item #1", "Test item #2", "Test item #3"}; + camelContext.createProducerTemplate().sendBody("{{entry}}", stringArray); + + SpringApplication.exit(context); + } +} diff --git a/eip-aggregator/src/main/java/com/iluwatar/eip/aggregator/routes/AggregatorRoute.java b/eip-aggregator/src/main/java/com/iluwatar/eip/aggregator/routes/AggregatorRoute.java new file mode 100644 index 000000000..536565339 --- /dev/null +++ b/eip-aggregator/src/main/java/com/iluwatar/eip/aggregator/routes/AggregatorRoute.java @@ -0,0 +1,60 @@ +/** + * The MIT License + * Copyright (c) 2014 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.eip.aggregator.routes; + +import org.apache.camel.builder.RouteBuilder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Sample aggregator route definition. + * + *

+ * It consumes messages out of the direct:entry entry point and forwards them to direct:endpoint. + * Route accepts messages containing String as a body, it aggregates the messages based on the settings and forwards + * them as CSV to the output chanel. + * + * Settings for the aggregation are: aggregate until 3 messages are bundled or wait 2000ms before sending bundled + * messages further. + *

+ * + * In this example input/output endpoints names are stored in application.properties file. + */ +@Component +public class AggregatorRoute extends RouteBuilder { + + @Autowired + private MessageAggregationStrategy aggregator; + + /** + * Configures the route + * @throws Exception in case of exception during configuration + */ + @Override + public void configure() throws Exception { + // Main route + from("{{entry}}").aggregate(constant(true), aggregator) + .completionSize(3).completionInterval(2000) + .to("{{endpoint}}"); + } +} diff --git a/eip-aggregator/src/main/java/com/iluwatar/eip/aggregator/routes/MessageAggregationStrategy.java b/eip-aggregator/src/main/java/com/iluwatar/eip/aggregator/routes/MessageAggregationStrategy.java new file mode 100644 index 000000000..4b5e4cb2f --- /dev/null +++ b/eip-aggregator/src/main/java/com/iluwatar/eip/aggregator/routes/MessageAggregationStrategy.java @@ -0,0 +1,49 @@ +/** + * The MIT License + * Copyright (c) 2014 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.eip.aggregator.routes; + +import org.apache.camel.Exchange; +import org.apache.camel.processor.aggregate.AggregationStrategy; +import org.springframework.stereotype.Component; + +/** + * Aggregation strategy joining bodies of messages. If message is first one oldMessage is null. All changes are + * made on IN messages. + */ +@Component +public class MessageAggregationStrategy implements AggregationStrategy { + + @Override + public Exchange aggregate(Exchange oldExchange, Exchange newExchange) { + if (oldExchange == null) { + return newExchange; + } + + String in1 = (String) oldExchange.getIn().getBody(); + String in2 = (String) newExchange.getIn().getBody(); + + oldExchange.getIn().setBody(in1 + ";" + in2); + + return oldExchange; + } +} diff --git a/eip-aggregator/src/main/resources/application.properties b/eip-aggregator/src/main/resources/application.properties new file mode 100644 index 000000000..044833fc1 --- /dev/null +++ b/eip-aggregator/src/main/resources/application.properties @@ -0,0 +1,25 @@ +# +# The MIT License +# Copyright (c) 2014 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. +# + +entry=direct:entry +endpoint=direct:endpoint diff --git a/eip-aggregator/src/test/java/com/iluwatar/eip/aggregator/AppTest.java b/eip-aggregator/src/test/java/com/iluwatar/eip/aggregator/AppTest.java new file mode 100644 index 000000000..40a336786 --- /dev/null +++ b/eip-aggregator/src/test/java/com/iluwatar/eip/aggregator/AppTest.java @@ -0,0 +1,37 @@ +/** + * The MIT License + * Copyright (c) 2014 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.eip.aggregator; + +import org.junit.jupiter.api.Test; + +/** + * Test for App class + */ +public class AppTest { + + @Test + public void testMain() throws Exception { + String[] args = {}; + App.main(args); + } +} diff --git a/eip-aggregator/src/test/java/com/iluwatar/eip/aggregator/routes/AggregatorRouteTest.java b/eip-aggregator/src/test/java/com/iluwatar/eip/aggregator/routes/AggregatorRouteTest.java new file mode 100644 index 000000000..2c7d207d6 --- /dev/null +++ b/eip-aggregator/src/test/java/com/iluwatar/eip/aggregator/routes/AggregatorRouteTest.java @@ -0,0 +1,84 @@ +/** + * The MIT License + * Copyright (c) 2014 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.eip.aggregator.routes; + +import org.apache.camel.EndpointInject; +import org.apache.camel.ProducerTemplate; +import org.apache.camel.component.mock.MockEndpoint; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Test class for AggregatorRoute. + *

+ * In order for it to work we have to mock endpoints we want to read/write to. To mock those we need to substitute + * original endpoint names to mocks. + *

+ */ +@ExtendWith(SpringExtension.class) +@SpringApplicationConfiguration(classes = AggregatorRouteTest.class) +@ActiveProfiles("test") +@EnableAutoConfiguration +@ComponentScan +public class AggregatorRouteTest { + + @EndpointInject(uri = "{{entry}}") + private ProducerTemplate entry; + + @EndpointInject(uri = "{{endpoint}}") + private MockEndpoint endpoint; + + /** + * Test if endpoint receives three separate messages. + * @throws Exception in case of en exception during the test + */ + @Test + @DirtiesContext + public void testSplitter() throws Exception { + + // Three items in one entry message + entry.sendBody("TEST1"); + entry.sendBody("TEST2"); + entry.sendBody("TEST3"); + entry.sendBody("TEST4"); + entry.sendBody("TEST5"); + + // Endpoint should have three different messages in the end order of the messages is not important + endpoint.expectedMessageCount(2); + endpoint.assertIsSatisfied(); + + String body = (String) endpoint.getReceivedExchanges().get(0).getIn().getBody(); + assertEquals(3, body.split(";").length); + + String body2 = (String) endpoint.getReceivedExchanges().get(1).getIn().getBody(); + assertEquals(2, body2.split(";").length); + } +} diff --git a/eip-aggregator/src/test/java/com/iluwatar/eip/aggregator/routes/MessageAggregationStrategyTest.java b/eip-aggregator/src/test/java/com/iluwatar/eip/aggregator/routes/MessageAggregationStrategyTest.java new file mode 100644 index 000000000..7a7a15154 --- /dev/null +++ b/eip-aggregator/src/test/java/com/iluwatar/eip/aggregator/routes/MessageAggregationStrategyTest.java @@ -0,0 +1,64 @@ +/** + * The MIT License + * Copyright (c) 2014 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.eip.aggregator.routes; + +import org.apache.camel.CamelContext; +import org.apache.camel.Exchange; +import org.apache.camel.impl.DefaultExchange; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Tests MessageAggregationStrategy + */ +public class MessageAggregationStrategyTest { + + @Test + public void testAggregate() { + MessageAggregationStrategy mas = new MessageAggregationStrategy(); + Exchange oldExchange = new DefaultExchange((CamelContext) null); + oldExchange.getIn().setBody("TEST1"); + + Exchange newExchange = new DefaultExchange((CamelContext) null); + newExchange.getIn().setBody("TEST2"); + + Exchange output = mas.aggregate(oldExchange, newExchange); + String outputBody = (String) output.getIn().getBody(); + assertEquals("TEST1;TEST2", outputBody); + } + + @Test + public void testAggregateOldNull() { + MessageAggregationStrategy mas = new MessageAggregationStrategy(); + + Exchange newExchange = new DefaultExchange((CamelContext) null); + newExchange.getIn().setBody("TEST2"); + + Exchange output = mas.aggregate(null, newExchange); + String outputBody = (String) output.getIn().getBody(); + + assertEquals(newExchange, output); + assertEquals("TEST2", outputBody); + } +} diff --git a/eip-aggregator/src/test/resources/application-test.properties b/eip-aggregator/src/test/resources/application-test.properties new file mode 100644 index 000000000..8d6ecbbd3 --- /dev/null +++ b/eip-aggregator/src/test/resources/application-test.properties @@ -0,0 +1,25 @@ +# +# The MIT License +# Copyright (c) 2014 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. +# + +entry=direct:entry +endpoint=mock:endpoint diff --git a/eip-splitter/README.md b/eip-splitter/README.md new file mode 100644 index 000000000..51a917bad --- /dev/null +++ b/eip-splitter/README.md @@ -0,0 +1,32 @@ +--- +layout: pattern +title: EIP Splitter +folder: eip-splitter +permalink: /patterns/eip-splitter/ +categories: Integration +tags: + - Java + - Difficulty-Intermittent + - EIP +--- + +## Intent +It is very common in integration systems that incoming messages consists of many items bundled together. For example +an invoice document contains multiple invoice lines describing transaction (quantity, name of provided +service/sold goods, price etc.). Such bundled messages may not be accepted by other systems. This is where splitter +pattern comes in handy. It will take the whole document, split it based on given criteria and send individual +items to the endpoint. + +![alt text](./etc/sequencer.gif "Splitter") + +## Applicability +Use the Splitter pattern when + +* You need to split received data into smaller pieces to process them individually +* You need to control the size of data batches you are able to process + +## Credits + +* [Gregor Hohpe, Bobby Woolf - Enterprise Integration Patterns](http://www.enterpriseintegrationpatterns.com/patterns/messaging/Sequencer.html) +* [Apache Camel - Documentation](http://camel.apache.org/splitter.html) + diff --git a/eip-splitter/etc/sequencer.gif b/eip-splitter/etc/sequencer.gif new file mode 100644 index 000000000..a925fa209 Binary files /dev/null and b/eip-splitter/etc/sequencer.gif differ diff --git a/eip-splitter/pom.xml b/eip-splitter/pom.xml new file mode 100644 index 000000000..54661f946 --- /dev/null +++ b/eip-splitter/pom.xml @@ -0,0 +1,78 @@ + + + + 4.0.0 + eip-splitter + + com.iluwatar + java-design-patterns + 1.19.0-SNAPSHOT + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.apache.camel + camel-core + ${camel.version} + + + + org.apache.camel + camel-spring-boot + ${camel.version} + + + + + com.github.sbrannen + spring-test-junit5 + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.springframework.boot + spring-boot-starter-test + + + + org.apache.camel + camel-test-spring + ${camel.version} + + + + \ No newline at end of file diff --git a/eip-splitter/src/main/java/com/iluwatar/eip/splitter/App.java b/eip-splitter/src/main/java/com/iluwatar/eip/splitter/App.java new file mode 100644 index 000000000..ceadb5f8d --- /dev/null +++ b/eip-splitter/src/main/java/com/iluwatar/eip/splitter/App.java @@ -0,0 +1,75 @@ +/** + * The MIT License + * Copyright (c) 2014 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.eip.splitter; + +import org.apache.camel.CamelContext; +import org.apache.camel.builder.RouteBuilder; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * It is very common in integration systems that incoming messages consists of many items bundled together. For example + * an invoice document contains multiple invoice lines describing transaction (quantity, name of provided + * service/sold goods, price etc.). Such bundled messages may not be accepted by other systems. This is where splitter + * pattern comes in handy. It will take the whole document, split it based on given criteria and send individual + * items to the endpoint. + * + *

+ * Splitter allows you to split messages based on defined criteria. It takes original message, process it and send + * multiple parts to the output channel. It is not defined if it should keep the order of items though. + *

+ * + */ +@SpringBootApplication +public class App { + + /** + * Program entry point. It starts Spring Boot application and using Apache Camel it auto-configures routes. + * + * @param args command line args + */ + public static void main(String[] args) throws Exception { + // Run Spring Boot application and obtain ApplicationContext + ConfigurableApplicationContext context = SpringApplication.run(App.class, args); + + // Get CamelContext from ApplicationContext + CamelContext camelContext = (CamelContext) context.getBean("camelContext"); + + // Add a new routes that will handle endpoints form SplitterRoute class. + camelContext.addRoutes(new RouteBuilder() { + + @Override + public void configure() throws Exception { + from("{{endpoint}}").log("ENDPOINT: ${body}"); + } + + }); + + // Add producer that will send test message to an entry point in WireTapRoute + String[] stringArray = {"Test item #1", "Test item #2", "Test item #3"}; + camelContext.createProducerTemplate().sendBody("{{entry}}", stringArray); + + SpringApplication.exit(context); + } +} diff --git a/eip-splitter/src/main/java/com/iluwatar/eip/splitter/routes/SplitterRoute.java b/eip-splitter/src/main/java/com/iluwatar/eip/splitter/routes/SplitterRoute.java new file mode 100644 index 000000000..f7eb28dea --- /dev/null +++ b/eip-splitter/src/main/java/com/iluwatar/eip/splitter/routes/SplitterRoute.java @@ -0,0 +1,51 @@ +/** + * The MIT License + * Copyright (c) 2014 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.eip.splitter.routes; + +import org.apache.camel.builder.RouteBuilder; +import org.springframework.stereotype.Component; + +/** + * Sample splitter route definition. + * + *

+ * It consumes messages out of the direct:entry entry point and forwards them to direct:endpoint. + * Route accepts messages having body of array or collection of objects. Splitter component split message body and + * forwards single objects to the endpoint. + *

+ * + * In this example input/output endpoints names are stored in application.properties file. + */ +@Component +public class SplitterRoute extends RouteBuilder { + + /** + * Configures the route + * @throws Exception in case of exception during configuration + */ + @Override + public void configure() throws Exception { + // Main route + from("{{entry}}").split().body().to("{{endpoint}}"); + } +} diff --git a/eip-splitter/src/main/resources/application.properties b/eip-splitter/src/main/resources/application.properties new file mode 100644 index 000000000..044833fc1 --- /dev/null +++ b/eip-splitter/src/main/resources/application.properties @@ -0,0 +1,25 @@ +# +# The MIT License +# Copyright (c) 2014 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. +# + +entry=direct:entry +endpoint=direct:endpoint diff --git a/eip-splitter/src/test/java/com/iluwatar/eip/splitter/AppTest.java b/eip-splitter/src/test/java/com/iluwatar/eip/splitter/AppTest.java new file mode 100644 index 000000000..fe3eca01e --- /dev/null +++ b/eip-splitter/src/test/java/com/iluwatar/eip/splitter/AppTest.java @@ -0,0 +1,37 @@ +/** + * The MIT License + * Copyright (c) 2014 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.eip.splitter; + +import org.junit.jupiter.api.Test; + +/** + * Test for App class + */ +public class AppTest { + + @Test + public void testMain() throws Exception { + String[] args = {}; + App.main(args); + } +} diff --git a/eip-splitter/src/test/java/com/iluwatar/eip/splitter/routes/SplitterRouteTest.java b/eip-splitter/src/test/java/com/iluwatar/eip/splitter/routes/SplitterRouteTest.java new file mode 100644 index 000000000..9257a4410 --- /dev/null +++ b/eip-splitter/src/test/java/com/iluwatar/eip/splitter/routes/SplitterRouteTest.java @@ -0,0 +1,72 @@ +/** + * The MIT License + * Copyright (c) 2014 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.eip.splitter.routes; + +import org.apache.camel.EndpointInject; +import org.apache.camel.ProducerTemplate; +import org.apache.camel.component.mock.MockEndpoint; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * Test class for SplitterRoute. + *

+ * In order for it to work we have to mock endpoints we want to read/write to. To mock those we need to substitute + * original endpoint names to mocks. + *

+ */ +@ExtendWith(SpringExtension.class) +@SpringApplicationConfiguration(classes = SplitterRouteTest.class) +@ActiveProfiles("test") +@EnableAutoConfiguration +@ComponentScan +public class SplitterRouteTest { + + @EndpointInject(uri = "{{entry}}") + private ProducerTemplate entry; + + @EndpointInject(uri = "{{endpoint}}") + private MockEndpoint endpoint; + + /** + * Test if endpoint receives three separate messages. + * @throws Exception in case of en exception during the test + */ + @Test + @DirtiesContext + public void testSplitter() throws Exception { + + // Three items in one entry message + entry.sendBody(new String[] {"TEST1", "TEST2", "TEST3"}); + + // Endpoint should have three different messages in the end order of the messages is not important + endpoint.expectedMessageCount(3); + endpoint.assertIsSatisfied(); + } +} diff --git a/eip-splitter/src/test/resources/application-test.properties b/eip-splitter/src/test/resources/application-test.properties new file mode 100644 index 000000000..8d6ecbbd3 --- /dev/null +++ b/eip-splitter/src/test/resources/application-test.properties @@ -0,0 +1,25 @@ +# +# The MIT License +# Copyright (c) 2014 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. +# + +entry=direct:entry +endpoint=mock:endpoint diff --git a/eip-wire-tap/README.md b/eip-wire-tap/README.md new file mode 100644 index 000000000..31cbd9123 --- /dev/null +++ b/eip-wire-tap/README.md @@ -0,0 +1,29 @@ +--- +layout: pattern +title: EIP Wire Tap +folder: eip-wire-tap +permalink: /patterns/eip-wire-tap/ +categories: Integration +tags: + - Java + - Difficulty-Intermittent + - EIP +--- + +## Intent +In most integration cases there is a need to monitor the messages flowing through the system. It is usually achieved +by intercepting the message and redirecting it to a different location like console, filesystem or the database. +It is important that such functionality should not modify the original message and influence the processing path. + +![alt text](./etc/wiretap.gif "Wire Tap") + +## Applicability +Use the Wire Tap pattern when + +* You need to monitor messages flowing through the system +* You need to redirect the same, unchanged message to two different endpoints/paths + +## Credits + +* [Gregor Hohpe, Bobby Woolf - Enterprise Integration Patterns](http://www.enterpriseintegrationpatterns.com/patterns/messaging/WireTap.html) +* [Apache Camel - Documentation](http://camel.apache.org/wire-tap.html) diff --git a/eip-wire-tap/etc/wiretap.gif b/eip-wire-tap/etc/wiretap.gif new file mode 100644 index 000000000..414173716 Binary files /dev/null and b/eip-wire-tap/etc/wiretap.gif differ diff --git a/eip-wire-tap/pom.xml b/eip-wire-tap/pom.xml new file mode 100644 index 000000000..688f4ad6d --- /dev/null +++ b/eip-wire-tap/pom.xml @@ -0,0 +1,78 @@ + + + + 4.0.0 + eip-wire-tap + + com.iluwatar + java-design-patterns + 1.19.0-SNAPSHOT + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.apache.camel + camel-core + ${camel.version} + + + + org.apache.camel + camel-spring-boot + ${camel.version} + + + + + com.github.sbrannen + spring-test-junit5 + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.springframework.boot + spring-boot-starter-test + + + + org.apache.camel + camel-test-spring + ${camel.version} + + + + \ No newline at end of file diff --git a/eip-wire-tap/src/main/java/com/iluwatar/eip/wiretap/App.java b/eip-wire-tap/src/main/java/com/iluwatar/eip/wiretap/App.java new file mode 100644 index 000000000..ca605cb78 --- /dev/null +++ b/eip-wire-tap/src/main/java/com/iluwatar/eip/wiretap/App.java @@ -0,0 +1,73 @@ +/** + * The MIT License + * Copyright (c) 2014 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.eip.wiretap; + +import org.apache.camel.CamelContext; +import org.apache.camel.builder.RouteBuilder; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * In most integration cases there is a need to monitor the messages flowing through the system. It is usually achieved + * by intercepting the message and redirecting it to a different location like console, filesystem or the database. + * It is important that such functionality should not modify the original message and influence the processing path. + * + *

+ * Wire Tap allows you to route messages to a separate location while they are being forwarded to the ultimate + * destination. It basically consumes messages of the input channel and publishes the unmodified message to both + * output channels. + *

+ */ +@SpringBootApplication +public class App { + + /** + * Program entry point. It starts Spring Boot application and using Apache Camel it auto-configures routes. + * + * @param args command line args + */ + public static void main(String[] args) throws Exception { + // Run Spring Boot application and obtain ApplicationContext + ConfigurableApplicationContext context = SpringApplication.run(App.class, args); + + // Get CamelContext from ApplicationContext + CamelContext camelContext = (CamelContext) context.getBean("camelContext"); + + // Add a new routes that will handle endpoints form WireTapRoute class. + camelContext.addRoutes(new RouteBuilder() { + + @Override + public void configure() throws Exception { + from("{{endpoint}}").log("ENDPOINT: ${body}"); + from("{{wireTapEndpoint}}").log("WIRETAPPED ENDPOINT: ${body}"); + } + + }); + + // Add producer that will send test message to an entry point in WireTapRoute + camelContext.createProducerTemplate().sendBody("{{entry}}", "Test message"); + + SpringApplication.exit(context); + } +} diff --git a/eip-wire-tap/src/main/java/com/iluwatar/eip/wiretap/routes/WireTapRoute.java b/eip-wire-tap/src/main/java/com/iluwatar/eip/wiretap/routes/WireTapRoute.java new file mode 100644 index 000000000..994ceacdb --- /dev/null +++ b/eip-wire-tap/src/main/java/com/iluwatar/eip/wiretap/routes/WireTapRoute.java @@ -0,0 +1,54 @@ +/** + * The MIT License + * Copyright (c) 2014 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.eip.wiretap.routes; + +import org.apache.camel.builder.RouteBuilder; +import org.springframework.stereotype.Component; + +/** + * Sample wire tap route definition. + * + *

+ * It consumes messages out of the direct:entry entry point and forwards them to direct:endpoint. + * Wire Tap intercepts the message and sends it to direct:wireTap, which in turn forwards it to + * direct:wireTapEndpoint. + *

+ * + * In this example input/output endpoints names are stored in application.properties file. + */ +@Component +public class WireTapRoute extends RouteBuilder { + + /** + * Configures the route + * @throws Exception in case of exception during configuration + */ + @Override + public void configure() throws Exception { + // Main route + from("{{entry}}").wireTap("direct:wireTap").to("{{endpoint}}"); + + // Wire tap route + from("direct:wireTap").log("Message: ${body}").to("{{wireTapEndpoint}}"); + } +} diff --git a/eip-wire-tap/src/main/resources/application.properties b/eip-wire-tap/src/main/resources/application.properties new file mode 100644 index 000000000..90a152425 --- /dev/null +++ b/eip-wire-tap/src/main/resources/application.properties @@ -0,0 +1,26 @@ +# +# The MIT License +# Copyright (c) 2014 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. +# + +entry=direct:entry +endpoint=direct:endpoint +wireTapEndpoint=direct:wireTapEndpoint \ No newline at end of file diff --git a/eip-wire-tap/src/test/java/com/iluwatar/eip/wiretap/AppTest.java b/eip-wire-tap/src/test/java/com/iluwatar/eip/wiretap/AppTest.java new file mode 100644 index 000000000..673803df7 --- /dev/null +++ b/eip-wire-tap/src/test/java/com/iluwatar/eip/wiretap/AppTest.java @@ -0,0 +1,37 @@ +/** + * The MIT License + * Copyright (c) 2014 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.eip.wiretap; + +import org.junit.jupiter.api.Test; + +/** + * Test for App class + */ +public class AppTest { + + @Test + public void testMain() throws Exception { + String[] args = {}; + App.main(args); + } +} diff --git a/eip-wire-tap/src/test/java/com/iluwatar/eip/wiretap/routes/WireTapRouteTest.java b/eip-wire-tap/src/test/java/com/iluwatar/eip/wiretap/routes/WireTapRouteTest.java new file mode 100644 index 000000000..449f86208 --- /dev/null +++ b/eip-wire-tap/src/test/java/com/iluwatar/eip/wiretap/routes/WireTapRouteTest.java @@ -0,0 +1,84 @@ +/** + * The MIT License + * Copyright (c) 2014 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.eip.wiretap.routes; + +import org.apache.camel.EndpointInject; +import org.apache.camel.Message; +import org.apache.camel.ProducerTemplate; +import org.apache.camel.component.mock.MockEndpoint; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Test class for WireTapRoute. + *

+ * In order for it to work we have to mock endpoints we want to read/write to. To mock those we need to substitute + * original endpoint names to mocks. + *

+ */ +@ExtendWith(SpringExtension.class) +@SpringApplicationConfiguration(classes = WireTapRouteTest.class) +@ActiveProfiles("test") +@EnableAutoConfiguration +@ComponentScan +public class WireTapRouteTest { + + @EndpointInject(uri = "{{entry}}") + private ProducerTemplate entry; + + @EndpointInject(uri = "{{endpoint}}") + private MockEndpoint endpoint; + + @EndpointInject(uri = "{{wireTapEndpoint}}") + private MockEndpoint wireTapEndpoint; + + /** + * Test if both endpoints receive exactly one message containing the same, unchanged body. + * @throws Exception in case of en exception during the test + */ + @Test + @DirtiesContext + public void testWireTap() throws Exception { + entry.sendBody("TEST"); + + endpoint.expectedMessageCount(1); + wireTapEndpoint.expectedMessageCount(1); + + endpoint.assertIsSatisfied(); + wireTapEndpoint.assertIsSatisfied(); + + Message endpointIn = endpoint.getExchanges().get(0).getIn(); + Message wireTapEndpointIn = wireTapEndpoint.getExchanges().get(0).getIn(); + + assertEquals("TEST", endpointIn.getBody()); + assertEquals("TEST", wireTapEndpointIn.getBody()); + } +} diff --git a/eip-wire-tap/src/test/resources/application-test.properties b/eip-wire-tap/src/test/resources/application-test.properties new file mode 100644 index 000000000..e76faa1fc --- /dev/null +++ b/eip-wire-tap/src/test/resources/application-test.properties @@ -0,0 +1,26 @@ +# +# The MIT License +# Copyright (c) 2014 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. +# + +entry=direct:entry +endpoint=mock:endpoint +wireTapEndpoint=mock:wireTapEndpoint \ No newline at end of file diff --git a/event-aggregator/pom.xml b/event-aggregator/pom.xml index b7de6e01b..c28fded6f 100644 --- a/event-aggregator/pom.xml +++ b/event-aggregator/pom.xml @@ -1,7 +1,7 @@ + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.19.0-SNAPSHOT + + event-asynchronous + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + diff --git a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/App.java b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/App.java new file mode 100644 index 000000000..6e9138d3c --- /dev/null +++ b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/App.java @@ -0,0 +1,211 @@ +/** + * The MIT License Copyright (c) 2014-2016 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.event.asynchronous; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; +import java.util.Scanner; + +/** + * + * This application demonstrates the Event-based Asynchronous pattern. Essentially, users (of the pattern) may + * choose to run events in an Asynchronous or Synchronous mode. There can be multiple Asynchronous events running at + * once but only one Synchronous event can run at a time. Asynchronous events are synonymous to multi-threads. The key + * point here is that the threads run in the background and the user is free to carry on with other processes. Once an + * event is complete, the appropriate listener/callback method will be called. The listener then proceeds to carry out + * further processing depending on the needs of the user. + * + * The {@link EventManager} manages the events/threads that the user creates. Currently, the supported event operations + * are: start, stop, getStatus. For Synchronous events, the user is unable to + * start another (Synchronous) event if one is already running at the time. The running event would have to either be + * stopped or completed before a new event can be started. + * + * The Event-based Asynchronous Pattern makes available the advantages of multithreaded applications while hiding many + * of the complex issues inherent in multithreaded design. Using a class that supports this pattern can allow you to:- + * (1) Perform time-consuming tasks, such as downloads and database operations, "in the background," without + * interrupting your application. (2) Execute multiple operations simultaneously, receiving notifications when each + * completes. (3) Wait for resources to become available without stopping ("hanging") your application. (4) Communicate + * with pending asynchronous operations using the familiar events-and-delegates model. + * + * @see EventManager + * @see Event + * + */ +public class App { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + public static final String PROP_FILE_NAME = "config.properties"; + + boolean interactiveMode = false; + + /** + * Program entry point. + * + * @param args command line args + */ + public static void main(String[] args) { + App app = new App(); + + app.setUp(); + app.run(); + } + + /** + * App can run in interactive mode or not. Interactive mode == Allow user interaction with command line. + * Non-interactive is a quick sequential run through the available {@link EventManager} operations. + */ + public void setUp() { + Properties prop = new Properties(); + + InputStream inputStream = App.class.getClassLoader().getResourceAsStream(PROP_FILE_NAME); + + if (inputStream != null) { + try { + prop.load(inputStream); + } catch (IOException e) { + LOGGER.error("{} was not found. Defaulting to non-interactive mode.", PROP_FILE_NAME, e); + } + String property = prop.getProperty("INTERACTIVE_MODE"); + if (property.equalsIgnoreCase("YES")) { + interactiveMode = true; + } + } + } + + /** + * Run program in either interactive mode or not. + */ + public void run() { + if (interactiveMode) { + runInteractiveMode(); + } else { + quickRun(); + } + } + + /** + * Run program in non-interactive mode. + */ + public void quickRun() { + EventManager eventManager = new EventManager(); + + try { + // Create an Asynchronous event. + int aEventId = eventManager.createAsync(60); + LOGGER.info("Async Event [{}] has been created.", aEventId); + eventManager.start(aEventId); + LOGGER.info("Async Event [{}] has been started.", aEventId); + + // Create a Synchronous event. + int sEventId = eventManager.create(60); + LOGGER.info("Sync Event [{}] has been created.", sEventId); + eventManager.start(sEventId); + LOGGER.info("Sync Event [{}] has been started.", sEventId); + + eventManager.status(aEventId); + eventManager.status(sEventId); + + eventManager.cancel(aEventId); + LOGGER.info("Async Event [{}] has been stopped.", aEventId); + eventManager.cancel(sEventId); + LOGGER.info("Sync Event [{}] has been stopped.", sEventId); + + } catch (MaxNumOfEventsAllowedException | LongRunningEventException | EventDoesNotExistException + | InvalidOperationException e) { + LOGGER.error(e.getMessage()); + } + } + + /** + * Run program in interactive mode. + */ + public void runInteractiveMode() { + EventManager eventManager = new EventManager(); + + Scanner s = new Scanner(System.in); + int option = -1; + while (option != 4) { + LOGGER.info("Hello. Would you like to boil some eggs?"); + LOGGER.info("(1) BOIL AN EGG \n(2) STOP BOILING THIS EGG \n(3) HOW ARE MY EGGS? \n(4) EXIT"); + LOGGER.info("Choose [1,2,3,4]: "); + option = s.nextInt(); + + if (option == 1) { + s.nextLine(); + LOGGER.info("Boil multiple eggs at once (A) or boil them one-by-one (S)?: "); + String eventType = s.nextLine(); + LOGGER.info("How long should this egg be boiled for (in seconds)?: "); + int eventTime = s.nextInt(); + if (eventType.equalsIgnoreCase("A")) { + try { + int eventId = eventManager.createAsync(eventTime); + eventManager.start(eventId); + LOGGER.info("Egg [{}] is being boiled.", eventId); + } catch (MaxNumOfEventsAllowedException | LongRunningEventException | EventDoesNotExistException e) { + LOGGER.error(e.getMessage()); + } + } else if (eventType.equalsIgnoreCase("S")) { + try { + int eventId = eventManager.create(eventTime); + eventManager.start(eventId); + LOGGER.info("Egg [{}] is being boiled.", eventId); + } catch (MaxNumOfEventsAllowedException | InvalidOperationException | LongRunningEventException + | EventDoesNotExistException e) { + LOGGER.error(e.getMessage()); + } + } else { + LOGGER.info("Unknown event type."); + } + } else if (option == 2) { + LOGGER.info("Which egg?: "); + int eventId = s.nextInt(); + try { + eventManager.cancel(eventId); + LOGGER.info("Egg [{}] is removed from boiler.", eventId); + } catch (EventDoesNotExistException e) { + LOGGER.error(e.getMessage()); + } + } else if (option == 3) { + s.nextLine(); + LOGGER.info("Just one egg (O) OR all of them (A) ?: "); + String eggChoice = s.nextLine(); + + if (eggChoice.equalsIgnoreCase("O")) { + LOGGER.info("Which egg?: "); + int eventId = s.nextInt(); + try { + eventManager.status(eventId); + } catch (EventDoesNotExistException e) { + LOGGER.error(e.getMessage()); + } + } else if (eggChoice.equalsIgnoreCase("A")) { + eventManager.statusOfAllEvents(); + } + } else if (option == 4) { + eventManager.shutdown(); + } + } + + s.close(); + } + +} diff --git a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/Event.java b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/Event.java new file mode 100644 index 000000000..5dc069bc7 --- /dev/null +++ b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/Event.java @@ -0,0 +1,106 @@ +/** + * The MIT License Copyright (c) 2014-2016 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.event.asynchronous; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * Each Event runs as a separate/individual thread. + * + */ +public class Event implements IEvent, Runnable { + + private static final Logger LOGGER = LoggerFactory.getLogger(Event.class); + + private int eventId; + private int eventTime; + private boolean isSynchronous; + private Thread thread; + private boolean isComplete = false; + private ThreadCompleteListener eventListener; + + /** + * + * @param eventId event ID + * @param eventTime event time + * @param isSynchronous is of synchronous type + */ + public Event(final int eventId, final int eventTime, final boolean isSynchronous) { + this.eventId = eventId; + this.eventTime = eventTime; + this.isSynchronous = isSynchronous; + } + + public boolean isSynchronous() { + return isSynchronous; + } + + @Override + public void start() { + thread = new Thread(this); + thread.start(); + } + + @Override + public void stop() { + if (null == thread) { + return; + } + thread.interrupt(); + } + + @Override + public void status() { + if (!isComplete) { + LOGGER.info("[{}] is not done.", eventId); + } else { + LOGGER.info("[{}] is done.", eventId); + } + } + + @Override + public void run() { + long currentTime = System.currentTimeMillis(); + long endTime = currentTime + (eventTime * 1000); + while (System.currentTimeMillis() < endTime) { + try { + Thread.sleep(1000); // Sleep for 1 second. + } catch (InterruptedException e) { + return; + } + } + isComplete = true; + completed(); + } + + public final void addListener(final ThreadCompleteListener listener) { + this.eventListener = listener; + } + + public final void removeListener(final ThreadCompleteListener listener) { + this.eventListener = null; + } + + private final void completed() { + if (eventListener != null) { + eventListener.completedEventHandler(eventId); + } + } + +} diff --git a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventDoesNotExistException.java b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventDoesNotExistException.java new file mode 100644 index 000000000..e7a5e75b6 --- /dev/null +++ b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventDoesNotExistException.java @@ -0,0 +1,29 @@ +/** + * The MIT License Copyright (c) 2014-2016 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.event.asynchronous; + +/** + * Custom Exception Class for Non Existent Event + */ +public class EventDoesNotExistException extends Exception { + + private static final long serialVersionUID = -3398463738273811509L; + + public EventDoesNotExistException(String message) { + super(message); + } +} diff --git a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventManager.java b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventManager.java new file mode 100644 index 000000000..729900620 --- /dev/null +++ b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventManager.java @@ -0,0 +1,218 @@ +/** + * The MIT License Copyright (c) 2014-2016 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.event.asynchronous; + +import java.util.Iterator; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.ConcurrentHashMap; + +/** + * + * EventManager handles and maintains a pool of event threads. {@link Event} threads are created upon user request. Thre + * are two types of events; Asynchronous and Synchronous. There can be multiple Asynchronous events running at once but + * only one Synchronous event running at a time. Currently supported event operations are: start, stop, and getStatus. + * Once an event is complete, it then notifies EventManager through a listener. The EventManager then takes the event + * out of the pool. + * + */ +public class EventManager implements ThreadCompleteListener { + + public static final int MAX_RUNNING_EVENTS = 1000; // Just don't wanna have too many running events. :) + public static final int MIN_ID = 1; + public static final int MAX_ID = MAX_RUNNING_EVENTS; + public static final int MAX_EVENT_TIME = 1800; // in seconds / 30 minutes. + private int currentlyRunningSyncEvent = -1; + private Random rand; + private Map eventPool; + + /** + * EventManager constructor. + * + */ + public EventManager() { + rand = new Random(1); + eventPool = new ConcurrentHashMap(MAX_RUNNING_EVENTS); + + } + + /** + * Create a Synchronous event. + * + * @param eventTime Time an event should run for. + * @return eventId + * @throws MaxNumOfEventsAllowedException When too many events are running at a time. + * @throws InvalidOperationException No new synchronous events can be created when one is already running. + * @throws LongRunningEventException Long running events are not allowed in the app. + */ + public int create(int eventTime) + throws MaxNumOfEventsAllowedException, InvalidOperationException, LongRunningEventException { + if (currentlyRunningSyncEvent != -1) { + throw new InvalidOperationException( + "Event [" + currentlyRunningSyncEvent + "] is still running. Please wait until it finishes and try again."); + } + + int eventId = createEvent(eventTime, true); + currentlyRunningSyncEvent = eventId; + + return eventId; + } + + /** + * Create an Asynchronous event. + * + * @param eventTime Time an event should run for. + * @return eventId + * @throws MaxNumOfEventsAllowedException When too many events are running at a time. + * @throws LongRunningEventException Long running events are not allowed in the app. + */ + public int createAsync(int eventTime) throws MaxNumOfEventsAllowedException, LongRunningEventException { + return createEvent(eventTime, false); + } + + private int createEvent(int eventTime, boolean isSynchronous) + throws MaxNumOfEventsAllowedException, LongRunningEventException { + if (eventPool.size() == MAX_RUNNING_EVENTS) { + throw new MaxNumOfEventsAllowedException("Too many events are running at the moment. Please try again later."); + } + + if (eventTime >= MAX_EVENT_TIME) { + throw new LongRunningEventException( + "Maximum event time allowed is " + MAX_EVENT_TIME + " seconds. Please try again."); + } + + int newEventId = generateId(); + + Event newEvent = new Event(newEventId, eventTime, isSynchronous); + newEvent.addListener(this); + eventPool.put(newEventId, newEvent); + + return newEventId; + } + + /** + * Starts event. + * + * @param eventId The event that needs to be started. + * @throws EventDoesNotExistException If event does not exist in our eventPool. + */ + public void start(int eventId) throws EventDoesNotExistException { + if (!eventPool.containsKey(eventId)) { + throw new EventDoesNotExistException(eventId + " does not exist."); + } + + eventPool.get(eventId).start(); + } + + /** + * Stops event. + * + * @param eventId The event that needs to be stopped. + * @throws EventDoesNotExistException If event does not exist in our eventPool. + */ + public void cancel(int eventId) throws EventDoesNotExistException { + if (!eventPool.containsKey(eventId)) { + throw new EventDoesNotExistException(eventId + " does not exist."); + } + + if (eventId == currentlyRunningSyncEvent) { + currentlyRunningSyncEvent = -1; + } + + eventPool.get(eventId).stop(); + eventPool.remove(eventId); + } + + /** + * Get status of a running event. + * + * @param eventId The event to inquire status of. + * @throws EventDoesNotExistException If event does not exist in our eventPool. + */ + public void status(int eventId) throws EventDoesNotExistException { + if (!eventPool.containsKey(eventId)) { + throw new EventDoesNotExistException(eventId + " does not exist."); + } + + eventPool.get(eventId).status(); + } + + /** + * Gets status of all running events. + */ + @SuppressWarnings("rawtypes") + public void statusOfAllEvents() { + Iterator it = eventPool.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry pair = (Map.Entry) it.next(); + ((Event) pair.getValue()).status(); + } + } + + /** + * Stop all running events. + */ + @SuppressWarnings("rawtypes") + public void shutdown() { + Iterator it = eventPool.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry pair = (Map.Entry) it.next(); + ((Event) pair.getValue()).stop(); + } + } + + /** + * Returns a pseudo-random number between min and max, inclusive. The difference between min and max can be at most + * Integer.MAX_VALUE - 1. + */ + private int generateId() { + // nextInt is normally exclusive of the top value, + // so add 1 to make it inclusive + int randomNum = rand.nextInt((MAX_ID - MIN_ID) + 1) + MIN_ID; + while (eventPool.containsKey(randomNum)) { + randomNum = rand.nextInt((MAX_ID - MIN_ID) + 1) + MIN_ID; + } + + return randomNum; + } + + /** + * Callback from an {@link Event} (once it is complete). The Event is then removed from the pool. + */ + @Override + public void completedEventHandler(int eventId) { + eventPool.get(eventId).status(); + if (eventPool.get(eventId).isSynchronous()) { + currentlyRunningSyncEvent = -1; + } + eventPool.remove(eventId); + } + + /** + * Getter method for event pool. + */ + public Map getEventPool() { + return eventPool; + } + + /** + * Get number of currently running Synchronous events. + */ + public int numOfCurrentlyRunningSyncEvent() { + return currentlyRunningSyncEvent; + } +} diff --git a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/IEvent.java b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/IEvent.java new file mode 100644 index 000000000..bfb5d4f04 --- /dev/null +++ b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/IEvent.java @@ -0,0 +1,31 @@ +/** + * The MIT License Copyright (c) 2014-2016 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.event.asynchronous; + +/** + * Events that fulfill the start stop and list out current status behaviour + * follow this interface + */ +public interface IEvent { + + void start(); + + void stop(); + + void status(); + +} diff --git a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/InvalidOperationException.java b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/InvalidOperationException.java new file mode 100644 index 000000000..7ca5cfc9a --- /dev/null +++ b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/InvalidOperationException.java @@ -0,0 +1,30 @@ +/** + * The MIT License Copyright (c) 2014-2016 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.event.asynchronous; + +/** + * Type of Exception raised when the Operation being invoked is Invalid + */ +public class InvalidOperationException extends Exception { + + private static final long serialVersionUID = -6191545255213410803L; + + public InvalidOperationException(String message) { + super(message); + } + +} diff --git a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/LongRunningEventException.java b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/LongRunningEventException.java new file mode 100644 index 000000000..b11f1a335 --- /dev/null +++ b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/LongRunningEventException.java @@ -0,0 +1,29 @@ +/** + * The MIT License Copyright (c) 2014-2016 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.event.asynchronous; + +/** + * Type of Exception raised when the Operation being invoked is Long Running + */ +public class LongRunningEventException extends Exception { + + private static final long serialVersionUID = -483423544320148809L; + + public LongRunningEventException(String message) { + super(message); + } +} diff --git a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/MaxNumOfEventsAllowedException.java b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/MaxNumOfEventsAllowedException.java new file mode 100644 index 000000000..17b46fd88 --- /dev/null +++ b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/MaxNumOfEventsAllowedException.java @@ -0,0 +1,29 @@ +/** + * The MIT License Copyright (c) 2014-2016 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.event.asynchronous; + +/** + * Type of Exception raised when the max number of allowed events is exceeded + */ +public class MaxNumOfEventsAllowedException extends Exception { + + private static final long serialVersionUID = -8430876973516292695L; + + public MaxNumOfEventsAllowedException(String message) { + super(message); + } +} diff --git a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/ThreadCompleteListener.java b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/ThreadCompleteListener.java new file mode 100644 index 000000000..7a37f7614 --- /dev/null +++ b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/ThreadCompleteListener.java @@ -0,0 +1,24 @@ +/** + * The MIT License Copyright (c) 2014-2016 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.event.asynchronous; + +/** + * Interface with listener behaviour related to Thread Completion. + */ +public interface ThreadCompleteListener { + void completedEventHandler(final int eventId); +} diff --git a/event-asynchronous/src/main/resources/config.properties b/event-asynchronous/src/main/resources/config.properties new file mode 100644 index 000000000..f76c07bb4 --- /dev/null +++ b/event-asynchronous/src/main/resources/config.properties @@ -0,0 +1,24 @@ +# +# The MIT License +# Copyright (c) 2014-2016 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. +# + +INTERACTIVE_MODE=NO \ No newline at end of file diff --git a/event-asynchronous/src/test/java/com/iluwatar/event/asynchronous/AppTest.java b/event-asynchronous/src/test/java/com/iluwatar/event/asynchronous/AppTest.java new file mode 100644 index 000000000..372bc2f58 --- /dev/null +++ b/event-asynchronous/src/test/java/com/iluwatar/event/asynchronous/AppTest.java @@ -0,0 +1,32 @@ +/** + * The MIT License Copyright (c) 2014-2016 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.event.asynchronous; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +/** + * Tests that EventAsynchronous example runs without errors. + */ +public class AppTest { + @Test + public void test() throws IOException { + String[] args = {}; + App.main(args); + } +} diff --git a/event-asynchronous/src/test/java/com/iluwatar/event/asynchronous/EventAsynchronousTest.java b/event-asynchronous/src/test/java/com/iluwatar/event/asynchronous/EventAsynchronousTest.java new file mode 100644 index 000000000..aa2c644cb --- /dev/null +++ b/event-asynchronous/src/test/java/com/iluwatar/event/asynchronous/EventAsynchronousTest.java @@ -0,0 +1,142 @@ +/** + * The MIT License Copyright (c) 2014-2016 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.event.asynchronous; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * + * Application test + * + */ +public class EventAsynchronousTest { + App app; + + private static final Logger LOGGER = LoggerFactory.getLogger(EventAsynchronousTest.class); + + @BeforeEach + public void setUp() { + app = new App(); + } + + @Test + public void testAsynchronousEvent() { + EventManager eventManager = new EventManager(); + try { + int aEventId = eventManager.createAsync(60); + eventManager.start(aEventId); + assertTrue(eventManager.getEventPool().size() == 1); + assertTrue(eventManager.getEventPool().size() < EventManager.MAX_RUNNING_EVENTS); + assertTrue(eventManager.numOfCurrentlyRunningSyncEvent() == -1); + eventManager.cancel(aEventId); + assertTrue(eventManager.getEventPool().size() == 0); + } catch (MaxNumOfEventsAllowedException | LongRunningEventException | EventDoesNotExistException e) { + LOGGER.error(e.getMessage()); + } + } + + @Test + public void testSynchronousEvent() { + EventManager eventManager = new EventManager(); + try { + int sEventId = eventManager.create(60); + eventManager.start(sEventId); + assertTrue(eventManager.getEventPool().size() == 1); + assertTrue(eventManager.getEventPool().size() < EventManager.MAX_RUNNING_EVENTS); + assertTrue(eventManager.numOfCurrentlyRunningSyncEvent() != -1); + eventManager.cancel(sEventId); + assertTrue(eventManager.getEventPool().size() == 0); + } catch (MaxNumOfEventsAllowedException | LongRunningEventException | EventDoesNotExistException + | InvalidOperationException e) { + LOGGER.error(e.getMessage()); + } + } + + @Test + public void testUnsuccessfulSynchronousEvent() throws InvalidOperationException { + assertThrows(InvalidOperationException.class, () -> { + EventManager eventManager = new EventManager(); + try { + int sEventId = eventManager.create(60); + eventManager.start(sEventId); + sEventId = eventManager.create(60); + eventManager.start(sEventId); + } catch (MaxNumOfEventsAllowedException | LongRunningEventException | EventDoesNotExistException e) { + LOGGER.error(e.getMessage()); + } + }); + } + + @Test + public void testFullSynchronousEvent() { + EventManager eventManager = new EventManager(); + try { + int eventTime = 1; + + int sEventId = eventManager.create(eventTime); + assertTrue(eventManager.getEventPool().size() == 1); + eventManager.start(sEventId); + + long currentTime = System.currentTimeMillis(); + long endTime = currentTime + (eventTime + 2 * 1000); // +2 to give a bit of buffer time for event to + // complete + // properly. + while (System.currentTimeMillis() < endTime) { + } + + assertTrue(eventManager.getEventPool().size() == 0); + + } catch (MaxNumOfEventsAllowedException | LongRunningEventException | EventDoesNotExistException + | InvalidOperationException e) { + LOGGER.error(e.getMessage()); + } + } + + @Test + public void testFullAsynchronousEvent() { + EventManager eventManager = new EventManager(); + try { + int eventTime = 1; + + int aEventId1 = eventManager.createAsync(eventTime); + int aEventId2 = eventManager.createAsync(eventTime); + int aEventId3 = eventManager.createAsync(eventTime); + assertTrue(eventManager.getEventPool().size() == 3); + + eventManager.start(aEventId1); + eventManager.start(aEventId2); + eventManager.start(aEventId3); + + long currentTime = System.currentTimeMillis(); + long endTime = currentTime + (eventTime + 2 * 1000); // +2 to give a bit of buffer time for event to complete + // properly. + while (System.currentTimeMillis() < endTime) { + } + + assertTrue(eventManager.getEventPool().size() == 0); + + } catch (MaxNumOfEventsAllowedException | LongRunningEventException | EventDoesNotExistException e) { + LOGGER.error(e.getMessage()); + } + } +} diff --git a/event-driven-architecture/README.md b/event-driven-architecture/README.md index 843e4c268..e2f06cf5a 100644 --- a/event-driven-architecture/README.md +++ b/event-driven-architecture/README.md @@ -31,7 +31,7 @@ Use an Event-driven architecture when ## Credits -* [Event-driven architecture - Wikipedia](http://www.computerweekly.com/feature/Write-through-write-around-write-back-Cache-explained) +* [Event-driven architecture - Wikipedia](https://en.wikipedia.org/wiki/Event-driven_architecture) * [Fundamental Components of an Event-Driven Architecture](http://giocc.com/fundamental-components-of-an-event-driven-architecture.html) * [Real World Applications/Event Driven Applications](https://wiki.haskell.org/Real_World_Applications/Event_Driven_Applications) * [Event-driven architecture definition](http://searchsoa.techtarget.com/definition/event-driven-architecture) diff --git a/event-driven-architecture/pom.xml b/event-driven-architecture/pom.xml index 99ed39891..4eb2a6946 100644 --- a/event-driven-architecture/pom.xml +++ b/event-driven-architecture/pom.xml @@ -2,7 +2,7 @@ + + 4.0.0 + + java-design-patterns + com.iluwatar + 1.19.0-SNAPSHOT + + event-queue + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + \ No newline at end of file diff --git a/event-queue/src/main/java/com/iluwatar/event/queue/App.java b/event-queue/src/main/java/com/iluwatar/event/queue/App.java new file mode 100644 index 000000000..ea107d6ca --- /dev/null +++ b/event-queue/src/main/java/com/iluwatar/event/queue/App.java @@ -0,0 +1,59 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 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.event.queue; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +import javax.sound.sampled.UnsupportedAudioFileException; + +/** + * Event or message queues provide an asynchronous communications protocol, meaning that the sender + * and receiver of the message do not need to interact with the message queue at the same time. + * Events or messages placed onto the queue are stored until the recipient retrieves them. Event + * or message queues have implicit or explicit limits on the size of data that may be transmitted + * in a single message and the number of messages that may remain outstanding on the queue. + * A queue stores a series of notifications or requests in first-in, first-out order. + * Sending a notification enqueues the request and returns. The request processor then processes + * items from the queue at a later time. + */ +public class App { + /** + * Program entry point. + * + * @param args command line args + * @throws IOException when there is a problem with the audio file loading + * @throws UnsupportedAudioFileException when the loaded audio file is unsupported + */ + public static void main(String[] args) throws UnsupportedAudioFileException, IOException { + Audio.playSound(Audio.getAudioStream("./etc/Bass-Drum-1.wav"), -10.0f); + Audio.playSound(Audio.getAudioStream("./etc/Closed-Hi-Hat-1.wav"), -8.0f); + + System.out.println("Press Enter key to stop the program..."); + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + br.read(); + Audio.stopService(); + } +} diff --git a/event-queue/src/main/java/com/iluwatar/event/queue/Audio.java b/event-queue/src/main/java/com/iluwatar/event/queue/Audio.java new file mode 100644 index 000000000..6534b563e --- /dev/null +++ b/event-queue/src/main/java/com/iluwatar/event/queue/Audio.java @@ -0,0 +1,169 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 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.event.queue; + +import java.io.File; +import java.io.IOException; + +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.Clip; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.UnsupportedAudioFileException; + +/** + * This class implements the Event Queue pattern. + * @author mkuprivecz + * + */ +public class Audio { + + private static final int MAX_PENDING = 16; + + private static int headIndex; + + private static int tailIndex; + + private static Thread updateThread = null; + + private static PlayMessage[] pendingAudio = new PlayMessage[MAX_PENDING]; + + /** + * This method stops the Update Method's thread. + */ + public static synchronized void stopService() { + if (updateThread != null) { + updateThread.interrupt(); + } + } + + /** + * This method check the Update Method's thread is started. + * @return boolean + */ + public static synchronized boolean isServiceRunning() { + if (updateThread != null && updateThread.isAlive() ) { + return true; + } else { + return false; + } + } + + /** + * Starts the thread for the Update Method pattern if it was not started previously. + * Also when the thread is is ready initializes the indexes of the queue + */ + public static void init() { + if (updateThread == null) { + updateThread = new Thread(new Runnable() { + public void run() { + while (!Thread.currentThread().isInterrupted()) { + Audio.update(); + } + } + }); + } + startThread(); + } + + /** + * This is a synchronized thread starter + */ + public static synchronized void startThread() { + if (!updateThread.isAlive()) { + updateThread.start(); + headIndex = 0; + tailIndex = 0; + } + } + + /** + * This method adds a new audio into the queue. + * @param stream is the AudioInputStream for the method + * @param volume is the level of the audio's volume + */ + public static void playSound(AudioInputStream stream, float volume) { + init(); + // Walk the pending requests. + for (int i = headIndex; i != tailIndex; i = (i + 1) % MAX_PENDING) { + if (getPendingAudio()[i].getStream() == stream) { + // Use the larger of the two volumes. + getPendingAudio()[i].setVolume(Math.max(volume, getPendingAudio()[i].getVolume())); + + // Don't need to enqueue. + return; + } + } + getPendingAudio()[tailIndex] = new PlayMessage(stream, volume); + tailIndex = (tailIndex + 1) % MAX_PENDING; + } + + /** + * This method uses the Update Method pattern. + * It takes the audio from the queue and plays it + */ + public static void update() { + // If there are no pending requests, do nothing. + if (headIndex == tailIndex) { + return; + } + Clip clip = null; + try { + AudioInputStream audioStream = getPendingAudio()[headIndex].getStream(); + headIndex++; + clip = AudioSystem.getClip(); + clip.open(audioStream); + clip.start(); + } catch (LineUnavailableException e) { + System.err.println("Error occoured while loading the audio: The line is unavailable"); + e.printStackTrace(); + } catch (IOException e) { + System.err.println("Input/Output error while loading the audio"); + e.printStackTrace(); + } catch (IllegalArgumentException e) { + System.err.println("The system doesn't support the sound: " + e.getMessage()); + } + } + + /** + * Returns the AudioInputStream of a file + * @param filePath is the path of the audio file + * @return AudioInputStream + * @throws UnsupportedAudioFileException when the audio file is not supported + * @throws IOException when the file is not readable + */ + public static AudioInputStream getAudioStream(String filePath) + throws UnsupportedAudioFileException, IOException { + return AudioSystem.getAudioInputStream(new File(filePath).getAbsoluteFile()); + } + + /** + * Returns with the message array of the queue + * @return PlayMessage[] + */ + public static PlayMessage[] getPendingAudio() { + return pendingAudio; + } + +} diff --git a/event-queue/src/main/java/com/iluwatar/event/queue/PlayMessage.java b/event-queue/src/main/java/com/iluwatar/event/queue/PlayMessage.java new file mode 100644 index 000000000..5ced2e3b3 --- /dev/null +++ b/event-queue/src/main/java/com/iluwatar/event/queue/PlayMessage.java @@ -0,0 +1,59 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 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.event.queue; + +import javax.sound.sampled.AudioInputStream; + +/** + * The Event Queue's queue will store the instances of this class. + * @author mkuprivecz + * + */ +public class PlayMessage { + + private AudioInputStream stream; + + private float volume; + + public PlayMessage(AudioInputStream stream, float volume) { + setStream(stream); + setVolume(volume); + } + + public AudioInputStream getStream() { + return stream; + } + + private void setStream(AudioInputStream stream) { + this.stream = stream; + } + + public float getVolume() { + return volume; + } + + public void setVolume(float volume) { + this.volume = volume; + } +} diff --git a/event-queue/src/test/java/com/iluwatar/event/queue/AudioTest.java b/event-queue/src/test/java/com/iluwatar/event/queue/AudioTest.java new file mode 100644 index 000000000..47f332526 --- /dev/null +++ b/event-queue/src/test/java/com/iluwatar/event/queue/AudioTest.java @@ -0,0 +1,79 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 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.event.queue; + +import org.junit.jupiter.api.Test; + +import javax.sound.sampled.UnsupportedAudioFileException; +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +/** + * Testing the Audio service of the Queue + * @author mkuprivecz + * + */ +public class AudioTest { + + /** + * Test here that the playSound method works correctly + * @throws UnsupportedAudioFileException when the audio file is not supported + * @throws IOException when the file is not readable + * @throws InterruptedException when the test is interrupted externally + */ + @Test + public void testPlaySound() throws UnsupportedAudioFileException, IOException, InterruptedException { + Audio.playSound(Audio.getAudioStream("./etc/Bass-Drum-1.wav"), -10.0f); + // test that service is started + assertTrue(Audio.isServiceRunning()); + // adding a small pause to be sure that the sound is ended + Thread.sleep(5000); + // test that service is finished + assertFalse(!Audio.isServiceRunning()); + } + + /** + * Test here that the Queue + * @throws UnsupportedAudioFileException when the audio file is not supported + * @throws IOException when the file is not readable + * @throws InterruptedException when the test is interrupted externally + */ + @Test + public void testQueue() throws UnsupportedAudioFileException, IOException, InterruptedException { + Audio.playSound(Audio.getAudioStream("./etc/Bass-Drum-1.aif"), -10.0f); + Audio.playSound(Audio.getAudioStream("./etc/Bass-Drum-1.aif"), -10.0f); + Audio.playSound(Audio.getAudioStream("./etc/Bass-Drum-1.aif"), -10.0f); + assertTrue(Audio.getPendingAudio().length > 0); + // test that service is started + assertTrue(Audio.isServiceRunning()); + // adding a small pause to be sure that the sound is ended + Thread.sleep(10000); + // test that service is finished + assertFalse(!Audio.isServiceRunning()); + } + +} diff --git a/event-sourcing/README.md b/event-sourcing/README.md new file mode 100644 index 000000000..1079405fa --- /dev/null +++ b/event-sourcing/README.md @@ -0,0 +1,33 @@ +--- +layout: pattern +title: Event Sourcing +folder: event-sourcing +permalink: /patterns/event-sourcing/ +categories: Architectural +tags: + - Java + - Difficulty Intermediate + - Performance +--- + +## Intent +Instead of storing just the current state of the data in a domain, use an append-only store to record the full series of actions taken on that data. The store acts as the system of record and can be used to materialize the domain objects. This can simplify tasks in complex domains, by avoiding the need to synchronize the data model and the business domain, while improving performance, scalability, and responsiveness. It can also provide consistency for transactional data, and maintain full audit trails and history that can enable compensating actions. + +![alt text](./etc/event-sourcing.png "Event Sourcing") + +## Applicability +Use the Event Sourcing pattern when + +* You need very high performance on persisting your application state even your application state have a complex relational data structure +* You need log of changes of your application state and ability to restore a state of any moment in time. +* You need to debug production problems by replaying the past events. + +## Real world examples + +* [The Lmax Architecture] (https://martinfowler.com/articles/lmax.html) + +## Credits + +* [Martin Fowler - Event Sourcing] (https://martinfowler.com/eaaDev/EventSourcing.html) +* [Event Sourcing | Microsoft Docs] (https://docs.microsoft.com/en-us/azure/architecture/patterns/event-sourcing) +* [Reference 3: Introducing Event Sourcing] (https://msdn.microsoft.com/en-us/library/jj591559.aspx) diff --git a/event-sourcing/etc/event-sourcing.png b/event-sourcing/etc/event-sourcing.png new file mode 100644 index 000000000..0b0098546 Binary files /dev/null and b/event-sourcing/etc/event-sourcing.png differ diff --git a/factory-method/etc/factory-method.ucls b/event-sourcing/etc/event-sourcing.ucls similarity index 51% rename from factory-method/etc/factory-method.ucls rename to event-sourcing/etc/event-sourcing.ucls index 562437995..2df56a479 100644 --- a/factory-method/etc/factory-method.ucls +++ b/event-sourcing/etc/event-sourcing.ucls @@ -1,111 +1,117 @@ - - + - + - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - + +
- - + + - + - - - - + + + + - - - - - - + + - + + + + 4.0.0 + + java-design-patterns + com.iluwatar + 1.19.0-SNAPSHOT + + event-sourcing + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + + com.google.code.gson + gson + 2.8.1 + + + + \ No newline at end of file diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/app/App.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/app/App.java new file mode 100644 index 000000000..50e9b503e --- /dev/null +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/app/App.java @@ -0,0 +1,115 @@ +/** + * The MIT License + * Copyright (c) 2014 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.event.sourcing.app; + +import com.iluwatar.event.sourcing.event.AccountCreateEvent; +import com.iluwatar.event.sourcing.event.MoneyDepositEvent; +import com.iluwatar.event.sourcing.event.MoneyTransferEvent; +import com.iluwatar.event.sourcing.processor.DomainEventProcessor; +import com.iluwatar.event.sourcing.state.AccountAggregate; +import java.math.BigDecimal; +import java.util.Date; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Event Sourcing : Instead of storing just the current state of the data in a domain, use an + * append-only store to record the full series of actions taken on that data. The store acts as the + * system of record and can be used to materialize the domain objects. This can simplify tasks in + * complex domains, by avoiding the need to synchronize the data model and the business domain, + * while improving performance, scalability, and responsiveness. It can also provide consistency for + * transactional data, and maintain full audit trails and history that can enable compensating + * actions. + * + * This App class is an example usage of Event Sourcing pattern. As an example, two bank account is + * created, then some money deposit and transfer actions are taken so a new state of accounts is + * created. At that point, state is cleared in order to represent a system shot down. After the shot + * down, system state is recovered by re-creating the past events from event journal. Then state is + * printed so a user can view the last state is same with the state before system shot down. + * + * Created by Serdar Hamzaogullari on 06.08.2017. + */ +public class App { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + /** + * The constant ACCOUNT OF DAENERYS. + */ + public static final int ACCOUNT_OF_DAENERYS = 1; + /** + * The constant ACCOUNT OF JON. + */ + public static final int ACCOUNT_OF_JON = 2; + + /** + * The entry point of application. + * + * @param args the input arguments + */ + public static void main(String[] args) { + + DomainEventProcessor eventProcessor = new DomainEventProcessor(); + + + LOGGER.info("Running the system first time............"); + eventProcessor.reset(); + + LOGGER.info("Creating th accounts............"); + + eventProcessor.process(new AccountCreateEvent( + 0, new Date().getTime(), ACCOUNT_OF_DAENERYS, "Daenerys Targaryen")); + + eventProcessor.process(new AccountCreateEvent( + 1, new Date().getTime(), ACCOUNT_OF_JON, "Jon Snow")); + + LOGGER.info("Do some money operations............"); + + eventProcessor.process(new MoneyDepositEvent( + 2, new Date().getTime(), ACCOUNT_OF_DAENERYS, new BigDecimal("100000"))); + + eventProcessor.process(new MoneyDepositEvent( + 3, new Date().getTime(), ACCOUNT_OF_JON, new BigDecimal("100"))); + + eventProcessor.process(new MoneyTransferEvent( + 4, new Date().getTime(), new BigDecimal("10000"), ACCOUNT_OF_DAENERYS, + ACCOUNT_OF_JON)); + + LOGGER.info("...............State:............"); + LOGGER.info(AccountAggregate.getAccount(ACCOUNT_OF_DAENERYS).toString()); + LOGGER.info(AccountAggregate.getAccount(ACCOUNT_OF_JON).toString()); + + LOGGER.info("At that point system had a shot down, state in memory is cleared............"); + AccountAggregate.resetState(); + + LOGGER.info("Recover the system by the events in journal file............"); + + eventProcessor = new DomainEventProcessor(); + eventProcessor.recover(); + + LOGGER.info("...............Recovered State:............"); + LOGGER.info(AccountAggregate.getAccount(ACCOUNT_OF_DAENERYS).toString()); + LOGGER.info(AccountAggregate.getAccount(ACCOUNT_OF_JON).toString()); + } + + +} diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/domain/Account.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/domain/Account.java new file mode 100644 index 000000000..d2e5f429b --- /dev/null +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/domain/Account.java @@ -0,0 +1,186 @@ +/** + * The MIT License + * Copyright (c) 2014 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.event.sourcing.domain; + +import com.iluwatar.event.sourcing.event.AccountCreateEvent; +import com.iluwatar.event.sourcing.event.MoneyDepositEvent; +import com.iluwatar.event.sourcing.event.MoneyTransferEvent; +import com.iluwatar.event.sourcing.state.AccountAggregate; +import java.math.BigDecimal; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This is the Account class that holds the account info, the account number, + * account owner name and money of the account. Account class also have the business logic of events + * that effects this account. + * + * Created by Serdar Hamzaogullari on 06.08.2017. + */ +public class Account { + + private static final Logger LOGGER = LoggerFactory.getLogger(Account.class); + + private final int accountNo; + private final String owner; + private BigDecimal money; + + /** + * Instantiates a new Account. + * + * @param accountNo the account no + * @param owner the owner + */ + public Account(int accountNo, String owner) { + this.accountNo = accountNo; + this.owner = owner; + money = BigDecimal.ZERO; + } + + /** + * Gets account no. + * + * @return the account no + */ + public int getAccountNo() { + return accountNo; + } + + /** + * Gets owner. + * + * @return the owner + */ + public String getOwner() { + return owner; + } + + /** + * Gets money. + * + * @return the money + */ + public BigDecimal getMoney() { + return money; + } + + /** + * Sets money. + * + * @param money the money + */ + public void setMoney(BigDecimal money) { + this.money = money; + } + + + /** + * Copy account. + * + * @return the account + */ + public Account copy() { + Account account = new Account(accountNo, owner); + account.setMoney(money); + return account; + } + + @Override + public String toString() { + return "Account{" + + "accountNo=" + accountNo + + ", owner='" + owner + '\'' + + ", money=" + money + + '}'; + } + + private void depositMoney(BigDecimal money) { + this.money = this.money.add(money); + } + + private void withdrawMoney(BigDecimal money) { + this.money = this.money.subtract(money); + } + + private void handleDeposit(BigDecimal money, boolean realTime) { + depositMoney(money); + AccountAggregate.putAccount(this); + if (realTime) { + LOGGER.info("Some external api for only realtime execution could be called here."); + } + } + + private void handleWithdrawal(BigDecimal money, boolean realTime) { + if (this.money.compareTo(money) == -1) { + throw new RuntimeException("Insufficient Account Balance"); + } + + withdrawMoney(money); + AccountAggregate.putAccount(this); + if (realTime) { + LOGGER.info("Some external api for only realtime execution could be called here."); + } + } + + /** + * Handles the MoneyDepositEvent. + * + * @param moneyDepositEvent the money deposit event + */ + public void handleEvent(MoneyDepositEvent moneyDepositEvent) { + handleDeposit(moneyDepositEvent.getMoney(), moneyDepositEvent.isRealTime()); + } + + + /** + * Handles the AccountCreateEvent. + * + * @param accountCreateEvent the account create event + */ + public void handleEvent(AccountCreateEvent accountCreateEvent) { + AccountAggregate.putAccount(this); + if (accountCreateEvent.isRealTime()) { + LOGGER.info("Some external api for only realtime execution could be called here."); + } + } + + /** + * Handles transfer from account event. + * + * @param moneyTransferEvent the money transfer event + */ + public void handleTransferFromEvent(MoneyTransferEvent moneyTransferEvent) { + handleWithdrawal(moneyTransferEvent.getMoney(), moneyTransferEvent.isRealTime()); + } + + /** + * Handles transfer to account event. + * + * @param moneyTransferEvent the money transfer event + */ + public void handleTransferToEvent(MoneyTransferEvent moneyTransferEvent) { + handleDeposit(moneyTransferEvent.getMoney(), moneyTransferEvent.isRealTime()); + } + + +} diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/AccountCreateEvent.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/AccountCreateEvent.java new file mode 100644 index 000000000..c526396f3 --- /dev/null +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/AccountCreateEvent.java @@ -0,0 +1,82 @@ +/** + * The MIT License + * Copyright (c) 2014 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.event.sourcing.event; + +import com.iluwatar.event.sourcing.domain.Account; +import com.iluwatar.event.sourcing.state.AccountAggregate; + +/** + * This is the class that implements account create event. + * Holds the necessary info for an account create event. + * Implements the process function that finds the event related + * domain objects and calls the related domain object's handle event functions + * + * Created by Serdar Hamzaogullari on 06.08.2017. + */ +public class AccountCreateEvent extends DomainEvent { + + private final int accountNo; + private final String owner; + + /** + * Instantiates a new Account create event. + * + * @param sequenceId the sequence id + * @param createdTime the created time + * @param accountNo the account no + * @param owner the owner + */ + public AccountCreateEvent(long sequenceId, long createdTime, int accountNo, String owner) { + super(sequenceId, createdTime, "AccountCreateEvent"); + this.accountNo = accountNo; + this.owner = owner; + } + + /** + * Gets account no. + * + * @return the account no + */ + public int getAccountNo() { + return accountNo; + } + + /** + * Gets owner. + * + * @return the owner + */ + public String getOwner() { + return owner; + } + + @Override + public void process() { + Account account = AccountAggregate.getAccount(accountNo); + if (account != null) { + throw new RuntimeException("Account already exists"); + } + account = new Account(accountNo, owner); + account.handleEvent(this); + } +} diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/DomainEvent.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/DomainEvent.java new file mode 100644 index 000000000..fa6539a4f --- /dev/null +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/DomainEvent.java @@ -0,0 +1,101 @@ +/** + * The MIT License + * Copyright (c) 2014 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.event.sourcing.event; + +import java.io.Serializable; + +/** + * This is the base class for domain events. All events must extend this class. + * + * Created by Serdar Hamzaogullari on 06.08.2017. + */ +public abstract class DomainEvent implements Serializable { + + private final long sequenceId; + private final long createdTime; + private final String eventClassName; + private boolean realTime = true; + + /** + * Instantiates a new Domain event. + * + * @param sequenceId the sequence id + * @param createdTime the created time + * @param eventClassName the event class name + */ + public DomainEvent(long sequenceId, long createdTime, String eventClassName) { + this.sequenceId = sequenceId; + this.createdTime = createdTime; + this.eventClassName = eventClassName; + } + + /** + * Gets sequence id. + * + * @return the sequence id + */ + public long getSequenceId() { + return sequenceId; + } + + /** + * Gets created time. + * + * @return the created time + */ + public long getCreatedTime() { + return createdTime; + } + + /** + * Is real time boolean. + * + * @return the boolean + */ + public boolean isRealTime() { + return realTime; + } + + /** + * Sets real time. + * + * @param realTime the real time + */ + public void setRealTime(boolean realTime) { + this.realTime = realTime; + } + + /** + * Process. + */ + public abstract void process(); + + /** + * Gets event class name. + * + * @return the event class name + */ + public String getEventClassName() { + return eventClassName; + } +} diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/MoneyDepositEvent.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/MoneyDepositEvent.java new file mode 100644 index 000000000..1629263a7 --- /dev/null +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/MoneyDepositEvent.java @@ -0,0 +1,82 @@ +/** + * The MIT License + * Copyright (c) 2014 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.event.sourcing.event; + +import com.iluwatar.event.sourcing.domain.Account; +import com.iluwatar.event.sourcing.state.AccountAggregate; +import java.math.BigDecimal; + +/** + * This is the class that implements money deposit event. + * Holds the necessary info for a money deposit event. + * Implements the process function that finds the event related + * domain objects and calls the related domain object's handle event functions + * + * Created by Serdar Hamzaogullari on 06.08.2017. + */ +public class MoneyDepositEvent extends DomainEvent { + + private final BigDecimal money; + private final int accountNo; + + /** + * Instantiates a new Money deposit event. + * + * @param sequenceId the sequence id + * @param createdTime the created time + * @param accountNo the account no + * @param money the money + */ + public MoneyDepositEvent(long sequenceId, long createdTime, int accountNo, BigDecimal money) { + super(sequenceId, createdTime, "MoneyDepositEvent"); + this.money = money; + this.accountNo = accountNo; + } + + /** + * Gets money. + * + * @return the money + */ + public BigDecimal getMoney() { + return money; + } + + /** + * Gets account no. + * + * @return the account no + */ + public int getAccountNo() { + return accountNo; + } + + @Override + public void process() { + Account account = AccountAggregate.getAccount(accountNo); + if (account == null) { + throw new RuntimeException("Account not found"); + } + account.handleEvent(this); + } +} diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/MoneyTransferEvent.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/MoneyTransferEvent.java new file mode 100644 index 000000000..0307d3af7 --- /dev/null +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/MoneyTransferEvent.java @@ -0,0 +1,101 @@ +/** + * The MIT License + * Copyright (c) 2014 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.event.sourcing.event; + +import com.iluwatar.event.sourcing.domain.Account; +import com.iluwatar.event.sourcing.state.AccountAggregate; +import java.math.BigDecimal; + +/** + * This is the class that implements money transfer event. + * Holds the necessary info for a money transfer event. + * Implements the process function that finds the event related + * domain objects and calls the related domain object's handle event functions + * + * Created by Serdar Hamzaogullari on 06.08.2017. + */ +public class MoneyTransferEvent extends DomainEvent { + + private final BigDecimal money; + private final int accountNoFrom; + private final int accountNoTo; + + /** + * Instantiates a new Money transfer event. + * + * @param sequenceId the sequence id + * @param createdTime the created time + * @param money the money + * @param accountNoFrom the account no from + * @param accountNoTo the account no to + */ + public MoneyTransferEvent(long sequenceId, long createdTime, BigDecimal money, int accountNoFrom, + int accountNoTo) { + super(sequenceId, createdTime, "MoneyTransferEvent"); + this.money = money; + this.accountNoFrom = accountNoFrom; + this.accountNoTo = accountNoTo; + } + + /** + * Gets money. + * + * @return the money + */ + public BigDecimal getMoney() { + return money; + } + + /** + * Gets account no which the money comes from. + * + * @return the account no from + */ + public int getAccountNoFrom() { + return accountNoFrom; + } + + /** + * Gets account no which the money goes to. + * + * @return the account no to + */ + public int getAccountNoTo() { + return accountNoTo; + } + + @Override + public void process() { + Account accountFrom = AccountAggregate.getAccount(accountNoFrom); + if (accountFrom == null) { + throw new RuntimeException("Account not found " + accountNoFrom); + } + Account accountTo = AccountAggregate.getAccount(accountNoTo); + if (accountTo == null) { + throw new RuntimeException("Account not found" + accountTo); + } + + accountFrom.handleTransferFromEvent(this); + accountTo.handleTransferToEvent(this); + } +} diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/service/LotteryService.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/DomainEventProcessor.java similarity index 55% rename from hexagonal/src/main/java/com/iluwatar/hexagonal/service/LotteryService.java rename to event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/DomainEventProcessor.java index 0056e794b..05308c7c1 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/service/LotteryService.java +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/DomainEventProcessor.java @@ -20,29 +20,50 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.hexagonal.service; +package com.iluwatar.event.sourcing.processor; -import java.util.Optional; - -import com.iluwatar.hexagonal.domain.LotteryNumbers; -import com.iluwatar.hexagonal.domain.LotteryTicket; -import com.iluwatar.hexagonal.domain.LotteryTicketCheckResult; -import com.iluwatar.hexagonal.domain.LotteryTicketId; +import com.iluwatar.event.sourcing.event.DomainEvent; /** - * - * Interface for submitting and checking lottery tickets. + * This is the implementation of event processor. + * All events are processed by this class. + * This processor uses processorJournal to persist and recover events. * + * Created by Serdar Hamzaogullari on 06.08.2017. */ -public interface LotteryService { +public class DomainEventProcessor { + + private final JsonFileJournal processorJournal = new JsonFileJournal(); /** - * Submit lottery ticket to participate in the lottery + * Process. + * + * @param domainEvent the domain event */ - Optional submitTicket(LotteryTicket ticket); + public void process(DomainEvent domainEvent) { + domainEvent.process(); + processorJournal.write(domainEvent); + } /** - * Check if lottery ticket has won + * Reset. */ - LotteryTicketCheckResult checkTicketForPrize(LotteryTicketId id, LotteryNumbers winningNumbers); + public void reset() { + processorJournal.reset(); + } + + /** + * Recover. + */ + public void recover() { + DomainEvent domainEvent; + while (true) { + domainEvent = processorJournal.readNext(); + if (domainEvent == null) { + break; + } else { + domainEvent.process(); + } + } + } } diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/JsonFileJournal.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/JsonFileJournal.java new file mode 100644 index 000000000..870d8d00f --- /dev/null +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/JsonFileJournal.java @@ -0,0 +1,144 @@ +/** + * The MIT License + * Copyright (c) 2014 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.event.sourcing.processor; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.iluwatar.event.sourcing.event.AccountCreateEvent; +import com.iluwatar.event.sourcing.event.DomainEvent; +import com.iluwatar.event.sourcing.event.MoneyDepositEvent; +import com.iluwatar.event.sourcing.event.MoneyTransferEvent; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.List; + +/** + * This is the implementation of event journal. + * This implementation serialize/deserialize the events with JSON + * and writes/reads them on a Journal.json file at the working directory. + * + * Created by Serdar Hamzaogullari on 06.08.2017. + */ +public class JsonFileJournal { + + private final File aFile; + private final List events = new ArrayList<>(); + private int index = 0; + + /** + * Instantiates a new Json file journal. + */ + public JsonFileJournal() { + aFile = new File("Journal.json"); + if (aFile.exists()) { + try (BufferedReader input = new BufferedReader( + new InputStreamReader(new FileInputStream(aFile), "UTF-8"))) { + String line; + while ((line = input.readLine()) != null) { + events.add(line); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } else { + reset(); + } + } + + + /** + * Write. + * + * @param domainEvent the domain event + */ + public void write(DomainEvent domainEvent) { + Gson gson = new Gson(); + JsonElement jsonElement; + if (domainEvent instanceof AccountCreateEvent) { + jsonElement = gson.toJsonTree(domainEvent, AccountCreateEvent.class); + } else if (domainEvent instanceof MoneyDepositEvent) { + jsonElement = gson.toJsonTree(domainEvent, MoneyDepositEvent.class); + } else if (domainEvent instanceof MoneyTransferEvent) { + jsonElement = gson.toJsonTree(domainEvent, MoneyTransferEvent.class); + } else { + throw new RuntimeException("Journal Event not recegnized"); + } + + try (Writer output = new BufferedWriter( + new OutputStreamWriter(new FileOutputStream(aFile, true), "UTF-8"))) { + String eventString = jsonElement.toString(); + output.write(eventString + "\r\n"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + + /** + * Reset. + */ + public void reset() { + aFile.delete(); + } + + + /** + * Read next domain event. + * + * @return the domain event + */ + public DomainEvent readNext() { + if (index >= events.size()) { + return null; + } + String event = events.get(index); + index++; + + JsonParser parser = new JsonParser(); + JsonElement jsonElement = parser.parse(event); + String eventClassName = jsonElement.getAsJsonObject().get("eventClassName").getAsString(); + Gson gson = new Gson(); + DomainEvent domainEvent; + if (eventClassName.equals("AccountCreateEvent")) { + domainEvent = gson.fromJson(jsonElement, AccountCreateEvent.class); + } else if (eventClassName.equals("MoneyDepositEvent")) { + domainEvent = gson.fromJson(jsonElement, MoneyDepositEvent.class); + } else if (eventClassName.equals("MoneyTransferEvent")) { + domainEvent = gson.fromJson(jsonElement, MoneyTransferEvent.class); + } else { + throw new RuntimeException("Journal Event not recegnized"); + } + + domainEvent.setRealTime(false); + return domainEvent; + } +} diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/state/AccountAggregate.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/state/AccountAggregate.java new file mode 100644 index 000000000..2d957268e --- /dev/null +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/state/AccountAggregate.java @@ -0,0 +1,71 @@ +/** + * The MIT License + * Copyright (c) 2014 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.event.sourcing.state; + +import com.iluwatar.event.sourcing.domain.Account; +import java.util.HashMap; +import java.util.Map; + +/** + * This is the static accounts map holder class. + * This class holds the state of the accounts. + * + * Created by Serdar Hamzaogullari on 06.08.2017. + */ +public class AccountAggregate { + + private static Map accounts = new HashMap<>(); + + private AccountAggregate() { + } + + /** + * Put account. + * + * @param account the account + */ + public static void putAccount(Account account) { + accounts.put(account.getAccountNo(), account); + } + + /** + * Gets account. + * + * @param accountNo the account no + * @return the copy of the account or null if not found + */ + public static Account getAccount(int accountNo) { + Account account = accounts.get(accountNo); + if (account == null) { + return null; + } + return account.copy(); + } + + /** + * Reset state. + */ + public static void resetState() { + accounts = new HashMap<>(); + } +} diff --git a/event-sourcing/src/test/java/IntegrationTest.java b/event-sourcing/src/test/java/IntegrationTest.java new file mode 100644 index 000000000..aab1ea7ea --- /dev/null +++ b/event-sourcing/src/test/java/IntegrationTest.java @@ -0,0 +1,99 @@ +/** + * The MIT License + * Copyright (c) 2014 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. + */ + +import com.iluwatar.event.sourcing.domain.Account; +import com.iluwatar.event.sourcing.event.AccountCreateEvent; +import com.iluwatar.event.sourcing.event.MoneyDepositEvent; +import com.iluwatar.event.sourcing.event.MoneyTransferEvent; +import com.iluwatar.event.sourcing.processor.DomainEventProcessor; +import com.iluwatar.event.sourcing.state.AccountAggregate; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.Date; + +import static com.iluwatar.event.sourcing.app.App.ACCOUNT_OF_DAENERYS; +import static com.iluwatar.event.sourcing.app.App.ACCOUNT_OF_JON; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Intergartion Test for Event Sourcing state recovery + * + * Created by Serdar Hamzaogullari on 19.08.2017. + */ +public class IntegrationTest { + + /** + * The Domain event processor. + */ + private DomainEventProcessor eventProcessor; + + /** + * Initialize. + */ + @BeforeEach + public void initialize() { + eventProcessor = new DomainEventProcessor(); + } + + /** + * Test state recovery. + */ + @Test + public void testStateRecovery() { + eventProcessor.reset(); + + eventProcessor.process(new AccountCreateEvent( + 0, new Date().getTime(), ACCOUNT_OF_DAENERYS, "Daenerys Targaryen")); + + eventProcessor.process(new AccountCreateEvent( + 1, new Date().getTime(), ACCOUNT_OF_JON, "Jon Snow")); + + eventProcessor.process(new MoneyDepositEvent( + 2, new Date().getTime(), ACCOUNT_OF_DAENERYS, new BigDecimal("100000"))); + + eventProcessor.process(new MoneyDepositEvent( + 3, new Date().getTime(), ACCOUNT_OF_JON, new BigDecimal("100"))); + + eventProcessor.process(new MoneyTransferEvent( + 4, new Date().getTime(), new BigDecimal("10000"), ACCOUNT_OF_DAENERYS, + ACCOUNT_OF_JON)); + + Account accountOfDaenerysBeforeShotDown = AccountAggregate.getAccount(ACCOUNT_OF_DAENERYS); + Account accountOfJonBeforeShotDown = AccountAggregate.getAccount(ACCOUNT_OF_JON); + + AccountAggregate.resetState(); + + eventProcessor = new DomainEventProcessor(); + eventProcessor.recover(); + + Account accountOfDaenerysAfterShotDown = AccountAggregate.getAccount(ACCOUNT_OF_DAENERYS); + Account accountOfJonAfterShotDown = AccountAggregate.getAccount(ACCOUNT_OF_JON); + + assertEquals(accountOfDaenerysBeforeShotDown.getMoney(), + accountOfDaenerysAfterShotDown.getMoney()); + assertEquals(accountOfJonBeforeShotDown.getMoney(), accountOfJonAfterShotDown.getMoney()); + } + +} \ No newline at end of file diff --git a/exclude-pmd.properties b/exclude-pmd.properties index aeda4353d..288ee9b2d 100644 --- a/exclude-pmd.properties +++ b/exclude-pmd.properties @@ -1,6 +1,6 @@ # # The MIT License -# Copyright (c) 2014 Ilkka Seppälä +# Copyright (c) 2014-2016 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 diff --git a/execute-around/pom.xml b/execute-around/pom.xml index 60bb6d0ab..2e43c3e4b 100644 --- a/execute-around/pom.xml +++ b/execute-around/pom.xml @@ -2,7 +2,7 @@ + + + java-design-patterns + com.iluwatar + 1.19.0-SNAPSHOT + + 4.0.0 + + extension-objects + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + + diff --git a/extension-objects/src/main/java/App.java b/extension-objects/src/main/java/App.java new file mode 100644 index 000000000..0fcc548dc --- /dev/null +++ b/extension-objects/src/main/java/App.java @@ -0,0 +1,84 @@ +/** + * The MIT License + * Copyright (c) 2014 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. + */ +import abstractextensions.CommanderExtension; +import abstractextensions.SergeantExtension; +import abstractextensions.SoldierExtension; +import units.CommanderUnit; +import units.SergeantUnit; +import units.SoldierUnit; +import units.Unit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Anticipate that an object’s interface needs to be extended in the future. + * Additional interfaces are defined by extension objects. + */ +public class App { + + /** + * Program entry point + * + * @param args command line args + */ + public static void main(String[] args) { + + //Create 3 different units + Unit soldierUnit = new SoldierUnit("SoldierUnit1"); + Unit sergeantUnit = new SergeantUnit("SergeantUnit1"); + Unit commanderUnit = new CommanderUnit("CommanderUnit1"); + + //check for each unit to have an extension + checkExtensionsForUnit(soldierUnit); + checkExtensionsForUnit(sergeantUnit); + checkExtensionsForUnit(commanderUnit); + + } + + private static void checkExtensionsForUnit(Unit unit) { + final Logger logger = LoggerFactory.getLogger(App.class); + + SoldierExtension soldierExtension = (SoldierExtension) unit.getUnitExtension("SoldierExtension"); + SergeantExtension sergeantExtension = (SergeantExtension) unit.getUnitExtension("SergeantExtension"); + CommanderExtension commanderExtension = (CommanderExtension) unit.getUnitExtension("CommanderExtension"); + + //if unit have extension call the method + if (soldierExtension != null) { + soldierExtension.soldierReady(); + } else { + logger.info(unit.getName() + " without SoldierExtension"); + } + + if (sergeantExtension != null) { + sergeantExtension.sergeantReady(); + } else { + logger.info(unit.getName() + " without SergeantExtension"); + } + + if (commanderExtension != null) { + commanderExtension.commanderReady(); + } else { + logger.info(unit.getName() + " without CommanderExtension"); + } + } +} diff --git a/bridge/src/main/java/com/iluwatar/bridge/BlindingMagicWeaponImpl.java b/extension-objects/src/main/java/abstractextensions/CommanderExtension.java similarity index 87% rename from bridge/src/main/java/com/iluwatar/bridge/BlindingMagicWeaponImpl.java rename to extension-objects/src/main/java/abstractextensions/CommanderExtension.java index 83f31e709..66dd3a25e 100644 --- a/bridge/src/main/java/com/iluwatar/bridge/BlindingMagicWeaponImpl.java +++ b/extension-objects/src/main/java/abstractextensions/CommanderExtension.java @@ -20,15 +20,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.bridge; +package abstractextensions; /** - * - * BlindingMagicWeaponImpl - * + * Interface with their method */ -public abstract class BlindingMagicWeaponImpl extends MagicWeaponImpl { - - public abstract void blindImp(); +public interface CommanderExtension extends UnitExtension { + void commanderReady(); } diff --git a/bridge/src/main/java/com/iluwatar/bridge/SoulEatingMagicWeaponImpl.java b/extension-objects/src/main/java/abstractextensions/SergeantExtension.java similarity index 86% rename from bridge/src/main/java/com/iluwatar/bridge/SoulEatingMagicWeaponImpl.java rename to extension-objects/src/main/java/abstractextensions/SergeantExtension.java index ad8bec48f..8df30c63e 100644 --- a/bridge/src/main/java/com/iluwatar/bridge/SoulEatingMagicWeaponImpl.java +++ b/extension-objects/src/main/java/abstractextensions/SergeantExtension.java @@ -20,15 +20,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.bridge; +package abstractextensions; /** - * - * SoulEatingMagicWeaponImpl - * + * Interface with their method */ -public abstract class SoulEatingMagicWeaponImpl extends MagicWeaponImpl { - - public abstract void eatSoulImp(); +public interface SergeantExtension extends UnitExtension { + void sergeantReady(); } diff --git a/extension-objects/src/main/java/abstractextensions/SoldierExtension.java b/extension-objects/src/main/java/abstractextensions/SoldierExtension.java new file mode 100644 index 000000000..9296b6204 --- /dev/null +++ b/extension-objects/src/main/java/abstractextensions/SoldierExtension.java @@ -0,0 +1,30 @@ +/** + * The MIT License + * Copyright (c) 2014 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 abstractextensions; + +/** + * Interface with their method + */ +public interface SoldierExtension extends UnitExtension { + void soldierReady(); +} diff --git a/extension-objects/src/main/java/abstractextensions/UnitExtension.java b/extension-objects/src/main/java/abstractextensions/UnitExtension.java new file mode 100644 index 000000000..b3b3bf7fa --- /dev/null +++ b/extension-objects/src/main/java/abstractextensions/UnitExtension.java @@ -0,0 +1,29 @@ +/** + * The MIT License + * Copyright (c) 2014 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 abstractextensions; + +/** + * Other Extensions will extend this interface + */ +public interface UnitExtension { +} diff --git a/extension-objects/src/main/java/concreteextensions/Commander.java b/extension-objects/src/main/java/concreteextensions/Commander.java new file mode 100644 index 000000000..8094d2887 --- /dev/null +++ b/extension-objects/src/main/java/concreteextensions/Commander.java @@ -0,0 +1,47 @@ +/** + * The MIT License + * Copyright (c) 2014 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 concreteextensions; + +import abstractextensions.CommanderExtension; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import units.CommanderUnit; + +/** + * Class defining Commander + */ +public class Commander implements CommanderExtension { + + private CommanderUnit unit; + + public Commander(CommanderUnit commanderUnit) { + this.unit = commanderUnit; + } + + final Logger logger = LoggerFactory.getLogger(Commander.class); + + @Override + public void commanderReady() { + logger.info("[Commander] " + unit.getName() + " is ready!"); + } +} diff --git a/bridge/src/main/java/com/iluwatar/bridge/FlyingMagicWeapon.java b/extension-objects/src/main/java/concreteextensions/Sergeant.java similarity index 70% rename from bridge/src/main/java/com/iluwatar/bridge/FlyingMagicWeapon.java rename to extension-objects/src/main/java/concreteextensions/Sergeant.java index 0988c179c..892a3d372 100644 --- a/bridge/src/main/java/com/iluwatar/bridge/FlyingMagicWeapon.java +++ b/extension-objects/src/main/java/concreteextensions/Sergeant.java @@ -20,40 +20,28 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.bridge; +package concreteextensions; + +import abstractextensions.SergeantExtension; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import units.SergeantUnit; /** - * - * FlyingMagicWeapon - * + * Class defining Sergeant */ -public class FlyingMagicWeapon extends MagicWeapon { +public class Sergeant implements SergeantExtension { - public FlyingMagicWeapon(FlyingMagicWeaponImpl imp) { - super(imp); + private SergeantUnit unit; + + public Sergeant(SergeantUnit sergeantUnit) { + this.unit = sergeantUnit; } - public FlyingMagicWeaponImpl getImp() { - return (FlyingMagicWeaponImpl) imp; - } + final Logger logger = LoggerFactory.getLogger(Sergeant.class); @Override - public void wield() { - getImp().wieldImp(); + public void sergeantReady() { + logger.info("[Sergeant] " + unit.getName() + " is ready! "); } - - @Override - public void swing() { - getImp().swingImp(); - } - - @Override - public void unwield() { - getImp().unwieldImp(); - } - - public void fly() { - getImp().flyImp(); - } - } diff --git a/bridge/src/main/java/com/iluwatar/bridge/Stormbringer.java b/extension-objects/src/main/java/concreteextensions/Soldier.java similarity index 70% rename from bridge/src/main/java/com/iluwatar/bridge/Stormbringer.java rename to extension-objects/src/main/java/concreteextensions/Soldier.java index 91cad9cc2..79db74d6c 100644 --- a/bridge/src/main/java/com/iluwatar/bridge/Stormbringer.java +++ b/extension-objects/src/main/java/concreteextensions/Soldier.java @@ -20,32 +20,28 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.bridge; +package concreteextensions; + +import abstractextensions.SoldierExtension; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import units.SoldierUnit; /** - * - * Stormbringer - * + * Class defining Soldier */ -public class Stormbringer extends SoulEatingMagicWeaponImpl { +public class Soldier implements SoldierExtension { - @Override - public void wieldImp() { - System.out.println("wielding Stormbringer"); + private SoldierUnit unit; + + public Soldier(SoldierUnit soldierUnit) { + this.unit = soldierUnit; } - @Override - public void swingImp() { - System.out.println("swinging Stormbringer"); - } + final Logger logger = LoggerFactory.getLogger(Soldier.class); @Override - public void unwieldImp() { - System.out.println("unwielding Stormbringer"); - } - - @Override - public void eatSoulImp() { - System.out.println("Stormbringer devours the enemy's soul"); + public void soldierReady() { + logger.info("[Solider] " + unit.getName() + " is ready!"); } } diff --git a/extension-objects/src/main/java/units/CommanderUnit.java b/extension-objects/src/main/java/units/CommanderUnit.java new file mode 100644 index 000000000..61b7a050d --- /dev/null +++ b/extension-objects/src/main/java/units/CommanderUnit.java @@ -0,0 +1,49 @@ +/** + * The MIT License + * Copyright (c) 2014 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 units; + +import abstractextensions.UnitExtension; +import concreteextensions.Commander; + +/** + * Class defining CommanderUnit + */ +public class CommanderUnit extends Unit { + + public CommanderUnit(String name) { + super(name); + } + + @Override + public UnitExtension getUnitExtension(String extensionName) { + + if (extensionName.equals("CommanderExtension")) { + if (unitExtension == null) { + unitExtension = new Commander(this); + } + return unitExtension; + } + + return super.getUnitExtension(extensionName); + } +} diff --git a/bridge/src/main/java/com/iluwatar/bridge/Mjollnir.java b/extension-objects/src/main/java/units/SergeantUnit.java similarity index 69% rename from bridge/src/main/java/com/iluwatar/bridge/Mjollnir.java rename to extension-objects/src/main/java/units/SergeantUnit.java index 0cc31b471..a655abac4 100644 --- a/bridge/src/main/java/com/iluwatar/bridge/Mjollnir.java +++ b/extension-objects/src/main/java/units/SergeantUnit.java @@ -20,32 +20,30 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.bridge; +package units; + +import abstractextensions.UnitExtension; +import concreteextensions.Sergeant; /** - * - * Mjollnir - * + * Class defining SergeantUnit */ -public class Mjollnir extends FlyingMagicWeaponImpl { +public class SergeantUnit extends Unit { - @Override - public void wieldImp() { - System.out.println("wielding Mjollnir"); + public SergeantUnit(String name) { + super(name); } @Override - public void swingImp() { - System.out.println("swinging Mjollnir"); - } + public UnitExtension getUnitExtension(String extensionName) { - @Override - public void unwieldImp() { - System.out.println("unwielding Mjollnir"); - } + if (extensionName.equals("SergeantExtension")) { + if (unitExtension == null) { + unitExtension = new Sergeant(this); + } + return unitExtension; + } - @Override - public void flyImp() { - System.out.println("Mjollnir hits the enemy in the air and returns back to the owner's hand"); + return super.getUnitExtension(extensionName); } } diff --git a/bridge/src/main/java/com/iluwatar/bridge/Excalibur.java b/extension-objects/src/main/java/units/SoldierUnit.java similarity index 70% rename from bridge/src/main/java/com/iluwatar/bridge/Excalibur.java rename to extension-objects/src/main/java/units/SoldierUnit.java index 2523b3557..03617dd27 100644 --- a/bridge/src/main/java/com/iluwatar/bridge/Excalibur.java +++ b/extension-objects/src/main/java/units/SoldierUnit.java @@ -20,32 +20,30 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.bridge; +package units; + +import abstractextensions.UnitExtension; +import concreteextensions.Soldier; /** - * - * Excalibur - * + * Class defining SoldierUnit */ -public class Excalibur extends BlindingMagicWeaponImpl { +public class SoldierUnit extends Unit { - @Override - public void wieldImp() { - System.out.println("wielding Excalibur"); + public SoldierUnit(String name) { + super(name); } @Override - public void swingImp() { - System.out.println("swinging Excalibur"); - } + public UnitExtension getUnitExtension(String extensionName) { - @Override - public void unwieldImp() { - System.out.println("unwielding Excalibur"); - } + if (extensionName.equals("SoldierExtension")) { + if (unitExtension == null) { + unitExtension = new Soldier(this); + } - @Override - public void blindImp() { - System.out.println("bright light streams from Excalibur blinding the enemy"); + return unitExtension; + } + return super.getUnitExtension(extensionName); } } diff --git a/extension-objects/src/main/java/units/Unit.java b/extension-objects/src/main/java/units/Unit.java new file mode 100644 index 000000000..1bf251a8e --- /dev/null +++ b/extension-objects/src/main/java/units/Unit.java @@ -0,0 +1,50 @@ +/** + * The MIT License + * Copyright (c) 2014 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 units; + +import abstractextensions.UnitExtension; + +/** + * Class defining Unit, other units will extend this class + */ +public class Unit { + + private String name; + protected UnitExtension unitExtension = null; + + public Unit(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public UnitExtension getUnitExtension(String extensionName) { + return null; + } +} diff --git a/extension-objects/src/test/java/AppTest.java b/extension-objects/src/test/java/AppTest.java new file mode 100644 index 000000000..f3111bebb --- /dev/null +++ b/extension-objects/src/test/java/AppTest.java @@ -0,0 +1,37 @@ +/** + * The MIT License + * Copyright (c) 2014 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. + */ + +import org.junit.jupiter.api.Test; + +/** + * Created by Srdjan on 03-May-17. + */ +public class AppTest { + @Test + public void main() throws Exception { + + String[] args = {}; + App.main(args); + } + +} \ No newline at end of file diff --git a/extension-objects/src/test/java/concreteextensions/CommanderTest.java b/extension-objects/src/test/java/concreteextensions/CommanderTest.java new file mode 100644 index 000000000..5dac551f4 --- /dev/null +++ b/extension-objects/src/test/java/concreteextensions/CommanderTest.java @@ -0,0 +1,39 @@ +/** + * The MIT License + * Copyright (c) 2014 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 concreteextensions; + +import org.junit.jupiter.api.Test; +import units.CommanderUnit; + +/** + * Created by Srdjan on 03-May-17. + */ +public class CommanderTest { + @Test + public void commanderReady() throws Exception { + final Commander commander = new Commander(new CommanderUnit("CommanderUnitTest")); + + commander.commanderReady(); + } + +} \ No newline at end of file diff --git a/extension-objects/src/test/java/concreteextensions/SergeantTest.java b/extension-objects/src/test/java/concreteextensions/SergeantTest.java new file mode 100644 index 000000000..f26830e2c --- /dev/null +++ b/extension-objects/src/test/java/concreteextensions/SergeantTest.java @@ -0,0 +1,39 @@ +/** + * The MIT License + * Copyright (c) 2014 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 concreteextensions; + +import org.junit.jupiter.api.Test; +import units.SergeantUnit; + +/** + * Created by Srdjan on 03-May-17. + */ +public class SergeantTest { + @Test + public void sergeantReady() throws Exception { + final Sergeant sergeant = new Sergeant(new SergeantUnit("SergeantUnitTest")); + + sergeant.sergeantReady(); + } + +} \ No newline at end of file diff --git a/extension-objects/src/test/java/concreteextensions/SoldierTest.java b/extension-objects/src/test/java/concreteextensions/SoldierTest.java new file mode 100644 index 000000000..a9a6c4768 --- /dev/null +++ b/extension-objects/src/test/java/concreteextensions/SoldierTest.java @@ -0,0 +1,39 @@ +/** + * The MIT License + * Copyright (c) 2014 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 concreteextensions; + +import org.junit.jupiter.api.Test; +import units.SoldierUnit; + +/** + * Created by Srdjan on 03-May-17. + */ +public class SoldierTest { + @Test + public void soldierReady() throws Exception { + final Soldier soldier = new Soldier(new SoldierUnit("SoldierUnitTest")); + + soldier.soldierReady(); + } + +} \ No newline at end of file diff --git a/extension-objects/src/test/java/units/CommanderUnitTest.java b/extension-objects/src/test/java/units/CommanderUnitTest.java new file mode 100644 index 000000000..536c3ae3f --- /dev/null +++ b/extension-objects/src/test/java/units/CommanderUnitTest.java @@ -0,0 +1,45 @@ +/** + * The MIT License + * Copyright (c) 2014 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 units; + +import abstractextensions.CommanderExtension; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * Created by Srdjan on 03-May-17. + */ +public class CommanderUnitTest { + @Test + public void getUnitExtension() throws Exception { + + final Unit unit = new CommanderUnit("CommanderUnitName"); + + assertNull(unit.getUnitExtension("SoldierExtension")); + assertNull(unit.getUnitExtension("SergeantExtension")); + assertNotNull((CommanderExtension) unit.getUnitExtension("CommanderExtension")); + } + +} \ No newline at end of file diff --git a/extension-objects/src/test/java/units/SergeantUnitTest.java b/extension-objects/src/test/java/units/SergeantUnitTest.java new file mode 100644 index 000000000..ac518b488 --- /dev/null +++ b/extension-objects/src/test/java/units/SergeantUnitTest.java @@ -0,0 +1,45 @@ +/** + * The MIT License + * Copyright (c) 2014 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 units; + +import abstractextensions.SergeantExtension; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * Created by Srdjan on 03-May-17. + */ +public class SergeantUnitTest { + @Test + public void getUnitExtension() throws Exception { + + final Unit unit = new SergeantUnit("SergeantUnitName"); + + assertNull(unit.getUnitExtension("SoldierExtension")); + assertNotNull((SergeantExtension) unit.getUnitExtension("SergeantExtension")); + assertNull(unit.getUnitExtension("CommanderExtension")); + } + +} \ No newline at end of file diff --git a/extension-objects/src/test/java/units/SoldierUnitTest.java b/extension-objects/src/test/java/units/SoldierUnitTest.java new file mode 100644 index 000000000..1aeb9a3cd --- /dev/null +++ b/extension-objects/src/test/java/units/SoldierUnitTest.java @@ -0,0 +1,47 @@ +/** + * The MIT License + * Copyright (c) 2014 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 units; + +import abstractextensions.SoldierExtension; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * Created by Srdjan on 03-May-17. + */ +public class SoldierUnitTest { + @Test + public void getUnitExtension() throws Exception { + + final Unit unit = new SoldierUnit("SoldierUnitName"); + + assertNotNull((SoldierExtension) unit.getUnitExtension("SoldierExtension")); + assertNull(unit.getUnitExtension("SergeantExtension")); + assertNull(unit.getUnitExtension("CommanderExtension")); + + + } + +} \ No newline at end of file diff --git a/extension-objects/src/test/java/units/UnitTest.java b/extension-objects/src/test/java/units/UnitTest.java new file mode 100644 index 000000000..389c9f753 --- /dev/null +++ b/extension-objects/src/test/java/units/UnitTest.java @@ -0,0 +1,52 @@ +/** + * The MIT License + * Copyright (c) 2014 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 units; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * Created by Srdjan on 03-May-17. + */ +public class UnitTest { + + @Test + public void testConstGetSet() throws Exception { + final String name = "testName"; + final Unit unit = new Unit(name); + assertEquals(name, unit.getName()); + + final String newName = "newName"; + unit.setName(newName); + assertEquals(newName, unit.getName()); + + + assertNull(unit.getUnitExtension("")); + assertNull(unit.getUnitExtension("SoldierExtension")); + assertNull(unit.getUnitExtension("SergeantExtension")); + assertNull(unit.getUnitExtension("CommanderExtension")); + } + +} \ No newline at end of file diff --git a/facade/README.md b/facade/README.md index c416552c7..7caa89d94 100644 --- a/facade/README.md +++ b/facade/README.md @@ -14,14 +14,195 @@ tags: Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use. -![alt text](./etc/facade_1.png "Facade") +## Explanation + +Real world example + +> How does a goldmine work? "Well, the miners go down there and dig gold!" you say. That is what you believe because you are using a simple interface that goldmine provides on the outside, internally it has to do a lot of stuff to make it happen. This simple interface to the complex subsystem is a facade. + +In plain words + +> Facade pattern provides a simplified interface to a complex subsystem. + +Wikipedia says + +> A facade is an object that provides a simplified interface to a larger body of code, such as a class library. + +**Programmatic Example** + +Taking our goldmine example from above. Here we have the dwarven mine worker hierarchy + +``` +public abstract class DwarvenMineWorker { + + private static final Logger LOGGER = LoggerFactory.getLogger(DwarvenMineWorker.class); + + public void goToSleep() { + LOGGER.info("{} goes to sleep.", name()); + } + + public void wakeUp() { + LOGGER.info("{} wakes up.", name()); + } + + public void goHome() { + LOGGER.info("{} goes home.", name()); + } + + public void goToMine() { + LOGGER.info("{} goes to the mine.", name()); + } + + private void action(Action action) { + switch (action) { + case GO_TO_SLEEP: + goToSleep(); + break; + case WAKE_UP: + wakeUp(); + break; + case GO_HOME: + goHome(); + break; + case GO_TO_MINE: + goToMine(); + break; + case WORK: + work(); + break; + default: + LOGGER.info("Undefined action"); + break; + } + } + + public void action(Action... actions) { + for (Action action : actions) { + action(action); + } + } + + public abstract void work(); + + public abstract String name(); + + static enum Action { + GO_TO_SLEEP, WAKE_UP, GO_HOME, GO_TO_MINE, WORK + } +} + +public class DwarvenTunnelDigger extends DwarvenMineWorker { + + private static final Logger LOGGER = LoggerFactory.getLogger(DwarvenTunnelDigger.class); + + @Override + public void work() { + LOGGER.info("{} creates another promising tunnel.", name()); + } + + @Override + public String name() { + return "Dwarven tunnel digger"; + } +} + +public class DwarvenGoldDigger extends DwarvenMineWorker { + + private static final Logger LOGGER = LoggerFactory.getLogger(DwarvenGoldDigger.class); + + @Override + public void work() { + LOGGER.info("{} digs for gold.", name()); + } + + @Override + public String name() { + return "Dwarf gold digger"; + } +} + +public class DwarvenCartOperator extends DwarvenMineWorker { + + private static final Logger LOGGER = LoggerFactory.getLogger(DwarvenCartOperator.class); + + @Override + public void work() { + LOGGER.info("{} moves gold chunks out of the mine.", name()); + } + + @Override + public String name() { + return "Dwarf cart operator"; + } +} + +``` + +To operate all these goldmine workers we have the facade + +``` +public class DwarvenGoldmineFacade { + + private final List workers; + + public DwarvenGoldmineFacade() { + workers = new ArrayList<>(); + workers.add(new DwarvenGoldDigger()); + workers.add(new DwarvenCartOperator()); + workers.add(new DwarvenTunnelDigger()); + } + + public void startNewDay() { + makeActions(workers, DwarvenMineWorker.Action.WAKE_UP, DwarvenMineWorker.Action.GO_TO_MINE); + } + + public void digOutGold() { + makeActions(workers, DwarvenMineWorker.Action.WORK); + } + + public void endDay() { + makeActions(workers, DwarvenMineWorker.Action.GO_HOME, DwarvenMineWorker.Action.GO_TO_SLEEP); + } + + private static void makeActions(Collection workers, + DwarvenMineWorker.Action... actions) { + for (DwarvenMineWorker worker : workers) { + worker.action(actions); + } + } +} +``` + +Now to use the facade + +``` +DwarvenGoldmineFacade facade = new DwarvenGoldmineFacade(); +facade.startNewDay(); +// Dwarf gold digger wakes up. +// Dwarf gold digger goes to the mine. +// Dwarf cart operator wakes up. +// Dwarf cart operator goes to the mine. +// Dwarven tunnel digger wakes up. +// Dwarven tunnel digger goes to the mine. +facade.digOutGold(); +// Dwarf gold digger digs for gold. +// Dwarf cart operator moves gold chunks out of the mine. +// Dwarven tunnel digger creates another promising tunnel. +facade.endDay(); +// Dwarf gold digger goes home. +// Dwarf gold digger goes to sleep. +// Dwarf cart operator goes home. +// Dwarf cart operator goes to sleep. +// Dwarven tunnel digger goes home. +// Dwarven tunnel digger goes to sleep. +``` ## Applicability Use the Facade pattern when * you want to provide a simple interface to a complex subsystem. Subsystems often get more complex as they evolve. Most patterns, when applied, result in more and smaller classes. This makes the subsystem more reusable and easier to customize, but it also becomes harder to use for clients that don't need to customize it. A facade can provide a simple default view of the subsystem that is good enough for most clients. Only clients needing more customizability will need to look beyond the facade. * there are many dependencies between clients and the implementation classes of an abstraction. Introduce a facade to decouple the subsystem from clients and other subsystems, thereby promoting subsystem independence and portability. -* you want to layer your subsystems. Use a facade to define an entry point to each subsystem level. If subsystems are dependent, the you can simplify the dependencies between them by making them communicate with each other solely through their facades +* you want to layer your subsystems. Use a facade to define an entry point to each subsystem level. If subsystems are dependent, then you can simplify the dependencies between them by making them communicate with each other solely through their facades. ## Credits diff --git a/facade/etc/facade.png b/facade/etc/facade.png deleted file mode 100644 index 5cc6271da..000000000 Binary files a/facade/etc/facade.png and /dev/null differ diff --git a/facade/etc/facade.ucls b/facade/etc/facade.ucls deleted file mode 100644 index 28756c189..000000000 --- a/facade/etc/facade.ucls +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/facade/etc/facade_1.png b/facade/etc/facade_1.png deleted file mode 100644 index 6ed0573fc..000000000 Binary files a/facade/etc/facade_1.png and /dev/null differ diff --git a/facade/pom.xml b/facade/pom.xml index e0e0f4a4d..deb56a382 100644 --- a/facade/pom.xml +++ b/facade/pom.xml @@ -2,7 +2,7 @@ + + + + Design Patterns - Factory Method Presentation + + + + + + + + + \ No newline at end of file diff --git a/factory-method/pom.xml b/factory-method/pom.xml index 5081ecf99..4cad86fdd 100644 --- a/factory-method/pom.xml +++ b/factory-method/pom.xml @@ -2,7 +2,7 @@ + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.19.0-SNAPSHOT + + jar + guarded-suspension + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + \ No newline at end of file diff --git a/guarded-suspension/src/main/java/com/iluwatar/guarded/suspension/App.java b/guarded-suspension/src/main/java/com/iluwatar/guarded/suspension/App.java new file mode 100644 index 000000000..8747c84e5 --- /dev/null +++ b/guarded-suspension/src/main/java/com/iluwatar/guarded/suspension/App.java @@ -0,0 +1,76 @@ +/** + * The MIT License + * Copyright (c) 2014 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. + */ +/** + * Guarded-suspension is a concurrent design pattern for handling situation when to execute some action we need + * condition to be satisfied. + *

+ * Implementation is based on GuardedQueue, which has two methods: get and put, + * the condition is that we cannot get from empty queue so when thread attempt + * to break the condition we invoke Object's wait method on him and when other thread put an element + * to the queue he notify the waiting one that now he can get from queue. + */ +package com.iluwatar.guarded.suspension; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +/** + * Created by robertt240 on 1/26/17. + */ +public class App { + /** + * Example pattern execution + * + * @param args - command line args + */ + public static void main(String[] args) { + GuardedQueue guardedQueue = new GuardedQueue(); + ExecutorService executorService = Executors.newFixedThreadPool(3); + + //here we create first thread which is supposed to get from guardedQueue + executorService.execute(() -> { + guardedQueue.get(); + } + ); + + //here we wait two seconds to show that the thread which is trying to get from guardedQueue will be waiting + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + //now we execute second thread which will put number to guardedQueue and notify first thread that it could get + executorService.execute(() -> { + guardedQueue.put(20); + } + ); + executorService.shutdown(); + try { + executorService.awaitTermination(30, TimeUnit.SECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + +} diff --git a/guarded-suspension/src/main/java/com/iluwatar/guarded/suspension/GuardedQueue.java b/guarded-suspension/src/main/java/com/iluwatar/guarded/suspension/GuardedQueue.java new file mode 100644 index 000000000..bf6142dd9 --- /dev/null +++ b/guarded-suspension/src/main/java/com/iluwatar/guarded/suspension/GuardedQueue.java @@ -0,0 +1,70 @@ +/** + * The MIT License + * Copyright (c) 2014 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.guarded.suspension; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * Guarded Queue is an implementation for Guarded Suspension Pattern + * Guarded suspension pattern is used to handle a situation when you want to execute a method + * on an object which is not in a proper state. + * @see http://java-design-patterns.com/patterns/guarded-suspension/ + */ +public class GuardedQueue { + private static final Logger LOGGER = LoggerFactory.getLogger(GuardedQueue.class); + private final Queue sourceList; + + public GuardedQueue() { + this.sourceList = new LinkedList<>(); + } + + /** + * @return last element of a queue if queue is not empty + */ + public synchronized Integer get() { + while (sourceList.isEmpty()) { + try { + LOGGER.info("waiting"); + wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + LOGGER.info("getting"); + return sourceList.peek(); + } + + /** + * @param e number which we want to put to our queue + */ + public synchronized void put(Integer e) { + LOGGER.info("putting"); + sourceList.add(e); + LOGGER.info("notifying"); + notify(); + } +} diff --git a/bridge/src/test/java/com/iluwatar/bridge/SoulEatingMagicWeaponTest.java b/guarded-suspension/src/test/java/com/iluwatar/guarded/suspension/GuardedQueueTest.java similarity index 53% rename from bridge/src/test/java/com/iluwatar/bridge/SoulEatingMagicWeaponTest.java rename to guarded-suspension/src/test/java/com/iluwatar/guarded/suspension/GuardedQueueTest.java index 0bd0a6b8d..2834bd4ef 100644 --- a/bridge/src/test/java/com/iluwatar/bridge/SoulEatingMagicWeaponTest.java +++ b/guarded-suspension/src/test/java/com/iluwatar/guarded/suspension/GuardedQueueTest.java @@ -1,17 +1,17 @@ /** * The MIT License * Copyright (c) 2014 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 @@ -20,36 +20,42 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.bridge; +package com.iluwatar.guarded.suspension; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.internal.verification.VerificationModeFactory.times; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertEquals; /** - * Date: 12/6/15 - 11:43 PM - * - * @author Jeroen Meulemeester + * Test for Guarded Queue */ -public class SoulEatingMagicWeaponTest extends MagicWeaponTest { +public class GuardedQueueTest { + private volatile Integer value; - /** - * Invoke all possible actions on the weapon and check if the actions are executed on the actual - * underlying weapon implementation. - */ @Test - public void testStormBringer() throws Exception { - final Stormbringer stormbringer = spy(new Stormbringer()); - final SoulEatingMagicWeapon soulEatingMagicWeapon = new SoulEatingMagicWeapon(stormbringer); - - testBasicWeaponActions(soulEatingMagicWeapon, stormbringer); - - soulEatingMagicWeapon.eatSoul(); - verify(stormbringer, times(1)).eatSoulImp(); - verifyNoMoreInteractions(stormbringer); + public void testGet() { + GuardedQueue g = new GuardedQueue(); + ExecutorService executorService = Executors.newFixedThreadPool(2); + executorService.submit(() -> value = g.get()); + executorService.submit(() -> g.put(Integer.valueOf(10))); + executorService.shutdown(); + try { + executorService.awaitTermination(30, TimeUnit.SECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + assertEquals(Integer.valueOf(10), value); } -} \ No newline at end of file + @Test + public void testPut() { + GuardedQueue g = new GuardedQueue(); + g.put(12); + assertEquals(Integer.valueOf(12), g.get()); + } + +} diff --git a/half-sync-half-async/pom.xml b/half-sync-half-async/pom.xml index 076dfb44d..3ed2da411 100644 --- a/half-sync-half-async/pom.xml +++ b/half-sync-half-async/pom.xml @@ -2,7 +2,7 @@ +7Zpdk6I4FIZ/jbdbJAHEyx7nY/diqrqqd2tnLiOJSDUSK6ZHe3/9BkmUfFiDDqBM2TcNBwLxOe85nBOYoPl6/4XjzeorI7SYwIDsJ+jjBEKA4kj+qyzvtWUag9qQ8Zyok06Gl/w/qoyBsr7lhG6NEwVjhcg3pjFlZUlTYdgw52xnnrZkhXnXDc6oY3hJceFa/82JWNXWJApO9j9pnq30nUGgjixw+ppx9laq+00gWh7+6sNrrK+lzt+uMGG7hgl9mqA5Z0zUW+v9nBYVW42tHvf5zNHjvDktRZsBqB7wAxdvVM/4MC/xrlnIKW6qzRXd44yVE/RhQ3m+poLyk/X5ZPqwW+WCvmxwWo3aSYFI20qsC7kH5Ka6I+WC7s/OGhxZSI1RJi/N3+UpagBMFD4lL6D3dydnIa2lVcNRMFZGrASSHa99giQ3FCc/MzhKZmgaGcyOKBrMAPQwO2r7V5iFDrM549ThJgfJEKc/B7LMi2LOCsYP46oYg2kq7VvB2SttHCHxIo7ibhCGATARJshBqDNfk2AXoot+Lrpr4RFMk6UXXpwmdLHsCF5s6W/qwkMe+aEO4MW/GzwEPQmvJ3jT0cMDMxNeGA0GLxk7PDQ1H7VDKm82fnh2zoODwdPlz3jphdCUHoyGkx5oUeRdTS+iCQl99BK4QHFHxUoUWfQ89V5v9Fq0FXdOz9ZeMCC9Nk/ckjxVfa7cK1lJTWJ0n4tvje3vcjuotks5k2/Nne/6rJJ8zqsJHQ7Vd6PE6ZEtenJG7I2n1KhQBeYZFU0duJAbECMPRG3jtMAi/2HOwkdW3eGZ5XJ+Z7vECFjOqWevRjWbZOtCCJkXCu2Cvv7NzoUOjj7+7Ha+b1MwXOz7P6IefQwfPr7Qx33WNYNkR5s60nXuANkR9lnXDEIPWc8WNBvu2aJn+8v5JfDkF/V0Ac2nS3e5J/Hkntkj91yWe7S7O/U/OO/9/nzfWC5/+L6d70dfldsrOSEcbiXHs4R9ZeTUGdEXO41E2mX0TD3Rc1+Vue2gq6Mn6S963BX4Z8bF1hGBlLcwPW/GhFJGM4CUCRd5VsrdVDrz8FaoCpY8xcWTOrDOCSnOBWcH8YVmoYHzGDgNWYS+12pdxJe7SP/PVlKQZ1Q0ltVvtVEfXrRSojD3/w4NALvudd9hAN97x7ALQO6ywN90WwUDzqiS903h2CvFiZucZ32xcdvmv6RqMi6zFCtvjuZY4et851lN6g2N221+xAIv8Pb28RQHJhcwG04yOrU1uHxl6au0kLvBYz3dYOTi6S3doPF2irpKNAqe2I96mIIHWJ6Mr20XgF059dcuoD5axWEEoOsRQwDThwAuFECbfvE+Ox5dlxkCSB4CuFAAXbW8vpcRvldRHT4CfAK46WrhKAXQ5quzOxUAdAWAbrpkOEoBuE35E8Eb2Y//XuseVh+CPP1ZR+secvf0dXftkdMn9OjT/w== \ No newline at end of file diff --git a/hexagonal/etc/presentation.html b/hexagonal/etc/presentation.html new file mode 100644 index 000000000..5c6d1d0a5 --- /dev/null +++ b/hexagonal/etc/presentation.html @@ -0,0 +1,127 @@ + + + + + Title + + + + + + + + + + diff --git a/hexagonal/pom.xml b/hexagonal/pom.xml index e9e2a502d..6907ca10c 100644 --- a/hexagonal/pom.xml +++ b/hexagonal/pom.xml @@ -2,7 +2,7 @@ + + + layers.log + + layers-%d.log + 5 + + + %-5p [%d{ISO8601,UTC}] %c: %m%n + + + + + + %-5p [%d{ISO8601,UTC}] %c: %m%n + + + + + + + + + + + + + + + + + + diff --git a/layers/src/test/java/com/iluwatar/layers/AppTest.java b/layers/src/test/java/com/iluwatar/layers/AppTest.java index 5a5374def..aa14e039d 100644 --- a/layers/src/test/java/com/iluwatar/layers/AppTest.java +++ b/layers/src/test/java/com/iluwatar/layers/AppTest.java @@ -1,6 +1,6 @@ /** * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright (c) 2014-2016 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 @@ -22,7 +22,7 @@ */ package com.iluwatar.layers; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * diff --git a/layers/src/test/java/com/iluwatar/layers/CakeBakingExceptionTest.java b/layers/src/test/java/com/iluwatar/layers/CakeBakingExceptionTest.java index 1cca98472..e13e148a3 100644 --- a/layers/src/test/java/com/iluwatar/layers/CakeBakingExceptionTest.java +++ b/layers/src/test/java/com/iluwatar/layers/CakeBakingExceptionTest.java @@ -1,6 +1,6 @@ /** * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright (c) 2014-2016 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 @@ -22,10 +22,10 @@ */ package com.iluwatar.layers; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; /** * Date: 12/15/15 - 7:57 PM diff --git a/layers/src/test/java/com/iluwatar/layers/CakeBakingServiceImplTest.java b/layers/src/test/java/com/iluwatar/layers/CakeBakingServiceImplTest.java index e1c539cf2..f740fb5f4 100644 --- a/layers/src/test/java/com/iluwatar/layers/CakeBakingServiceImplTest.java +++ b/layers/src/test/java/com/iluwatar/layers/CakeBakingServiceImplTest.java @@ -1,6 +1,6 @@ /** * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright (c) 2014-2016 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 @@ -22,16 +22,17 @@ */ package com.iluwatar.layers; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.Arrays; import java.util.Collections; import java.util.List; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Date: 12/15/15 - 9:55 PM @@ -123,7 +124,7 @@ public class CakeBakingServiceImplTest { } - @Test(expected = CakeBakingException.class) + @Test public void testBakeCakeMissingTopping() throws CakeBakingException { final CakeBakingServiceImpl service = new CakeBakingServiceImpl(); @@ -133,10 +134,12 @@ public class CakeBakingServiceImplTest { service.saveNewLayer(layer2); final CakeToppingInfo missingTopping = new CakeToppingInfo("Topping1", 1000); - service.bakeNewCake(new CakeInfo(missingTopping, Arrays.asList(layer1, layer2))); + assertThrows(CakeBakingException.class, () -> { + service.bakeNewCake(new CakeInfo(missingTopping, Arrays.asList(layer1, layer2))); + }); } - @Test(expected = CakeBakingException.class) + @Test public void testBakeCakeMissingLayer() throws CakeBakingException { final CakeBakingServiceImpl service = new CakeBakingServiceImpl(); @@ -151,11 +154,12 @@ public class CakeBakingServiceImplTest { service.saveNewLayer(layer1); final CakeLayerInfo missingLayer = new CakeLayerInfo("Layer2", 2000); - service.bakeNewCake(new CakeInfo(topping1, Arrays.asList(layer1, missingLayer))); - + assertThrows(CakeBakingException.class, () -> { + service.bakeNewCake(new CakeInfo(topping1, Arrays.asList(layer1, missingLayer))); + }); } - @Test(expected = CakeBakingException.class) + @Test public void testBakeCakesUsedLayer() throws CakeBakingException { final CakeBakingServiceImpl service = new CakeBakingServiceImpl(); @@ -174,8 +178,9 @@ public class CakeBakingServiceImplTest { service.saveNewLayer(layer2); service.bakeNewCake(new CakeInfo(topping1, Arrays.asList(layer1, layer2))); - service.bakeNewCake(new CakeInfo(topping2, Collections.singletonList(layer2))); - + assertThrows(CakeBakingException.class, () -> { + service.bakeNewCake(new CakeInfo(topping2, Collections.singletonList(layer2))); + }); } } diff --git a/layers/src/test/java/com/iluwatar/layers/CakeTest.java b/layers/src/test/java/com/iluwatar/layers/CakeTest.java index 6cd50106c..f71a0b35d 100644 --- a/layers/src/test/java/com/iluwatar/layers/CakeTest.java +++ b/layers/src/test/java/com/iluwatar/layers/CakeTest.java @@ -1,6 +1,6 @@ /** * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright (c) 2014-2016 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 @@ -22,15 +22,15 @@ */ package com.iluwatar.layers; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.HashSet; import java.util.Set; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Date: 12/15/15 - 8:02 PM diff --git a/layers/src/test/java/com/iluwatar/layers/CakeViewImplTest.java b/layers/src/test/java/com/iluwatar/layers/CakeViewImplTest.java index a69aa23bb..4aed18869 100644 --- a/layers/src/test/java/com/iluwatar/layers/CakeViewImplTest.java +++ b/layers/src/test/java/com/iluwatar/layers/CakeViewImplTest.java @@ -1,6 +1,6 @@ /** * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright (c) 2014-2016 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 @@ -22,19 +22,40 @@ */ package com.iluwatar.layers; -import org.junit.Test; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.AppenderBase; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.LoggerFactory; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; -import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; /** * Date: 12/15/15 - 10:04 PM * * @author Jeroen Meulemeester */ -public class CakeViewImplTest extends StdOutTest { +public class CakeViewImplTest { + + private InMemoryAppender appender; + + @BeforeEach + public void setUp() { + appender = new InMemoryAppender(CakeViewImpl.class); + } + + @AfterEach + public void tearDown() { + appender.stop(); + } /** * Verify if the cake view renders the expected result @@ -56,11 +77,34 @@ public class CakeViewImplTest extends StdOutTest { final CakeViewImpl cakeView = new CakeViewImpl(bakingService); - verifyZeroInteractions(getStdOutMock()); + assertEquals(0, appender.getLogSize()); cakeView.render(); - verify(getStdOutMock(), times(1)).println(cake); + assertEquals(cake.toString(), appender.getLastMessage()); } + private class InMemoryAppender extends AppenderBase { + + private List log = new LinkedList<>(); + + public InMemoryAppender(Class clazz) { + ((Logger) LoggerFactory.getLogger(clazz)).addAppender(this); + start(); + } + + @Override + protected void append(ILoggingEvent eventObject) { + log.add(eventObject); + } + + public String getLastMessage() { + return log.get(log.size() - 1).getFormattedMessage(); + } + + public int getLogSize() { + return log.size(); + } + } + } diff --git a/layers/src/test/java/com/iluwatar/layers/StdOutTest.java b/layers/src/test/java/com/iluwatar/layers/StdOutTest.java deleted file mode 100644 index 3c94b178c..000000000 --- a/layers/src/test/java/com/iluwatar/layers/StdOutTest.java +++ /dev/null @@ -1,76 +0,0 @@ -/** - * The MIT License - * Copyright (c) 2014 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.layers; - -import org.junit.After; -import org.junit.Before; - -import java.io.PrintStream; - -import static org.mockito.Mockito.mock; - -/** - * Date: 12/10/15 - 8:37 PM - * - * @author Jeroen Meulemeester - */ -public abstract class StdOutTest { - - /** - * The mocked standard out {@link PrintStream}, required since the actions of the views don't have - * any influence on any other accessible objects, except for writing to std-out using {@link - * System#out} - */ - private final PrintStream stdOutMock = mock(PrintStream.class); - - /** - * Keep the original std-out so it can be restored after the test - */ - private final PrintStream stdOutOrig = System.out; - - /** - * Inject the mocked std-out {@link PrintStream} into the {@link System} class before each test - */ - @Before - public void setUp() { - System.setOut(this.stdOutMock); - } - - /** - * Removed the mocked std-out {@link PrintStream} again from the {@link System} class - */ - @After - public void tearDown() { - System.setOut(this.stdOutOrig); - } - - /** - * Get the mocked stdOut {@link PrintStream} - * - * @return The stdOut print stream mock, renewed before each test - */ - final PrintStream getStdOutMock() { - return this.stdOutMock; - } - -} diff --git a/lazy-loading/pom.xml b/lazy-loading/pom.xml index fe6d44c78..8d799d07c 100644 --- a/lazy-loading/pom.xml +++ b/lazy-loading/pom.xml @@ -2,7 +2,7 @@ + + + java-design-patterns + com.iluwatar + 1.19.0-SNAPSHOT + + 4.0.0 + + marker + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.hamcrest + hamcrest-core + 1.3 + test + + + + + diff --git a/marker/src/main/java/App.java b/marker/src/main/java/App.java new file mode 100644 index 000000000..d8fd54ecb --- /dev/null +++ b/marker/src/main/java/App.java @@ -0,0 +1,70 @@ +/** + * The MIT License + * Copyright (c) 2014 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. + */ +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Created by Alexis on 28-Apr-17. + * With Marker interface idea is to make empty interface and extend it. + * Basically it is just to identify the special objects from normal objects. + * Like in case of serialization , objects that need to be serialized must implement serializable interface + * (it is empty interface) and down the line writeObject() method must be checking + * if it is a instance of serializable or not. + *

+ * Marker interface vs annotation + * Marker interfaces and marker annotations both have their uses, + * neither of them is obsolete or always better then the other one. + * If you want to define a type that does not have any new methods associated with it, + * a marker interface is the way to go. + * If you want to mark program elements other than classes and interfaces, + * to allow for the possibility of adding more information to the marker in the future, + * or to fit the marker into a framework that already makes heavy use of annotation types, + * then a marker annotation is the correct choice + */ +public class App { + + /** + * Program entry point + * + * @param args command line args + */ + public static void main(String[] args) { + + final Logger logger = LoggerFactory.getLogger(App.class); + Guard guard = new Guard(); + Thief thief = new Thief(); + + if (guard instanceof Permission) { + guard.enter(); + } else { + logger.info("You have no permission to enter, please leave this area"); + } + + if (thief instanceof Permission) { + thief.steal(); + } else { + thief.doNothing(); + } + } +} + diff --git a/marker/src/main/java/Guard.java b/marker/src/main/java/Guard.java new file mode 100644 index 000000000..4dd2a4887 --- /dev/null +++ b/marker/src/main/java/Guard.java @@ -0,0 +1,37 @@ +/** + * The MIT License + * Copyright (c) 2014 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. + */ +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Class defining Guard + */ +public class Guard implements Permission { + + private static final Logger LOGGER = LoggerFactory.getLogger(Guard.class); + + protected static void enter() { + + LOGGER.info("You can enter"); + } +} diff --git a/marker/src/main/java/Permission.java b/marker/src/main/java/Permission.java new file mode 100644 index 000000000..2a85b3363 --- /dev/null +++ b/marker/src/main/java/Permission.java @@ -0,0 +1,28 @@ +/** + * The MIT License + * Copyright (c) 2014 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. + */ +/** + * Interface without any methods + * Marker interface is based on that assumption + */ +public interface Permission { +} diff --git a/bridge/src/main/java/com/iluwatar/bridge/MagicWeapon.java b/marker/src/main/java/Thief.java similarity index 76% rename from bridge/src/main/java/com/iluwatar/bridge/MagicWeapon.java rename to marker/src/main/java/Thief.java index 038da7c4f..b8d6464e5 100644 --- a/bridge/src/main/java/com/iluwatar/bridge/MagicWeapon.java +++ b/marker/src/main/java/Thief.java @@ -20,28 +20,21 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.bridge; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * - * MagicWeapon - * + * Class defining Thief */ -public abstract class MagicWeapon { +public class Thief { - protected MagicWeaponImpl imp; + private static final Logger LOGGER = LoggerFactory.getLogger(Thief.class); - public MagicWeapon(MagicWeaponImpl imp) { - this.imp = imp; + protected static void steal() { + LOGGER.info("Steal valuable items"); } - public abstract void wield(); - - public abstract void swing(); - - public abstract void unwield(); - - public MagicWeaponImpl getImp() { - return imp; + protected static void doNothing() { + LOGGER.info("Pretend nothing happened and just leave"); } } diff --git a/bridge/src/main/java/com/iluwatar/bridge/FlyingMagicWeaponImpl.java b/marker/src/test/java/AppTest.java similarity index 87% rename from bridge/src/main/java/com/iluwatar/bridge/FlyingMagicWeaponImpl.java rename to marker/src/test/java/AppTest.java index 4bdb8801b..856635007 100644 --- a/bridge/src/main/java/com/iluwatar/bridge/FlyingMagicWeaponImpl.java +++ b/marker/src/test/java/AppTest.java @@ -20,15 +20,17 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.bridge; + +import org.junit.jupiter.api.Test; /** - * - * FlyingMagicWeaponImpl - * + * Application test */ -public abstract class FlyingMagicWeaponImpl extends MagicWeaponImpl { - - public abstract void flyImp(); +public class AppTest { + @Test + public void test() { + String[] args = {}; + App.main(args); + } } diff --git a/marker/src/test/java/GuardTest.java b/marker/src/test/java/GuardTest.java new file mode 100644 index 000000000..cadd82f82 --- /dev/null +++ b/marker/src/test/java/GuardTest.java @@ -0,0 +1,39 @@ +/** + * The MIT License + * Copyright (c) 2014 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. + */ + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Guard test + */ +public class GuardTest { + + @Test + public void testGuard() { + Guard guard = new Guard(); + assertThat(guard, instanceOf(Permission.class)); + } +} \ No newline at end of file diff --git a/marker/src/test/java/ThiefTest.java b/marker/src/test/java/ThiefTest.java new file mode 100644 index 000000000..56492334c --- /dev/null +++ b/marker/src/test/java/ThiefTest.java @@ -0,0 +1,37 @@ +/** + * The MIT License + * Copyright (c) 2014 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. + */ + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +/** + * Thief test + */ +public class ThiefTest { + @Test + public void testThief() { + Thief thief = new Thief(); + assertFalse(thief instanceof Permission); + } +} \ No newline at end of file diff --git a/mediator/README.md b/mediator/README.md index c7a0478c8..3452082ef 100644 --- a/mediator/README.md +++ b/mediator/README.md @@ -24,6 +24,14 @@ Use the Mediator pattern when * reusing an object is difficult because it refers to and communicates with many other objects * a behavior that's distributed between several classes should be customizable without a lot of subclassing +## Real world examples + +* All scheduleXXX() methods of [java.util.Timer](http://docs.oracle.com/javase/8/docs/api/java/util/Timer.html) +* [java.util.concurrent.Executor#execute()](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html#execute-java.lang.Runnable-) +* submit() and invokeXXX() methods of [java.util.concurrent.ExecutorService](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html) +* scheduleXXX() methods of [java.util.concurrent.ScheduledExecutorService](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ScheduledExecutorService.html) +* [java.lang.reflect.Method#invoke()](http://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Method.html#invoke-java.lang.Object-java.lang.Object...-) + ## Credits -* [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) +* [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) diff --git a/mediator/pom.xml b/mediator/pom.xml index a6cdd028e..c9369b1a9 100644 --- a/mediator/pom.xml +++ b/mediator/pom.xml @@ -2,7 +2,7 @@ + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.19.0-SNAPSHOT + + module + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + log4j + log4j + + + diff --git a/module/src/main/java/com/iluwatar/module/App.java b/module/src/main/java/com/iluwatar/module/App.java new file mode 100644 index 000000000..1c117722f --- /dev/null +++ b/module/src/main/java/com/iluwatar/module/App.java @@ -0,0 +1,92 @@ +/** + * The MIT License Copyright (c) 2014 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.module; + +import java.io.FileNotFoundException; + +/** + * The Module pattern can be considered a Creational pattern and a Structural pattern. It manages + * the creation and organization of other elements, and groups them as the structural pattern does. + * An object that applies this pattern can provide the equivalent of a namespace, providing the + * initialization and finalization process of a static class or a class with static members with + * cleaner, more concise syntax and semantics. + *

+ * The below example demonstrates a use case for testing two different modules: File Logger and + * Console Logger + * + */ +public final class App { + + public static FileLoggerModule fileLoggerModule; + public static ConsoleLoggerModule consoleLoggerModule; + + /** + * Following method performs the initialization + * + * @throws FileNotFoundException if program is not able to find log files (output.txt and + * error.txt) + */ + public static void prepare() throws FileNotFoundException { + + /* Create new singleton objects and prepare their modules */ + fileLoggerModule = FileLoggerModule.getSingleton().prepare(); + consoleLoggerModule = ConsoleLoggerModule.getSingleton().prepare(); + } + + /** + * Following method performs the finalization + */ + public static void unprepare() { + + /* Close all resources */ + fileLoggerModule.unprepare(); + consoleLoggerModule.unprepare(); + } + + /** + * Following method is main executor + * + * @param args for providing default program arguments + */ + public static void execute(final String... args) { + + /* Send logs on file system */ + fileLoggerModule.printString("Message"); + fileLoggerModule.printErrorString("Error"); + + /* Send logs on console */ + consoleLoggerModule.printString("Message"); + consoleLoggerModule.printErrorString("Error"); + } + + /** + * Program entry point. + * + * @param args command line args. + * @throws FileNotFoundException if program is not able to find log files (output.txt and + * error.txt) + */ + public static void main(final String... args) throws FileNotFoundException { + prepare(); + execute(args); + unprepare(); + } + + private App() {} +} diff --git a/module/src/main/java/com/iluwatar/module/ConsoleLoggerModule.java b/module/src/main/java/com/iluwatar/module/ConsoleLoggerModule.java new file mode 100644 index 000000000..0efdd9033 --- /dev/null +++ b/module/src/main/java/com/iluwatar/module/ConsoleLoggerModule.java @@ -0,0 +1,106 @@ +/** + * The MIT License Copyright (c) 2014 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.module; + +import java.io.PrintStream; + +import org.apache.log4j.Logger; + +/** + * The ConsoleLoggerModule is responsible for showing logs on System Console + *

+ * The below example demonstrates a Console logger module, which can print simple and error messages + * in two designated formats + */ +public final class ConsoleLoggerModule { + + private static final Logger LOGGER = Logger.getLogger(ConsoleLoggerModule.class); + + private static ConsoleLoggerModule singleton = null; + + public PrintStream output = null; + public PrintStream error = null; + + private ConsoleLoggerModule() {} + + /** + * Static method to get single instance of class + * + * @return singleton instance of ConsoleLoggerModule + */ + public static ConsoleLoggerModule getSingleton() { + + if (ConsoleLoggerModule.singleton == null) { + ConsoleLoggerModule.singleton = new ConsoleLoggerModule(); + } + + return ConsoleLoggerModule.singleton; + } + + /** + * Following method performs the initialization + */ + public ConsoleLoggerModule prepare() { + + LOGGER.debug("ConsoleLoggerModule::prepare();"); + + this.output = new PrintStream(System.out); + this.error = new PrintStream(System.err); + + return this; + } + + /** + * Following method performs the finalization + */ + public void unprepare() { + + if (this.output != null) { + + this.output.flush(); + this.output.close(); + } + + if (this.error != null) { + + this.error.flush(); + this.error.close(); + } + + LOGGER.debug("ConsoleLoggerModule::unprepare();"); + } + + /** + * Used to print a message + * + * @param value will be printed on console + */ + public void printString(final String value) { + this.output.println(value); + } + + /** + * Used to print a error message + * + * @param value will be printed on error console + */ + public void printErrorString(final String value) { + this.error.println(value); + } +} diff --git a/module/src/main/java/com/iluwatar/module/FileLoggerModule.java b/module/src/main/java/com/iluwatar/module/FileLoggerModule.java new file mode 100644 index 000000000..e80444dab --- /dev/null +++ b/module/src/main/java/com/iluwatar/module/FileLoggerModule.java @@ -0,0 +1,114 @@ +/** + * The MIT License Copyright (c) 2014 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.module; + +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.PrintStream; + +import org.apache.log4j.Logger; + +/** + * The FileLoggerModule is responsible for showing logs on File System + *

+ * The below example demonstrates a File logger module, which can print simple and error messages in + * two designated files + */ +public final class FileLoggerModule { + + private static final Logger LOGGER = Logger.getLogger(FileLoggerModule.class); + + private static FileLoggerModule singleton = null; + + private static final String OUTPUT_FILE = "output.txt"; + private static final String ERROR_FILE = "error.txt"; + + public PrintStream output = null; + public PrintStream error = null; + + private FileLoggerModule() {} + + /** + * Static method to get single instance of class + * + * @return singleton instance of FileLoggerModule + */ + public static FileLoggerModule getSingleton() { + + if (FileLoggerModule.singleton == null) { + FileLoggerModule.singleton = new FileLoggerModule(); + } + + return FileLoggerModule.singleton; + } + + /** + * Following method performs the initialization + * + * @throws FileNotFoundException if program is not able to find log files (output.txt and + * error.txt) + */ + public FileLoggerModule prepare() throws FileNotFoundException { + + LOGGER.debug("FileLoggerModule::prepare();"); + + this.output = new PrintStream(new FileOutputStream(OUTPUT_FILE)); + this.error = new PrintStream(new FileOutputStream(ERROR_FILE)); + + return this; + } + + /** + * Following method performs the finalization + */ + public void unprepare() { + + if (this.output != null) { + + this.output.flush(); + this.output.close(); + } + + if (this.error != null) { + + this.error.flush(); + this.error.close(); + } + + LOGGER.debug("FileLoggerModule::unprepare();"); + } + + /** + * Used to print a message + * + * @param value will be printed in file + */ + public void printString(final String value) { + this.output.println(value); + } + + /** + * Used to print a error message + * + * @param value will be printed on error file + */ + public void printErrorString(final String value) { + this.error.println(value); + } +} diff --git a/module/src/main/resources/log4j.xml b/module/src/main/resources/log4j.xml new file mode 100644 index 000000000..b591c17e1 --- /dev/null +++ b/module/src/main/resources/log4j.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/module/src/test/java/com/iluwatar/module/AppTest.java b/module/src/test/java/com/iluwatar/module/AppTest.java new file mode 100644 index 000000000..0514dff3a --- /dev/null +++ b/module/src/test/java/com/iluwatar/module/AppTest.java @@ -0,0 +1,35 @@ +/** + * The MIT License Copyright (c) 2014 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.module; + +import org.junit.jupiter.api.Test; + +import java.io.FileNotFoundException; + +/** + * Tests that Module example runs without errors. + */ +public final class AppTest { + + @Test + public void test() throws FileNotFoundException { + final String[] args = {}; + App.main(args); + } +} diff --git a/module/src/test/java/com/iluwatar/module/FileLoggerModuleTest.java b/module/src/test/java/com/iluwatar/module/FileLoggerModuleTest.java new file mode 100644 index 000000000..bdae597bc --- /dev/null +++ b/module/src/test/java/com/iluwatar/module/FileLoggerModuleTest.java @@ -0,0 +1,182 @@ +/** + * The MIT License Copyright (c) 2014 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.module; + +import org.apache.log4j.Logger; +import org.junit.jupiter.api.Test; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * The Module pattern can be considered a Creational pattern and a Structural pattern. It manages + * the creation and organization of other elements, and groups them as the structural pattern does. + * An object that applies this pattern can provide the equivalent of a namespace, providing the + * initialization and finalization process of a static class or a class with static members with + * cleaner, more concise syntax and semantics. + *

+ * The below example demonstrates a JUnit test for testing two different modules: File Logger and + * Console Logger + */ +public final class FileLoggerModuleTest { + + private static final Logger LOGGER = Logger.getLogger(FileLoggerModuleTest.class); + + private static final String OUTPUT_FILE = "output.txt"; + private static final String ERROR_FILE = "error.txt"; + + private static final String MESSAGE = "MESSAGE"; + private static final String ERROR = "ERROR"; + + + /** + * This test verify that 'MESSAGE' is perfectly printed in output file + * + * @throws IOException if program is not able to find log files (output.txt and error.txt) + */ + @Test + public void testFileMessage() throws IOException { + + /* Get singletong instance of File Logger Module */ + final FileLoggerModule fileLoggerModule = FileLoggerModule.getSingleton(); + + /* Prepare the essential sub modules, to perform the sequence of jobs */ + fileLoggerModule.prepare(); + + /* Print 'Message' in file */ + fileLoggerModule.printString(MESSAGE); + + /* Test if 'Message' is printed in file */ + assertEquals(readFirstLine(OUTPUT_FILE), MESSAGE); + + /* Unprepare to cleanup the modules */ + fileLoggerModule.unprepare(); + } + + /** + * This test verify that nothing is printed in output file + * + * @throws IOException if program is not able to find log files (output.txt and error.txt) + */ + @Test + public void testNoFileMessage() throws IOException { + + /* Get singletong instance of File Logger Module */ + final FileLoggerModule fileLoggerModule = FileLoggerModule.getSingleton(); + + /* Prepare the essential sub modules, to perform the sequence of jobs */ + fileLoggerModule.prepare(); + + /* Test if nothing is printed in file */ + assertEquals(readFirstLine(OUTPUT_FILE), null); + + /* Unprepare to cleanup the modules */ + fileLoggerModule.unprepare(); + } + + /** + * This test verify that 'ERROR' is perfectly printed in error file + * + * @throws FileNotFoundException if program is not able to find log files (output.txt and + * error.txt) + */ + @Test + public void testFileErrorMessage() throws FileNotFoundException { + + /* Get singletong instance of File Logger Module */ + final FileLoggerModule fileLoggerModule = FileLoggerModule.getSingleton(); + + /* Prepare the essential sub modules, to perform the sequence of jobs */ + fileLoggerModule.prepare(); + + /* Print 'Error' in file */ + fileLoggerModule.printErrorString(ERROR); + + /* Test if 'Message' is printed in file */ + assertEquals(readFirstLine(ERROR_FILE), ERROR); + + /* Unprepare to cleanup the modules */ + fileLoggerModule.unprepare(); + } + + /** + * This test verify that nothing is printed in error file + * + * @throws FileNotFoundException if program is not able to find log files (output.txt and + * error.txt) + */ + @Test + public void testNoFileErrorMessage() throws FileNotFoundException { + + /* Get singletong instance of File Logger Module */ + final FileLoggerModule fileLoggerModule = FileLoggerModule.getSingleton(); + + /* Prepare the essential sub modules, to perform the sequence of jobs */ + fileLoggerModule.prepare(); + + /* Test if nothing is printed in file */ + assertEquals(readFirstLine(ERROR_FILE), null); + + /* Unprepare to cleanup the modules */ + fileLoggerModule.unprepare(); + } + + /** + * Utility method to read first line of a file + * + * @param file as file name to be read + * @return a string value as first line in file + */ + private static final String readFirstLine(final String file) { + + String firstLine = null; + BufferedReader bufferedReader = null; + try { + + /* Create a buffered reader */ + bufferedReader = new BufferedReader(new FileReader(file)); + + while (bufferedReader.ready()) { + + /* Read the line */ + firstLine = bufferedReader.readLine(); + } + + LOGGER.info("ModuleTest::readFirstLine() : firstLine : " + firstLine); + + } catch (final IOException e) { + LOGGER.error("ModuleTest::readFirstLine()", e); + } finally { + + if (bufferedReader != null) { + try { + bufferedReader.close(); + } catch (final IOException e) { + LOGGER.error("ModuleTest::readFirstLine()", e); + } + } + } + + return firstLine; + } +} diff --git a/monad/pom.xml b/monad/pom.xml index 7f128272d..316c4ff9b 100644 --- a/monad/pom.xml +++ b/monad/pom.xml @@ -2,7 +2,7 @@ org.objenesis diff --git a/naked-objects/dom/src/main/java/domainapp/dom/app/homepage/HomePageService.java b/naked-objects/dom/src/main/java/domainapp/dom/app/homepage/HomePageService.java index 162cd3bb4..30469ba8e 100644 --- a/naked-objects/dom/src/main/java/domainapp/dom/app/homepage/HomePageService.java +++ b/naked-objects/dom/src/main/java/domainapp/dom/app/homepage/HomePageService.java @@ -4,9 +4,9 @@ * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. You may obtain a * copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under @@ -21,6 +21,10 @@ import org.apache.isis.applib.annotation.HomePage; import org.apache.isis.applib.annotation.NatureOfService; import org.apache.isis.applib.annotation.SemanticsOf; +/** + * HomePage Domain Service + * @see HomePageViewModel linked view to HomePage + */ @DomainService(nature = NatureOfService.VIEW_CONTRIBUTIONS_ONLY) public class HomePageService { diff --git a/naked-objects/dom/src/main/java/domainapp/dom/app/homepage/HomePageViewModel.java b/naked-objects/dom/src/main/java/domainapp/dom/app/homepage/HomePageViewModel.java index f367a39fd..dfb499f0f 100644 --- a/naked-objects/dom/src/main/java/domainapp/dom/app/homepage/HomePageViewModel.java +++ b/naked-objects/dom/src/main/java/domainapp/dom/app/homepage/HomePageViewModel.java @@ -4,9 +4,9 @@ * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. You may obtain a * copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under @@ -21,6 +21,11 @@ import org.apache.isis.applib.annotation.ViewModel; import domainapp.dom.modules.simple.SimpleObject; import domainapp.dom.modules.simple.SimpleObjects; +/** + * Model linked to the HomePage + * The underlying layout is specified by json + * @see HomePageService - Service Linked to the HomePage + */ @ViewModel public class HomePageViewModel { diff --git a/naked-objects/dom/src/main/java/domainapp/dom/modules/simple/SimpleObject.java b/naked-objects/dom/src/main/java/domainapp/dom/modules/simple/SimpleObject.java index bbbd54b00..c7972ab84 100644 --- a/naked-objects/dom/src/main/java/domainapp/dom/modules/simple/SimpleObject.java +++ b/naked-objects/dom/src/main/java/domainapp/dom/modules/simple/SimpleObject.java @@ -4,9 +4,9 @@ * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. You may obtain a * copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under @@ -33,6 +33,9 @@ import org.apache.isis.applib.services.eventbus.ActionDomainEvent; import org.apache.isis.applib.services.i18n.TranslatableString; import org.apache.isis.applib.util.ObjectContracts; +/** + * Definition of a Simple Object + */ @javax.jdo.annotations.PersistenceCapable(identityType = IdentityType.DATASTORE, schema = "simple", table = "SimpleObject") @javax.jdo.annotations.DatastoreIdentity( @@ -53,7 +56,7 @@ public class SimpleObject implements Comparable { // region > name (property) private String name; - + // region > identificatiom public TranslatableString title() { return TranslatableString.tr("Object: {name}", "name", getName()); @@ -74,6 +77,9 @@ public class SimpleObject implements Comparable { // region > updateName (action) + /** + * Event used to update the Name in the Domain + */ public static class UpdateNameDomainEvent extends ActionDomainEvent { public UpdateNameDomainEvent(final SimpleObject source, final Identifier identifier, final Object... arguments) { diff --git a/naked-objects/dom/src/main/java/domainapp/dom/modules/simple/SimpleObjects.java b/naked-objects/dom/src/main/java/domainapp/dom/modules/simple/SimpleObjects.java index 5ebad0159..249fbb1f0 100644 --- a/naked-objects/dom/src/main/java/domainapp/dom/modules/simple/SimpleObjects.java +++ b/naked-objects/dom/src/main/java/domainapp/dom/modules/simple/SimpleObjects.java @@ -4,9 +4,9 @@ * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. You may obtain a * copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under @@ -30,6 +30,9 @@ import org.apache.isis.applib.query.QueryDefault; import org.apache.isis.applib.services.eventbus.ActionDomainEvent; import org.apache.isis.applib.services.i18n.TranslatableString; +/** + * Domain Service for Simple Objects + */ @DomainService(repositoryFor = SimpleObject.class) @DomainServiceLayout(menuOrder = "10") public class SimpleObjects { @@ -69,6 +72,9 @@ public class SimpleObjects { // endregion + /** + * Create Domain Event on SimpleObjects + */ // region > create (action) public static class CreateDomainEvent extends ActionDomainEvent { public CreateDomainEvent(final SimpleObjects source, final Identifier identifier, diff --git a/naked-objects/dom/src/test/java/domainapp/dom/modules/simple/SimpleObjectTest.java b/naked-objects/dom/src/test/java/domainapp/dom/modules/simple/SimpleObjectTest.java index fc62239c2..d80d0786a 100644 --- a/naked-objects/dom/src/test/java/domainapp/dom/modules/simple/SimpleObjectTest.java +++ b/naked-objects/dom/src/test/java/domainapp/dom/modules/simple/SimpleObjectTest.java @@ -14,11 +14,14 @@ */ package domainapp.dom.modules.simple; +import static org.assertj.core.api.Assertions.assertThat; + import org.junit.Before; import org.junit.Test; -import static org.assertj.core.api.Assertions.assertThat; - +/** + * Test for SimpleObject + */ public class SimpleObjectTest { SimpleObject simpleObject; @@ -28,6 +31,9 @@ public class SimpleObjectTest { simpleObject = new SimpleObject(); } + /** + * Test for Names for SimpleObjects + */ public static class Name extends SimpleObjectTest { @Test diff --git a/naked-objects/dom/src/test/java/domainapp/dom/modules/simple/SimpleObjectsTest.java b/naked-objects/dom/src/test/java/domainapp/dom/modules/simple/SimpleObjectsTest.java index 47cad61b8..91fe3d715 100644 --- a/naked-objects/dom/src/test/java/domainapp/dom/modules/simple/SimpleObjectsTest.java +++ b/naked-objects/dom/src/test/java/domainapp/dom/modules/simple/SimpleObjectsTest.java @@ -14,10 +14,13 @@ */ package domainapp.dom.modules.simple; -import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; import com.google.common.collect.Lists; - +import java.util.List; +import org.apache.isis.applib.DomainObjectContainer; +import org.apache.isis.core.unittestsupport.jmocking.JUnitRuleMockery2; +import org.apache.isis.core.unittestsupport.jmocking.JUnitRuleMockery2.Mode; import org.jmock.Expectations; import org.jmock.Sequence; import org.jmock.auto.Mock; @@ -25,12 +28,9 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.apache.isis.applib.DomainObjectContainer; -import org.apache.isis.core.unittestsupport.jmocking.JUnitRuleMockery2; -import org.apache.isis.core.unittestsupport.jmocking.JUnitRuleMockery2.Mode; - -import static org.assertj.core.api.Assertions.assertThat; - +/** + * Test for SimpleObjects + */ public class SimpleObjectsTest { @Rule @@ -47,6 +47,9 @@ public class SimpleObjectsTest { simpleObjects.container = mockContainer; } + /** + * Test Creation of Simple Objects + */ public static class Create extends SimpleObjectsTest { @Test @@ -77,6 +80,9 @@ public class SimpleObjectsTest { } + /** + * Test Listing of Simple Objects + */ public static class ListAll extends SimpleObjectsTest { @Test diff --git a/naked-objects/fixture/pom.xml b/naked-objects/fixture/pom.xml index 46b281468..bf3cc4de8 100644 --- a/naked-objects/fixture/pom.xml +++ b/naked-objects/fixture/pom.xml @@ -16,7 +16,7 @@ com.iluwatar naked-objects - 1.13.0-SNAPSHOT + 1.19.0-SNAPSHOT naked-objects-fixture diff --git a/naked-objects/fixture/src/main/java/domainapp/fixture/modules/simple/SimpleObjectCreate.java b/naked-objects/fixture/src/main/java/domainapp/fixture/modules/simple/SimpleObjectCreate.java index f0617fea2..58b656a98 100644 --- a/naked-objects/fixture/src/main/java/domainapp/fixture/modules/simple/SimpleObjectCreate.java +++ b/naked-objects/fixture/src/main/java/domainapp/fixture/modules/simple/SimpleObjectCreate.java @@ -4,9 +4,9 @@ * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. You may obtain a * copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under @@ -20,6 +20,9 @@ import org.apache.isis.applib.fixturescripts.FixtureScript; import domainapp.dom.modules.simple.SimpleObject; import domainapp.dom.modules.simple.SimpleObjects; +/** + * Fixture to create a simple object + */ public class SimpleObjectCreate extends FixtureScript { // endregion @@ -45,7 +48,7 @@ public class SimpleObjectCreate extends FixtureScript { this.name = name; return this; } - + /** * The created simple object (output). */ @@ -65,5 +68,5 @@ public class SimpleObjectCreate extends FixtureScript { // also make available to UI ec.addResult(this, simpleObject); } - + } diff --git a/naked-objects/fixture/src/main/java/domainapp/fixture/modules/simple/SimpleObjectsTearDown.java b/naked-objects/fixture/src/main/java/domainapp/fixture/modules/simple/SimpleObjectsTearDown.java index 7000bf4c0..c0319d953 100644 --- a/naked-objects/fixture/src/main/java/domainapp/fixture/modules/simple/SimpleObjectsTearDown.java +++ b/naked-objects/fixture/src/main/java/domainapp/fixture/modules/simple/SimpleObjectsTearDown.java @@ -4,9 +4,9 @@ * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. You may obtain a * copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under @@ -18,6 +18,9 @@ package domainapp.fixture.modules.simple; import org.apache.isis.applib.fixturescripts.FixtureScript; import org.apache.isis.applib.services.jdosupport.IsisJdoSupport; +/** + * TearDown/Cleanup for SimpleObjects + */ public class SimpleObjectsTearDown extends FixtureScript { @javax.inject.Inject @@ -27,5 +30,5 @@ public class SimpleObjectsTearDown extends FixtureScript { protected void execute(ExecutionContext executionContext) { isisJdoSupport.executeUpdate("delete from \"simple\".\"SimpleObject\""); } - + } diff --git a/naked-objects/fixture/src/main/java/domainapp/fixture/scenarios/RecreateSimpleObjects.java b/naked-objects/fixture/src/main/java/domainapp/fixture/scenarios/RecreateSimpleObjects.java index 62ad0405a..be891158a 100644 --- a/naked-objects/fixture/src/main/java/domainapp/fixture/scenarios/RecreateSimpleObjects.java +++ b/naked-objects/fixture/src/main/java/domainapp/fixture/scenarios/RecreateSimpleObjects.java @@ -4,9 +4,9 @@ * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. You may obtain a * copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under @@ -27,6 +27,10 @@ import domainapp.dom.modules.simple.SimpleObject; import domainapp.fixture.modules.simple.SimpleObjectCreate; import domainapp.fixture.modules.simple.SimpleObjectsTearDown; + +/** + * Create a bunch of simple Objects + */ public class RecreateSimpleObjects extends FixtureScript { public final List names = Collections.unmodifiableList(Arrays.asList("Foo", "Bar", "Baz", @@ -43,7 +47,7 @@ public class RecreateSimpleObjects extends FixtureScript { public RecreateSimpleObjects() { withDiscoverability(Discoverability.DISCOVERABLE); } - + /** * The number of objects to create, up to 10; optional, defaults to 3. */ @@ -55,7 +59,7 @@ public class RecreateSimpleObjects extends FixtureScript { this.number = number; return this; } - + /** * The simpleobjects created by this fixture (output). */ diff --git a/naked-objects/integtests/logging.properties b/naked-objects/integtests/logging.properties deleted file mode 100644 index b55249569..000000000 --- a/naked-objects/integtests/logging.properties +++ /dev/null @@ -1,111 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - - -# -# Isis uses log4j is used to provide system logging -# -log4j.rootCategory=INFO, Console - -# The console appender -log4j.appender.Console=org.apache.log4j.ConsoleAppender -log4j.appender.Console.target=System.out -log4j.appender.Console.layout=org.apache.log4j.PatternLayout -log4j.appender.Console.layout.ConversionPattern=%d{ABSOLUTE} [%-20c{1} %-10t %-5p] %m%n - -log4j.appender.File=org.apache.log4j.RollingFileAppender -log4j.appender.File.file=isis.log -log4j.appender.File.append=false -log4j.appender.File.layout=org.apache.log4j.PatternLayout -log4j.appender.File.layout.ConversionPattern=%d [%-20c{1} %-10t %-5p] %m%n - -log4j.appender.translations-po=org.apache.log4j.FileAppender -log4j.appender.translations-po.File=./translations.pot -log4j.appender.translations-po.Append=false -log4j.appender.translations-po.layout=org.apache.log4j.PatternLayout -log4j.appender.translations-po.layout.ConversionPattern=%m%n - -! turn on the internal log4j debugging flag so we can see what it is doing -#log4j.debug=true - - -# DataNucleus -# the first two log the DML and DDL (if set to DEBUG) -log4j.logger.DataNucleus.Datastore.Native=WARN, Console -log4j.logger.DataNucleus.Datastore.Schema=DEBUG, Console -# the remainder can probably be left to WARN -log4j.logger.DataNucleus.Persistence=WARN, Console -log4j.logger.DataNucleus.Transaction=WARN, Console -log4j.logger.DataNucleus.Connection=WARN, Console -log4j.logger.DataNucleus.Query=WARN, Console -log4j.logger.DataNucleus.Cache=WARN, Console -log4j.logger.DataNucleus.MetaData=WARN, Console -log4j.logger.DataNucleus.Datastore=WARN, Console -log4j.logger.DataNucleus.Datastore.Persist=WARN, Console -log4j.logger.DataNucleus.Datastore.Retrieve=WARN, Console -log4j.logger.DataNucleus.General=WARN, Console -log4j.logger.DataNucleus.Lifecycle=WARN, Console -log4j.logger.DataNucleus.ValueGeneration=WARN, Console -log4j.logger.DataNucleus.Enhancer=WARN, Console -log4j.logger.DataNucleus.SchemaTool=ERROR, Console -log4j.logger.DataNucleus.JDO=WARN, Console -log4j.logger.DataNucleus.JPA=ERROR, Console -log4j.logger.DataNucleus.JCA=WARN, Console -log4j.logger.DataNucleus.IDE=ERROR, Console - -log4j.additivity.DataNucleus.Datastore.Native=false -log4j.additivity.DataNucleus.Datastore.Schema=false -log4j.additivity.DataNucleus.Datastore.Persistence=false -log4j.additivity.DataNucleus.Datastore.Transaction=false -log4j.additivity.DataNucleus.Datastore.Connection=false -log4j.additivity.DataNucleus.Datastore.Query=false -log4j.additivity.DataNucleus.Datastore.Cache=false -log4j.additivity.DataNucleus.Datastore.MetaData=false -log4j.additivity.DataNucleus.Datastore.Datastore=false -log4j.additivity.DataNucleus.Datastore.Datastore.Persist=false -log4j.additivity.DataNucleus.Datastore.Datastore.Retrieve=false -log4j.additivity.DataNucleus.Datastore.General=false -log4j.additivity.DataNucleus.Datastore.Lifecycle=false -log4j.additivity.DataNucleus.Datastore.ValueGeneration=false -log4j.additivity.DataNucleus.Datastore.Enhancer=false -log4j.additivity.DataNucleus.Datastore.SchemaTool=false -log4j.additivity.DataNucleus.Datastore.JDO=false -log4j.additivity.DataNucleus.Datastore.JPA=false -log4j.additivity.DataNucleus.Datastore.JCA=false -log4j.additivity.DataNucleus.Datastore.IDE=false - - - - -# if using log4jdbc-remix as JDBC driver -#log4j.logger.jdbc.sqlonly=DEBUG, sql, Console -#log4j.additivity.jdbc.sqlonly=false -#log4j.logger.jdbc.resultsettable=DEBUG, jdbc, Console -#log4j.additivity.jdbc.resultsettable=false - -#log4j.logger.jdbc.audit=WARN,jdbc, Console -#log4j.additivity.jdbc.audit=false -#log4j.logger.jdbc.resultset=WARN,jdbc -#log4j.additivity.jdbc.resultset=false -#log4j.logger.jdbc.sqltiming=WARN,sqltiming -#log4j.additivity.jdbc.sqltiming=false -#log4j.logger.jdbc.connection=FATAL,connection -#log4j.additivity.jdbc.connection=false - - -log4j.logger.org.apache.isis.core.runtime.services.i18n.po.PoWriter=INFO,translations-po -log4j.additivity.org.apache.isis.core.runtime.services.i18n.po.PotWriter=false diff --git a/naked-objects/integtests/pom.xml b/naked-objects/integtests/pom.xml index dccaf64a3..8d2cf3b55 100644 --- a/naked-objects/integtests/pom.xml +++ b/naked-objects/integtests/pom.xml @@ -16,7 +16,7 @@ com.iluwatar naked-objects - 1.13.0-SNAPSHOT + 1.19.0-SNAPSHOT naked-objects-integtests @@ -79,6 +79,12 @@ test + + org.junit.vintage + junit-vintage-engine + test + + org.hsqldb hsqldb diff --git a/naked-objects/integtests/src/test/java/domainapp/integtests/bootstrap/SimpleAppSystemInitializer.java b/naked-objects/integtests/src/test/java/domainapp/integtests/bootstrap/SimpleAppSystemInitializer.java index b7c76d0ed..3ac5a1d75 100644 --- a/naked-objects/integtests/src/test/java/domainapp/integtests/bootstrap/SimpleAppSystemInitializer.java +++ b/naked-objects/integtests/src/test/java/domainapp/integtests/bootstrap/SimpleAppSystemInitializer.java @@ -4,9 +4,9 @@ * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. You may obtain a * copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under @@ -19,6 +19,9 @@ import org.apache.isis.core.integtestsupport.IsisSystemForTest; import org.apache.isis.objectstore.jdo.datanucleus.DataNucleusPersistenceMechanismInstaller; import org.apache.isis.objectstore.jdo.datanucleus.IsisConfigurationForJdoIntegTests; +/** + * Initializer for the Simple App + */ public final class SimpleAppSystemInitializer { private SimpleAppSystemInitializer() { diff --git a/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/BootstrappingGlue.java b/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/BootstrappingGlue.java index 190e1f5bb..e41399fdd 100644 --- a/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/BootstrappingGlue.java +++ b/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/BootstrappingGlue.java @@ -21,6 +21,9 @@ import cucumber.api.java.After; import cucumber.api.java.Before; import domainapp.integtests.bootstrap.SimpleAppSystemInitializer; +/** + * BootStrapping IntegrationTesting Before and After Steps + */ public class BootstrappingGlue extends CukeGlueAbstract { @Before(value = {"@integration"}, order = 100) diff --git a/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/CatalogOfFixturesGlue.java b/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/CatalogOfFixturesGlue.java index 2fcb7cca7..7a75a0381 100644 --- a/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/CatalogOfFixturesGlue.java +++ b/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/CatalogOfFixturesGlue.java @@ -19,6 +19,9 @@ import org.apache.isis.core.specsupport.specs.CukeGlueAbstract; import cucumber.api.java.Before; import domainapp.fixture.scenarios.RecreateSimpleObjects; +/** + * Test Execution to append a fixture of SimpleObjects + */ public class CatalogOfFixturesGlue extends CukeGlueAbstract { @Before(value = {"@integration", "@SimpleObjectsFixture"}, order = 20000) diff --git a/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/modules/simple/SimpleObjectGlue.java b/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/modules/simple/SimpleObjectGlue.java index 2ea375b4a..b7af9f052 100644 --- a/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/modules/simple/SimpleObjectGlue.java +++ b/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/modules/simple/SimpleObjectGlue.java @@ -14,18 +14,20 @@ */ package domainapp.integtests.specglue.modules.simple; -import java.util.List; -import java.util.UUID; - -import org.apache.isis.core.specsupport.specs.CukeGlueAbstract; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; import cucumber.api.java.en.Given; import cucumber.api.java.en.When; import domainapp.dom.modules.simple.SimpleObject; import domainapp.dom.modules.simple.SimpleObjects; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; +import java.util.List; +import java.util.UUID; +import org.apache.isis.core.specsupport.specs.CukeGlueAbstract; +/** + * Test Simple Object Operations + */ public class SimpleObjectGlue extends CukeGlueAbstract { @Given("^there are.* (\\d+) simple objects$") diff --git a/naked-objects/integtests/src/test/java/domainapp/integtests/tests/SimpleAppIntegTest.java b/naked-objects/integtests/src/test/java/domainapp/integtests/tests/SimpleAppIntegTest.java index 7a7ad91b2..66deaeb84 100644 --- a/naked-objects/integtests/src/test/java/domainapp/integtests/tests/SimpleAppIntegTest.java +++ b/naked-objects/integtests/src/test/java/domainapp/integtests/tests/SimpleAppIntegTest.java @@ -25,6 +25,9 @@ import org.apache.isis.core.integtestsupport.scenarios.ScenarioExecutionForInteg import domainapp.integtests.bootstrap.SimpleAppSystemInitializer; +/** + * SimpleApp Integration Tests will implement this Abstract Class. + */ public abstract class SimpleAppIntegTest extends IntegrationTestAbstract { @BeforeClass @@ -35,5 +38,4 @@ public abstract class SimpleAppIntegTest extends IntegrationTestAbstract { // instantiating will install onto ThreadLocal new ScenarioExecutionForIntegration(); } - } diff --git a/naked-objects/integtests/src/test/java/domainapp/integtests/tests/modules/simple/SimpleObjectIntegTest.java b/naked-objects/integtests/src/test/java/domainapp/integtests/tests/modules/simple/SimpleObjectIntegTest.java index 872aff7a3..f2cbb1723 100644 --- a/naked-objects/integtests/src/test/java/domainapp/integtests/tests/modules/simple/SimpleObjectIntegTest.java +++ b/naked-objects/integtests/src/test/java/domainapp/integtests/tests/modules/simple/SimpleObjectIntegTest.java @@ -18,21 +18,22 @@ */ package domainapp.integtests.tests.modules.simple; -import javax.inject.Inject; - -import org.junit.Before; -import org.junit.Test; - -import org.apache.isis.applib.DomainObjectContainer; -import org.apache.isis.applib.fixturescripts.FixtureScripts; -import org.apache.isis.applib.services.wrapper.DisabledException; -import org.apache.isis.applib.services.wrapper.InvalidException; +import static org.assertj.core.api.Assertions.assertThat; import domainapp.dom.modules.simple.SimpleObject; import domainapp.fixture.scenarios.RecreateSimpleObjects; import domainapp.integtests.tests.SimpleAppIntegTest; -import static org.assertj.core.api.Assertions.assertThat; +import javax.inject.Inject; +import org.apache.isis.applib.DomainObjectContainer; +import org.apache.isis.applib.fixturescripts.FixtureScripts; +import org.apache.isis.applib.services.wrapper.DisabledException; +import org.apache.isis.applib.services.wrapper.InvalidException; +import org.junit.Before; +import org.junit.Test; +/** + * Test Fixtures with Simple Objects + */ public class SimpleObjectIntegTest extends SimpleAppIntegTest { @Inject @@ -54,6 +55,9 @@ public class SimpleObjectIntegTest extends SimpleAppIntegTest { simpleObjectWrapped = wrap(simpleObjectPojo); } + /** + * Test Object Name accessibility + */ public static class Name extends SimpleObjectIntegTest { @Test @@ -75,6 +79,9 @@ public class SimpleObjectIntegTest extends SimpleAppIntegTest { } } + /** + * Test Validation of SimpleObject Names + */ public static class UpdateName extends SimpleObjectIntegTest { @Test @@ -99,6 +106,9 @@ public class SimpleObjectIntegTest extends SimpleAppIntegTest { } } + /** + * Test ContainerTitle generation based on SimpleObject Name + */ public static class Title extends SimpleObjectIntegTest { @Inject @@ -117,4 +127,4 @@ public class SimpleObjectIntegTest extends SimpleAppIntegTest { assertThat(title).isEqualTo("Object: " + name); } } -} \ No newline at end of file +} diff --git a/naked-objects/integtests/src/test/java/domainapp/integtests/tests/modules/simple/SimpleObjectsIntegTest.java b/naked-objects/integtests/src/test/java/domainapp/integtests/tests/modules/simple/SimpleObjectsIntegTest.java index 332213542..d57454dc1 100644 --- a/naked-objects/integtests/src/test/java/domainapp/integtests/tests/modules/simple/SimpleObjectsIntegTest.java +++ b/naked-objects/integtests/src/test/java/domainapp/integtests/tests/modules/simple/SimpleObjectsIntegTest.java @@ -18,28 +18,27 @@ */ package domainapp.integtests.tests.modules.simple; -import java.sql.SQLIntegrityConstraintViolationException; -import java.util.List; - -import javax.inject.Inject; +import static org.assertj.core.api.Assertions.assertThat; import com.google.common.base.Throwables; - -import org.hamcrest.Description; -import org.hamcrest.Matcher; -import org.hamcrest.TypeSafeMatcher; -import org.junit.Test; - -import org.apache.isis.applib.fixturescripts.FixtureScript; -import org.apache.isis.applib.fixturescripts.FixtureScripts; - import domainapp.dom.modules.simple.SimpleObject; import domainapp.dom.modules.simple.SimpleObjects; import domainapp.fixture.modules.simple.SimpleObjectsTearDown; import domainapp.fixture.scenarios.RecreateSimpleObjects; import domainapp.integtests.tests.SimpleAppIntegTest; -import static org.assertj.core.api.Assertions.assertThat; +import java.sql.SQLIntegrityConstraintViolationException; +import java.util.List; +import javax.inject.Inject; +import org.apache.isis.applib.fixturescripts.FixtureScript; +import org.apache.isis.applib.fixturescripts.FixtureScripts; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; +import org.junit.Test; +/** + * Fixture Pattern Integration Test + */ public class SimpleObjectsIntegTest extends SimpleAppIntegTest { @Inject @@ -47,6 +46,9 @@ public class SimpleObjectsIntegTest extends SimpleAppIntegTest { @Inject SimpleObjects simpleObjects; + /** + * Test Listing of All Simple Objects + */ public static class ListAll extends SimpleObjectsIntegTest { @Test @@ -83,6 +85,10 @@ public class SimpleObjectsIntegTest extends SimpleAppIntegTest { } } + + /** + * Test Creation of Simple Objects + */ public static class Create extends SimpleObjectsIntegTest { @Test @@ -140,4 +146,4 @@ public class SimpleObjectsIntegTest extends SimpleAppIntegTest { } } -} \ No newline at end of file +} diff --git a/naked-objects/pom.xml b/naked-objects/pom.xml index b3e48dcb6..222a9d321 100644 --- a/naked-objects/pom.xml +++ b/naked-objects/pom.xml @@ -15,7 +15,7 @@ java-design-patterns com.iluwatar - 1.13.0-SNAPSHOT + 1.19.0-SNAPSHOT naked-objects @@ -350,17 +350,17 @@ ${project.groupId} naked-objects-dom - 1.13.0-SNAPSHOT + 1.19.0-SNAPSHOT ${project.groupId} naked-objects-fixture - 1.13.0-SNAPSHOT + 1.19.0-SNAPSHOT ${project.groupId} naked-objects-webapp - 1.13.0-SNAPSHOT + 1.19.0-SNAPSHOT @@ -387,4 +387,4 @@ integtests webapp - \ No newline at end of file + diff --git a/naked-objects/webapp/ide/intellij/launch/README.txt b/naked-objects/webapp/ide/intellij/launch/README.txt index d33eaceb4..6659454ef 100644 --- a/naked-objects/webapp/ide/intellij/launch/README.txt +++ b/naked-objects/webapp/ide/intellij/launch/README.txt @@ -1,6 +1,6 @@ ==== The MIT License - Copyright (c) 2014 Ilkka Seppälä + Copyright (c) 2014-2016 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 diff --git a/naked-objects/webapp/ide/intellij/launch/SimpleApp_PROTOTYPE.xml b/naked-objects/webapp/ide/intellij/launch/SimpleApp_PROTOTYPE.xml index e01c95512..9831f9a1f 100644 --- a/naked-objects/webapp/ide/intellij/launch/SimpleApp_PROTOTYPE.xml +++ b/naked-objects/webapp/ide/intellij/launch/SimpleApp_PROTOTYPE.xml @@ -1,7 +1,7 @@ %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n%n - -log4j.appender.sqltiming=org.apache.log4j.FileAppender -log4j.appender.sqltiming.File=./logs/sqltiming.log -log4j.appender.sqltiming.Append=false -log4j.appender.sqltiming.layout=org.apache.log4j.PatternLayout -log4j.appender.sqltiming.layout.ConversionPattern=-----> %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n%n - -log4j.appender.jdbc=org.apache.log4j.FileAppender -log4j.appender.jdbc.File=./logs/jdbc.log -log4j.appender.jdbc.Append=false -log4j.appender.jdbc.layout=org.apache.log4j.PatternLayout -log4j.appender.jdbc.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} %m%n - -log4j.appender.connection=org.apache.log4j.FileAppender -log4j.appender.connection.File=./logs/connection.log -log4j.appender.connection.Append=false -log4j.appender.connection.layout=org.apache.log4j.PatternLayout -log4j.appender.connection.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} %m%n - - - - -! turn on the internal log4j debugging flag so we can see what it is doing -#log4j.debug=true - - -# DataNucleus -# the first two log the DML and DDL (if set to DEBUG) -log4j.logger.DataNucleus.Datastore.Native=WARN, Console -log4j.logger.DataNucleus.Datastore.Schema=DEBUG, Console -# the remainder can probably be left to WARN -log4j.logger.DataNucleus.Persistence=WARN, Console -log4j.logger.DataNucleus.Transaction=WARN, Console -log4j.logger.DataNucleus.Connection=WARN, Console -log4j.logger.DataNucleus.Query=WARN, Console -log4j.logger.DataNucleus.Cache=WARN, Console -log4j.logger.DataNucleus.MetaData=WARN, Console -log4j.logger.DataNucleus.Datastore=WARN, Console -log4j.logger.DataNucleus.Datastore.Persist=WARN, Console -log4j.logger.DataNucleus.Datastore.Retrieve=WARN, Console -log4j.logger.DataNucleus.General=WARN, Console -log4j.logger.DataNucleus.Lifecycle=WARN, Console -log4j.logger.DataNucleus.ValueGeneration=WARN, Console -log4j.logger.DataNucleus.Enhancer=WARN, Console -log4j.logger.DataNucleus.SchemaTool=ERROR, Console -log4j.logger.DataNucleus.JDO=WARN, Console -log4j.logger.DataNucleus.JPA=ERROR, Console -log4j.logger.DataNucleus.JCA=WARN, Console -log4j.logger.DataNucleus.IDE=ERROR, Console - -log4j.additivity.DataNucleus.Datastore.Native=false -log4j.additivity.DataNucleus.Datastore.Schema=false -log4j.additivity.DataNucleus.Datastore.Persistence=false -log4j.additivity.DataNucleus.Datastore.Transaction=false -log4j.additivity.DataNucleus.Datastore.Connection=false -log4j.additivity.DataNucleus.Datastore.Query=false -log4j.additivity.DataNucleus.Datastore.Cache=false -log4j.additivity.DataNucleus.Datastore.MetaData=false -log4j.additivity.DataNucleus.Datastore.Datastore=false -log4j.additivity.DataNucleus.Datastore.Datastore.Persist=false -log4j.additivity.DataNucleus.Datastore.Datastore.Retrieve=false -log4j.additivity.DataNucleus.Datastore.General=false -log4j.additivity.DataNucleus.Datastore.Lifecycle=false -log4j.additivity.DataNucleus.Datastore.ValueGeneration=false -log4j.additivity.DataNucleus.Datastore.Enhancer=false -log4j.additivity.DataNucleus.Datastore.SchemaTool=false -log4j.additivity.DataNucleus.Datastore.JDO=false -log4j.additivity.DataNucleus.Datastore.JPA=false -log4j.additivity.DataNucleus.Datastore.JCA=false -log4j.additivity.DataNucleus.Datastore.IDE=false - - -# if using log4jdbc-remix as JDBC driver -#log4j.logger.jdbc.sqlonly=DEBUG, sql, Console -#log4j.additivity.jdbc.sqlonly=false -#log4j.logger.jdbc.resultsettable=DEBUG, jdbc, Console -#log4j.additivity.jdbc.resultsettable=false - -#log4j.logger.jdbc.audit=WARN,jdbc, Console -#log4j.additivity.jdbc.audit=false -#log4j.logger.jdbc.resultset=WARN,jdbc -#log4j.additivity.jdbc.resultset=false -#log4j.logger.jdbc.sqltiming=WARN,sqltiming -#log4j.additivity.jdbc.sqltiming=false -#log4j.logger.jdbc.connection=FATAL,connection -#log4j.additivity.jdbc.connection=false - - - -# track Isis/JDO lifecycle integration - -#log4j.logger.org.apache.isis.runtimes.dflt.objectstores.jdo.datanucleus.persistence.FrameworkSynchronizer=DEBUG, Console -#log4j.additivity.org.apache.isis.runtimes.dflt.objectstores.jdo.datanucleus.persistence.FrameworkSynchronizer=false - -#log4j.logger.org.apache.isis.objectstore.jdo.datanucleus.persistence.IsisLifecycleListener=DEBUG,Console -#log4j.additivity.org.apache.isis.objectstore.jdo.datanucleus.persistence.IsisLifecycleListener=false - - - - -# track Isis/Wicket lifecycle integration - -#log4j.logger.org.apache.isis.viewer.wicket.viewer.integration.wicket.WebRequestCycleForIsis=DEBUG, Console -#log4j.additivity.org.apache.isis.viewer.wicket.viewer.integration.wicket.WebRequestCycleForIsis=false - -#log4j.logger.org.apache.isis.viewer.wicket.viewer.integration.isis.IsisContextForWicket=INFO,Console -#log4j.additivity.org.apache.isis.viewer.wicket.viewer.integration.isis.IsisContextForWicket=false - - - - -# quieten some of the noisier classes in Isis' bootstrapping -log4j.logger.org.apache.isis.core.metamodel.specloader.specimpl.FacetedMethodsBuilder=WARN,Console -log4j.additivity.org.apache.isis.core.metamodel.specloader.specimpl.FacetedMethodsBuilder=false - -log4j.logger.org.apache.isis.core.metamodel.specloader.ServiceInitializer=WARN,Console -log4j.additivity.org.apache.isis.core.metamodel.specloader.ServiceInitializer=false - -log4j.logger.org.apache.isis.core.runtime.services.ServicesInstallerFromConfiguration=WARN,Console -log4j.additivity.org.apache.isis.core.runtime.services.ServicesInstallerFromConfiguration=false - -log4j.logger.org.apache.isis.core.commons.config.IsisConfigurationDefault=WARN,Console -log4j.additivity.org.apache.isis.core.commons.config.IsisConfigurationDefault=false - -log4j.logger.org.apache.isis.core.runtime.installers.InstallerLookupDefault=WARN,Console -log4j.additivity.org.apache.isis.core.runtime.installers.InstallerLookupDefault=false - - -# quieten Shiro -log4j.logger.org.apache.shiro.realm.AuthorizingRealm=WARN,Console -log4j.additivity.log4j.logger.org.apache.shiro.realm.AuthorizingRealm=false - - -# Application-specific logging -log4j.logger.dom.simple.SimpleObject=DEBUG, Stderr -log4j.additivity.dom.simple.SimpleObject=false \ No newline at end of file diff --git a/naked-objects/webapp/src/main/webapp/about/index.html b/naked-objects/webapp/src/main/webapp/about/index.html index bfcc52017..80180f1cd 100644 --- a/naked-objects/webapp/src/main/webapp/about/index.html +++ b/naked-objects/webapp/src/main/webapp/about/index.html @@ -1,7 +1,7 @@ + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.19.0-SNAPSHOT + + object-mother + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.mockito + mockito-core + test + + + \ No newline at end of file diff --git a/object-mother/src/main/java/com/iluwatar/objectmother/King.java b/object-mother/src/main/java/com/iluwatar/objectmother/King.java new file mode 100644 index 000000000..b1b5f3610 --- /dev/null +++ b/object-mother/src/main/java/com/iluwatar/objectmother/King.java @@ -0,0 +1,69 @@ +/** + * The MIT License + * Copyright (c) 2016 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.objectmother; + +/** + * Defines all attributes and behaviour related to the King + */ +public class King implements Royalty { + boolean isDrunk = false; + boolean isHappy = false; + + @Override + public void makeDrunk() { + isDrunk = true; + } + + @Override + public void makeSober() { + isDrunk = false; + } + + @Override + public void makeHappy() { + isHappy = true; + } + + @Override + public void makeUnhappy() { + isHappy = false; + } + + public boolean isHappy() { + return isHappy; + } + + /** + * Method to flirt to a queen. + * @param queen Queen which should be flirted. + */ + public void flirt(Queen queen) { + boolean flirtStatus = queen.getFlirted(this); + if (flirtStatus == false) { + this.makeUnhappy(); + } else { + this.makeHappy(); + } + + } +} diff --git a/object-mother/src/main/java/com/iluwatar/objectmother/Queen.java b/object-mother/src/main/java/com/iluwatar/objectmother/Queen.java new file mode 100644 index 000000000..e7e488602 --- /dev/null +++ b/object-mother/src/main/java/com/iluwatar/objectmother/Queen.java @@ -0,0 +1,72 @@ +/** + * The MIT License + * Copyright (c) 2016 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.objectmother; + +/** + * Defines all attributes and behaviour related to the Queen + */ +public class Queen implements Royalty { + private boolean isDrunk = false; + private boolean isHappy = false; + private boolean isFlirty = false; + + @Override + public void makeDrunk() { + isDrunk = true; + } + + @Override + public void makeSober() { + isDrunk = false; + } + + @Override + public void makeHappy() { + isHappy = true; + } + + @Override + public void makeUnhappy() { + isHappy = false; + } + + public boolean isFlirty() { + return isFlirty; + } + + public void setFlirtiness(boolean flirtiness) { + this.isFlirty = flirtiness; + } + + /** + * Method which is called when the king is flirting to a queen. + * @param king King who initialized the flirt. + * @return A value which describes if the flirt was successful or not. + */ + public boolean getFlirted(King king) { + if (this.isFlirty && king.isHappy && !king.isDrunk) { + return true; + } + return false; + } +} diff --git a/object-mother/src/main/java/com/iluwatar/objectmother/Royalty.java b/object-mother/src/main/java/com/iluwatar/objectmother/Royalty.java new file mode 100644 index 000000000..42271a21d --- /dev/null +++ b/object-mother/src/main/java/com/iluwatar/objectmother/Royalty.java @@ -0,0 +1,36 @@ +/** + * The MIT License + * Copyright (c) 2016 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.objectmother; + +/** + * Interface contracting Royalty Behaviour + */ +public interface Royalty { + void makeDrunk(); + + void makeSober(); + + void makeHappy(); + + void makeUnhappy(); +} diff --git a/object-mother/src/main/java/com/iluwatar/objectmother/RoyaltyObjectMother.java b/object-mother/src/main/java/com/iluwatar/objectmother/RoyaltyObjectMother.java new file mode 100644 index 000000000..118253d56 --- /dev/null +++ b/object-mother/src/main/java/com/iluwatar/objectmother/RoyaltyObjectMother.java @@ -0,0 +1,86 @@ +/** + * The MIT License + * Copyright (c) 2016 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.objectmother; + +/** + * Object Mother Pattern generating Royalty Types + */ +public final class RoyaltyObjectMother { + + /** + * Method to create a sober and unhappy king. The standard paramters are set. + * @return An instance of {@link com.iluwatar.objectmother.King} with the standard properties. + */ + public static King createSoberUnhappyKing() { + return new King(); + } + + /** + * Method of the object mother to create a drunk king. + * @return A drunk {@link com.iluwatar.objectmother.King}. + */ + public static King createDrunkKing() { + King king = new King(); + king.makeDrunk(); + return king; + } + + /** + * Method to create a happy king. + * @return A happy {@link com.iluwatar.objectmother.King}. + */ + public static King createHappyKing() { + King king = new King(); + king.makeHappy(); + return king; + } + + /** + * Method to create a happy and drunk king. + * @return A drunk and happy {@link com.iluwatar.objectmother.King}. + */ + public static King createHappyDrunkKing() { + King king = new King(); + king.makeHappy(); + king.makeDrunk(); + return king; + } + + /** + * Method to create a flirty queen. + * @return A flirty {@link com.iluwatar.objectmother.Queen}. + */ + public static Queen createFlirtyQueen() { + Queen queen = new Queen(); + queen.setFlirtiness(true); + return queen; + } + + /** + * Method to create a not flirty queen. + * @return A not flirty {@link com.iluwatar.objectmother.Queen}. + */ + public static Queen createNotFlirtyQueen() { + return new Queen(); + } +} diff --git a/object-mother/src/test/java/com/iluwatar/objectmother/test/RoyaltyObjectMotherTest.java b/object-mother/src/test/java/com/iluwatar/objectmother/test/RoyaltyObjectMotherTest.java new file mode 100644 index 000000000..9bc29611c --- /dev/null +++ b/object-mother/src/test/java/com/iluwatar/objectmother/test/RoyaltyObjectMotherTest.java @@ -0,0 +1,91 @@ +/** + * The MIT License + * Copyright (c) 2016 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.objectmother.test; + +import com.iluwatar.objectmother.King; +import com.iluwatar.objectmother.Queen; +import com.iluwatar.objectmother.Royalty; +import com.iluwatar.objectmother.RoyaltyObjectMother; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test Generation of Royalty Types using the object-mother + */ +public class RoyaltyObjectMotherTest { + + @Test + public void unsuccessfulKingFlirt() { + King soberUnhappyKing = RoyaltyObjectMother.createSoberUnhappyKing(); + Queen flirtyQueen = RoyaltyObjectMother.createFlirtyQueen(); + soberUnhappyKing.flirt(flirtyQueen); + assertFalse(soberUnhappyKing.isHappy()); + } + + @Test + public void queenIsBlockingFlirtCauseDrunkKing() { + King drunkUnhappyKing = RoyaltyObjectMother.createDrunkKing(); + Queen notFlirtyQueen = RoyaltyObjectMother.createNotFlirtyQueen(); + drunkUnhappyKing.flirt(notFlirtyQueen); + assertFalse(drunkUnhappyKing.isHappy()); + } + + @Test + public void queenIsBlockingFlirt() { + King soberHappyKing = RoyaltyObjectMother.createHappyKing(); + Queen notFlirtyQueen = RoyaltyObjectMother.createNotFlirtyQueen(); + soberHappyKing.flirt(notFlirtyQueen); + assertFalse(soberHappyKing.isHappy()); + } + + @Test + public void successfullKingFlirt() { + King soberHappyKing = RoyaltyObjectMother.createHappyKing(); + Queen flirtyQueen = RoyaltyObjectMother.createFlirtyQueen(); + soberHappyKing.flirt(flirtyQueen); + assertTrue(soberHappyKing.isHappy()); + } + + @Test + public void testQueenType() { + Royalty flirtyQueen = RoyaltyObjectMother.createFlirtyQueen(); + Royalty notFlirtyQueen = RoyaltyObjectMother.createNotFlirtyQueen(); + assertEquals(flirtyQueen.getClass(), Queen.class); + assertEquals(notFlirtyQueen.getClass(), Queen.class); + } + + @Test + public void testKingType() { + Royalty drunkKing = RoyaltyObjectMother.createDrunkKing(); + Royalty happyDrunkKing = RoyaltyObjectMother.createHappyDrunkKing(); + Royalty happyKing = RoyaltyObjectMother.createHappyKing(); + Royalty soberUnhappyKing = RoyaltyObjectMother.createSoberUnhappyKing(); + assertEquals(drunkKing.getClass(), King.class); + assertEquals(happyDrunkKing.getClass(), King.class); + assertEquals(happyKing.getClass(), King.class); + assertEquals(soberUnhappyKing.getClass(), King.class); + } +} diff --git a/object-pool/pom.xml b/object-pool/pom.xml index 666242d1d..5e59bbfd9 100644 --- a/object-pool/pom.xml +++ b/object-pool/pom.xml @@ -2,7 +2,7 @@ + + + java-design-patterns + com.iluwatar + 1.19.0-SNAPSHOT + + 4.0.0 + + partial-response + + + junit + junit + + + org.junit.vintage + junit-vintage-engine + test + + + org.mockito + mockito-core + + + + + \ No newline at end of file diff --git a/partial-response/src/main/java/com/iluwatar/partialresponse/App.java b/partial-response/src/main/java/com/iluwatar/partialresponse/App.java new file mode 100644 index 000000000..2977b50a7 --- /dev/null +++ b/partial-response/src/main/java/com/iluwatar/partialresponse/App.java @@ -0,0 +1,74 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2017 Gopinath Langote + * + * 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.partialresponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; + +/** + * The Partial response pattern is a design pattern in which client specifies fields to fetch to serve. + * Here {@link App} is playing as client for {@link VideoResource} server. + * Client ask for specific fields information in video to server. + *

+ *

+ * {@link VideoResource} act as server to serve video information. + */ + +public class App { + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + /** + * Method as act client and request to server for video details. + * + * @param args program argument. + */ + public static void main(String[] args) throws Exception { + Map videos = new HashMap<>(); + videos.put(1, new Video(1, "Avatar", 178, "epic science fiction film", "James Cameron", "English")); + videos.put(2, new Video(2, "Godzilla Resurgence", 120, "Action & drama movie|", "Hideaki Anno", "Japanese")); + videos.put(3, new Video(3, "Interstellar", 169, "Adventure & Sci-Fi", "Christopher Nolan", "English")); + VideoResource videoResource = new VideoResource(new FieldJsonMapper(), videos); + + + LOGGER.info("Retrieving full response from server:-"); + LOGGER.info("Get all video information:"); + String videoDetails = videoResource.getDetails(1); + LOGGER.info(videoDetails); + + LOGGER.info("----------------------------------------------------------"); + + LOGGER.info("Retrieving partial response from server:-"); + LOGGER.info("Get video @id, @title, @director:"); + String specificFieldsDetails = videoResource.getDetails(3, "id", "title", "director"); + LOGGER.info(specificFieldsDetails); + + LOGGER.info("Get video @id, @length:"); + String videoLength = videoResource.getDetails(3, "id", "length"); + LOGGER.info(videoLength); + } +} diff --git a/partial-response/src/main/java/com/iluwatar/partialresponse/FieldJsonMapper.java b/partial-response/src/main/java/com/iluwatar/partialresponse/FieldJsonMapper.java new file mode 100644 index 000000000..9283cfa69 --- /dev/null +++ b/partial-response/src/main/java/com/iluwatar/partialresponse/FieldJsonMapper.java @@ -0,0 +1,60 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2017 Gopinath Langote + * + * 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.partialresponse; + +import java.lang.reflect.Field; + +/** + * Map a video to json + */ +public class FieldJsonMapper { + + /** + * @param video object containing video information + * @param fields fields information to get + * @return json of required fields from video + */ + public String toJson(Video video, String[] fields) throws Exception { + StringBuilder json = new StringBuilder().append("{"); + + for (int i = 0, fieldsLength = fields.length; i < fieldsLength; i++) { + json.append(getString(video, Video.class.getDeclaredField(fields[i]))); + if (i != fieldsLength - 1) { + json.append(","); + } + } + json.append("}"); + return json.toString(); + } + + private String getString(Video video, Field declaredField) throws IllegalAccessException { + declaredField.setAccessible(true); + Object value = declaredField.get(video); + if (declaredField.get(video) instanceof Integer) { + return "\"" + declaredField.getName() + "\"" + ": " + value; + } + return "\"" + declaredField.getName() + "\"" + ": " + "\"" + value.toString() + "\""; + } +} diff --git a/partial-response/src/main/java/com/iluwatar/partialresponse/Video.java b/partial-response/src/main/java/com/iluwatar/partialresponse/Video.java new file mode 100644 index 000000000..e242965e3 --- /dev/null +++ b/partial-response/src/main/java/com/iluwatar/partialresponse/Video.java @@ -0,0 +1,70 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2017 Gopinath Langote + * + * 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.partialresponse; + +/** + * {@link Video} is a entity to serve from server.It contains all video related information.. + *

+ */ +public class Video { + private final Integer id; + private final String title; + private final Integer length; + private final String description; + private final String director; + private final String language; + + /** + * @param id video unique id + * @param title video title + * @param length video length in minutes + * @param description video description by publisher + * @param director video director name + * @param language video language {private, public} + */ + public Video(Integer id, String title, Integer length, String description, String director, String language) { + this.id = id; + this.title = title; + this.length = length; + this.description = description; + this.director = director; + this.language = language; + } + + /** + * @return json representaion of video + */ + @Override + public String toString() { + return "{" + + "\"id\": " + id + "," + + "\"title\": \"" + title + "\"," + + "\"length\": " + length + "," + + "\"description\": \"" + description + "\"," + + "\"director\": \"" + director + "\"," + + "\"language\": \"" + language + "\"," + + "}"; + } +} diff --git a/partial-response/src/main/java/com/iluwatar/partialresponse/VideoResource.java b/partial-response/src/main/java/com/iluwatar/partialresponse/VideoResource.java new file mode 100644 index 000000000..2bf7a73c1 --- /dev/null +++ b/partial-response/src/main/java/com/iluwatar/partialresponse/VideoResource.java @@ -0,0 +1,57 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2017 Gopinath Langote + * + * 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.partialresponse; + +import java.util.Map; + +/** + * The resource class which serves video information. + * This class act as server in the demo. Which has all video details. + */ +public class VideoResource { + private FieldJsonMapper fieldJsonMapper; + private Map videos; + + /** + * @param fieldJsonMapper map object to json. + * @param videos initialize resource with existing videos. Act as database. + */ + public VideoResource(FieldJsonMapper fieldJsonMapper, Map videos) { + this.fieldJsonMapper = fieldJsonMapper; + this.videos = videos; + } + + /** + * @param id video id + * @param fields fields to get information about + * @return full response if no fields specified else partial response for given field. + */ + public String getDetails(Integer id, String... fields) throws Exception { + if (fields.length == 0) { + return videos.get(id).toString(); + } + return fieldJsonMapper.toJson(videos.get(id), fields); + } +} diff --git a/partial-response/src/test/java/com/iluwatar/partialresponse/AppTest.java b/partial-response/src/test/java/com/iluwatar/partialresponse/AppTest.java new file mode 100644 index 000000000..2ac34dd0d --- /dev/null +++ b/partial-response/src/test/java/com/iluwatar/partialresponse/AppTest.java @@ -0,0 +1,40 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2017 Gopinath Langote + * + * 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.partialresponse; + +import org.junit.Test; + +/** + * Application test + */ +public class AppTest { + + @Test + public void main() throws Exception { + String[] args = {}; + App.main(args); + } + +} \ No newline at end of file diff --git a/partial-response/src/test/java/com/iluwatar/partialresponse/FieldJsonMapperTest.java b/partial-response/src/test/java/com/iluwatar/partialresponse/FieldJsonMapperTest.java new file mode 100644 index 000000000..9bc078b57 --- /dev/null +++ b/partial-response/src/test/java/com/iluwatar/partialresponse/FieldJsonMapperTest.java @@ -0,0 +1,53 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2017 Gopinath Langote + * + * 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.partialresponse; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * tests {@link FieldJsonMapper}. + */ +public class FieldJsonMapperTest { + private FieldJsonMapper mapper; + + @Before + public void setUp() { + mapper = new FieldJsonMapper(); + } + + @Test + public void shouldReturnJsonForSpecifiedFieldsInVideo() throws Exception { + String[] fields = new String[]{"id", "title", "length"}; + Video video = new Video(2, "Godzilla Resurgence", 120, "Action & drama movie|", "Hideaki Anno", "Japanese"); + + String jsonFieldResponse = mapper.toJson(video, fields); + + String expectedDetails = "{\"id\": 2,\"title\": \"Godzilla Resurgence\",\"length\": 120}"; + assertEquals(expectedDetails, jsonFieldResponse); + } +} \ No newline at end of file diff --git a/partial-response/src/test/java/com/iluwatar/partialresponse/VideoResourceTest.java b/partial-response/src/test/java/com/iluwatar/partialresponse/VideoResourceTest.java new file mode 100644 index 000000000..0453e8ffd --- /dev/null +++ b/partial-response/src/test/java/com/iluwatar/partialresponse/VideoResourceTest.java @@ -0,0 +1,80 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2017 Gopinath Langote + * + * 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.partialresponse; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.when; + +/** + * tests {@link VideoResource}. + */ +@RunWith(MockitoJUnitRunner.class) +public class VideoResourceTest { + @Mock + private FieldJsonMapper fieldJsonMapper; + + private VideoResource resource; + + @Before + public void setUp() { + Map videos = new HashMap<>(); + videos.put(1, new Video(1, "Avatar", 178, "epic science fiction film", "James Cameron", "English")); + videos.put(2, new Video(2, "Godzilla Resurgence", 120, "Action & drama movie|", "Hideaki Anno", "Japanese")); + videos.put(3, new Video(3, "Interstellar", 169, "Adventure & Sci-Fi", "Christopher Nolan", "English")); + resource = new VideoResource(fieldJsonMapper, videos); + } + + @Test + public void shouldGiveVideoDetailsById() throws Exception { + String actualDetails = resource.getDetails(1); + + String expectedDetails = "{\"id\": 1,\"title\": \"Avatar\",\"length\": 178,\"description\": " + + "\"epic science fiction film\",\"director\": \"James Cameron\",\"language\": \"English\",}"; + assertEquals(expectedDetails, actualDetails); + } + + @Test + public void shouldGiveSpecifiedFieldsInformationOfVideo() throws Exception { + String[] fields = new String[]{"id", "title", "length"}; + + String expectedDetails = "{\"id\": 1,\"title\": \"Avatar\",\"length\": 178}"; + when(fieldJsonMapper.toJson(any(Video.class), eq(fields))).thenReturn(expectedDetails); + + String actualFieldsDetails = resource.getDetails(2, fields); + + assertEquals(expectedDetails, actualFieldsDetails); + } +} \ No newline at end of file diff --git a/poison-pill/pom.xml b/poison-pill/pom.xml index 9717f5e13..5b9d48126 100644 --- a/poison-pill/pom.xml +++ b/poison-pill/pom.xml @@ -2,7 +2,7 @@ - 4.0.0 - - com.iluwatar - java-design-patterns - 1.13.0-SNAPSHOT - pom - - 2014 - - - UTF-8 - 5.0.1.Final - 4.2.4.RELEASE - 1.3.3.RELEASE - 1.9.2.RELEASE - 1.4.190 - 4.12 - 3.0 - 4.0.0 - 0.7.2.201409121644 - 1.4 - 2.16.1 - 1.2.17 - 19.0 - 1.15.1 - 1.10.19 - 4.12.1 - 4.5.2 + 4.0.0 + com.iluwatar + java-design-patterns + 1.19.0-SNAPSHOT + pom + 2014 + + UTF-8 + 5.0.1.Final + 4.2.4.RELEASE + 1.3.3.RELEASE + 1.9.2.RELEASE + 1.4.190 + 4.12 + 5.0.2 + ${junit.version}.2 + 1.0.2 + 1.0.2 + 3.0 + 0.7.2.201409121644 + 1.4 + 2.16.1 + 19.0 + 1.10.19 + 4.5.2 2.22 - - - abstract-factory - builder - factory-method - prototype - singleton - adapter - bridge - composite - dao - data-mapper - decorator - facade - flyweight - proxy - chain - command - interpreter - iterator - mediator - memento - model-view-presenter - observer - state - strategy - template-method - visitor - double-checked-locking - servant - service-locator - null-object - event-aggregator - callback - execute-around - property - intercepting-filter - producer-consumer - poison-pill - reader-writer-lock - lazy-loading - service-layer - specification - tolerant-reader - model-view-controller - flux - double-dispatch - multiton - resource-acquisition-is-initialization - thread-pool - twin - private-class-data - object-pool - dependency-injection - naked-objects - front-controller - repository - async-method-invocation - monostate - step-builder - business-delegate - half-sync-half-async - layers - message-channel - fluentinterface - reactor - caching - publish-subscribe - delegation - event-driven-architecture - api-gateway - factory-kit - feature-toggle - value-object - monad - mute-idiom - mutex - semaphore - hexagonal - abstract-document - aggregator-microservices + 4.0 + 3.3.0 + 1.7.21 + 1.1.7 + + + abstract-factory + tls + builder + factory-method + prototype + singleton + adapter + bridge + composite + dao + data-mapper + decorator + facade + flyweight + proxy + chain + command + interpreter + iterator + mediator + memento + model-view-presenter + observer + state + strategy + template-method + visitor + double-checked-locking + servant + service-locator + null-object + event-aggregator + callback + execute-around + property + intercepting-filter + producer-consumer + poison-pill + reader-writer-lock + lazy-loading + service-layer + specification + tolerant-reader + model-view-controller + flux + double-dispatch + multiton + resource-acquisition-is-initialization + thread-pool + twin + private-class-data + object-pool + dependency-injection + naked-objects + front-controller + repository + async-method-invocation + monostate + step-builder + business-delegate + half-sync-half-async + layers + message-channel + fluentinterface + reactor + caching + publish-subscribe + delegation + event-driven-architecture + api-gateway + factory-kit + feature-toggle + value-object + module + monad + mute-idiom + mutex + semaphore + hexagonal + abstract-document + aggregator-microservices + promise page-object - + event-asynchronous + event-queue + queue-load-leveling + object-mother + data-bus + converter + guarded-suspension + balking + extension-objects + marker + cqrs + event-sourcing + data-transfer-object + throttling - - - - org.hibernate - hibernate-core - ${hibernate.version} - - - org.hibernate - hibernate-entitymanager - ${hibernate.version} - - - org.springframework - spring-test - ${spring.version} - - - org.springframework.boot - spring-boot-dependencies - ${spring-boot.version} - pom - import - - - org.springframework.data - spring-data-jpa - ${spring-data.version} - - - org.springframework - spring-webmvc - ${spring.version} - - - org.springframework.boot - spring-boot-starter-web - ${spring-boot.version} - - - org.apache.httpcomponents - httpclient - ${apache-httpcomponents.version} - - - com.h2database - h2 - ${h2.version} - - - commons-dbcp - commons-dbcp - ${commons-dbcp.version} - - - org.apache.camel - camel-core - ${camel.version} - - - org.apache.camel - camel-stream - ${camel.version} - - - junit - junit - ${junit.version} - test - - - org.mockito - mockito-core - ${mockito.version} - test - - - log4j - log4j - ${log4j.version} - - - com.google.guava - guava - ${guava.version} - - - com.github.stefanbirkner - system-rules - ${systemrules.version} - test - - - de.bechte.junit - junit-hierarchicalcontextrunner - ${hierarchical-junit-runner-version} - test - - - net.sourceforge.htmlunit - htmlunit - ${htmlunit.version} - test - - - + unit-of-work + partial-response + eip-wire-tap + eip-splitter + eip-aggregator + retry + dirty-flag + + + + jitpack.io + https://jitpack.io + + - - - - - - org.eclipse.m2e - lifecycle-mapping - 1.0.0 - - - - - - org.jacoco - - jacoco-maven-plugin - - - [0.6.2,) - - - prepare-agent - - - - - - - - - - - - + + + + org.hibernate + hibernate-core + ${hibernate.version} + + + org.hibernate + hibernate-entitymanager + ${hibernate.version} + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + org.springframework.data + spring-data-jpa + ${spring-data.version} + + + org.springframework + spring-webmvc + ${spring.version} + + + org.springframework.boot + spring-boot-starter-web + ${spring-boot.version} + + + org.apache.httpcomponents + httpclient + ${apache-httpcomponents.version} + + + com.h2database + h2 + ${h2.version} + + + commons-dbcp + commons-dbcp + ${commons-dbcp.version} + + + org.apache.camel + camel-core + ${camel.version} + + + org.apache.camel + camel-stream + ${camel.version} + + + org.junit.jupiter + junit-jupiter-api + ${junit-jupiter.version} + test + + + junit + junit + ${junit.version} + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit-jupiter.version} + test + + + org.junit.jupiter + junit-jupiter-params + ${junit-jupiter.version} + test + + + org.junit.jupiter + junit-jupiter-migrationsupport + ${junit-jupiter.version} + test + + + org.junit.vintage + junit-vintage-engine + ${junit-vintage.version} + test + + + com.github.sbrannen + spring-test-junit5 + ${sping-test-junit5.version} + test + + + org.mockito + mockito-core + ${mockito.version} + test + + + com.google.guava + guava + ${guava.version} + + + net.sourceforge.htmlunit + htmlunit + ${htmlunit.version} + test + + + com.google.inject + guice + ${guice.version} + + + org.mongodb + mongo-java-driver + ${mongo-java-driver.version} + + + - - - - org.apache.maven.plugins - maven-compiler-plugin - ${compiler.version} - - 1.8 - 1.8 - - - - org.eluder.coveralls - coveralls-maven-plugin - ${coveralls.version} - - jb6wYzxkVvjolD6qOWpzWdcWBzYk2fAmF - - - - org.jacoco - jacoco-maven-plugin - ${jacoco.version} - - - - - domainapp/dom/modules/simple/QSimpleObject.class - **com.steadystate* - - - - - prepare-agent - - prepare-agent - - - - + + + org.slf4j + slf4j-api + ${slf4j.version} + + + ch.qos.logback + logback-classic + ${logback.version} + + + ch.qos.logback + logback-core + ${logback.version} + + - - - org.apache.maven.plugins - maven-checkstyle-plugin - 2.17 - - - validate - - check - - validate - - checkstyle.xml - checkstyle-suppressions.xml - UTF-8 - true - true - true - - - - + + + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + org.jacoco + + jacoco-maven-plugin + + + [0.6.2,) + + + prepare-agent + + + + + + + + + + + + - - org.jacoco - jacoco-maven-plugin - 0.7.5.201505241946 - - - - prepare-agent - - - - report - prepare-package - - report - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.18.1 - - - org.apache.maven.surefire - surefire-junit47 - 2.18.1 - - - - -Xmx1024M ${argLine} - - + + - org.apache.maven.plugins - maven-pmd-plugin - 3.6 - - true - 5 - true - - - - - check - - - exclude-pmd.properties - - - + org.apache.maven.plugins + maven-compiler-plugin + ${compiler.version} + + 1.8 + 1.8 + + + + org.jacoco + jacoco-maven-plugin + ${jacoco.version} + + + prepare-agent + + prepare-agent + + + - - com.mycila - license-maven-plugin - 2.11 - -

com/mycila/maven/plugin/license/templates/MIT.txt
- - Ilkka Seppälä - - true - - - - install-format - install - - format - - - - - - + + + org.apache.maven.plugins + maven-checkstyle-plugin + 2.17 + + + validate + + check + + validate + + checkstyle.xml + checkstyle-suppressions.xml + UTF-8 + true + true + true + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.19.1 + + + org.junit.platform + junit-platform-surefire-provider + ${junit-platform.version} + + + + -Xmx1024M ${argLine} + + + + org.apache.maven.plugins + maven-pmd-plugin + 3.6 + + true + 5 + true + + + + + check + + + exclude-pmd.properties + + + + - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.6 - - - + + com.mycila + license-maven-plugin + 2.11 + +
com/mycila/maven/plugin/license/templates/MIT.txt
+ + Ilkka Seppälä + + true +
+ + + install-format + install + + format + + + +
+ + - + + + + org.apache.maven.plugins + maven-pmd-plugin + 3.6 + + + + + \ No newline at end of file diff --git a/private-class-data/pom.xml b/private-class-data/pom.xml index 20484245b..ddf9cef28 100644 --- a/private-class-data/pom.xml +++ b/private-class-data/pom.xml @@ -2,7 +2,7 @@ + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.19.0-SNAPSHOT + + promise + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.mockito + mockito-core + test + + + diff --git a/promise/src/main/java/com/iluwatar/promise/App.java b/promise/src/main/java/com/iluwatar/promise/App.java new file mode 100644 index 000000000..dd40ec15b --- /dev/null +++ b/promise/src/main/java/com/iluwatar/promise/App.java @@ -0,0 +1,181 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 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.promise; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * + * The Promise object is used for asynchronous computations. A Promise represents an operation + * that hasn't completed yet, but is expected in the future. + * + *

A Promise represents a proxy for a value not necessarily known when the promise is created. It + * allows you to associate dependent promises to an asynchronous action's eventual success value or + * failure reason. This lets asynchronous methods return values like synchronous methods: instead + * of the final value, the asynchronous method returns a promise of having a value at some point + * in the future. + * + *

Promises provide a few advantages over callback objects: + *

    + *
  • Functional composition and error handling + *
  • Prevents callback hell and provides callback aggregation + *
+ * + *

+ * In this application the usage of promise is demonstrated with two examples: + *

    + *
  • Count Lines: In this example a file is downloaded and its line count is calculated. + * The calculated line count is then consumed and printed on console. + *
  • Lowest Character Frequency: In this example a file is downloaded and its lowest frequency + * character is found and printed on console. This happens via a chain of promises, we start with + * a file download promise, then a promise of character frequency, then a promise of lowest frequency + * character which is finally consumed and result is printed on console. + *
+ * + * @see CompletableFuture + */ +public class App { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + private static final String DEFAULT_URL = "https://raw.githubusercontent.com/iluwatar/java-design-patterns/master/promise/README.md"; + private final ExecutorService executor; + private final CountDownLatch stopLatch; + + private App() { + executor = Executors.newFixedThreadPool(2); + stopLatch = new CountDownLatch(2); + } + + /** + * Program entry point + * @param args arguments + * @throws InterruptedException if main thread is interrupted. + * @throws ExecutionException if an execution error occurs. + */ + public static void main(String[] args) throws InterruptedException, ExecutionException { + App app = new App(); + try { + app.promiseUsage(); + } finally { + app.stop(); + } + } + + private void promiseUsage() { + calculateLineCount(); + + calculateLowestFrequencyChar(); + } + + /* + * Calculate the lowest frequency character and when that promise is fulfilled, + * consume the result in a Consumer + */ + private void calculateLowestFrequencyChar() { + lowestFrequencyChar() + .thenAccept( + charFrequency -> { + LOGGER.info("Char with lowest frequency is: {}", charFrequency); + taskCompleted(); + } + ); + } + + /* + * Calculate the line count and when that promise is fulfilled, consume the result + * in a Consumer + */ + private void calculateLineCount() { + countLines() + .thenAccept( + count -> { + LOGGER.info("Line count is: {}", count); + taskCompleted(); + } + ); + } + + /* + * Calculate the character frequency of a file and when that promise is fulfilled, + * then promise to apply function to calculate lowest character frequency. + */ + private Promise lowestFrequencyChar() { + return characterFrequency() + .thenApply(Utility::lowestFrequencyChar); + } + + /* + * Download the file at DEFAULT_URL and when that promise is fulfilled, + * then promise to apply function to calculate character frequency. + */ + private Promise> characterFrequency() { + return download(DEFAULT_URL) + .thenApply(Utility::characterFrequency); + } + + /* + * Download the file at DEFAULT_URL and when that promise is fulfilled, + * then promise to apply function to count lines in that file. + */ + private Promise countLines() { + return download(DEFAULT_URL) + .thenApply(Utility::countLines); + } + + /* + * Return a promise to provide the local absolute path of the file downloaded in background. + * This is an async method and does not wait until the file is downloaded. + */ + private Promise download(String urlString) { + Promise downloadPromise = new Promise() + .fulfillInAsync( + () -> { + return Utility.downloadFile(urlString); + }, executor) + .onError( + throwable -> { + throwable.printStackTrace(); + taskCompleted(); + } + ); + + return downloadPromise; + } + + private void stop() throws InterruptedException { + stopLatch.await(); + executor.shutdownNow(); + } + + private void taskCompleted() { + stopLatch.countDown(); + } +} diff --git a/promise/src/main/java/com/iluwatar/promise/Promise.java b/promise/src/main/java/com/iluwatar/promise/Promise.java new file mode 100644 index 000000000..6b321e196 --- /dev/null +++ b/promise/src/main/java/com/iluwatar/promise/Promise.java @@ -0,0 +1,193 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 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.promise; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * A Promise represents a proxy for a value not necessarily known when the promise is created. It + * allows you to associate dependent promises to an asynchronous action's eventual success value or + * failure reason. This lets asynchronous methods return values like synchronous methods: instead + * of the final value, the asynchronous method returns a promise of having a value at some point + * in the future. + * + * @param type of result. + */ +public class Promise extends PromiseSupport { + + private Runnable fulfillmentAction; + private Consumer exceptionHandler; + + /** + * Creates a promise that will be fulfilled in future. + */ + public Promise() { + } + + /** + * Fulfills the promise with the provided value. + * @param value the fulfilled value that can be accessed using {@link #get()}. + */ + @Override + public void fulfill(T value) { + super.fulfill(value); + postFulfillment(); + } + + /** + * Fulfills the promise with exception due to error in execution. + * @param exception the exception will be wrapped in {@link ExecutionException} + * when accessing the value using {@link #get()}. + */ + @Override + public void fulfillExceptionally(Exception exception) { + super.fulfillExceptionally(exception); + handleException(exception); + postFulfillment(); + } + + private void handleException(Exception exception) { + if (exceptionHandler == null) { + return; + } + exceptionHandler.accept(exception); + } + + private void postFulfillment() { + if (fulfillmentAction == null) { + return; + } + fulfillmentAction.run(); + } + + /** + * Executes the task using the executor in other thread and fulfills the promise returned + * once the task completes either successfully or with an exception. + * + * @param task the task that will provide the value to fulfill the promise. + * @param executor the executor in which the task should be run. + * @return a promise that represents the result of running the task provided. + */ + public Promise fulfillInAsync(final Callable task, Executor executor) { + executor.execute(() -> { + try { + fulfill(task.call()); + } catch (Exception ex) { + fulfillExceptionally(ex); + } + }); + return this; + } + + /** + * Returns a new promise that, when this promise is fulfilled normally, is fulfilled with + * result of this promise as argument to the action provided. + * @param action action to be executed. + * @return a new promise. + */ + public Promise thenAccept(Consumer action) { + Promise dest = new Promise<>(); + fulfillmentAction = new ConsumeAction(this, dest, action); + return dest; + } + + /** + * Set the exception handler on this promise. + * @param exceptionHandler a consumer that will handle the exception occurred while fulfilling + * the promise. + * @return this + */ + public Promise onError(Consumer exceptionHandler) { + this.exceptionHandler = exceptionHandler; + return this; + } + + /** + * Returns a new promise that, when this promise is fulfilled normally, is fulfilled with + * result of this promise as argument to the function provided. + * @param func function to be executed. + * @return a new promise. + */ + public Promise thenApply(Function func) { + Promise dest = new Promise<>(); + fulfillmentAction = new TransformAction(this, dest, func); + return dest; + } + + /** + * Accesses the value from source promise and calls the consumer, then fulfills the + * destination promise. + */ + private class ConsumeAction implements Runnable { + + private final Promise src; + private final Promise dest; + private final Consumer action; + + private ConsumeAction(Promise src, Promise dest, Consumer action) { + this.src = src; + this.dest = dest; + this.action = action; + } + + @Override + public void run() { + try { + action.accept(src.get()); + dest.fulfill(null); + } catch (Throwable throwable) { + dest.fulfillExceptionally((Exception) throwable.getCause()); + } + } + } + + /** + * Accesses the value from source promise, then fulfills the destination promise using the + * transformed value. The source value is transformed using the transformation function. + */ + private class TransformAction implements Runnable { + + private final Promise src; + private final Promise dest; + private final Function func; + + private TransformAction(Promise src, Promise dest, Function func) { + this.src = src; + this.dest = dest; + this.func = func; + } + + @Override + public void run() { + try { + dest.fulfill(func.apply(src.get())); + } catch (Throwable throwable) { + dest.fulfillExceptionally((Exception) throwable.getCause()); + } + } + } +} \ No newline at end of file diff --git a/promise/src/main/java/com/iluwatar/promise/PromiseSupport.java b/promise/src/main/java/com/iluwatar/promise/PromiseSupport.java new file mode 100644 index 000000000..ae90a927e --- /dev/null +++ b/promise/src/main/java/com/iluwatar/promise/PromiseSupport.java @@ -0,0 +1,118 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 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.promise; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A really simplified implementation of future that allows completing it successfully with a value + * or exceptionally with an exception. + */ +class PromiseSupport implements Future { + + private static final Logger LOGGER = LoggerFactory.getLogger(PromiseSupport.class); + + private static final int RUNNING = 1; + private static final int FAILED = 2; + private static final int COMPLETED = 3; + + private final Object lock; + + private volatile int state = RUNNING; + private T value; + private Exception exception; + + PromiseSupport() { + this.lock = new Object(); + } + + void fulfill(T value) { + this.value = value; + this.state = COMPLETED; + synchronized (lock) { + lock.notifyAll(); + } + } + + void fulfillExceptionally(Exception exception) { + this.exception = exception; + this.state = FAILED; + synchronized (lock) { + lock.notifyAll(); + } + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return false; + } + + @Override + public boolean isCancelled() { + return false; + } + + @Override + public boolean isDone() { + return state > RUNNING; + } + + @Override + public T get() throws InterruptedException, ExecutionException { + synchronized (lock) { + while (state == RUNNING) { + lock.wait(); + } + } + if (state == COMPLETED) { + return value; + } + throw new ExecutionException(exception); + } + + @Override + public T get(long timeout, TimeUnit unit) + throws ExecutionException, TimeoutException { + synchronized (lock) { + while (state == RUNNING) { + try { + lock.wait(unit.toMillis(timeout)); + } catch (InterruptedException e) { + LOGGER.warn("Interrupted!", e); + Thread.currentThread().interrupt(); + } + } + } + + if (state == COMPLETED) { + return value; + } + throw new ExecutionException(exception); + } +} \ No newline at end of file diff --git a/promise/src/main/java/com/iluwatar/promise/Utility.java b/promise/src/main/java/com/iluwatar/promise/Utility.java new file mode 100644 index 000000000..41e07be45 --- /dev/null +++ b/promise/src/main/java/com/iluwatar/promise/Utility.java @@ -0,0 +1,131 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 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.promise; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +/** + * Utility to perform various operations + */ +public class Utility { + + private static final Logger LOGGER = LoggerFactory.getLogger(Utility.class); + + /** + * Calculates character frequency of the file provided. + * @param fileLocation location of the file. + * @return a map of character to its frequency, an empty map if file does not exist. + */ + public static Map characterFrequency(String fileLocation) { + Map characterToFrequency = new HashMap<>(); + try (Reader reader = new FileReader(fileLocation); + BufferedReader bufferedReader = new BufferedReader(reader)) { + for (String line; (line = bufferedReader.readLine()) != null;) { + for (char c : line.toCharArray()) { + if (!characterToFrequency.containsKey(c)) { + characterToFrequency.put(c, 1); + } else { + characterToFrequency.put(c, characterToFrequency.get(c) + 1); + } + } + } + } catch (IOException ex) { + ex.printStackTrace(); + } + return characterToFrequency; + } + + /** + * @return the character with lowest frequency if it exists, {@code Optional.empty()} otherwise. + */ + public static Character lowestFrequencyChar(Map charFrequency) { + Character lowestFrequencyChar = null; + Iterator> iterator = charFrequency.entrySet().iterator(); + Entry entry = iterator.next(); + int minFrequency = entry.getValue(); + lowestFrequencyChar = entry.getKey(); + + while (iterator.hasNext()) { + entry = iterator.next(); + if (entry.getValue() < minFrequency) { + minFrequency = entry.getValue(); + lowestFrequencyChar = entry.getKey(); + } + } + + return lowestFrequencyChar; + } + + /** + * @return number of lines in the file at provided location. 0 if file does not exist. + */ + public static Integer countLines(String fileLocation) { + int lineCount = 0; + try (Reader reader = new FileReader(fileLocation); + BufferedReader bufferedReader = new BufferedReader(reader)) { + while (bufferedReader.readLine() != null) { + lineCount++; + } + } catch (IOException ex) { + ex.printStackTrace(); + } + return lineCount; + } + + /** + * Downloads the contents from the given urlString, and stores it in a temporary directory. + * @return the absolute path of the file downloaded. + */ + public static String downloadFile(String urlString) throws MalformedURLException, IOException { + LOGGER.info("Downloading contents from url: {}", urlString); + URL url = new URL(urlString); + File file = File.createTempFile("promise_pattern", null); + try (Reader reader = new InputStreamReader(url.openStream()); + BufferedReader bufferedReader = new BufferedReader(reader); + FileWriter writer = new FileWriter(file)) { + for (String line; (line = bufferedReader.readLine()) != null; ) { + writer.write(line); + writer.write("\n"); + } + LOGGER.info("File downloaded at: {}", file.getAbsolutePath()); + return file.getAbsolutePath(); + } catch (IOException ex) { + throw ex; + } + } +} diff --git a/promise/src/test/java/com/iluwatar/promise/AppTest.java b/promise/src/test/java/com/iluwatar/promise/AppTest.java new file mode 100644 index 000000000..eb8790a4a --- /dev/null +++ b/promise/src/test/java/com/iluwatar/promise/AppTest.java @@ -0,0 +1,39 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 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.promise; + +import org.junit.jupiter.api.Test; + +import java.util.concurrent.ExecutionException; + +/** + * + * Application test. + */ +public class AppTest { + + @Test + public void testApp() throws InterruptedException, ExecutionException { + App.main(null); + } +} diff --git a/promise/src/test/java/com/iluwatar/promise/PromiseTest.java b/promise/src/test/java/com/iluwatar/promise/PromiseTest.java new file mode 100644 index 000000000..68868bd1e --- /dev/null +++ b/promise/src/test/java/com/iluwatar/promise/PromiseTest.java @@ -0,0 +1,261 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 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.promise; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; +import java.util.function.Function; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +/** + * Tests Promise class. + */ +public class PromiseTest { + + private Executor executor; + private Promise promise; + + @BeforeEach + public void setUp() { + executor = Executors.newSingleThreadExecutor(); + promise = new Promise<>(); + } + + @Test + public void promiseIsFulfilledWithTheResultantValueOfExecutingTheTask() + throws InterruptedException, ExecutionException { + promise.fulfillInAsync(new NumberCrunchingTask(), executor); + + assertEquals(NumberCrunchingTask.CRUNCHED_NUMBER, promise.get()); + assertTrue(promise.isDone()); + assertFalse(promise.isCancelled()); + } + + @Test + public void promiseIsFulfilledWithAnExceptionIfTaskThrowsAnException() + throws InterruptedException, ExecutionException, TimeoutException { + testWaitingForeverForPromiseToBeFulfilled(); + testWaitingSomeTimeForPromiseToBeFulfilled(); + } + + private void testWaitingForeverForPromiseToBeFulfilled() + throws InterruptedException, TimeoutException { + Promise promise = new Promise<>(); + promise.fulfillInAsync(new Callable() { + + @Override + public Integer call() throws Exception { + throw new RuntimeException("Barf!"); + } + }, executor); + + try { + promise.get(); + fail("Fetching promise should result in exception if the task threw an exception"); + } catch (ExecutionException ex) { + assertTrue(promise.isDone()); + assertFalse(promise.isCancelled()); + } + + try { + promise.get(1000, TimeUnit.SECONDS); + fail("Fetching promise should result in exception if the task threw an exception"); + } catch (ExecutionException ex) { + assertTrue(promise.isDone()); + assertFalse(promise.isCancelled()); + } + } + + private void testWaitingSomeTimeForPromiseToBeFulfilled() + throws InterruptedException, TimeoutException { + Promise promise = new Promise<>(); + promise.fulfillInAsync(new Callable() { + + @Override + public Integer call() throws Exception { + throw new RuntimeException("Barf!"); + } + }, executor); + + try { + promise.get(1000, TimeUnit.SECONDS); + fail("Fetching promise should result in exception if the task threw an exception"); + } catch (ExecutionException ex) { + assertTrue(promise.isDone()); + assertFalse(promise.isCancelled()); + } + + try { + promise.get(); + fail("Fetching promise should result in exception if the task threw an exception"); + } catch (ExecutionException ex) { + assertTrue(promise.isDone()); + assertFalse(promise.isCancelled()); + } + + } + + @Test + public void dependentPromiseIsFulfilledAfterTheConsumerConsumesTheResultOfThisPromise() + throws InterruptedException, ExecutionException { + Promise dependentPromise = promise + .fulfillInAsync(new NumberCrunchingTask(), executor) + .thenAccept(value -> { + assertEquals(NumberCrunchingTask.CRUNCHED_NUMBER, value); + }); + + + dependentPromise.get(); + assertTrue(dependentPromise.isDone()); + assertFalse(dependentPromise.isCancelled()); + } + + @Test + public void dependentPromiseIsFulfilledWithAnExceptionIfConsumerThrowsAnException() + throws InterruptedException, ExecutionException, TimeoutException { + Promise dependentPromise = promise + .fulfillInAsync(new NumberCrunchingTask(), executor) + .thenAccept(new Consumer() { + + @Override + public void accept(Integer value) { + throw new RuntimeException("Barf!"); + } + }); + + try { + dependentPromise.get(); + fail("Fetching dependent promise should result in exception " + + "if the action threw an exception"); + } catch (ExecutionException ex) { + assertTrue(promise.isDone()); + assertFalse(promise.isCancelled()); + } + + try { + dependentPromise.get(1000, TimeUnit.SECONDS); + fail("Fetching dependent promise should result in exception " + + "if the action threw an exception"); + } catch (ExecutionException ex) { + assertTrue(promise.isDone()); + assertFalse(promise.isCancelled()); + } + } + + @Test + public void dependentPromiseIsFulfilledAfterTheFunctionTransformsTheResultOfThisPromise() + throws InterruptedException, ExecutionException { + Promise dependentPromise = promise + .fulfillInAsync(new NumberCrunchingTask(), executor) + .thenApply(value -> { + assertEquals(NumberCrunchingTask.CRUNCHED_NUMBER, value); + return String.valueOf(value); + }); + + + assertEquals(String.valueOf(NumberCrunchingTask.CRUNCHED_NUMBER), dependentPromise.get()); + assertTrue(dependentPromise.isDone()); + assertFalse(dependentPromise.isCancelled()); + } + + @Test + public void dependentPromiseIsFulfilledWithAnExceptionIfTheFunctionThrowsException() + throws InterruptedException, ExecutionException, TimeoutException { + Promise dependentPromise = promise + .fulfillInAsync(new NumberCrunchingTask(), executor) + .thenApply(new Function() { + + @Override + public String apply(Integer value) { + throw new RuntimeException("Barf!"); + } + }); + + try { + dependentPromise.get(); + fail("Fetching dependent promise should result in exception " + + "if the function threw an exception"); + } catch (ExecutionException ex) { + assertTrue(promise.isDone()); + assertFalse(promise.isCancelled()); + } + + try { + dependentPromise.get(1000, TimeUnit.SECONDS); + fail("Fetching dependent promise should result in exception " + + "if the function threw an exception"); + } catch (ExecutionException ex) { + assertTrue(promise.isDone()); + assertFalse(promise.isCancelled()); + } + } + + @Test + public void fetchingAnAlreadyFulfilledPromiseReturnsTheFulfilledValueImmediately() + throws InterruptedException, ExecutionException, TimeoutException { + Promise promise = new Promise<>(); + promise.fulfill(NumberCrunchingTask.CRUNCHED_NUMBER); + + promise.get(1000, TimeUnit.SECONDS); + } + + @SuppressWarnings("unchecked") + @Test + public void exceptionHandlerIsCalledWhenPromiseIsFulfilledExceptionally() { + Promise promise = new Promise<>(); + Consumer exceptionHandler = mock(Consumer.class); + promise.onError(exceptionHandler); + + Exception exception = new Exception("barf!"); + promise.fulfillExceptionally(exception); + + verify(exceptionHandler).accept(eq(exception)); + } + + private static class NumberCrunchingTask implements Callable { + + private static final Integer CRUNCHED_NUMBER = Integer.MAX_VALUE; + + @Override + public Integer call() throws Exception { + // Do number crunching + Thread.sleep(100); + return CRUNCHED_NUMBER; + } + } +} diff --git a/property/pom.xml b/property/pom.xml index 1e9d0f0c2..b3bc915e9 100644 --- a/property/pom.xml +++ b/property/pom.xml @@ -2,7 +2,7 @@ + + + + Title + + + + + + + + + diff --git a/proxy/etc/proxy-concept.png b/proxy/etc/proxy-concept.png new file mode 100644 index 000000000..dac5d954b Binary files /dev/null and b/proxy/etc/proxy-concept.png differ diff --git a/proxy/etc/proxy-concept.xml b/proxy/etc/proxy-concept.xml new file mode 100644 index 000000000..83373a929 --- /dev/null +++ b/proxy/etc/proxy-concept.xml @@ -0,0 +1,25 @@ + +7Vhtb5swEP41kbYPnQJuaPexeVv3YVK1aur60YEL8WowM05K+utnYxswkC6p2rSbmkoNfnx3vpfnLsAATZLiC8fZ6huLgA78YVQM0HTg+x4KPPmlkK1GziwQcxIZoRq4Jg9gwKFB1ySC3BEUjFFBMhcMWZpCKBwMc87uXbElo+6pGY6hA1yHmHbRGxKJlUbPR8MavwQSr+zJ3tDsLHB4F3O2Ts15Ax8ty4/eTrC1ZeTzFY7YfQNCswGacMaEvkqKCVCVW5s2rTffsVv5zSEV+yica4UNpmuwHgdUqo4z5Z3YmowEv9fKpXGCeUzSAbqQu8OskP8lWAam8BPBMr132tgTUIgTTEls9ELpG/DapryKzXd5MmkAOJEGx7S7+qpsLHEIFeyqOBZl/KR9yoJ3EAtcrxe/FKtq7UVbVmJZG1txlTLLZRudtzvQJ6WYwlI0crzLdje8PpdfxgMlW9b4ww15wDz6qKU3TLJuT/d8xzF/A1wQ2Z4XmkXTkmdjw6mpdmjMpNSSlt20JJLlaLxkqTDDxfPNeo4TQtVYugS6AWVVlU4kVAkpGak6YZTx8mTbvWicC87uoLEzLD+KahxHRMbb2Juezrz5qIpEuQ/Fzhb1qsaXAxVYAoJvpYhR8M/NrDCz1A6h+3owVeNk5Q4lMxDNMIwr0/VAkBdmJvTPB+vumxoQVTd+B0yP1awR2fQGbFOuHPcf68g8w+lBPSLddnUquPTl9VvlhYmPfJf4fcz3TnuYHzwH8f23TPwrzortO+X/O8oHnkv5s9ERGY/eIuN336VNqCrE/k3wj99SzAL19zw0G70iy7pPHLNCQBrlnQpJ8EI9y8nVgrLwTsYuIZv8QC/nZVmmQzfjMg98+1Phn0Z2eWv3CiIaW3J1awxoDyDqPBu28iq9ZGsegnuLJCTtQTQeq7rpbybYJpMDxYJs3CP7MmzMXTGiWL+jlAi1aqQ9NUrNB8GWHd/7iyEdXsdQWe8qxP0o8PmoFPCaBKiL/tOoVAQodw5v1SeSxn8nzUGkQd37sR85PEYYlkHq8kXN6wjnK4gMLZp0MZTwOpSoZ0mXSE8sPnov/mHF77s12V35lKVQ9izmomd8NOZFKWLWbULs8RPSJcvz/YToEI/CiKD1hqF643AsSsgi4W1DLFMC+eEO1wzTFvfkm1zWb1y1eP1aG83+AA== \ No newline at end of file diff --git a/proxy/etc/proxy.png b/proxy/etc/proxy.png deleted file mode 100644 index 64c61f1f2..000000000 Binary files a/proxy/etc/proxy.png and /dev/null differ diff --git a/proxy/etc/proxy_1.png b/proxy/etc/proxy_1.png deleted file mode 100644 index ba86fa8d6..000000000 Binary files a/proxy/etc/proxy_1.png and /dev/null differ diff --git a/proxy/pom.xml b/proxy/pom.xml index f16736a2c..31e87c04b 100644 --- a/proxy/pom.xml +++ b/proxy/pom.xml @@ -2,7 +2,7 @@ + + + publish-subscribe.log + + publish-subscribe-%d.log + 5 + + + %-5p [%d{ISO8601,UTC}] %c: %m%n + + + + + + %-5p [%d{ISO8601,UTC}] %c: %m%n + + + + + + + + + + + + + diff --git a/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/AppTest.java b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/AppTest.java index 9f387be33..128385faf 100644 --- a/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/AppTest.java +++ b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/AppTest.java @@ -1,6 +1,6 @@ /** * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright (c) 2014-2016 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 @@ -22,7 +22,7 @@ */ package com.iluwatar.publish.subscribe; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * diff --git a/queue-load-leveling/README.md b/queue-load-leveling/README.md new file mode 100644 index 000000000..13821a54e --- /dev/null +++ b/queue-load-leveling/README.md @@ -0,0 +1,36 @@ +--- +layout: pattern +title: Queue based load leveling +folder: queue-load-leveling +permalink: /patterns/queue-load-leveling/ +categories: Other +tags: + - Java + - Difficulty-Intermediate + - Performance +--- + +## Intent +Use a queue that acts as a buffer between a task and a service that it invokes in order to smooth +intermittent heavy loads that may otherwise cause the service to fail or the task to time out. +This pattern can help to minimize the impact of peaks in demand on availability and responsiveness +for both the task and the service. + +![alt text](./etc/queue-load-leveling.gif "queue-load-leveling") + + +## Applicability + +* This pattern is ideally suited to any type of application that uses services that may be subject to overloading. +* This pattern might not be suitable if the application expects a response from the service with minimal latency. + +## Tutorials +* [Queue-Based Load Leveling Pattern](http://java-design-patterns.com/blog/queue-load-leveling/) + +## Real world example + +* A Microsoft Azure web role stores data by using a separate storage service. If a large number of instances of the web role run concurrently, it is possible that the storage service could be overwhelmed and be unable to respond to requests quickly enough to prevent these requests from timing out or failing. + +## Credits + +* [Microsoft Cloud Design Patterns: Queue-Based Load Leveling Pattern](https://msdn.microsoft.com/en-us/library/dn589783.aspx) diff --git a/queue-load-leveling/etc/queue-load-leveling.gif b/queue-load-leveling/etc/queue-load-leveling.gif new file mode 100644 index 000000000..4f57c3e50 Binary files /dev/null and b/queue-load-leveling/etc/queue-load-leveling.gif differ diff --git a/queue-load-leveling/etc/queue-load-leveling.ucls b/queue-load-leveling/etc/queue-load-leveling.ucls new file mode 100644 index 000000000..91440e276 --- /dev/null +++ b/queue-load-leveling/etc/queue-load-leveling.ucls @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/queue-load-leveling/pom.xml b/queue-load-leveling/pom.xml new file mode 100644 index 000000000..681409e96 --- /dev/null +++ b/queue-load-leveling/pom.xml @@ -0,0 +1,47 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.19.0-SNAPSHOT + + queue-load-leveling + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + diff --git a/queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/App.java b/queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/App.java new file mode 100644 index 000000000..f71d30c16 --- /dev/null +++ b/queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/App.java @@ -0,0 +1,119 @@ +/** + * The MIT License + * Copyright (c) 2014 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.queue.load.leveling; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * Many solutions in the cloud involve running tasks that invoke services. In this environment, + * if a service is subjected to intermittent heavy loads, it can cause performance or reliability issues. + *

+ * A service could be a component that is part of the same solution as the tasks that utilize it, or it + * could be a third-party service providing access to frequently used resources such as a cache or a storage service. + * If the same service is utilized by a number of tasks running concurrently, it can be difficult to predict the + * volume of requests to which the service might be subjected at any given point in time. + *

+ * We will build a queue-based-load-leveling to solve above problem. + * Refactor the solution and introduce a queue between the task and the service. + * The task and the service run asynchronously. The task posts a message containing the data required + * by the service to a queue. The queue acts as a buffer, storing the message until it is retrieved + * by the service. The service retrieves the messages from the queue and processes them. + * Requests from a number of tasks, which can be generated at a highly variable rate, can be passed + * to the service through the same message queue. + *

+ * The queue effectively decouples the tasks from the service, and the service can handle the messages + * at its own pace irrespective of the volume of requests from concurrent tasks. Additionally, + * there is no delay to a task if the service is not available at the time it posts a message to the queue. + *

+ * In this example we have a class {@link MessageQueue} to hold the message {@link Message} objects. + * All the worker threads {@link TaskGenerator} will submit the messages to the MessageQueue. + * The service executor class {@link ServiceExecutor} will pick up one task at a time from the Queue and + * execute them. + * + */ +public class App { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + //Executor shut down time limit. + private static final int SHUTDOWN_TIME = 15; + + /** + * Program entry point + * + * @param args command line args + */ + public static void main(String[] args) { + + // An Executor that provides methods to manage termination and methods that can + // produce a Future for tracking progress of one or more asynchronous tasks. + ExecutorService executor = null; + + try { + // Create a MessageQueue object. + MessageQueue msgQueue = new MessageQueue(); + + LOGGER.info("Submitting TaskGenerators and ServiceExecutor threads."); + + // Create three TaskGenerator threads. Each of them will submit different number of jobs. + Runnable taskRunnable1 = new TaskGenerator(msgQueue, 5); + Runnable taskRunnable2 = new TaskGenerator(msgQueue, 1); + Runnable taskRunnable3 = new TaskGenerator(msgQueue, 2); + + // Create e service which should process the submitted jobs. + Runnable srvRunnable = new ServiceExecutor(msgQueue); + + // Create a ThreadPool of 2 threads and + // submit all Runnable task for execution to executor.. + executor = Executors.newFixedThreadPool(2); + executor.submit(taskRunnable1); + executor.submit(taskRunnable2); + executor.submit(taskRunnable3); + + // submitting serviceExecutor thread to the Executor service. + executor.submit(srvRunnable); + + // Initiates an orderly shutdown. + LOGGER.info("Intiating shutdown. Executor will shutdown only after all the Threads are completed."); + executor.shutdown(); + + // Wait for SHUTDOWN_TIME seconds for all the threads to complete + // their tasks and then shut down the executor and then exit. + if ( !executor.awaitTermination(SHUTDOWN_TIME, TimeUnit.SECONDS) ) { + LOGGER.info("Executor was shut down and Exiting."); + executor.shutdownNow(); + } + } catch (InterruptedException ie) { + LOGGER.error(ie.getMessage()); + } catch (Exception e) { + LOGGER.error(e.getMessage()); + } + } +} \ No newline at end of file diff --git a/queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/Message.java b/queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/Message.java new file mode 100644 index 000000000..1bfc3fe35 --- /dev/null +++ b/queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/Message.java @@ -0,0 +1,46 @@ +/** + * The MIT License + * Copyright (c) 2014 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.queue.load.leveling; + +/** + * Message class with only one parameter. + * +*/ +public class Message { + private final String msg; + + // Parameter constructor. + public Message(String msg) { + this.msg = msg; + } + + // Get Method for attribute msg. + public String getMsg() { + return msg; + } + + @Override + public String toString() { + return msg; + } +} \ No newline at end of file diff --git a/queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/MessageQueue.java b/queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/MessageQueue.java new file mode 100644 index 000000000..c1e6d4c28 --- /dev/null +++ b/queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/MessageQueue.java @@ -0,0 +1,77 @@ +/** + * The MIT License + * Copyright (c) 2014 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.queue.load.leveling; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * MessageQueue class. + * In this class we will create a Blocking Queue and + * submit/retrieve all the messages from it. + */ +public class MessageQueue { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + private final BlockingQueue blkQueue; + + // Default constructor when called creates Blocking Queue object. + public MessageQueue() { + this.blkQueue = new ArrayBlockingQueue(1024); + } + + /** + * All the TaskGenerator threads will call this method to insert the + * Messages in to the Blocking Queue. + */ + public void submitMsg(Message msg) { + try { + if (null != msg) { + blkQueue.add(msg); + } + } catch (Exception e) { + LOGGER.error(e.getMessage()); + } + } + + /** + * All the messages will be retrieved by the ServiceExecutor by + * calling this method and process them. + * Retrieves and removes the head of this queue, or returns null if this queue is empty. + */ + public Message retrieveMsg() { + Message retrievedMsg = null; + try { + retrievedMsg = blkQueue.poll(); + } catch (Exception e) { + LOGGER.error(e.getMessage()); + } + + return retrievedMsg; + } +} \ No newline at end of file diff --git a/queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/ServiceExecutor.java b/queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/ServiceExecutor.java new file mode 100644 index 000000000..2b423ffaf --- /dev/null +++ b/queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/ServiceExecutor.java @@ -0,0 +1,67 @@ +/** + * The MIT License + * Copyright (c) 2014 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.queue.load.leveling; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * ServiceExecuotr class. + * This class will pick up Messages one by one from + * the Blocking Queue and process them. + */ +public class ServiceExecutor implements Runnable { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + private final MessageQueue msgQueue; + + public ServiceExecutor(MessageQueue msgQueue) { + this.msgQueue = msgQueue; + } + + /** + * The ServiceExecutor thread will retrieve each message and process it. + */ + public void run() { + try { + while (!Thread.currentThread().isInterrupted()) { + Message msg = msgQueue.retrieveMsg(); + + if (null != msg) { + LOGGER.info(msg.toString() + " is served."); + } else { + LOGGER.info("Service Executor: Waiting for Messages to serve .. "); + } + + Thread.sleep(1000); + } + } catch (InterruptedException ie) { + LOGGER.error(ie.getMessage()); + } catch (Exception e) { + LOGGER.error(e.getMessage()); + } + } +} \ No newline at end of file diff --git a/queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/Task.java b/queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/Task.java new file mode 100644 index 000000000..f85f7d49c --- /dev/null +++ b/queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/Task.java @@ -0,0 +1,30 @@ +/** + * The MIT License + * Copyright (c) 2014 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.queue.load.leveling; +/** + * Task Interface. + * +*/ +public interface Task { + void submit(Message msg); +} diff --git a/queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/TaskGenerator.java b/queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/TaskGenerator.java new file mode 100644 index 000000000..e99e918db --- /dev/null +++ b/queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/TaskGenerator.java @@ -0,0 +1,89 @@ +/** + * The MIT License + * Copyright (c) 2014 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.queue.load.leveling; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * TaskGenerator class. + * Each TaskGenerator thread will be a Worker which submit's messages to the queue. + * We need to mention the message count for each of the TaskGenerator threads. + * +*/ +public class TaskGenerator implements Task, Runnable { + + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + // MessageQueue reference using which we will submit our messages. + private final MessageQueue msgQueue; + + // Total message count that a TaskGenerator will submit. + private final int msgCount; + + // Parameterized constructor. + public TaskGenerator(MessageQueue msgQueue, int msgCount) { + this.msgQueue = msgQueue; + this.msgCount = msgCount; + } + + /** + * Submit messages to the Blocking Queue. + */ + public void submit(Message msg) { + try { + this.msgQueue.submitMsg(msg); + } catch (Exception e) { + LOGGER.error(e.getMessage()); + } + } + + /** + * Each TaskGenerator thread will submit all the messages to the Queue. + * After every message submission TaskGenerator thread will sleep for 1 second. + */ + public void run() { + + int count = this.msgCount; + + try { + while (count > 0) { + String statusMsg = "Message-" + count + " submitted by " + Thread.currentThread().getName(); + this.submit(new Message(statusMsg)); + + LOGGER.info(statusMsg); + + // reduce the message count. + count--; + + // Make the current thread to sleep after every Message submission. + Thread.sleep(1000); + } + } catch (InterruptedException ie) { + LOGGER.error(ie.getMessage()); + } catch (Exception e) { + LOGGER.error(e.getMessage()); + } + } +} \ No newline at end of file diff --git a/queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/AppTest.java b/queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/AppTest.java new file mode 100644 index 000000000..820062574 --- /dev/null +++ b/queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/AppTest.java @@ -0,0 +1,38 @@ +/** + * The MIT License + * Copyright (c) 2014 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.queue.load.leveling; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +/** + * Application Test + */ +public class AppTest { + @Test + public void test() throws IOException { + String[] args = {}; + App.main(args); + } +} \ No newline at end of file diff --git a/queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/MessageQueueTest.java b/queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/MessageQueueTest.java new file mode 100644 index 000000000..13a1db706 --- /dev/null +++ b/queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/MessageQueueTest.java @@ -0,0 +1,48 @@ +/** + * The MIT License + * Copyright (c) 2014 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.queue.load.leveling; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * + * Test case for submitting and retrieving messages from Blocking Queue. + * + */ +public class MessageQueueTest { + + @Test + public void messageQueueTest() { + + MessageQueue msgQueue = new MessageQueue(); + + // submit message + msgQueue.submitMsg(new Message("MessageQueue Test")); + + // retrieve message + assertEquals(msgQueue.retrieveMsg().getMsg(), "MessageQueue Test"); + } + +} diff --git a/queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/MessageTest.java b/queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/MessageTest.java new file mode 100644 index 000000000..d6b0167cf --- /dev/null +++ b/queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/MessageTest.java @@ -0,0 +1,44 @@ +/** + * The MIT License + * Copyright (c) 2014 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.queue.load.leveling; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * + * Test case for creating and checking the Message. + * + */ +public class MessageTest { + + @Test + public void messageTest() { + + // Parameterized constructor test. + String testMsg = "Message Test"; + Message msg = new Message(testMsg); + assertEquals(msg.getMsg(), testMsg); + } +} diff --git a/queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/TaskGenSrvExeTest.java b/queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/TaskGenSrvExeTest.java new file mode 100644 index 000000000..12fb4bf33 --- /dev/null +++ b/queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/TaskGenSrvExeTest.java @@ -0,0 +1,50 @@ +/** + * The MIT License + * Copyright (c) 2014 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.queue.load.leveling; + +import org.junit.jupiter.api.Test; + +/** + * + * Test case for submitting Message to Blocking Queue by TaskGenerator + * and retrieve the message by ServiceExecutor. + * + */ +public class TaskGenSrvExeTest { + + @Test + public void taskGeneratorTest() { + MessageQueue msgQueue = new MessageQueue(); + + // Create a task generator thread with 1 job to submit. + Runnable taskRunnable = new TaskGenerator(msgQueue, 1); + Thread taskGenThr = new Thread(taskRunnable); + taskGenThr.start(); + + // Create a service executor thread. + Runnable srvRunnable = new ServiceExecutor(msgQueue); + Thread srvExeThr = new Thread(srvRunnable); + srvExeThr.start(); + } + +} diff --git a/reactor/README.md b/reactor/README.md index b9ba98948..ba96f9c68 100644 --- a/reactor/README.md +++ b/reactor/README.md @@ -3,8 +3,9 @@ layout: pattern title: Reactor folder: reactor permalink: /patterns/reactor/ +pumlformat: svg categories: Concurrency -tags: +tags: - Java - Difficulty-Expert - I/O diff --git a/reactor/pom.xml b/reactor/pom.xml index c06584c6f..e8a145f7b 100644 --- a/reactor/pom.xml +++ b/reactor/pom.xml @@ -2,7 +2,7 @@ + + + repository.log + + repository-%d.log + 5 + + + %-5p [%d{ISO8601,UTC}] %c: %m%n + + + + + + %-5p [%d{ISO8601,UTC}] %c: %m%n + + + + + + + + + + + + + diff --git a/repository/src/test/java/com/iluwatar/repository/AnnotationBasedRepositoryTest.java b/repository/src/test/java/com/iluwatar/repository/AnnotationBasedRepositoryTest.java index ccd3fcc41..4cfb6e022 100644 --- a/repository/src/test/java/com/iluwatar/repository/AnnotationBasedRepositoryTest.java +++ b/repository/src/test/java/com/iluwatar/repository/AnnotationBasedRepositoryTest.java @@ -1,6 +1,6 @@ /** * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright (c) 2014-2016 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 @@ -22,21 +22,21 @@ */ package com.iluwatar.repository; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Arrays; import java.util.List; import javax.annotation.Resource; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.support.AnnotationConfigContextLoader; import com.google.common.collect.Lists; @@ -46,7 +46,7 @@ import com.google.common.collect.Lists; * by {@link org.springframework.data.jpa.domain.Specification} are also test. * */ -@RunWith(SpringJUnit4ClassRunner.class) +@ExtendWith(SpringExtension.class) @ContextConfiguration(classes = { AppConfig.class }, loader = AnnotationConfigContextLoader.class) public class AnnotationBasedRepositoryTest { @@ -63,7 +63,7 @@ public class AnnotationBasedRepositoryTest { /** * Prepare data for test */ - @Before + @BeforeEach public void setup() { repository.save(persons); @@ -123,7 +123,7 @@ public class AnnotationBasedRepositoryTest { assertEquals(terry, actual); } - @After + @AfterEach public void cleanup() { repository.deleteAll(); diff --git a/repository/src/test/java/com/iluwatar/repository/AppConfigTest.java b/repository/src/test/java/com/iluwatar/repository/AppConfigTest.java index 28623bf45..882d2bef7 100644 --- a/repository/src/test/java/com/iluwatar/repository/AppConfigTest.java +++ b/repository/src/test/java/com/iluwatar/repository/AppConfigTest.java @@ -1,6 +1,6 @@ /** * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright (c) 2014-2016 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 @@ -22,19 +22,19 @@ */ package com.iluwatar.repository; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.sql.ResultSet; import java.sql.SQLException; import javax.sql.DataSource; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.support.AnnotationConfigContextLoader; import org.springframework.transaction.annotation.Transactional; @@ -42,7 +42,7 @@ import org.springframework.transaction.annotation.Transactional; * This case is Just for test the Annotation Based configuration * */ -@RunWith(SpringJUnit4ClassRunner.class) +@ExtendWith(SpringExtension.class) @ContextConfiguration(classes = { AppConfig.class }, loader = AnnotationConfigContextLoader.class) public class AppConfigTest { diff --git a/repository/src/test/java/com/iluwatar/repository/AppTest.java b/repository/src/test/java/com/iluwatar/repository/AppTest.java index e1fc27bef..17052b51e 100644 --- a/repository/src/test/java/com/iluwatar/repository/AppTest.java +++ b/repository/src/test/java/com/iluwatar/repository/AppTest.java @@ -1,6 +1,6 @@ /** * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright (c) 2014-2016 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 @@ -22,7 +22,7 @@ */ package com.iluwatar.repository; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; diff --git a/repository/src/test/java/com/iluwatar/repository/RepositoryTest.java b/repository/src/test/java/com/iluwatar/repository/RepositoryTest.java index 63f9f7c6d..6a347ffd4 100644 --- a/repository/src/test/java/com/iluwatar/repository/RepositoryTest.java +++ b/repository/src/test/java/com/iluwatar/repository/RepositoryTest.java @@ -1,6 +1,6 @@ /** * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright (c) 2014-2016 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 @@ -22,21 +22,21 @@ */ package com.iluwatar.repository; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Arrays; import java.util.List; import javax.annotation.Resource; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringExtension; import com.google.common.collect.Lists; @@ -44,7 +44,7 @@ import com.google.common.collect.Lists; * Test case to test the functions of {@link PersonRepository}, beside the CRUD functions, the query * by {@link org.springframework.data.jpa.domain.Specification} are also test. */ -@RunWith(SpringJUnit4ClassRunner.class) +@ExtendWith(SpringExtension.class) @ContextConfiguration(locations = { "classpath:applicationContext.xml" }) public class RepositoryTest { @@ -61,7 +61,7 @@ public class RepositoryTest { /** * Prepare data for test */ - @Before + @BeforeEach public void setup() { repository.save(persons); @@ -121,7 +121,7 @@ public class RepositoryTest { assertEquals(terry, actual); } - @After + @AfterEach public void cleanup() { repository.deleteAll(); diff --git a/resource-acquisition-is-initialization/pom.xml b/resource-acquisition-is-initialization/pom.xml index 7b411f46c..3d68966c5 100644 --- a/resource-acquisition-is-initialization/pom.xml +++ b/resource-acquisition-is-initialization/pom.xml @@ -2,7 +2,7 @@ + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.19.0-SNAPSHOT + + retry + jar + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.hamcrest + hamcrest-core + test + + + \ No newline at end of file diff --git a/retry/src/main/java/com/iluwatar/retry/App.java b/retry/src/main/java/com/iluwatar/retry/App.java new file mode 100644 index 000000000..376a64fbf --- /dev/null +++ b/retry/src/main/java/com/iluwatar/retry/App.java @@ -0,0 +1,105 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2016 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.retry; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The Retry pattern enables applications to handle potentially recoverable failures from + * the environment if the business requirements and nature of the failures allow it. By retrying + * failed operations on external dependencies, the application may maintain stability and minimize + * negative impact on the user experience. + *

+ * In our example, we have the {@link BusinessOperation} interface as an abstraction over + * all operations that our application performs involving remote systems. The calling code should + * remain decoupled from implementations. + *

+ * {@link FindCustomer} is a business operation that looks up a customer's record and returns + * its ID. Imagine its job is performed by looking up the customer in our local database and + * returning its ID. We can pass {@link CustomerNotFoundException} as one of its + * {@link FindCustomer#FindCustomer(java.lang.String, com.iluwatar.retry.BusinessException...) + * constructor parameters} in order to simulate not finding the customer. + *

+ * Imagine that, lately, this operation has experienced intermittent failures due to some weird + * corruption and/or locking in the data. After retrying a few times the customer is found. The + * database is still, however, expected to always be available. While a definitive solution is + * found to the problem, our engineers advise us to retry the operation a set number + * of times with a set delay between retries, although not too many retries otherwise the end user + * will be left waiting for a long time, while delays that are too short will not allow the database + * to recover from the load. + *

+ * To keep the calling code as decoupled as possible from this workaround, we have implemented the + * retry mechanism as a {@link BusinessOperation} named {@link Retry}. + * + * @author George Aristy (george.aristy@gmail.com) + * @see Retry pattern (Microsoft Azure Docs) + */ +public final class App { + private static final Logger LOG = LoggerFactory.getLogger(App.class); + private static BusinessOperation op; + + /** + * Entry point. + * + * @param args not used + * @throws Exception not expected + */ + public static void main(String[] args) throws Exception { + noErrors(); + errorNoRetry(); + errorWithRetry(); + } + + private static void noErrors() throws Exception { + op = new FindCustomer("123"); + op.perform(); + LOG.info("Sometimes the operation executes with no errors."); + } + + private static void errorNoRetry() throws Exception { + op = new FindCustomer("123", new CustomerNotFoundException("not found")); + try { + op.perform(); + } catch (CustomerNotFoundException e) { + LOG.info("Yet the operation will throw an error every once in a while."); + } + } + + private static void errorWithRetry() throws Exception { + final Retry retry = new Retry<>( + new FindCustomer("123", new CustomerNotFoundException("not found")), + 3, //3 attempts + 100, //100 ms delay between attempts + e -> CustomerNotFoundException.class.isAssignableFrom(e.getClass()) + ); + op = retry; + final String customerId = op.perform(); + LOG.info(String.format( + "However, retrying the operation while ignoring a recoverable error will eventually yield " + + "the result %s after a number of attempts %s", customerId, retry.attempts() + )); + } +} diff --git a/retry/src/main/java/com/iluwatar/retry/BusinessException.java b/retry/src/main/java/com/iluwatar/retry/BusinessException.java new file mode 100644 index 000000000..eefbf2813 --- /dev/null +++ b/retry/src/main/java/com/iluwatar/retry/BusinessException.java @@ -0,0 +1,46 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2016 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.retry; + +/** + * The top-most type in our exception hierarchy that signifies that an unexpected error + * condition occurred. Its use is reserved as a "catch-all" for cases where no other subtype + * captures the specificity of the error condition in question. Calling code is not expected to + * be able to handle this error and should be reported to the maintainers immediately. + * + * @author George Aristy (george.aristy@gmail.com) + */ +public class BusinessException extends Exception { + private static final long serialVersionUID = 6235833142062144336L; + + /** + * Ctor + * + * @param message the error message + */ + public BusinessException(String message) { + super(message); + } +} diff --git a/retry/src/main/java/com/iluwatar/retry/BusinessOperation.java b/retry/src/main/java/com/iluwatar/retry/BusinessOperation.java new file mode 100644 index 000000000..aefb589c7 --- /dev/null +++ b/retry/src/main/java/com/iluwatar/retry/BusinessOperation.java @@ -0,0 +1,44 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2016 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.retry; + +/** + * Performs some business operation. + * + * @author George Aristy (george.aristy@gmail.com) + * @param the return type + */ +@FunctionalInterface +public interface BusinessOperation { + /** + * Performs some business operation, returning a value {@code T} if successful, otherwise throwing + * an exception if an error occurs. + * + * @return the return value + * @throws BusinessException if the operation fails. Implementations are allowed to throw more + * specific subtypes depending on the error conditions + */ + T perform() throws BusinessException; +} diff --git a/retry/src/main/java/com/iluwatar/retry/CustomerNotFoundException.java b/retry/src/main/java/com/iluwatar/retry/CustomerNotFoundException.java new file mode 100644 index 000000000..596d8584d --- /dev/null +++ b/retry/src/main/java/com/iluwatar/retry/CustomerNotFoundException.java @@ -0,0 +1,46 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2016 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.retry; + +/** + * Indicates that the customer was not found. + *

+ * The severity of this error is bounded by its context: was the search for the customer triggered + * by an input from some end user, or were the search parameters pulled from your database? + * + * @author George Aristy (george.aristy@gmail.com) + */ +public final class CustomerNotFoundException extends BusinessException { + private static final long serialVersionUID = -6972888602621778664L; + + /** + * Ctor. + * + * @param message the error message + */ + public CustomerNotFoundException(String message) { + super(message); + } +} diff --git a/retry/src/main/java/com/iluwatar/retry/DatabaseNotAvailableException.java b/retry/src/main/java/com/iluwatar/retry/DatabaseNotAvailableException.java new file mode 100644 index 000000000..2a93e992d --- /dev/null +++ b/retry/src/main/java/com/iluwatar/retry/DatabaseNotAvailableException.java @@ -0,0 +1,43 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2016 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.retry; + +/** + * Catastrophic error indicating that we have lost connection to our database. + * + * @author George Aristy (george.aristy@gmail.com) + */ +public final class DatabaseNotAvailableException extends BusinessException { + private static final long serialVersionUID = -3750769625095997799L; + + /** + * Ctor. + * + * @param message the error message + */ + public DatabaseNotAvailableException(String message) { + super(message); + } +} diff --git a/retry/src/main/java/com/iluwatar/retry/FindCustomer.java b/retry/src/main/java/com/iluwatar/retry/FindCustomer.java new file mode 100644 index 000000000..421f450e5 --- /dev/null +++ b/retry/src/main/java/com/iluwatar/retry/FindCustomer.java @@ -0,0 +1,63 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2016 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.retry; + +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Deque; + +/** + * Finds a customer, returning its ID from our records. + *

+ * This is an imaginary operation that, for some imagined input, returns the ID for a customer. + * However, this is a "flaky" operation that is supposed to fail intermittently, but for the + * purposes of this example it fails in a programmed way depending on the constructor parameters. + * + * @author George Aristy (george.aristy@gmail.com) + */ +public final class FindCustomer implements BusinessOperation { + private final String customerId; + private final Deque errors; + + /** + * Ctor. + * + * @param customerId the final result of the remote operation + * @param errors the errors to throw before returning {@code customerId} + */ + public FindCustomer(String customerId, BusinessException... errors) { + this.customerId = customerId; + this.errors = new ArrayDeque<>(Arrays.asList(errors)); + } + + @Override + public String perform() throws BusinessException { + if (!this.errors.isEmpty()) { + throw this.errors.pop(); + } + + return this.customerId; + } +} diff --git a/retry/src/main/java/com/iluwatar/retry/Retry.java b/retry/src/main/java/com/iluwatar/retry/Retry.java new file mode 100644 index 000000000..5ed60f98b --- /dev/null +++ b/retry/src/main/java/com/iluwatar/retry/Retry.java @@ -0,0 +1,110 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2016 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.retry; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; + +/** + * Decorates {@link BusinessOperation business operation} with "retry" capabilities. + * + * @author George Aristy (george.aristy@gmail.com) + * @param the remote op's return type + */ +public final class Retry implements BusinessOperation { + private final BusinessOperation op; + private final int maxAttempts; + private final long delay; + private final AtomicInteger attempts; + private final Predicate test; + private final List errors; + + /** + * Ctor. + * + * @param op the {@link BusinessOperation} to retry + * @param maxAttempts number of times to retry + * @param delay delay (in milliseconds) between attempts + * @param ignoreTests tests to check whether the remote exception can be ignored. No exceptions + * will be ignored if no tests are given + */ + @SafeVarargs + public Retry( + BusinessOperation op, + int maxAttempts, + long delay, + Predicate... ignoreTests + ) { + this.op = op; + this.maxAttempts = maxAttempts; + this.delay = delay; + this.attempts = new AtomicInteger(); + this.test = Arrays.stream(ignoreTests).reduce(Predicate::or).orElse(e -> false); + this.errors = new ArrayList<>(); + } + + /** + * The errors encountered while retrying, in the encounter order. + * + * @return the errors encountered while retrying + */ + public List errors() { + return Collections.unmodifiableList(this.errors); + } + + /** + * The number of retries performed. + * + * @return the number of retries performed + */ + public int attempts() { + return this.attempts.intValue(); + } + + @Override + public T perform() throws BusinessException { + do { + try { + return this.op.perform(); + } catch (BusinessException e) { + this.errors.add(e); + + if (this.attempts.incrementAndGet() >= this.maxAttempts || !this.test.test(e)) { + throw e; + } + + try { + Thread.sleep(this.delay); + } catch (InterruptedException f) { + //ignore + } + } + } while (true); + } +} diff --git a/retry/src/test/java/com/iluwatar/retry/FindCustomerTest.java b/retry/src/test/java/com/iluwatar/retry/FindCustomerTest.java new file mode 100644 index 000000000..46c4c62e6 --- /dev/null +++ b/retry/src/test/java/com/iluwatar/retry/FindCustomerTest.java @@ -0,0 +1,90 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2016 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.retry; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Unit tests for {@link FindCustomer}. + * + * @author George Aristy (george.aristy@gmail.com) + */ +public class FindCustomerTest { + /** + * Returns the given result with no exceptions. + */ + @Test + public void noExceptions() throws Exception { + assertThat( + new FindCustomer("123").perform(), + is("123") + ); + } + + /** + * Throws the given exception. + * + * @throws Exception the expected exception + */ + @Test + public void oneException() throws Exception { + assertThrows(BusinessException.class, () -> { + new FindCustomer("123", new BusinessException("test")).perform(); + }); + } + + /** + * Should first throw the given exceptions, then return the given result. + * + * @throws Exception not an expected exception + */ + @Test + public void resultAfterExceptions() throws Exception { + final BusinessOperation op = new FindCustomer( + "123", + new CustomerNotFoundException("not found"), + new DatabaseNotAvailableException("not available") + ); + try { + op.perform(); + } catch (CustomerNotFoundException e) { + //ignore + } + try { + op.perform(); + } catch (DatabaseNotAvailableException e) { + //ignore + } + + assertThat( + op.perform(), + is("123") + ); + } +} diff --git a/retry/src/test/java/com/iluwatar/retry/RetryTest.java b/retry/src/test/java/com/iluwatar/retry/RetryTest.java new file mode 100644 index 000000000..233e0e588 --- /dev/null +++ b/retry/src/test/java/com/iluwatar/retry/RetryTest.java @@ -0,0 +1,111 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2016 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.retry; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.hasItem; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Unit tests for {@link Retry}. + * + * @author George Aristy (george.aristy@gmail.com) + */ +public class RetryTest { + /** + * Should contain all errors thrown. + */ + @Test + public void errors() throws Exception { + final BusinessException e = new BusinessException("unhandled"); + final Retry retry = new Retry<>( + () -> { throw e; }, + 2, + 0 + ); + try { + retry.perform(); + } catch (BusinessException ex) { + //ignore + } + + assertThat( + retry.errors(), + hasItem(e) + ); + } + + /** + * No exceptions will be ignored, hence final number of attempts should be 1 even if we're asking + * it to attempt twice. + */ + @Test + public void attempts() { + final BusinessException e = new BusinessException("unhandled"); + final Retry retry = new Retry<>( + () -> { throw e; }, + 2, + 0 + ); + try { + retry.perform(); + } catch (BusinessException ex) { + //ignore + } + + assertThat( + retry.attempts(), + is(1) + ); + } + + /** + * Final number of attempts should be equal to the number of attempts asked because we are + * asking it to ignore the exception that will be thrown. + */ + @Test + public void ignore() throws Exception { + final BusinessException e = new CustomerNotFoundException("customer not found"); + final Retry retry = new Retry<>( + () -> { throw e; }, + 2, + 0, + ex -> CustomerNotFoundException.class.isAssignableFrom(ex.getClass()) + ); + try { + retry.perform(); + } catch (BusinessException ex) { + //ignore + } + + assertThat( + retry.attempts(), + is(2) + ); + } + +} \ No newline at end of file diff --git a/semaphore/pom.xml b/semaphore/pom.xml index 1b3bf8b5d..6337951a4 100644 --- a/semaphore/pom.xml +++ b/semaphore/pom.xml @@ -29,13 +29,18 @@ com.iluwatar java-design-patterns - 1.13.0-SNAPSHOT + 1.19.0-SNAPSHOT semaphore - junit - junit + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine test diff --git a/semaphore/src/main/java/com/iluwatar/semaphore/App.java b/semaphore/src/main/java/com/iluwatar/semaphore/App.java index 272de37b0..90b96cc6f 100644 --- a/semaphore/src/main/java/com/iluwatar/semaphore/App.java +++ b/semaphore/src/main/java/com/iluwatar/semaphore/App.java @@ -1,6 +1,6 @@ /** * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright (c) 2014-2016 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 diff --git a/semaphore/src/main/java/com/iluwatar/semaphore/Customer.java b/semaphore/src/main/java/com/iluwatar/semaphore/Customer.java index 0a4713438..9c480cb84 100644 --- a/semaphore/src/main/java/com/iluwatar/semaphore/Customer.java +++ b/semaphore/src/main/java/com/iluwatar/semaphore/Customer.java @@ -1,6 +1,6 @@ /** * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright (c) 2014-2016 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 @@ -22,12 +22,17 @@ */ package com.iluwatar.semaphore; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * A Customer attempts to repeatedly take Fruit from the FruitShop by * taking Fruit from FruitBowl instances. */ public class Customer extends Thread { + private static final Logger LOGGER = LoggerFactory.getLogger(Customer.class); + /** * Name of the Customer. */ @@ -63,13 +68,13 @@ public class Customer extends Thread { Fruit fruit; if (bowl != null && (fruit = bowl.take()) != null) { - System.out.println(name + " took an " + fruit); + LOGGER.info("{} took an {}", name, fruit); fruitBowl.put(fruit); fruitShop.returnBowl(bowl); } } - - System.out.println(name + " took " + fruitBowl); + + LOGGER.info("{} took {}", name, fruitBowl); } diff --git a/semaphore/src/main/java/com/iluwatar/semaphore/Fruit.java b/semaphore/src/main/java/com/iluwatar/semaphore/Fruit.java index 0a4224d20..88255997f 100644 --- a/semaphore/src/main/java/com/iluwatar/semaphore/Fruit.java +++ b/semaphore/src/main/java/com/iluwatar/semaphore/Fruit.java @@ -1,6 +1,6 @@ /** * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright (c) 2014-2016 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 @@ -23,36 +23,39 @@ package com.iluwatar.semaphore; /** - * Fruit is a resource stored in a FruitBowl. + * Fruit is a resource stored in a FruitBowl. */ public class Fruit { + /** + * Enumeration of Fruit Types + */ public static enum FruitType { ORANGE, APPLE, LEMON } - + private FruitType type; - + public Fruit(FruitType type) { this.type = type; } - + public FruitType getType() { return type; } - + /** * toString method - */ + */ public String toString() { switch (type) { case ORANGE: return "Orange"; - case APPLE: + case APPLE: return "Apple"; - case LEMON: + case LEMON: return "Lemon"; - default: + default: return ""; } } diff --git a/semaphore/src/main/java/com/iluwatar/semaphore/FruitBowl.java b/semaphore/src/main/java/com/iluwatar/semaphore/FruitBowl.java index ac2b87ada..a53672109 100644 --- a/semaphore/src/main/java/com/iluwatar/semaphore/FruitBowl.java +++ b/semaphore/src/main/java/com/iluwatar/semaphore/FruitBowl.java @@ -1,6 +1,6 @@ /** * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright (c) 2014-2016 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 @@ -22,6 +22,7 @@ */ package com.iluwatar.semaphore; +import java.util.List; import java.util.ArrayList; /** @@ -29,7 +30,7 @@ import java.util.ArrayList; */ public class FruitBowl { - private ArrayList fruit = new ArrayList<>(); + private List fruit = new ArrayList<>(); /** * diff --git a/semaphore/src/main/java/com/iluwatar/semaphore/FruitShop.java b/semaphore/src/main/java/com/iluwatar/semaphore/FruitShop.java index 315b46986..63c1dfa54 100644 --- a/semaphore/src/main/java/com/iluwatar/semaphore/FruitShop.java +++ b/semaphore/src/main/java/com/iluwatar/semaphore/FruitShop.java @@ -1,6 +1,6 @@ /** * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright (c) 2014-2016 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 diff --git a/semaphore/src/main/java/com/iluwatar/semaphore/Lock.java b/semaphore/src/main/java/com/iluwatar/semaphore/Lock.java index 4b651685c..d4f591754 100644 --- a/semaphore/src/main/java/com/iluwatar/semaphore/Lock.java +++ b/semaphore/src/main/java/com/iluwatar/semaphore/Lock.java @@ -1,6 +1,6 @@ /** * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright (c) 2014-2016 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 diff --git a/semaphore/src/main/java/com/iluwatar/semaphore/Semaphore.java b/semaphore/src/main/java/com/iluwatar/semaphore/Semaphore.java index 2e4a54c7c..01351ad9a 100644 --- a/semaphore/src/main/java/com/iluwatar/semaphore/Semaphore.java +++ b/semaphore/src/main/java/com/iluwatar/semaphore/Semaphore.java @@ -1,6 +1,6 @@ /** * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright (c) 2014-2016 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 diff --git a/semaphore/src/test/java/com/iluwatar/semaphore/AppTest.java b/semaphore/src/test/java/com/iluwatar/semaphore/AppTest.java index 4b286588d..6ad023e5d 100644 --- a/semaphore/src/test/java/com/iluwatar/semaphore/AppTest.java +++ b/semaphore/src/test/java/com/iluwatar/semaphore/AppTest.java @@ -1,6 +1,6 @@ /** * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright (c) 2014-2016 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 @@ -22,13 +22,17 @@ */ package com.iluwatar.semaphore; -import org.junit.Test; +import org.junit.jupiter.api.Test; + import java.io.IOException; -public class AppTest{ +/** + * Application Test Entrypoint + */ +public class AppTest { @Test public void test() throws IOException { String[] args = {}; App.main(args); } -} \ No newline at end of file +} diff --git a/semaphore/src/test/java/com/iluwatar/semaphore/FruitBowlTest.java b/semaphore/src/test/java/com/iluwatar/semaphore/FruitBowlTest.java index be31f77ab..7bc391478 100644 --- a/semaphore/src/test/java/com/iluwatar/semaphore/FruitBowlTest.java +++ b/semaphore/src/test/java/com/iluwatar/semaphore/FruitBowlTest.java @@ -1,6 +1,6 @@ /** * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright (c) 2014-2016 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 @@ -22,8 +22,11 @@ */ package com.iluwatar.semaphore; -import org.junit.Test; -import static org.junit.Assert.*; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; /** * Test taking from and putting Fruit into a FruitBowl diff --git a/semaphore/src/test/java/com/iluwatar/semaphore/SemaphoreTest.java b/semaphore/src/test/java/com/iluwatar/semaphore/SemaphoreTest.java index 14587a485..ad67026ae 100644 --- a/semaphore/src/test/java/com/iluwatar/semaphore/SemaphoreTest.java +++ b/semaphore/src/test/java/com/iluwatar/semaphore/SemaphoreTest.java @@ -1,6 +1,6 @@ /** * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright (c) 2014-2016 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 @@ -22,8 +22,10 @@ */ package com.iluwatar.semaphore; -import org.junit.Test; -import static org.junit.Assert.*; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; /** * Test case for acquiring and releasing a Semaphore diff --git a/servant/pom.xml b/servant/pom.xml index c235b005c..220e197f1 100644 --- a/servant/pom.xml +++ b/servant/pom.xml @@ -2,7 +2,7 @@ + + + service-layer.log + + service-layer-%d.log + 5 + + + %-5p [%d{ISO8601,UTC}] %c: %m%n + + + + + + %-5p [%d{ISO8601,UTC}] %c: %m%n + + + + + + + + + + + + + + + + + + diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/app/AppTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/app/AppTest.java index 3626c3339..295383513 100644 --- a/service-layer/src/test/java/com/iluwatar/servicelayer/app/AppTest.java +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/app/AppTest.java @@ -1,6 +1,6 @@ /** * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright (c) 2014-2016 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 @@ -23,9 +23,8 @@ package com.iluwatar.servicelayer.app; import com.iluwatar.servicelayer.hibernate.HibernateUtil; - -import org.junit.After; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; /** * @@ -40,7 +39,7 @@ public class AppTest { App.main(args); } - @After + @AfterEach public void tearDown() throws Exception { HibernateUtil.dropSession(); } diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/common/BaseDaoTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/common/BaseDaoTest.java index 789ded428..2bc557d75 100644 --- a/service-layer/src/test/java/com/iluwatar/servicelayer/common/BaseDaoTest.java +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/common/BaseDaoTest.java @@ -1,6 +1,6 @@ /** * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright (c) 2014-2016 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 @@ -23,22 +23,23 @@ package com.iluwatar.servicelayer.common; import com.iluwatar.servicelayer.hibernate.HibernateUtil; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; /** * Date: 12/28/15 - 10:53 PM - * + * Test for Base Data Access Objects + * @param Type of Base Entity + * @param Type of Dao Base Implementation * @author Jeroen Meulemeester */ public abstract class BaseDaoTest> { @@ -74,7 +75,7 @@ public abstract class BaseDaoTest this.dao = dao; } - @Before + @BeforeEach public void setUp() throws Exception { for (int i = 0; i < INITIAL_COUNT; i++) { final String className = dao.persistentClass.getSimpleName(); @@ -83,7 +84,7 @@ public abstract class BaseDaoTest } } - @After + @AfterEach public void tearDown() throws Exception { HibernateUtil.dropSession(); } @@ -142,4 +143,4 @@ public abstract class BaseDaoTest assertEquals(expectedName, entity.toString()); } -} \ No newline at end of file +} diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/magic/MagicServiceImplTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/magic/MagicServiceImplTest.java index dddc46916..66750c5b2 100644 --- a/service-layer/src/test/java/com/iluwatar/servicelayer/magic/MagicServiceImplTest.java +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/magic/MagicServiceImplTest.java @@ -1,6 +1,6 @@ /** * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright (c) 2014-2016 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 @@ -28,15 +28,14 @@ import com.iluwatar.servicelayer.spellbook.Spellbook; import com.iluwatar.servicelayer.spellbook.SpellbookDao; import com.iluwatar.servicelayer.wizard.Wizard; import com.iluwatar.servicelayer.wizard.WizardDao; - -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.HashSet; import java.util.List; import java.util.Set; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/spell/SpellDaoImplTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/spell/SpellDaoImplTest.java index 892ec6d2e..e4a5bfa65 100644 --- a/service-layer/src/test/java/com/iluwatar/servicelayer/spell/SpellDaoImplTest.java +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/spell/SpellDaoImplTest.java @@ -1,6 +1,6 @@ /** * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright (c) 2014-2016 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 @@ -23,13 +23,12 @@ package com.iluwatar.servicelayer.spell; import com.iluwatar.servicelayer.common.BaseDaoTest; - -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.List; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; /** * Date: 12/28/15 - 11:02 PM diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImplTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImplTest.java index 957c07bea..a6f1b3ea3 100644 --- a/service-layer/src/test/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImplTest.java +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImplTest.java @@ -1,6 +1,6 @@ /** * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright (c) 2014-2016 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 @@ -23,13 +23,12 @@ package com.iluwatar.servicelayer.spellbook; import com.iluwatar.servicelayer.common.BaseDaoTest; - -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.List; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; /** * Date: 12/28/15 - 11:44 PM diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/wizard/WizardDaoImplTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/wizard/WizardDaoImplTest.java index 8649ee0a0..d8a093319 100644 --- a/service-layer/src/test/java/com/iluwatar/servicelayer/wizard/WizardDaoImplTest.java +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/wizard/WizardDaoImplTest.java @@ -1,6 +1,6 @@ /** * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright (c) 2014-2016 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 @@ -23,13 +23,12 @@ package com.iluwatar.servicelayer.wizard; import com.iluwatar.servicelayer.common.BaseDaoTest; - -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.List; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; /** * Date: 12/28/15 - 11:46 PM diff --git a/service-locator/README.md b/service-locator/README.md index af4d8c3ac..974a2a5fe 100644 --- a/service-locator/README.md +++ b/service-locator/README.md @@ -32,6 +32,12 @@ improves the performance of application to great extent. * lookups of services are done quite frequently * large number of services are being used +## Consequences + +* Violates Interface Segregation Principle (ISP) by providing pattern consumers with an access +to a number of services that they don't potentially need. +* Creates hidden dependencies that can break the clients at runtime. + ## Credits * [J2EE Design Patterns](http://www.amazon.com/J2EE-Design-Patterns-William-Crawford/dp/0596004273/ref=sr_1_2) diff --git a/service-locator/pom.xml b/service-locator/pom.xml index 0f17b3dcc..a8fc5ceb1 100644 --- a/service-locator/pom.xml +++ b/service-locator/pom.xml @@ -2,7 +2,7 @@ + + + java-design-patterns + com.iluwatar + 1.19.0-SNAPSHOT + + 4.0.0 + + throttling + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + + \ No newline at end of file diff --git a/throttling/src/main/java/com/iluwatar/throttling/App.java b/throttling/src/main/java/com/iluwatar/throttling/App.java new file mode 100644 index 000000000..2dce68ab9 --- /dev/null +++ b/throttling/src/main/java/com/iluwatar/throttling/App.java @@ -0,0 +1,90 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 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.throttling; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.iluwatar.throttling.timer.Throttler; +import com.iluwatar.throttling.timer.ThrottleTimerImpl; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +/** + * Throttling pattern is a design pattern to throttle or limit the use of resources or even a complete service by + * users or a particular tenant. This can allow systems to continue to function and meet service level agreements, + * even when an increase in demand places load on resources. + *

+ * In this example we have ({@link App}) as the initiating point of the service. + * This is a time based throttling, i.e. only a certain number of calls are allowed per second. + *

+ * ({@link Tenant}) is the Tenant POJO class with which many tenants can be created + * ({@link B2BService}) is the service which is consumed by the tenants and is throttled. + */ +public class App { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + /** + * Application entry point + * @param args main arguments + */ + public static void main(String[] args) { + + Tenant adidas = new Tenant("Adidas", 5); + Tenant nike = new Tenant("Nike", 6); + + ExecutorService executorService = Executors.newFixedThreadPool(2); + + executorService.execute(() -> makeServiceCalls(adidas)); + executorService.execute(() -> makeServiceCalls(nike)); + + executorService.shutdown(); + try { + executorService.awaitTermination(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + LOGGER.error("Executor Service terminated: {}", e.getMessage()); + } + } + + /** + * Make calls to the B2BService dummy API + * @param service an instance of B2BService + */ + private static void makeServiceCalls(Tenant tenant) { + Throttler timer = new ThrottleTimerImpl(10); + B2BService service = new B2BService(timer); + for (int i = 0; i < 20; i++) { + service.dummyCustomerApi(tenant); +// Sleep is introduced to keep the output in check and easy to view and analyze the results. + try { + Thread.sleep(1); + } catch (InterruptedException e) { + LOGGER.error("Thread interrupted: {}", e.getMessage()); + } + } + } +} diff --git a/throttling/src/main/java/com/iluwatar/throttling/B2BService.java b/throttling/src/main/java/com/iluwatar/throttling/B2BService.java new file mode 100644 index 000000000..51ed492eb --- /dev/null +++ b/throttling/src/main/java/com/iluwatar/throttling/B2BService.java @@ -0,0 +1,62 @@ +/** + * The MIT License + * Copyright (c) 2014 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.throttling; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.iluwatar.throttling.timer.Throttler; + +import java.util.concurrent.ThreadLocalRandom; + +/** + * A service which accepts a tenant and throttles the resource based on the time given to the tenant. + */ +class B2BService { + + private static final Logger LOGGER = LoggerFactory.getLogger(B2BService.class); + + public B2BService(Throttler timer) { + timer.start(); + } + + /** + * + * @return customer id which is randomly generated + */ + public int dummyCustomerApi(Tenant tenant) { + String tenantName = tenant.getName(); + long count = CallsCount.getCount(tenantName); + LOGGER.debug("Counter for {} : {} ", tenant.getName(), count); + if (count >= tenant.getAllowedCallsPerSecond()) { + LOGGER.error("API access per second limit reached for: {}", tenantName); + return -1; + } + CallsCount.incrementCount(tenantName); + return getRandomCustomerId(); + } + + private int getRandomCustomerId() { + return ThreadLocalRandom.current().nextInt(1, 10000); + } +} diff --git a/throttling/src/main/java/com/iluwatar/throttling/CallsCount.java b/throttling/src/main/java/com/iluwatar/throttling/CallsCount.java new file mode 100644 index 000000000..9b274849a --- /dev/null +++ b/throttling/src/main/java/com/iluwatar/throttling/CallsCount.java @@ -0,0 +1,77 @@ +/** + * The MIT License + * Copyright (c) 2014 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.throttling; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; + +/** + * A class to keep track of the counter of different Tenants + * @author drastogi + * + */ +public final class CallsCount { + + private static final Logger LOGGER = LoggerFactory.getLogger(CallsCount.class); + private static Map tenantCallsCount = new ConcurrentHashMap<>(); + + /** + * Add a new tenant to the map. + * @param tenantName name of the tenant. + */ + public static void addTenant(String tenantName) { + tenantCallsCount.putIfAbsent(tenantName, new AtomicLong(0)); + } + + /** + * Increment the count of the specified tenant. + * @param tenantName name of the tenant. + */ + public static void incrementCount(String tenantName) { + tenantCallsCount.get(tenantName).incrementAndGet(); + } + + /** + * + * @param tenantName name of the tenant. + * @return the count of the tenant. + */ + public static long getCount(String tenantName) { + return tenantCallsCount.get(tenantName).get(); + } + + /** + * Resets the count of all the tenants in the map. + */ + public static void reset() { + LOGGER.debug("Resetting the map."); + for (Entry e : tenantCallsCount.entrySet()) { + tenantCallsCount.put(e.getKey(), new AtomicLong(0)); + } + } +} diff --git a/bridge/src/test/java/com/iluwatar/bridge/FlyingMagicWeaponTest.java b/throttling/src/main/java/com/iluwatar/throttling/Tenant.java similarity index 56% rename from bridge/src/test/java/com/iluwatar/bridge/FlyingMagicWeaponTest.java rename to throttling/src/main/java/com/iluwatar/throttling/Tenant.java index 9053a7bb0..a720e154b 100644 --- a/bridge/src/test/java/com/iluwatar/bridge/FlyingMagicWeaponTest.java +++ b/throttling/src/main/java/com/iluwatar/throttling/Tenant.java @@ -20,36 +20,38 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.bridge; +package com.iluwatar.throttling; -import org.junit.Test; - -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.internal.verification.VerificationModeFactory.times; +import java.security.InvalidParameterException; /** - * Date: 12/6/15 - 11:26 PM - * - * @author Jeroen Meulemeester + * A Pojo class to create a basic Tenant with the allowed calls per second. */ -public class FlyingMagicWeaponTest extends MagicWeaponTest { +public class Tenant { + + private String name; + private int allowedCallsPerSecond; /** - * Invoke all possible actions on the weapon and check if the actions are executed on the actual - * underlying weapon implementation. + * + * @param name Name of the tenant + * @param allowedCallsPerSecond The number of calls allowed for a particular tenant. + * @throws InvalidParameterException If number of calls is less than 0, throws exception. */ - @Test - public void testMjollnir() throws Exception { - final Mjollnir mjollnir = spy(new Mjollnir()); - final FlyingMagicWeapon flyingMagicWeapon = new FlyingMagicWeapon(mjollnir); - - testBasicWeaponActions(flyingMagicWeapon, mjollnir); - - flyingMagicWeapon.fly(); - verify(mjollnir, times(1)).flyImp(); - verifyNoMoreInteractions(mjollnir); + public Tenant(String name, int allowedCallsPerSecond) { + if (allowedCallsPerSecond < 0) { + throw new InvalidParameterException("Number of calls less than 0 not allowed"); + } + this.name = name; + this.allowedCallsPerSecond = allowedCallsPerSecond; + CallsCount.addTenant(name); } -} \ No newline at end of file + public String getName() { + return name; + } + + public int getAllowedCallsPerSecond() { + return allowedCallsPerSecond; + } +} diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/administration/LotteryAdministration.java b/throttling/src/main/java/com/iluwatar/throttling/timer/ThrottleTimerImpl.java similarity index 62% rename from hexagonal/src/main/java/com/iluwatar/hexagonal/administration/LotteryAdministration.java rename to throttling/src/main/java/com/iluwatar/throttling/timer/ThrottleTimerImpl.java index bc625b230..4ff4ce246 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/administration/LotteryAdministration.java +++ b/throttling/src/main/java/com/iluwatar/throttling/timer/ThrottleTimerImpl.java @@ -20,34 +20,39 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.hexagonal.administration; - -import java.util.Map; - -import com.iluwatar.hexagonal.domain.LotteryNumbers; -import com.iluwatar.hexagonal.domain.LotteryTicket; -import com.iluwatar.hexagonal.domain.LotteryTicketId; - /** * - * Administrator interface for lottery service. + */ +package com.iluwatar.throttling.timer; + +import java.util.Timer; +import java.util.TimerTask; + +import com.iluwatar.throttling.CallsCount; + +/** + * Implementation of throttler interface. This class resets the counter every second. + * @author drastogi * */ -public interface LotteryAdministration { +public class ThrottleTimerImpl implements Throttler { - /** - * Get all the lottery tickets submitted for lottery - */ - Map getAllSubmittedTickets(); - - /** - * Draw lottery numbers - */ - LotteryNumbers performLottery(); - - /** - * Begin new lottery round - */ - void resetLottery(); + private int throttlePeriod; + public ThrottleTimerImpl(int throttlePeriod) { + this.throttlePeriod = throttlePeriod; + } + + /** + * A timer is initiated with this method. The timer runs every second and resets the + * counter. + */ + public void start() { + new Timer(true).schedule(new TimerTask() { + @Override + public void run() { + CallsCount.reset(); + } + }, 0, throttlePeriod); + } } diff --git a/decorator/src/main/java/com/iluwatar/decorator/Hostile.java b/throttling/src/main/java/com/iluwatar/throttling/timer/Throttler.java similarity index 85% rename from decorator/src/main/java/com/iluwatar/decorator/Hostile.java rename to throttling/src/main/java/com/iluwatar/throttling/timer/Throttler.java index d3414c9dd..24b73d92f 100644 --- a/decorator/src/main/java/com/iluwatar/decorator/Hostile.java +++ b/throttling/src/main/java/com/iluwatar/throttling/timer/Throttler.java @@ -1,38 +1,36 @@ -/** - * The MIT License - * Copyright (c) 2014 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.decorator; - -/** - * - * Interface for the hostile enemies. - * - */ -public interface Hostile { - - void attack(); - - int getAttackPower(); - - void fleeBattle(); - -} +/** + * The MIT License + * Copyright (c) 2014 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.throttling.timer; + +/** + * An interface for defining the structure of different types of throttling ways. + * @author drastogi + * + */ +public interface Throttler { + + void start(); +} diff --git a/throttling/src/test/java/com/iluwatar/throttling/AppTest.java b/throttling/src/test/java/com/iluwatar/throttling/AppTest.java new file mode 100644 index 000000000..97a54864c --- /dev/null +++ b/throttling/src/test/java/com/iluwatar/throttling/AppTest.java @@ -0,0 +1,37 @@ +/** + * The MIT License + * Copyright (c) 2014 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.throttling; + +import org.junit.jupiter.api.Test; + +/** + * Application test + */ +public class AppTest { + + @Test + public void test() { + final String[] args = {}; + App.main(args); + } +} diff --git a/throttling/src/test/java/com/iluwatar/throttling/B2BServiceTest.java b/throttling/src/test/java/com/iluwatar/throttling/B2BServiceTest.java new file mode 100644 index 000000000..0c15bac6e --- /dev/null +++ b/throttling/src/test/java/com/iluwatar/throttling/B2BServiceTest.java @@ -0,0 +1,48 @@ +/** + * The MIT License + * Copyright (c) 2014 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.throttling; + +import com.iluwatar.throttling.timer.ThrottleTimerImpl; +import com.iluwatar.throttling.timer.Throttler; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * B2BServiceTest class to test the B2BService + */ +public class B2BServiceTest { + + @Test + public void dummyCustomerApiTest() { + Tenant tenant = new Tenant("testTenant", 2); + Throttler timer = new ThrottleTimerImpl(100); + B2BService service = new B2BService(timer); + + for (int i = 0; i < 5; i++) { + service.dummyCustomerApi(tenant); + } + long counter = CallsCount.getCount(tenant.getName()); + assertTrue(counter == 2, "Counter limit must be reached"); + } +} diff --git a/throttling/src/test/java/com/iluwatar/throttling/TenantTest.java b/throttling/src/test/java/com/iluwatar/throttling/TenantTest.java new file mode 100644 index 000000000..00e546d02 --- /dev/null +++ b/throttling/src/test/java/com/iluwatar/throttling/TenantTest.java @@ -0,0 +1,42 @@ +/** + * The MIT License + * Copyright (c) 2014 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.throttling; + +import org.junit.jupiter.api.Test; + +import java.security.InvalidParameterException; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * TenantTest to test the creation of Tenant with valid parameters. + */ +public class TenantTest { + + @Test + public void constructorTest() { + assertThrows(InvalidParameterException.class, () -> { + Tenant tenant = new Tenant("FailTenant", -1); + }); + } +} diff --git a/tls/README.md b/tls/README.md new file mode 100644 index 000000000..3fb5e9a6b --- /dev/null +++ b/tls/README.md @@ -0,0 +1,22 @@ +--- +layout: pattern +title: Thread Local Storage +folder: tls +permalink: /patterns/tls/ +pumlid: +categories: Concurrency +tags: + - Java + - Difficulty-Intermediate +--- + +## Intent +Securing variables global to a thread against being spoiled by other threads. That is needed if you use class variables or static variables in your Callable object or Runnable object that are not read-only. + +![alt text](./etc/tls.png "Thread Local Storage") + +## Applicability +Use the Thread Local Storage in any of the following situations + +* when you use class variables in your Callable / Runnable object that are not read-only and you use the same Callable instance in more than one thread running in parallel. +* when you use static variables in your Callable / Runnable object that are not read-only and more than one instances of the Callable / Runnable may run in parallel threads. diff --git a/tls/etc/tls.png b/tls/etc/tls.png new file mode 100644 index 000000000..442f39a0c Binary files /dev/null and b/tls/etc/tls.png differ diff --git a/composite/etc/composite.ucls b/tls/etc/tls.ucls similarity index 55% rename from composite/etc/composite.ucls rename to tls/etc/tls.ucls index 184c452f4..bd238346d 100644 --- a/composite/etc/composite.ucls +++ b/tls/etc/tls.ucls @@ -1,75 +1,79 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tls/pom.xml b/tls/pom.xml new file mode 100644 index 000000000..a25904707 --- /dev/null +++ b/tls/pom.xml @@ -0,0 +1,48 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.19.0-SNAPSHOT + + tls + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + diff --git a/tls/src/main/java/com/iluwatar/tls/App.java b/tls/src/main/java/com/iluwatar/tls/App.java new file mode 100644 index 000000000..634d36d26 --- /dev/null +++ b/tls/src/main/java/com/iluwatar/tls/App.java @@ -0,0 +1,146 @@ +/** + * The MIT License + * Copyright (c) 2016 Thomas Bauer + * + * 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.tls; + +import java.util.Calendar; +import java.util.Date; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +/** + * ThreadLocal pattern + *

+ * This App shows how to create an isolated space per each thread. In this + * example the usage of SimpleDateFormat is made to be thread-safe. This is an + * example of the ThreadLocal pattern. + *

+ * By applying the ThreadLocal pattern you can keep track of application + * instances or locale settings throughout the handling of a request. The + * ThreadLocal class works like a static variable, with the exception that it is + * only bound to the current thread! This allows us to use static variables in a + * thread-safe way. + *

+ * In Java, thread-local variables are implemented by the ThreadLocal class + * object. ThreadLocal holds a variable of type T, which is accessible via get/set + * methods. + *

+ * SimpleDateFormat is one of the basic Java classes and is not thread-safe. If + * you do not isolate the instance of SimpleDateFormat per each thread then + * problems arise. + *

+ * App converts the String date value 15/12/2015 to the Date format using the + * Java class SimpleDateFormat. It does this 20 times using 4 threads, each doing + * it 5 times. With the usage of as ThreadLocal in DateFormatCallable everything + * runs well. But if you comment out the ThreadLocal variant (marked with "//TLTL") + * and comment in the non ThreadLocal variant (marked with "//NTLNTL") you can + * see what will happen without the ThreadLocal. Most likely you will get incorrect + * date values and / or exceptions. + *

+ * This example clearly show what will happen when using non thread-safe classes + * in a thread. In real life this may happen one in of 1.000 or 10.000 conversions + * and those are really hard to find errors. + * + * @author Thomas Bauer, 2017 + */ +public class App { + /** + * Program entry point + * + * @param args + * command line args + */ + public static void main(String[] args) { + int counterDateValues = 0; + int counterExceptions = 0; + + // Create a callable + DateFormatCallable callableDf = new DateFormatCallable("dd/MM/yyyy", "15/12/2015"); + // start 4 threads, each using the same Callable instance + ExecutorService executor = Executors.newCachedThreadPool(); + + Future futureResult1 = executor.submit(callableDf); + Future futureResult2 = executor.submit(callableDf); + Future futureResult3 = executor.submit(callableDf); + Future futureResult4 = executor.submit(callableDf); + try { + Result[] result = new Result[4]; + result[0] = futureResult1.get(); + result[1] = futureResult2.get(); + result[2] = futureResult3.get(); + result[3] = futureResult4.get(); + + // Print results of thread executions (converted dates and raised exceptions) + // and count them + for (int i = 0; i < result.length; i++) { + counterDateValues = counterDateValues + printAndCountDates(result[i]); + counterExceptions = counterExceptions + printAndCountExceptions(result[i]); + } + + // a correct run should deliver 20 times 15.12.2015 + // and a correct run shouldn't deliver any exception + System.out.println("The List dateList contains " + counterDateValues + " date values"); + System.out.println("The List exceptionList contains " + counterExceptions + " exceptions"); + + } catch (Exception e) { + System.out.println("Abnormal end of program. Program throws exception: " + e); + } + executor.shutdown(); + } + + /** + * Print result (date values) of a thread execution and count dates + * + * @param res contains results of a thread execution + */ + private static int printAndCountDates(Result res) { + // a correct run should deliver 5 times 15.12.2015 per each thread + int counter = 0; + for (Date dt : res.getDateList()) { + counter++; + Calendar cal = Calendar.getInstance(); + cal.setTime(dt); + // Formatted output of the date value: DD.MM.YYYY + System.out.println( + cal.get(Calendar.DAY_OF_MONTH) + "." + cal.get(Calendar.MONTH) + "." + +cal.get(Calendar.YEAR)); + } + return counter; + } + + /** + * Print result (exceptions) of a thread execution and count exceptions + * + * @param res contains results of a thread execution + * @return number of dates + */ + private static int printAndCountExceptions(Result res) { + // a correct run shouldn't deliver any exception + int counter = 0; + for (String ex : res.getExceptionList()) { + counter++; + System.out.println(ex); + } + return counter; + } +} diff --git a/tls/src/main/java/com/iluwatar/tls/DateFormatCallable.java b/tls/src/main/java/com/iluwatar/tls/DateFormatCallable.java new file mode 100644 index 000000000..a28e24d4e --- /dev/null +++ b/tls/src/main/java/com/iluwatar/tls/DateFormatCallable.java @@ -0,0 +1,98 @@ +/** + * The MIT License + * Copyright (c) 2016 Thomas Bauer + * + * 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.tls; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.concurrent.Callable; + +/** + * DateFormatCallable converts string dates to a date format using + * SimpleDateFormat. The date format and the date value will be passed to the + * Callable by the constructor. The constructor creates a instance of + * SimpleDateFormat and stores it in a ThreadLocal class variable. For the + * complete description of the example see {@link App} + * + * You can comment out the code marked with //TLTL and comment in the + * code marked //NTLNTL. Then you can see what will happen if you do not + * use the ThreadLocal. For details see the description of {@link App} + * + * @author Thomas Bauer, 2017 + */ +public class DateFormatCallable implements Callable { + // class variables (members) + private ThreadLocal df; //TLTL + // private DateFormat df; //NTLNTL + + private String dateValue; // for dateValue Thread Local not needed + + + /** + * The date format and the date value are passed to the constructor + * + * @param inDateFormat + * string date format string, e.g. "dd/MM/yyyy" + * @param inDateValue + * string date value, e.g. "21/06/2016" + */ + public DateFormatCallable(String inDateFormat, String inDateValue) { + final String idf = inDateFormat; //TLTL + this.df = new ThreadLocal() { //TLTL + @Override //TLTL + protected DateFormat initialValue() { //TLTL + return new SimpleDateFormat(idf); //TLTL + } //TLTL + }; //TLTL + // this.df = new SimpleDateFormat(inDateFormat); //NTLNTL + this.dateValue = inDateValue; + } + + /** + * @see java.util.concurrent.Callable#call() + */ + @Override + public Result call() { + System.out.println(Thread.currentThread() + " started executing..."); + Result result = new Result(); + + // Convert date value to date 5 times + for (int i = 1; i <= 5; i++) { + try { + // this is the statement where it is important to have the + // instance of SimpleDateFormat locally + // Create the date value and store it in dateList + result.getDateList().add(this.df.get().parse(this.dateValue)); //TLTL +// result.getDateList().add(this.df.parse(this.dateValue)); //NTLNTL + } catch (Exception e) { + // write the Exception to a list and continue work + result.getExceptionList().add(e.getClass() + ": " + e.getMessage()); + } + + } + + System.out.println(Thread.currentThread() + " finished processing part of the thread"); + + return result; + } +} diff --git a/bridge/src/test/java/com/iluwatar/bridge/BlindingMagicWeaponTest.java b/tls/src/main/java/com/iluwatar/tls/Result.java similarity index 54% rename from bridge/src/test/java/com/iluwatar/bridge/BlindingMagicWeaponTest.java rename to tls/src/main/java/com/iluwatar/tls/Result.java index 54435555e..69ec5c502 100644 --- a/bridge/src/test/java/com/iluwatar/bridge/BlindingMagicWeaponTest.java +++ b/tls/src/main/java/com/iluwatar/tls/Result.java @@ -1,55 +1,62 @@ -/** - * The MIT License - * Copyright (c) 2014 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.bridge; - -import org.junit.Test; - -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.internal.verification.VerificationModeFactory.times; - -/** - * Date: 12/6/15 - 11:15 PM - * - * @author Jeroen Meulemeester - */ -public class BlindingMagicWeaponTest extends MagicWeaponTest { - - /** - * Invoke all possible actions on the weapon and check if the actions are executed on the actual - * underlying weapon implementation. - */ - @Test - public void testExcalibur() throws Exception { - final Excalibur excalibur = spy(new Excalibur()); - final BlindingMagicWeapon blindingMagicWeapon = new BlindingMagicWeapon(excalibur); - - testBasicWeaponActions(blindingMagicWeapon, excalibur); - - blindingMagicWeapon.blind(); - verify(excalibur, times(1)).blindImp(); - verifyNoMoreInteractions(excalibur); - } - -} +/** + * The MIT License + * Copyright (c) 2014 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. + */ +/* + * Fiducia IT AG, All rights reserved. Use is subject to license terms. + */ + +package com.iluwatar.tls; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * Result object that will be returned by the Callable {@link DateFormatCallable} + * used in {@link App} + * + * @author Thomas Bauer, 2017 + */ +public class Result { + // A list to collect the date values created in one thread + private List dateList = new ArrayList(); + + // A list to collect Exceptions thrown in one threads (should be none in + // this example) + private List exceptionList = new ArrayList(); + + /** + * + * @return List of date values collected within an thread execution + */ + public List getDateList() { + return dateList; + } + + /** + * + * @return List of exceptions thrown within an thread execution + */ + public List getExceptionList() { + return exceptionList; + } +} diff --git a/tls/src/test/java/com/iluwatar/tls/AppTest.java b/tls/src/test/java/com/iluwatar/tls/AppTest.java new file mode 100644 index 000000000..12bb1ea3c --- /dev/null +++ b/tls/src/test/java/com/iluwatar/tls/AppTest.java @@ -0,0 +1,40 @@ +/** + * The MIT License + * Copyright (c) 2016 Thomas Bauer + * + * 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.tls; + +import org.junit.jupiter.api.Test; + +/** + * Tests that thread local storage example runs without errors. + * + * @author Thomas Bauer, January 2017 + * + */ +public class AppTest { + @Test + public void test() throws Exception { + String[] args = {}; + App.main(args); + } +} diff --git a/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTest.java b/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTest.java new file mode 100644 index 000000000..bb2dfafb7 --- /dev/null +++ b/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTest.java @@ -0,0 +1,145 @@ +/** + * The MIT License + * Copyright (c) 2016 Thomas Bauer + * + * 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.tls; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * + * Test of the Callable + * + * In this test {@link DateFormatCallable} is tested with only one thread (i.e. without concurrency situation) + *

+ * After a successful run 5 date values should be in the result object. All dates should have + * the same value (15.11.2015). To avoid problems with time zone not the date instances themselves + * are compared by the test. For the test the dates are converted into string format DD.MM.YYY + *

+ * Additionally the number of list entries are tested for both the list with the date values + * and the list with the exceptions + * + * @author Thomas Bauer, January 2017 + * + */ +public class DateFormatCallableTest { + + // Class variables used in setup() have to be static because setup() has to be static + /** + * Result object given back by DateFormatCallable + * -- Array with converted date values + * -- Array with thrown exceptions + */ + static Result result; + + /** + * The date values created by the run of of DateFormatRunnalbe. List will be filled in the setup() method + */ + static List createdDateValues = new ArrayList(); + + /** + * Expected number of date values in the date value list created by the run of DateFormatRunnalbe + */ + int expectedCounterDateValues = 5; + + /** + * Expected number of exceptions in the exception list created by the run of DateFormatRunnalbe. + */ + int expectedCounterExceptions = 0; + + /** + * Expected content of the list containing the date values created by the run of DateFormatRunnalbe + */ + List expectedDateValues = Arrays.asList("15.11.2015", "15.11.2015", "15.11.2015", "15.11.2015", "15.11.2015"); + + /** + * Run Callable and prepare results for usage in the test methods + */ + @BeforeAll + public static void setup() { + // Create a callable + DateFormatCallable callableDf = new DateFormatCallable("dd/MM/yyyy", "15/12/2015"); + // start thread using the Callable instance + ExecutorService executor = Executors.newCachedThreadPool(); + Future futureResult = executor.submit(callableDf); + try { + result = futureResult.get(); + createdDateValues = convertDatesToString(result); + } catch (Exception e) { + fail("Setup failed: " + e); + } + executor.shutdown(); + } + + private static List convertDatesToString(Result res) { + // Format date value as DD.MM.YYYY + if (res == null || res.getDateList() == null || res.getDateList().size() == 0) { + return null; + } + List returnList = new ArrayList(); + + for (Date dt : res.getDateList()) { + Calendar cal = Calendar.getInstance(); + cal.setTime(dt); + returnList.add(cal.get(Calendar.DAY_OF_MONTH) + "." + cal.get(Calendar.MONTH) + "." + cal.get(Calendar.YEAR)); + } + return returnList; + } + + /** + * Test date values after the run of DateFormatRunnalbe. A correct run should deliver 5 times 15.12.2015 + */ + @Test + public void testDateValues() { + assertEquals(expectedDateValues, createdDateValues); + } + + /** + * Test number of dates in the list after the run of DateFormatRunnalbe. A correct run should deliver 5 date values + */ + @Test + public void testCounterDateValues() { + assertEquals(expectedCounterDateValues, result.getDateList().size()); + } + + /** + * Test number of Exceptions in the list after the run of DateFormatRunnalbe. A correct run should deliver + * no exceptions + */ + @Test + public void testCounterExceptions() { + assertEquals(expectedCounterExceptions, result.getExceptionList().size()); + } +} diff --git a/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestIncorrectDateFormat.java b/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestIncorrectDateFormat.java new file mode 100644 index 000000000..da1b9c264 --- /dev/null +++ b/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestIncorrectDateFormat.java @@ -0,0 +1,128 @@ +/** + * The MIT License + * Copyright (c) 2016 Thomas Bauer + * + * 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.tls; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * + * Test of the Callable + * + * In this test {@link DateFormatCallable} is tested with only one thread (i.e. without concurrency situation) + *

+ * An incorrect formatted date is passed to the Callable + * After a successful run 0 date values and 5 exceptions should be in the result object. + * + * @author Thomas Bauer, January 2017 + * + */ +public class DateFormatCallableTestIncorrectDateFormat { + + // Class variables used in setup() have to be static because setup() has to be static + /** + * Result object given back by DateFormatCallable + * -- Array with converted date values + * -- Array with thrown exceptions + */ + static Result result; + + /** + * The date values created by the run of DateFormatRunnalbe. List will be filled in the setup() method + */ + static List createdExceptions = new ArrayList(); + + /** + * Expected number of date values in the date value list created by the run of DateFormatRunnalbe + */ + int expectedCounterDateValues = 0; + + /** + * Expected number of exceptions in the exception list created by the run of DateFormatRunnalbe. + */ + int expectedCounterExceptions = 5; + + /** + * Expected content of the list containing the exceptions created by the run of DateFormatRunnalbe + */ + List expectedExceptions = Arrays.asList("class java.text.ParseException: Unparseable date: \"15.12.2015\"", + "class java.text.ParseException: Unparseable date: \"15.12.2015\"", + "class java.text.ParseException: Unparseable date: \"15.12.2015\"", + "class java.text.ParseException: Unparseable date: \"15.12.2015\"", + "class java.text.ParseException: Unparseable date: \"15.12.2015\""); + + /** + * Run Callable and prepare results for usage in the test methods + */ + @BeforeAll + public static void setup() { + // Create a callable. Pass a string date value not matching the format string + DateFormatCallable callableDf = new DateFormatCallable("dd/MM/yyyy", "15.12.2015"); + // start thread using the Callable instance + ExecutorService executor = Executors.newCachedThreadPool(); + Future futureResult = executor.submit(callableDf); + try { + result = futureResult.get(); + } catch (Exception e) { + fail("Setup failed: " + e); + } + executor.shutdown(); + } + + /** + * Test Exceptions after the run of DateFormatRunnalbe. A correct run should deliver 5 times the + * same exception + */ + @Test + public void testExecptions() { + assertEquals(expectedExceptions, result.getExceptionList()); + } + + /** + * Test number of dates in the list after the run of DateFormatRunnalbe. A correct run should deliver no date values + */ + @Test + public void testCounterDateValues() { + assertEquals(expectedCounterDateValues, result.getDateList().size()); + } + + /** + * Test number of Exceptions in the list after the run of DateFormatRunnalbe. A correct run should + * deliver 5 exceptions + */ + @Test + public void testCounterExceptions() { + assertEquals(expectedCounterExceptions, result.getExceptionList().size()); + } +} diff --git a/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestMultiThread.java b/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestMultiThread.java new file mode 100644 index 000000000..845ab366d --- /dev/null +++ b/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestMultiThread.java @@ -0,0 +1,165 @@ +/** + * The MIT License + * Copyright (c) 2016 Thomas Bauer + * + * 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.tls; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * + * Test of the Callable + * + * In this test {@link DateFormatCallable} is used by 4 threads in parallel + *

+ * After a successful run 5 date values should be in the result object of each thread. All dates + * should have the same value (15.11.2015). To avoid problems with time zone not the date instances + * themselves are compared by the test. For the test the dates are converted into string format DD.MM.YYY + *

+ * Additionally the number of list entries are tested for both the list with the date values + * and the list with the exceptions + * + * @author Thomas Bauer, January 2017 + * + */ +public class DateFormatCallableTestMultiThread { + + // Class variables used in setup() have to be static because setup() has to be static + /** + * Result object given back by DateFormatCallable, one for each thread + * -- Array with converted date values + * -- Array with thrown exceptions + */ + static Result[] result = new Result[4]; + + /** + * The date values created by the run of of DateFormatRunnalbe. List will be filled in the setup() method + */ + @SuppressWarnings("serial") + static class StringArrayList extends ArrayList { + /* nothing needed here */ + } + static List[] createdDateValues = new StringArrayList[4]; + + /** + * Expected number of date values in the date value list created by each thread + */ + int expectedCounterDateValues = 5; + + /** + * Expected number of exceptions in the exception list created by each thread + */ + int expectedCounterExceptions = 0; + + /** + * Expected content of the list containing the date values created by each thread + */ + List expectedDateValues = Arrays.asList("15.11.2015", "15.11.2015", "15.11.2015", "15.11.2015", "15.11.2015"); + + /** + * Run Callable and prepare results for usage in the test methods + */ + @BeforeAll + public static void setup() { + // Create a callable + DateFormatCallable callableDf = new DateFormatCallable("dd/MM/yyyy", "15/12/2015"); + // start thread using the Callable instance + ExecutorService executor = Executors.newCachedThreadPool(); + Future futureResult1 = executor.submit(callableDf); + Future futureResult2 = executor.submit(callableDf); + Future futureResult3 = executor.submit(callableDf); + Future futureResult4 = executor.submit(callableDf); + try { + result[0] = futureResult1.get(); + result[1] = futureResult2.get(); + result[2] = futureResult3.get(); + result[3] = futureResult4.get(); + for (int i = 0; i < result.length; i++) { + createdDateValues[i] = convertDatesToString(result[i]); + } + } catch (Exception e) { + fail("Setup failed: " + e); + } + executor.shutdown(); + } + + private static List convertDatesToString(Result res) { + // Format date value as DD.MM.YYYY + if (res == null || res.getDateList() == null || res.getDateList().size() == 0) { + return null; + } + List returnList = new StringArrayList(); + + for (Date dt : res.getDateList()) { + Calendar cal = Calendar.getInstance(); + cal.setTime(dt); + returnList.add(cal.get(Calendar.DAY_OF_MONTH) + "." + cal.get(Calendar.MONTH) + "." + cal.get(Calendar.YEAR)); + } + return returnList; + } + + /** + * Test date values after the run of DateFormatRunnalbe. A correct run should deliver 5 times 15.12.2015 + * by each thread + */ + @Test + public void testDateValues() { + for (int i = 0; i < createdDateValues.length; i++) { + assertEquals(expectedDateValues, createdDateValues[i]); + } + } + + /** + * Test number of dates in the list after the run of DateFormatRunnalbe. A correct run should + * deliver 5 date values by each thread + */ + @Test + public void testCounterDateValues() { + for (int i = 0; i < result.length; i++) { + assertEquals(expectedCounterDateValues, result[i].getDateList().size()); + } + } + + /** + * Test number of Exceptions in the list after the run of DateFormatRunnalbe. A correct run should + * deliver no exceptions + */ + @Test + public void testCounterExceptions() { + for (int i = 0; i < result.length; i++) { + assertEquals(expectedCounterExceptions, result[i].getExceptionList().size()); + } + } +} diff --git a/tolerant-reader/pom.xml b/tolerant-reader/pom.xml index e5dd3ba88..3acc0b188 100644 --- a/tolerant-reader/pom.xml +++ b/tolerant-reader/pom.xml @@ -2,7 +2,7 @@ "-studentDatabase" StudentDatabase +StudentRepository ..|> IUnitOfWork +@enduml \ No newline at end of file diff --git a/unit-of-work/pom.xml b/unit-of-work/pom.xml new file mode 100644 index 000000000..8a75b16af --- /dev/null +++ b/unit-of-work/pom.xml @@ -0,0 +1,54 @@ + + + + + java-design-patterns + com.iluwatar + 1.19.0-SNAPSHOT + + 4.0.0 + + unit-of-work + + + junit + junit + + + org.junit.vintage + junit-vintage-engine + test + + + org.mockito + mockito-core + + + + + \ No newline at end of file diff --git a/unit-of-work/src/main/java/com/iluwatar/unitofwork/App.java b/unit-of-work/src/main/java/com/iluwatar/unitofwork/App.java new file mode 100644 index 000000000..9b4b2f15c --- /dev/null +++ b/unit-of-work/src/main/java/com/iluwatar/unitofwork/App.java @@ -0,0 +1,52 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2017 Piyush Chaudhari + * + * 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.unitofwork; + +import java.util.HashMap; +import java.util.List; + +/** + * {@link App} Application for managing student data. + */ +public class App { + /** + * + * @param args no argument sent + */ + public static void main(String[] args) { + Student ram = new Student(1, "Ram", "Street 9, Cupertino"); + Student shyam = new Student(2, "Shyam", "Z bridge, Pune"); + Student gopi = new Student(3, "Gopi", "Street 10, Mumbai"); + + HashMap> context = new HashMap<>(); + StudentDatabase studentDatabase = new StudentDatabase(); + StudentRepository studentRepository = new StudentRepository(context, studentDatabase); + + studentRepository.registerNew(ram); + studentRepository.registerModified(shyam); + studentRepository.registerDeleted(gopi); + studentRepository.commit(); + } +} diff --git a/unit-of-work/src/main/java/com/iluwatar/unitofwork/IUnitOfWork.java b/unit-of-work/src/main/java/com/iluwatar/unitofwork/IUnitOfWork.java new file mode 100644 index 000000000..e0b453498 --- /dev/null +++ b/unit-of-work/src/main/java/com/iluwatar/unitofwork/IUnitOfWork.java @@ -0,0 +1,55 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2017 Piyush Chaudhari + * + * 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.unitofwork; + +/** + * @param Any generic entity + */ +public interface IUnitOfWork { + String INSERT = "INSERT"; + String DELETE = "DELETE"; + String MODIFY = "MODIFY"; + + /** + * Any register new operation occurring on UnitOfWork is only going to be performed on commit. + */ + void registerNew(T entity); + + /** + * Any register modify operation occurring on UnitOfWork is only going to be performed on commit. + */ + void registerModified(T entity); + + /** + * Any register delete operation occurring on UnitOfWork is only going to be performed on commit. + */ + void registerDeleted(T entity); + + /*** + * All UnitOfWork operations batched together executed in commit only. + */ + void commit(); + +} \ No newline at end of file diff --git a/unit-of-work/src/main/java/com/iluwatar/unitofwork/Student.java b/unit-of-work/src/main/java/com/iluwatar/unitofwork/Student.java new file mode 100644 index 000000000..5a57ccdde --- /dev/null +++ b/unit-of-work/src/main/java/com/iluwatar/unitofwork/Student.java @@ -0,0 +1,57 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2017 Piyush Chaudhari + * + * 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.unitofwork; + +/** + * {@link Student} is an entity. + */ +public class Student { + private final Integer id; + private final String name; + private final String address; + + /** + * @param id student unique id + * @param name name of student + * @param address address of student + */ + public Student(Integer id, String name, String address) { + this.id = id; + this.name = name; + this.address = address; + } + + public String getName() { + return name; + } + + public Integer getId() { + return id; + } + + public String getAddress() { + return address; + } +} diff --git a/unit-of-work/src/main/java/com/iluwatar/unitofwork/StudentDatabase.java b/unit-of-work/src/main/java/com/iluwatar/unitofwork/StudentDatabase.java new file mode 100644 index 000000000..6b0e85517 --- /dev/null +++ b/unit-of-work/src/main/java/com/iluwatar/unitofwork/StudentDatabase.java @@ -0,0 +1,43 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2017 Piyush Chaudhari + * + * 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.unitofwork; + +/** + * Act as Database for student records. + */ +public class StudentDatabase { + + public void insert(Student student) { + //Some insert logic to DB + } + + public void modify(Student student) { + //Some modify logic to DB + } + + public void delete(Student student) { + //Some delete logic to DB + } +} diff --git a/unit-of-work/src/main/java/com/iluwatar/unitofwork/StudentRepository.java b/unit-of-work/src/main/java/com/iluwatar/unitofwork/StudentRepository.java new file mode 100644 index 000000000..ff1136620 --- /dev/null +++ b/unit-of-work/src/main/java/com/iluwatar/unitofwork/StudentRepository.java @@ -0,0 +1,126 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2017 Piyush Chaudhari + * + * 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.unitofwork; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * {@link StudentRepository} Student database repository. + * supports unit of work for student data. + */ +public class StudentRepository implements IUnitOfWork { + private static final Logger LOGGER = LoggerFactory.getLogger(StudentRepository.class); + + private Map> context; + private StudentDatabase studentDatabase; + + /** + * @param context set of operations to be perform during commit. + * @param studentDatabase Database for student records. + */ + public StudentRepository(Map> context, StudentDatabase studentDatabase) { + this.context = context; + this.studentDatabase = studentDatabase; + } + + @Override + public void registerNew(Student student) { + LOGGER.info("Registering {} for insert in context.", student.getName()); + register(student, IUnitOfWork.INSERT); + } + + @Override + public void registerModified(Student student) { + LOGGER.info("Registering {} for modify in context.", student.getName()); + register(student, IUnitOfWork.MODIFY); + + } + + @Override + public void registerDeleted(Student student) { + LOGGER.info("Registering {} for delete in context.", student.getName()); + register(student, IUnitOfWork.DELETE); + } + + private void register(Student student, String operation) { + List studentsToOperate = context.get(operation); + if (studentsToOperate == null) { + studentsToOperate = new ArrayList<>(); + } + studentsToOperate.add(student); + context.put(operation, studentsToOperate); + } + + /** + * All UnitOfWork operations are batched and executed together on commit only. + */ + @Override + public void commit() { + if (context == null || context.size() == 0) { + return; + } + LOGGER.info("Commit started"); + if (context.containsKey(IUnitOfWork.INSERT)) { + commitInsert(); + } + + if (context.containsKey(IUnitOfWork.MODIFY)) { + commitModify(); + } + if (context.containsKey(IUnitOfWork.DELETE)) { + commitDelete(); + } + LOGGER.info("Commit finished."); + } + + private void commitInsert() { + List studentsToBeInserted = context.get(IUnitOfWork.INSERT); + for (Student student : studentsToBeInserted) { + LOGGER.info("Saving {} to database.", student.getName()); + studentDatabase.insert(student); + } + } + + private void commitModify() { + List modifiedStudents = context.get(IUnitOfWork.MODIFY); + for (Student student : modifiedStudents) { + LOGGER.info("Modifying {} to database.", student.getName()); + studentDatabase.modify(student); + } + } + + private void commitDelete() { + List deletedStudents = context.get(IUnitOfWork.DELETE); + for (Student student : deletedStudents) { + LOGGER.info("Deleting {} to database.", student.getName()); + studentDatabase.delete(student); + } + } +} diff --git a/unit-of-work/src/test/java/com/iluwatar/unitofwork/AppTest.java b/unit-of-work/src/test/java/com/iluwatar/unitofwork/AppTest.java new file mode 100644 index 000000000..942db781f --- /dev/null +++ b/unit-of-work/src/test/java/com/iluwatar/unitofwork/AppTest.java @@ -0,0 +1,40 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2017 Piyush Chaudhari + * + * 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.unitofwork; + +import org.junit.Test; + +import java.io.IOException; + +/** + * AppTest + */ +public class AppTest { + @Test + public void test() throws IOException { + String[] args = {}; + App.main(args); + } +} diff --git a/unit-of-work/src/test/java/com/iluwatar/unitofwork/StudentRepositoryTest.java b/unit-of-work/src/test/java/com/iluwatar/unitofwork/StudentRepositoryTest.java new file mode 100644 index 000000000..ee63442a5 --- /dev/null +++ b/unit-of-work/src/test/java/com/iluwatar/unitofwork/StudentRepositoryTest.java @@ -0,0 +1,147 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2017 Piyush Chaudhari + * + * 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.unitofwork; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.*; + +/** + * tests {@link StudentRepository} + */ +@RunWith(MockitoJUnitRunner.class) +public class StudentRepositoryTest { + private final Student student1 = new Student(1, "Ram", "street 9, cupertino"); + private final Student student2 = new Student(1, "Sham", "Z bridge, pune"); + + private Map> context; + @Mock + private StudentDatabase studentDatabase; + private StudentRepository studentRepository; + + @Before + public void setUp() throws Exception { + context = new HashMap<>(); + studentRepository = new StudentRepository(context, studentDatabase); + } + + @Test + public void shouldSaveNewStudentWithoutWritingToDb() throws Exception { + studentRepository.registerNew(student1); + studentRepository.registerNew(student2); + + assertEquals(2, context.get(IUnitOfWork.INSERT).size()); + verifyNoMoreInteractions(studentDatabase); + } + + @Test + public void shouldSaveDeletedStudentWithoutWritingToDb() throws Exception { + studentRepository.registerDeleted(student1); + studentRepository.registerDeleted(student2); + + assertEquals(2, context.get(IUnitOfWork.DELETE).size()); + verifyNoMoreInteractions(studentDatabase); + } + + @Test + public void shouldSaveModifiedStudentWithoutWritingToDb() throws Exception { + studentRepository.registerModified(student1); + studentRepository.registerModified(student2); + + assertEquals(2, context.get(IUnitOfWork.MODIFY).size()); + verifyNoMoreInteractions(studentDatabase); + } + + @Test + public void shouldSaveAllLocalChangesToDb() throws Exception { + context.put(IUnitOfWork.INSERT, Collections.singletonList(student1)); + context.put(IUnitOfWork.MODIFY, Collections.singletonList(student1)); + context.put(IUnitOfWork.DELETE, Collections.singletonList(student1)); + + studentRepository.commit(); + + verify(studentDatabase, times(1)).insert(student1); + verify(studentDatabase, times(1)).modify(student1); + verify(studentDatabase, times(1)).delete(student1); + } + + @Test + public void shouldNotWriteToDbIfContextIsNull() throws Exception { + StudentRepository studentRepository = new StudentRepository(null, studentDatabase); + + studentRepository.commit(); + + verifyNoMoreInteractions(studentDatabase); + } + + @Test + public void shouldNotWriteToDbIfNothingToCommit() throws Exception { + StudentRepository studentRepository = new StudentRepository(new HashMap<>(), studentDatabase); + + studentRepository.commit(); + + verifyZeroInteractions(studentDatabase); + } + + @Test + public void shouldNotInsertToDbIfNoRegisteredStudentsToBeCommitted() throws Exception { + context.put(IUnitOfWork.MODIFY, Collections.singletonList(student1)); + context.put(IUnitOfWork.DELETE, Collections.singletonList(student1)); + + studentRepository.commit(); + + verify(studentDatabase, never()).insert(student1); + } + + @Test + public void shouldNotModifyToDbIfNotRegisteredStudentsToBeCommitted() throws Exception { + context.put(IUnitOfWork.INSERT, Collections.singletonList(student1)); + context.put(IUnitOfWork.DELETE, Collections.singletonList(student1)); + + studentRepository.commit(); + + verify(studentDatabase, never()).modify(student1); + } + + @Test + public void shouldNotDeleteFromDbIfNotRegisteredStudentsToBeCommitted() throws Exception { + context.put(IUnitOfWork.INSERT, Collections.singletonList(student1)); + context.put(IUnitOfWork.MODIFY, Collections.singletonList(student1)); + + studentRepository.commit(); + + verify(studentDatabase, never()).delete(student1); + } +} \ No newline at end of file diff --git a/update-ghpages.sh b/update-ghpages.sh index aee888ba9..d240453b0 100644 --- a/update-ghpages.sh +++ b/update-ghpages.sh @@ -1,7 +1,7 @@ #!/bin/bash # # The MIT License -# Copyright (c) 2014 Ilkka Seppälä +# Copyright (c) 2014-2016 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 diff --git a/value-object/pom.xml b/value-object/pom.xml index ec8de1681..82fca9508 100644 --- a/value-object/pom.xml +++ b/value-object/pom.xml @@ -2,7 +2,7 @@