diff --git a/.gitignore b/.gitignore index 5d2cd77e176a..589d3fb13b6f 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,9 @@ target .idea *.iml *.swp -datanucleus.log \ No newline at end of file +datanucleus.log +/bin/ +/bin/ +/bin/ + +data-mapper/src/main/resources/log4j.xml \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index deb436cd2db3..817b6635f021 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://sonarqube.com -Dsonar.login=$SONAR_TOKEN - bash update-ghpages.sh +# 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: false # route the build to the container-based infrastructure for a faster build diff --git a/README.md b/README.md index 8db718edbf72..6eed9561b551 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,9 @@ # 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://sonarqube.com/api/badges/gate?key=com.iluwatar%3Ajava-design-patterns)](https://sonarqube.com/dashboard/index/com.iluwatar%3Ajava-design-patterns) # Introduction @@ -23,7 +24,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/_scripts/postPumlsToServer.firstrun.output b/_scripts/postPumlsToServer.firstrun.output new file mode 100644 index 000000000000..ea77a49a51f8 --- /dev/null +++ b/_scripts/postPumlsToServer.firstrun.output @@ -0,0 +1,190 @@ +parent: half-sync-half-async; artifact: half-sync-half-async +Puml Server ID: RScv3SCm3030LU819FRPXg5fIm552tnYPFiyjRi3RkbAaYkdoQr5JBy369vrxz7oaSv6XmPhL3e6TCaJ0msU-CAoilTToyG8DdKOw5z0GzcAlvNAN_WZSD1brBHHPmxv0000 +parent: abstract-document; artifact: abstract-document +Puml Server ID: PSjB3eCm34NHhPG599vtDyQn85L-ifzX-p3lxEf8Twj3MXGDQvyJMFubChxpKN767gucSq07iinEjSNDOACVNvoAUZr6MWoe3QVE_WRnxZ0Mf38b-hkIGlurX_MyehS7 +parent: tolerant-reader; artifact: tolerant-reader +Puml Server ID: NSZ14SCm20NHLf829ExfXaYChGn26lZ4xSVdtFRjSrZJx9AkZnFOyI9olkenSEOxGxmjWnXgMvE6viLWfmz_kNI9SLZP38XRqEIuWx1Kd0t5XVjjGVj_DNtMdLD_ +parent: event-driven-architecture; artifact: event-driven-architecture +Puml Server ID: TOhH3SCW30LNQGS0_tSRnrZ15H1adfFromBzkfFktZQaHT7mzgh0N1yYvoUVXXf7B7Mv1dGWozN9MZmCTlhopQdeidEaoO3wMDHvRI6zzvwAssPYbsfGGRYIGlxN7DxpZDv- +parent: publish-subscribe; artifact: publish-subscribe +Puml Server ID: PSZB3SCm203GLTe1RExT1XCKKs5YyMdMR--zFRsd66aTNAwFcRdZ1U1uzrDorgXWfykIBJjT2qJhnaI7Dtwm7HnoMjkOoMu12-C7s3LKOhQe4UGo63ZfVtlvwhkMVW40 +parent: facade; artifact: facade +Puml Server ID: BSP15eCm20N0gxG7CEoz3ILKqvTW7dpq-hhehERTJ7fMJU-l7PYn4ZbVPMlOyvEXBeT13KMEGQtdnM2d7v-yL8sssJ8PKBUWmV64lYnSbHJoRqaVPUReDm00 +parent: service-locator; artifact: service-locator +Puml Server ID: NSjB3iCm203HgxG7iDdtDeIWX0fZYqzo_MRTtUX9ynOZhPtBzNLchlW0EDxza3nhgs2dQScMdUO0qRenqU6B5xQTGmvh2pFPBM1WF07FSmbnqqcOqu6J_gsNZxvgw0y0 +parent: dao; artifact: dao +Puml Server ID: 5SR14OKW30N0LhG0oVrt4o6ZE12Ov4NR_thQNQlc5aN2sd82qtz4naywAixOmyNoK8WYvT6fjdWOR7JnpLiHhuTkam4nTUhiRwZm847-J64zpUZj3m00 +parent: model-view-presenter; artifact: model-view-presenter +Puml Server ID: ROlR3SGW3C1MkGu0-RzjKeXQJWcWFChwPO3xispvQBrmL0hbp-q-xGkWkFBL_8upZBICxjGzbo7GE1OwAlpmmLJ9sjNJH7VIRY1e6q169KvFevMcakrtI_BoD-HGoJE4Nm00 +parent: observer; artifact: observer +Puml Server ID: FSkn4OGm30NHLg00hFow4KO3PcpP8tr1-pYwx6smQz5Suv2mkbp0y1-HyPlEWYlsSB7S5Q98kJSgDLu66ztyy7Q8brEtmO2OEZNs2Uhxl9u9GVv72cjfHAiV +parent: intercepting-filter; artifact: intercepting-filter +Puml Server ID: RSfB3i8m303Hgy014k-vZN5DQkIuaJ_q-fGzkz7JtCL8Q-DolUsPAnu0ZcSVadizAzZfi6JBJiS4qJenqU6D7smRXmnh2pFPBM1YN05o_KwyKcoqb-ZFEEcVz_BPLqtz0W00 +parent: factory-method; artifact: factory-method +Puml Server ID: NSZB3G8n30N0Lg20n7UwCOxPP9MVx6TMT0zdRgEvjoazYeRrMmMsFuYChtmqr7Y6gycQq8aiQr3hSJ7OwEGtfwBUZfas0shJQR3_G2yMBFkaeQYha4B-AeUDl6FqBm00 +parent: private-class-data; artifact: private-class-data +Puml Server ID: RShR3SCm243HLTe1RFwx3S4eeSB4uf6itmpGlwkZ-nOZhS7b-ZeoLtm07E--InwrLR3JQScMdSu9edLZeiCNBso3GtPh2pFPBM1YF07BvSBaHeeHRJm_SD8VxkMphvhw0m00 +parent: async-method-invocation; artifact: async-method-invocation +Puml Server ID: TSdB3SCW303GLTe1mFTkunWhk0A3_4dKxTi5UdlIUuhIoCPfuz4Zjhy03EzwIlGyqjbeQR16fJL1HjuOQF362qjZbrFBnWWsTPZeFm3wHwbCZhvQ4RqMOSXIuA1_LzDctJd75m00 +parent: execute-around; artifact: execute-around +Puml Server ID: NSZ14G8n20NGLhI0XBlT865suoGa0n_NylNixSsxTvEHJTF7xGHsF8YShtfqdFdCK9TbK4ELDQcFl1ZizE8tbwRH3okR0NKBcXm_a7vK4bhOLreZXVnLJPzrvnnV +parent: monostate; artifact: monostate +Puml Server ID: HSV14OGm20NGLjO23FVj1YEZsGaa0nzjVxrvUszfLdlkaju_9p3ZI-HybwFXp2r3l0w364eTIgtdpM2d7r-yxXBji7Ko86v1ol60TDW8C8G4zLr9rp9J-ny0 +parent: thread-pool; artifact: thread-pool +Puml Server ID: JSV14SCW30J0Lk82GFzq8uF6a1624IUx_UIPt-xHhMXK2TTN0zP-4pa_-UfeSSOMBzCWXbpceAxnCDZfmpUdAhjVbXO3uhPfyFw1q5oufZMdag3yFuUFl6Be5m00 +parent: delegation; artifact: delegation +Puml Server ID: JSV14GCX20NGLf82LkxfXbN69OFeu2VRVdBCxRsdUhLiac6F2rZxHHHybwwuyimjKQT37ANEGMfvCpZepHy-ccpjVYm697pJuFq3DJ7f39rEWlhNaZ7Aoc5V +parent: chain; artifact: chain +Puml Server ID: 9SR13SCm20NGLTe1OkxTXX0KKzd4Wa-pVYlrdTxJN4OTMZ4U7LZv8Wg-ssdejLTgoELGHvDhaesw6HpqvWzlXwQTlYq6D3nfSlv2qjcS5F9VgvXjrHnV +parent: resource-acquisition-is-initialization; artifact: resource-acquisition-is-initialization +Puml Server ID: ZShR3S8m343HLUW0YV_PnhXMQvGumOzMOdhA1lqxkhgBABLSEQqzzeZfJm33isuIUxxIsMXei4QbqK5QdXXeyCO3oyekcvQ94MpgqD4lWB6FDEA2z4bn2HbQn8leHMponNy13hgvrhHUP_Rs0m00 +parent: fluentinterface; artifact: fluentinterface +Puml Server ID: NOj93eCm302_KXv0VEzlN6F0bMCYB_3zvjpRQ3IpY97MnkNwEZD7l04SdtP8dlMfOAVBaYqRNHr4wy54Xo_Uk6uSSjWwC9FT0Zh61DYrPY_pyXs9WPF-NIllRLJN7m00 +parent: service-layer; artifact: service-layer +Puml Server ID: LOl93SCm3C1MQGUmzUysgY8aAcJ5q96WszVV_aW2V8gHriRb-ZWoPxm07E--Inxrhc2dqv8jEvq3HEl6H8SFNjWs3jcjJSnaju21iG3MSmbnK_mkuwJ_qij7dpNq1m00 +parent: visitor; artifact: visitor +Puml Server ID: DSR14OGm20NGLhG0mtsxmSWeJa8oyD7sTo_xJczLgoqFIM_B1Spu43c_vLHSkMU8rs4GGwcZaxPy6UnqyyFR8Q6dRPC1SGlg7B_Gew4OJeBwVqdlPMPlNm00 +parent: double-dispatch; artifact: double-dispatch +Puml Server ID: NSbB3iCW303HgpG70Ezx6yTOWSeOv4zp_MRTtUZDCPGa6wV9gqTiVmCOtlKQqVDCPwEbmHgLreGXUMEWmGU_M1hxkBHiZ61JXud-1BILft1fmvz37JZetshQh3kd_000 +parent: monad; artifact: monad +Puml Server ID: 9SR13SCm20NGLPe1OkxTXjWeSMMm1Pza_LRgExsjMntP97syBc35cyZvAMV7bKU6U9q6CPGwbVh8Xy5E7xvvRnBzj7qn86v1ol4BwJHk9AZ_bNGjAtLy0G00 +parent: front-controller; artifact: front-controller +Puml Server ID: PSlB3OGm303HLfO24j-t6nCC13bEvC_IFk6yjz6JPgbIE3OAvS_fFkmBe7Zde_ePQnXfwU8adajlK3bkT5Iuy8Tf8wk7f87kf6BGq6R0hlD8xwQTUG9v-SCSslA8nWy0 +parent: strategy; artifact: strategy +Puml Server ID: FSV13OCm30NGLM00udktCS4AGOaJsTz5tRwSkBstLiqj3WbhombC_n0PtwbKdB67Y-MX44NAerDjSJFOwE8lRuTuBRfD1iJKgRC_88SnfFn8aD-ai9vczFO7 +parent: command; artifact: command +Puml Server ID: DSgn4OCm30NGLM00h3xR25i7vYpXaxx2-g59zugtTgiZcwIFvGHcV8YSdt9qdBbdYDVR88PIRwK-yc6mqyLVtff4FsoR38XRa7Aye3SgMoD1_RkaQvcfumS0 +parent: abstract-factory; artifact: abstract-factory +Puml Server ID: PSZB3OD034NHLa81Czwd6sCC39gVxEUWT1_ssLmTtQLqgR5fM7sTmFGtaV6TZu8prd0r6HtQaMKqAZLk1XjT_E6qgPUZfyc0MdTgx0-8LuUn8ErFXdr98NypXxKyezKV +parent: flux; artifact: flux +Puml Server ID: 7SP14eCm20NGg-W13FlU1YFLE0GpyAazVZk-rPkRLSrDqdKwW14l8kUxx0r7hXdYzJA8eTIhKzEy6UnqyeUNJQBjjWm6n2seS_n3Ryql2UgJajxBoAu_ +parent: event-aggregator; artifact: event-aggregator +Puml Server ID: PSf13iCW30NHgxG70Ezx6uTOX0eCih-JwvTzTwEdUJSjFKu9wwyBMFuXCdvoRRZY21ShKo6ANEQWrkDXiD6NRqwdUAkQ5WDYwZJOTv3SUqzSgqbbp0qeVvZ3Hbun-Wy0 +parent: singleton; artifact: singleton +Puml Server ID: HSV14SCm20J0Lk82BFxf1ikCh0n26ZZizfDVVhjRjwfvIhg-Bc35cyZvAQtZoYD3l4w364gTWxhcms2d3z-ydnAzsRuO4BUWmV43HRUcWcaagF-Lz55M3lq2 +parent: null-object; artifact: null-object +Puml Server ID: JSV14SCm20J0Lk829Fxf1cF6bWSX3JhYzfDdVhjRSx4yDCDU5p3NcoZugMV3bNik3HaETLGPdPhbm-2WcpzS3btjz38PqF15dTSFv6bMndwhW1Jo_vhHwynkNm00 +parent: multiton; artifact: multiton +Puml Server ID: FST14i8m20NGg-W16lRUXgPCYnD81Zxs-hfozzvJlOywf68yBc3bYoZuRgVYghrIea-7E5gVHZhgPd3Gcp-y7P9w-hOOaF0au_o1h0OKqqdG_saLrbRP-080 +parent: composite; artifact: composite +Puml Server ID: HSf13eCm30NHgy01YFUzZGaM62LEP7-NwvTTT_EaMTLgoqFIst81Cpv4payv5LVk6U9r6CHGwkYaBHy6EztyvUsGqDEsoO2u1NMED-WTvmY5aA3-LT9xcTdR3m00 +parent: api-gateway; artifact: image-microservice +Puml Server ID: 3Sp13SCm2030LTe1RFxTXX3aK1biOOZLxPlVlUujHZrFJk-lAsAk3u3ZhatYoYCNEmqBjgWq5AJdna27BzvOJbxIh4oCOBS5Yki1u9JIC7ZZ3pW8HB5nKI4VJtSBSKtNEbFx7m00 +I dont want to program this, just add the following lines to the README.md file that corresponds to this puml file 'api-gateway/etc/image-microservice.urm.puml' +pumlid: 3Sp13SCm2030LTe1RFxTXX3aK1biOOZLxPlVlUujHZrFJk-lAsAk3u3ZhatYoYCNEmqBjgWq5AJdna27BzvOJbxIh4oCOBS5Yki1u9JIC7ZZ3pW8HB5nKI4VJtSBSKtNEbFx7m00 +parent: api-gateway; artifact: api-gateway-service +Puml Server ID: JSox3SCm303HLP819FRUXg49cO542_nOyFPncUvUSszHwhbpMdyT4TCt0CDLcyIHdtGsEZLOez8vG7ek33JuueLbPvUcPM84cpeCz2S0fvI6mGjluA1_b-Tt2N5D6tNcw3y0 +I dont want to program this, just add the following lines to the README.md file that corresponds to this puml file 'api-gateway/etc/api-gateway-service.urm.puml' +pumlid: JSox3SCm303HLP819FRUXg49cO542_nOyFPncUvUSszHwhbpMdyT4TCt0CDLcyIHdtGsEZLOez8vG7ek33JuueLbPvUcPM84cpeCz2S0fvI6mGjluA1_b-Tt2N5D6tNcw3y0 +parent: api-gateway; artifact: price-microservice +Puml Server ID: 3Sn13iGW243HgqmFeEpdDfGIoqJK8DJqzkFklyq_f56DYyFgvtOVymjWk78Hl-ECoKQzEJVFr1Mana97Wny-c2wUKbeQwCxM9YZE7O13Ka7dXI-m4mmJugH2rlVksSXXcaTe_GC0 +I dont want to program this, just add the following lines to the README.md file that corresponds to this puml file 'api-gateway/etc/price-microservice.urm.puml' +pumlid: 3Sn13iGW243HgqmFeEpdDfGIoqJK8DJqzkFklyq_f56DYyFgvtOVymjWk78Hl-ECoKQzEJVFr1Mana97Wny-c2wUKbeQwCxM9YZE7O13Ka7dXI-m4mmJugH2rlVksSXXcaTe_GC0 +parent: object-pool; artifact: object-pool +Puml Server ID: JSV94SCm2030Lk829Fxf1cF6bWU1XYDkFtdcjxiD9Qc3o-LrPQvu0pW-_HnvrLx1JgR9cfrimf1wCD7XnW-sWsESsXPcicl0nFW1RB-PiYqp0KxwVo-VVTMKBm00 +parent: adapter; artifact: adapter +Puml Server ID: DSR14S8m30J0Lg20M7-wEMnDOiPMFDA9j0yyUEtUkzMHJTF7xI1NF4GSLzaxZtncgDVJgCPIpobzv0N2vOKtjgRHTziMI7KBcOXl10thfxB-Nz9dMJd71m00 +parent: hexagonal; artifact: hexagonal +Puml Server ID: HSTB4W8X30N0g-W1XkozpPD90LO8L3wEnzUTk-xxq2fvSfhSUiJs1v7XAcr4psSwMrqQh57gcZGaBmICNdZZEDb7qsCZWasT9lm7wln1MmeXZlfVIPjbvvGl +parent: value-object; artifact: value-object +Puml Server ID: LSZ13SCm20NGLTe1RExTXX2KECBOmfza_VRQszDxDnVBNJFiTG9pVOY2dteqdBdbqf3XK4ULqQbPFWmEklZcikjgXvV9W8Olwhn-e9ijjOpjKW4fv2zgHgypktq1 +parent: twin; artifact: twin +Puml Server ID: 7SR13OCm30NGLUW0n7UsCS42eyH4zdUpFbNVwNtKQij3qjjo0ICs8kTPJiMLUuPuVGnYAFNff2qdWvrk_l9wIEXfws10t88wno-4gKQ2-az9xsLaRoy0 +parent: semaphore; artifact: semaphore +Puml Server ID: HSV14SCm20J0Lk82BFxf1ikCfOn06ZZizfDVVhjRjphobFJnQi2ADv7pKwwEbaU6U9q6CPGwbVh8Xy5E7xvvFoNwPVjYGDo2bEC72b5URRgGeFvNqhMirF45 +parent: message-channel; artifact: message-channel +Puml Server ID: NSZB3SCm203GLTe1RExTXX1akm9YyMdMRy-zFRtdCf8wkLmUCtF72y3nxcFbhAE2dIvBjknqAIof6nCTtlZ1TdAiOMrZ9hi5ACOFe1o1WnjDD6C1Jlg_NgvzbyeN +parent: poison-pill; artifact: poison-pill +Puml Server ID: JSZ14SCm20NHLf82BExfXiYCJGOX3NpYzkDZRllsgTwjTgcmnmciV145N-rGdFMkbEZJ8OxMvo2rkXWSzE4lRxka7huj1YGyQN3UGMjgpdkh6Gdwlrl5QAk6_G00 +parent: aggregator-microservices; artifact: aggregator-service +Puml Server ID: JOov3SCm301NIGQGs7iRXYPa1g8ayB7NjuiKwGvtmBrbKC-Tq_hhY5Y-0HXUjKaS-Kbdepc2HrIQ2jBpma23BvvOTdPfeooCO1iEYlu0O6l63MDQKI6Rp-CKOWSE-ey_NzEqhjH-0m00 +I dont want to program this, just add the following lines to the README.md file that corresponds to this puml file 'aggregator-microservices/etc/aggregator-service.urm.puml' +pumlid: JOov3SCm301NIGQGs7iRXYPa1g8ayB7NjuiKwGvtmBrbKC-Tq_hhY5Y-0HXUjKaS-Kbdepc2HrIQ2jBpma23BvvOTdPfeooCO1iEYlu0O6l63MDQKI6Rp-CKOWSE-ey_NzEqhjH-0m00 +parent: aggregator-microservices; artifact: information-microservice +Puml Server ID: LSnB3i8m303Hgy016k-vZN5DQXGxaJ_jzUcMtKXFcgSOZTgvV3oEp1Kl0CUhTScZtXNiD2tPij5Ka54N9ZfyySHjvv1ksy9CTWjGZ3i0UtVkcDCt5V9vFquX3k0a4FjCLqoPzgUjNDig7Jy0 +I dont want to program this, just add the following lines to the README.md file that corresponds to this puml file 'aggregator-microservices/etc/information-microservice.urm.puml' +pumlid: LSnB3i8m303Hgy016k-vZN5DQXGxaJ_jzUcMtKXFcgSOZTgvV3oEp1Kl0CUhTScZtXNiD2tPij5Ka54N9ZfyySHjvv1ksy9CTWjGZ3i0UtVkcDCt5V9vFquX3k0a4FjCLqoPzgUjNDig7Jy0 +parent: aggregator-microservices; artifact: inventory-microservice +Puml Server ID: LSpB3G8n303HLg20ZUzqOxnMrYXn8d-oedjovJRIIEyfIYrFJckFAsBw2y3mBbNYodSw6mqDrYWqEaZB6mCDFhZmEDcbwZ4nWaqTEleEm5gDAyQmemlPsCOIOWSE0j6riM7VlrVIUfdPsmy0 +I dont want to program this, just add the following lines to the README.md file that corresponds to this puml file 'aggregator-microservices/etc/inventory-microservice.urm.puml' +pumlid: LSpB3G8n303HLg20ZUzqOxnMrYXn8d-oedjovJRIIEyfIYrFJckFAsBw2y3mBbNYodSw6mqDrYWqEaZB6mCDFhZmEDcbwZ4nWaqTEleEm5gDAyQmemlPsCOIOWSE0j6riM7VlrVIUfdPsmy0 +parent: bridge; artifact: bridge +Puml Server ID: BSR14SCm20J0Lf82BFxf1akCJ4R26ZZYzkE7zxLljJgoIVfu7S2A3v7pLRhYo3r3l9u6CPHwJjAH5uETllpZhKbejsqn86v1a-CExQwj2mdgqv8-oyev_W00 +parent: servant; artifact: servant +Puml Server ID: DSkn4O0m20NGLNG0G-ys63cDbv0SV7HzRUnUy-QYkSOkONKwWU4haV6JZe8pjd2nt1MYIBatAZKU1XjTVFEoYvT3by60c3erzW_qdPiL9CY_KrXB8rfz0G00 +parent: lazy-loading; artifact: lazy-loading +Puml Server ID: LSXB3W8X303Gg-W1e7jlqu66gIc5zED4JwzRTo_lpjeaEwN9xOpO_W0mlEhWEFD89sjBWpHgMnDOyi90WoU-i7Ho7besHf2fmqJ_0GG_xo8BE-i0YlONDMtMdLE- +parent: flyweight; artifact: flyweight +Puml Server ID: HSV94S8m3030Lg20M7-w4OvYAoCh7Xtnq3ty-Eq-MQlaJcdow17JNm26gpIEdkzqidffa4Qfrm2MN1XeSEADsqxEJRU94MJgCD1_W4C-YxZr08hwNqaRPUQGBm00 +parent: mutex; artifact: mutex +Puml Server ID: 9SR13OCm30NGLSe0n7UsCS62LB69x6zWV2hrdTxKhFRS9Br_3c34GkHybxtXo3L3l9u6CPHwAhMUDuETldpnl4cqtUR1WBW5ASSlf0bvI53_A-bQHcf_0G00 +parent: mediator; artifact: mediator +Puml Server ID: FSV14SCm20J0Lk82BFxf1akCJKOW3JhizfDNVhkRUktP9AE_Bc2kDr7mKqx5bKSkYJeSuYXr66dFXy517xvvRxBqz7qo8E6BZDSFPDAKCO84zP-IOMMczIy0 +parent: page-object; artifact: page-object +Puml Server ID: JSV14OGW30NGLjO28FVj9iOCua1Wme-sxnxtzjvMJLeS6ju-9p3NbyZvoQNYZ3sMkWo36hACJhN5ms2dYszEXwvQB4q6r6rHv_K3JIwQndwfW1Jo_npUyupUNW00 +parent: factory-kit; artifact: factory-kit +Puml Server ID: JST15i8m20N0g-W14lRU1YcsQ4BooCS-RwzBTpDNSscvQKQx7C1SDwBWi-w68--vD6Gur55bTBAM9uE3dlpcikcotSjaGCCNTLu_q8C58pxbPI25_Bzcz3gpjoy0 +parent: property; artifact: property +Puml Server ID: FSV13OCm30NGLTe1YEziumOBKYMEPN-3s9wUUdlltRJst2Izlmx0OYLolihUSEGdGxnEXIXAdODQpul1Jby-UTaasgwBCI2kGOFZ1pAV9ewR1FMVaZwAvUWF +parent: dependency-injection; artifact: dependency-injection +Puml Server ID: RSdB3SCW303GLPe1mFTkunWhSGG6-PEesxS3zFQajubIpyPf_NL6B7y363xra3XpJsUZgS4QbUO0wVbWeC65DvR6BeUMXH5iwZ3GVu36YxMnqgU8NamXKu63_aPD6tNbw5y0 +parent: layers; artifact: layers +Puml Server ID: BSR13OCm30NGLSe0n7UsCS62L8w9x6yGszD3t-bDpQhc9kdwEO0H2v7pNVQ68zSCyNeQn53gsQbftWns-lB5yoRHTfi70-8Mr3b-8UL7F4XG_otflOpi-W80 +parent: producer-consumer; artifact: producer-consumer +Puml Server ID: PSjB3iCW303HgxG70Ezx6zTO2HKso9_a-c7VtUX9y-vA8nkdZTSPiVm3O7ZNeyUPttGscXgiKMaAz94t1XhyyCBIsFkXPM44cpe8-WvODbiIMzcdfspXe7-jQL9NodW0 +parent: builder; artifact: builder +Puml Server ID: DSR94O0m2030LhG0mzzkC64KXs26GzlNZw_TcRLADagJwOWOlW8OFcNdE79B9wkN1ccKUdLWoGS33KwySMdalEioC89C7Jhw5zYIfNrIrFybhPUHNLu0 +parent: specification; artifact: specification +Puml Server ID: LSX14i8m20NGg-W16lRU1YcsE0d9mCTUNxVkthoxkVJQjQBVJc3bWoZuQeVXh6UbXao7EfhCGTRhOd3Gcp-yxPfs-BOOqF2amVa3vLAnbmd3ffD2_gTLZBPgz2y0 +parent: state; artifact: state +Puml Server ID: 9SRH3O0m20N0LNG0ox_RO2LQqz867hg-9jxNpKLpZLt2wdG2mrSYuoST1MTiuMAvAqIHSczKQZmCDhhuvcKNBuSkWm4nTMhiNyZ141BaVocifH6jlW00 +parent: reader-writer-lock; artifact: reader-writer-lock +Puml Server ID: RSZB4S8m303HLg00MtUw4R8cCP5bZpwuVL80jttxx4gIZTFaSKOiVm4OxdhqEFETpaPJWpKgpG5TScEWmGU_M1fxFxGiZ61JXu5-1nXZOolR-gqYaoxWe3-xfeswSiWF +parent: interpreter; artifact: interpreter +Puml Server ID: JSf13eCm30NHgz034E-vZGaM62Kcih_BzQ6xxjv8yr6hBJT9RzC1Z5Y8dE-oAuvSCyJhPH13gLSdRNapsEdaBy-RXEus3mR4BQXpl21zVnykFmlgVvVqNaRszW00 +parent: template-method; artifact: template-method +Puml Server ID: NSZ13SCW30NGLPe1mFTkuu0Lg6n0vZjPlpttzlIEFef6bN1zDM3jDv7paw-E5cTiyJ87P22NQTGr7WOxVVZcL6NtQwJ5WFZOPBn_88WjPKWoGPkL1EN_ShZb5QPV +parent: feature-toggle; artifact: feature-toggle +Puml Server ID: NSZ14G8X30NGLhG0oDrk8XjPd12OvCTjNy_UthpxiAPvIBhUJc37WyZvgdtWp6U6U5i6CTIs9WtDYy5ER_vmEIH6jx8P4BUWoV43lOIHBWMhTnKIjB-gwRFkdFe5 +parent: business-delegate; artifact: business-delegate +Puml Server ID: POl13SCm3CHMQGU8zUysgYCuBcJ5a4x9-l6_Fu84tzsgvYxf-Zg06HyYvxkqZYE_6UBrD8YXr7DGrxmPxFJZYxTTeZVR9WFY5ZGu5j2wkad4wYgD8IIe_xQaZp9pw0C0 +parent: naked-objects; artifact: naked-objects-integtests +Puml Server ID: LSmn4iCW30NHgoG70FMvZGmQ6ni48tt5ru_RT3kls7VJqgDAM7yTmF8FaV6TzuOZjd2nCXMYo6KEQZrk1XkT_ELKnTkkQJ4Wfaw3_GbIlgIckPrIu2Ge_vBQyziX3izX8wyO_GS0 +I dont want to program this, just add the following lines to the README.md file that corresponds to this puml file 'naked-objects/etc/naked-objects-integtests.urm.puml' +pumlid: LSmn4iCW30NHgoG70FMvZGmQ6ni48tt5ru_RT3kls7VJqgDAM7yTmF8FaV6TzuOZjd2nCXMYo6KEQZrk1XkT_ELKnTkkQJ4Wfaw3_GbIlgIckPrIu2Ge_vBQyziX3izX8wyO_GS0 +parent: naked-objects; artifact: naked-objects-dom +Puml Server ID: LSZ94SCW3030Lf82G7zt8mkDZOC4eyDkF_dcjxFlhZIoSTfudH7BDm33fnuzpjpJsMXgi4QbAT17FXXeSE6DfR7tGyl223Pr4FGVGF73hSpzOWe73lgVqgRKDAahPNm1 +I dont want to program this, just add the following lines to the README.md file that corresponds to this puml file 'naked-objects/etc/naked-objects-dom.urm.puml' +pumlid: LSZ94SCW3030Lf82G7zt8mkDZOC4eyDkF_dcjxFlhZIoSTfudH7BDm33fnuzpjpJsMXgi4QbAT17FXXeSE6DfR7tGyl223Pr4FGVGF73hSpzOWe73lgVqgRKDAahPNm1 +parent: naked-objects; artifact: naked-objects-fixture +Puml Server ID: LSX15i8W30N0g-W187jlaq9igH1uoO_r-BfrDs_kJKkFAc7zTW3B7qJ6LzuRZjZ2nSfKY2ANEQZrk1XiTFARKnLlkwR5W9Ww3VOVIFabDStjb08dGVcVz6mVX4aE6td5w5y0 +I dont want to program this, just add the following lines to the README.md file that corresponds to this puml file 'naked-objects/etc/naked-objects-fixture.urm.puml' +pumlid: LSX15i8W30N0g-W187jlaq9igH1uoO_r-BfrDs_kJKkFAc7zTW3B7qJ6LzuRZjZ2nSfKY2ANEQZrk1XiTFARKnLlkwR5W9Ww3VOVIFabDStjb08dGVcVz6mVX4aE6td5w5y0 +parent: model-view-controller; artifact: model-view-controller +Puml Server ID: ROl13SCm201NQGUm-NSRQgE42h258Lw_wR-_qvtkoTOaEwNBuuoOwmNWkEl1SUOx5taR5cHHsr1WoOs13X-yi7HQV5YP645k2nJN3Q2ZavIBQPVVwqFajXJjVwdfMcUgV040 +parent: proxy; artifact: proxy +Puml Server ID: 9SR13OCm30NGLM00udktCS62eCI9x6yesrEfx_Jcehd69c5rEe3X7oBZE-q5HwpXOhahH95oRrHgt0msEldYPHClkow30J5rQko_qB3-VKYG_qjXBOrezGK0 +parent: memento; artifact: memento +Puml Server ID: DSgn4OCm30NGLM00h3xR2AC3SvRiaxx2-g59zugtDgiz3qdlomNC-10vF-Lik7BF4A_388PIXrBh-J3OwUOlRuT4EssR38XRa7Ay81Lz_o11_RkaQvcf_GS0 +parent: decorator; artifact: decorator +Puml Server ID: HSV14SCm20J0Lk82BFxf1YF6LaP26ZZizfDVVhjRC-bPDRs_Bc35cyZvAMV3bKU6kao36ehCGQtdms2d3z-yLursshuOKBUWmV43LPNfZEcaaFzA-YWhH_y2 +parent: data-mapper; artifact: data-mapper +Puml Server ID: JShB3OGm303HLg20nFVjnYGM1CN6ycTfVtFSsnjfzY5jPgUqkLqHwXy0mxUU8wuyqidQ8q4IjJqCO-QBWGOtVh5qyd5AKOmW4mT6Nu2-ZiAekapH_hkcSTNa-GC0 +parent: caching; artifact: caching +Puml Server ID: DSRB4OKm2030LhG0m_rrWyWaE0bc-6ZxpujxsbMKUXwSrfSMCVq7OFYKAj5oJsUZIuCr2bq3fEU3WGOdthWTx59rcnZ1fWu3_GqGKXEjm47VIzeeCqV_0m00 +parent: reactor; artifact: reactor +Puml Server ID: DSR14OGm20NGLjO23FVj1f7Hx2Ga0nzjVxtuJc-f9YrtJM-V4vZn9NA-or5nvfQXBiEWXYAZKsrvCzZfnnUlkqOzR9qCg5jGvtX2hYmOJWfvNz9xcTdR7m00 +parent: iterator; artifact: iterator +Puml Server ID: FSV13OGm30NHLg00uljsOu85HeaJsTzB-yjfBwCtgrfjUKXwMovWneV8-IcduiezGxmEWnXA7PsqvSDWfvk_l1qIUjes6H2teCxnWlGDOpW9wdzAUYypU_i1 +parent: callback; artifact: callback +Puml Server ID: FSVB4S8m30N0Lg20M7UwUL4qYOciUFGXxSE9s-wp6sjjKgwF8tF6YyXnjxtdKMk5E5-MOjdu6jIrRYIStlXWsIJwRij4fhW53SGFn51TmIT9yZ-jVBHPGxy0 +parent: repository; artifact: repository +Puml Server ID: JSV13OCm30NGLM00udktCS42eyI9xE-YRjyUUtjlLQij3qblomNCU14vF-LKNBbdYDTX44EfevEsV1ZiTFERjqD2Jzic0-8Mr3b-89SvGZ7yGuBwrvBUoypUlW00 +parent: mute-idiom; artifact: mute-idiom +Puml Server ID: JSf13iCm20NHgxG7iDdtDjH62PKX5luarq-MtSsJvgtUHdR96AyTcEj357pLJR7dDvT4EnpYgEqmqf4NWuD-V7BfidJpCXcGy4N6wmcoX1Jj-lo2ziUQONMcZHi0 +parent: prototype; artifact: prototype +Puml Server ID: HSV13OCm30NGLM00udktCS62eCInxE-YRj_UUdjlRLfx7fBUbmkmU14vF-Lik7BF4AzJ8OfIvw3Mys6mqyrltWw9Tkfc38XhqE3uWSmd9Zuc9AZ_bVHHB4V_0W00 +parent: step-builder; artifact: step-builder +Puml Server ID: LOZ93SCm3C1MQGQmzUysYYqaAcJ5q96i7t_x8KXkh4soKvfypeZfNm33fnuSP-xfPEtI88tQhW4i-M2WmGzlB9sS3oqJ8yZKOQ0lWOLPzcJfAoZQtwXfeyuSyW80 +parent: double-checked-locking; artifact: double-checked-locking +Puml Server ID: TSdH4SCW203GLTe1bFzkGv1J6qGFeLc_MI1_x-wzkv94uJ1vDVUrFm26LwxTMnonsMYgitgcEQ1BNEXeyCKVfiAxLqqBtTbqmy1z0ygCGpXHOpgv99bqTgt0JW-LmqPUCUGF diff --git a/_scripts/postPumlsToServer.py b/_scripts/postPumlsToServer.py new file mode 100644 index 000000000000..3929b3c865aa --- /dev/null +++ b/_scripts/postPumlsToServer.py @@ -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. +# + +import requests, glob, re, os + +# taken from here: http://stackoverflow.com/a/13641746 +def replace(file, pattern, subst): + # Read contents from file as a single string + file_handle = open(file, 'r') + file_string = file_handle.read() + file_handle.close() + + # Use RE package to allow for replacement (also allowing for (multiline) REGEX) + file_string = (re.sub(pattern, subst, file_string)) + + # Write contents to file. + # Using mode 'w' truncates the file. + file_handle = open(file, 'w') + file_handle.write(file_string) + file_handle.close() + +# list of all puml files +fileList = glob.glob('*/etc/*.puml') +for puml in fileList: + pathSplit = puml.split("/") + # parent folder + parent = pathSplit[0] + # individual artifact/project name + artifact = pathSplit[2].replace(".urm.puml", "") + print "parent: " + parent + "; artifact: " + artifact + + # do a POST to the official plantuml hosting site with a little trick "!includeurl" and raw github content + data = { + 'text': "!includeurl https://raw.githubusercontent.com/iluwatar/java-design-patterns/master/" + puml + } + r = requests.post('http://plantuml.com/plantuml/uml', data=data) + pumlId = r.url.replace("http://plantuml.com/plantuml/uml/", "") + + # the only thing needed to get a png/svg/ascii from the server back + print "Puml Server ID: " + pumlId + + # add the id so jekyll/liquid can use it + if (parent == artifact): + replace("./" + parent + "/README.md", "categories:", "pumlid: {}\\ncategories:".format(pumlId)) + else: + print "I dont want to program this, just add the following lines to the README.md file that corresponds to this puml file '" + puml + "'\npumlid: {}".format(pumlId) + diff --git a/abstract-document/README.md b/abstract-document/README.md new file mode 100644 index 000000000000..c8755ce078a0 --- /dev/null +++ b/abstract-document/README.md @@ -0,0 +1,32 @@ +--- +layout: pattern +title: Abstract Document +folder: abstract-document +permalink: /patterns/abstract-document/ +pumlid: PSjB3eCm34NHhPG599vtDyQn85L-ifzX-p3lxEf8Twj3MXGDQvyJMFubChxpKN767gucSq07iinEjSNDOACVNvoAUZr6MWoe3QVE_WRnxZ0Mf38b-hkIGlurX_MyehS7 +categories: Structural +tags: + - Java + - Difficulty-Intermediate +--- + +## 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") + + +## Applicability +Use the Abstract Document Pattern when + +* there is a need to add new properties on the fly +* you want a flexible way to organize domain in tree like structure +* you want more loosely coupled system + + +## Credits + +* [Wikipedia: Abstract Document Pattern](https://en.wikipedia.org/wiki/Abstract_Document_Pattern) +* [Martin Fowler: Dealing with properties](http://martinfowler.com/apsupp/properties.pdf) \ No newline at end of file diff --git a/abstract-document/etc/abstract-document-base.png b/abstract-document/etc/abstract-document-base.png new file mode 100644 index 000000000000..13345dbb862c Binary files /dev/null and b/abstract-document/etc/abstract-document-base.png differ diff --git a/abstract-document/etc/abstract-document-base.ucls b/abstract-document/etc/abstract-document-base.ucls new file mode 100644 index 000000000000..bfe927ed9357 --- /dev/null +++ b/abstract-document/etc/abstract-document-base.ucls @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/abstract-document/etc/abstract-document.png b/abstract-document/etc/abstract-document.png new file mode 100644 index 000000000000..98d186f7e7aa Binary files /dev/null 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 new file mode 100644 index 000000000000..ad97457fdfc2 --- /dev/null +++ b/abstract-document/etc/abstract-document.ucls @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/abstract-document/etc/abstract-document.urm.puml b/abstract-document/etc/abstract-document.urm.puml new file mode 100644 index 000000000000..fa15eb3c759d --- /dev/null +++ b/abstract-document/etc/abstract-document.urm.puml @@ -0,0 +1,59 @@ +@startuml +package com.iluwatar.abstractdocument.domain { + class Part { + + Part(properties : Map) + } + interface HasPrice { + + PROPERTY : String {static} + + getPrice() : Optional + } + interface HasParts { + + PROPERTY : String {static} + + getParts() : Stream + } + class Car { + + Car(properties : Map) + } + interface HasType { + + PROPERTY : String {static} + + getType() : Optional + } + interface HasModel { + + PROPERTY : String {static} + + getModel() : Optional + } +} +package com.iluwatar.abstractdocument { + class App { + + App() + + main(args : String[]) {static} + } + abstract class AbstractDocument { + - properties : Map + # AbstractDocument(properties : Map) + + children(key : String, constructor : Function, T>) : Stream + + get(key : String) : Object + + put(key : String, value : Object) + + toString() : String + } + interface Document { + + children(String, Function, T>) : Stream {abstract} + + get(String) : Object {abstract} + + put(String, Object) {abstract} + } +} +AbstractDocument --+ Map +Part ..|> HasType +Part ..|> HasModel +Part ..|> HasPrice +Part --|> AbstractDocument +AbstractDocument ..|> Document +HasPrice --|> Document +HasParts --|> Document +Car ..|> HasModel +Car ..|> HasPrice +Car ..|> HasParts +Car --|> AbstractDocument +HasType --|> Document +HasModel --|> Document +@enduml \ No newline at end of file diff --git a/abstract-document/pom.xml b/abstract-document/pom.xml new file mode 100644 index 000000000000..1ab6d5a2b315 --- /dev/null +++ b/abstract-document/pom.xml @@ -0,0 +1,42 @@ + + + + 4.0.0 + + java-design-patterns + com.iluwatar + 1.14.0-SNAPSHOT + + abstract-document + + + junit + junit + test + + + \ No newline at end of file diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/AbstractDocument.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/AbstractDocument.java new file mode 100644 index 000000000000..4bf8f0d146ec --- /dev/null +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/AbstractDocument.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.abstractdocument; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; + +/** + * Abstract implementation of Document interface + */ +public abstract class AbstractDocument implements Document { + + private final Map properties; + + protected AbstractDocument(Map properties) { + Objects.requireNonNull(properties, "properties map is required"); + this.properties = properties; + } + + @Override + public Void put(String key, Object value) { + properties.put(key, value); + return null; + } + + @Override + public Object get(String key) { + return properties.get(key); + } + + @Override + public Stream children(String key, Function, T> constructor) { + Optional>> any = Stream.of(get(key)).filter(el -> el != null) + .map(el -> (List>) el).findAny(); + return any.isPresent() ? any.get().stream().map(constructor) : Stream.empty(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(getClass().getName()).append("["); + properties.entrySet() + .forEach(e -> builder.append("[").append(e.getKey()).append(" : ").append(e.getValue()).append("]")); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/App.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/App.java new file mode 100644 index 000000000000..d7758b6f745e --- /dev/null +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/App.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.abstractdocument; + +import com.iluwatar.abstractdocument.domain.Car; +import com.iluwatar.abstractdocument.domain.HasModel; +import com.iluwatar.abstractdocument.domain.HasParts; +import com.iluwatar.abstractdocument.domain.HasPrice; +import com.iluwatar.abstractdocument.domain.HasType; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * The Abstract Document pattern enables handling additional, non-static + * properties. This pattern uses concept of traits to enable type safety and + * separate properties of different classes into set of interfaces. + *

+ *

+ * In Abstract Document pattern,({@link AbstractDocument}) fully implements + * {@link Document}) interface. Traits are then defined to enable access to + * properties in usual, static way. + */ +public class App { + + /** + * Executes the App + */ + public App() { + System.out.println("Constructing parts and car"); + + Map carProperties = new HashMap<>(); + carProperties.put(HasModel.PROPERTY, "300SL"); + carProperties.put(HasPrice.PROPERTY, 10000L); + + Map wheelProperties = new HashMap<>(); + wheelProperties.put(HasType.PROPERTY, "wheel"); + wheelProperties.put(HasModel.PROPERTY, "15C"); + wheelProperties.put(HasPrice.PROPERTY, 100L); + + Map doorProperties = new HashMap<>(); + doorProperties.put(HasType.PROPERTY, "door"); + doorProperties.put(HasModel.PROPERTY, "Lambo"); + doorProperties.put(HasPrice.PROPERTY, 300L); + + carProperties.put(HasParts.PROPERTY, Arrays.asList(wheelProperties, doorProperties)); + + Car car = new Car(carProperties); + + System.out.println("Here is our car:"); + System.out.println("-> model: " + car.getModel().get()); + System.out.println("-> price: " + car.getPrice().get()); + System.out.println("-> parts: "); + car.getParts().forEach(p -> System.out + .println("\t" + p.getType().get() + "/" + p.getModel().get() + "/" + p.getPrice().get())); + } + + /** + * Program entry point + * + * @param args command line args + */ + public static void main(String[] args) { + new App(); + } + +} diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/Document.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/Document.java new file mode 100644 index 000000000000..7705f37eb357 --- /dev/null +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/Document.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. + */ +package com.iluwatar.abstractdocument; + +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Stream; + +/** + * Document interface + */ +public interface Document { + + /** + * Puts the value related to the key + * + * @param key element key + * @param value element value + * @return Void + */ + Void put(String key, Object value); + + /** + * Gets the value for the key + * + * @param key element key + * @return value or null + */ + Object get(String key); + + /** + * Gets the stream of child documents + * + * @param key element key + * @param constructor constructor of child class + * @return child documents + */ + Stream children(String key, Function, T> constructor); +} diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Car.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Car.java new file mode 100644 index 000000000000..e29ee63da77d --- /dev/null +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Car.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.abstractdocument.domain; + +import java.util.Map; + +import com.iluwatar.abstractdocument.AbstractDocument; + +/** + * Car entity + */ +public class Car extends AbstractDocument implements HasModel, HasPrice, HasParts { + + public Car(Map properties) { + super(properties); + } + +} diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasModel.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasModel.java new file mode 100644 index 000000000000..fbd8c0d7fd5c --- /dev/null +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasModel.java @@ -0,0 +1,40 @@ +/** + * 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.abstractdocument.domain; + +import java.util.Optional; + +import com.iluwatar.abstractdocument.Document; + +/** + * HasModel trait for static access to 'model' property + */ +public interface HasModel extends Document { + + String PROPERTY = "model"; + + default Optional getModel() { + return Optional.ofNullable((String) get(PROPERTY)); + } + +} diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasParts.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasParts.java new file mode 100644 index 000000000000..581702cc9f1a --- /dev/null +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasParts.java @@ -0,0 +1,40 @@ +/** + * 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.abstractdocument.domain; + +import java.util.stream.Stream; + +import com.iluwatar.abstractdocument.Document; + +/** + * HasParts trait for static access to 'parts' property + */ +public interface HasParts extends Document { + + String PROPERTY = "parts"; + + default Stream getParts() { + return children(PROPERTY, Part::new); + } + +} diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasPrice.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasPrice.java new file mode 100644 index 000000000000..3d1d0e3e7562 --- /dev/null +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasPrice.java @@ -0,0 +1,40 @@ +/** + * 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.abstractdocument.domain; + +import java.util.Optional; + +import com.iluwatar.abstractdocument.Document; + +/** + * HasPrice trait for static access to 'price' property + */ +public interface HasPrice extends Document { + + String PROPERTY = "price"; + + default Optional getPrice() { + return Optional.ofNullable((Number) get(PROPERTY)); + } + +} diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasType.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasType.java new file mode 100644 index 000000000000..b0f292bb6ae5 --- /dev/null +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasType.java @@ -0,0 +1,40 @@ +/** + * 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.abstractdocument.domain; + +import com.iluwatar.abstractdocument.Document; + +import java.util.Optional; + +/** + * HasType trait for static access to 'type' property + */ +public interface HasType extends Document { + + String PROPERTY = "type"; + + default Optional getType() { + return Optional.ofNullable((String) get(PROPERTY)); + } + +} diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Part.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Part.java new file mode 100644 index 000000000000..e42f099d9bc6 --- /dev/null +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Part.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.abstractdocument.domain; + +import java.util.Map; + +import com.iluwatar.abstractdocument.AbstractDocument; + +/** + * Part entity + */ +public class Part extends AbstractDocument implements HasType, HasModel, HasPrice { + + public Part(Map properties) { + super(properties); + } + +} diff --git a/abstract-document/src/test/java/com/iluwatar/abstractdocument/AbstractDocumentTest.java b/abstract-document/src/test/java/com/iluwatar/abstractdocument/AbstractDocumentTest.java new file mode 100644 index 000000000000..b6467e232598 --- /dev/null +++ b/abstract-document/src/test/java/com/iluwatar/abstractdocument/AbstractDocumentTest.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.abstractdocument; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertNotNull; + +/** + * AbstractDocument test class + */ +public class AbstractDocumentTest { + + private static final String KEY = "key"; + private static final String VALUE = "value"; + + private class DocumentImplementation extends AbstractDocument { + + DocumentImplementation(Map properties) { + super(properties); + } + } + + private DocumentImplementation document = new DocumentImplementation(new HashMap<>()); + + @Test + public void shouldPutAndGetValue() { + document.put(KEY, VALUE); + assertEquals(VALUE, document.get(KEY)); + } + + @Test + public void shouldRetrieveChildren() { + Map child1 = new HashMap<>(); + Map child2 = new HashMap<>(); + List> children = Arrays.asList(child1, child2); + + document.put(KEY, children); + + Stream childrenStream = document.children(KEY, DocumentImplementation::new); + assertNotNull(children); + assertEquals(2, childrenStream.count()); + } + + @Test + public void shouldRetrieveEmptyStreamForNonExistingChildren() { + Stream children = document.children(KEY, DocumentImplementation::new); + assertNotNull(children); + assertEquals(0, children.count()); + } + + @Test + public void shouldIncludePropsInToString() { + Map props = new HashMap<>(); + props.put(KEY, VALUE); + DocumentImplementation document = new DocumentImplementation(props); + assertNotNull(document.toString().contains(KEY)); + assertNotNull(document.toString().contains(VALUE)); + } + +} diff --git a/abstract-document/src/test/java/com/iluwatar/abstractdocument/AppTest.java b/abstract-document/src/test/java/com/iluwatar/abstractdocument/AppTest.java new file mode 100644 index 000000000000..787ae3aa6468 --- /dev/null +++ b/abstract-document/src/test/java/com/iluwatar/abstractdocument/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.abstractdocument; + +import org.junit.Test; + +/** + * Simple App test + */ +public class AppTest { + + @Test + public void shouldExecuteAppWithoutException() { + App.main(null); + } + +} diff --git a/abstract-document/src/test/java/com/iluwatar/abstractdocument/DomainTest.java b/abstract-document/src/test/java/com/iluwatar/abstractdocument/DomainTest.java new file mode 100644 index 000000000000..437244a3d34f --- /dev/null +++ b/abstract-document/src/test/java/com/iluwatar/abstractdocument/DomainTest.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.abstractdocument; + +import com.iluwatar.abstractdocument.domain.Car; +import com.iluwatar.abstractdocument.domain.HasModel; +import com.iluwatar.abstractdocument.domain.HasParts; +import com.iluwatar.abstractdocument.domain.HasPrice; +import com.iluwatar.abstractdocument.domain.HasType; +import com.iluwatar.abstractdocument.domain.Part; +import org.junit.Test; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import static junit.framework.TestCase.assertEquals; + +/** + * Test for Part and Car + */ +public class DomainTest { + + private static final String TEST_PART_TYPE = "test-part-type"; + private static final String TEST_PART_MODEL = "test-part-model"; + private static final long TEST_PART_PRICE = 0L; + + private static final String TEST_CAR_MODEL = "test-car-model"; + private static final long TEST_CAR_PRICE = 1L; + + @Test + public void shouldConstructPart() { + Map partProperties = new HashMap<>(); + partProperties.put(HasType.PROPERTY, TEST_PART_TYPE); + partProperties.put(HasModel.PROPERTY, TEST_PART_MODEL); + partProperties.put(HasPrice.PROPERTY, TEST_PART_PRICE); + Part part = new Part(partProperties); + + assertEquals(TEST_PART_TYPE, part.getType().get()); + assertEquals(TEST_PART_MODEL, part.getModel().get()); + assertEquals(TEST_PART_PRICE, part.getPrice().get()); + } + + @Test + public void shouldConstructCar() { + Map carProperties = new HashMap<>(); + carProperties.put(HasModel.PROPERTY, TEST_CAR_MODEL); + carProperties.put(HasPrice.PROPERTY, TEST_CAR_PRICE); + carProperties.put(HasParts.PROPERTY, Arrays.asList(new HashMap<>(), new HashMap<>())); + Car car = new Car(carProperties); + + assertEquals(TEST_CAR_MODEL, car.getModel().get()); + assertEquals(TEST_CAR_PRICE, car.getPrice().get()); + assertEquals(2, car.getParts().count()); + } + +} diff --git a/abstract-factory/README.md b/abstract-factory/README.md index 485599b984fb..a3881b893fce 100644 --- a/abstract-factory/README.md +++ b/abstract-factory/README.md @@ -3,8 +3,9 @@ layout: pattern title: Abstract Factory folder: abstract-factory permalink: /patterns/abstract-factory/ +pumlid: PSZB3OD034NHLa81Czwd6sCC39gVxEUWT1_ssLmTtQLqgR5fM7sTmFGtaV6TZu8prd0r6HtQaMKqAZLk1XjT_E6qgPUZfyc0MdTgx0-8LuUn8ErFXdr98NypXxKyezKV categories: Creational -tags: +tags: - Java - Gang Of Four - Difficulty-Intermediate @@ -30,6 +31,8 @@ Use the Abstract Factory pattern when ## Real world examples * [javax.xml.parsers.DocumentBuilderFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/parsers/DocumentBuilderFactory.html) +* [javax.xml.transform.TransformerFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/transform/TransformerFactory.html#newInstance--) +* [javax.xml.xpath.XPathFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/xpath/XPathFactory.html#newInstance--) ## Credits diff --git a/abstract-factory/etc/abstract-factory.urm.puml b/abstract-factory/etc/abstract-factory.urm.puml new file mode 100644 index 000000000000..9648a6a96049 --- /dev/null +++ b/abstract-factory/etc/abstract-factory.urm.puml @@ -0,0 +1,88 @@ +@startuml +package com.iluwatar.abstractfactory { + class App { + - army : Army + - castle : Castle + - king : King + + App() + + createKingdom(factory : KingdomFactory) + + getArmy() : Army + ~ getArmy(factory : KingdomFactory) : Army + + getCastle() : Castle + ~ getCastle(factory : KingdomFactory) : Castle + + getKing() : King + ~ getKing(factory : KingdomFactory) : King + + main(args : String[]) {static} + - setArmy(army : Army) + - setCastle(castle : Castle) + - setKing(king : King) + } + class OrcKingdomFactory { + + OrcKingdomFactory() + + createArmy() : Army + + createCastle() : Castle + + createKing() : King + } + class ElfCastle { + ~ DESCRIPTION : String {static} + + ElfCastle() + + getDescription() : String + } + class OrcCastle { + ~ DESCRIPTION : String {static} + + OrcCastle() + + getDescription() : String + } + interface KingdomFactory { + + createArmy() : Army {abstract} + + createCastle() : Castle {abstract} + + createKing() : King {abstract} + } + class ElfKing { + ~ DESCRIPTION : String {static} + + ElfKing() + + getDescription() : String + } + class ElfArmy { + ~ DESCRIPTION : String {static} + + ElfArmy() + + getDescription() : String + } + interface Castle { + + getDescription() : String {abstract} + } + interface Army { + + getDescription() : String {abstract} + } + class OrcKing { + ~ DESCRIPTION : String {static} + + OrcKing() + + getDescription() : String + } + class OrcArmy { + ~ DESCRIPTION : String {static} + + OrcArmy() + + getDescription() : String + } + interface King { + + getDescription() : String {abstract} + } + class ElfKingdomFactory { + + ElfKingdomFactory() + + createArmy() : Army + + createCastle() : Castle + + createKing() : King + } +} +App --> "-castle" Castle +App --> "-king" King +App --> "-army" Army +OrcKingdomFactory ..|> KingdomFactory +ElfCastle ..|> Castle +OrcCastle ..|> Castle +ElfKing ..|> King +ElfArmy ..|> Army +OrcKing ..|> King +OrcArmy ..|> Army +ElfKingdomFactory ..|> KingdomFactory +@enduml \ No newline at end of file diff --git a/abstract-factory/pom.xml b/abstract-factory/pom.xml index 734020720542..5c3d4d16abf7 100644 --- a/abstract-factory/pom.xml +++ b/abstract-factory/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT abstract-factory diff --git a/adapter/README.md b/adapter/README.md index ea3baa7fa91c..6bd6d66e171a 100644 --- a/adapter/README.md +++ b/adapter/README.md @@ -3,6 +3,7 @@ layout: pattern title: Adapter folder: adapter permalink: /patterns/adapter/ +pumlid: DSR14S8m30J0Lg20M7-wEMnDOiPMFDA9j0yyUEtUkzMHJTF7xI1NF4GSLzaxZtncgDVJgCPIpobzv0N2vOKtjgRHTziMI7KBcOXl10thfxB-Nz9dMJd71m00 categories: Structural tags: - Java @@ -30,6 +31,10 @@ Use the Adapter pattern when ## Real world examples * [java.util.Arrays#asList()](http://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#asList%28T...%29) +* [java.util.Collections#list()](https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#list-java.util.Enumeration-) +* [java.util.Collections#enumeration()](https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#enumeration-java.util.Collection-) +* [javax.xml.bind.annotation.adapters.XMLAdapter](http://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/adapters/XmlAdapter.html#marshal-BoundType-) + ## Credits diff --git a/adapter/etc/adapter.urm.puml b/adapter/etc/adapter.urm.puml new file mode 100644 index 000000000000..a7c962a3b09b --- /dev/null +++ b/adapter/etc/adapter.urm.puml @@ -0,0 +1,35 @@ +@startuml +package com.iluwatar.adapter { + interface BattleShip { + + fire() {abstract} + + move() {abstract} + } + class BattleFishingBoat { + - boat : FishingBoat + + BattleFishingBoat() + + fire() + + move() + } + class App { + + App() + + main(args : String[]) {static} + } + class Captain { + - battleship : BattleShip + + Captain() + + Captain(battleship : BattleShip) + + fire() + + move() + + setBattleship(battleship : BattleShip) + } + class FishingBoat { + + FishingBoat() + + fish() + + sail() + } +} +BattleFishingBoat --> "-boat" FishingBoat +Captain --> "-battleship" BattleShip +BattleFishingBoat ..|> BattleShip +Captain ..|> BattleShip +@enduml \ No newline at end of file diff --git a/adapter/pom.xml b/adapter/pom.xml index 476eb4dce898..83a930653c34 100644 --- a/adapter/pom.xml +++ b/adapter/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT adapter diff --git a/aggregator-microservices/README.md b/aggregator-microservices/README.md new file mode 100644 index 000000000000..75fe373fd301 --- /dev/null +++ b/aggregator-microservices/README.md @@ -0,0 +1,30 @@ +--- +layout: pattern +title: Aggregator Microservices +folder: aggregator-microservices +permalink: /patterns/aggregator-microservices/ +pumlid: JOov3SCm301NIGQGs7iRXYPa1g8ayB7NjuiKwGvtmBrbKC-Tq_hhY5Y-0HXUjKaS-Kbdepc2HrIQ2jBpma23BvvOTdPfeooCO1iEYlu0O6l63MDQKI6Rp-CKOWSE-ey_NzEqhjH-0m00 +categories: Architectural +tags: +- Java +- Spring +--- + +## Intent + +The user makes a single call to the Aggregator, and the aggregator then calls each relevant microservice and collects +the data, apply business logic to it, and further publish is as a REST Endpoint. +More variations of the aggregator are: +- Proxy Microservice Design Pattern: A different microservice is called upon the business need. +- Chained Microservice Design Pattern: In this case each microservice is dependent/ chained to a series +of other microservices. + +![alt text](./etc/aggregator-microservice.png "Aggregator Microservice") + +## Applicability + +Use the Aggregator Microservices pattern when you need a unified API for various microservices, regardless the client device. + +## Credits + +* [Microservice Design Patterns](http://blog.arungupta.me/microservice-design-patterns/) diff --git a/aggregator-microservices/aggregator-service/pom.xml b/aggregator-microservices/aggregator-service/pom.xml new file mode 100644 index 000000000000..520aefaa81a5 --- /dev/null +++ b/aggregator-microservices/aggregator-service/pom.xml @@ -0,0 +1,88 @@ + + + + + aggregator-microservices + com.iluwatar + 1.14.0-SNAPSHOT + + 4.0.0 + + aggregator-service + jar + + + + + org.springframework.boot + spring-boot-dependencies + + + + + + org.springframework + spring-webmvc + + + org.springframework.boot + spring-boot-starter-web + + + junit + junit + test + + + org.mockito + mockito-core + test + + + org.apache.httpcomponents + httpclient + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + + repackage + + + + + + + \ No newline at end of file diff --git a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Aggregator.java b/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Aggregator.java new file mode 100644 index 000000000000..639349db9499 --- /dev/null +++ b/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Aggregator.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.aggregator.microservices; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +/** + * The aggregator aggregates calls on various micro-services, collects + * data and further publishes them under a REST endpoint. + */ +@RestController +public class Aggregator { + + + @Resource + private ProductInformationClient informationClient; + + @Resource + private ProductInventoryClient inventoryClient; + + + /** + * Retrieves product data. + * + * @return a Product. + */ + @RequestMapping("/product") + public Product getProduct() { + Product product = new Product(); + product.setTitle(informationClient.getProductTitle()); + product.setProductInventories(inventoryClient.getProductInventories()); + return product; + } + +} diff --git a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/App.java b/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/App.java new file mode 100644 index 000000000000..25bb9ee6fc8a --- /dev/null +++ b/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/App.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.aggregator.microservices; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class App { + + /** + * Program entry point + * + * @param args command line args + */ + public static void main(String[] args) { + SpringApplication.run(App.class, args); + } +} diff --git a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Product.java b/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Product.java new file mode 100644 index 000000000000..edf0c121db92 --- /dev/null +++ b/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Product.java @@ -0,0 +1,57 @@ +/** + * 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.aggregator.microservices; + +/** + * Encapsulates all the data for a Product that clients will request. + */ +public class Product { + + /** + * The title of the product. + */ + private String title; + + + /** + * The inventories of the product. + */ + private int productInventories; + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public int getProductInventories() { + return productInventories; + } + + public void setProductInventories(int productInventories) { + this.productInventories = productInventories; + } + +} diff --git a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClient.java b/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClient.java new file mode 100644 index 000000000000..863db4759206 --- /dev/null +++ b/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClient.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.aggregator.microservices; + +/** + * Interface for the Information micro-service. + */ +public interface ProductInformationClient { + + String getProductTitle(); + +} diff --git a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClientImpl.java b/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClientImpl.java new file mode 100644 index 000000000000..1c5c1527c459 --- /dev/null +++ b/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClientImpl.java @@ -0,0 +1,53 @@ +/** + * 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.aggregator.microservices; + +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +/** + * An adapter to communicate with information micro-service. + */ +@Component +public class ProductInformationClientImpl implements ProductInformationClient { + + @Override + public String getProductTitle() { + String response = null; + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + HttpGet httpGet = new HttpGet("http://localhost:51515/information"); + try (CloseableHttpResponse httpResponse = httpClient.execute(httpGet)) { + response = EntityUtils.toString(httpResponse.getEntity()); + } + } catch (IOException e) { + e.printStackTrace(); + } + return response; + } +} diff --git a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClient.java b/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClient.java new file mode 100644 index 000000000000..22be900b3bac --- /dev/null +++ b/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClient.java @@ -0,0 +1,31 @@ +/** + * 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.aggregator.microservices; + +/** + * Interface to Inventory micro-service. + */ +public interface ProductInventoryClient { + + int getProductInventories(); +} diff --git a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClientImpl.java b/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClientImpl.java new file mode 100644 index 000000000000..14d0a32c421c --- /dev/null +++ b/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClientImpl.java @@ -0,0 +1,53 @@ +/** + * 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.aggregator.microservices; + +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +/** + * An adapter to communicate with inventory micro-service. + */ +@Component +public class ProductInventoryClientImpl implements ProductInventoryClient { + + @Override + public int getProductInventories() { + String response = "0"; + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + HttpGet httpGet = new HttpGet("http://localhost:51516/inventories"); + try (CloseableHttpResponse httpResponse = httpClient.execute(httpGet)) { + response = EntityUtils.toString(httpResponse.getEntity()); + } + } catch (IOException e) { + e.printStackTrace(); + } + return Integer.parseInt(response); + } +} diff --git a/aggregator-microservices/aggregator-service/src/main/resources/application.properties b/aggregator-microservices/aggregator-service/src/main/resources/application.properties new file mode 100644 index 000000000000..69f58171237a --- /dev/null +++ b/aggregator-microservices/aggregator-service/src/main/resources/application.properties @@ -0,0 +1,24 @@ +# +# 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. +# + +server.port=50004 \ No newline at end of file diff --git a/aggregator-microservices/aggregator-service/src/test/java/com/iluwatar/aggregator/microservices/AggregatorTest.java b/aggregator-microservices/aggregator-service/src/test/java/com/iluwatar/aggregator/microservices/AggregatorTest.java new file mode 100644 index 000000000000..95d36fe25933 --- /dev/null +++ b/aggregator-microservices/aggregator-service/src/test/java/com/iluwatar/aggregator/microservices/AggregatorTest.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.aggregator.microservices; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +public class AggregatorTest { + + @InjectMocks + private Aggregator aggregator; + + @Mock + private ProductInformationClient informationClient; + + @Mock + private ProductInventoryClient inventoryClient; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + /** + * Tests getting the data for a desktop client + */ + @Test + public void testGetProduct() { + String title = "The Product Title."; + int inventories = 5; + + when(informationClient.getProductTitle()).thenReturn(title); + when(inventoryClient.getProductInventories()).thenReturn(inventories); + + Product testProduct = aggregator.getProduct(); + + assertEquals(title, testProduct.getTitle()); + assertEquals(inventories, testProduct.getProductInventories()); + } + +} \ No newline at end of file diff --git a/aggregator-microservices/etc/aggregator-microservice.png b/aggregator-microservices/etc/aggregator-microservice.png new file mode 100644 index 000000000000..ad344a7e1e0f Binary files /dev/null and b/aggregator-microservices/etc/aggregator-microservice.png differ diff --git a/aggregator-microservices/etc/aggregator-service.urm.puml b/aggregator-microservices/etc/aggregator-service.urm.puml new file mode 100644 index 000000000000..2a6600531e55 --- /dev/null +++ b/aggregator-microservices/etc/aggregator-service.urm.puml @@ -0,0 +1,41 @@ +@startuml +package com.iluwatar.aggregator.microservices { + class ProductInventoryClientImpl { + + ProductInventoryClientImpl() + + getProductInventories() : int + } + class App { + + App() + + main(args : String[]) {static} + } + interface ProductInventoryClient { + + getProductInventories() : int {abstract} + } + class Product { + - productInventories : int + - title : String + + Product() + + getProductInventories() : int + + getTitle() : String + + setProductInventories(productInventories : int) + + setTitle(title : String) + } + class Aggregator { + - informationClient : ProductInformationClient + - inventoryClient : ProductInventoryClient + + Aggregator() + + getProduct() : Product + } + class ProductInformationClientImpl { + + ProductInformationClientImpl() + + getProductTitle() : String + } + interface ProductInformationClient { + + getProductTitle() : String {abstract} + } +} +Aggregator --> "-inventoryClient" ProductInventoryClient +Aggregator --> "-informationClient" ProductInformationClient +ProductInventoryClientImpl ..|> ProductInventoryClient +ProductInformationClientImpl ..|> ProductInformationClient +@enduml \ No newline at end of file diff --git a/aggregator-microservices/etc/information-microservice.urm.puml b/aggregator-microservices/etc/information-microservice.urm.puml new file mode 100644 index 000000000000..e0a2ccb24fb2 --- /dev/null +++ b/aggregator-microservices/etc/information-microservice.urm.puml @@ -0,0 +1,12 @@ +@startuml +package com.iluwatar.information.microservice { + class InformationApplication { + + InformationApplication() + + main(args : String[]) {static} + } + class InformationController { + + InformationController() + + getProductTitle() : String + } +} +@enduml \ No newline at end of file diff --git a/aggregator-microservices/etc/inventory-microservice.urm.puml b/aggregator-microservices/etc/inventory-microservice.urm.puml new file mode 100644 index 000000000000..a07a36306376 --- /dev/null +++ b/aggregator-microservices/etc/inventory-microservice.urm.puml @@ -0,0 +1,12 @@ +@startuml +package com.iluwatar.inventory.microservice { + class InventoryController { + + InventoryController() + + getProductInventories() : int + } + class InventoryApplication { + + InventoryApplication() + + main(args : String[]) {static} + } +} +@enduml \ No newline at end of file diff --git a/aggregator-microservices/information-microservice/pom.xml b/aggregator-microservices/information-microservice/pom.xml new file mode 100644 index 000000000000..4cd1916c60ce --- /dev/null +++ b/aggregator-microservices/information-microservice/pom.xml @@ -0,0 +1,79 @@ + + + + + aggregator-microservices + com.iluwatar + 1.14.0-SNAPSHOT + + 4.0.0 + + information-microservice + jar + + + + + org.springframework.boot + spring-boot-dependencies + + + + + + org.springframework + spring-webmvc + + + org.springframework.boot + spring-boot-starter-web + + + junit + junit + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + + repackage + + + + + + + diff --git a/aggregator-microservices/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationApplication.java b/aggregator-microservices/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationApplication.java new file mode 100644 index 000000000000..c93219c17bdd --- /dev/null +++ b/aggregator-microservices/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationApplication.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.information.microservice; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Inventory Application starts container (Spring Boot) and exposes the Inventory micro-service. + */ +@SpringBootApplication +public class InformationApplication { + + public static void main(String[] args) { + SpringApplication.run(InformationApplication.class, args); + } +} diff --git a/aggregator-microservices/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationController.java b/aggregator-microservices/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationController.java new file mode 100644 index 000000000000..37ec45c1be65 --- /dev/null +++ b/aggregator-microservices/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationController.java @@ -0,0 +1,41 @@ +/** + * 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.information.microservice; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class InformationController { + + /** + * Endpoint to retrieve a product's informations. + * + * @return product inventory. + */ + @RequestMapping(value = "/information", method = RequestMethod.GET) + public String getProductTitle() { + return "The Product Title."; + } +} diff --git a/aggregator-microservices/information-microservice/src/main/resources/application.properties b/aggregator-microservices/information-microservice/src/main/resources/application.properties new file mode 100644 index 000000000000..3c8452f1f3ea --- /dev/null +++ b/aggregator-microservices/information-microservice/src/main/resources/application.properties @@ -0,0 +1,24 @@ +# +# 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. +# + +server.port=51515 \ No newline at end of file diff --git a/aggregator-microservices/information-microservice/src/test/java/com/iluwatar/information/microservice/InformationControllerTest.java b/aggregator-microservices/information-microservice/src/test/java/com/iluwatar/information/microservice/InformationControllerTest.java new file mode 100644 index 000000000000..9c3a6b98de9d --- /dev/null +++ b/aggregator-microservices/information-microservice/src/test/java/com/iluwatar/information/microservice/InformationControllerTest.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.information.microservice; + +import org.junit.Assert; +import org.junit.Test; + +public class InformationControllerTest { + + @Test + public void shouldGetProductTitle() { + InformationController infoController = new InformationController(); + + String title = infoController.getProductTitle(); + + Assert.assertEquals("The Product Title.", title); + } + +} \ No newline at end of file diff --git a/aggregator-microservices/inventory-microservice/pom.xml b/aggregator-microservices/inventory-microservice/pom.xml new file mode 100644 index 000000000000..1bf655b45ee2 --- /dev/null +++ b/aggregator-microservices/inventory-microservice/pom.xml @@ -0,0 +1,79 @@ + + + + + aggregator-microservices + com.iluwatar + 1.14.0-SNAPSHOT + + 4.0.0 + + inventory-microservice + jar + + + + + org.springframework.boot + spring-boot-dependencies + + + + + + org.springframework + spring-webmvc + + + org.springframework.boot + spring-boot-starter-web + + + junit + junit + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + + repackage + + + + + + + \ No newline at end of file diff --git a/aggregator-microservices/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryApplication.java b/aggregator-microservices/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryApplication.java new file mode 100644 index 000000000000..3e2cf9e601d4 --- /dev/null +++ b/aggregator-microservices/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryApplication.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.inventory.microservice; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Inventory Application starts container (Spring Boot) and exposes the Inventory micro-service. + */ +@SpringBootApplication +public class InventoryApplication { + + public static void main(String[] args) { + SpringApplication.run(InventoryApplication.class, args); + } + +} diff --git a/aggregator-microservices/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryController.java b/aggregator-microservices/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryController.java new file mode 100644 index 000000000000..483e28bf329c --- /dev/null +++ b/aggregator-microservices/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryController.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.inventory.microservice; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class InventoryController { + + /** + * Endpoint to retrieve a product's inventories. + * + * @return product inventory. + */ + @RequestMapping(value = "/inventories", method = RequestMethod.GET) + public int getProductInventories() { + return 5; + } + +} diff --git a/aggregator-microservices/inventory-microservice/src/main/resources/application.properties b/aggregator-microservices/inventory-microservice/src/main/resources/application.properties new file mode 100644 index 000000000000..36f7589fc243 --- /dev/null +++ b/aggregator-microservices/inventory-microservice/src/main/resources/application.properties @@ -0,0 +1,24 @@ +# +# 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. +# + +server.port=51516 \ No newline at end of file diff --git a/aggregator-microservices/inventory-microservice/src/test/java/com/iluwatar/inventory/microservice/InventoryControllerTest.java b/aggregator-microservices/inventory-microservice/src/test/java/com/iluwatar/inventory/microservice/InventoryControllerTest.java new file mode 100644 index 000000000000..787ec0e611d5 --- /dev/null +++ b/aggregator-microservices/inventory-microservice/src/test/java/com/iluwatar/inventory/microservice/InventoryControllerTest.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.inventory.microservice; + +import org.junit.Assert; +import org.junit.Test; + +public class InventoryControllerTest { + + @Test + public void testGetProductInventories() throws Exception { + InventoryController inventoryController = new InventoryController(); + + int numberOfInventories = inventoryController.getProductInventories(); + + Assert.assertEquals(5, numberOfInventories); + } +} \ No newline at end of file diff --git a/aggregator-microservices/pom.xml b/aggregator-microservices/pom.xml new file mode 100644 index 000000000000..cedc35eec819 --- /dev/null +++ b/aggregator-microservices/pom.xml @@ -0,0 +1,63 @@ + + + + + java-design-patterns + com.iluwatar + 1.14.0-SNAPSHOT + + 4.0.0 + aggregator-microservices + pom + + + + + com.github.markusmo3.urm + urm-maven-plugin + ${urm.version} + + ${project.basedir}/../etc + + com.iluwatar + + + + aggregator-microservices + + + + + + + + information-microservice + aggregator-service + inventory-microservice + + diff --git a/api-gateway/README.md b/api-gateway/README.md new file mode 100644 index 000000000000..93b975e131d3 --- /dev/null +++ b/api-gateway/README.md @@ -0,0 +1,31 @@ +--- +layout: pattern +title: API Gateway +folder: api-gateway +permalink: /patterns/api-gateway/ +pumlid: JSox3SCm303HLP819FRUXg49cO542_nOyFPncUvUSszHwhbpMdyT4TCt0CDLcyIHdtGsEZLOez8vG7ek33JuueLbPvUcPM84cpeCz2S0fvI6mGjluA1_b-Tt2N5D6tNcw3y0 +categories: Architectural +tags: +- Java +- Difficulty-Intermediate +- Spring +--- + +## Intent + +Aggregate calls to microservices in a single location: the API Gateway. The user makes a single +call to the API Gateway, and the API Gateway then calls each relevant microservice. + +![alt text](./etc/api-gateway.png "API Gateway") + +## Applicability + +Use the API Gateway pattern when + +* you're also using the Microservices pattern and need a single point of aggregation for your +microservice calls + +## Credits + +* [microservices.io - API Gateway](http://microservices.io/patterns/apigateway.html) +* [NGINX - Building Microservices: Using an API Gateway](https://www.nginx.com/blog/building-microservices-using-an-api-gateway/) diff --git a/api-gateway/api-gateway-service/pom.xml b/api-gateway/api-gateway-service/pom.xml new file mode 100644 index 000000000000..e2ab9e6d9a4e --- /dev/null +++ b/api-gateway/api-gateway-service/pom.xml @@ -0,0 +1,87 @@ + + + + + api-gateway + com.iluwatar + 1.14.0-SNAPSHOT + + 4.0.0 + api-gateway-service + jar + + + + + org.springframework.boot + spring-boot-dependencies + + + + + + org.springframework + spring-webmvc + + + org.springframework.boot + spring-boot-starter-web + + + junit + junit + test + + + org.mockito + mockito-core + test + + + org.apache.httpcomponents + httpclient + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + + repackage + + + + + + + \ No newline at end of file diff --git a/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ApiGateway.java b/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ApiGateway.java new file mode 100644 index 000000000000..60f0b6d543a4 --- /dev/null +++ b/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ApiGateway.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.api.gateway; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +/** + * The ApiGateway aggregates calls to microservices based on the needs of the individual clients. + */ +@RestController +public class ApiGateway { + + @Resource + private ImageClient imageClient; + + @Resource + private PriceClient priceClient; + + /** + * Retrieves product information that desktop clients need + * @return Product information for clients on a desktop + */ + @RequestMapping("/desktop") + public DesktopProduct getProductDesktop() { + DesktopProduct desktopProduct = new DesktopProduct(); + desktopProduct.setImagePath(imageClient.getImagePath()); + desktopProduct.setPrice(priceClient.getPrice()); + return desktopProduct; + } + + /** + * Retrieves product information that mobile clients need + * @return Product information for clients on a mobile device + */ + @RequestMapping("/mobile") + public MobileProduct getProductMobile() { + MobileProduct mobileProduct = new MobileProduct(); + mobileProduct.setPrice(priceClient.getPrice()); + return mobileProduct; + } +} diff --git a/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/App.java b/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/App.java new file mode 100644 index 000000000000..9a644a0f7545 --- /dev/null +++ b/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/App.java @@ -0,0 +1,66 @@ +/** + * 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.api.gateway; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * With the Microservices pattern, a client may need data from multiple different microservices. + * If the client called each microservice directly, that could contribute to longer load times, + * since the client would have to make a network request for each microservice called. Moreover, + * having the client call each microservice directly ties the client to that microservice - if the + * internal implementations of the microservices change (for example, if two microservices are + * combined sometime in the future) or if the location (host and port) of a microservice changes, + * then every client that makes use of those microservices must be updated. + * + *

+ * The intent of the API Gateway pattern is to alleviate some of these issues. In the API Gateway + * pattern, an additional entity (the API Gateway) is placed between the client and the + * microservices. The job of the API Gateway is to aggregate the calls to the microservices. + * Rather than the client calling each microservice individually, the client calls the API Gateway + * a single time. The API Gateway then calls each of the microservices that the client needs. + * + *

+ * This implementation shows what the API Gateway pattern could look like for an e-commerce site. + * The {@link ApiGateway} makes calls to the Image and Price microservices using the + * {@link ImageClientImpl} and {@link PriceClientImpl} respectively. Customers viewing the site on a + * desktop device can see both price information and an image of a product, so the {@link ApiGateway} + * calls both of the microservices and aggregates the data in the {@link DesktopProduct} model. + * However, mobile users only see price information; they do not see a product image. For mobile + * users, the {@link ApiGateway} only retrieves price information, which it uses to populate the + * {@link MobileProduct}. + */ +@SpringBootApplication +public class App { + + /** + * Program entry point + * + * @param args + * command line args + */ + public static void main(String[] args) { + SpringApplication.run(App.class, args); + } +} diff --git a/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/DesktopProduct.java b/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/DesktopProduct.java new file mode 100644 index 000000000000..0a04db4022b9 --- /dev/null +++ b/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/DesktopProduct.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.api.gateway; + +/** + * Encapsulates all of the information that a desktop client needs to display a product. + */ +public class DesktopProduct { + /** + * The price of the product + */ + private String price; + + /** + * The path to the image of the product + */ + private String imagePath; + + public String getPrice() { + return price; + } + + public void setPrice(String price) { + this.price = price; + } + + public String getImagePath() { + return imagePath; + } + + public void setImagePath(String imagePath) { + this.imagePath = imagePath; + } +} diff --git a/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClient.java b/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClient.java new file mode 100644 index 000000000000..935dab7781a7 --- /dev/null +++ b/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClient.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.api.gateway; + +/** + * An interface used to communicate with the Image microservice + */ +public interface ImageClient { + String getImagePath(); +} diff --git a/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClientImpl.java b/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClientImpl.java new file mode 100644 index 000000000000..64fde1e66230 --- /dev/null +++ b/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClientImpl.java @@ -0,0 +1,56 @@ +/** + * 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.api.gateway; + +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +/** + * An adapter to communicate with the Image microservice + */ +@Component +public class ImageClientImpl implements ImageClient{ + /** + * Makes a simple HTTP Get request to the Image microservice + * @return The path to the image + */ + @Override + public String getImagePath() { + String response = null; + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + HttpGet httpGet = new HttpGet("http://localhost:50005/image-path"); + try (CloseableHttpResponse httpResponse = httpClient.execute(httpGet)) { + response = EntityUtils.toString(httpResponse.getEntity()); + } + } catch (IOException e) { + e.printStackTrace(); + } + return response; + } +} diff --git a/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/MobileProduct.java b/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/MobileProduct.java new file mode 100644 index 000000000000..db358b781fbd --- /dev/null +++ b/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/MobileProduct.java @@ -0,0 +1,41 @@ +/** + * 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.api.gateway; + +/** + * Encapsulates all of the information that mobile client needs to display a product. + */ +public class MobileProduct { + /** + * The price of the product + */ + private String price; + + public String getPrice() { + return price; + } + + public void setPrice(String price) { + this.price = price; + } +} diff --git a/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/PriceClient.java b/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/PriceClient.java new file mode 100644 index 000000000000..44497b99710c --- /dev/null +++ b/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/PriceClient.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.api.gateway; + +/** + * An interface used to communicate with the Price microservice + */ +public interface PriceClient { + String getPrice(); +} diff --git a/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/PriceClientImpl.java b/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/PriceClientImpl.java new file mode 100644 index 000000000000..bdc7cb00bd73 --- /dev/null +++ b/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/PriceClientImpl.java @@ -0,0 +1,56 @@ +/** + * 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.api.gateway; + +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +/** + * An adapter to communicate with the Price microservice + */ +@Component +public class PriceClientImpl implements PriceClient{ + /** + * Makes a simple HTTP Get request to the Price microservice + * @return The price of the product + */ + @Override + public String getPrice() { + String response = null; + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + HttpGet httpGet = new HttpGet("http://localhost:50006/price"); + try (CloseableHttpResponse httpResponse = httpClient.execute(httpGet)) { + response = EntityUtils.toString(httpResponse.getEntity()); + } + } catch (IOException e) { + e.printStackTrace(); + } + return response; + } +} diff --git a/api-gateway/api-gateway-service/src/main/resources/application.properties b/api-gateway/api-gateway-service/src/main/resources/application.properties new file mode 100644 index 000000000000..69f58171237a --- /dev/null +++ b/api-gateway/api-gateway-service/src/main/resources/application.properties @@ -0,0 +1,24 @@ +# +# 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. +# + +server.port=50004 \ No newline at end of file diff --git a/api-gateway/api-gateway-service/src/test/java/com/iluwatar/api/gateway/ApiGatewayTest.java b/api-gateway/api-gateway-service/src/test/java/com/iluwatar/api/gateway/ApiGatewayTest.java new file mode 100644 index 000000000000..d1a210f05db4 --- /dev/null +++ b/api-gateway/api-gateway-service/src/test/java/com/iluwatar/api/gateway/ApiGatewayTest.java @@ -0,0 +1,78 @@ +/** + * 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.api.gateway; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +public class ApiGatewayTest { + + @InjectMocks + private ApiGateway apiGateway; + + @Mock + private ImageClient imageClient; + + @Mock + private PriceClient priceClient; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + /** + * Tests getting the data for a desktop client + */ + @Test + public void testGetProductDesktop() { + String imagePath = "/product-image.png"; + String price = "20"; + when(imageClient.getImagePath()).thenReturn(imagePath); + when(priceClient.getPrice()).thenReturn(price); + + DesktopProduct desktopProduct = apiGateway.getProductDesktop(); + + assertEquals(price, desktopProduct.getPrice()); + assertEquals(imagePath, desktopProduct.getImagePath()); + } + + /** + * Tests getting the data for a mobile client + */ + @Test + public void testGetProductMobile() { + String price = "20"; + when(priceClient.getPrice()).thenReturn(price); + + MobileProduct mobileProduct = apiGateway.getProductMobile(); + + assertEquals(price, mobileProduct.getPrice()); + } +} diff --git a/api-gateway/etc/api-gateway-service.urm.puml b/api-gateway/etc/api-gateway-service.urm.puml new file mode 100644 index 000000000000..409e7ee91842 --- /dev/null +++ b/api-gateway/etc/api-gateway-service.urm.puml @@ -0,0 +1,48 @@ +@startuml +package com.iluwatar.api.gateway { + class App { + + App() + + main(args : String[]) {static} + } + class PriceClientImpl { + + PriceClientImpl() + + getPrice() : String + } + class ImageClientImpl { + + ImageClientImpl() + + getImagePath() : String + } + class MobileProduct { + - price : String + + MobileProduct() + + getPrice() : String + + setPrice(price : String) + } + interface ImageClient { + + getImagePath() : String {abstract} + } + class ApiGateway { + - imageClient : ImageClient + - priceClient : PriceClient + + ApiGateway() + + getProductDesktop() : DesktopProduct + + getProductMobile() : MobileProduct + } + class DesktopProduct { + - imagePath : String + - price : String + + DesktopProduct() + + getImagePath() : String + + getPrice() : String + + setImagePath(imagePath : String) + + setPrice(price : String) + } + interface PriceClient { + + getPrice() : String {abstract} + } +} +ApiGateway --> "-imageClient" ImageClient +ApiGateway --> "-priceClient" PriceClient +PriceClientImpl ..|> PriceClient +ImageClientImpl ..|> ImageClient +@enduml \ No newline at end of file diff --git a/api-gateway/etc/api-gateway.png b/api-gateway/etc/api-gateway.png new file mode 100644 index 000000000000..bb3ec2e2e1a8 Binary files /dev/null and b/api-gateway/etc/api-gateway.png differ diff --git a/api-gateway/etc/api-gateway.ucls b/api-gateway/etc/api-gateway.ucls new file mode 100644 index 000000000000..4a74c2108b22 --- /dev/null +++ b/api-gateway/etc/api-gateway.ucls @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/api-gateway/etc/image-microservice.urm.puml b/api-gateway/etc/image-microservice.urm.puml new file mode 100644 index 000000000000..130dac9de383 --- /dev/null +++ b/api-gateway/etc/image-microservice.urm.puml @@ -0,0 +1,12 @@ +@startuml +package com.iluwatar.image.microservice { + class ImageApplication { + + ImageApplication() + + main(args : String[]) {static} + } + class ImageController { + + ImageController() + + getImagePath() : String + } +} +@enduml \ No newline at end of file diff --git a/api-gateway/etc/price-microservice.urm.puml b/api-gateway/etc/price-microservice.urm.puml new file mode 100644 index 000000000000..9893c9c601d1 --- /dev/null +++ b/api-gateway/etc/price-microservice.urm.puml @@ -0,0 +1,12 @@ +@startuml +package com.iluwatar.price.microservice { + class PriceApplication { + + PriceApplication() + + main(args : String[]) {static} + } + class PriceController { + + PriceController() + + getPrice() : String + } +} +@enduml \ No newline at end of file diff --git a/api-gateway/image-microservice/etc/image-microservice.png b/api-gateway/image-microservice/etc/image-microservice.png new file mode 100644 index 000000000000..340285a14f64 Binary files /dev/null and b/api-gateway/image-microservice/etc/image-microservice.png differ diff --git a/api-gateway/image-microservice/etc/image-microservice.ucls b/api-gateway/image-microservice/etc/image-microservice.ucls new file mode 100644 index 000000000000..d3520768f851 --- /dev/null +++ b/api-gateway/image-microservice/etc/image-microservice.ucls @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/api-gateway/image-microservice/pom.xml b/api-gateway/image-microservice/pom.xml new file mode 100644 index 000000000000..db25b947b721 --- /dev/null +++ b/api-gateway/image-microservice/pom.xml @@ -0,0 +1,79 @@ + + + + + api-gateway + com.iluwatar + 1.14.0-SNAPSHOT + + + 4.0.0 + image-microservice + jar + + + + + org.springframework.boot + spring-boot-dependencies + + + + + + org.springframework + spring-webmvc + + + org.springframework.boot + spring-boot-starter-web + + + junit + junit + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + + repackage + + + + + + + \ No newline at end of file diff --git a/api-gateway/image-microservice/src/main/java/com/iluwatar/image/microservice/ImageApplication.java b/api-gateway/image-microservice/src/main/java/com/iluwatar/image/microservice/ImageApplication.java new file mode 100644 index 000000000000..b4cb3d25041b --- /dev/null +++ b/api-gateway/image-microservice/src/main/java/com/iluwatar/image/microservice/ImageApplication.java @@ -0,0 +1,43 @@ +/** + * 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.image.microservice; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * ImageApplication starts up Spring Boot, exposing endpoints for the Image microservice through + * the {@link ImageController}. + */ +@SpringBootApplication +public class ImageApplication { + + /** + * Microservice entry point + * @param args + * command line args + */ + public static void main(String[] args) { + SpringApplication.run(ImageApplication.class, args); + } +} diff --git a/api-gateway/image-microservice/src/main/java/com/iluwatar/image/microservice/ImageController.java b/api-gateway/image-microservice/src/main/java/com/iluwatar/image/microservice/ImageController.java new file mode 100644 index 000000000000..16faf5b70a2b --- /dev/null +++ b/api-gateway/image-microservice/src/main/java/com/iluwatar/image/microservice/ImageController.java @@ -0,0 +1,43 @@ +/** + * 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.image.microservice; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +/** + * Exposes the Image microservice's endpoints + */ +@RestController +public class ImageController { + + /** + * An endpoint for a user to retrieve an image path + * @return An image path + */ + @RequestMapping(value = "/image-path", method = RequestMethod.GET) + public String getImagePath() { + return "/product-image.png"; + } +} diff --git a/api-gateway/image-microservice/src/main/resources/application.properties b/api-gateway/image-microservice/src/main/resources/application.properties new file mode 100644 index 000000000000..aeda0c24cab9 --- /dev/null +++ b/api-gateway/image-microservice/src/main/resources/application.properties @@ -0,0 +1,24 @@ +# +# 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. +# + +server.port=50005 \ No newline at end of file diff --git a/api-gateway/image-microservice/src/test/java/com/iluwatar/image/microservice/ImageControllerTest.java b/api-gateway/image-microservice/src/test/java/com/iluwatar/image/microservice/ImageControllerTest.java new file mode 100644 index 000000000000..cf0c099c1f01 --- /dev/null +++ b/api-gateway/image-microservice/src/test/java/com/iluwatar/image/microservice/ImageControllerTest.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.image.microservice; + +import org.junit.Assert; +import org.junit.Test; + +public class ImageControllerTest { + @Test + public void testGetImagePath() { + ImageController imageController = new ImageController(); + + String imagePath = imageController.getImagePath(); + + Assert.assertEquals("/product-image.png", imagePath); + } +} diff --git a/api-gateway/pom.xml b/api-gateway/pom.xml new file mode 100644 index 000000000000..94f91cabb2d2 --- /dev/null +++ b/api-gateway/pom.xml @@ -0,0 +1,63 @@ + + + + + java-design-patterns + com.iluwatar + 1.14.0-SNAPSHOT + + 4.0.0 + api-gateway + pom + + + + + com.github.markusmo3.urm + urm-maven-plugin + ${urm.version} + + ${project.basedir}/../etc + + com.iluwatar + + + + api-gateway + + + + + + + + image-microservice + price-microservice + api-gateway-service + + diff --git a/api-gateway/price-microservice/etc/price-microservice.png b/api-gateway/price-microservice/etc/price-microservice.png new file mode 100644 index 000000000000..7c57160257cb Binary files /dev/null and b/api-gateway/price-microservice/etc/price-microservice.png differ diff --git a/api-gateway/price-microservice/etc/price-microservice.ucls b/api-gateway/price-microservice/etc/price-microservice.ucls new file mode 100644 index 000000000000..5878395b65fc --- /dev/null +++ b/api-gateway/price-microservice/etc/price-microservice.ucls @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/api-gateway/price-microservice/pom.xml b/api-gateway/price-microservice/pom.xml new file mode 100644 index 000000000000..ce47e9db0e58 --- /dev/null +++ b/api-gateway/price-microservice/pom.xml @@ -0,0 +1,79 @@ + + + + + api-gateway + com.iluwatar + 1.14.0-SNAPSHOT + + + 4.0.0 + price-microservice + jar + + + + + org.springframework.boot + spring-boot-dependencies + + + + + + org.springframework + spring-webmvc + + + org.springframework.boot + spring-boot-starter-web + + + junit + junit + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + + repackage + + + + + + + \ No newline at end of file diff --git a/api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceApplication.java b/api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceApplication.java new file mode 100644 index 000000000000..e777c7c2b970 --- /dev/null +++ b/api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceApplication.java @@ -0,0 +1,43 @@ +/** + * 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.price.microservice; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * PriceApplication starts up Spring Boot, exposing endpoints for the Price microservice through + * the {@link PriceController}. + */ +@SpringBootApplication +public class PriceApplication { + + /** + * Microservice entry point + * @param args + * command line args + */ + public static void main(String[] args) { + SpringApplication.run(PriceApplication.class, args); + } +} diff --git a/api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceController.java b/api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceController.java new file mode 100644 index 000000000000..ec43618faec2 --- /dev/null +++ b/api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceController.java @@ -0,0 +1,43 @@ +/** + * 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.price.microservice; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +/** + * Exposes the Price microservice's endpoints + */ +@RestController +public class PriceController { + + /** + * An endpoint for a user to retrieve a product's price + * @return A product's price + */ + @RequestMapping(value = "/price", method = RequestMethod.GET) + public String getPrice() { + return "20"; + } +} diff --git a/api-gateway/price-microservice/src/main/resources/application.properties b/api-gateway/price-microservice/src/main/resources/application.properties new file mode 100644 index 000000000000..f0b479dfdf0d --- /dev/null +++ b/api-gateway/price-microservice/src/main/resources/application.properties @@ -0,0 +1,24 @@ +# +# 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. +# + +server.port=50006 \ No newline at end of file diff --git a/api-gateway/price-microservice/src/test/java/com/iluwatar/price/microservice/PriceControllerTest.java b/api-gateway/price-microservice/src/test/java/com/iluwatar/price/microservice/PriceControllerTest.java new file mode 100644 index 000000000000..c289549f251e --- /dev/null +++ b/api-gateway/price-microservice/src/test/java/com/iluwatar/price/microservice/PriceControllerTest.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.price.microservice; + +import org.junit.Assert; +import org.junit.Test; + +public class PriceControllerTest { + @Test + public void testgetPrice() { + PriceController priceController = new PriceController(); + + String price = priceController.getPrice(); + + Assert.assertEquals("20", price); + } +} diff --git a/async-method-invocation/README.md b/async-method-invocation/README.md index 93c0249d9639..b96c1d77a925 100644 --- a/async-method-invocation/README.md +++ b/async-method-invocation/README.md @@ -3,11 +3,13 @@ layout: pattern title: Async Method Invocation folder: async-method-invocation permalink: /patterns/async-method-invocation/ +pumlid: TSdB3SCW303GLTe1mFTkunWhk0A3_4dKxTi5UdlIUuhIoCPfuz4Zjhy03EzwIlGyqjbeQR16fJL1HjuOQF362qjZbrFBnWWsTPZeFm3wHwbCZhvQ4RqMOSXIuA1_LzDctJd75m00 categories: Concurrency tags: - Java - Difficulty-Intermediate - Functional + - Reactive --- ## Intent diff --git a/async-method-invocation/etc/async-method-invocation.urm.puml b/async-method-invocation/etc/async-method-invocation.urm.puml new file mode 100644 index 000000000000..b96e843d3f7a --- /dev/null +++ b/async-method-invocation/etc/async-method-invocation.urm.puml @@ -0,0 +1,50 @@ +@startuml +package com.iluwatar.async.method.invocation { + class App { + + App() + - callback(name : String) : AsyncCallback {static} + - lazyval(value : T, delayMillis : long) : Callable {static} + - log(msg : String) {static} + + main(args : String[]) {static} + } + interface AsyncResult { + + await() {abstract} + + getValue() : T {abstract} + + isCompleted() : boolean {abstract} + } + interface AsyncExecutor { + + endProcess(AsyncResult) : T {abstract} + + startProcess(Callable) : AsyncResult {abstract} + + startProcess(Callable, AsyncCallback) : AsyncResult {abstract} + } + class ThreadAsyncExecutor { + - idx : AtomicInteger + + ThreadAsyncExecutor() + + endProcess(asyncResult : AsyncResult) : T + + startProcess(task : Callable) : AsyncResult + + startProcess(task : Callable, callback : AsyncCallback) : AsyncResult + } + interface AsyncCallback { + + onComplete(T, Optional) {abstract} + } + -class CompletableResult { + ~ COMPLETED : int {static} + ~ FAILED : int {static} + ~ RUNNING : int {static} + ~ callback : Optional> + ~ exception : Exception + ~ lock : Object + ~ state : int + ~ value : T + ~ CompletableResult(callback : AsyncCallback) + + await() + + getValue() : T + + isCompleted() : boolean + ~ setException(exception : Exception) + ~ setValue(value : T) + } +} +CompletableResult ..+ ThreadAsyncExecutor +ThreadAsyncExecutor ..|> AsyncExecutor +CompletableResult ..|> AsyncResult +@enduml \ No newline at end of file diff --git a/async-method-invocation/pom.xml b/async-method-invocation/pom.xml index 292eb136b9c4..e374b4753554 100644 --- a/async-method-invocation/pom.xml +++ b/async-method-invocation/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT async-method-invocation diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java index 7f96d9ab7c6e..5a10232f0c56 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java @@ -57,12 +57,10 @@ public AsyncResult startProcess(Callable task, AsyncCallback callba @Override public T endProcess(AsyncResult asyncResult) throws ExecutionException, InterruptedException { - if (asyncResult.isCompleted()) { - return asyncResult.getValue(); - } else { + if (!asyncResult.isCompleted()) { asyncResult.await(); - return asyncResult.getValue(); } + return asyncResult.getValue(); } /** diff --git a/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutorTest.java b/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutorTest.java index b4a23222abe6..09126e99fe3d 100644 --- a/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutorTest.java +++ b/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutorTest.java @@ -31,7 +31,6 @@ import java.util.concurrent.ExecutionException; import static org.junit.Assert.*; -import static org.mockito.Matchers.eq; import static org.mockito.Mockito.*; import static org.mockito.internal.verification.VerificationModeFactory.times; diff --git a/bridge/README.md b/bridge/README.md index 49dad14e46fa..6c1e70631578 100644 --- a/bridge/README.md +++ b/bridge/README.md @@ -3,6 +3,7 @@ layout: pattern title: Bridge folder: bridge permalink: /patterns/bridge/ +pumlid: BSR14SCm20J0Lf82BFxf1akCJ4R26ZZYzkE7zxLljJgoIVfu7S2A3v7pLRhYo3r3l9u6CPHwJjAH5uETllpZhKbejsqn86v1a-CExQwj2mdgqv8-oyev_W00 categories: Structural tags: - Java diff --git a/bridge/etc/bridge.urm.puml b/bridge/etc/bridge.urm.puml new file mode 100644 index 000000000000..84e250a06e50 --- /dev/null +++ b/bridge/etc/bridge.urm.puml @@ -0,0 +1,89 @@ +@startuml +package com.iluwatar.bridge { + class FlyingMagicWeapon { + + FlyingMagicWeapon(imp : FlyingMagicWeaponImpl) + + fly() + + getImp() : FlyingMagicWeaponImpl + + swing() + + unwield() + + wield() + } + class Stormbringer { + + Stormbringer() + + eatSoulImp() + + swingImp() + + unwieldImp() + + wieldImp() + } + abstract class FlyingMagicWeaponImpl { + + FlyingMagicWeaponImpl() + + flyImp() {abstract} + } + class SoulEatingMagicWeapon { + + SoulEatingMagicWeapon(imp : SoulEatingMagicWeaponImpl) + + eatSoul() + + getImp() : SoulEatingMagicWeaponImpl + + swing() + + unwield() + + wield() + } + abstract class MagicWeaponImpl { + + MagicWeaponImpl() + + swingImp() {abstract} + + unwieldImp() {abstract} + + wieldImp() {abstract} + } + abstract class SoulEatingMagicWeaponImpl { + + SoulEatingMagicWeaponImpl() + + eatSoulImp() {abstract} + } + class Excalibur { + + Excalibur() + + blindImp() + + swingImp() + + unwieldImp() + + wieldImp() + } + class Mjollnir { + + Mjollnir() + + flyImp() + + swingImp() + + unwieldImp() + + wieldImp() + } + class App { + + App() + + main(args : String[]) {static} + } + abstract class MagicWeapon { + # imp : MagicWeaponImpl + + MagicWeapon(imp : MagicWeaponImpl) + + getImp() : MagicWeaponImpl + + swing() {abstract} + + unwield() {abstract} + + wield() {abstract} + } + abstract class BlindingMagicWeaponImpl { + + BlindingMagicWeaponImpl() + + blindImp() {abstract} + } + class BlindingMagicWeapon { + + BlindingMagicWeapon(imp : BlindingMagicWeaponImpl) + + blind() + + getImp() : BlindingMagicWeaponImpl + + swing() + + unwield() + + wield() + } +} +MagicWeapon --> "-imp" MagicWeaponImpl +FlyingMagicWeapon --|> MagicWeapon +Stormbringer --|> SoulEatingMagicWeaponImpl +FlyingMagicWeaponImpl --|> MagicWeaponImpl +SoulEatingMagicWeapon --|> MagicWeapon +SoulEatingMagicWeaponImpl --|> MagicWeaponImpl +Excalibur --|> BlindingMagicWeaponImpl +Mjollnir --|> FlyingMagicWeaponImpl +BlindingMagicWeaponImpl --|> MagicWeaponImpl +BlindingMagicWeapon --|> MagicWeapon +@enduml \ No newline at end of file diff --git a/bridge/pom.xml b/bridge/pom.xml index 6ac261286c70..4960dbd0831b 100644 --- a/bridge/pom.xml +++ b/bridge/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT bridge diff --git a/builder/README.md b/builder/README.md index 5d1f3d24d469..335862676ac8 100644 --- a/builder/README.md +++ b/builder/README.md @@ -3,6 +3,7 @@ layout: pattern title: Builder folder: builder permalink: /patterns/builder/ +pumlid: DSR94O0m2030LhG0mzzkC64KXs26GzlNZw_TcRLADagJwOWOlW8OFcNdE79B9wkN1ccKUdLWoGS33KwySMdalEioC89C7Jhw5zYIfNrIrFybhPUHNLu0 categories: Creational tags: - Java @@ -26,6 +27,9 @@ Use the Builder pattern when ## Real world examples * [java.lang.StringBuilder](http://docs.oracle.com/javase/8/docs/api/java/lang/StringBuilder.html) +* [java.nio.ByteBuffer](http://docs.oracle.com/javase/8/docs/api/java/nio/ByteBuffer.html#put-byte-) as well as similar buffers such as FloatBuffer, IntBuffer and so on. +* [java.lang.StringBuffer](http://docs.oracle.com/javase/8/docs/api/java/lang/StringBuffer.html#append-boolean-) +* All implementations of [java.lang.Appendable](http://docs.oracle.com/javase/8/docs/api/java/lang/Appendable.html) * [Apache Camel builders](https://github.com/apache/camel/tree/0e195428ee04531be27a0b659005e3aa8d159d23/camel-core/src/main/java/org/apache/camel/builder) ## Credits diff --git a/builder/etc/builder.ucls b/builder/etc/builder.ucls index 06a83ced7f79..073c5cea70f6 100644 --- a/builder/etc/builder.ucls +++ b/builder/etc/builder.ucls @@ -28,7 +28,7 @@ - "-profession" Profession +Builder ..+ Hero +Hero --> "-armor" Armor +App --+ Hero +Builder --> "-weapon" Weapon +Builder --> "-hairColor" HairColor +Builder --> "-hairType" HairType +Hero --> "-hairColor" HairColor +Builder --> "-profession" Profession +Hero --> "-weapon" Weapon +Hero --> "-hairType" HairType +Builder --> "-armor" Armor +@enduml \ No newline at end of file diff --git a/builder/pom.xml b/builder/pom.xml index 7d83f6c2871c..030990fa2b01 100644 --- a/builder/pom.xml +++ b/builder/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT builder diff --git a/builder/src/main/java/com/iluwatar/builder/App.java b/builder/src/main/java/com/iluwatar/builder/App.java index b5b612529f05..d421de7b6012 100644 --- a/builder/src/main/java/com/iluwatar/builder/App.java +++ b/builder/src/main/java/com/iluwatar/builder/App.java @@ -22,7 +22,7 @@ */ package com.iluwatar.builder; -import com.iluwatar.builder.Hero.HeroBuilder; +import com.iluwatar.builder.Hero.Builder; /** * @@ -41,10 +41,10 @@ * Java 2nd Edition. *

* We want to build {@link Hero} objects, but its construction is complex because of the many - * parameters needed. To aid the user we introduce {@link HeroBuilder} class. {@link HeroBuilder} + * parameters needed. To aid the user we introduce {@link Builder} class. {@link Hero.Builder} * takes the minimum parameters to build {@link Hero} object in its constructor. After that * additional configuration for the {@link Hero} object can be done using the fluent - * {@link HeroBuilder} interface. When configuration is ready the build method is called to receive + * {@link Builder} interface. When configuration is ready the build method is called to receive * the final {@link Hero} object. * */ @@ -58,18 +58,18 @@ public class App { public static void main(String[] args) { Hero mage = - new HeroBuilder(Profession.MAGE, "Riobard").withHairColor(HairColor.BLACK) + new Hero.Builder(Profession.MAGE, "Riobard").withHairColor(HairColor.BLACK) .withWeapon(Weapon.DAGGER).build(); System.out.println(mage); Hero warrior = - new HeroBuilder(Profession.WARRIOR, "Amberjill").withHairColor(HairColor.BLOND) + new Hero.Builder(Profession.WARRIOR, "Amberjill").withHairColor(HairColor.BLOND) .withHairType(HairType.LONG_CURLY).withArmor(Armor.CHAIN_MAIL).withWeapon(Weapon.SWORD) .build(); System.out.println(warrior); Hero thief = - new HeroBuilder(Profession.THIEF, "Desmond").withHairType(HairType.BALD) + new Hero.Builder(Profession.THIEF, "Desmond").withHairType(HairType.BALD) .withWeapon(Weapon.BOW).build(); System.out.println(thief); diff --git a/builder/src/main/java/com/iluwatar/builder/Hero.java b/builder/src/main/java/com/iluwatar/builder/Hero.java index 1835df6384b5..25445851a2bb 100644 --- a/builder/src/main/java/com/iluwatar/builder/Hero.java +++ b/builder/src/main/java/com/iluwatar/builder/Hero.java @@ -36,7 +36,7 @@ public final class Hero { private final Armor armor; private final Weapon weapon; - private Hero(HeroBuilder builder) { + private Hero(Builder builder) { this.profession = builder.profession; this.name = builder.name; this.hairColor = builder.hairColor; @@ -102,7 +102,7 @@ public String toString() { * The builder class. * */ - public static class HeroBuilder { + public static class Builder { private final Profession profession; private final String name; @@ -114,7 +114,7 @@ public static class HeroBuilder { /** * Constructor */ - public HeroBuilder(Profession profession, String name) { + public Builder(Profession profession, String name) { if (profession == null || name == null) { throw new IllegalArgumentException("profession and name can not be null"); } @@ -122,22 +122,22 @@ public HeroBuilder(Profession profession, String name) { this.name = name; } - public HeroBuilder withHairType(HairType hairType) { + public Builder withHairType(HairType hairType) { this.hairType = hairType; return this; } - public HeroBuilder withHairColor(HairColor hairColor) { + public Builder withHairColor(HairColor hairColor) { this.hairColor = hairColor; return this; } - public HeroBuilder withArmor(Armor armor) { + public Builder withArmor(Armor armor) { this.armor = armor; return this; } - public HeroBuilder withWeapon(Weapon weapon) { + public Builder withWeapon(Weapon weapon) { this.weapon = weapon; return this; } diff --git a/builder/src/test/java/com/iluwatar/builder/HeroTest.java b/builder/src/test/java/com/iluwatar/builder/HeroTest.java index 3c308e8c1e05..b0a73a9d0ed7 100644 --- a/builder/src/test/java/com/iluwatar/builder/HeroTest.java +++ b/builder/src/test/java/com/iluwatar/builder/HeroTest.java @@ -39,7 +39,7 @@ public class HeroTest { */ @Test(expected = IllegalArgumentException.class) public void testMissingProfession() throws Exception { - new Hero.HeroBuilder(null, "Sir without a job"); + new Hero.Builder(null, "Sir without a job"); } /** @@ -47,7 +47,7 @@ public void testMissingProfession() throws Exception { */ @Test(expected = IllegalArgumentException.class) public void testMissingName() throws Exception { - new Hero.HeroBuilder(Profession.THIEF, null); + new Hero.Builder(Profession.THIEF, null); } /** @@ -57,7 +57,7 @@ public void testMissingName() throws Exception { public void testBuildHero() throws Exception { final String heroName = "Sir Lancelot"; - final Hero hero = new Hero.HeroBuilder(Profession.WARRIOR, heroName) + final Hero hero = new Hero.Builder(Profession.WARRIOR, heroName) .withArmor(Armor.CHAIN_MAIL) .withWeapon(Weapon.SWORD) .withHairType(HairType.LONG_CURLY) diff --git a/business-delegate/README.md b/business-delegate/README.md index e6e2491222e0..8e6e3456c410 100644 --- a/business-delegate/README.md +++ b/business-delegate/README.md @@ -3,6 +3,7 @@ layout: pattern title: Business Delegate folder: business-delegate permalink: /patterns/business-delegate/ +pumlid: POl13SCm3CHMQGU8zUysgYCuBcJ5a4x9-l6_Fu84tzsgvYxf-Zg06HyYvxkqZYE_6UBrD8YXr7DGrxmPxFJZYxTTeZVR9WFY5ZGu5j2wkad4wYgD8IIe_xQaZp9pw0C0 categories: Business Tier tags: - Java diff --git a/business-delegate/etc/business-delegate.urm.puml b/business-delegate/etc/business-delegate.urm.puml new file mode 100644 index 000000000000..a58136c8ce8d --- /dev/null +++ b/business-delegate/etc/business-delegate.urm.puml @@ -0,0 +1,55 @@ +@startuml +package com.iluwatar.business.delegate { + class EjbService { + + EjbService() + + doProcessing() + } + class BusinessLookup { + - ejbService : EjbService + - jmsService : JmsService + + BusinessLookup() + + getBusinessService(serviceType : ServiceType) : BusinessService + + setEjbService(ejbService : EjbService) + + setJmsService(jmsService : JmsService) + } + class App { + + App() + + main(args : String[]) {static} + } + interface BusinessService { + + doProcessing() {abstract} + } + class Client { + - businessDelegate : BusinessDelegate + + Client(businessDelegate : BusinessDelegate) + + doTask() + } + class BusinessDelegate { + - businessService : BusinessService + - lookupService : BusinessLookup + - serviceType : ServiceType + + BusinessDelegate() + + doTask() + + setLookupService(businessLookup : BusinessLookup) + + setServiceType(serviceType : ServiceType) + } + class JmsService { + + JmsService() + + doProcessing() + } + enum ServiceType { + + EJB {static} + + JMS {static} + + valueOf(name : String) : ServiceType {static} + + values() : ServiceType[] {static} + } +} +BusinessDelegate --> "-serviceType" ServiceType +BusinessLookup --> "-ejbService" EjbService +Client --> "-businessDelegate" BusinessDelegate +BusinessDelegate --> "-businessService" BusinessService +BusinessDelegate --> "-lookupService" BusinessLookup +BusinessLookup --> "-jmsService" JmsService +EjbService ..|> BusinessService +JmsService ..|> BusinessService +@enduml \ No newline at end of file diff --git a/business-delegate/pom.xml b/business-delegate/pom.xml index 3834b4c8fee4..2b0ce9119386 100644 --- a/business-delegate/pom.xml +++ b/business-delegate/pom.xml @@ -30,7 +30,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT business-delegate diff --git a/caching/README.md b/caching/README.md index 2b89d0559e96..b48d060f0012 100644 --- a/caching/README.md +++ b/caching/README.md @@ -3,6 +3,7 @@ layout: pattern title: Caching folder: caching permalink: /patterns/caching/ +pumlid: DSRB4OKm2030LhG0m_rrWyWaE0bc-6ZxpujxsbMKUXwSrfSMCVq7OFYKAj5oJsUZIuCr2bq3fEU3WGOdthWTx59rcnZ1fWu3_GqGKXEjm47VIzeeCqV_0m00 categories: Other tags: - Java @@ -26,3 +27,4 @@ Use the Caching pattern(s) when * [Write-through, write-around, write-back: Cache explained](http://www.computerweekly.com/feature/Write-through-write-around-write-back-Cache-explained) * [Read-Through, Write-Through, Write-Behind, and Refresh-Ahead Caching](https://docs.oracle.com/cd/E15357_01/coh.360/e15723/cache_rtwtwbra.htm#COHDG5177) +* [Cache-Aside](https://msdn.microsoft.com/en-us/library/dn589799.aspx) diff --git a/caching/etc/caching.png b/caching/etc/caching.png index 6b3b2d0556c5..b6ed703ab8b7 100644 Binary files a/caching/etc/caching.png and b/caching/etc/caching.png differ diff --git a/caching/etc/caching.ucls b/caching/etc/caching.ucls index 815a62ec99ce..a058277ea2fd 100644 --- a/caching/etc/caching.ucls +++ b/caching/etc/caching.ucls @@ -1,106 +1,90 @@ - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/caching/etc/caching.urm.puml b/caching/etc/caching.urm.puml new file mode 100644 index 000000000000..b8f4fb49b6b4 --- /dev/null +++ b/caching/etc/caching.urm.puml @@ -0,0 +1,100 @@ +@startuml +package com.iluwatar.caching { + class App { + + App() + + main(args : String[]) {static} + + useReadAndWriteThroughStrategy() + + useReadThroughAndWriteAroundStrategy() + + useReadThroughAndWriteBehindStrategy() + } + ~class Node { + ~ next : Node + ~ previous : Node + ~ userAccount : UserAccount + ~ userId : String + + Node(this$0 : String, userId : UserAccount) + } + class CacheStore { + ~ cache : LruCache {static} + - CacheStore() + + clearCache() {static} + + flushCache() {static} + + initCapacity(capacity : int) {static} + + print() : String {static} + + readThrough(userId : String) : UserAccount {static} + + readThroughWithWriteBackPolicy(userId : String) : UserAccount {static} + + writeAround(userAccount : UserAccount) {static} + + writeBehind(userAccount : UserAccount) {static} + + writeThrough(userAccount : UserAccount) {static} + } + class AppManager { + - cachingPolicy : CachingPolicy {static} + - AppManager() + + find(userId : String) : UserAccount {static} + + initCacheCapacity(capacity : int) {static} + + initCachingPolicy(policy : CachingPolicy) {static} + + initDb(useMongoDb : boolean) {static} + + printCacheContent() : String {static} + + save(userAccount : UserAccount) {static} + } + class UserAccount { + - additionalInfo : String + - userId : String + - userName : String + + UserAccount(userId : String, userName : String, additionalInfo : String) + + getAdditionalInfo() : String + + getUserId() : String + + getUserName() : String + + setAdditionalInfo(additionalInfo : String) + + setUserId(userId : String) + + setUserName(userName : String) + + toString() : String + } + class LruCache { + ~ cache : Map + ~ capacity : int + ~ end : Node + ~ head : Node + + LruCache(capacity : int) + + clear() + + contains(userId : String) : boolean + + get(userId : String) : UserAccount + + getCacheDataInListForm() : List + + getLruData() : UserAccount + + invalidate(userId : String) + + isFull() : boolean + + remove(node : Node) + + set(userId : String, userAccount : UserAccount) + + setCapacity(newCapacity : int) + + setHead(node : Node) + } + class DbManager { + - db : MongoDatabase {static} + - mongoClient : MongoClient {static} + - useMongoDB : boolean {static} + - virtualDB : Map {static} + - DbManager() + + connect() {static} + + createVirtualDb() {static} + + readFromDb(userId : String) : UserAccount {static} + + updateDb(userAccount : UserAccount) {static} + + upsertDb(userAccount : UserAccount) {static} + + writeToDb(userAccount : UserAccount) {static} + } + enum CachingPolicy { + + AROUND {static} + + BEHIND {static} + + THROUGH {static} + - policy : String + + getPolicy() : String + + valueOf(name : String) : CachingPolicy {static} + + values() : CachingPolicy[] {static} + } +} +Node --+ LruCache +LruCache --> "-head" Node +Node --> "-previous" Node +AppManager --> "-cachingPolicy" CachingPolicy +Node --> "-userAccount" UserAccount +CacheStore --> "-cache" LruCache +@enduml \ No newline at end of file diff --git a/caching/pom.xml b/caching/pom.xml index 7b1b2235473d..07347e5c758b 100644 --- a/caching/pom.xml +++ b/caching/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT caching diff --git a/caching/src/main/java/com/iluwatar/caching/App.java b/caching/src/main/java/com/iluwatar/caching/App.java index 8e5a84085ff9..1b1b5e1cac91 100644 --- a/caching/src/main/java/com/iluwatar/caching/App.java +++ b/caching/src/main/java/com/iluwatar/caching/App.java @@ -26,22 +26,23 @@ * * The Caching pattern describes how to avoid expensive re-acquisition of resources by not releasing * the resources immediately after their use. The resources retain their identity, are kept in some - * fast-access storage, and are re-used to avoid having to acquire them again. There are three main - * caching strategies/techniques in this pattern; each with their own pros and cons. They are: + * fast-access storage, and are re-used to avoid having to acquire them again. There are four main + * caching strategies/techniques in this pattern; each with their own pros and cons. They are; * write-through which writes data to the cache and DB in a single transaction, - * write-around which writes data immediately into the DB instead of the cache, and + * write-around which writes data immediately into the DB instead of the cache, * write-behind which writes data into the cache initially whilst the data is only - * written into the DB when the cache is full. The read-through strategy is also - * included in the mentioned three strategies -- returns data from the cache to the caller if - * it exists else queries from DB and stores it into the cache for future use. These - * strategies determine when the data in the cache should be written back to the backing store (i.e. - * Database) and help keep both data sources synchronized/up-to-date. This pattern can improve - * performance and also helps to maintain consistency between data held in the cache and the data in - * the underlying data store. + * written into the DB when the cache is full, and cache-aside which pushes the + * responsibility of keeping the data synchronized in both data sources to the application itself. + * The read-through strategy is also included in the mentioned four strategies -- + * returns data from the cache to the caller if it exists else queries from DB and + * stores it into the cache for future use. These strategies determine when the data in the cache + * should be written back to the backing store (i.e. Database) and help keep both data sources + * synchronized/up-to-date. This pattern can improve performance and also helps to maintain + * consistency between data held in the cache and the data in the underlying data store. *

* In this example, the user account ({@link UserAccount}) entity is used as the underlying * application data. The cache itself is implemented as an internal (Java) data structure. It adopts - * a Least-Recently-Used (LRU) strategy for evicting data from itself when its full. The three + * a Least-Recently-Used (LRU) strategy for evicting data from itself when its full. The four * strategies are individually tested. The testing of the cache is restricted towards saving and * querying of user accounts from the underlying data store ( {@link DbManager}). The main class ( * {@link App} is not aware of the underlying mechanics of the application (i.e. save and query) and @@ -74,6 +75,7 @@ public static void main(String[] args) { app.useReadAndWriteThroughStrategy(); app.useReadThroughAndWriteAroundStrategy(); app.useReadThroughAndWriteBehindStrategy(); + app.useCacheAsideStategy(); } /** @@ -136,4 +138,26 @@ public void useReadThroughAndWriteBehindStrategy() { AppManager.find("004"); System.out.println(AppManager.printCacheContent()); } + + /** + * Cache-Aside + */ + public void useCacheAsideStategy() { + System.out.println("# CachingPolicy.ASIDE"); + AppManager.initCachingPolicy(CachingPolicy.ASIDE); + System.out.println(AppManager.printCacheContent()); + + UserAccount userAccount3 = new UserAccount("003", "Adam", "He likes food."); + UserAccount userAccount4 = new UserAccount("004", "Rita", "She hates cats."); + UserAccount userAccount5 = new UserAccount("005", "Isaac", "He is allergic to mustard."); + AppManager.save(userAccount3); + AppManager.save(userAccount4); + AppManager.save(userAccount5); + + System.out.println(AppManager.printCacheContent()); + AppManager.find("003"); + System.out.println(AppManager.printCacheContent()); + AppManager.find("004"); + System.out.println(AppManager.printCacheContent()); + } } diff --git a/caching/src/main/java/com/iluwatar/caching/AppManager.java b/caching/src/main/java/com/iluwatar/caching/AppManager.java index 2967c759f7b7..2d6a424a35b4 100644 --- a/caching/src/main/java/com/iluwatar/caching/AppManager.java +++ b/caching/src/main/java/com/iluwatar/caching/AppManager.java @@ -64,12 +64,7 @@ public static void initDb(boolean useMongoDb) { public static void initCachingPolicy(CachingPolicy policy) { cachingPolicy = policy; if (cachingPolicy == CachingPolicy.BEHIND) { - Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { - @Override - public void run() { - CacheStore.flushCache(); - } - })); + Runtime.getRuntime().addShutdownHook(new Thread(CacheStore::flushCache)); } CacheStore.clearCache(); } @@ -86,6 +81,8 @@ public static UserAccount find(String userId) { return CacheStore.readThrough(userId); } else if (cachingPolicy == CachingPolicy.BEHIND) { return CacheStore.readThroughWithWriteBackPolicy(userId); + } else if (cachingPolicy == CachingPolicy.ASIDE) { + return findAside(userId); } return null; } @@ -100,10 +97,37 @@ public static void save(UserAccount userAccount) { CacheStore.writeAround(userAccount); } else if (cachingPolicy == CachingPolicy.BEHIND) { CacheStore.writeBehind(userAccount); + } else if (cachingPolicy == CachingPolicy.ASIDE) { + saveAside(userAccount); } } public static String printCacheContent() { return CacheStore.print(); } + + /** + * Cache-Aside save user account helper + */ + private static void saveAside(UserAccount userAccount) { + DbManager.updateDb(userAccount); + CacheStore.invalidate(userAccount.getUserId()); + } + + /** + * Cache-Aside find user account helper + */ + private static UserAccount findAside(String userId) { + UserAccount userAccount = CacheStore.get(userId); + if (userAccount != null) { + return userAccount; + } + + userAccount = DbManager.readFromDb(userId); + if (userAccount != null) { + CacheStore.set(userId, userAccount); + } + + return userAccount; + } } diff --git a/caching/src/main/java/com/iluwatar/caching/CacheStore.java b/caching/src/main/java/com/iluwatar/caching/CacheStore.java index 5903f82193e9..c2b95a1bdb55 100644 --- a/caching/src/main/java/com/iluwatar/caching/CacheStore.java +++ b/caching/src/main/java/com/iluwatar/caching/CacheStore.java @@ -40,7 +40,7 @@ private CacheStore() { * Init cache capacity */ public static void initCapacity(int capacity) { - if (null == cache) { + if (cache == null) { cache = new LruCache(capacity); } else { cache.setCapacity(capacity); @@ -121,7 +121,7 @@ public static void writeBehind(UserAccount userAccount) { * Clears cache */ public static void clearCache() { - if (null != cache) { + if (cache != null) { cache.clear(); } } @@ -153,4 +153,25 @@ public static String print() { sb.append("----\n"); return sb.toString(); } + + /** + * Delegate to backing cache store + */ + public static UserAccount get(String userId) { + return cache.get(userId); + } + + /** + * Delegate to backing cache store + */ + public static void set(String userId, UserAccount userAccount) { + cache.set(userId, userAccount); + } + + /** + * Delegate to backing cache store + */ + public static void invalidate(String userId) { + cache.invalidate(userId); + } } diff --git a/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java b/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java index 490113baa3d9..5ad32f6993a5 100644 --- a/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java +++ b/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java @@ -24,11 +24,11 @@ /** * - * Enum class containing the three caching strategies implemented in the pattern. + * Enum class containing the four caching strategies implemented in the pattern. * */ public enum CachingPolicy { - THROUGH("through"), AROUND("around"), BEHIND("behind"); + THROUGH("through"), AROUND("around"), BEHIND("behind"), ASIDE("aside"); private String policy; diff --git a/caching/src/main/java/com/iluwatar/caching/DbManager.java b/caching/src/main/java/com/iluwatar/caching/DbManager.java index c12461d0c690..455302106b57 100644 --- a/caching/src/main/java/com/iluwatar/caching/DbManager.java +++ b/caching/src/main/java/com/iluwatar/caching/DbManager.java @@ -82,7 +82,7 @@ public static UserAccount readFromDb(String userId) { } return null; } - if (null == db) { + if (db == null) { try { connect(); } catch (ParseException e) { @@ -106,7 +106,7 @@ public static void writeToDb(UserAccount userAccount) { virtualDB.put(userAccount.getUserId(), userAccount); return; } - if (null == db) { + if (db == null) { try { connect(); } catch (ParseException e) { @@ -126,7 +126,7 @@ public static void updateDb(UserAccount userAccount) { virtualDB.put(userAccount.getUserId(), userAccount); return; } - if (null == db) { + if (db == null) { try { connect(); } catch (ParseException e) { @@ -148,7 +148,7 @@ public static void upsertDb(UserAccount userAccount) { virtualDB.put(userAccount.getUserId(), userAccount); return; } - if (null == db) { + if (db == null) { try { connect(); } catch (ParseException e) { diff --git a/caching/src/main/java/com/iluwatar/caching/LruCache.java b/caching/src/main/java/com/iluwatar/caching/LruCache.java index 5c5549afd9d7..00e27ccf1fc1 100644 --- a/caching/src/main/java/com/iluwatar/caching/LruCache.java +++ b/caching/src/main/java/com/iluwatar/caching/LruCache.java @@ -73,7 +73,6 @@ public UserAccount get(String userId) { } /** - * * Remove node from linked list. */ public void remove(Node node) { @@ -90,7 +89,6 @@ public void remove(Node node) { } /** - * * Move node to the front of the list. */ public void setHead(Node node) { @@ -136,10 +134,11 @@ public boolean contains(String userId) { * Invalidate cache for user */ public void invalidate(String userId) { - System.out.println("# " + userId + " has been updated! Removing older version from cache..."); - Node toBeRemoved = cache.get(userId); - remove(toBeRemoved); - cache.remove(userId); + Node toBeRemoved = cache.remove(userId); + if (toBeRemoved != null) { + System.out.println("# " + userId + " has been updated! Removing older version from cache..."); + remove(toBeRemoved); + } } public boolean isFull() { @@ -160,7 +159,6 @@ public void clear() { } /** - * * Returns cache data in list form. */ public List getCacheDataInListForm() { diff --git a/caching/src/main/java/com/iluwatar/caching/UserAccount.java b/caching/src/main/java/com/iluwatar/caching/UserAccount.java index e2444ad72769..657f12fa33f1 100644 --- a/caching/src/main/java/com/iluwatar/caching/UserAccount.java +++ b/caching/src/main/java/com/iluwatar/caching/UserAccount.java @@ -23,9 +23,7 @@ package com.iluwatar.caching; /** - * * Entity class (stored in cache and DB) used in the application. - * */ public class UserAccount { private String userId; diff --git a/caching/src/test/java/com/iluwatar/caching/CachingTest.java b/caching/src/test/java/com/iluwatar/caching/CachingTest.java index 19262a3b6e35..9d2299902a8c 100644 --- a/caching/src/test/java/com/iluwatar/caching/CachingTest.java +++ b/caching/src/test/java/com/iluwatar/caching/CachingTest.java @@ -60,4 +60,9 @@ public void testReadThroughAndWriteAroundStrategy() { public void testReadThroughAndWriteBehindStrategy() { app.useReadThroughAndWriteBehindStrategy(); } + + @Test + public void testCacheAsideStrategy() { + app.useCacheAsideStategy(); + } } diff --git a/callback/README.md b/callback/README.md index a408fd5e0bd5..278aa9b0aa5a 100644 --- a/callback/README.md +++ b/callback/README.md @@ -3,6 +3,7 @@ layout: pattern title: Callback folder: callback permalink: /patterns/callback/ +pumlid: FSVB4S8m30N0Lg20M7UwUL4qYOciUFGXxSE9s-wp6sjjKgwF8tF6YyXnjxtdKMk5E5-MOjdu6jIrRYIStlXWsIJwRij4fhW53SGFn51TmIT9yZ-jVBHPGxy0 categories: Other tags: - Java diff --git a/callback/etc/callback.urm.puml b/callback/etc/callback.urm.puml new file mode 100644 index 000000000000..b9e1ca694e44 --- /dev/null +++ b/callback/etc/callback.urm.puml @@ -0,0 +1,25 @@ +@startuml +package com.iluwatar.callback { + interface Callback { + + call() {abstract} + } + abstract class Task { + + Task() + + execute() {abstract} + + executeWith(callback : Callback) + } + class App { + + App() + + main(args : String[]) {static} + } + class SimpleTask { + + SimpleTask() + + execute() + } + class LambdasApp { + + LambdasApp() + + main(args : String[]) {static} + } +} +SimpleTask --|> Task +@enduml \ No newline at end of file diff --git a/callback/pom.xml b/callback/pom.xml index 8615c4653e57..70dd8e5e278a 100644 --- a/callback/pom.xml +++ b/callback/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT callback diff --git a/chain/README.md b/chain/README.md index ef18f6f64ca5..f5f6c9de9763 100644 --- a/chain/README.md +++ b/chain/README.md @@ -3,6 +3,7 @@ layout: pattern title: Chain of responsibility folder: chain permalink: /patterns/chain/ +pumlid: 9SR13SCm20NGLTe1OkxTXX0KKzd4Wa-pVYlrdTxJN4OTMZ4U7LZv8Wg-ssdejLTgoELGHvDhaesw6HpqvWzlXwQTlYq6D3nfSlv2qjcS5F9VgvXjrHnV categories: Behavioral tags: - Java @@ -28,6 +29,7 @@ Use Chain of Responsibility when * [java.util.logging.Logger#log()](http://docs.oracle.com/javase/8/docs/api/java/util/logging/Logger.html#log%28java.util.logging.Level,%20java.lang.String%29) * [Apache Commons Chain](https://commons.apache.org/proper/commons-chain/index.html) +* [javax.servlet.Filter#doFilter()](http://docs.oracle.com/javaee/7/api/javax/servlet/Filter.html#doFilter-javax.servlet.ServletRequest-javax.servlet.ServletResponse-javax.servlet.FilterChain-) ## Credits diff --git a/chain/etc/chain.urm.puml b/chain/etc/chain.urm.puml new file mode 100644 index 000000000000..21365765d0e3 --- /dev/null +++ b/chain/etc/chain.urm.puml @@ -0,0 +1,60 @@ +@startuml +package com.iluwatar.chain { + class OrcSoldier { + + OrcSoldier(handler : RequestHandler) + + handleRequest(req : Request) + + toString() : String + } + class OrcCommander { + + OrcCommander(handler : RequestHandler) + + handleRequest(req : Request) + + toString() : String + } + class OrcKing { + ~ chain : RequestHandler + + OrcKing() + - buildChain() + + makeRequest(req : Request) + } + class Request { + - handled : boolean + - requestDescription : String + - requestType : RequestType + + Request(requestType : RequestType, requestDescription : String) + + getRequestDescription() : String + + getRequestType() : RequestType + + isHandled() : boolean + + markHandled() + + toString() : String + } + class App { + + App() + + main(args : String[]) {static} + } + abstract class RequestHandler { + - next : RequestHandler + + RequestHandler(next : RequestHandler) + + handleRequest(req : Request) + # printHandling(req : Request) + + toString() : String {abstract} + } + class OrcOfficer { + + OrcOfficer(handler : RequestHandler) + + handleRequest(req : Request) + + toString() : String + } + enum RequestType { + + COLLECT_TAX {static} + + DEFEND_CASTLE {static} + + TORTURE_PRISONER {static} + + valueOf(name : String) : RequestType {static} + + values() : RequestType[] {static} + } +} +RequestHandler --> "-next" RequestHandler +Request --> "-requestType" RequestType +OrcKing --> "-chain" RequestHandler +OrcSoldier --|> RequestHandler +OrcCommander --|> RequestHandler +OrcOfficer --|> RequestHandler +@enduml \ No newline at end of file diff --git a/chain/pom.xml b/chain/pom.xml index a1cbffcb2479..5593d3e73e50 100644 --- a/chain/pom.xml +++ b/chain/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT chain diff --git a/command/README.md b/command/README.md index ee0003ab780c..3996d2842baf 100644 --- a/command/README.md +++ b/command/README.md @@ -3,11 +3,13 @@ layout: pattern title: Command folder: command permalink: /patterns/command/ +pumlid: DSgn4OCm30NGLM00h3xR25i7vYpXaxx2-g59zugtTgiZcwIFvGHcV8YSdt9qdBbdYDVR88PIRwK-yc6mqyLVtff4FsoR38XRa7Aye3SgMoD1_RkaQvcfumS0 categories: Behavioral -tags: +tags: - Java - Gang Of Four - Difficulty-Intermediate + - Functional --- ## Also known as @@ -39,6 +41,7 @@ Use the Command pattern when you want to * [java.lang.Runnable](http://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html) * [Netflix Hystrix](https://github.com/Netflix/Hystrix/wiki) +* [javax.swing.Action](http://docs.oracle.com/javase/8/docs/api/javax/swing/Action.html) ## Credits diff --git a/command/etc/command.urm.puml b/command/etc/command.urm.puml new file mode 100644 index 000000000000..27bff7330605 --- /dev/null +++ b/command/etc/command.urm.puml @@ -0,0 +1,84 @@ +@startuml +package com.iluwatar.command { + class InvisibilitySpell { + - target : Target + + InvisibilitySpell() + + execute(target : Target) + + redo() + + toString() : String + + undo() + } + class App { + + App() + + main(args : String[]) {static} + } + abstract class Command { + + Command() + + execute(Target) {abstract} + + redo() {abstract} + + toString() : String {abstract} + + undo() {abstract} + } + class Goblin { + + Goblin() + + toString() : String + } + abstract class Target { + - size : Size + - visibility : Visibility + + Target() + + getSize() : Size + + getVisibility() : Visibility + + printStatus() + + setSize(size : Size) + + setVisibility(visibility : Visibility) + + toString() : String {abstract} + } + class Wizard { + - redoStack : Deque + - undoStack : Deque + + Wizard() + + castSpell(command : Command, target : Target) + + redoLastSpell() + + toString() : String + + undoLastSpell() + } + class ShrinkSpell { + - oldSize : Size + - target : Target + + ShrinkSpell() + + execute(target : Target) + + redo() + + toString() : String + + undo() + } + enum Size { + + LARGE {static} + + NORMAL {static} + + SMALL {static} + + UNDEFINED {static} + - title : String + + toString() : String + + valueOf(name : String) : Size {static} + + values() : Size[] {static} + } + enum Visibility { + + INVISIBLE {static} + + UNDEFINED {static} + + VISIBLE {static} + - title : String + + toString() : String + + valueOf(name : String) : Visibility {static} + + values() : Visibility[] {static} + } +} +Target --> "-size" Size +Wizard --> "-undoStack" Command +ShrinkSpell --> "-oldSize" Size +InvisibilitySpell --> "-target" Target +ShrinkSpell --> "-target" Target +Target --> "-visibility" Visibility +InvisibilitySpell --|> Command +Goblin --|> Target +ShrinkSpell --|> Command +@enduml \ No newline at end of file diff --git a/command/pom.xml b/command/pom.xml index bb5cd78ff053..406c6d100123 100644 --- a/command/pom.xml +++ b/command/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT command diff --git a/composite/README.md b/composite/README.md index 8b980292d9ce..fce6ed6af780 100644 --- a/composite/README.md +++ b/composite/README.md @@ -3,6 +3,7 @@ layout: pattern title: Composite folder: composite permalink: /patterns/composite/ +pumlid: HSf13eCm30NHgy01YFUzZGaM62LEP7-NwvTTT_EaMTLgoqFIst81Cpv4payv5LVk6U9r6CHGwkYaBHy6EztyvUsGqDEsoO2u1NMED-WTvmY5aA3-LT9xcTdR3m00 categories: Structural tags: - Java diff --git a/composite/etc/composite.urm.puml b/composite/etc/composite.urm.puml new file mode 100644 index 000000000000..82e9b65eb059 --- /dev/null +++ b/composite/etc/composite.urm.puml @@ -0,0 +1,42 @@ +@startuml +package com.iluwatar.composite { + class Word { + + Word(letters : List) + # printThisAfter() + # printThisBefore() + } + class App { + + App() + + main(args : String[]) {static} + } + class Messenger { + + Messenger() + ~ messageFromElves() : LetterComposite + ~ messageFromOrcs() : LetterComposite + } + class Letter { + - c : char + + Letter(c : char) + # printThisAfter() + # printThisBefore() + } + class Sentence { + + Sentence(words : List) + # printThisAfter() + # printThisBefore() + } + abstract class LetterComposite { + - children : List + + LetterComposite() + + add(letter : LetterComposite) + + count() : int + + print() + # printThisAfter() {abstract} + # printThisBefore() {abstract} + } +} +LetterComposite --> "-children" LetterComposite +Word --|> LetterComposite +Letter --|> LetterComposite +Sentence --|> LetterComposite +@enduml \ No newline at end of file diff --git a/composite/pom.xml b/composite/pom.xml index 6513a104e900..48d693d2fc86 100644 --- a/composite/pom.xml +++ b/composite/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT composite diff --git a/dao/README.md b/dao/README.md index 785a1c362a58..b1b655edf96f 100644 --- a/dao/README.md +++ b/dao/README.md @@ -3,6 +3,7 @@ layout: pattern title: Data Access Object folder: dao permalink: /patterns/dao/ +pumlid: 5SR14OKW30N0LhG0oVrt4o6ZE12Ov4NR_thQNQlc5aN2sd82qtz4naywAixOmyNoK8WYvT6fjdWOR7JnpLiHhuTkam4nTUhiRwZm847-J64zpUZj3m00 categories: Persistence Tier tags: - Java diff --git a/dao/etc/dao.urm.puml b/dao/etc/dao.urm.puml new file mode 100644 index 000000000000..90a9c9725f78 --- /dev/null +++ b/dao/etc/dao.urm.puml @@ -0,0 +1,65 @@ +@startuml +package com.iluwatar.dao { + class InMemoryCustomerDao { + - idToCustomer : Map + + InMemoryCustomerDao() + + add(customer : Customer) : boolean + + delete(customer : Customer) : boolean + + getAll() : Stream + + getById(id : int) : Optional + + update(customer : Customer) : boolean + } + interface CustomerSchemaSql { + + CREATE_SCHEMA_SQL : String {static} + + DELETE_SCHEMA_SQL : String {static} + } + interface CustomerDao { + + add(Customer) : boolean {abstract} + + delete(Customer) : boolean {abstract} + + getAll() : Stream {abstract} + + getById(int) : Optional {abstract} + + update(Customer) : boolean {abstract} + } + class App { + - DB_URL : String {static} + - log : Logger {static} + + App() + - addCustomers(customerDao : CustomerDao) {static} + - createDataSource() : DataSource {static} + - createSchema(dataSource : DataSource) {static} + - deleteSchema(dataSource : DataSource) {static} + + generateSampleCustomers() : List {static} + + main(args : String[]) {static} + - performOperationsUsing(customerDao : CustomerDao) {static} + } + class DbCustomerDao { + - dataSource : DataSource + + DbCustomerDao(dataSource : DataSource) + + add(customer : Customer) : boolean + - createCustomer(resultSet : ResultSet) : Customer + + delete(customer : Customer) : boolean + + getAll() : Stream + + getById(id : int) : Optional + - getConnection() : Connection + - mutedClose(connection : Connection) + + update(customer : Customer) : boolean + } + class Customer { + - firstName : String + - id : int + - lastName : String + + Customer(id : int, firstName : String, lastName : String) + + equals(that : Object) : boolean + + getFirstName() : String + + getId() : int + + getLastName() : String + + hashCode() : int + + setFirstName(firstName : String) + + setId(id : int) + + setLastName(lastName : String) + + toString() : String + } +} +InMemoryCustomerDao ..|> CustomerDao +DbCustomerDao ..|> CustomerDao +@enduml \ No newline at end of file diff --git a/dao/pom.xml b/dao/pom.xml index 26c35abb0d7d..e414f2b27c06 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -30,7 +30,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT dao diff --git a/data-mapper/README.md b/data-mapper/README.md new file mode 100644 index 000000000000..362f19c51d14 --- /dev/null +++ b/data-mapper/README.md @@ -0,0 +1,26 @@ +--- +layout: pattern +title: Data Mapper +folder: data-mapper +permalink: /patterns/data-mapper/ +pumlid: JShB3OGm303HLg20nFVjnYGM1CN6ycTfVtFSsnjfzY5jPgUqkLqHwXy0mxUU8wuyqidQ8q4IjJqCO-QBWGOtVh5qyd5AKOmW4mT6Nu2-ZiAekapH_hkcSTNa-GC0 +categories: Persistence Tier +tags: + - Java + - Difficulty-Beginner +--- + +## Intent +A layer of mappers that moves data between objects and a database while keeping them independent of each other and the mapper itself + +![alt text](./etc/data-mapper.png "Data Mapper") + +## Applicability +Use the Data Mapper in any of the following situations + +* when you want to decouple data objects from DB access layer +* when you want to write multiple data retrieval/persistence implementations + +## Credits + +* [Data Mapper](http://richard.jp.leguen.ca/tutoring/soen343-f2010/tutorials/implementing-data-mapper/) diff --git a/data-mapper/etc/data-mapper.png b/data-mapper/etc/data-mapper.png new file mode 100644 index 000000000000..bcda8054aac8 Binary files /dev/null and b/data-mapper/etc/data-mapper.png differ diff --git a/data-mapper/etc/data-mapper.ucls b/data-mapper/etc/data-mapper.ucls new file mode 100644 index 000000000000..2467983ce212 --- /dev/null +++ b/data-mapper/etc/data-mapper.ucls @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data-mapper/etc/data-mapper.urm.puml b/data-mapper/etc/data-mapper.urm.puml new file mode 100644 index 000000000000..a16df0020089 --- /dev/null +++ b/data-mapper/etc/data-mapper.urm.puml @@ -0,0 +1,42 @@ +@startuml +package com.iluwatar.datamapper { + interface StudentDataMapper { + + delete(Student) {abstract} + + find(int) : Optional {abstract} + + insert(Student) {abstract} + + update(Student) {abstract} + } + class App { + - log : Logger {static} + - App() + + main(args : String[]) {static} + } + class Student { + - grade : char + - name : String + - serialVersionUID : long {static} + - studentId : int + + Student(studentId : int, name : String, grade : char) + + equals(inputObject : Object) : boolean + + getGrade() : char + + getName() : String + + getStudentId() : int + + hashCode() : int + + setGrade(grade : char) + + setName(name : String) + + setStudentId(studentId : int) + + toString() : String + } + class StudentDataMapperImpl { + - students : List + + StudentDataMapperImpl() + + delete(studentToBeDeleted : Student) + + find(studentId : int) : Optional + + getStudents() : List + + insert(studentToBeInserted : Student) + + update(studentToBeUpdated : Student) + } +} +StudentDataMapperImpl --> "-students" Student +StudentDataMapperImpl ..|> StudentDataMapper +@enduml \ No newline at end of file diff --git a/data-mapper/pom.xml b/data-mapper/pom.xml new file mode 100644 index 000000000000..eb342e9eb7a7 --- /dev/null +++ b/data-mapper/pom.xml @@ -0,0 +1,45 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.14.0-SNAPSHOT + + data-mapper + + + junit + junit + test + + + log4j + log4j + + + diff --git a/data-mapper/src/main/java/com/iluwatar/datamapper/App.java b/data-mapper/src/main/java/com/iluwatar/datamapper/App.java new file mode 100644 index 000000000000..5fcd0d9ea04c --- /dev/null +++ b/data-mapper/src/main/java/com/iluwatar/datamapper/App.java @@ -0,0 +1,77 @@ +/** + * The MIT License Copyright (c) 2016 Amit Dixit + * + * 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.datamapper; + +import java.util.Optional; + +import org.apache.log4j.Logger; + +/** + * The Data Mapper (DM) is a layer of software that separates the in-memory objects from the + * database. Its responsibility is to transfer data between the two and also to isolate them from + * each other. With Data Mapper the in-memory objects needn't know even that there's a database + * present; they need no SQL interface code, and certainly no knowledge of the database schema. (The + * database schema is always ignorant of the objects that use it.) Since it's a form of Mapper , + * Data Mapper itself is even unknown to the domain layer. + *

+ * The below example demonstrates basic CRUD operations: Create, Read, Update, and Delete. + * + */ +public final class App { + + private static Logger log = Logger.getLogger(App.class); + + /** + * Program entry point. + * + * @param args command line args. + */ + public static void main(final String... args) { + + /* Create new data mapper for type 'first' */ + final StudentDataMapper mapper = new StudentDataMapperImpl(); + + /* Create new student */ + Student student = new Student(1, "Adam", 'A'); + + /* Add student in respectibe store */ + mapper.insert(student); + + log.debug("App.main(), student : " + student + ", is inserted"); + + /* Find this student */ + final Optional studentToBeFound = mapper.find(student.getStudentId()); + + log.debug("App.main(), student : " + studentToBeFound + ", is searched"); + + /* Update existing student object */ + student = new Student(student.getStudentId(), "AdamUpdated", 'A'); + + /* Update student in respectibe db */ + mapper.update(student); + + log.debug("App.main(), student : " + student + ", is updated"); + log.debug("App.main(), student : " + student + ", is going to be deleted"); + + /* Delete student in db */ + mapper.delete(student); + } + + private App() {} +} diff --git a/data-mapper/src/main/java/com/iluwatar/datamapper/DataMapperException.java b/data-mapper/src/main/java/com/iluwatar/datamapper/DataMapperException.java new file mode 100644 index 000000000000..a6995b06d6fe --- /dev/null +++ b/data-mapper/src/main/java/com/iluwatar/datamapper/DataMapperException.java @@ -0,0 +1,42 @@ +/** + * The MIT License Copyright (c) 2016 Amit Dixit + * + * 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.datamapper; + +/** + * Using Runtime Exception for avoiding dependancy on implementation exceptions. This helps in + * decoupling. + * + * @author amit.dixit + * + */ +public final class DataMapperException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + /** + * Constructs a new runtime exception with the specified detail message. The cause is not + * initialized, and may subsequently be initialized by a call to {@link #initCause}. + * + * @param message the detail message. The detail message is saved for later retrieval by the + * {@link #getMessage()} method. + */ + public DataMapperException(final String message) { + super(message); + } +} diff --git a/data-mapper/src/main/java/com/iluwatar/datamapper/Student.java b/data-mapper/src/main/java/com/iluwatar/datamapper/Student.java new file mode 100644 index 000000000000..0164533c85fc --- /dev/null +++ b/data-mapper/src/main/java/com/iluwatar/datamapper/Student.java @@ -0,0 +1,139 @@ +/** + * The MIT License Copyright (c) 2016 Amit Dixit + * + * 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.datamapper; + + +import java.io.Serializable; + +public final class Student implements Serializable { + + private static final long serialVersionUID = 1L; + + private int studentId; + private String name; + private char grade; + + + /** + * 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() { + return studentId; + } + + /** + * + * @param studentId as unique student id + */ + public void setStudentId(final int studentId) { + this.studentId = studentId; + } + + /** + * + * @return name of student + */ + public String getName() { + return name; + } + + /** + * + * @param name as 'name' of student + */ + public void setName(final String name) { + this.name = name; + } + + /** + * + * @return grade of student + */ + public char getGrade() { + return grade; + } + + /** + * + * @param grade as 'grade of student' + */ + public void setGrade(final char grade) { + this.grade = grade; + } + + /** + * + */ + @Override + public boolean equals(final Object inputObject) { + + boolean isEqual = false; + + /* Check if both objects are same */ + if (this == inputObject) { + + isEqual = true; + } else if (inputObject != null && getClass() == inputObject.getClass()) { + + final Student inputStudent = (Student) inputObject; + + /* If student id matched */ + if (this.getStudentId() == inputStudent.getStudentId()) { + + isEqual = true; + } + } + + return isEqual; + } + + /** + * + */ + @Override + public int hashCode() { + + /* Student id is assumed to be unique */ + return this.getStudentId(); + } + + /** + * + */ + @Override + public String toString() { + return "Student [studentId=" + studentId + ", name=" + name + ", grade=" + grade + "]"; + } +} diff --git a/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapper.java b/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapper.java new file mode 100644 index 000000000000..40f0c5c72052 --- /dev/null +++ b/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapper.java @@ -0,0 +1,32 @@ +/** + * The MIT License Copyright (c) 2016 Amit Dixit + * + * 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.datamapper; + +import java.util.Optional; + +public interface StudentDataMapper { + + Optional find(int studentId); + + void insert(Student student) throws DataMapperException; + + void update(Student student) throws DataMapperException; + + void delete(Student student) throws DataMapperException; +} diff --git a/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapperImpl.java b/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapperImpl.java new file mode 100644 index 000000000000..7ecd9e7f84d6 --- /dev/null +++ b/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapperImpl.java @@ -0,0 +1,102 @@ +/** + * The MIT License Copyright (c) 2016 Amit Dixit + * + * 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.datamapper; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public final class StudentDataMapperImpl implements StudentDataMapper { + + /* Note: Normally this would be in the form of an actual database */ + private List students = new ArrayList<>(); + + @Override + public Optional find(int studentId) { + + /* Compare with existing students */ + for (final Student student : this.getStudents()) { + + /* Check if student is found */ + if (student.getStudentId() == studentId) { + + return Optional.of(student); + } + } + + /* Return empty value */ + return Optional.empty(); + } + + @Override + public void update(Student studentToBeUpdated) throws DataMapperException { + + + /* Check with existing students */ + if (this.getStudents().contains(studentToBeUpdated)) { + + /* Get the index of student in list */ + final int index = this.getStudents().indexOf(studentToBeUpdated); + + /* Update the student in list */ + this.getStudents().set(index, studentToBeUpdated); + + } else { + + /* Throw user error after wrapping in a runtime exception */ + throw new DataMapperException("Student [" + studentToBeUpdated.getName() + "] is not found"); + } + } + + @Override + public void insert(Student studentToBeInserted) throws DataMapperException { + + /* Check with existing students */ + if (!this.getStudents().contains(studentToBeInserted)) { + + /* Add student in list */ + this.getStudents().add(studentToBeInserted); + + } else { + + /* Throw user error after wrapping in a runtime exception */ + throw new DataMapperException("Student already [" + studentToBeInserted.getName() + "] exists"); + } + } + + @Override + public void delete(Student studentToBeDeleted) throws DataMapperException { + + /* Check with existing students */ + if (this.getStudents().contains(studentToBeDeleted)) { + + /* Delete the student from list */ + this.getStudents().remove(studentToBeDeleted); + + } else { + + /* Throw user error after wrapping in a runtime exception */ + throw new DataMapperException("Student [" + studentToBeDeleted.getName() + "] is not found"); + } + } + + public List getStudents() { + return this.students; + } +} diff --git a/data-mapper/src/main/resources/log4j.xml b/data-mapper/src/main/resources/log4j.xml new file mode 100644 index 000000000000..b591c17e1665 --- /dev/null +++ b/data-mapper/src/main/resources/log4j.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data-mapper/src/test/java/com/iluwatar/datamapper/AppTest.java b/data-mapper/src/test/java/com/iluwatar/datamapper/AppTest.java new file mode 100644 index 000000000000..f2858100efbb --- /dev/null +++ b/data-mapper/src/test/java/com/iluwatar/datamapper/AppTest.java @@ -0,0 +1,34 @@ +/** + * The MIT License Copyright (c) 2016 Amit Dixit + * + * 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.datamapper; + +import com.iluwatar.datamapper.App; +import org.junit.Test; + +/** + * Tests that Data-Mapper example runs without errors. + */ +public final class AppTest { + + @Test + public void test() { + final String[] args = {}; + App.main(args); + } +} diff --git a/data-mapper/src/test/java/com/iluwatar/datamapper/DataMapperTest.java b/data-mapper/src/test/java/com/iluwatar/datamapper/DataMapperTest.java new file mode 100644 index 000000000000..17f4d3922447 --- /dev/null +++ b/data-mapper/src/test/java/com/iluwatar/datamapper/DataMapperTest.java @@ -0,0 +1,73 @@ +/** + * The MIT License Copyright (c) 2016 Amit Dixit + * + * 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.datamapper; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import com.iluwatar.datamapper.Student; +import com.iluwatar.datamapper.StudentDataMapper; +import com.iluwatar.datamapper.StudentDataMapperImpl; + +/** + * The Data Mapper (DM) is a layer of software that separates the in-memory objects from the + * database. Its responsibility is to transfer data between the two and also to isolate them from + * each other. With Data Mapper the in-memory objects needn't know even that there's a database + * present; they need no SQL interface code, and certainly no knowledge of the database schema. (The + * database schema is always ignorant of the objects that use it.) Since it's a form of Mapper , + * Data Mapper itself is even unknown to the domain layer. + *

+ */ +public class DataMapperTest { + + /** + * This test verify that first data mapper is able to perform all CRUD operations on Student + */ + @Test + public void testFirstDataMapper() { + + /* Create new data mapper of first type */ + final StudentDataMapper mapper = new StudentDataMapperImpl(); + + /* Create new student */ + Student student = new Student(1, "Adam", 'A'); + + /* Add student in respectibe db */ + mapper.insert(student); + + /* Check if student is added in db */ + assertEquals(student.getStudentId(), mapper.find(student.getStudentId()).get().getStudentId()); + + /* Update existing student object */ + student = new Student(student.getStudentId(), "AdamUpdated", 'A'); + + /* Update student in respectibe db */ + mapper.update(student); + + /* Check if student is updated in db */ + assertEquals(mapper.find(student.getStudentId()).get().getName(), "AdamUpdated"); + + /* Delete student in db */ + mapper.delete(student); + + /* Result should be false */ + assertEquals(false, mapper.find(student.getStudentId()).isPresent()); + } +} diff --git a/data-mapper/src/test/java/com/iluwatar/datamapper/StudentTest.java b/data-mapper/src/test/java/com/iluwatar/datamapper/StudentTest.java new file mode 100644 index 000000000000..a3c0e46c1dc6 --- /dev/null +++ b/data-mapper/src/test/java/com/iluwatar/datamapper/StudentTest.java @@ -0,0 +1,51 @@ +/** + * The MIT License Copyright (c) 2016 Amit Dixit + * + * 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.datamapper; + +import org.junit.Test; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; + +public final class StudentTest { + + @Test + /** + * This API tests the equality behaviour of Student object + * Object Equality should work as per logic defined in equals method + * + * @throws Exception if any execution error during test + */ + public void testEquality() throws Exception { + + /* Create some students */ + final Student firstStudent = new Student(1, "Adam", 'A'); + final Student secondStudent = new Student(2, "Donald", 'B'); + final Student secondSameStudent = new Student(2, "Donald", 'B'); + final Student firstSameStudent = firstStudent; + + /* Check equals functionality: should return 'true' */ + assertTrue(firstStudent.equals(firstSameStudent)); + + /* Check equals functionality: should return 'false' */ + assertFalse(firstStudent.equals(secondStudent)); + + /* Check equals functionality: should return 'true' */ + assertTrue(secondStudent.equals(secondSameStudent)); + } +} diff --git a/decorator/README.md b/decorator/README.md index 63795114c798..8198734292b5 100644 --- a/decorator/README.md +++ b/decorator/README.md @@ -3,6 +3,7 @@ layout: pattern title: Decorator folder: decorator permalink: /patterns/decorator/ +pumlid: HSV14SCm20J0Lk82BFxf1YF6LaP26ZZizfDVVhjRC-bPDRs_Bc35cyZvAMV3bKU6kao36ehCGQtdms2d3z-yLursshuOKBUWmV43LPNfZEcaaFzA-YWhH_y2 categories: Structural tags: - Java @@ -27,6 +28,14 @@ Use Decorator * 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 * [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) diff --git a/decorator/etc/decorator.urm.puml b/decorator/etc/decorator.urm.puml new file mode 100644 index 000000000000..4862ddfeb47f --- /dev/null +++ b/decorator/etc/decorator.urm.puml @@ -0,0 +1,29 @@ +@startuml +package com.iluwatar.decorator { + interface Hostile { + + attack() {abstract} + + fleeBattle() {abstract} + + getAttackPower() : int {abstract} + } + class App { + + App() + + main(args : String[]) {static} + } + class Troll { + + Troll() + + attack() + + fleeBattle() + + getAttackPower() : int + } + class SmartHostile { + - decorated : Hostile + + SmartHostile(decorated : Hostile) + + attack() + + fleeBattle() + + getAttackPower() : int + } +} +SmartHostile --> "-decorated" Hostile +Troll ..|> Hostile +SmartHostile ..|> Hostile +@enduml \ No newline at end of file diff --git a/decorator/pom.xml b/decorator/pom.xml index 58f2bb0bacc2..36c3eef01ab4 100644 --- a/decorator/pom.xml +++ b/decorator/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT decorator diff --git a/delegation/README.md b/delegation/README.md index e5c0c63760c4..b294607ec92a 100644 --- a/delegation/README.md +++ b/delegation/README.md @@ -3,6 +3,7 @@ layout: pattern title: Delegation folder: delegation permalink: /patterns/delegation/ +pumlid: JSV14GCX20NGLf82LkxfXbN69OFeu2VRVdBCxRsdUhLiac6F2rZxHHHybwwuyimjKQT37ANEGMfvCpZepHy-ccpjVYm697pJuFq3DJ7f39rEWlhNaZ7Aoc5V categories: Behavioral tags: - Java diff --git a/delegation/etc/delegation.urm.puml b/delegation/etc/delegation.urm.puml new file mode 100644 index 000000000000..378587019bb6 --- /dev/null +++ b/delegation/etc/delegation.urm.puml @@ -0,0 +1,36 @@ +@startuml +package com.iluwatar.delegation.simple.printers { + class HpPrinter { + + HpPrinter() + + print(message : String) + } + class EpsonPrinter { + + EpsonPrinter() + + print(message : String) + } + class CanonPrinter { + + CanonPrinter() + + print(message : String) + } +} +package com.iluwatar.delegation.simple { + class App { + + MESSAGE_TO_PRINT : String {static} + + App() + + main(args : String[]) {static} + } + class PrinterController { + - printer : Printer + + PrinterController(printer : Printer) + + print(message : String) + } + interface Printer { + + print(String) {abstract} + } +} +PrinterController --> "-printer" Printer +HpPrinter ..|> Printer +PrinterController ..|> Printer +EpsonPrinter ..|> Printer +CanonPrinter ..|> Printer +@enduml \ No newline at end of file diff --git a/delegation/pom.xml b/delegation/pom.xml index 33592dd829e3..ed5f6608eb77 100644 --- a/delegation/pom.xml +++ b/delegation/pom.xml @@ -30,7 +30,7 @@ java-design-patterns com.iluwatar - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT 4.0.0 diff --git a/dependency-injection/README.md b/dependency-injection/README.md index 735f589b1555..b4ec12e6b082 100644 --- a/dependency-injection/README.md +++ b/dependency-injection/README.md @@ -3,6 +3,7 @@ layout: pattern title: Dependency Injection folder: dependency-injection permalink: /patterns/dependency-injection/ +pumlid: RSdB3SCW303GLPe1mFTkunWhSGG6-PEesxS3zFQajubIpyPf_NL6B7y363xra3XpJsUZgS4QbUO0wVbWeC65DvR6BeUMXH5iwZ3GVu36YxMnqgU8NamXKu63_aPD6tNbw5y0 categories: Behavioral tags: - Java diff --git a/dependency-injection/etc/dependency-injection.urm.puml b/dependency-injection/etc/dependency-injection.urm.puml new file mode 100644 index 000000000000..bf4d105998fa --- /dev/null +++ b/dependency-injection/etc/dependency-injection.urm.puml @@ -0,0 +1,48 @@ +@startuml +package com.iluwatar.dependency.injection { + class AdvancedWizard { + - tobacco : Tobacco + + AdvancedWizard(tobacco : Tobacco) + + smoke() + } + interface Wizard { + + smoke() {abstract} + } + class RivendellTobacco { + + RivendellTobacco() + } + class SimpleWizard { + - tobacco : OldTobyTobacco + + SimpleWizard() + + smoke() + } + class OldTobyTobacco { + + OldTobyTobacco() + } + class SecondBreakfastTobacco { + + SecondBreakfastTobacco() + } + class App { + + App() + + main(args : String[]) {static} + } + abstract class Tobacco { + + Tobacco() + + smoke(wizard : Wizard) + } + class GuiceWizard { + - tobacco : Tobacco + + GuiceWizard(tobacco : Tobacco) + + smoke() + } +} +SimpleWizard --> "-tobacco" OldTobyTobacco +AdvancedWizard --> "-tobacco" Tobacco +GuiceWizard --> "-tobacco" Tobacco +AdvancedWizard ..|> Wizard +RivendellTobacco --|> Tobacco +SimpleWizard ..|> Wizard +OldTobyTobacco --|> Tobacco +SecondBreakfastTobacco --|> Tobacco +GuiceWizard ..|> Wizard +@enduml \ No newline at end of file diff --git a/dependency-injection/pom.xml b/dependency-injection/pom.xml index d8be9fcffd53..ab4619b63783 100644 --- a/dependency-injection/pom.xml +++ b/dependency-injection/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT dependency-injection @@ -43,10 +43,9 @@ mockito-core test - - com.google.inject - guice - 4.0 - + + com.google.inject + guice + diff --git a/double-checked-locking/README.md b/double-checked-locking/README.md index da1fdd1a2ee7..582e4aac657c 100644 --- a/double-checked-locking/README.md +++ b/double-checked-locking/README.md @@ -3,6 +3,7 @@ layout: pattern title: Double Checked Locking folder: double-checked-locking permalink: /patterns/double-checked-locking/ +pumlid: TSdH4SCW203GLTe1bFzkGv1J6qGFeLc_MI1_x-wzkv94uJ1vDVUrFm26LwxTMnonsMYgitgcEQ1BNEXeyCKVfiAxLqqBtTbqmy1z0ygCGpXHOpgv99bqTgt0JW-LmqPUCUGF categories: Concurrency tags: - Java diff --git a/double-checked-locking/etc/double-checked-locking.urm.puml b/double-checked-locking/etc/double-checked-locking.urm.puml new file mode 100644 index 000000000000..6feb989019cc --- /dev/null +++ b/double-checked-locking/etc/double-checked-locking.urm.puml @@ -0,0 +1,20 @@ +@startuml +package com.iluwatar.doublechecked.locking { + class App { + + App() + + main(args : String[]) {static} + } + class Inventory { + - inventorySize : int + - items : List + - lock : Lock + + Inventory(inventorySize : int) + + addItem(item : Item) : boolean + + getItems() : List + } + class Item { + + Item() + } +} +Inventory --> "-items" Item +@enduml \ No newline at end of file diff --git a/double-checked-locking/pom.xml b/double-checked-locking/pom.xml index f8ac5fa2e907..27c97479a428 100644 --- a/double-checked-locking/pom.xml +++ b/double-checked-locking/pom.xml @@ -27,7 +27,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT double-checked-locking diff --git a/double-dispatch/README.md b/double-dispatch/README.md index ae87208a2639..b0f2185f9a34 100644 --- a/double-dispatch/README.md +++ b/double-dispatch/README.md @@ -3,6 +3,7 @@ layout: pattern title: Double Dispatch folder: double-dispatch permalink: /patterns/double-dispatch/ +pumlid: NSbB3iCW303HgpG70Ezx6yTOWSeOv4zp_MRTtUZDCPGa6wV9gqTiVmCOtlKQqVDCPwEbmHgLreGXUMEWmGU_M1hxkBHiZ61JXud-1BILft1fmvz37JZetshQh3kd_000 categories: Other tags: - Java diff --git a/double-dispatch/etc/double-dispatch.urm.puml b/double-dispatch/etc/double-dispatch.urm.puml new file mode 100644 index 000000000000..78d361326fe3 --- /dev/null +++ b/double-dispatch/etc/double-dispatch.urm.puml @@ -0,0 +1,65 @@ +@startuml +package com.iluwatar.doubledispatch { + abstract class GameObject { + - damaged : boolean + - onFire : boolean + + GameObject(left : int, top : int, right : int, bottom : int) + + collision(GameObject) {abstract} + + collisionResolve(FlamingAsteroid) {abstract} + + collisionResolve(Meteoroid) {abstract} + + collisionResolve(SpaceStationIss) {abstract} + + collisionResolve(SpaceStationMir) {abstract} + + isDamaged() : boolean + + isOnFire() : boolean + + setDamaged(damaged : boolean) + + setOnFire(onFire : boolean) + + toString() : String + } + class SpaceStationIss { + + SpaceStationIss(left : int, top : int, right : int, bottom : int) + + collision(gameObject : GameObject) + } + class FlamingAsteroid { + + FlamingAsteroid(left : int, top : int, right : int, bottom : int) + + collision(gameObject : GameObject) + } + class SpaceStationMir { + + SpaceStationMir(left : int, top : int, right : int, bottom : int) + + collision(gameObject : GameObject) + + collisionResolve(asteroid : FlamingAsteroid) + + collisionResolve(iss : SpaceStationIss) + + collisionResolve(meteoroid : Meteoroid) + + collisionResolve(mir : SpaceStationMir) + } + class Meteoroid { + + Meteoroid(left : int, top : int, right : int, bottom : int) + + collision(gameObject : GameObject) + + collisionResolve(asteroid : FlamingAsteroid) + + collisionResolve(iss : SpaceStationIss) + + collisionResolve(meteoroid : Meteoroid) + + collisionResolve(mir : SpaceStationMir) + } + class Rectangle { + - bottom : int + - left : int + - right : int + - top : int + + Rectangle(left : int, top : int, right : int, bottom : int) + + getBottom() : int + + getLeft() : int + + getRight() : int + + getTop() : int + ~ intersectsWith(r : Rectangle) : boolean + + toString() : String + } + class App { + + App() + + main(args : String[]) {static} + } +} +GameObject --|> Rectangle +SpaceStationIss --|> SpaceStationMir +FlamingAsteroid --|> Meteoroid +SpaceStationMir --|> GameObject +Meteoroid --|> GameObject +@enduml \ No newline at end of file diff --git a/double-dispatch/pom.xml b/double-dispatch/pom.xml index 8896abd9ff8f..5093cced82fa 100644 --- a/double-dispatch/pom.xml +++ b/double-dispatch/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT double-dispatch diff --git a/event-aggregator/README.md b/event-aggregator/README.md index 5462a2a5d8bc..ce7f358de9f8 100644 --- a/event-aggregator/README.md +++ b/event-aggregator/README.md @@ -3,10 +3,12 @@ layout: pattern title: Event Aggregator folder: event-aggregator permalink: /patterns/event-aggregator/ +pumlid: PSf13iCW30NHgxG70Ezx6uTOX0eCih-JwvTzTwEdUJSjFKu9wwyBMFuXCdvoRRZY21ShKo6ANEQWrkDXiD6NRqwdUAkQ5WDYwZJOTv3SUqzSgqbbp0qeVvZ3Hbun-Wy0 categories: Structural tags: - Java - Difficulty-Beginner + - Reactive --- ## Intent diff --git a/event-aggregator/etc/event-aggregator.urm.puml b/event-aggregator/etc/event-aggregator.urm.puml new file mode 100644 index 000000000000..3ccfea536fd4 --- /dev/null +++ b/event-aggregator/etc/event-aggregator.urm.puml @@ -0,0 +1,73 @@ +@startuml +package com.iluwatar.event.aggregator { + class LordVarys { + + LordVarys() + + LordVarys(obs : EventObserver) + + timePasses(day : Weekday) + } + abstract class EventEmitter { + - observers : List + + EventEmitter() + + EventEmitter(obs : EventObserver) + # notifyObservers(e : Event) + + registerObserver(obs : EventObserver) + + timePasses(Weekday) {abstract} + } + class KingJoffrey { + + KingJoffrey() + + onEvent(e : Event) + } + class LordBaelish { + + LordBaelish() + + LordBaelish(obs : EventObserver) + + timePasses(day : Weekday) + } + interface EventObserver { + + onEvent(Event) {abstract} + } + class KingsHand { + + KingsHand() + + KingsHand(obs : EventObserver) + + onEvent(e : Event) + + timePasses(day : Weekday) + } + class Scout { + + Scout() + + Scout(obs : EventObserver) + + timePasses(day : Weekday) + } + class App { + + App() + + main(args : String[]) {static} + } + enum Weekday { + + FRIDAY {static} + + MONDAY {static} + + SATURDAY {static} + + SUNDAY {static} + + THURSDAY {static} + + TUESDAY {static} + + WEDNESDAY {static} + - description : String + + toString() : String + + valueOf(name : String) : Weekday {static} + + values() : Weekday[] {static} + } + enum Event { + + STARK_SIGHTED {static} + + TRAITOR_DETECTED {static} + + WARSHIPS_APPROACHING {static} + - description : String + + toString() : String + + valueOf(name : String) : Event {static} + + values() : Event[] {static} + } +} +EventEmitter --> "-observers" EventObserver +LordVarys --|> EventEmitter +KingJoffrey ..|> EventObserver +LordBaelish --|> EventEmitter +KingsHand ..|> EventObserver +KingsHand --|> EventEmitter +Scout --|> EventEmitter +@enduml \ No newline at end of file diff --git a/event-aggregator/pom.xml b/event-aggregator/pom.xml index 1b1f289b27d1..29d99ca2bbd0 100644 --- a/event-aggregator/pom.xml +++ b/event-aggregator/pom.xml @@ -28,7 +28,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT event-aggregator diff --git a/event-asynchronous/README.md b/event-asynchronous/README.md new file mode 100644 index 000000000000..ef35d0b383fc --- /dev/null +++ b/event-asynchronous/README.md @@ -0,0 +1,30 @@ +--- +layout: pattern +title: Event-based Asynchronous +folder: event-asynchronous +permalink: /patterns/event-asynchronous/ +categories: Concurrency +tags: + - difficulty-intermediate + - performance + - Java +--- + +## Intent +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. + +![alt text](./etc/event-asynchronous.png "Event-based Asynchronous") + +## Applicability +Use the Event-based Asynchronous pattern(s) when + +* Time-consuming tasks are needed to run in the background without disrupting the current application. + +## Credits + +* [Event-based Asynchronous Pattern Overview](https://msdn.microsoft.com/en-us/library/wewwczdw%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396) diff --git a/event-asynchronous/etc/event-asynchronous.png b/event-asynchronous/etc/event-asynchronous.png new file mode 100644 index 000000000000..a46ffe1c5d17 Binary files /dev/null and b/event-asynchronous/etc/event-asynchronous.png differ diff --git a/event-asynchronous/etc/event-asynchronous.ucls b/event-asynchronous/etc/event-asynchronous.ucls new file mode 100644 index 000000000000..df09fc28d955 --- /dev/null +++ b/event-asynchronous/etc/event-asynchronous.ucls @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/event-asynchronous/etc/event-asynchronous.urm.puml b/event-asynchronous/etc/event-asynchronous.urm.puml new file mode 100644 index 000000000000..c5b183187c24 --- /dev/null +++ b/event-asynchronous/etc/event-asynchronous.urm.puml @@ -0,0 +1,65 @@ +@startuml +package com.iluwatar.event.asynchronous { + class Event { + - eventId : int + - eventListener : ThreadCompleteListener + - eventTime : int + - isComplete : boolean + - isSynchronous : boolean + - thread : Thread + + Event(eventId : int, eventTime : int, isSynchronous : boolean) + + addListener(listener : ThreadCompleteListener) + - completed() + + isSynchronous() : boolean + + removeListener(listener : ThreadCompleteListener) + + run() + + start() + + status() + + stop() + } + interface ThreadCompleteListener { + + completedEventHandler(int) {abstract} + } + class EventManager { + + MAX_EVENT_TIME : int {static} + + MAX_ID : int {static} + + MAX_RUNNING_EVENTS : int {static} + + MIN_ID : int {static} + - currentlyRunningSyncEvent : int + - eventPool : Map + - rand : Random + + EventManager() + + cancel(eventId : int) + + completedEventHandler(eventId : int) + + create(eventTime : int) : int + + createAsync(eventTime : int) : int + - createEvent(eventTime : int, isSynchronous : boolean) : int + - generateId() : int + + getEventPool() : Map + + numOfCurrentlyRunningSyncEvent() : int + + shutdown() + + start(eventId : int) + + status(eventId : int) + + statusOfAllEvents() + } + class App { + + PROP_FILE_NAME : String {static} + ~ interactiveMode : boolean + + App() + + main(args : String[]) {static} + + quickRun() + + run() + + runInteractiveMode() + + setUp() + } + interface IEvent { + + start() {abstract} + + status() {abstract} + + stop() {abstract} + } +} +Event --> "-eventListener" ThreadCompleteListener +EventManager --+ Map +Event ..|> IEvent +EventManager ..|> ThreadCompleteListener +@enduml \ No newline at end of file diff --git a/event-asynchronous/pom.xml b/event-asynchronous/pom.xml new file mode 100644 index 000000000000..1bc4549d4aaa --- /dev/null +++ b/event-asynchronous/pom.xml @@ -0,0 +1,42 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.14.0-SNAPSHOT + + event-asynchronous + + + junit + junit + 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 000000000000..5a2565940839 --- /dev/null +++ b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/App.java @@ -0,0 +1,206 @@ +/** + * 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.asynchronous; + +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 { + + 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) { + System.out.println(PROP_FILE_NAME + " was not found. Defaulting to non-interactive mode."); + } + 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); + System.out.println("Async Event [" + aEventId + "] has been created."); + eventManager.start(aEventId); + System.out.println("Async Event [" + aEventId + "] has been started."); + + // Create a Synchronous event. + int sEventId = eventManager.create(60); + System.out.println("Sync Event [" + sEventId + "] has been created."); + eventManager.start(sEventId); + System.out.println("Sync Event [" + sEventId + "] has been started."); + + eventManager.status(aEventId); + eventManager.status(sEventId); + + eventManager.cancel(aEventId); + System.out.println("Async Event [" + aEventId + "] has been stopped."); + eventManager.cancel(sEventId); + System.out.println("Sync Event [" + sEventId + "] has been stopped."); + + } catch (MaxNumOfEventsAllowedException | LongRunningEventException | EventDoesNotExistException + | InvalidOperationException e) { + System.out.println(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) { + System.out.println("Hello. Would you like to boil some eggs?"); + System.out.println("(1) BOIL AN EGG \n(2) STOP BOILING THIS EGG \n(3) HOW ARE MY EGGS? \n(4) EXIT"); + System.out.print("Choose [1,2,3,4]: "); + option = s.nextInt(); + + if (option == 1) { + s.nextLine(); + System.out.print("Boil multiple eggs at once (A) or boil them one-by-one (S)?: "); + String eventType = s.nextLine(); + System.out.print("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); + System.out.println("Egg [" + eventId + "] is being boiled."); + } catch (MaxNumOfEventsAllowedException | LongRunningEventException | EventDoesNotExistException e) { + System.out.println(e.getMessage()); + } + } else if (eventType.equalsIgnoreCase("S")) { + try { + int eventId = eventManager.create(eventTime); + eventManager.start(eventId); + System.out.println("Egg [" + eventId + "] is being boiled."); + } catch (MaxNumOfEventsAllowedException | InvalidOperationException | LongRunningEventException + | EventDoesNotExistException e) { + System.out.println(e.getMessage()); + } + } else { + System.out.println("Unknown event type."); + } + } else if (option == 2) { + System.out.print("Which egg?: "); + int eventId = s.nextInt(); + try { + eventManager.cancel(eventId); + System.out.println("Egg [" + eventId + "] is removed from boiler."); + } catch (EventDoesNotExistException e) { + System.out.println(e.getMessage()); + } + } else if (option == 3) { + s.nextLine(); + System.out.print("Just one egg (O) OR all of them (A) ?: "); + String eggChoice = s.nextLine(); + + if (eggChoice.equalsIgnoreCase("O")) { + System.out.print("Which egg?: "); + int eventId = s.nextInt(); + try { + eventManager.status(eventId); + } catch (EventDoesNotExistException e) { + System.out.println(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 000000000000..5e557fff2e0e --- /dev/null +++ b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/Event.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.asynchronous; + +/** + * + * Each Event runs as a separate/individual thread. + * + */ +public class Event implements IEvent, Runnable { + + 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) { + System.out.println("[" + eventId + "] is not done."); + } else { + System.out.println("[" + eventId + "] is done."); + } + } + + @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 000000000000..77c1d479bfcb --- /dev/null +++ b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventDoesNotExistException.java @@ -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. + */ +package com.iluwatar.event.asynchronous; + +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 000000000000..dae995e382ab --- /dev/null +++ b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventManager.java @@ -0,0 +1,218 @@ +/** + * 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.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 000000000000..bcd78b6c05e6 --- /dev/null +++ b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/IEvent.java @@ -0,0 +1,27 @@ +/** + * 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.asynchronous; + +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 000000000000..4fd5b0eed632 --- /dev/null +++ b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/InvalidOperationException.java @@ -0,0 +1,27 @@ +/** + * 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.asynchronous; + +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 000000000000..6817b1dd8c1b --- /dev/null +++ b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/LongRunningEventException.java @@ -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. + */ +package com.iluwatar.event.asynchronous; + +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 000000000000..9f8f2891c14e --- /dev/null +++ b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/MaxNumOfEventsAllowedException.java @@ -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. + */ +package com.iluwatar.event.asynchronous; + +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 000000000000..fd62a3e8053d --- /dev/null +++ b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/ThreadCompleteListener.java @@ -0,0 +1,21 @@ +/** + * 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.asynchronous; + +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 000000000000..d8bfa1f7e054 --- /dev/null +++ b/event-asynchronous/src/main/resources/config.properties @@ -0,0 +1,24 @@ +# +# 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. +# + +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 000000000000..8736fcf777e0 --- /dev/null +++ b/event-asynchronous/src/test/java/com/iluwatar/event/asynchronous/AppTest.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.event.asynchronous; + +import java.io.IOException; + +import org.junit.Test; + +/** + * 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 000000000000..213439203326 --- /dev/null +++ b/event-asynchronous/src/test/java/com/iluwatar/event/asynchronous/EventAsynchronousTest.java @@ -0,0 +1,135 @@ +/** + * 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.asynchronous; + +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; + +/** + * + * Application test + * + */ +public class EventAsynchronousTest { + App app; + + @Before + 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) { + System.out.println(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) { + System.out.println(e.getMessage()); + } + } + + @Test(expected = InvalidOperationException.class) + public void testUnsuccessfulSynchronousEvent() throws InvalidOperationException { + 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) { + System.out.println(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) { + System.out.println(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) { + System.out.println(e.getMessage()); + } + } +} diff --git a/event-driven-architecture/README.md b/event-driven-architecture/README.md index 7c786b76c75c..0f698273b996 100644 --- a/event-driven-architecture/README.md +++ b/event-driven-architecture/README.md @@ -1,7 +1,15 @@ +--- layout: pattern title: Event Driven Architecture folder: event-driven-architecture -permalink: /patterns/event-driven-architecture +permalink: /patterns/event-driven-architecture/ +pumlid: TOhH3SCW30LNQGS0_tSRnrZ15H1adfFromBzkfFktZQaHT7mzgh0N1yYvoUVXXf7B7Mv1dGWozN9MZmCTlhopQdeidEaoO3wMDHvRI6zzvwAssPYbsfGGRYIGlxN7DxpZDv- +categories: Architectural +tags: + - Java + - Difficulty-Intermediate + - Reactive +--- ## Intent Send and notify state changes of your objects to other applications using an Event-driven Architecture. diff --git a/event-driven-architecture/etc/event-driven-architecture.urm.puml b/event-driven-architecture/etc/event-driven-architecture.urm.puml new file mode 100644 index 000000000000..8314913a6114 --- /dev/null +++ b/event-driven-architecture/etc/event-driven-architecture.urm.puml @@ -0,0 +1,62 @@ +@startuml +package com.iluwatar.eda.handler { + class UserCreatedEventHandler { + + UserCreatedEventHandler() + + onEvent(event : UserCreatedEvent) + } + class UserUpdatedEventHandler { + + UserUpdatedEventHandler() + + onEvent(event : UserUpdatedEvent) + } +} +package com.iluwatar.eda.event { + abstract class AbstractEvent { + + AbstractEvent() + + getType() : Class + } + class UserCreatedEvent { + - user : User + + UserCreatedEvent(user : User) + + getUser() : User + } + class UserUpdatedEvent { + - user : User + + UserUpdatedEvent(user : User) + + getUser() : User + } +} +package com.iluwatar.eda.framework { + interface Handler { + + onEvent(E extends Event) {abstract} + } + class EventDispatcher { + - handlers : Map, Handler> + + EventDispatcher() + + dispatch(event : E extends Event) + + registerHandler(eventType : Class, handler : Handler) + } + interface Event { + + getType() : Class {abstract} + } +} +package com.iluwatar.eda.model { + class User { + - username : String + + User(username : String) + + getUsername() : String + } +} +package com.iluwatar.eda { + class App { + + App() + + main(args : String[]) {static} + } +} +UserUpdatedEvent --> "-user" User +UserCreatedEvent --> "-user" User +AbstractEvent ..|> Event +UserCreatedEvent --|> AbstractEvent +UserCreatedEventHandler ..|> Handler +UserUpdatedEvent --|> AbstractEvent +UserUpdatedEventHandler ..|> Handler +@enduml \ No newline at end of file diff --git a/event-driven-architecture/pom.xml b/event-driven-architecture/pom.xml index cccf3908826d..149ca7409da7 100644 --- a/event-driven-architecture/pom.xml +++ b/event-driven-architecture/pom.xml @@ -31,7 +31,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT event-driven-architecture diff --git a/execute-around/README.md b/execute-around/README.md index f669f18ff0f3..e0ae128b537f 100644 --- a/execute-around/README.md +++ b/execute-around/README.md @@ -3,6 +3,7 @@ layout: pattern title: Execute Around folder: execute-around permalink: /patterns/execute-around/ +pumlid: NSZ14G8n20NGLhI0XBlT865suoGa0n_NylNixSsxTvEHJTF7xGHsF8YShtfqdFdCK9TbK4ELDQcFl1ZizE8tbwRH3okR0NKBcXm_a7vK4bhOLreZXVnLJPzrvnnV categories: Other tags: - Java diff --git a/execute-around/etc/execute-around.urm.puml b/execute-around/etc/execute-around.urm.puml new file mode 100644 index 000000000000..4fdc4bffb7d2 --- /dev/null +++ b/execute-around/etc/execute-around.urm.puml @@ -0,0 +1,14 @@ +@startuml +package com.iluwatar.execute.around { + class SimpleFileWriter { + + SimpleFileWriter(filename : String, action : FileWriterAction) + } + class App { + + App() + + main(args : String[]) {static} + } + interface FileWriterAction { + + writeFile(FileWriter) {abstract} + } +} +@enduml \ No newline at end of file diff --git a/execute-around/pom.xml b/execute-around/pom.xml index 23a0a4a15170..115c201cbe17 100644 --- a/execute-around/pom.xml +++ b/execute-around/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT execute-around diff --git a/facade/README.md b/facade/README.md index c416552c7505..22ccd69113c0 100644 --- a/facade/README.md +++ b/facade/README.md @@ -3,6 +3,7 @@ layout: pattern title: Facade folder: facade permalink: /patterns/facade/ +pumlid: BSP15eCm20N0gxG7CEoz3ILKqvTW7dpq-hhehERTJ7fMJU-l7PYn4ZbVPMlOyvEXBeT13KMEGQtdnM2d7v-yL8sssJ8PKBUWmV64lYnSbHJoRqaVPUReDm00 categories: Structural tags: - Java diff --git a/facade/etc/facade.urm.puml b/facade/etc/facade.urm.puml new file mode 100644 index 000000000000..8c53cb728b26 --- /dev/null +++ b/facade/etc/facade.urm.puml @@ -0,0 +1,57 @@ +@startuml +package com.iluwatar.facade { + class DwarvenGoldDigger { + + DwarvenGoldDigger() + + name() : String + + work() + } + class DwarvenGoldmineFacade { + - workers : List + + DwarvenGoldmineFacade() + + digOutGold() + + endDay() + - makeActions(workers : Collection, actions : Action[]) {static} + + startNewDay() + } + class DwarvenTunnelDigger { + + DwarvenTunnelDigger() + + name() : String + + work() + } + abstract class DwarvenMineWorker { + + DwarvenMineWorker() + - action(action : Action) + + action(actions : Action[]) + + goHome() + + goToMine() + + goToSleep() + + name() : String {abstract} + + wakeUp() + + work() {abstract} + } + class App { + + App() + + main(args : String[]) {static} + } + class DwarvenCartOperator { + + DwarvenCartOperator() + + name() : String + + work() + } + ~enum Action { + + GO_HOME {static} + + GO_TO_MINE {static} + + GO_TO_SLEEP {static} + + WAKE_UP {static} + + WORK {static} + + valueOf(name : String) : Action {static} + + values() : Action[] {static} + } +} +DwarvenGoldmineFacade --+ DwarvenMineWorker +DwarvenGoldmineFacade --> "-workers" DwarvenMineWorker +Action ..+ DwarvenMineWorker +DwarvenGoldDigger --|> DwarvenMineWorker +DwarvenTunnelDigger --|> DwarvenMineWorker +DwarvenCartOperator --|> DwarvenMineWorker +@enduml \ No newline at end of file diff --git a/facade/pom.xml b/facade/pom.xml index a526243a1204..7816d51e656e 100644 --- a/facade/pom.xml +++ b/facade/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT facade diff --git a/factory-kit/README.md b/factory-kit/README.md index c257010479bd..b47cbff76ba4 100644 --- a/factory-kit/README.md +++ b/factory-kit/README.md @@ -3,6 +3,7 @@ layout: pattern title: Factory Kit folder: factory-kit permalink: /patterns/factory-kit/ +pumlid: JST15i8m20N0g-W14lRU1YcsQ4BooCS-RwzBTpDNSscvQKQx7C1SDwBWi-w68--vD6Gur55bTBAM9uE3dlpcikcotSjaGCCNTLu_q8C58pxbPI25_Bzcz3gpjoy0 categories: Creational tags: - Java diff --git a/factory-kit/etc/factory-kit.urm.puml b/factory-kit/etc/factory-kit.urm.puml new file mode 100644 index 000000000000..fdee9a01c57f --- /dev/null +++ b/factory-kit/etc/factory-kit.urm.puml @@ -0,0 +1,45 @@ +@startuml +package com.iluwatar.factorykit { + class Spear { + + Spear() + + toString() : String + } + class App { + + App() + + main(args : String[]) {static} + } + interface Weapon { + } + interface WeaponFactory { + + create(WeaponType) : Weapon {abstract} + + factory(consumer : Consumer) : WeaponFactory {static} + } + class Axe { + + Axe() + + toString() : String + } + class Sword { + + Sword() + + toString() : String + } + class Bow { + + Bow() + + toString() : String + } + interface Builder { + + add(WeaponType, Supplier) {abstract} + } + enum WeaponType { + + AXE {static} + + BOW {static} + + SPEAR {static} + + SWORD {static} + + valueOf(name : String) : WeaponType {static} + + values() : WeaponType[] {static} + } +} +Spear ..|> Weapon +Axe ..|> Weapon +Sword ..|> Weapon +Bow ..|> Weapon +@enduml \ No newline at end of file diff --git a/factory-kit/pom.xml b/factory-kit/pom.xml index ea2aa597625a..32705cc466a7 100644 --- a/factory-kit/pom.xml +++ b/factory-kit/pom.xml @@ -30,7 +30,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT factory-kit diff --git a/factory-method/README.md b/factory-method/README.md index 42237883d6b6..17e0818e91de 100644 --- a/factory-method/README.md +++ b/factory-method/README.md @@ -3,8 +3,9 @@ layout: pattern title: Factory Method folder: factory-method permalink: /patterns/factory-method/ +pumlid: NSZB3G8n30N0Lg20n7UwCOxPP9MVx6TMT0zdRgEvjoazYeRrMmMsFuYChtmqr7Y6gycQq8aiQr3hSJ7OwEGtfwBUZfas0shJQR3_G2yMBFkaeQYha4B-AeUDl6FqBm00 categories: Creational -tags: +tags: - Java - Difficulty-Beginner - Gang Of Four @@ -27,6 +28,16 @@ Use the Factory Method pattern when * a class wants its subclasses to specify the objects it creates * classes delegate responsibility to one of several helper subclasses, and you want to localize the knowledge of which helper subclass is the delegate +## Known uses + +* [java.util.Calendar](http://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html#getInstance--) +* [java.util.ResourceBundle](http://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html#getBundle-java.lang.String-) +* [java.text.NumberFormat](http://docs.oracle.com/javase/8/docs/api/java/text/NumberFormat.html#getInstance--) +* [java.nio.charset.Charset](http://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html#forName-java.lang.String-) +* [java.net.URLStreamHandlerFactory](http://docs.oracle.com/javase/8/docs/api/java/net/URLStreamHandlerFactory.html#createURLStreamHandler-java.lang.String-) +* [java.util.EnumSet](https://docs.oracle.com/javase/8/docs/api/java/util/EnumSet.html#of-E-) +* [javax.xml.bind.JAXBContext](https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/JAXBContext.html#createMarshaller--) + ## Credits * [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) diff --git a/factory-method/etc/factory-method.urm.puml b/factory-method/etc/factory-method.urm.puml new file mode 100644 index 000000000000..d61984a85047 --- /dev/null +++ b/factory-method/etc/factory-method.urm.puml @@ -0,0 +1,53 @@ +@startuml +package com.iluwatar.factory.method { + class OrcBlacksmith { + + OrcBlacksmith() + + manufactureWeapon(weaponType : WeaponType) : Weapon + } + class ElfBlacksmith { + + ElfBlacksmith() + + manufactureWeapon(weaponType : WeaponType) : Weapon + } + class OrcWeapon { + - weaponType : WeaponType + + OrcWeapon(weaponType : WeaponType) + + getWeaponType() : WeaponType + + toString() : String + } + interface Blacksmith { + + manufactureWeapon(WeaponType) : Weapon {abstract} + } + class ElfWeapon { + - weaponType : WeaponType + + ElfWeapon(weaponType : WeaponType) + + getWeaponType() : WeaponType + + toString() : String + } + class App { + - blacksmith : Blacksmith + + App(blacksmith : Blacksmith) + + main(args : String[]) {static} + - manufactureWeapons() + } + interface Weapon { + + getWeaponType() : WeaponType {abstract} + } + enum WeaponType { + + AXE {static} + + SHORT_SWORD {static} + + SPEAR {static} + + UNDEFINED {static} + - title : String + + toString() : String + + valueOf(name : String) : WeaponType {static} + + values() : WeaponType[] {static} + } +} +ElfWeapon --> "-weaponType" WeaponType +OrcWeapon --> "-weaponType" WeaponType +App --> "-blacksmith" Blacksmith +OrcBlacksmith ..|> Blacksmith +ElfBlacksmith ..|> Blacksmith +OrcWeapon ..|> Weapon +ElfWeapon ..|> Weapon +@enduml \ No newline at end of file diff --git a/factory-method/pom.xml b/factory-method/pom.xml index 7e1cfd28f543..74dc91a02419 100644 --- a/factory-method/pom.xml +++ b/factory-method/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT factory-method diff --git a/faq.md b/faq.md index 69f7b795ee92..01a7e7b7235d 100644 --- a/faq.md +++ b/faq.md @@ -3,7 +3,7 @@ layout: page title: FAQ permalink: /faq/ icon: fa-question -page-index: 1 +page-index: 5 --- ### Q1: What is the difference between State and Strategy patterns? {#Q1} @@ -65,3 +65,11 @@ Flyweight. ### Q7: What are the differences between FluentInterface and Builder patterns? {#Q7} Fluent interfaces are sometimes confused with the Builder pattern, because they share method chaining and a fluent usage. However, fluent interfaces are not primarily used to create shared (mutable) objects, but to configure complex objects without having to respecify the target object on every property change. + +### Q8: What is the difference between java.io.Serialization and Memento pattern? {#Q8} + +Memento is typically used to implement rollback/save-point support. Example we might want to mark the state of an object at a point in time, do some work and then decide to rollback to the previous state. + +On the other hand serialization may be used as a tool to save the state of an object into byte[] and preserving the contents in memory or disk. When someone invokes the memento to revert object's previous state then we can deserialize the information stored and recreate previous state. + +So Memento is a pattern and serialization is a tool that can be used to implement this pattern. Other ways to implement the pattern can be to clone the contents of the object and keep track of those clones. diff --git a/feature-toggle/README.md b/feature-toggle/README.md index 51747ac09bdf..3bb91ad5ae7d 100644 --- a/feature-toggle/README.md +++ b/feature-toggle/README.md @@ -3,6 +3,7 @@ layout: pattern title: Feature Toggle folder: feature-toggle permalink: /patterns/feature-toggle/ +pumlid: NSZ14G8X30NGLhG0oDrk8XjPd12OvCTjNy_UthpxiAPvIBhUJc37WyZvgdtWp6U6U5i6CTIs9WtDYy5ER_vmEIH6jx8P4BUWoV43lOIHBWMhTnKIjB-gwRFkdFe5 categories: Behavioral tags: - Java diff --git a/feature-toggle/etc/feature-toggle.urm.puml b/feature-toggle/etc/feature-toggle.urm.puml new file mode 100644 index 000000000000..6c935f3e64e5 --- /dev/null +++ b/feature-toggle/etc/feature-toggle.urm.puml @@ -0,0 +1,47 @@ +@startuml +package com.iluwatar.featuretoggle.pattern { + interface Service { + + getWelcomeMessage(User) : String {abstract} + + isEnhanced() : boolean {abstract} + } +} +package com.iluwatar.featuretoggle.user { + class User { + - name : String + + User(name : String) + + toString() : String + } + class UserGroup { + - freeGroup : List {static} + - paidGroup : List {static} + + UserGroup() + + addUserToFreeGroup(user : User) {static} + + addUserToPaidGroup(user : User) {static} + + isPaid(user : User) : boolean {static} + } +} +package com.iluwatar.featuretoggle.pattern.tieredversion { + class TieredFeatureToggleVersion { + + TieredFeatureToggleVersion() + + getWelcomeMessage(user : User) : String + + isEnhanced() : boolean + } +} +package com.iluwatar.featuretoggle.pattern.propertiesversion { + class PropertiesFeatureToggleVersion { + - isEnhanced : boolean + + PropertiesFeatureToggleVersion(properties : Properties) + + getWelcomeMessage(user : User) : String + + isEnhanced() : boolean + } +} +package com.iluwatar.featuretoggle { + class App { + + App() + + main(args : String[]) {static} + } +} +UserGroup --> "-freeGroup" User +PropertiesFeatureToggleVersion ..|> Service +TieredFeatureToggleVersion ..|> Service +@enduml \ No newline at end of file diff --git a/feature-toggle/pom.xml b/feature-toggle/pom.xml index cf479dd0ae46..eebc433e80c5 100644 --- a/feature-toggle/pom.xml +++ b/feature-toggle/pom.xml @@ -30,7 +30,7 @@ java-design-patterns com.iluwatar - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT 4.0.0 diff --git a/fluentinterface/README.md b/fluentinterface/README.md index 767792da79ab..8bf43d97433b 100644 --- a/fluentinterface/README.md +++ b/fluentinterface/README.md @@ -3,6 +3,7 @@ layout: pattern title: Fluent Interface folder: fluentinterface permalink: /patterns/fluentinterface/ +pumlid: NOj93eCm302_KXv0VEzlN6F0bMCYB_3zvjpRQ3IpY97MnkNwEZD7l04SdtP8dlMfOAVBaYqRNHr4wy54Xo_Uk6uSSjWwC9FT0Zh61DYrPY_pyXs9WPF-NIllRLJN7m00 categories: Other tags: - Java diff --git a/fluentinterface/etc/fluentinterface.urm.puml b/fluentinterface/etc/fluentinterface.urm.puml new file mode 100644 index 000000000000..0283e20b594c --- /dev/null +++ b/fluentinterface/etc/fluentinterface.urm.puml @@ -0,0 +1,71 @@ +@startuml +package com.iluwatar.fluentinterface.fluentiterable.simple { + class SimpleFluentIterable { + - iterable : Iterable + # SimpleFluentIterable(iterable : Iterable) + + asList() : List + + filter(predicate : Predicate) : FluentIterable + + first() : Optional + + first(count : int) : FluentIterable + + forEach(action : Consumer) + + from(iterable : Iterable) : FluentIterable {static} + + fromCopyOf(iterable : Iterable) : FluentIterable {static} + + getRemainingElementsCount() : int + + iterator() : Iterator + + last() : Optional + + last(count : int) : FluentIterable + + map(function : Function) : FluentIterable + + spliterator() : Spliterator + + toList(iterator : Iterator) : List {static} + } +} +package com.iluwatar.fluentinterface.app { + class App { + + App() + + main(args : String[]) {static} + - negatives() : Predicate {static} + - positives() : Predicate {static} + - prettyPrint(delimiter : String, prefix : String, iterable : Iterable) {static} + - prettyPrint(prefix : String, iterable : Iterable) {static} + - transformToString() : Function {static} + } +} +package com.iluwatar.fluentinterface.fluentiterable.lazy { + abstract class DecoratingIterator { + # fromIterator : Iterator + - next : E + + DecoratingIterator(fromIterator : Iterator) + + computeNext() : E {abstract} + + hasNext() : boolean + + next() : E + } + class LazyFluentIterable { + - iterable : Iterable + # LazyFluentIterable() + # LazyFluentIterable(iterable : Iterable) + + asList() : List + + filter(predicate : Predicate) : FluentIterable + + first() : Optional + + first(count : int) : FluentIterable + + from(iterable : Iterable) : FluentIterable {static} + + iterator() : Iterator + + last() : Optional + + last(count : int) : FluentIterable + + map(function : Function) : FluentIterable + } +} +package com.iluwatar.fluentinterface.fluentiterable { + interface FluentIterable { + + asList() : List {abstract} + + copyToList(iterable : Iterable) : List {static} + + filter(Predicate) : FluentIterable {abstract} + + first() : Optional {abstract} + + first(int) : FluentIterable {abstract} + + last() : Optional {abstract} + + last(int) : FluentIterable {abstract} + + map(Function) : FluentIterable {abstract} + } +} +LazyFluentIterable ..|> FluentIterable +SimpleFluentIterable ..|> FluentIterable +@enduml \ No newline at end of file diff --git a/fluentinterface/pom.xml b/fluentinterface/pom.xml index 2e4129b2d1f8..b438103e489d 100644 --- a/fluentinterface/pom.xml +++ b/fluentinterface/pom.xml @@ -29,7 +29,7 @@ java-design-patterns com.iluwatar - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT 4.0.0 diff --git a/flux/README.md b/flux/README.md index 7ac312c443d2..e36b73900e9d 100644 --- a/flux/README.md +++ b/flux/README.md @@ -3,6 +3,7 @@ layout: pattern title: Flux folder: flux permalink: /patterns/flux/ +pumlid: 7SP14eCm20NGg-W13FlU1YFLE0GpyAazVZk-rPkRLSrDqdKwW14l8kUxx0r7hXdYzJA8eTIhKzEy6UnqyeUNJQBjjWm6n2seS_n3Ryql2UgJajxBoAu_ categories: Presentation Tier tags: - Java diff --git a/flux/etc/flux.urm.puml b/flux/etc/flux.urm.puml new file mode 100644 index 000000000000..a4e0b0622eca --- /dev/null +++ b/flux/etc/flux.urm.puml @@ -0,0 +1,115 @@ +@startuml +package com.iluwatar.flux.view { + interface View { + + render() {abstract} + + storeChanged(Store) {abstract} + } + class MenuView { + - selected : MenuItem + + MenuView() + + itemClicked(item : MenuItem) + + render() + + storeChanged(store : Store) + } + class ContentView { + - content : Content + + ContentView() + + render() + + storeChanged(store : Store) + } +} +package com.iluwatar.flux.action { + class MenuAction { + - menuItem : MenuItem + + MenuAction(menuItem : MenuItem) + + getMenuItem() : MenuItem + } + class ContentAction { + - content : Content + + ContentAction(content : Content) + + getContent() : Content + } + abstract class Action { + - type : ActionType + + Action(type : ActionType) + + getType() : ActionType + } + enum ActionType { + + CONTENT_CHANGED {static} + + MENU_ITEM_SELECTED {static} + + valueOf(name : String) : ActionType {static} + + values() : ActionType[] {static} + } + enum MenuItem { + + COMPANY {static} + + HOME {static} + + PRODUCTS {static} + - title : String + + toString() : String + + valueOf(name : String) : MenuItem {static} + + values() : MenuItem[] {static} + } + enum Content { + + COMPANY {static} + + PRODUCTS {static} + - title : String + + toString() : String + + valueOf(name : String) : Content {static} + + values() : Content[] {static} + } +} +package com.iluwatar.flux.app { + class App { + + App() + + main(args : String[]) {static} + } +} +package com.iluwatar.flux.dispatcher { + class Dispatcher { + - instance : Dispatcher {static} + - stores : List + - Dispatcher() + - dispatchAction(action : Action) + + getInstance() : Dispatcher {static} + + menuItemSelected(menuItem : MenuItem) + + registerStore(store : Store) + } +} +package com.iluwatar.flux.store { + class ContentStore { + - content : Content + + ContentStore() + + getContent() : Content + + onAction(action : Action) + } + class MenuStore { + - selected : MenuItem + + MenuStore() + + getSelected() : MenuItem + + onAction(action : Action) + } + abstract class Store { + - views : List + + Store() + # notifyChange() + + onAction(Action) {abstract} + + registerView(view : View) + } +} +MenuAction --> "-menuItem" MenuItem +Action --> "-type" ActionType +MenuStore --> "-selected" MenuItem +Dispatcher --> "-instance" Dispatcher +ContentView --> "-content" Content +Dispatcher --> "-stores" Store +MenuView --> "-selected" MenuItem +Store --> "-views" View +ContentAction --> "-content" Content +ContentStore --> "-content" Content +ContentStore --|> Store +MenuAction --|> Action +MenuStore --|> Store +ContentAction --|> Action +MenuView ..|> View +ContentView ..|> View +@enduml \ No newline at end of file diff --git a/flux/pom.xml b/flux/pom.xml index bd454ca23460..50f5d395a4d4 100644 --- a/flux/pom.xml +++ b/flux/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT flux diff --git a/flyweight/README.md b/flyweight/README.md index a98dced8e4e3..469bb8626179 100644 --- a/flyweight/README.md +++ b/flyweight/README.md @@ -3,8 +3,9 @@ layout: pattern title: Flyweight folder: flyweight permalink: /patterns/flyweight/ +pumlid: HSV94S8m3030Lg20M7-w4OvYAoCh7Xtnq3ty-Eq-MQlaJcdow17JNm26gpIEdkzqidffa4Qfrm2MN1XeSEADsqxEJRU94MJgCD1_W4C-YxZr08hwNqaRPUQGBm00 categories: Structural -tags: +tags: - Java - Gang Of Four - Difficulty-Intermediate @@ -30,7 +31,7 @@ true ## Real world examples -* [java.lang.Integer#valueOf(int)](http://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html#valueOf%28int%29) +* [java.lang.Integer#valueOf(int)](http://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html#valueOf%28int%29) and similarly for Byte, Character and other wrapped types. ## Credits diff --git a/flyweight/etc/flyweight.urm.puml b/flyweight/etc/flyweight.urm.puml new file mode 100644 index 000000000000..3f2203712e15 --- /dev/null +++ b/flyweight/etc/flyweight.urm.puml @@ -0,0 +1,60 @@ +@startuml +package com.iluwatar.flyweight { + class PotionFactory { + - potions : Map + + PotionFactory() + ~ createPotion(type : PotionType) : Potion + } + class HealingPotion { + + HealingPotion() + + drink() + } + class InvisibilityPotion { + + InvisibilityPotion() + + drink() + } + class AlchemistShop { + - bottomShelf : List + - topShelf : List + + AlchemistShop() + + enumerate() + - fillShelves() + + getBottomShelf() : List + + getTopShelf() : List + } + class App { + + App() + + main(args : String[]) {static} + } + interface Potion { + + drink() {abstract} + } + class PoisonPotion { + + PoisonPotion() + + drink() + } + class StrengthPotion { + + StrengthPotion() + + drink() + } + class HolyWaterPotion { + + HolyWaterPotion() + + drink() + } + enum PotionType { + + HEALING {static} + + HOLY_WATER {static} + + INVISIBILITY {static} + + POISON {static} + + STRENGTH {static} + + valueOf(name : String) : PotionType {static} + + values() : PotionType[] {static} + } +} +AlchemistShop --> "-topShelf" Potion +HealingPotion ..|> Potion +InvisibilityPotion ..|> Potion +PoisonPotion ..|> Potion +StrengthPotion ..|> Potion +HolyWaterPotion ..|> Potion +@enduml \ No newline at end of file diff --git a/flyweight/pom.xml b/flyweight/pom.xml index a4221d7625fd..eb168c4865eb 100644 --- a/flyweight/pom.xml +++ b/flyweight/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT flyweight diff --git a/front-controller/README.md b/front-controller/README.md index a462a08e0ab6..c832674dc13b 100644 --- a/front-controller/README.md +++ b/front-controller/README.md @@ -3,6 +3,7 @@ layout: pattern title: Front Controller folder: front-controller permalink: /patterns/front-controller/ +pumlid: PSlB3OGm303HLfO24j-t6nCC13bEvC_IFk6yjz6JPgbIE3OAvS_fFkmBe7Zde_ePQnXfwU8adajlK3bkT5Iuy8Tf8wk7f87kf6BGq6R0hlD8xwQTUG9v-SCSslA8nWy0 categories: Presentation Tier tags: - Java diff --git a/front-controller/etc/front-controller.urm.puml b/front-controller/etc/front-controller.urm.puml new file mode 100644 index 000000000000..8fb9be184488 --- /dev/null +++ b/front-controller/etc/front-controller.urm.puml @@ -0,0 +1,50 @@ +@startuml +package com.iluwatar.front.controller { + class ArcherView { + + ArcherView() + + display() + } + interface View { + + display() {abstract} + } + class CatapultView { + + CatapultView() + + display() + } + class ArcherCommand { + + ArcherCommand() + + process() + } + class App { + + App() + + main(args : String[]) {static} + } + class FrontController { + + FrontController() + - getCommand(request : String) : Command + - getCommandClass(request : String) : Class {static} + + handleRequest(request : String) + } + class UnknownCommand { + + UnknownCommand() + + process() + } + class ErrorView { + + ErrorView() + + display() + } + class CatapultCommand { + + CatapultCommand() + + process() + } + interface Command { + + process() {abstract} + } +} +ArcherView ..|> View +CatapultView ..|> View +ArcherCommand ..|> Command +UnknownCommand ..|> Command +ErrorView ..|> View +CatapultCommand ..|> Command +@enduml \ No newline at end of file diff --git a/front-controller/pom.xml b/front-controller/pom.xml index c8f136ca803c..c08ed8d0fae1 100644 --- a/front-controller/pom.xml +++ b/front-controller/pom.xml @@ -30,7 +30,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT front-controller diff --git a/half-sync-half-async/README.md b/half-sync-half-async/README.md index 8a091f8133a3..55891e770601 100644 --- a/half-sync-half-async/README.md +++ b/half-sync-half-async/README.md @@ -3,6 +3,7 @@ layout: pattern title: Half-Sync/Half-Async folder: half-sync-half-async permalink: /patterns/half-sync-half-async/ +pumlid: RScv3SCm3030LU819FRPXg5fIm552tnYPFiyjRi3RkbAaYkdoQr5JBy369vrxz7oaSv6XmPhL3e6TCaJ0msU-CAoilTToyG8DdKOw5z0GzcAlvNAN_WZSD1brBHHPmxv0000 categories: Concurrency tags: - Java diff --git a/half-sync-half-async/etc/half-sync-half-async.urm.puml b/half-sync-half-async/etc/half-sync-half-async.urm.puml new file mode 100644 index 000000000000..88f49eb4da50 --- /dev/null +++ b/half-sync-half-async/etc/half-sync-half-async.urm.puml @@ -0,0 +1,30 @@ +@startuml +package com.iluwatar.halfsynchalfasync { + class AsynchronousService { + - service : ExecutorService + + AsynchronousService(workQueue : BlockingQueue) + + execute(task : AsyncTask) + } + ~class ArithmeticSumTask { + - n : long + + ArithmeticSumTask(n : long) + + call() : Long + + onError(throwable : Throwable) + + onPostCall(result : Long) + + onPreCall() + } + class App { + + App() + - ap(i : long) : long {static} + + main(args : String[]) {static} + } + interface AsyncTask { + + call() : O {abstract} + + onError(Throwable) {abstract} + + onPostCall(O) {abstract} + + onPreCall() {abstract} + } +} +ArithmeticSumTask ..+ App +ArithmeticSumTask ..|> AsyncTask +@enduml \ No newline at end of file diff --git a/half-sync-half-async/pom.xml b/half-sync-half-async/pom.xml index 5b6ce56b1f48..d22e65a5e402 100644 --- a/half-sync-half-async/pom.xml +++ b/half-sync-half-async/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT half-sync-half-async diff --git a/hexagonal/README.md b/hexagonal/README.md new file mode 100644 index 000000000000..33c2ba9cbd0b --- /dev/null +++ b/hexagonal/README.md @@ -0,0 +1,35 @@ +--- +layout: pattern +title: Hexagonal Architecture +folder: hexagonal +permalink: /patterns/hexagonal/ +pumlid: HSTB4W8X30N0g-W1XkozpPD90LO8L3wEnzUTk-xxq2fvSfhSUiJs1v7XAcr4psSwMrqQh57gcZGaBmICNdZZEDb7qsCZWasT9lm7wln1MmeXZlfVIPjbvvGl +categories: Architectural +tags: + - Java + - Difficulty-Expert +--- + +## Also known as +* Ports and Adapters +* Clean Architecture +* Onion Architecture + +## Intent +Allow an application to equally be driven by users, programs, automated test or batch scripts, and to be developed and tested in isolation from its eventual run-time devices and databases. + +![Hexagonal Architecture class diagram](./etc/hexagonal.png) + +## Applicability +Use Hexagonal Architecture pattern when + +* it is important that the application is fully testable +* you use Domain Driven Design methodology and/or Microservices architectural style + +## Real world examples + +* [Apache Isis](https://isis.apache.org/) + +## Credits + +* [Alistair Cockburn - Hexagonal Architecture](http://alistair.cockburn.us/Hexagonal+architecture) diff --git a/hexagonal/etc/hexagonal.png b/hexagonal/etc/hexagonal.png new file mode 100644 index 000000000000..1ad077fd4fd0 Binary files /dev/null and b/hexagonal/etc/hexagonal.png differ diff --git a/hexagonal/etc/hexagonal.ucls b/hexagonal/etc/hexagonal.ucls new file mode 100644 index 000000000000..d3b46b23d08e --- /dev/null +++ b/hexagonal/etc/hexagonal.ucls @@ -0,0 +1,280 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hexagonal/etc/hexagonal.urm.puml b/hexagonal/etc/hexagonal.urm.puml new file mode 100644 index 000000000000..a2f5d04691ec --- /dev/null +++ b/hexagonal/etc/hexagonal.urm.puml @@ -0,0 +1,276 @@ +@startuml +package com.iluwatar.hexagonal.sampledata { + class SampleData { + - PLAYERS : List {static} + + SampleData() + - getRandomPlayerDetails() : PlayerDetails {static} + + submitTickets(lotteryService : LotteryService, numTickets : int) {static} + } +} +package com.iluwatar.hexagonal.service { + class ConsoleLottery { + + ConsoleLottery() + + main(args : String[]) {static} + - printMainMenu() {static} + - readString(scanner : Scanner) : String {static} + } +} +package com.iluwatar.hexagonal.mongo { + class MongoConnectionPropertiesLoader { + - DEFAULT_HOST : String {static} + - DEFAULT_PORT : int {static} + + MongoConnectionPropertiesLoader() + + load() {static} + } +} +package com.iluwatar.hexagonal.domain { + class LotteryTicketId { + - id : int + - numAllocated : int {static} + + LotteryTicketId() + + LotteryTicketId(id : int) + + equals(o : Object) : boolean + + getId() : int + + hashCode() : int + + toString() : String + } + class LotteryAdministration { + - notifications : LotteryEventLog + - repository : LotteryTicketRepository + - wireTransfers : WireTransfers + + LotteryAdministration(repository : LotteryTicketRepository, notifications : LotteryEventLog, wireTransfers : WireTransfers) + + getAllSubmittedTickets() : Map + + performLottery() : LotteryNumbers + + resetLottery() + } + class LotteryNumbers { + + MAX_NUMBER : int {static} + + MIN_NUMBER : int {static} + + NUM_NUMBERS : int {static} + - numbers : Set + - LotteryNumbers() + - LotteryNumbers(givenNumbers : Set) + + create(givenNumbers : Set) : LotteryNumbers {static} + + createRandom() : LotteryNumbers {static} + + equals(obj : Object) : boolean + - generateRandomNumbers() + + getNumbers() : Set + + getNumbersAsString() : String + + hashCode() : int + + toString() : String + } + class LotteryService { + - notifications : LotteryEventLog + - repository : LotteryTicketRepository + - wireTransfers : WireTransfers + + LotteryService(repository : LotteryTicketRepository, notifications : LotteryEventLog, wireTransfers : WireTransfers) + + checkTicketForPrize(id : LotteryTicketId, winningNumbers : LotteryNumbers) : LotteryTicketCheckResult + + submitTicket(ticket : LotteryTicket) : Optional + } + -class RandomNumberGenerator { + - randomIterator : OfInt + + RandomNumberGenerator(min : int, max : int) + + nextInt() : int + } + class PlayerDetails { + - bankAccountNumber : String + - emailAddress : String + - phoneNumber : String + + PlayerDetails(email : String, bankAccount : String, phone : String) + + equals(obj : Object) : boolean + + getBankAccount() : String + + getEmail() : String + + getPhoneNumber() : String + + hashCode() : int + + toString() : String + } + class LotteryTicketCheckResult { + - checkResult : CheckResult + - prizeAmount : int + + LotteryTicketCheckResult(result : CheckResult) + + LotteryTicketCheckResult(result : CheckResult, amount : int) + + equals(obj : Object) : boolean + + getPrizeAmount() : int + + getResult() : CheckResult + + hashCode() : int + } + class LotteryConstants { + + PLAYER_MAX_SALDO : int {static} + + PRIZE_AMOUNT : int {static} + + SERVICE_BANK_ACCOUNT : String {static} + + SERVICE_BANK_ACCOUNT_SALDO : int {static} + + TICKET_PRIZE : int {static} + - LotteryConstants() + } + class LotteryTicket { + - id : LotteryTicketId + - lotteryNumbers : LotteryNumbers + - playerDetails : PlayerDetails + + LotteryTicket(id : LotteryTicketId, details : PlayerDetails, numbers : LotteryNumbers) + + equals(obj : Object) : boolean + + getId() : LotteryTicketId + + getNumbers() : LotteryNumbers + + getPlayerDetails() : PlayerDetails + + hashCode() : int + + setId(id : LotteryTicketId) + + toString() : String + } + class LotteryUtils { + - LotteryUtils() + + checkTicketForPrize(repository : LotteryTicketRepository, id : LotteryTicketId, winningNumbers : LotteryNumbers) : LotteryTicketCheckResult {static} + } + enum CheckResult { + + NO_PRIZE {static} + + TICKET_NOT_SUBMITTED {static} + + WIN_PRIZE {static} + + valueOf(name : String) : CheckResult {static} + + values() : CheckResult[] {static} + } +} +package com.iluwatar.hexagonal.banking { + interface WireTransfers { + + getFunds(String) : int {abstract} + + setFunds(String, int) {abstract} + + transferFunds(int, String, String) : boolean {abstract} + } + class MongoBank { + - DEFAULT_ACCOUNTS_COLLECTION : String {static} + - DEFAULT_DB : String {static} + - accountsCollection : MongoCollection + - database : MongoDatabase + - mongoClient : MongoClient + + MongoBank() + + MongoBank(dbName : String, accountsCollectionName : String) + + connect() + + connect(dbName : String, accountsCollectionName : String) + + getAccountsCollection() : MongoCollection + + getFunds(bankAccount : String) : int + + getMongoClient() : MongoClient + + getMongoDatabase() : MongoDatabase + + setFunds(bankAccount : String, amount : int) + + transferFunds(amount : int, sourceBackAccount : String, destinationBankAccount : String) : boolean + } + class InMemoryBank { + - accounts : Map {static} + + InMemoryBank() + + getFunds(bankAccount : String) : int + + setFunds(bankAccount : String, amount : int) + + transferFunds(amount : int, sourceBackAccount : String, destinationBankAccount : String) : boolean + } +} +package com.iluwatar.hexagonal.database { + class MongoTicketRepository { + - DEFAULT_COUNTERS_COLLECTION : String {static} + - DEFAULT_DB : String {static} + - DEFAULT_TICKETS_COLLECTION : String {static} + - countersCollection : MongoCollection + - database : MongoDatabase + - mongoClient : MongoClient + - ticketsCollection : MongoCollection + + MongoTicketRepository() + + MongoTicketRepository(dbName : String, ticketsCollectionName : String, countersCollectionName : String) + + connect() + + connect(dbName : String, ticketsCollectionName : String, countersCollectionName : String) + + deleteAll() + - docToTicket(doc : Document) : LotteryTicket + + findAll() : Map + + findById(id : LotteryTicketId) : Optional + + getCountersCollection() : MongoCollection + + getMongoClient() : MongoClient + + getMongoDatabase() : MongoDatabase + + getNextId() : int + + getTicketsCollection() : MongoCollection + - initCounters() + + save(ticket : LotteryTicket) : Optional + } + class InMemoryTicketRepository { + - tickets : Map {static} + + InMemoryTicketRepository() + + deleteAll() + + findAll() : Map + + findById(id : LotteryTicketId) : Optional + + save(ticket : LotteryTicket) : Optional + } + interface LotteryTicketRepository { + + deleteAll() {abstract} + + findAll() : Map {abstract} + + findById(LotteryTicketId) : Optional {abstract} + + save(LotteryTicket) : Optional {abstract} + } +} +package com.iluwatar.hexagonal { + class App { + + App() + + main(args : String[]) {static} + } +} +package com.iluwatar.hexagonal.administration { + class ConsoleAdministration { + + ConsoleAdministration() + + main(args : String[]) {static} + - printMainMenu() {static} + - readString(scanner : Scanner) : String {static} + } +} +package com.iluwatar.hexagonal.eventlog { + interface LotteryEventLog { + + prizeError(PlayerDetails, int) {abstract} + + ticketDidNotWin(PlayerDetails) {abstract} + + ticketSubmitError(PlayerDetails) {abstract} + + ticketSubmitted(PlayerDetails) {abstract} + + ticketWon(PlayerDetails, int) {abstract} + } + class StdOutEventLog { + + StdOutEventLog() + + prizeError(details : PlayerDetails, prizeAmount : int) + + ticketDidNotWin(details : PlayerDetails) + + ticketSubmitError(details : PlayerDetails) + + ticketSubmitted(details : PlayerDetails) + + ticketWon(details : PlayerDetails, prizeAmount : int) + } + class MongoEventLog { + - DEFAULT_DB : String {static} + - DEFAULT_EVENTS_COLLECTION : String {static} + - database : MongoDatabase + - eventsCollection : MongoCollection + - mongoClient : MongoClient + - stdOutEventLog : StdOutEventLog + + MongoEventLog() + + MongoEventLog(dbName : String, eventsCollectionName : String) + + connect() + + connect(dbName : String, eventsCollectionName : String) + + getEventsCollection() : MongoCollection + + getMongoClient() : MongoClient + + getMongoDatabase() : MongoDatabase + + prizeError(details : PlayerDetails, prizeAmount : int) + + ticketDidNotWin(details : PlayerDetails) + + ticketSubmitError(details : PlayerDetails) + + ticketSubmitted(details : PlayerDetails) + + ticketWon(details : PlayerDetails, prizeAmount : int) + } +} +LotteryAdministration --+ LotteryTicketCheckResult +LotteryTicket --> "-playerDetails" PlayerDetails +MongoEventLog --> "-stdOutEventLog" StdOutEventLog +LotteryService --> "-wireTransfers" WireTransfers +LotteryAdministration --> "-notifications" LotteryEventLog +RandomNumberGenerator ..+ PrimitiveIterator +LotteryAdministration --> "-wireTransfers" WireTransfers +LotteryTicket --> "-id" LotteryTicketId +LotteryService --> "-notifications" LotteryEventLog +LotteryAdministration --> "-repository" LotteryTicketRepository +LotteryTicket --> "-lotteryNumbers" LotteryNumbers +SampleData --> "-PLAYERS" PlayerDetails +ConsoleLottery --+ LotteryTicketCheckResult +RandomNumberGenerator ..+ LotteryNumbers +LotteryService --> "-repository" LotteryTicketRepository +LotteryUtils --+ LotteryTicketCheckResult +LotteryTicketCheckResult --> "-checkResult" CheckResult +CheckResult ..+ LotteryTicketCheckResult +MongoTicketRepository ..|> LotteryTicketRepository +MongoBank ..|> WireTransfers +InMemoryBank ..|> WireTransfers +StdOutEventLog ..|> LotteryEventLog +InMemoryTicketRepository ..|> LotteryTicketRepository +MongoEventLog ..|> LotteryEventLog +@enduml \ No newline at end of file diff --git a/hexagonal/pom.xml b/hexagonal/pom.xml new file mode 100644 index 000000000000..c489c45c498f --- /dev/null +++ b/hexagonal/pom.xml @@ -0,0 +1,51 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.14.0-SNAPSHOT + + hexagonal + + + junit + junit + test + + + com.google.inject + guice + + + org.mongodb + mongo-java-driver + + + diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/App.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/App.java new file mode 100644 index 000000000000..97190937c460 --- /dev/null +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/App.java @@ -0,0 +1,83 @@ +/** + * 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.hexagonal; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.iluwatar.hexagonal.domain.LotteryAdministration; +import com.iluwatar.hexagonal.domain.LotteryService; +import com.iluwatar.hexagonal.module.LotteryTestingModule; +import com.iluwatar.hexagonal.sampledata.SampleData; + +/** + * + * Hexagonal Architecture pattern decouples the application core from the + * services it uses. This allows the services to be plugged in and the + * application will run with or without the services.

+ * + * The core logic, or business logic, of an application consists of the + * algorithms that are essential to its purpose. They implement the use + * cases that are the heart of the application. When you change them, you + * change the essence of the application.

+ * + * The services are not essential. They can be replaced without changing + * the purpose of the application. Examples: database access and other + * types of storage, user interface components, e-mail and other + * communication components, hardware devices.

+ * + * This example demonstrates Hexagonal Architecture with a lottery system. + * The application core is separate from the services that drive it and + * from the services it uses.

+ * + * The primary ports for the application are console interfaces + * {@link ConsoleAdministration} through which the lottery round is + * initiated and run and {@link ConsoleLottery} that allows players to + * submit lottery tickets for the draw.

+ * + * The secondary ports that application core uses are {@link WireTransfers} + * which is a banking service, {@link LotteryEventLog} that delivers + * eventlog as lottery events occur and {@link LotteryTicketRepository} + * that is the storage for the lottery tickets. + * + */ +public class App { + + /** + * Program entry point + */ + public static void main(String[] args) { + + Injector injector = Guice.createInjector(new LotteryTestingModule()); + + // start new lottery round + LotteryAdministration administartion = injector.getInstance(LotteryAdministration.class); + administartion.resetLottery(); + + // submit some lottery tickets + LotteryService service = injector.getInstance(LotteryService.class); + SampleData.submitTickets(service, 20); + + // perform lottery + administartion.performLottery(); + } +} diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministration.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministration.java new file mode 100644 index 000000000000..ea2f33699416 --- /dev/null +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministration.java @@ -0,0 +1,86 @@ +/** + * 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.hexagonal.administration; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.iluwatar.hexagonal.domain.LotteryAdministration; +import com.iluwatar.hexagonal.domain.LotteryNumbers; +import com.iluwatar.hexagonal.domain.LotteryService; +import com.iluwatar.hexagonal.module.LotteryModule; +import com.iluwatar.hexagonal.mongo.MongoConnectionPropertiesLoader; +import com.iluwatar.hexagonal.sampledata.SampleData; + +import java.util.Scanner; + +/** + * Console interface for lottery administration + */ +public class ConsoleAdministration { + + /** + * Program entry point + */ + public static void main(String[] args) { + MongoConnectionPropertiesLoader.load(); + Injector injector = Guice.createInjector(new LotteryModule()); + LotteryAdministration administartion = injector.getInstance(LotteryAdministration.class); + LotteryService service = injector.getInstance(LotteryService.class); + SampleData.submitTickets(service, 20); + Scanner scanner = new Scanner(System.in); + boolean exit = false; + while (!exit) { + printMainMenu(); + String cmd = readString(scanner); + if (cmd.equals("1")) { + administartion.getAllSubmittedTickets().forEach((k,v)->System.out.println("Key: " + k + " Value: " + v)); + } else if (cmd.equals("2")) { + LotteryNumbers numbers = administartion.performLottery(); + System.out.println("The winning numbers: " + numbers.getNumbersAsString()); + System.out.println("Time to reset the database for next round, eh?"); + } else if (cmd.equals("3")) { + administartion.resetLottery(); + System.out.println("The lottery ticket database was cleared."); + } else if (cmd.equals("4")) { + exit = true; + } else { + System.out.println("Unknown command: " + cmd); + } + } + } + + private static void printMainMenu() { + System.out.println(""); + System.out.println("### Lottery Administration Console ###"); + System.out.println("(1) Show all submitted tickets"); + System.out.println("(2) Perform lottery draw"); + System.out.println("(3) Reset lottery ticket database"); + System.out.println("(4) Exit"); + } + + private static String readString(Scanner scanner) { + System.out.print("> "); + String cmd = scanner.next(); + return cmd; + } +} diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/banking/InMemoryBank.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/banking/InMemoryBank.java new file mode 100644 index 000000000000..85b4121ebc42 --- /dev/null +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/banking/InMemoryBank.java @@ -0,0 +1,63 @@ +/** + * 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.hexagonal.banking; + +import java.util.HashMap; +import java.util.Map; + +import com.iluwatar.hexagonal.domain.LotteryConstants; + +/** + * + * Banking implementation + * + */ +public class InMemoryBank implements WireTransfers { + + private static Map accounts = new HashMap<>(); + + static { + accounts.put(LotteryConstants.SERVICE_BANK_ACCOUNT, LotteryConstants.SERVICE_BANK_ACCOUNT_SALDO); + } + + @Override + public void setFunds(String bankAccount, int amount) { + accounts.put(bankAccount, amount); + } + + @Override + public int getFunds(String bankAccount) { + return accounts.getOrDefault(bankAccount, 0); + } + + @Override + public boolean transferFunds(int amount, String sourceBackAccount, String destinationBankAccount) { + if (accounts.getOrDefault(sourceBackAccount, 0) >= amount) { + accounts.put(sourceBackAccount, accounts.get(sourceBackAccount) - amount); + accounts.put(destinationBankAccount, accounts.get(destinationBankAccount) + amount); + return true; + } else { + return false; + } + } +} diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/banking/MongoBank.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/banking/MongoBank.java new file mode 100644 index 000000000000..23a0e376a724 --- /dev/null +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/banking/MongoBank.java @@ -0,0 +1,133 @@ +/** + * 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.hexagonal.banking; + +import com.mongodb.MongoClient; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.UpdateOptions; +import org.bson.Document; + +import java.util.ArrayList; + +/** + * Mongo based banking adapter + */ +public class MongoBank implements WireTransfers { + + private static final String DEFAULT_DB = "lotteryDB"; + private static final String DEFAULT_ACCOUNTS_COLLECTION = "accounts"; + + private MongoClient mongoClient; + private MongoDatabase database; + private MongoCollection accountsCollection; + + /** + * Constructor + */ + public MongoBank() { + connect(); + } + + /** + * Constructor accepting parameters + */ + public MongoBank(String dbName, String accountsCollectionName) { + connect(dbName, accountsCollectionName); + } + + /** + * Connect to database with default parameters + */ + public void connect() { + connect(DEFAULT_DB, DEFAULT_ACCOUNTS_COLLECTION); + } + + /** + * Connect to database with given parameters + */ + public void connect(String dbName, String accountsCollectionName) { + if (mongoClient != null) { + mongoClient.close(); + } + mongoClient = new MongoClient(System.getProperty("mongo-host"), + Integer.parseInt(System.getProperty("mongo-port"))); + database = mongoClient.getDatabase(dbName); + accountsCollection = database.getCollection(accountsCollectionName); + } + + /** + * @return mongo client + */ + public MongoClient getMongoClient() { + return mongoClient; + } + + /** + * + * @return mongo database + */ + public MongoDatabase getMongoDatabase() { + return database; + } + + /** + * + * @return accounts collection + */ + public MongoCollection getAccountsCollection() { + return accountsCollection; + } + + + @Override + public void setFunds(String bankAccount, int amount) { + Document search = new Document("_id", bankAccount); + Document update = new Document("_id", bankAccount).append("funds", amount); + accountsCollection.updateOne(search, new Document("$set", update), new UpdateOptions().upsert(true)); + } + + @Override + public int getFunds(String bankAccount) { + Document search = new Document("_id", bankAccount); + ArrayList results = accountsCollection.find(search).limit(1).into(new ArrayList()); + if (results.size() > 0) { + return results.get(0).getInteger("funds"); + } else { + return 0; + } + } + + @Override + public boolean transferFunds(int amount, String sourceBackAccount, String destinationBankAccount) { + int sourceFunds = getFunds(sourceBackAccount); + if (sourceFunds < amount) { + return false; + } else { + int destFunds = getFunds(destinationBankAccount); + setFunds(sourceBackAccount, sourceFunds - amount); + setFunds(destinationBankAccount, destFunds + amount); + return true; + } + } +} diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/banking/WireTransfers.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/banking/WireTransfers.java new file mode 100644 index 000000000000..7d21e7bf328f --- /dev/null +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/banking/WireTransfers.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 com.iluwatar.hexagonal.banking; + +/** + * + * Interface to bank accounts. + * + */ +public interface WireTransfers { + + /** + * Set amount of funds for bank account + */ + void setFunds(String bankAccount, int amount); + + /** + * Get amount of funds for bank account + */ + int getFunds(String bankAccount); + + /** + * Transfer funds from one bank account to another + */ + boolean transferFunds(int amount, String sourceBackAccount, String destinationBankAccount); + +} diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/database/InMemoryTicketRepository.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/database/InMemoryTicketRepository.java new file mode 100644 index 000000000000..0d510b411731 --- /dev/null +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/database/InMemoryTicketRepository.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.hexagonal.database; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import com.iluwatar.hexagonal.domain.LotteryTicket; +import com.iluwatar.hexagonal.domain.LotteryTicketId; + +/** + * + * Mock database for lottery tickets. + * + */ +public class InMemoryTicketRepository implements LotteryTicketRepository { + + private static Map tickets = new HashMap<>(); + + @Override + public Optional findById(LotteryTicketId id) { + LotteryTicket ticket = tickets.get(id); + if (ticket == null) { + return Optional.empty(); + } else { + return Optional.of(ticket); + } + } + + @Override + public Optional save(LotteryTicket ticket) { + LotteryTicketId id = new LotteryTicketId(); + tickets.put(id, ticket); + return Optional.of(id); + } + + @Override + public Map findAll() { + return tickets; + } + + @Override + public void deleteAll() { + tickets.clear(); + } +} diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/database/LotteryTicketRepository.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/database/LotteryTicketRepository.java new file mode 100644 index 000000000000..4c6522838a75 --- /dev/null +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/database/LotteryTicketRepository.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.hexagonal.database; + +import java.util.Map; +import java.util.Optional; + +import com.iluwatar.hexagonal.domain.LotteryTicket; +import com.iluwatar.hexagonal.domain.LotteryTicketId; + +/** + * + * Interface for accessing lottery tickets in database. + * + */ +public interface LotteryTicketRepository { + + /** + * Find lottery ticket by id + */ + Optional findById(LotteryTicketId id); + + /** + * Save lottery ticket + */ + Optional save(LotteryTicket ticket); + + /** + * Get all lottery tickets + */ + Map findAll(); + + /** + * Delete all lottery tickets + */ + void deleteAll(); + +} diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/database/MongoTicketRepository.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/database/MongoTicketRepository.java new file mode 100644 index 000000000000..13a937a1bc78 --- /dev/null +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/database/MongoTicketRepository.java @@ -0,0 +1,193 @@ +/** + * 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.hexagonal.database; + +import com.iluwatar.hexagonal.domain.LotteryNumbers; +import com.iluwatar.hexagonal.domain.LotteryTicket; +import com.iluwatar.hexagonal.domain.LotteryTicketId; +import com.iluwatar.hexagonal.domain.PlayerDetails; +import com.mongodb.MongoClient; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import org.bson.Document; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; + +/** + * Mongo lottery ticket database + */ +public class MongoTicketRepository implements LotteryTicketRepository { + + private static final String DEFAULT_DB = "lotteryDB"; + private static final String DEFAULT_TICKETS_COLLECTION = "lotteryTickets"; + private static final String DEFAULT_COUNTERS_COLLECTION = "counters"; + + private MongoClient mongoClient; + private MongoDatabase database; + private MongoCollection ticketsCollection; + private MongoCollection countersCollection; + + /** + * Constructor + */ + public MongoTicketRepository() { + connect(); + } + + /** + * Constructor accepting parameters + */ + public MongoTicketRepository(String dbName, String ticketsCollectionName, + String countersCollectionName) { + connect(dbName, ticketsCollectionName, countersCollectionName); + } + + /** + * Connect to database with default parameters + */ + public void connect() { + connect(DEFAULT_DB, DEFAULT_TICKETS_COLLECTION, DEFAULT_COUNTERS_COLLECTION); + } + + /** + * Connect to database with given parameters + */ + public void connect(String dbName, String ticketsCollectionName, + String countersCollectionName) { + if (mongoClient != null) { + mongoClient.close(); + } + mongoClient = new MongoClient(System.getProperty("mongo-host"), + Integer.parseInt(System.getProperty("mongo-port"))); + database = mongoClient.getDatabase(dbName); + ticketsCollection = database.getCollection(ticketsCollectionName); + countersCollection = database.getCollection(countersCollectionName); + if (countersCollection.count() <= 0) { + initCounters(); + } + } + + private void initCounters() { + Document doc = new Document("_id", "ticketId").append("seq", 1); + countersCollection.insertOne(doc); + } + + /** + * @return next ticket id + */ + public int getNextId() { + Document find = new Document("_id", "ticketId"); + Document increase = new Document("seq", 1); + Document update = new Document("$inc", increase); + Document result = countersCollection.findOneAndUpdate(find, update); + return result.getInteger("seq"); + } + + /** + * @return mongo client + */ + public MongoClient getMongoClient() { + return mongoClient; + } + + /** + * + * @return mongo database + */ + public MongoDatabase getMongoDatabase() { + return database; + } + + /** + * + * @return tickets collection + */ + public MongoCollection getTicketsCollection() { + return ticketsCollection; + } + + /** + * + * @return counters collection + */ + public MongoCollection getCountersCollection() { + return countersCollection; + } + + @Override + public Optional findById(LotteryTicketId id) { + Document find = new Document("ticketId", id.getId()); + ArrayList results = ticketsCollection.find(find).limit(1).into(new ArrayList()); + if (results.size() > 0) { + LotteryTicket lotteryTicket = docToTicket(results.get(0)); + return Optional.of(lotteryTicket); + } else { + return Optional.empty(); + } + } + + @Override + public Optional save(LotteryTicket ticket) { + int ticketId = getNextId(); + Document doc = new Document("ticketId", ticketId); + doc.put("email", ticket.getPlayerDetails().getEmail()); + doc.put("bank", ticket.getPlayerDetails().getBankAccount()); + doc.put("phone", ticket.getPlayerDetails().getPhoneNumber()); + doc.put("numbers", ticket.getNumbers().getNumbersAsString()); + ticketsCollection.insertOne(doc); + return Optional.of(new LotteryTicketId(ticketId)); + } + + @Override + public Map findAll() { + Map map = new HashMap<>(); + ArrayList docs = ticketsCollection.find(new Document()).into(new ArrayList()); + for (Document doc: docs) { + LotteryTicket lotteryTicket = docToTicket(doc); + map.put(lotteryTicket.getId(), lotteryTicket); + } + return map; + } + + @Override + public void deleteAll() { + ticketsCollection.deleteMany(new Document()); + } + + private LotteryTicket docToTicket(Document doc) { + PlayerDetails playerDetails = new PlayerDetails(doc.getString("email"), doc.getString("bank"), + doc.getString("phone")); + int[] numArray = Arrays.asList(doc.getString("numbers").split(",")).stream().mapToInt(Integer::parseInt).toArray(); + HashSet numbers = new HashSet<>(); + for (int num: numArray) { + numbers.add(num); + } + LotteryNumbers lotteryNumbers = LotteryNumbers.create(numbers); + return new LotteryTicket(new LotteryTicketId(doc.getInteger("ticketId")), playerDetails, lotteryNumbers); + } +} diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryAdministration.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryAdministration.java new file mode 100644 index 000000000000..bc264a9ef7c6 --- /dev/null +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryAdministration.java @@ -0,0 +1,90 @@ +/** + * 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.hexagonal.domain; + +import com.google.inject.Inject; +import com.iluwatar.hexagonal.banking.WireTransfers; +import com.iluwatar.hexagonal.database.LotteryTicketRepository; +import com.iluwatar.hexagonal.eventlog.LotteryEventLog; + +import java.util.Map; + +/** + * + * Lottery administration implementation + * + */ +public class LotteryAdministration { + + private final LotteryTicketRepository repository; + private final LotteryEventLog notifications; + private final WireTransfers wireTransfers; + + /** + * Constructor + */ + @Inject + public LotteryAdministration(LotteryTicketRepository repository, LotteryEventLog notifications, + WireTransfers wireTransfers) { + this.repository = repository; + this.notifications = notifications; + this.wireTransfers = wireTransfers; + } + + /** + * Get all the lottery tickets submitted for lottery + */ + public Map getAllSubmittedTickets() { + return repository.findAll(); + } + + /** + * Draw lottery numbers + */ + public LotteryNumbers performLottery() { + LotteryNumbers numbers = LotteryNumbers.createRandom(); + Map tickets = getAllSubmittedTickets(); + for (LotteryTicketId id : tickets.keySet()) { + LotteryTicketCheckResult result = LotteryUtils.checkTicketForPrize(repository, id, numbers); + if (result.getResult().equals(LotteryTicketCheckResult.CheckResult.WIN_PRIZE)) { + boolean transferred = wireTransfers.transferFunds(LotteryConstants.PRIZE_AMOUNT, + LotteryConstants.SERVICE_BANK_ACCOUNT, tickets.get(id).getPlayerDetails().getBankAccount()); + if (transferred) { + notifications.ticketWon(tickets.get(id).getPlayerDetails(), LotteryConstants.PRIZE_AMOUNT); + } else { + notifications.prizeError(tickets.get(id).getPlayerDetails(), LotteryConstants.PRIZE_AMOUNT); + } + } else if (result.getResult().equals(LotteryTicketCheckResult.CheckResult.NO_PRIZE)) { + notifications.ticketDidNotWin(tickets.get(id).getPlayerDetails()); + } + } + return numbers; + } + + /** + * Begin new lottery round + */ + public void resetLottery() { + repository.deleteAll(); + } +} diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryConstants.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryConstants.java new file mode 100644 index 000000000000..28fd4e2f49fc --- /dev/null +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryConstants.java @@ -0,0 +1,41 @@ +/** + * 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.hexagonal.domain; + +/** + * + * Lottery domain constants + * + */ +public class LotteryConstants { + + private LotteryConstants() { + } + + public static final int PRIZE_AMOUNT = 100000; + public static final String SERVICE_BANK_ACCOUNT = "123-123"; + public static final int TICKET_PRIZE = 3; + public static final int SERVICE_BANK_ACCOUNT_SALDO = 150000; + public static final int PLAYER_MAX_SALDO = 100; + +} diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryNumbers.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryNumbers.java new file mode 100644 index 000000000000..930c919da074 --- /dev/null +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryNumbers.java @@ -0,0 +1,175 @@ +/** + * 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.hexagonal.domain; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.PrimitiveIterator; +import java.util.Random; +import java.util.Set; + +/** + * + * Value object representing lottery numbers. This lottery uses sets of 4 numbers. The numbers must be unique and + * between 1 and 20. + * + */ +public class LotteryNumbers { + + private final Set numbers; + + public static final int MIN_NUMBER = 1; + public static final int MAX_NUMBER = 20; + public static final int NUM_NUMBERS = 4; + + /** + * Constructor. Creates random lottery numbers. + */ + private LotteryNumbers() { + numbers = new HashSet<>(); + generateRandomNumbers(); + } + + /** + * Constructor. Uses given numbers. + */ + private LotteryNumbers(Set givenNumbers) { + numbers = new HashSet<>(); + numbers.addAll(givenNumbers); + } + + /** + * @return random LotteryNumbers + */ + public static LotteryNumbers createRandom() { + return new LotteryNumbers(); + } + + /** + * @return given LotteryNumbers + */ + public static LotteryNumbers create(Set givenNumbers) { + return new LotteryNumbers(givenNumbers); + } + + /** + * @return lottery numbers + */ + public Set getNumbers() { + return Collections.unmodifiableSet(numbers); + } + + /** + * @return numbers as comma separated string + */ + public String getNumbersAsString() { + List list = new ArrayList<>(); + list.addAll(numbers); + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < NUM_NUMBERS; i++) { + builder.append(list.get(i)); + if (i < NUM_NUMBERS - 1) { + builder.append(","); + } + } + return builder.toString(); + } + + /** + * Generates 4 unique random numbers between 1-20 into numbers set. + */ + private void generateRandomNumbers() { + numbers.clear(); + RandomNumberGenerator generator = new RandomNumberGenerator(MIN_NUMBER, MAX_NUMBER); + while (numbers.size() < NUM_NUMBERS) { + int num = generator.nextInt(); + if (!numbers.contains(num)) { + numbers.add(num); + } + } + } + + @Override + public String toString() { + return "LotteryNumbers{" + "numbers=" + numbers + '}'; + } + + /** + * + * Helper class for generating random numbers. + * + */ + private static class RandomNumberGenerator { + + private PrimitiveIterator.OfInt randomIterator; + + /** + * Initialize a new random number generator that generates random numbers in the range [min, max] + * + * @param min the min value (inclusive) + * @param max the max value (inclusive) + */ + public RandomNumberGenerator(int min, int max) { + randomIterator = new Random().ints(min, max + 1).iterator(); + } + + /** + * @return a random number in the range (min, max) + */ + public int nextInt() { + return randomIterator.nextInt(); + } + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((numbers == null) ? 0 : numbers.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + LotteryNumbers other = (LotteryNumbers) obj; + if (numbers == null) { + if (other.numbers != null) { + return false; + } + } else if (!numbers.equals(other.numbers)) { + return false; + } + return true; + } +} diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryService.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryService.java new file mode 100644 index 000000000000..dceac26e4e8e --- /dev/null +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryService.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.hexagonal.domain; + +import com.google.inject.Inject; +import com.iluwatar.hexagonal.banking.WireTransfers; +import com.iluwatar.hexagonal.database.LotteryTicketRepository; +import com.iluwatar.hexagonal.eventlog.LotteryEventLog; + +import java.util.Optional; + +/** + * + * Implementation for lottery service + * + */ +public class LotteryService { + + private final LotteryTicketRepository repository; + private final LotteryEventLog notifications; + private final WireTransfers wireTransfers; + + /** + * Constructor + */ + @Inject + public LotteryService(LotteryTicketRepository repository, LotteryEventLog notifications, + WireTransfers wireTransfers) { + this.repository = repository; + this.notifications = notifications; + this.wireTransfers = wireTransfers; + } + + /** + * Submit lottery ticket to participate in the lottery + */ + public Optional submitTicket(LotteryTicket ticket) { + boolean result = wireTransfers.transferFunds(LotteryConstants.TICKET_PRIZE, + ticket.getPlayerDetails().getBankAccount(), LotteryConstants.SERVICE_BANK_ACCOUNT); + if (result == false) { + notifications.ticketSubmitError(ticket.getPlayerDetails()); + return Optional.empty(); + } + Optional optional = repository.save(ticket); + if (optional.isPresent()) { + notifications.ticketSubmitted(ticket.getPlayerDetails()); + } + return optional; + } + + /** + * Check if lottery ticket has won + */ + public LotteryTicketCheckResult checkTicketForPrize(LotteryTicketId id, LotteryNumbers winningNumbers) { + return LotteryUtils.checkTicketForPrize(repository, id, winningNumbers); + } +} diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicket.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicket.java new file mode 100644 index 000000000000..9fa318e4c8ef --- /dev/null +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicket.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.hexagonal.domain; + +/** + * + * Immutable value object representing lottery ticket. + * + */ +public class LotteryTicket { + + private LotteryTicketId id; + private final PlayerDetails playerDetails; + private final LotteryNumbers lotteryNumbers; + + /** + * Constructor. + */ + public LotteryTicket(LotteryTicketId id, PlayerDetails details, LotteryNumbers numbers) { + this.id = id; + playerDetails = details; + lotteryNumbers = numbers; + } + + /** + * @return player details + */ + public PlayerDetails getPlayerDetails() { + return playerDetails; + } + + /** + * @return lottery numbers + */ + public LotteryNumbers getNumbers() { + return lotteryNumbers; + } + + /** + * @return id + */ + public LotteryTicketId getId() { + return id; + } + + /** + * set id + */ + public void setId(LotteryTicketId id) { + this.id = id; + } + + @Override + public String toString() { + return playerDetails.toString() + " " + lotteryNumbers.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((lotteryNumbers == null) ? 0 : lotteryNumbers.hashCode()); + result = prime * result + ((playerDetails == null) ? 0 : playerDetails.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + LotteryTicket other = (LotteryTicket) obj; + if (lotteryNumbers == null) { + if (other.lotteryNumbers != null) { + return false; + } + } else if (!lotteryNumbers.equals(other.lotteryNumbers)) { + return false; + } + if (playerDetails == null) { + if (other.playerDetails != null) { + return false; + } + } else if (!playerDetails.equals(other.playerDetails)) { + return false; + } + return true; + } +} diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicketCheckResult.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicketCheckResult.java new file mode 100644 index 000000000000..ded65d27032c --- /dev/null +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicketCheckResult.java @@ -0,0 +1,96 @@ +/** + * 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.hexagonal.domain; + +/** + * + * Represents lottery ticket check result. + * + */ +public class LotteryTicketCheckResult { + + public enum CheckResult { WIN_PRIZE, NO_PRIZE, TICKET_NOT_SUBMITTED }; + + private final CheckResult checkResult; + private final int prizeAmount; + + /** + * Constructor. + */ + public LotteryTicketCheckResult(CheckResult result) { + checkResult = result; + prizeAmount = 0; + } + + /** + * Constructor. + */ + public LotteryTicketCheckResult(CheckResult result, int amount) { + checkResult = result; + prizeAmount = amount; + } + + /** + * @return check result + */ + public CheckResult getResult() { + return checkResult; + } + + /** + * @return prize amount + */ + public int getPrizeAmount() { + return prizeAmount; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((checkResult == null) ? 0 : checkResult.hashCode()); + result = prime * result + prizeAmount; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + LotteryTicketCheckResult other = (LotteryTicketCheckResult) obj; + if (checkResult != other.checkResult) { + return false; + } + if (prizeAmount != other.prizeAmount) { + return false; + } + return true; + } +} diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicketId.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicketId.java new file mode 100644 index 000000000000..2f8a59bbaa46 --- /dev/null +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicketId.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.hexagonal.domain; + +/** + * Lottery ticked id + */ +public class LotteryTicketId { + + private static volatile int numAllocated; + private final int id; + + public LotteryTicketId() { + this.id = numAllocated + 1; + numAllocated++; + } + + public LotteryTicketId(int id) { + this.id = id; + } + + public int getId() { + return id; + } + + @Override + public String toString() { + return String.format("%d", id); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + LotteryTicketId that = (LotteryTicketId) o; + + return id == that.id; + + } + + @Override + public int hashCode() { + return id; + } +} diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryUtils.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryUtils.java new file mode 100644 index 000000000000..dc34c7f7da82 --- /dev/null +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryUtils.java @@ -0,0 +1,53 @@ +/** + * 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.hexagonal.domain; + +import com.iluwatar.hexagonal.database.LotteryTicketRepository; + +import java.util.Optional; + +/** + * Lottery utilities + */ +public class LotteryUtils { + + private LotteryUtils() { + } + + /** + * Checks if lottery ticket has won + */ + public static LotteryTicketCheckResult checkTicketForPrize(LotteryTicketRepository repository, LotteryTicketId id, + LotteryNumbers winningNumbers) { + Optional optional = repository.findById(id); + if (optional.isPresent()) { + if (optional.get().getNumbers().equals(winningNumbers)) { + return new LotteryTicketCheckResult(LotteryTicketCheckResult.CheckResult.WIN_PRIZE, 1000); + } else { + return new LotteryTicketCheckResult(LotteryTicketCheckResult.CheckResult.NO_PRIZE); + } + } else { + return new LotteryTicketCheckResult(LotteryTicketCheckResult.CheckResult.TICKET_NOT_SUBMITTED); + } + } +} diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/PlayerDetails.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/PlayerDetails.java new file mode 100644 index 000000000000..7af115a7cee8 --- /dev/null +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/PlayerDetails.java @@ -0,0 +1,118 @@ +/** + * 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.hexagonal.domain; + +/** + * + * Immutable value object containing lottery player details. + * + */ +public class PlayerDetails { + + private final String emailAddress; + private final String bankAccountNumber; + private final String phoneNumber; + + /** + * Constructor. + */ + public PlayerDetails(String email, String bankAccount, String phone) { + emailAddress = email; + bankAccountNumber = bankAccount; + phoneNumber = phone; + } + + /** + * @return email + */ + public String getEmail() { + return emailAddress; + } + + /** + * @return bank account number + */ + public String getBankAccount() { + return bankAccountNumber; + } + + /** + * @return phone number + */ + public String getPhoneNumber() { + return phoneNumber; + } + + @Override + public String toString() { + return "PlayerDetails{" + "emailAddress='" + emailAddress + '\'' + + ", bankAccountNumber='" + bankAccountNumber + '\'' + + ", phoneNumber='" + phoneNumber + '\'' + '}'; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((bankAccountNumber == null) ? 0 : bankAccountNumber.hashCode()); + result = prime * result + ((emailAddress == null) ? 0 : emailAddress.hashCode()); + result = prime * result + ((phoneNumber == null) ? 0 : phoneNumber.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + PlayerDetails other = (PlayerDetails) obj; + if (bankAccountNumber == null) { + if (other.bankAccountNumber != null) { + return false; + } + } else if (!bankAccountNumber.equals(other.bankAccountNumber)) { + return false; + } + if (emailAddress == null) { + if (other.emailAddress != null) { + return false; + } + } else if (!emailAddress.equals(other.emailAddress)) { + return false; + } + if (phoneNumber == null) { + if (other.phoneNumber != null) { + return false; + } + } else if (!phoneNumber.equals(other.phoneNumber)) { + return false; + } + return true; + } +} diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/eventlog/LotteryEventLog.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/eventlog/LotteryEventLog.java new file mode 100644 index 000000000000..e47640e27baa --- /dev/null +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/eventlog/LotteryEventLog.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. + */ +package com.iluwatar.hexagonal.eventlog; + +import com.iluwatar.hexagonal.domain.PlayerDetails; + +/** + * + * Event log for lottery events + * + */ +public interface LotteryEventLog { + + /** + * lottery ticket submitted + */ + void ticketSubmitted(PlayerDetails details); + + /** + * error submitting lottery ticket + */ + void ticketSubmitError(PlayerDetails details); + + /** + * lottery ticket did not win + */ + void ticketDidNotWin(PlayerDetails details); + + /** + * lottery ticket won + */ + void ticketWon(PlayerDetails details, int prizeAmount); + + /** + * error paying the prize + */ + void prizeError(PlayerDetails details, int prizeAmount); + +} diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/eventlog/MongoEventLog.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/eventlog/MongoEventLog.java new file mode 100644 index 000000000000..7f9b4da5e61d --- /dev/null +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/eventlog/MongoEventLog.java @@ -0,0 +1,154 @@ +/** + * 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.hexagonal.eventlog; + +import com.iluwatar.hexagonal.domain.PlayerDetails; +import com.mongodb.MongoClient; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import org.bson.Document; + +/** + * Mongo based event log + */ +public class MongoEventLog implements LotteryEventLog { + + private static final String DEFAULT_DB = "lotteryDB"; + private static final String DEFAULT_EVENTS_COLLECTION = "events"; + + private MongoClient mongoClient; + private MongoDatabase database; + private MongoCollection eventsCollection; + + private StdOutEventLog stdOutEventLog = new StdOutEventLog(); + + /** + * Constructor + */ + public MongoEventLog() { + connect(); + } + + /** + * Constructor accepting parameters + */ + public MongoEventLog(String dbName, String eventsCollectionName) { + connect(dbName, eventsCollectionName); + } + + /** + * Connect to database with default parameters + */ + public void connect() { + connect(DEFAULT_DB, DEFAULT_EVENTS_COLLECTION); + } + + /** + * Connect to database with given parameters + */ + public void connect(String dbName, String eventsCollectionName) { + if (mongoClient != null) { + mongoClient.close(); + } + mongoClient = new MongoClient(System.getProperty("mongo-host"), + Integer.parseInt(System.getProperty("mongo-port"))); + database = mongoClient.getDatabase(dbName); + eventsCollection = database.getCollection(eventsCollectionName); + } + + /** + * @return mongo client + */ + public MongoClient getMongoClient() { + return mongoClient; + } + + /** + * + * @return mongo database + */ + public MongoDatabase getMongoDatabase() { + return database; + } + + /** + * + * @return accounts collection + */ + public MongoCollection getEventsCollection() { + return eventsCollection; + } + + + @Override + public void ticketSubmitted(PlayerDetails details) { + Document document = new Document("email", details.getEmail()); + document.put("phone", details.getPhoneNumber()); + document.put("bank", details.getBankAccount()); + document.put("message", String.format("Lottery ticket was submitted and bank account was charged for 3 credits.")); + eventsCollection.insertOne(document); + stdOutEventLog.ticketSubmitted(details); + } + + @Override + public void ticketSubmitError(PlayerDetails details) { + Document document = new Document("email", details.getEmail()); + document.put("phone", details.getPhoneNumber()); + document.put("bank", details.getBankAccount()); + document.put("message", String.format("Lottery ticket could not be submitted because lack of funds.")); + eventsCollection.insertOne(document); + stdOutEventLog.ticketSubmitError(details); + } + + @Override + public void ticketDidNotWin(PlayerDetails details) { + Document document = new Document("email", details.getEmail()); + document.put("phone", details.getPhoneNumber()); + document.put("bank", details.getBankAccount()); + document.put("message", String.format("Lottery ticket was checked and unfortunately did not win this time.")); + eventsCollection.insertOne(document); + stdOutEventLog.ticketDidNotWin(details); + } + + @Override + public void ticketWon(PlayerDetails details, int prizeAmount) { + Document document = new Document("email", details.getEmail()); + document.put("phone", details.getPhoneNumber()); + document.put("bank", details.getBankAccount()); + document.put("message", String.format("Lottery ticket won! The bank account was deposited with %d credits.", + prizeAmount)); + eventsCollection.insertOne(document); + stdOutEventLog.ticketWon(details, prizeAmount); + } + + @Override + public void prizeError(PlayerDetails details, int prizeAmount) { + Document document = new Document("email", details.getEmail()); + document.put("phone", details.getPhoneNumber()); + document.put("bank", details.getBankAccount()); + document.put("message", String.format("Lottery ticket won! Unfortunately the bank credit transfer of %d failed.", + prizeAmount)); + eventsCollection.insertOne(document); + stdOutEventLog.prizeError(details, prizeAmount); + } +} diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/eventlog/StdOutEventLog.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/eventlog/StdOutEventLog.java new file mode 100644 index 000000000000..4150dd4011f8 --- /dev/null +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/eventlog/StdOutEventLog.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.hexagonal.eventlog; + +import com.iluwatar.hexagonal.domain.PlayerDetails; + +/** + * Standard output event log + */ +public class StdOutEventLog implements LotteryEventLog { + + @Override + public void ticketSubmitted(PlayerDetails details) { + System.out.println(String.format("Lottery ticket for %s was submitted. Bank account %s was charged for 3 credits.", + details.getEmail(), details.getBankAccount())); + } + + @Override + public void ticketDidNotWin(PlayerDetails details) { + System.out.println(String.format("Lottery ticket for %s was checked and unfortunately did not win this time.", + details.getEmail())); + } + + @Override + public void ticketWon(PlayerDetails details, int prizeAmount) { + System.out + .println(String.format("Lottery ticket for %s has won! The bank account %s was deposited with %d credits.", + details.getEmail(), details.getBankAccount(), prizeAmount)); + } + + @Override + public void prizeError(PlayerDetails details, int prizeAmount) { + System.out + .println(String.format("Lottery ticket for %s has won! Unfortunately the bank credit transfer of %d failed.", + details.getEmail(), prizeAmount)); + } + + @Override + public void ticketSubmitError(PlayerDetails details) { + System.out.println( + String.format("Lottery ticket for %s could not be submitted because the credit transfer of 3 credits failed.", + details.getEmail())); + } +} diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/module/LotteryModule.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/module/LotteryModule.java new file mode 100644 index 000000000000..7e784548d7ce --- /dev/null +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/module/LotteryModule.java @@ -0,0 +1,43 @@ +/** + * 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.hexagonal.module; + +import com.google.inject.AbstractModule; +import com.iluwatar.hexagonal.banking.MongoBank; +import com.iluwatar.hexagonal.banking.WireTransfers; +import com.iluwatar.hexagonal.database.LotteryTicketRepository; +import com.iluwatar.hexagonal.database.MongoTicketRepository; +import com.iluwatar.hexagonal.eventlog.LotteryEventLog; +import com.iluwatar.hexagonal.eventlog.MongoEventLog; + +/** + * Guice module for binding production dependencies + */ +public class LotteryModule extends AbstractModule { + @Override + protected void configure() { + bind(LotteryTicketRepository.class).to(MongoTicketRepository.class); + bind(LotteryEventLog.class).to(MongoEventLog.class); + bind(WireTransfers.class).to(MongoBank.class); + } +} diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/module/LotteryTestingModule.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/module/LotteryTestingModule.java new file mode 100644 index 000000000000..2a1ad1155bf6 --- /dev/null +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/module/LotteryTestingModule.java @@ -0,0 +1,43 @@ +/** + * 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.hexagonal.module; + +import com.google.inject.AbstractModule; +import com.iluwatar.hexagonal.banking.InMemoryBank; +import com.iluwatar.hexagonal.banking.WireTransfers; +import com.iluwatar.hexagonal.database.InMemoryTicketRepository; +import com.iluwatar.hexagonal.database.LotteryTicketRepository; +import com.iluwatar.hexagonal.eventlog.LotteryEventLog; +import com.iluwatar.hexagonal.eventlog.StdOutEventLog; + +/** + * Guice module for testing dependencies + */ +public class LotteryTestingModule extends AbstractModule { + @Override + protected void configure() { + bind(LotteryTicketRepository.class).to(InMemoryTicketRepository.class); + bind(LotteryEventLog.class).to(StdOutEventLog.class); + bind(WireTransfers.class).to(InMemoryBank.class); + } +} diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/mongo/MongoConnectionPropertiesLoader.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/mongo/MongoConnectionPropertiesLoader.java new file mode 100644 index 000000000000..a9bb39803809 --- /dev/null +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/mongo/MongoConnectionPropertiesLoader.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.hexagonal.mongo; + +import java.io.FileInputStream; +import java.util.Properties; + +/** + * Mongo connection properties loader + */ +public class MongoConnectionPropertiesLoader { + + private static final String DEFAULT_HOST = "localhost"; + private static final int DEFAULT_PORT = 27017; + + /** + * Try to load connection properties from file. + * Fall back to default connection properties. + */ + public static void load() { + String host = DEFAULT_HOST; + int port = DEFAULT_PORT; + String path = System.getProperty("hexagonal.properties.path"); + Properties properties = new Properties(); + if (path != null) { + try (FileInputStream fin = new FileInputStream(path)) { + properties.load(fin); + host = properties.getProperty("mongo-host"); + port = Integer.parseInt(properties.getProperty("mongo-port")); + } catch (Exception e) { + // error occurred, use default properties + e.printStackTrace(); + } + } + System.setProperty("mongo-host", host); + System.setProperty("mongo-port", String.format("%d", port)); + } +} diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/sampledata/SampleData.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/sampledata/SampleData.java new file mode 100644 index 000000000000..13458d02ac52 --- /dev/null +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/sampledata/SampleData.java @@ -0,0 +1,109 @@ +/** + * 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.hexagonal.sampledata; + +import com.iluwatar.hexagonal.banking.InMemoryBank; +import com.iluwatar.hexagonal.domain.LotteryConstants; +import com.iluwatar.hexagonal.domain.LotteryNumbers; +import com.iluwatar.hexagonal.domain.LotteryService; +import com.iluwatar.hexagonal.domain.LotteryTicket; +import com.iluwatar.hexagonal.domain.LotteryTicketId; +import com.iluwatar.hexagonal.domain.PlayerDetails; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * Utilities for creating sample lottery tickets + */ +public class SampleData { + + private static final List PLAYERS; + + static { + PLAYERS = new ArrayList<>(); + PLAYERS.add(new PlayerDetails("john@google.com", "312-342", "+3242434242")); + PLAYERS.add(new PlayerDetails("mary@google.com", "234-987", "+23452346")); + PLAYERS.add(new PlayerDetails("steve@google.com", "833-836", "+63457543")); + PLAYERS.add(new PlayerDetails("wayne@google.com", "319-826", "+24626")); + PLAYERS.add(new PlayerDetails("johnie@google.com", "983-322", "+3635635")); + PLAYERS.add(new PlayerDetails("andy@google.com", "934-734", "+0898245")); + PLAYERS.add(new PlayerDetails("richard@google.com", "536-738", "+09845325")); + PLAYERS.add(new PlayerDetails("kevin@google.com", "453-936", "+2423532")); + PLAYERS.add(new PlayerDetails("arnold@google.com", "114-988", "+5646346524")); + PLAYERS.add(new PlayerDetails("ian@google.com", "663-765", "+928394235")); + PLAYERS.add(new PlayerDetails("robin@google.com", "334-763", "+35448")); + PLAYERS.add(new PlayerDetails("ted@google.com", "735-964", "+98752345")); + PLAYERS.add(new PlayerDetails("larry@google.com", "734-853", "+043842423")); + PLAYERS.add(new PlayerDetails("calvin@google.com", "334-746", "+73294135")); + PLAYERS.add(new PlayerDetails("jacob@google.com", "444-766", "+358042354")); + PLAYERS.add(new PlayerDetails("edwin@google.com", "895-345", "+9752435")); + PLAYERS.add(new PlayerDetails("mary@google.com", "760-009", "+34203542")); + PLAYERS.add(new PlayerDetails("lolita@google.com", "425-907", "+9872342")); + PLAYERS.add(new PlayerDetails("bruno@google.com", "023-638", "+673824122")); + PLAYERS.add(new PlayerDetails("peter@google.com", "335-886", "+5432503945")); + PLAYERS.add(new PlayerDetails("warren@google.com", "225-946", "+9872341324")); + PLAYERS.add(new PlayerDetails("monica@google.com", "265-748", "+134124")); + PLAYERS.add(new PlayerDetails("ollie@google.com", "190-045", "+34453452")); + PLAYERS.add(new PlayerDetails("yngwie@google.com", "241-465", "+9897641231")); + PLAYERS.add(new PlayerDetails("lars@google.com", "746-936", "+42345298345")); + PLAYERS.add(new PlayerDetails("bobbie@google.com", "946-384", "+79831742")); + PLAYERS.add(new PlayerDetails("tyron@google.com", "310-992", "+0498837412")); + PLAYERS.add(new PlayerDetails("tyrell@google.com", "032-045", "+67834134")); + PLAYERS.add(new PlayerDetails("nadja@google.com", "000-346", "+498723")); + PLAYERS.add(new PlayerDetails("wendy@google.com", "994-989", "+987324454")); + PLAYERS.add(new PlayerDetails("luke@google.com", "546-634", "+987642435")); + PLAYERS.add(new PlayerDetails("bjorn@google.com", "342-874", "+7834325")); + PLAYERS.add(new PlayerDetails("lisa@google.com", "024-653", "+980742154")); + PLAYERS.add(new PlayerDetails("anton@google.com", "834-935", "+876423145")); + PLAYERS.add(new PlayerDetails("bruce@google.com", "284-936", "+09843212345")); + PLAYERS.add(new PlayerDetails("ray@google.com", "843-073", "+678324123")); + PLAYERS.add(new PlayerDetails("ron@google.com", "637-738", "+09842354")); + PLAYERS.add(new PlayerDetails("xavier@google.com", "143-947", "+375245")); + PLAYERS.add(new PlayerDetails("harriet@google.com", "842-404", "+131243252")); + InMemoryBank wireTransfers = new InMemoryBank(); + Random random = new Random(); + for (int i = 0; i < PLAYERS.size(); i++) { + wireTransfers.setFunds(PLAYERS.get(i).getBankAccount(), + random.nextInt(LotteryConstants.PLAYER_MAX_SALDO)); + } + } + + /** + * Inserts lottery tickets into the database based on the sample data + */ + public static void submitTickets(LotteryService lotteryService, int numTickets) { + for (int i = 0; i < numTickets; i++) { + LotteryTicket ticket = new LotteryTicket(new LotteryTicketId(), + getRandomPlayerDetails(), LotteryNumbers.createRandom()); + lotteryService.submitTicket(ticket); + } + } + + private static PlayerDetails getRandomPlayerDetails() { + Random random = new Random(); + int idx = random.nextInt(PLAYERS.size()); + return PLAYERS.get(idx); + } +} diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/service/ConsoleLottery.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/service/ConsoleLottery.java new file mode 100644 index 000000000000..cc13d389d248 --- /dev/null +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/service/ConsoleLottery.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.hexagonal.service; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.iluwatar.hexagonal.banking.WireTransfers; +import com.iluwatar.hexagonal.domain.LotteryNumbers; +import com.iluwatar.hexagonal.domain.LotteryService; +import com.iluwatar.hexagonal.domain.LotteryTicket; +import com.iluwatar.hexagonal.domain.LotteryTicketCheckResult; +import com.iluwatar.hexagonal.domain.LotteryTicketId; +import com.iluwatar.hexagonal.domain.PlayerDetails; +import com.iluwatar.hexagonal.module.LotteryModule; +import com.iluwatar.hexagonal.mongo.MongoConnectionPropertiesLoader; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Scanner; +import java.util.Set; + +/** + * Console interface for lottery players + */ +public class ConsoleLottery { + + + /** + * Program entry point + */ + public static void main(String[] args) { + MongoConnectionPropertiesLoader.load(); + Injector injector = Guice.createInjector(new LotteryModule()); + LotteryService service = injector.getInstance(LotteryService.class); + WireTransfers bank = injector.getInstance(WireTransfers.class); + Scanner scanner = new Scanner(System.in); + boolean exit = false; + while (!exit) { + printMainMenu(); + String cmd = readString(scanner); + if (cmd.equals("1")) { + System.out.println("What is the account number?"); + String account = readString(scanner); + System.out.println(String.format("The account %s has %d credits.", account, bank.getFunds(account))); + } else if (cmd.equals("2")) { + System.out.println("What is the account number?"); + String account = readString(scanner); + System.out.println("How many credits do you want to deposit?"); + String amount = readString(scanner); + bank.setFunds(account, Integer.parseInt(amount)); + System.out.println(String.format("The account %s now has %d credits.", account, bank.getFunds(account))); + } else if (cmd.equals("3")) { + System.out.println("What is your email address?"); + String email = readString(scanner); + System.out.println("What is your bank account number?"); + String account = readString(scanner); + System.out.println("What is your phone number?"); + String phone = readString(scanner); + PlayerDetails details = new PlayerDetails(email, account, phone); + System.out.println("Give 4 comma separated lottery numbers?"); + String numbers = readString(scanner); + try { + String[] parts = numbers.split(","); + Set chosen = new HashSet<>(); + for (int i = 0; i < 4; i++) { + chosen.add(Integer.parseInt(parts[i])); + } + LotteryNumbers lotteryNumbers = LotteryNumbers.create(chosen); + LotteryTicket lotteryTicket = new LotteryTicket(new LotteryTicketId(), details, lotteryNumbers); + Optional id = service.submitTicket(lotteryTicket); + if (id.isPresent()) { + System.out.println("Submitted lottery ticket with id: " + id.get()); + } else { + System.out.println("Failed submitting lottery ticket - please try again."); + } + } catch (Exception e) { + System.out.println("Failed submitting lottery ticket - please try again."); + } + } else if (cmd.equals("4")) { + System.out.println("What is the ID of the lottery ticket?"); + String id = readString(scanner); + System.out.println("Give the 4 comma separated winning numbers?"); + String numbers = readString(scanner); + try { + String[] parts = numbers.split(","); + Set winningNumbers = new HashSet<>(); + for (int i = 0; i < 4; i++) { + winningNumbers.add(Integer.parseInt(parts[i])); + } + LotteryTicketCheckResult result = service.checkTicketForPrize( + new LotteryTicketId(Integer.parseInt(id)), LotteryNumbers.create(winningNumbers)); + if (result.getResult().equals(LotteryTicketCheckResult.CheckResult.WIN_PRIZE)) { + System.out.println("Congratulations! The lottery ticket has won!"); + } else if (result.getResult().equals(LotteryTicketCheckResult.CheckResult.NO_PRIZE)) { + System.out.println("Unfortunately the lottery ticket did not win."); + } else { + System.out.println("Such lottery ticket has not been submitted."); + } + } catch (Exception e) { + System.out.println("Failed checking the lottery ticket - please try again."); + } + } else if (cmd.equals("5")) { + exit = true; + } else { + System.out.println("Unknown command"); + } + } + } + + private static void printMainMenu() { + System.out.println(""); + System.out.println("### Lottery Service Console ###"); + System.out.println("(1) Query lottery account funds"); + System.out.println("(2) Add funds to lottery account"); + System.out.println("(3) Submit ticket"); + System.out.println("(4) Check ticket"); + System.out.println("(5) Exit"); + } + + private static String readString(Scanner scanner) { + System.out.print("> "); + String cmd = scanner.next(); + return cmd; + } +} diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/AppTest.java b/hexagonal/src/test/java/com/iluwatar/hexagonal/AppTest.java new file mode 100644 index 000000000000..8e19cca3514b --- /dev/null +++ b/hexagonal/src/test/java/com/iluwatar/hexagonal/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.hexagonal; + +import org.junit.Test; + +/** + * Unit test for simple App. + */ +public class AppTest { + + @Test + public void testApp() { + String[] args = {}; + App.main(args); + } +} diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/banking/InMemoryBankTest.java b/hexagonal/src/test/java/com/iluwatar/hexagonal/banking/InMemoryBankTest.java new file mode 100644 index 000000000000..c5efda24046a --- /dev/null +++ b/hexagonal/src/test/java/com/iluwatar/hexagonal/banking/InMemoryBankTest.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.hexagonal.banking; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +/** + * + * Tests for banking + * + */ +public class InMemoryBankTest { + + private final WireTransfers bank = new InMemoryBank(); + + @Test + public void testInit() { + assertEquals(bank.getFunds("foo"), 0); + bank.setFunds("foo", 100); + assertEquals(bank.getFunds("foo"), 100); + bank.setFunds("bar", 150); + assertEquals(bank.getFunds("bar"), 150); + assertTrue(bank.transferFunds(50, "bar", "foo")); + assertEquals(bank.getFunds("foo"), 150); + assertEquals(bank.getFunds("bar"), 100); + } +} diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/banking/MongoBankTest.java b/hexagonal/src/test/java/com/iluwatar/hexagonal/banking/MongoBankTest.java new file mode 100644 index 000000000000..ce5c9ff1aeef --- /dev/null +++ b/hexagonal/src/test/java/com/iluwatar/hexagonal/banking/MongoBankTest.java @@ -0,0 +1,69 @@ +/** + * 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.hexagonal.banking; + +import com.iluwatar.hexagonal.mongo.MongoConnectionPropertiesLoader; +import com.mongodb.MongoClient; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Tests for Mongo banking adapter + */ +@Ignore +public class MongoBankTest { + + private static final String TEST_DB = "lotteryDBTest"; + private static final String TEST_ACCOUNTS_COLLECTION = "testAccounts"; + + private MongoBank mongoBank; + + @Before + public void init() { + MongoConnectionPropertiesLoader.load(); + MongoClient mongoClient = new MongoClient(System.getProperty("mongo-host"), + Integer.parseInt(System.getProperty("mongo-port"))); + mongoClient.dropDatabase(TEST_DB); + mongoClient.close(); + mongoBank = new MongoBank(TEST_DB, TEST_ACCOUNTS_COLLECTION); + } + + @Test + public void testSetup() { + assertEquals(0, mongoBank.getAccountsCollection().count()); + } + + @Test + public void testFundTransfers() { + assertEquals(0, mongoBank.getFunds("000-000")); + mongoBank.setFunds("000-000", 10); + assertEquals(10, mongoBank.getFunds("000-000")); + assertEquals(0, mongoBank.getFunds("111-111")); + mongoBank.transferFunds(9, "000-000", "111-111"); + assertEquals(1, mongoBank.getFunds("000-000")); + assertEquals(9, mongoBank.getFunds("111-111")); + } +} diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/database/InMemoryTicketRepositoryTest.java b/hexagonal/src/test/java/com/iluwatar/hexagonal/database/InMemoryTicketRepositoryTest.java new file mode 100644 index 000000000000..d32e594a8f06 --- /dev/null +++ b/hexagonal/src/test/java/com/iluwatar/hexagonal/database/InMemoryTicketRepositoryTest.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.hexagonal.database; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Optional; + +import org.junit.Before; +import org.junit.Test; + +import com.iluwatar.hexagonal.domain.LotteryTicket; +import com.iluwatar.hexagonal.domain.LotteryTicketId; +import com.iluwatar.hexagonal.test.LotteryTestUtils; + +/** + * + * Tests for {@link LotteryTicketRepository} + * + */ +public class InMemoryTicketRepositoryTest { + + private final LotteryTicketRepository repository = new InMemoryTicketRepository(); + + @Before + public void clear() { + repository.deleteAll(); + } + + @Test + public void testCrudOperations() { + LotteryTicketRepository repository = new InMemoryTicketRepository(); + assertEquals(repository.findAll().size(), 0); + LotteryTicket ticket = LotteryTestUtils.createLotteryTicket(); + Optional id = repository.save(ticket); + assertTrue(id.isPresent()); + assertEquals(repository.findAll().size(), 1); + Optional optionalTicket = repository.findById(id.get()); + assertTrue(optionalTicket.isPresent()); + } +} diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/database/MongoTicketRepositoryTest.java b/hexagonal/src/test/java/com/iluwatar/hexagonal/database/MongoTicketRepositoryTest.java new file mode 100644 index 000000000000..e30468f9922f --- /dev/null +++ b/hexagonal/src/test/java/com/iluwatar/hexagonal/database/MongoTicketRepositoryTest.java @@ -0,0 +1,97 @@ +/** + * 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.hexagonal.database; + +import com.iluwatar.hexagonal.domain.LotteryNumbers; +import com.iluwatar.hexagonal.domain.LotteryTicket; +import com.iluwatar.hexagonal.domain.LotteryTicketId; +import com.iluwatar.hexagonal.domain.PlayerDetails; +import com.iluwatar.hexagonal.mongo.MongoConnectionPropertiesLoader; +import com.mongodb.MongoClient; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.Optional; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Tests for Mongo based ticket repository + */ +@Ignore +public class MongoTicketRepositoryTest { + + private static final String TEST_DB = "lotteryTestDB"; + private static final String TEST_TICKETS_COLLECTION = "lotteryTestTickets"; + private static final String TEST_COUNTERS_COLLECTION = "testCounters"; + + private MongoTicketRepository repository; + + @Before + public void init() { + MongoConnectionPropertiesLoader.load(); + MongoClient mongoClient = new MongoClient(System.getProperty("mongo-host"), + Integer.parseInt(System.getProperty("mongo-port"))); + mongoClient.dropDatabase(TEST_DB); + mongoClient.close(); + repository = new MongoTicketRepository(TEST_DB, TEST_TICKETS_COLLECTION, + TEST_COUNTERS_COLLECTION); + } + + @Test + public void testSetup() { + assertTrue(repository.getCountersCollection().count() == 1); + assertTrue(repository.getTicketsCollection().count() == 0); + } + + @Test + public void testNextId() { + assertEquals(1, repository.getNextId()); + assertEquals(2, repository.getNextId()); + assertEquals(3, repository.getNextId()); + } + + @Test + public void testCrudOperations() { + // create new lottery ticket and save it + PlayerDetails details = new PlayerDetails("foo@bar.com", "123-123", "07001234"); + LotteryNumbers random = LotteryNumbers.createRandom(); + LotteryTicket original = new LotteryTicket(new LotteryTicketId(), details, random); + Optional saved = repository.save(original); + assertEquals(1, repository.getTicketsCollection().count()); + assertTrue(saved.isPresent()); + // fetch the saved lottery ticket from database and check its contents + Optional found = repository.findById(saved.get()); + assertTrue(found.isPresent()); + LotteryTicket ticket = found.get(); + assertEquals("foo@bar.com", ticket.getPlayerDetails().getEmail()); + assertEquals("123-123", ticket.getPlayerDetails().getBankAccount()); + assertEquals("07001234", ticket.getPlayerDetails().getPhoneNumber()); + assertEquals(original.getNumbers(), ticket.getNumbers()); + // clear the collection + repository.deleteAll(); + assertEquals(0, repository.getTicketsCollection().count()); + } +} diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryNumbersTest.java b/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryNumbersTest.java new file mode 100644 index 000000000000..4f2751167623 --- /dev/null +++ b/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryNumbersTest.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.hexagonal.domain; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.HashSet; + +import org.junit.Test; + +/** + * + * Unit tests for {@link LotteryNumbers} + * + */ +public class LotteryNumbersTest { + + @Test + public void testGivenNumbers() { + LotteryNumbers numbers = LotteryNumbers.create( + new HashSet<>(Arrays.asList(1, 2, 3, 4))); + assertEquals(numbers.getNumbers().size(), 4); + assertTrue(numbers.getNumbers().contains(1)); + assertTrue(numbers.getNumbers().contains(2)); + assertTrue(numbers.getNumbers().contains(3)); + assertTrue(numbers.getNumbers().contains(4)); + } + + @Test(expected = UnsupportedOperationException.class) + public void testNumbersCantBeModified() { + LotteryNumbers numbers = LotteryNumbers.create( + new HashSet<>(Arrays.asList(1, 2, 3, 4))); + numbers.getNumbers().add(5); + } + + @Test + public void testRandomNumbers() { + LotteryNumbers numbers = LotteryNumbers.createRandom(); + assertEquals(numbers.getNumbers().size(), LotteryNumbers.NUM_NUMBERS); + } + + @Test + public void testEquals() { + LotteryNumbers numbers1 = LotteryNumbers.create( + new HashSet<>(Arrays.asList(1, 2, 3, 4))); + LotteryNumbers numbers2 = LotteryNumbers.create( + new HashSet<>(Arrays.asList(1, 2, 3, 4))); + assertTrue(numbers1.equals(numbers2)); + LotteryNumbers numbers3 = LotteryNumbers.create( + new HashSet<>(Arrays.asList(11, 12, 13, 14))); + assertFalse(numbers1.equals(numbers3)); + } +} diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTest.java b/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTest.java new file mode 100644 index 000000000000..943440f079ae --- /dev/null +++ b/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTest.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.hexagonal.domain; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; + +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.iluwatar.hexagonal.module.LotteryTestingModule; +import org.junit.Before; +import org.junit.Test; + +import com.iluwatar.hexagonal.banking.WireTransfers; +import com.iluwatar.hexagonal.domain.LotteryTicketCheckResult.CheckResult; +import com.iluwatar.hexagonal.test.LotteryTestUtils; + +/** + * + * Test the lottery system + * + */ +public class LotteryTest { + + private Injector injector; + @Inject + private LotteryAdministration administration; + @Inject + private LotteryService service; + @Inject + private WireTransfers wireTransfers; + + public LotteryTest() { + this.injector = Guice.createInjector(new LotteryTestingModule()); + } + + @Before + public void setup() { + injector.injectMembers(this); + // add funds to the test player's bank account + wireTransfers.setFunds("123-12312", 100); + } + + @Test + public void testLottery() { + // admin resets the lottery + administration.resetLottery(); + assertEquals(administration.getAllSubmittedTickets().size(), 0); + + // players submit the lottery tickets + Optional ticket1 = service.submitTicket(LotteryTestUtils.createLotteryTicket("cvt@bbb.com", + "123-12312", "+32425255", new HashSet<>(Arrays.asList(1, 2, 3, 4)))); + assertTrue(ticket1.isPresent()); + Optional ticket2 = service.submitTicket(LotteryTestUtils.createLotteryTicket("ant@bac.com", + "123-12312", "+32423455", new HashSet<>(Arrays.asList(11, 12, 13, 14)))); + assertTrue(ticket2.isPresent()); + Optional ticket3 = service.submitTicket(LotteryTestUtils.createLotteryTicket("arg@boo.com", + "123-12312", "+32421255", new HashSet<>(Arrays.asList(6, 8, 13, 19)))); + assertTrue(ticket3.isPresent()); + assertEquals(administration.getAllSubmittedTickets().size(), 3); + + // perform lottery + LotteryNumbers winningNumbers = administration.performLottery(); + + // cheat a bit for testing sake, use winning numbers to submit another ticket + Optional ticket4 = service.submitTicket(LotteryTestUtils.createLotteryTicket("lucky@orb.com", + "123-12312", "+12421255", winningNumbers.getNumbers())); + assertTrue(ticket4.isPresent()); + assertEquals(administration.getAllSubmittedTickets().size(), 4); + + // check winners + Map tickets = administration.getAllSubmittedTickets(); + for (LotteryTicketId id: tickets.keySet()) { + LotteryTicketCheckResult checkResult = service.checkTicketForPrize(id, winningNumbers); + assertTrue(checkResult.getResult() != CheckResult.TICKET_NOT_SUBMITTED); + if (checkResult.getResult().equals(CheckResult.WIN_PRIZE)) { + assertTrue(checkResult.getPrizeAmount() > 0); + } else if (checkResult.getResult().equals(CheckResult.WIN_PRIZE)) { + assertEquals(checkResult.getPrizeAmount(), 0); + } + } + + // check another ticket that has not been submitted + LotteryTicketCheckResult checkResult = service.checkTicketForPrize(new LotteryTicketId(), winningNumbers); + assertTrue(checkResult.getResult() == CheckResult.TICKET_NOT_SUBMITTED); + assertEquals(checkResult.getPrizeAmount(), 0); + } +} diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketCheckResultTest.java b/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketCheckResultTest.java new file mode 100644 index 000000000000..237ab85d3e5d --- /dev/null +++ b/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketCheckResultTest.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 com.iluwatar.hexagonal.domain; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import org.junit.Test; + +import com.iluwatar.hexagonal.domain.LotteryTicketCheckResult.CheckResult; + +/** + * + * Unit tests for {@link LotteryTicketCheckResult} + * + */ +public class LotteryTicketCheckResultTest { + + @Test + public void testEquals() { + LotteryTicketCheckResult result1 = new LotteryTicketCheckResult(CheckResult.NO_PRIZE); + LotteryTicketCheckResult result2 = new LotteryTicketCheckResult(CheckResult.NO_PRIZE); + assertEquals(result1, result2); + LotteryTicketCheckResult result3 = new LotteryTicketCheckResult(CheckResult.WIN_PRIZE, 300000); + assertFalse(result1.equals(result3)); + } +} diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketIdTest.java b/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketIdTest.java new file mode 100644 index 000000000000..c5e4c1541fbf --- /dev/null +++ b/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketIdTest.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.hexagonal.domain; + +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Tests for lottery ticket id + */ +public class LotteryTicketIdTest { + + @Test + public void testEquals() { + LotteryTicketId ticketId1 = new LotteryTicketId(); + LotteryTicketId ticketId2 = new LotteryTicketId(); + LotteryTicketId ticketId3 = new LotteryTicketId(); + assertFalse(ticketId1.equals(ticketId2)); + assertFalse(ticketId2.equals(ticketId3)); + LotteryTicketId ticketId4 = new LotteryTicketId(ticketId1.getId()); + assertTrue(ticketId1.equals(ticketId4)); + } +} diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketTest.java b/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketTest.java new file mode 100644 index 000000000000..ce1e6b4b06ae --- /dev/null +++ b/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketTest.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.hexagonal.domain; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import java.util.Arrays; +import java.util.HashSet; + +import org.junit.Test; + +public class LotteryTicketTest { + + @Test + public void testEquals() { + PlayerDetails details1 = new PlayerDetails("bob@foo.bar", "1212-121212", "+34332322"); + LotteryNumbers numbers1 = LotteryNumbers.create(new HashSet(Arrays.asList(1, 2, 3, 4))); + LotteryTicket ticket1 = new LotteryTicket(new LotteryTicketId(), details1, numbers1); + PlayerDetails details2 = new PlayerDetails("bob@foo.bar", "1212-121212", "+34332322"); + LotteryNumbers numbers2 = LotteryNumbers.create(new HashSet(Arrays.asList(1, 2, 3, 4))); + LotteryTicket ticket2 = new LotteryTicket(new LotteryTicketId(), details2, numbers2); + assertEquals(ticket1, ticket2); + PlayerDetails details3 = new PlayerDetails("elsa@foo.bar", "1223-121212", "+49332322"); + LotteryNumbers numbers3 = LotteryNumbers.create(new HashSet(Arrays.asList(1, 2, 3, 8))); + LotteryTicket ticket3 = new LotteryTicket(new LotteryTicketId(), details3, numbers3); + assertFalse(ticket1.equals(ticket3)); + } +} diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/PlayerDetailsTest.java b/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/PlayerDetailsTest.java new file mode 100644 index 000000000000..53aa2d9d594f --- /dev/null +++ b/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/PlayerDetailsTest.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.hexagonal.domain; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import org.junit.Test; + +/** + * + * Unit tests for {@link PlayerDetails} + * + */ +public class PlayerDetailsTest { + + @Test + public void testEquals() { + PlayerDetails details1 = new PlayerDetails("tom@foo.bar", "11212-123434", "+12323425"); + PlayerDetails details2 = new PlayerDetails("tom@foo.bar", "11212-123434", "+12323425"); + assertEquals(details1, details2); + PlayerDetails details3 = new PlayerDetails("john@foo.bar", "16412-123439", "+34323432"); + assertFalse(details1.equals(details3)); + } +} diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/eventlog/MongoEventLogTest.java b/hexagonal/src/test/java/com/iluwatar/hexagonal/eventlog/MongoEventLogTest.java new file mode 100644 index 000000000000..d02694826f4b --- /dev/null +++ b/hexagonal/src/test/java/com/iluwatar/hexagonal/eventlog/MongoEventLogTest.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.hexagonal.eventlog; + +import com.iluwatar.hexagonal.domain.PlayerDetails; +import com.iluwatar.hexagonal.mongo.MongoConnectionPropertiesLoader; +import com.mongodb.MongoClient; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Tests for Mongo event log + */ +@Ignore +public class MongoEventLogTest { + + private static final String TEST_DB = "lotteryDBTest"; + private static final String TEST_EVENTS_COLLECTION = "testEvents"; + + private MongoEventLog mongoEventLog; + + @Before + public void init() { + MongoConnectionPropertiesLoader.load(); + MongoClient mongoClient = new MongoClient(System.getProperty("mongo-host"), + Integer.parseInt(System.getProperty("mongo-port"))); + mongoClient.dropDatabase(TEST_DB); + mongoClient.close(); + mongoEventLog = new MongoEventLog(TEST_DB, TEST_EVENTS_COLLECTION); + } + + @Test + public void testSetup() { + assertEquals(0, mongoEventLog.getEventsCollection().count()); + } + + @Test + public void testFundTransfers() { + PlayerDetails playerDetails = new PlayerDetails("john@wayne.com", "000-000", "03432534543"); + mongoEventLog.prizeError(playerDetails, 1000); + assertEquals(1, mongoEventLog.getEventsCollection().count()); + mongoEventLog.prizeError(playerDetails, 1000); + assertEquals(2, mongoEventLog.getEventsCollection().count()); + mongoEventLog.ticketDidNotWin(playerDetails); + assertEquals(3, mongoEventLog.getEventsCollection().count()); + mongoEventLog.ticketDidNotWin(playerDetails); + assertEquals(4, mongoEventLog.getEventsCollection().count()); + mongoEventLog.ticketSubmitError(playerDetails); + assertEquals(5, mongoEventLog.getEventsCollection().count()); + mongoEventLog.ticketSubmitError(playerDetails); + assertEquals(6, mongoEventLog.getEventsCollection().count()); + mongoEventLog.ticketSubmitted(playerDetails); + assertEquals(7, mongoEventLog.getEventsCollection().count()); + mongoEventLog.ticketSubmitted(playerDetails); + assertEquals(8, mongoEventLog.getEventsCollection().count()); + mongoEventLog.ticketWon(playerDetails, 1000); + assertEquals(9, mongoEventLog.getEventsCollection().count()); + mongoEventLog.ticketWon(playerDetails, 1000); + assertEquals(10, mongoEventLog.getEventsCollection().count()); + } +} diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/test/LotteryTestUtils.java b/hexagonal/src/test/java/com/iluwatar/hexagonal/test/LotteryTestUtils.java new file mode 100644 index 000000000000..ae15cd3d7e43 --- /dev/null +++ b/hexagonal/src/test/java/com/iluwatar/hexagonal/test/LotteryTestUtils.java @@ -0,0 +1,57 @@ +/** + * 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.hexagonal.test; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import com.iluwatar.hexagonal.domain.LotteryNumbers; +import com.iluwatar.hexagonal.domain.LotteryTicket; +import com.iluwatar.hexagonal.domain.LotteryTicketId; +import com.iluwatar.hexagonal.domain.PlayerDetails; + +/** + * + * Utilities for lottery tests + * + */ +public class LotteryTestUtils { + + /** + * @return lottery ticket + */ + public static LotteryTicket createLotteryTicket() { + return createLotteryTicket("foo@bar.com", "12231-213132", "+99324554", new HashSet<>(Arrays.asList(1, 2, 3, 4))); + } + + /** + * @return lottery ticket + */ + public static LotteryTicket createLotteryTicket(String email, String account, String phone, + Set givenNumbers) { + PlayerDetails details = new PlayerDetails(email, account, phone); + LotteryNumbers numbers = LotteryNumbers.create(givenNumbers); + return new LotteryTicket(new LotteryTicketId(), details, numbers); + } +} diff --git a/intercepting-filter/README.md b/intercepting-filter/README.md index 327f091b1e93..4981299ad27b 100644 --- a/intercepting-filter/README.md +++ b/intercepting-filter/README.md @@ -3,6 +3,7 @@ layout: pattern title: Intercepting Filter folder: intercepting-filter permalink: /patterns/intercepting-filter/ +pumlid: RSfB3i8m303Hgy014k-vZN5DQkIuaJ_q-fGzkz7JtCL8Q-DolUsPAnu0ZcSVadizAzZfi6JBJiS4qJenqU6D7smRXmnh2pFPBM1YN05o_KwyKcoqb-ZFEEcVz_BPLqtz0W00 categories: Behavioral tags: - Java @@ -24,6 +25,7 @@ Use the Intercepting Filter pattern when ## Real world examples +* [javax.servlet.FilterChain](https://tomcat.apache.org/tomcat-8.0-doc/servletapi/javax/servlet/FilterChain.html) and [javax.servlet.Filter](https://tomcat.apache.org/tomcat-8.0-doc/servletapi/javax/servlet/Filter.html) * [Struts 2 - Interceptors](https://struts.apache.org/docs/interceptors.html) ## Credits diff --git a/intercepting-filter/etc/intercepting-filter.urm.puml b/intercepting-filter/etc/intercepting-filter.urm.puml new file mode 100644 index 000000000000..e3616c3abf64 --- /dev/null +++ b/intercepting-filter/etc/intercepting-filter.urm.puml @@ -0,0 +1,88 @@ +@startuml +package com.iluwatar.intercepting.filter { + class DepositFilter { + + DepositFilter() + + execute(order : Order) : String + } + class AddressFilter { + + AddressFilter() + + execute(order : Order) : String + } + class App { + + App() + + main(args : String[]) {static} + } + abstract class AbstractFilter { + - next : Filter + + AbstractFilter() + + AbstractFilter(next : Filter) + + execute(order : Order) : String + + getLast() : Filter + + getNext() : Filter + + setNext(filter : Filter) + } + class Order { + - address : String + - contactNumber : String + - depositNumber : String + - name : String + - order : String + + Order() + + Order(name : String, contactNumber : String, address : String, depositNumber : String, order : String) + + getAddress() : String + + getContactNumber() : String + + getDepositNumber() : String + + getName() : String + + getOrder() : String + + setAddress(address : String) + + setContactNumber(contactNumber : String) + + setDepositNumber(depositNumber : String) + + setName(name : String) + + setOrder(order : String) + } + class FilterManager { + - filterChain : FilterChain + + FilterManager() + + addFilter(filter : Filter) + + filterRequest(order : Order) : String + } + class NameFilter { + + NameFilter() + + execute(order : Order) : String + } + class ContactFilter { + + ContactFilter() + + execute(order : Order) : String + } + interface Filter { + + execute(Order) : String {abstract} + + getLast() : Filter {abstract} + + getNext() : Filter {abstract} + + setNext(Filter) {abstract} + } + ~class DListener { + ~ DListener() + + actionPerformed(e : ActionEvent) + } + class OrderFilter { + + OrderFilter() + + execute(order : Order) : String + } + class FilterChain { + - chain : Filter + + FilterChain() + + addFilter(filter : Filter) + + execute(order : Order) : String + } +} +AbstractFilter --> "-next" Filter +DListener --+ Target +FilterChain --> "-chain" Filter +FilterManager --> "-filterChain" FilterChain +DepositFilter --|> AbstractFilter +AddressFilter --|> AbstractFilter +AbstractFilter ..|> Filter +NameFilter --|> AbstractFilter +ContactFilter --|> AbstractFilter +OrderFilter --|> AbstractFilter +@enduml \ No newline at end of file diff --git a/intercepting-filter/pom.xml b/intercepting-filter/pom.xml index 6ef1e25bc43a..487ac1c14698 100644 --- a/intercepting-filter/pom.xml +++ b/intercepting-filter/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT intercepting-filter diff --git a/interpreter/README.md b/interpreter/README.md index 87c1c47f7892..8600f0300220 100644 --- a/interpreter/README.md +++ b/interpreter/README.md @@ -3,8 +3,9 @@ layout: pattern title: Interpreter folder: interpreter permalink: /patterns/interpreter/ +pumlid: JSf13eCm30NHgz034E-vZGaM62Kcih_BzQ6xxjv8yr6hBJT9RzC1Z5Y8dE-oAuvSCyJhPH13gLSdRNapsEdaBy-RXEus3mR4BQXpl21zVnykFmlgVvVqNaRszW00 categories: Behavioral -tags: +tags: - Java - Gang Of Four - Difficulty-Intermediate @@ -25,6 +26,13 @@ trees. The Interpreter pattern works best when * the grammar is simple. For complex grammars, the class hierarchy for the grammar becomes large and unmanageable. Tools such as parser generators are a better alternative in such cases. They can interpret expressions without building abstract syntax trees, which can save space and possibly time * efficiency is not a critical concern. The most efficient interpreters are usually not implemented by interpreting parse trees directly but by first translating them into another form. For example, regular expressions are often transformed into state machines. But even then, the translator can be implemented by the Interpreter pattern, so the pattern is still applicable +## Real world examples +* [java.util.Pattern](http://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html) +* [java.text.Normalizer](http://docs.oracle.com/javase/8/docs/api/java/text/Normalizer.html) +* All subclasses of [java.text.Format](http://docs.oracle.com/javase/8/docs/api/java/text/Format.html) +* [javax.el.ELResolver](http://docs.oracle.com/javaee/7/api/javax/el/ELResolver.html) + + ## Credits * [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) diff --git a/interpreter/etc/interpreter.urm.puml b/interpreter/etc/interpreter.urm.puml new file mode 100644 index 000000000000..dc0d83bf2fb1 --- /dev/null +++ b/interpreter/etc/interpreter.urm.puml @@ -0,0 +1,50 @@ +@startuml +package com.iluwatar.interpreter { + class NumberExpression { + - number : int + + NumberExpression(number : int) + + NumberExpression(s : String) + + interpret() : int + + toString() : String + } + class MinusExpression { + - leftExpression : Expression + - rightExpression : Expression + + MinusExpression(leftExpression : Expression, rightExpression : Expression) + + interpret() : int + + toString() : String + } + class App { + + App() + + getOperatorInstance(s : String, left : Expression, right : Expression) : Expression {static} + + isOperator(s : String) : boolean {static} + + main(args : String[]) {static} + } + abstract class Expression { + + Expression() + + interpret() : int {abstract} + + toString() : String {abstract} + } + class MultiplyExpression { + - leftExpression : Expression + - rightExpression : Expression + + MultiplyExpression(leftExpression : Expression, rightExpression : Expression) + + interpret() : int + + toString() : String + } + class PlusExpression { + - leftExpression : Expression + - rightExpression : Expression + + PlusExpression(leftExpression : Expression, rightExpression : Expression) + + interpret() : int + + toString() : String + } +} +MultiplyExpression --> "-leftExpression" Expression +MinusExpression --> "-leftExpression" Expression +PlusExpression --> "-leftExpression" Expression +NumberExpression --|> Expression +MinusExpression --|> Expression +MultiplyExpression --|> Expression +PlusExpression --|> Expression +@enduml \ No newline at end of file diff --git a/interpreter/pom.xml b/interpreter/pom.xml index a0003672a5e8..e2c8f4d4dcca 100644 --- a/interpreter/pom.xml +++ b/interpreter/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT interpreter diff --git a/iterator/README.md b/iterator/README.md index d6be7758dda1..61bd8f6f551d 100644 --- a/iterator/README.md +++ b/iterator/README.md @@ -3,8 +3,9 @@ layout: pattern title: Iterator folder: iterator permalink: /patterns/iterator/ +pumlid: FSV13OGm30NHLg00uljsOu85HeaJsTzB-yjfBwCtgrfjUKXwMovWneV8-IcduiezGxmEWnXA7PsqvSDWfvk_l1qIUjes6H2teCxnWlGDOpW9wdzAUYypU_i1 categories: Behavioral -tags: +tags: - Java - Difficulty-Beginner - Gang Of Four @@ -29,6 +30,7 @@ Use the Iterator pattern ## Real world examples * [java.util.Iterator](http://docs.oracle.com/javase/8/docs/api/java/util/Iterator.html) +* [java.util.Enumeration](http://docs.oracle.com/javase/8/docs/api/java/util/Enumeration.html) ## Credits diff --git a/iterator/etc/iterator.urm.puml b/iterator/etc/iterator.urm.puml new file mode 100644 index 000000000000..032cd4c165b4 --- /dev/null +++ b/iterator/etc/iterator.urm.puml @@ -0,0 +1,48 @@ +@startuml +package com.iluwatar.iterator { + class App { + + App() + + main(args : String[]) {static} + } + class Item { + - name : String + - type : ItemType + + Item(type : ItemType, name : String) + + getType() : ItemType + + setType(type : ItemType) + + toString() : String + } + class TreasureChestItemIterator { + - chest : TreasureChest + - idx : int + - type : ItemType + + TreasureChestItemIterator(chest : TreasureChest, type : ItemType) + - findNextIdx() : int + + hasNext() : boolean + + next() : Item + } + class TreasureChest { + - items : List + + TreasureChest() + + getItems() : List + ~ iterator(itemType : ItemType) : ItemIterator + } + interface ItemIterator { + + hasNext() : boolean {abstract} + + next() : Item {abstract} + } + enum ItemType { + + ANY {static} + + POTION {static} + + RING {static} + + WEAPON {static} + + valueOf(name : String) : ItemType {static} + + values() : ItemType[] {static} + } +} +Item --> "-type" ItemType +TreasureChest --> "-items" Item +TreasureChestItemIterator --> "-type" ItemType +TreasureChestItemIterator --> "-chest" TreasureChest +TreasureChestItemIterator ..|> ItemIterator +@enduml \ No newline at end of file diff --git a/iterator/pom.xml b/iterator/pom.xml index dcd70f809f22..6d589b4a7132 100644 --- a/iterator/pom.xml +++ b/iterator/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT iterator diff --git a/layers/README.md b/layers/README.md index 8e8eda36666e..d62c6b6b71e9 100644 --- a/layers/README.md +++ b/layers/README.md @@ -3,6 +3,7 @@ layout: pattern title: Layers folder: layers permalink: /patterns/layers/ +pumlid: BSR13OCm30NGLSe0n7UsCS62L8w9x6yGszD3t-bDpQhc9kdwEO0H2v7pNVQ68zSCyNeQn53gsQbftWns-lB5yoRHTfi70-8Mr3b-8UL7F4XG_otflOpi-W80 categories: Architectural tags: - Java diff --git a/layers/etc/layers.urm.puml b/layers/etc/layers.urm.puml new file mode 100644 index 000000000000..2ec6e142eabd --- /dev/null +++ b/layers/etc/layers.urm.puml @@ -0,0 +1,125 @@ +@startuml +package com.iluwatar.layers { + interface View { + + render() {abstract} + } + class CakeToppingInfo { + + calories : int + + id : Optional + + name : String + + CakeToppingInfo(id : Long, name : String, calories : int) + + CakeToppingInfo(name : String, calories : int) + + toString() : String + } + class CakeLayer { + - cake : Cake + - calories : int + - id : Long + - name : String + + CakeLayer() + + CakeLayer(name : String, calories : int) + + getCake() : Cake + + getCalories() : int + + getId() : Long + + getName() : String + + setCake(cake : Cake) + + setCalories(calories : int) + + setId(id : Long) + + setName(name : String) + + toString() : String + } + class CakeLayerInfo { + + calories : int + + id : Optional + + name : String + + CakeLayerInfo(id : Long, name : String, calories : int) + + CakeLayerInfo(name : String, calories : int) + + toString() : String + } + class CakeViewImpl { + - cakeBakingService : CakeBakingService + + CakeViewImpl(cakeBakingService : CakeBakingService) + + render() + } + class CakeBakingServiceImpl { + - context : AbstractApplicationContext + + CakeBakingServiceImpl() + + bakeNewCake(cakeInfo : CakeInfo) + + getAllCakes() : List + - getAvailableLayerEntities() : List + + getAvailableLayers() : List + - getAvailableToppingEntities() : List + + getAvailableToppings() : List + + saveNewLayer(layerInfo : CakeLayerInfo) + + saveNewTopping(toppingInfo : CakeToppingInfo) + } + class CakeTopping { + - cake : Cake + - calories : int + - id : Long + - name : String + + CakeTopping() + + CakeTopping(name : String, calories : int) + + getCake() : Cake + + getCalories() : int + + getId() : Long + + getName() : String + + setCake(cake : Cake) + + setCalories(calories : int) + + setId(id : Long) + + setName(name : String) + + toString() : String + } + class Cake { + - id : Long + - layers : Set + - topping : CakeTopping + + Cake() + + addLayer(layer : CakeLayer) + + getId() : Long + + getLayers() : Set + + getTopping() : CakeTopping + + setId(id : Long) + + setLayers(layers : Set) + + setTopping(topping : CakeTopping) + + toString() : String + } + interface CakeToppingDao { + } + interface CakeBakingService { + + bakeNewCake(CakeInfo) {abstract} + + getAllCakes() : List {abstract} + + getAvailableLayers() : List {abstract} + + getAvailableToppings() : List {abstract} + + saveNewLayer(CakeLayerInfo) {abstract} + + saveNewTopping(CakeToppingInfo) {abstract} + } + class App { + - cakeBakingService : CakeBakingService {static} + + App() + - initializeData(cakeBakingService : CakeBakingService) {static} + + main(args : String[]) {static} + } + interface CakeDao { + } + interface CakeLayerDao { + } + class CakeInfo { + + cakeLayerInfos : List + + cakeToppingInfo : CakeToppingInfo + + id : Optional + + CakeInfo(cakeToppingInfo : CakeToppingInfo, cakeLayerInfos : List) + + CakeInfo(id : Long, cakeToppingInfo : CakeToppingInfo, cakeLayerInfos : List) + + calculateTotalCalories() : int + + toString() : String + } +} +CakeViewImpl --> "-cakeBakingService" CakeBakingService +CakeInfo --> "-cakeToppingInfo" CakeToppingInfo +CakeInfo --> "-cakeLayerInfos" CakeLayerInfo +App --> "-cakeBakingService" CakeBakingService +CakeLayer --> "-cake" Cake +Cake --> "-topping" CakeTopping +CakeViewImpl ..|> View +CakeBakingServiceImpl ..|> CakeBakingService +@enduml \ No newline at end of file diff --git a/layers/pom.xml b/layers/pom.xml index abd888afaea6..811f5e050f7e 100644 --- a/layers/pom.xml +++ b/layers/pom.xml @@ -30,7 +30,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT com.iluwatar.layers layers diff --git a/lazy-loading/README.md b/lazy-loading/README.md index d40061293531..4b7a580c3f6f 100644 --- a/lazy-loading/README.md +++ b/lazy-loading/README.md @@ -3,6 +3,7 @@ layout: pattern title: Lazy Loading folder: lazy-loading permalink: /patterns/lazy-loading/ +pumlid: LSXB3W8X303Gg-W1e7jlqu66gIc5zED4JwzRTo_lpjeaEwN9xOpO_W0mlEhWEFD89sjBWpHgMnDOyi90WoU-i7Ho7besHf2fmqJ_0GG_xo8BE-i0YlONDMtMdLE- categories: Other tags: - Java diff --git a/lazy-loading/etc/lazy-loading.urm.puml b/lazy-loading/etc/lazy-loading.urm.puml new file mode 100644 index 000000000000..80192160a425 --- /dev/null +++ b/lazy-loading/etc/lazy-loading.urm.puml @@ -0,0 +1,35 @@ +@startuml +package com.iluwatar.lazy.loading { + ~class HeavyFactory { + - heavyInstance : Heavy + ~ HeavyFactory() + + get() : Heavy + } + class App { + + App() + + main(args : String[]) {static} + } + class HolderThreadSafe { + - heavy : Heavy + + HolderThreadSafe() + + getHeavy() : Heavy + } + class Heavy { + + Heavy() + } + class HolderNaive { + - heavy : Heavy + + HolderNaive() + + getHeavy() : Heavy + } + class Java8Holder { + - heavy : Supplier + + Java8Holder() + - createAndCacheHeavy() : Heavy + + getHeavy() : Heavy + } +} +HolderNaive --> "-heavy" Heavy +HolderThreadSafe --> "-heavy" Heavy +HeavyFactory --> "-heavyInstance" Heavy +@enduml \ No newline at end of file diff --git a/lazy-loading/pom.xml b/lazy-loading/pom.xml index 37b321aecd85..d70587395183 100644 --- a/lazy-loading/pom.xml +++ b/lazy-loading/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT lazy-loading diff --git a/mediator/README.md b/mediator/README.md index c7a0478c8c66..084e59256dbe 100644 --- a/mediator/README.md +++ b/mediator/README.md @@ -3,6 +3,7 @@ layout: pattern title: Mediator folder: mediator permalink: /patterns/mediator/ +pumlid: FSV14SCm20J0Lk82BFxf1akCJKOW3JhizfDNVhkRUktP9AE_Bc2kDr7mKqx5bKSkYJeSuYXr66dFXy517xvvRxBqz7qo8E6BZDSFPDAKCO84zP-IOMMczIy0 categories: Behavioral tags: - Java @@ -24,6 +25,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/etc/mediator.urm.puml b/mediator/etc/mediator.urm.puml new file mode 100644 index 000000000000..330e024abb1b --- /dev/null +++ b/mediator/etc/mediator.urm.puml @@ -0,0 +1,68 @@ +@startuml +package com.iluwatar.mediator { + interface Party { + + act(PartyMember, Action) {abstract} + + addMember(PartyMember) {abstract} + } + interface PartyMember { + + act(Action) {abstract} + + joinedParty(Party) {abstract} + + partyAction(Action) {abstract} + } + class Rogue { + + Rogue() + + toString() : String + } + class Hunter { + + Hunter() + + toString() : String + } + class PartyImpl { + - members : List + + PartyImpl() + + act(actor : PartyMember, action : Action) + + addMember(member : PartyMember) + } + class Hobbit { + + Hobbit() + + toString() : String + } + class Wizard { + + Wizard() + + toString() : String + } + abstract class PartyMemberBase { + # party : Party + + PartyMemberBase() + + act(action : Action) + + joinedParty(party : Party) + + partyAction(action : Action) + + toString() : String {abstract} + } + class App { + + App() + + main(args : String[]) {static} + } + enum Action { + + ENEMY {static} + + GOLD {static} + + HUNT {static} + + NONE {static} + + TALE {static} + - description : String + - title : String + + getDescription() : String + + toString() : String + + valueOf(name : String) : Action {static} + + values() : Action[] {static} + } +} +PartyImpl --> "-members" PartyMember +PartyMemberBase --> "-party" Party +Rogue --|> PartyMemberBase +Hunter --|> PartyMemberBase +PartyImpl ..|> Party +Hobbit --|> PartyMemberBase +Wizard --|> PartyMemberBase +PartyMemberBase ..|> PartyMember +@enduml \ No newline at end of file diff --git a/mediator/pom.xml b/mediator/pom.xml index 7f3209479882..e3b227237fbd 100644 --- a/mediator/pom.xml +++ b/mediator/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT mediator diff --git a/memento/README.md b/memento/README.md index 463b5fec0836..f4384932980f 100644 --- a/memento/README.md +++ b/memento/README.md @@ -3,6 +3,7 @@ layout: pattern title: Memento folder: memento permalink: /patterns/memento/ +pumlid: DSgn4OCm30NGLM00h3xR2AC3SvRiaxx2-g59zugtDgiz3qdlomNC-10vF-Lik7BF4A_388PIXrBh-J3OwUOlRuT4EssR38XRa7Ay81Lz_o11_RkaQvcf_GS0 categories: Behavioral tags: - Java diff --git a/memento/etc/memento.urm.puml b/memento/etc/memento.urm.puml new file mode 100644 index 000000000000..316d4047ddbd --- /dev/null +++ b/memento/etc/memento.urm.puml @@ -0,0 +1,48 @@ +@startuml +package com.iluwatar.memento { + -class StarMementoInternal { + - ageYears : int + - massTons : int + - type : StarType + - StarMementoInternal() + + getAgeYears() : int + + getMassTons() : int + + getType() : StarType + + setAgeYears(ageYears : int) + + setMassTons(massTons : int) + + setType(type : StarType) + } + class App { + + App() + + main(args : String[]) {static} + } + interface StarMemento { + } + class Star { + - ageYears : int + - massTons : int + - type : StarType + + Star(startType : StarType, startAge : int, startMass : int) + ~ getMemento() : StarMemento + ~ setMemento(memento : StarMemento) + + timePasses() + + toString() : String + } + enum StarType { + + DEAD {static} + + RED_GIANT {static} + + SUN {static} + + SUPERNOVA {static} + + UNDEFINED {static} + + WHITE_DWARF {static} + - title : String + + toString() : String + + valueOf(name : String) : StarType {static} + + values() : StarType[] {static} + } +} +StarMementoInternal --> "-type" StarType +Star --> "-type" StarType +StarMementoInternal ..+ Star +StarMementoInternal ..|> StarMemento +@enduml \ No newline at end of file diff --git a/memento/pom.xml b/memento/pom.xml index b7355784c1d2..3dc7e5f52602 100644 --- a/memento/pom.xml +++ b/memento/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT memento diff --git a/message-channel/README.md b/message-channel/README.md index 8aebd01576f4..aa357ac0c390 100644 --- a/message-channel/README.md +++ b/message-channel/README.md @@ -3,6 +3,7 @@ layout: pattern title: Message Channel folder: message-channel permalink: /patterns/message-channel/ +pumlid: NSZB3SCm203GLTe1RExTXX1akm9YyMdMRy-zFRtdCf8wkLmUCtF72y3nxcFbhAE2dIvBjknqAIof6nCTtlZ1TdAiOMrZ9hi5ACOFe1o1WnjDD6C1Jlg_NgvzbyeN categories: Integration tags: - Java diff --git a/message-channel/etc/message-channel.urm.puml b/message-channel/etc/message-channel.urm.puml new file mode 100644 index 000000000000..9e2b24032f1e --- /dev/null +++ b/message-channel/etc/message-channel.urm.puml @@ -0,0 +1,8 @@ +@startuml +package com.iluwatar.message.channel { + class App { + + App() + + main(args : String[]) {static} + } +} +@enduml \ No newline at end of file diff --git a/message-channel/pom.xml b/message-channel/pom.xml index 3afc5b32ea44..7c1825b5e728 100644 --- a/message-channel/pom.xml +++ b/message-channel/pom.xml @@ -30,7 +30,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT message-channel diff --git a/model-view-controller/README.md b/model-view-controller/README.md index bc96f7ab1e01..9907b98bddc2 100644 --- a/model-view-controller/README.md +++ b/model-view-controller/README.md @@ -3,6 +3,7 @@ layout: pattern title: Model-View-Controller folder: model-view-controller permalink: /patterns/model-view-controller/ +pumlid: ROl13SCm201NQGUm-NSRQgE42h258Lw_wR-_qvtkoTOaEwNBuuoOwmNWkEl1SUOx5taR5cHHsr1WoOs13X-yi7HQV5YP645k2nJN3Q2ZavIBQPVVwqFajXJjVwdfMcUgV040 categories: Presentation Tier tags: - Java diff --git a/model-view-controller/etc/model-view-controller.urm.puml b/model-view-controller/etc/model-view-controller.urm.puml new file mode 100644 index 000000000000..ad979fe0e066 --- /dev/null +++ b/model-view-controller/etc/model-view-controller.urm.puml @@ -0,0 +1,69 @@ +@startuml +package com.iluwatar.model.view.controller { + class GiantModel { + - fatigue : Fatigue + - health : Health + - nourishment : Nourishment + ~ GiantModel(health : Health, fatigue : Fatigue, nourishment : Nourishment) + + getFatigue() : Fatigue + + getHealth() : Health + + getNourishment() : Nourishment + + setFatigue(fatigue : Fatigue) + + setHealth(health : Health) + + setNourishment(nourishment : Nourishment) + + toString() : String + } + class GiantController { + - giant : GiantModel + - view : GiantView + + GiantController(giant : GiantModel, view : GiantView) + + getFatigue() : Fatigue + + getHealth() : Health + + getNourishment() : Nourishment + + setFatigue(fatigue : Fatigue) + + setHealth(health : Health) + + setNourishment(nourishment : Nourishment) + + updateView() + } + class GiantView { + + GiantView() + + displayGiant(giant : GiantModel) + } + class App { + + App() + + main(args : String[]) {static} + } + enum Health { + + DEAD {static} + + HEALTHY {static} + + WOUNDED {static} + - title : String + + toString() : String + + valueOf(name : String) : Health {static} + + values() : Health[] {static} + } + enum Nourishment { + + HUNGRY {static} + + SATURATED {static} + + STARVING {static} + - title : String + + toString() : String + + valueOf(name : String) : Nourishment {static} + + values() : Nourishment[] {static} + } + enum Fatigue { + + ALERT {static} + + SLEEPING {static} + + TIRED {static} + - title : String + + toString() : String + + valueOf(name : String) : Fatigue {static} + + values() : Fatigue[] {static} + } +} +GiantModel --> "-nourishment" Nourishment +GiantController --> "-giant" GiantModel +GiantModel --> "-fatigue" Fatigue +GiantModel --> "-health" Health +GiantController --> "-view" GiantView +@enduml \ No newline at end of file diff --git a/model-view-controller/pom.xml b/model-view-controller/pom.xml index 0ae6a6fc614d..38e756313e47 100644 --- a/model-view-controller/pom.xml +++ b/model-view-controller/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT model-view-controller diff --git a/model-view-presenter/README.md b/model-view-presenter/README.md index a3b921ce4f04..04a1fa559521 100644 --- a/model-view-presenter/README.md +++ b/model-view-presenter/README.md @@ -3,6 +3,7 @@ layout: pattern title: Model-View-Presenter folder: model-view-presenter permalink: /patterns/model-view-presenter/ +pumlid: ROlR3SGW3C1MkGu0-RzjKeXQJWcWFChwPO3xispvQBrmL0hbp-q-xGkWkFBL_8upZBICxjGzbo7GE1OwAlpmmLJ9sjNJH7VIRY1e6q169KvFevMcakrtI_BoD-HGoJE4Nm00 categories: Presentation Tier tags: - Java diff --git a/model-view-presenter/etc/model-view-presenter.urm.puml b/model-view-presenter/etc/model-view-presenter.urm.puml new file mode 100644 index 000000000000..0cec653e44c1 --- /dev/null +++ b/model-view-presenter/etc/model-view-presenter.urm.puml @@ -0,0 +1,87 @@ +@startuml +package com.iluwatar.model.view.presenter { + interface FileSelectorView { + + close() {abstract} + + displayData(String) {abstract} + + getFileName() : String {abstract} + + getPresenter() : FileSelectorPresenter {abstract} + + isOpened() : boolean {abstract} + + open() {abstract} + + setFileName(String) {abstract} + + setPresenter(FileSelectorPresenter) {abstract} + + showMessage(String) {abstract} + } + class FileSelectorStub { + - dataDisplayed : boolean + - name : String + - numOfMessageSent : int + - opened : boolean + - presenter : FileSelectorPresenter + + FileSelectorStub() + + close() + + dataDisplayed() : boolean + + displayData(data : String) + + getFileName() : String + + getMessagesSent() : int + + getPresenter() : FileSelectorPresenter + + isOpened() : boolean + + open() + + setFileName(name : String) + + setPresenter(presenter : FileSelectorPresenter) + + showMessage(message : String) + } + class FileLoader { + - fileName : String + - loaded : boolean + + FileLoader() + + fileExists() : boolean + + getFileName() : String + + isLoaded() : boolean + + loadData() : String + + setFileName(fileName : String) + } + class FileSelectorPresenter { + - loader : FileLoader + - view : FileSelectorView + + FileSelectorPresenter(view : FileSelectorView) + + cancelled() + + confirmed() + + fileNameChanged() + + setLoader(loader : FileLoader) + + start() + } + class App { + + App() + + main(args : String[]) {static} + } + class FileSelectorJFrame { + - area : JTextArea + - cancel : JButton + - contents : JLabel + - fileName : String + - info : JLabel + - input : JTextField + - ok : JButton + - panel : JPanel + - presenter : FileSelectorPresenter + - serialVersionUID : long {static} + + FileSelectorJFrame() + + actionPerformed(e : ActionEvent) + + close() + + displayData(data : String) + + getFileName() : String + + getPresenter() : FileSelectorPresenter + + isOpened() : boolean + + open() + + setFileName(name : String) + + setPresenter(presenter : FileSelectorPresenter) + + showMessage(message : String) + } +} +FileSelectorJFrame --> "-presenter" FileSelectorPresenter +FileSelectorStub --> "-presenter" FileSelectorPresenter +FileSelectorPresenter --> "-loader" FileLoader +FileSelectorPresenter --> "-view" FileSelectorView +FileSelectorStub ..|> FileSelectorView +FileSelectorJFrame ..|> FileSelectorView +@enduml \ No newline at end of file diff --git a/model-view-presenter/pom.xml b/model-view-presenter/pom.xml index e0a3c57fbde3..6fb7ab31e969 100644 --- a/model-view-presenter/pom.xml +++ b/model-view-presenter/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT model-view-presenter model-view-presenter diff --git a/monad/README.md b/monad/README.md index ffc67a3541b2..bf6ee58b891e 100644 --- a/monad/README.md +++ b/monad/README.md @@ -3,10 +3,11 @@ layout: pattern title: Monad folder: monad permalink: /patterns/monad/ +pumlid: 9SR13SCm20NGLPe1OkxTXjWeSMMm1Pza_LRgExsjMntP97syBc35cyZvAMV7bKU6U9q6CPGwbVh8Xy5E7xvvRnBzj7qn86v1ol4BwJHk9AZ_bNGjAtLy0G00 categories: Other tags: - Java - - Difficulty-Advanced + - Difficulty-Expert - Functional --- diff --git a/monad/etc/monad.urm.puml b/monad/etc/monad.urm.puml new file mode 100644 index 000000000000..6fe037064dcd --- /dev/null +++ b/monad/etc/monad.urm.puml @@ -0,0 +1,35 @@ +@startuml +package com.iluwatar.monad { + class Validator { + - exceptions : List + - t : T + - Validator(t : T) + + get() : T + + of(t : T) : Validator {static} + + validate(projection : Function, validation : Predicate, message : String) : Validator + + validate(validation : Predicate, message : String) : Validator + } + class App { + + App() + + main(args : String[]) {static} + } + class User { + - age : int + - email : String + - name : String + - sex : Sex + + User(name : String, age : int, sex : Sex, email : String) + + getAge() : int + + getEmail() : String + + getName() : String + + getSex() : Sex + } + enum Sex { + + FEMALE {static} + + MALE {static} + + valueOf(name : String) : Sex {static} + + values() : Sex[] {static} + } +} +User --> "-sex" Sex +@enduml \ No newline at end of file diff --git a/monad/pom.xml b/monad/pom.xml index b737e2174af5..482cf4e4d5be 100644 --- a/monad/pom.xml +++ b/monad/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT monad diff --git a/monostate/README.md b/monostate/README.md index 415d13f0e7f9..8c47b5da4d2e 100644 --- a/monostate/README.md +++ b/monostate/README.md @@ -3,12 +3,16 @@ layout: pattern title: MonoState folder: monostate permalink: /patterns/monostate/ +pumlid: HSV14OGm20NGLjO23FVj1YEZsGaa0nzjVxrvUszfLdlkaju_9p3ZI-HybwFXp2r3l0w364eTIgtdpM2d7r-yxXBji7Ko86v1ol60TDW8C8G4zLr9rp9J-ny0 categories: Creational tags: - Java - Difficulty-Beginner --- +## Also known as +Borg + ## Intent Enforces a behaviour like sharing the same state amongst all instances. diff --git a/monostate/etc/monostate.urm.puml b/monostate/etc/monostate.urm.puml new file mode 100644 index 000000000000..6574bcad1f0e --- /dev/null +++ b/monostate/etc/monostate.urm.puml @@ -0,0 +1,32 @@ +@startuml +package com.iluwatar.monostate { + class App { + + App() + + main(args : String[]) {static} + } + class Request { + + value : String + + Request(value : String) + } + class LoadBalancer { + - id : int {static} + - lastServedId : int {static} + - servers : List {static} + + LoadBalancer() + + addServer(server : Server) + + getLastServedId() : int {static} + + getNoOfServers() : int + + serverRequest(request : Request) + } + class Server { + + host : String + + id : int + + port : int + + Server(host : String, port : int, id : int) + + getHost() : String + + getPort() : int + + serve(request : Request) + } +} +LoadBalancer --> "-servers" Server +@enduml \ No newline at end of file diff --git a/monostate/pom.xml b/monostate/pom.xml index bb246d38c5e5..d42870a17b9a 100644 --- a/monostate/pom.xml +++ b/monostate/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT monostate diff --git a/multiton/README.md b/multiton/README.md index 0462ff0ecc48..a1154e7bb945 100644 --- a/multiton/README.md +++ b/multiton/README.md @@ -3,6 +3,7 @@ layout: pattern title: Multiton folder: multiton permalink: /patterns/multiton/ +pumlid: FST14i8m20NGg-W16lRUXgPCYnD81Zxs-hfozzvJlOywf68yBc3bYoZuRgVYghrIea-7E5gVHZhgPd3Gcp-y7P9w-hOOaF0au_o1h0OKqqdG_saLrbRP-080 categories: Creational tags: - Java diff --git a/multiton/etc/multiton.urm.puml b/multiton/etc/multiton.urm.puml new file mode 100644 index 000000000000..c582a6379ce0 --- /dev/null +++ b/multiton/etc/multiton.urm.puml @@ -0,0 +1,29 @@ +@startuml +package com.iluwatar.multiton { + class App { + + App() + + main(args : String[]) {static} + } + class Nazgul { + - name : NazgulName + - nazguls : Map {static} + - Nazgul(name : NazgulName) + + getInstance(name : NazgulName) : Nazgul {static} + + getName() : NazgulName + } + enum NazgulName { + + ADUNAPHEL {static} + + AKHORAHIL {static} + + DWAR {static} + + HOARMURATH {static} + + JI_INDUR {static} + + KHAMUL {static} + + MURAZOR {static} + + REN {static} + + UVATHA {static} + + valueOf(name : String) : NazgulName {static} + + values() : NazgulName[] {static} + } +} +Nazgul --> "-name" NazgulName +@enduml \ No newline at end of file diff --git a/multiton/pom.xml b/multiton/pom.xml index 2482c056bac8..28e208d0ca97 100644 --- a/multiton/pom.xml +++ b/multiton/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT multiton diff --git a/mute-idiom/README.md b/mute-idiom/README.md index efc09f3069d0..5c3dbf10b9af 100644 --- a/mute-idiom/README.md +++ b/mute-idiom/README.md @@ -3,6 +3,7 @@ layout: pattern title: Mute Idiom folder: mute-idiom permalink: /patterns/mute-idiom/ +pumlid: JSf13iCm20NHgxG7iDdtDjH62PKX5luarq-MtSsJvgtUHdR96AyTcEj357pLJR7dDvT4EnpYgEqmqf4NWuD-V7BfidJpCXcGy4N6wmcoX1Jj-lo2ziUQONMcZHi0 categories: Other tags: - Java @@ -11,7 +12,7 @@ tags: --- ## Intent -Provide a template to supress any exceptions that either are declared but cannot occur or should only be logged; +Provide a template to suppress any exceptions that either are declared but cannot occur or should only be logged; while executing some business logic. The template removes the need to write repeated `try-catch` blocks. @@ -20,7 +21,7 @@ while executing some business logic. The template removes the need to write repe ## Applicability Use this idiom when -* an API declares some exception but can never throw that exception. Eg. ByteArrayOutputStream bulk write method. +* an API declares some exception but can never throw that exception eg. ByteArrayOutputStream bulk write method. * you need to suppress some exception just by logging it, such as closing a resource. ## Credits diff --git a/mute-idiom/etc/mute-idiom.urm.puml b/mute-idiom/etc/mute-idiom.urm.puml new file mode 100644 index 000000000000..c992773f52c1 --- /dev/null +++ b/mute-idiom/etc/mute-idiom.urm.puml @@ -0,0 +1,23 @@ +@startuml +package com.iluwatar.mute { + interface CheckedRunnable { + + run() {abstract} + } + class App { + + App() + - acquireResource() : Resource {static} + - closeResource(resource : Resource) {static} + + main(args : String[]) {static} + - useOfLoggedMute() {static} + - useOfMute() {static} + - utilizeResource(resource : Resource) {static} + } + class Mute { + - Mute() + + loggedMute(runnable : CheckedRunnable) {static} + + mute(runnable : CheckedRunnable) {static} + } + interface Resource { + } +} +@enduml \ No newline at end of file diff --git a/mute-idiom/pom.xml b/mute-idiom/pom.xml index 3bfc3bf8aed4..cae190e868ed 100644 --- a/mute-idiom/pom.xml +++ b/mute-idiom/pom.xml @@ -21,7 +21,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT mute-idiom diff --git a/mutex/README.md b/mutex/README.md new file mode 100644 index 000000000000..78cda9060de9 --- /dev/null +++ b/mutex/README.md @@ -0,0 +1,31 @@ +--- +layout: pattern +title: Mutex +folder: mutex +permalink: /patterns/mutex/ +pumlid: 9SR13OCm30NGLSe0n7UsCS62LB69x6zWV2hrdTxKhFRS9Br_3c34GkHybxtXo3L3l9u6CPHwAhMUDuETldpnl4cqtUR1WBW5ASSlf0bvI53_A-bQHcf_0G00 +categories: Concurrency +tags: + - Java + - Difficulty-Intermediate +--- + +## Also known as +Mutual Exclusion Lock +Binary Semaphore + +## Intent +Create a lock which only allows a single thread to access a resource at any one instant. + +![alt text](./etc/mutex.png "Mutex") + +## Applicability +Use a Mutex when + +* you need to prevent two threads accessing a critical section at the same time +* concurrent access to a resource could lead to a race condition + +## Credits + +* [Lock (computer science)] (http://en.wikipedia.org/wiki/Lock_(computer_science)) +* [Semaphores] (http://tutorials.jenkov.com/java-concurrency/semaphores.html) diff --git a/mutex/etc/mutex.png b/mutex/etc/mutex.png new file mode 100644 index 000000000000..3b7c966f87df Binary files /dev/null and b/mutex/etc/mutex.png differ diff --git a/mutex/etc/mutex.urm.puml b/mutex/etc/mutex.urm.puml new file mode 100644 index 000000000000..68b96d66816a --- /dev/null +++ b/mutex/etc/mutex.urm.puml @@ -0,0 +1,27 @@ +@startuml +package com.iluwatar.mutex { + class Jar { + - beans : int + - lock : Lock + + Jar(beans : int, lock : Lock) + + takeBean() : boolean + } + interface Lock { + + acquire() {abstract} + + release() {abstract} + } + class Mutex { + - owner : Object + + Mutex() + + acquire() + + getOwner() : Object + + release() + } + class App { + + App() + + main(args : String[]) {static} + } +} +Jar --> "-lock" Lock +Mutex ..|> Lock +@enduml \ No newline at end of file diff --git a/mutex/pom.xml b/mutex/pom.xml new file mode 100644 index 000000000000..b53587aa375f --- /dev/null +++ b/mutex/pom.xml @@ -0,0 +1,42 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.14.0-SNAPSHOT + + mutex + + + junit + junit + test + + + diff --git a/mutex/src/main/java/com/iluwatar/mutex/App.java b/mutex/src/main/java/com/iluwatar/mutex/App.java new file mode 100644 index 000000000000..f6494bd5cbc5 --- /dev/null +++ b/mutex/src/main/java/com/iluwatar/mutex/App.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.mutex; + +/** + * A Mutex prevents multiple threads from accessing a resource simultaneously. + *

+ * In this example we have two thieves who are taking beans from a jar. + * Only one thief can take a bean at a time. This is ensured by a Mutex lock + * which must be acquired in order to access the jar. Each thief attempts to + * acquire the lock, take a bean and then release the lock. If the lock has + * already been acquired, the thief will be prevented from continuing (blocked) + * until the lock has been released. The thieves stop taking beans once there + * are no beans left to take. + */ +public class App { + + /** + * main method + */ + public static void main(String[] args) { + Mutex mutex = new Mutex(); + Jar jar = new Jar(1000, mutex); + Thief peter = new Thief("Peter", jar); + Thief john = new Thief("John", jar); + peter.start(); + john.start(); + } + +} diff --git a/mutex/src/main/java/com/iluwatar/mutex/Jar.java b/mutex/src/main/java/com/iluwatar/mutex/Jar.java new file mode 100644 index 000000000000..4fe0a4637065 --- /dev/null +++ b/mutex/src/main/java/com/iluwatar/mutex/Jar.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.mutex; + +/** + * A Jar has a resource of beans which can only be accessed by a single Thief + * (thread) at any one time. A Mutex lock is used to prevent more than one Thief + * taking a bean simultaneously. + */ +public class Jar { + + /** + * The lock which must be acquired to access the beans resource. + */ + private final Lock lock; + + /** + * The resource within the jar. + */ + private int beans; + + public Jar(int beans, Lock lock) { + this.beans = beans; + this.lock = lock; + } + + /** + * Method for a thief to take a bean. + */ + public boolean takeBean() { + boolean success = false; + try { + lock.acquire(); + success = beans > 0; + if (success) { + beans = beans - 1; + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + lock.release(); + } + + return success; + } + +} diff --git a/mutex/src/main/java/com/iluwatar/mutex/Lock.java b/mutex/src/main/java/com/iluwatar/mutex/Lock.java new file mode 100644 index 000000000000..fef67010eca2 --- /dev/null +++ b/mutex/src/main/java/com/iluwatar/mutex/Lock.java @@ -0,0 +1,34 @@ +/** + * 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.mutex; + +/** + * Lock is an interface for a lock which can be acquired and released. + */ +public interface Lock { + + void acquire() throws InterruptedException; + + void release(); + +} diff --git a/mutex/src/main/java/com/iluwatar/mutex/Mutex.java b/mutex/src/main/java/com/iluwatar/mutex/Mutex.java new file mode 100644 index 000000000000..8e08534cdab6 --- /dev/null +++ b/mutex/src/main/java/com/iluwatar/mutex/Mutex.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.mutex; + +/** + * Mutex is an implementation of a mutual exclusion lock. + */ +public class Mutex implements Lock { + + /** + * The current owner of the lock. + */ + private Object owner; + + /** + * Returns the current owner of the Mutex, or null if available + */ + public Object getOwner() { + return owner; + } + + /** + * Method called by a thread to acquire the lock. If the lock has already + * been acquired this will wait until the lock has been released to + * re-attempt the acquire. + */ + @Override + public synchronized void acquire() throws InterruptedException { + while (owner != null) { + wait(); + } + + owner = Thread.currentThread(); + } + + /** + * Method called by a thread to release the lock. + */ + @Override + public synchronized void release() { + if (Thread.currentThread() == owner) { + owner = null; + notify(); + } + } + +} diff --git a/mutex/src/main/java/com/iluwatar/mutex/Thief.java b/mutex/src/main/java/com/iluwatar/mutex/Thief.java new file mode 100644 index 000000000000..d2225876c89a --- /dev/null +++ b/mutex/src/main/java/com/iluwatar/mutex/Thief.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.mutex; + +/** + * Thief is a class which continually tries to acquire a jar and take a bean + * from it. When the jar is empty the thief stops. + */ +public class Thief extends Thread { + + /** + * The name of the thief. + */ + private final String name; + + /** + * The jar + */ + private final Jar jar; + + public Thief(String name, Jar jar) { + this.name = name; + this.jar = jar; + } + + /** + * In the run method the thief repeatedly tries to take a bean until none + * are left. + */ + @Override + public void run() { + int beans = 0; + + while (jar.takeBean()) { + beans = beans + 1; + System.out.println(name + " took a bean."); + } + + System.out.println(name + " took " + beans + " beans."); + } + +} diff --git a/mutex/src/test/java/com/iluwatar/mutex/AppTest.java b/mutex/src/test/java/com/iluwatar/mutex/AppTest.java new file mode 100644 index 000000000000..f3a1235196e3 --- /dev/null +++ b/mutex/src/test/java/com/iluwatar/mutex/AppTest.java @@ -0,0 +1,34 @@ +/** + * 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.mutex; + +import org.junit.Test; +import java.io.IOException; + +public class AppTest{ + @Test + public void test() throws IOException { + String[] args = {}; + App.main(args); + } +} \ No newline at end of file diff --git a/mutex/src/test/java/com/iluwatar/mutex/JarTest.java b/mutex/src/test/java/com/iluwatar/mutex/JarTest.java new file mode 100644 index 000000000000..97101466a16d --- /dev/null +++ b/mutex/src/test/java/com/iluwatar/mutex/JarTest.java @@ -0,0 +1,43 @@ +/** + * 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.mutex; + +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * Test case for taking beans from a Jar + */ +public class JarTest { + + @Test + public void testTakeBeans() { + Mutex mutex = new Mutex(); + Jar jar = new Jar(10, mutex); + for (int i = 0; i < 10; i++) { + assertTrue(jar.takeBean()); + } + assertFalse(jar.takeBean()); + } + +} \ No newline at end of file diff --git a/mutex/src/test/java/com/iluwatar/mutex/MutexTest.java b/mutex/src/test/java/com/iluwatar/mutex/MutexTest.java new file mode 100644 index 000000000000..8b3e82cb4db7 --- /dev/null +++ b/mutex/src/test/java/com/iluwatar/mutex/MutexTest.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 com.iluwatar.mutex; + +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * Test case for acquiring and releasing a Mutex + */ +public class MutexTest { + + @Test + public void acquireReleaseTest() { + Mutex mutex = new Mutex(); + assertNull(mutex.getOwner()); + try { + mutex.acquire(); + assertEquals(mutex.getOwner(), Thread.currentThread()); + } catch (InterruptedException e) { + fail(e.toString()); + } + mutex.release(); + assertNull(mutex.getOwner()); + } + +} \ No newline at end of file diff --git a/naked-objects/README.md b/naked-objects/README.md index eb1c083b14e2..14391dd40622 100644 --- a/naked-objects/README.md +++ b/naked-objects/README.md @@ -3,6 +3,7 @@ layout: pattern title: Naked Objects folder: naked-objects permalink: /patterns/naked-objects/ +pumlid: LSX15i8W30N0g-W187jlaq9igH1uoO_r-BfrDs_kJKkFAc7zTW3B7qJ6LzuRZjZ2nSfKY2ANEQZrk1XiTFARKnLlkwR5W9Ww3VOVIFabDStjb08dGVcVz6mVX4aE6td5w5y0 categories: Architectural tags: - Java @@ -29,4 +30,4 @@ Use the Naked Objects pattern when ## Credits -* [Richard Pawson - Naked Objects](http://downloads.nakedobjects.net/resources/Pawson%20thesis.pdf) +* [Richard Pawson - Naked Objects](https://isis.apache.org/resources/thesis/Pawson-Naked-Objects-thesis.pdf) diff --git a/naked-objects/dom/pom.xml b/naked-objects/dom/pom.xml index be96b2456aee..7e16ca000b8d 100644 --- a/naked-objects/dom/pom.xml +++ b/naked-objects/dom/pom.xml @@ -16,7 +16,7 @@ com.iluwatar naked-objects - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT naked-objects-dom diff --git a/naked-objects/etc/naked-objects-dom.urm.puml b/naked-objects/etc/naked-objects-dom.urm.puml new file mode 100644 index 000000000000..6662a560a683 --- /dev/null +++ b/naked-objects/etc/naked-objects-dom.urm.puml @@ -0,0 +1,39 @@ +@startuml +package domainapp.dom.app.homepage { + class HomePageViewModel { + ~ simpleObjects : SimpleObjects + + HomePageViewModel() + + getObjects() : List + + title() : String + } + class HomePageService { + ~ container : DomainObjectContainer + + HomePageService() + + homePage() : HomePageViewModel + } +} +package domainapp.dom.modules.simple { + class SimpleObjects { + ~ container : DomainObjectContainer + + SimpleObjects() + + create(name : String) : SimpleObject + + findByName(name : String) : List + + listAll() : List + + title() : TranslatableString + } + class SimpleObject { + - container : DomainObjectContainer + - name : String + + SimpleObject() + + compareTo(other : SimpleObject) : int + + default0UpdateName() : String + + getName() : String + + getVersionSequence() : Long + + setName(name : String) + + title() : TranslatableString + + updateName(name : String) : SimpleObject + + validateUpdateName(name : String) : TranslatableString + } +} +HomePageViewModel --> "-simpleObjects" SimpleObjects +@enduml \ No newline at end of file diff --git a/naked-objects/etc/naked-objects-fixture.urm.puml b/naked-objects/etc/naked-objects-fixture.urm.puml new file mode 100644 index 000000000000..21e38d7102ac --- /dev/null +++ b/naked-objects/etc/naked-objects-fixture.urm.puml @@ -0,0 +1,92 @@ +@startuml +package domainapp.dom.app.homepage { + class HomePageService { + ~ container : DomainObjectContainer + + HomePageService() + + homePage() : HomePageViewModel + } + class HomePageViewModel { + ~ simpleObjects : SimpleObjects + + HomePageViewModel() + + getObjects() : List + + title() : String + } +} +package domainapp.dom.modules.simple { + class SimpleObject { + - container : DomainObjectContainer + - dnFieldFlags : byte[] {static} + - dnFieldNames : String[] {static} + - dnFieldTypes : Class[] {static} + # dnFlags : byte + - dnInheritedFieldCount : int {static} + - dnPersistableSuperclass : Class {static} + # dnStateManager : StateManager + - name : String + + SimpleObject() + + ___dn$loadClass(className : String) : Class {static} + - __dnFieldFlagsInit() : byte[] {static} + - __dnFieldNamesInit() : String[] {static} + - __dnFieldTypesInit() : Class[] {static} + # __dnGetInheritedFieldCount() : int {static} + - __dnPersistableSuperclassInit() : Class {static} + + compareTo(other : SimpleObject) : int + + default0UpdateName() : String + # dnCopyField(obj : SimpleObject, index : int) + + dnCopyFields(obj : Object, indices : int[]) + + dnCopyKeyFieldsFromObjectId(fc : ObjectIdFieldConsumer, oid : Object) + # dnCopyKeyFieldsFromObjectId(oid : Object) + + dnCopyKeyFieldsToObjectId(fs : ObjectIdFieldSupplier, oid : Object) + + dnCopyKeyFieldsToObjectId(oid : Object) + + dnGetExecutionContext() : ExecutionContextReference + # dnGetManagedFieldCount() : int {static} + + dnGetObjectId() : Object + + dnGetTransactionalObjectId() : Object + + dnGetVersion() : Object + + dnGetname() : String + + dnIsDeleted() : boolean + + dnIsDetached() : boolean + + dnIsDirty() : boolean + + dnIsNew() : boolean + + dnIsPersistent() : boolean + + dnIsTransactional() : boolean + + dnMakeDirty(fieldName : String) + + dnNewInstance(sm : StateManager) : Persistable + + dnNewInstance(sm : StateManager, obj : Object) : Persistable + + dnNewObjectIdInstance() : Object + + dnNewObjectIdInstance(key : Object) : Object + # dnPreSerialize() + + dnProvideField(index : int) + + dnProvideFields(indices : int[]) + + dnReplaceField(index : int) + + dnReplaceFields(indices : int[]) + + dnReplaceFlags() + + dnReplaceStateManager(sm : StateManager) + + dnSetname(name : String) + - dnSuperClone() : Object + + getName() : String + + getVersionSequence() : Long + + setName(val : String) + + title() : TranslatableString + + updateName(name : String) : SimpleObject + + validateUpdateName(name : String) : TranslatableString + } + class SimpleObjects { + ~ container : DomainObjectContainer + + SimpleObjects() + + create(name : String) : SimpleObject + + findByName(name : String) : List + + listAll() : List + + title() : TranslatableString + } +} +package domainapp.fixture { + class DomainAppFixturesProvider { + + DomainAppFixturesProvider() + + getSpecification() : FixtureScriptsSpecification + } +} +DomainAppFixturesProvider --+ FixtureScripts +DomainAppFixturesProvider --+ FixtureScriptsSpecification +HomePageViewModel --> "-simpleObjects" SimpleObjects +@enduml \ No newline at end of file diff --git a/naked-objects/etc/naked-objects-integtests.urm.puml b/naked-objects/etc/naked-objects-integtests.urm.puml new file mode 100644 index 000000000000..65c44410ae92 --- /dev/null +++ b/naked-objects/etc/naked-objects-integtests.urm.puml @@ -0,0 +1,92 @@ +@startuml +package domainapp.dom.app.homepage { + class HomePageViewModel { + ~ simpleObjects : SimpleObjects + + HomePageViewModel() + + getObjects() : List + + title() : String + } + class HomePageService { + ~ container : DomainObjectContainer + + HomePageService() + + homePage() : HomePageViewModel + } +} +package domainapp.dom.modules.simple { + class SimpleObject { + - container : DomainObjectContainer + - dnFieldFlags : byte[] {static} + - dnFieldNames : String[] {static} + - dnFieldTypes : Class[] {static} + # dnFlags : byte + - dnInheritedFieldCount : int {static} + - dnPersistableSuperclass : Class {static} + # dnStateManager : StateManager + - name : String + + SimpleObject() + + ___dn$loadClass(className : String) : Class {static} + - __dnFieldFlagsInit() : byte[] {static} + - __dnFieldNamesInit() : String[] {static} + - __dnFieldTypesInit() : Class[] {static} + # __dnGetInheritedFieldCount() : int {static} + - __dnPersistableSuperclassInit() : Class {static} + + compareTo(other : SimpleObject) : int + + default0UpdateName() : String + # dnCopyField(obj : SimpleObject, index : int) + + dnCopyFields(obj : Object, indices : int[]) + + dnCopyKeyFieldsFromObjectId(fc : ObjectIdFieldConsumer, oid : Object) + # dnCopyKeyFieldsFromObjectId(oid : Object) + + dnCopyKeyFieldsToObjectId(fs : ObjectIdFieldSupplier, oid : Object) + + dnCopyKeyFieldsToObjectId(oid : Object) + + dnGetExecutionContext() : ExecutionContextReference + # dnGetManagedFieldCount() : int {static} + + dnGetObjectId() : Object + + dnGetTransactionalObjectId() : Object + + dnGetVersion() : Object + + dnGetname() : String + + dnIsDeleted() : boolean + + dnIsDetached() : boolean + + dnIsDirty() : boolean + + dnIsNew() : boolean + + dnIsPersistent() : boolean + + dnIsTransactional() : boolean + + dnMakeDirty(fieldName : String) + + dnNewInstance(sm : StateManager) : Persistable + + dnNewInstance(sm : StateManager, obj : Object) : Persistable + + dnNewObjectIdInstance() : Object + + dnNewObjectIdInstance(key : Object) : Object + # dnPreSerialize() + + dnProvideField(index : int) + + dnProvideFields(indices : int[]) + + dnReplaceField(index : int) + + dnReplaceFields(indices : int[]) + + dnReplaceFlags() + + dnReplaceStateManager(sm : StateManager) + + dnSetname(name : String) + - dnSuperClone() : Object + + getName() : String + + getVersionSequence() : Long + + setName(val : String) + + title() : TranslatableString + + updateName(name : String) : SimpleObject + + validateUpdateName(name : String) : TranslatableString + } + class SimpleObjects { + ~ container : DomainObjectContainer + + SimpleObjects() + + create(name : String) : SimpleObject + + findByName(name : String) : List + + listAll() : List + + title() : TranslatableString + } +} +package domainapp.fixture { + class DomainAppFixturesProvider { + + DomainAppFixturesProvider() + + getSpecification() : FixtureScriptsSpecification + } +} +DomainAppFixturesProvider --+ FixtureScripts +DomainAppFixturesProvider --+ FixtureScriptsSpecification +HomePageViewModel --> "-simpleObjects" SimpleObjects +@enduml \ No newline at end of file diff --git a/naked-objects/fixture/pom.xml b/naked-objects/fixture/pom.xml index 6bbaaaeb16ef..f31b10fe2337 100644 --- a/naked-objects/fixture/pom.xml +++ b/naked-objects/fixture/pom.xml @@ -16,7 +16,7 @@ com.iluwatar naked-objects - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT naked-objects-fixture diff --git a/naked-objects/integtests/pom.xml b/naked-objects/integtests/pom.xml index 68cac3790dcc..547e88268118 100644 --- a/naked-objects/integtests/pom.xml +++ b/naked-objects/integtests/pom.xml @@ -16,7 +16,7 @@ com.iluwatar naked-objects - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT naked-objects-integtests diff --git a/naked-objects/pom.xml b/naked-objects/pom.xml index d34dba9a3953..344a234acbdb 100644 --- a/naked-objects/pom.xml +++ b/naked-objects/pom.xml @@ -15,7 +15,7 @@ java-design-patterns com.iluwatar - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT naked-objects @@ -317,6 +317,23 @@ org.apache.maven.plugins maven-surefire-report-plugin + + com.github.markusmo3.urm + urm-maven-plugin + ${urm.version} + + ${project.basedir}/../etc + + com.iluwatar + domainapp + + + + naked-objects + naked-objects-webapp + + + @@ -350,17 +367,17 @@ ${project.groupId} naked-objects-dom - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT ${project.groupId} naked-objects-fixture - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT ${project.groupId} naked-objects-webapp - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT @@ -387,4 +404,4 @@ integtests webapp - \ No newline at end of file + diff --git a/naked-objects/webapp/pom.xml b/naked-objects/webapp/pom.xml index aa7d60438520..1a729ade9a3c 100644 --- a/naked-objects/webapp/pom.xml +++ b/naked-objects/webapp/pom.xml @@ -16,7 +16,7 @@ com.iluwatar naked-objects - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT naked-objects-webapp diff --git a/null-object/README.md b/null-object/README.md index 0ed28a0af33c..bfaaeac660f5 100644 --- a/null-object/README.md +++ b/null-object/README.md @@ -3,6 +3,7 @@ layout: pattern title: Null Object folder: null-object permalink: /patterns/null-object/ +pumlid: JSV14SCm20J0Lk829Fxf1cF6bWSX3JhYzfDdVhjRSx4yDCDU5p3NcoZugMV3bNik3HaETLGPdPhbm-2WcpzS3btjz38PqF15dTSFv6bMndwhW1Jo_vhHwynkNm00 categories: Behavioral tags: - Java diff --git a/null-object/etc/null-object.urm.puml b/null-object/etc/null-object.urm.puml new file mode 100644 index 000000000000..bd4f8446ea5c --- /dev/null +++ b/null-object/etc/null-object.urm.puml @@ -0,0 +1,40 @@ +@startuml +package com.iluwatar.nullobject { + class App { + + App() + + main(args : String[]) {static} + } + class NodeImpl { + - left : Node + - name : String + - right : Node + + NodeImpl(name : String, left : Node, right : Node) + + getLeft() : Node + + getName() : String + + getRight() : Node + + getTreeSize() : int + + walk() + } + interface Node { + + getLeft() : Node {abstract} + + getName() : String {abstract} + + getRight() : Node {abstract} + + getTreeSize() : int {abstract} + + walk() {abstract} + } + class NullNode { + - instance : NullNode {static} + - NullNode() + + getInstance() : NullNode {static} + + getLeft() : Node + + getName() : String + + getRight() : Node + + getTreeSize() : int + + walk() + } +} +NullNode --> "-instance" NullNode +NodeImpl --> "-left" Node +NodeImpl ..|> Node +NullNode ..|> Node +@enduml \ No newline at end of file diff --git a/null-object/pom.xml b/null-object/pom.xml index b765c6537561..832337772688 100644 --- a/null-object/pom.xml +++ b/null-object/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT null-object diff --git a/object-pool/README.md b/object-pool/README.md index cf36d98809e7..15fee51aa0fc 100644 --- a/object-pool/README.md +++ b/object-pool/README.md @@ -3,6 +3,7 @@ layout: pattern title: Object Pool folder: object-pool permalink: /patterns/object-pool/ +pumlid: JSV94SCm2030Lk829Fxf1cF6bWU1XYDkFtdcjxiD9Qc3o-LrPQvu0pW-_HnvrLx1JgR9cfrimf1wCD7XnW-sWsESsXPcicl0nFW1RB-PiYqp0KxwVo-VVTMKBm00 categories: Creational tags: - Java diff --git a/object-pool/etc/object-pool.urm.puml b/object-pool/etc/object-pool.urm.puml new file mode 100644 index 000000000000..caf0b6d7e90f --- /dev/null +++ b/object-pool/etc/object-pool.urm.puml @@ -0,0 +1,29 @@ +@startuml +package com.iluwatar.object.pool { + class OliphauntPool { + + OliphauntPool() + # create() : Oliphaunt + } + class Oliphaunt { + - counter : int {static} + - id : int + + Oliphaunt() + + getId() : int + + toString() : String + } + abstract class ObjectPool { + - available : HashSet + - inUse : HashSet + + ObjectPool() + + checkIn(instance : T) + + checkOut() : T + # create() : T {abstract} + + toString() : String + } + class App { + + App() + + main(args : String[]) {static} + } +} +OliphauntPool --|> ObjectPool +@enduml \ No newline at end of file diff --git a/object-pool/pom.xml b/object-pool/pom.xml index 6022ef8a7485..927570b66a36 100644 --- a/object-pool/pom.xml +++ b/object-pool/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT object-pool diff --git a/observer/README.md b/observer/README.md index db6320a1da52..2ac98c401694 100644 --- a/observer/README.md +++ b/observer/README.md @@ -3,11 +3,13 @@ layout: pattern title: Observer folder: observer permalink: /patterns/observer/ +pumlid: FSkn4OGm30NHLg00hFow4KO3PcpP8tr1-pYwx6smQz5Suv2mkbp0y1-HyPlEWYlsSB7S5Q98kJSgDLu66ztyy7Q8brEtmO2OEZNs2Uhxl9u9GVv72cjfHAiV categories: Behavioral -tags: +tags: - Java - Difficulty-Beginner - Gang Of Four + - Reactive --- ## Also known as @@ -34,6 +36,8 @@ Use the Observer pattern in any of the following situations ## Real world examples * [java.util.Observer](http://docs.oracle.com/javase/8/docs/api/java/util/Observer.html) +* [java.util.EventListener](http://docs.oracle.com/javase/8/docs/api/java/util/EventListener.html) +* [javax.servlet.http.HttpSessionBindingListener](http://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpSessionBindingListener.html) ## Credits diff --git a/observer/etc/observer.urm.puml b/observer/etc/observer.urm.puml new file mode 100644 index 000000000000..e9a8f7b9b18a --- /dev/null +++ b/observer/etc/observer.urm.puml @@ -0,0 +1,73 @@ +@startuml +package com.iluwatar.observer { + class Orcs { + + Orcs() + + update(currentWeather : WeatherType) + } + interface WeatherObserver { + + update(WeatherType) {abstract} + } + class App { + + App() + + main(args : String[]) {static} + } + class Weather { + - currentWeather : WeatherType + - observers : List + + Weather() + + addObserver(obs : WeatherObserver) + - notifyObservers() + + removeObserver(obs : WeatherObserver) + + timePasses() + } + class Hobbits { + + Hobbits() + + update(currentWeather : WeatherType) + } + enum WeatherType { + + COLD {static} + + RAINY {static} + + SUNNY {static} + + WINDY {static} + + toString() : String + + valueOf(name : String) : WeatherType {static} + + values() : WeatherType[] {static} + } +} +package com.iluwatar.observer.generic { + class GOrcs { + + GOrcs() + + update(weather : GWeather, weatherType : WeatherType) + } + interface Race { + } + class GWeather { + - currentWeather : WeatherType + + GWeather() + + timePasses() + } + abstract class Observable, A> { + # observers : List> + + Observable, A>() + + addObserver(observer : O extends Observer) + + notifyObservers(argument : A) + + removeObserver(observer : O extends Observer) + } + class GHobbits { + + GHobbits() + + update(weather : GWeather, weatherType : WeatherType) + } + interface Observer, O extends Observer, A> { + + update(S extends Observable, A) {abstract} + } +} +Weather --> "-currentWeather" WeatherType +GWeather --> "-currentWeather" WeatherType +Weather --> "-observers" WeatherObserver +GOrcs ..|> Race +Orcs ..|> WeatherObserver +Race --|> Observer +GWeather --|> Observable +GHobbits ..|> Race +Hobbits ..|> WeatherObserver +@enduml \ No newline at end of file diff --git a/observer/pom.xml b/observer/pom.xml index fcc3e88aa229..48909e1aa84c 100644 --- a/observer/pom.xml +++ b/observer/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT observer diff --git a/page-object/README.md b/page-object/README.md new file mode 100644 index 000000000000..2219a077c282 --- /dev/null +++ b/page-object/README.md @@ -0,0 +1,32 @@ +--- +layout: pattern +title: Page Object +folder: page-object +permalink: /patterns/page-object/ +pumlid: JSV14OGW30NGLjO28FVj9iOCua1Wme-sxnxtzjvMJLeS6ju-9p3NbyZvoQNYZ3sMkWo36hACJhN5ms2dYszEXwvQB4q6r6rHv_K3JIwQndwfW1Jo_npUyupUNW00 +categories: Testing +tags: +- Java +- Difficulty-Intermediate +--- + +## Intent + +Page Object encapsulates the UI, hiding the underlying UI widgetry of an application (commonly a web application) and providing an application-specific API to allow the manipulation of UI components required for tests. In doing so, it allows the test class itself to focus on the test logic instead. + + +![alt text](./etc/page-object.png "Page Object") + + +## Applicability + +Use the Page Object pattern when + +* You are writing automated tests for your web application and you want to separate the UI manipulation required for the tests from the actual test logic. +* Make your tests less brittle, and more readable and robust + +## Credits + +* [Martin Fowler - PageObject](http://martinfowler.com/bliki/PageObject.html) +* [Selenium - Page Objects](https://github.com/SeleniumHQ/selenium/wiki/PageObjects) + diff --git a/page-object/etc/page-object.png b/page-object/etc/page-object.png new file mode 100644 index 000000000000..4240b438efbb Binary files /dev/null and b/page-object/etc/page-object.png differ diff --git a/page-object/etc/page-object.ucls b/page-object/etc/page-object.ucls new file mode 100644 index 000000000000..466c542fd238 --- /dev/null +++ b/page-object/etc/page-object.ucls @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/page-object/etc/page-object.urm.puml b/page-object/etc/page-object.urm.puml new file mode 100644 index 000000000000..735cf2889bcb --- /dev/null +++ b/page-object/etc/page-object.urm.puml @@ -0,0 +1,8 @@ +@startuml +package com.iluwatar.pageobject { + class App { + - App() + + main(args : String[]) {static} + } +} +@enduml \ No newline at end of file diff --git a/page-object/pom.xml b/page-object/pom.xml new file mode 100644 index 000000000000..207d04092d98 --- /dev/null +++ b/page-object/pom.xml @@ -0,0 +1,46 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.14.0-SNAPSHOT + + page-object + + + junit + junit + test + + + net.sourceforge.htmlunit + htmlunit + + + diff --git a/page-object/src/main/java/com/iluwatar/pageobject/App.java b/page-object/src/main/java/com/iluwatar/pageobject/App.java new file mode 100644 index 000000000000..235eedd25528 --- /dev/null +++ b/page-object/src/main/java/com/iluwatar/pageobject/App.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.pageobject; + +import java.awt.Desktop; +import java.io.File; +import java.io.IOException; + +/** + * Page Object pattern wraps an UI component with an application specific API allowing you to + * manipulate the UI elements without having to dig around with the underlying UI technology used. This is + * especially useful for testing as it means your tests will be less brittle. Your tests can concentrate on + * the actual test cases where as the manipulation of the UI can be left to the internals of the page object + * itself. + * + *

+ * Due to this reason, it has become very popular within the test automation community. + * In particular, it is very common in that the page object is used to represent the html pages of a + * web application that is under test. This web application is referred to as AUT (Application Under Test). + * A web browser automation tool/framework like Selenium for instance, is then used to drive the automating + * of the browser navigation and user actions journeys through this web application. Your test class would + * therefore only be responsible for particular test cases and page object would be used by the test class + * for UI manipulation required for the tests. + * + *

+ * In this implementation rather than using Selenium, the HtmlUnit library is used as a replacement to + * represent the specific html elements and to drive the browser. The purpose of this example is just to + * provide a simple version that showcase the intentions of this pattern and how this pattern is used + * in order to understand it. + */ +public final class App { + + private App() { + } + + /** + * Application entry point + * + *

+ * The application under development is a web application. Normally you would probably have a + * backend that is probably implemented in an object-oriented language (e.g. Java) that serves + * the frontend which comprises of a series of HTML, CSS, JS etc... + * + *

+ * For illustrations purposes only, a very simple static html app is used here. This main method + * just fires up this simple web app in a default browser. + * + * @param args arguments + */ + public static void main(String[] args) { + + try { + File applicationFile = new File(App.class.getClassLoader().getResource("sample-ui/login.html").getPath()); + + // should work for unix like OS (mac, unix etc...) + if (Desktop.isDesktopSupported()) { + Desktop.getDesktop().open(applicationFile); + + } else { + // java Desktop not supported - above unlikely to work for Windows so try following instead... + Runtime.getRuntime().exec("cmd.exe start " + applicationFile); + } + + } catch (IOException ex) { + ex.printStackTrace(); + } + + } +} diff --git a/page-object/src/main/resources/sample-ui/album-list.html b/page-object/src/main/resources/sample-ui/album-list.html new file mode 100644 index 000000000000..e02e65cf3b5a --- /dev/null +++ b/page-object/src/main/resources/sample-ui/album-list.html @@ -0,0 +1,60 @@ + + + + + + Album List + + + + +

+

My Album Viewer

+
+ +
+
+ + + + + + + + + + + + + + + +
Album TitleAlbum YearAlbum RatingNumber of SongsArtist
212011A11Adele
+
+
+ + + \ No newline at end of file diff --git a/page-object/src/main/resources/sample-ui/album-page.html b/page-object/src/main/resources/sample-ui/album-page.html new file mode 100644 index 000000000000..5bf843be265d --- /dev/null +++ b/page-object/src/main/resources/sample-ui/album-page.html @@ -0,0 +1,74 @@ + + + + + + Album Page + + + +
+

21

+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + +
Title:
Artist:
Year: + +
Rating:
Number of Songs:
+
+
+
+ + + \ No newline at end of file diff --git a/page-object/src/main/resources/sample-ui/css/album-list.css b/page-object/src/main/resources/sample-ui/css/album-list.css new file mode 100644 index 000000000000..61fd0af813cc --- /dev/null +++ b/page-object/src/main/resources/sample-ui/css/album-list.css @@ -0,0 +1,68 @@ +/** + * 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. + */ + +table { + font-size: 16px; + border-collapse: collapse; +} + +th { + background-color: #FFFFFF; + border: 1px solid black; + color: black; + width: 150px; + height: 20px; +} + +td { + border: 1px solid black; + background-color: white; +} + +th, td { + padding: 15px; + text-align: left; +} \ No newline at end of file diff --git a/page-object/src/main/resources/sample-ui/css/style.css b/page-object/src/main/resources/sample-ui/css/style.css new file mode 100644 index 000000000000..691bba77fe78 --- /dev/null +++ b/page-object/src/main/resources/sample-ui/css/style.css @@ -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. + */ + +body { + font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; +} \ No newline at end of file diff --git a/page-object/src/main/resources/sample-ui/login.html b/page-object/src/main/resources/sample-ui/login.html new file mode 100644 index 000000000000..1d77860572f2 --- /dev/null +++ b/page-object/src/main/resources/sample-ui/login.html @@ -0,0 +1,48 @@ + + + + + + Login + + + +
+

Login

+
+ +
+
+ + + + +
Username:
Password:
+ +
+
+ + \ No newline at end of file diff --git a/page-object/src/test/java/com/iluwatar/pageobject/AlbumListPageTest.java b/page-object/src/test/java/com/iluwatar/pageobject/AlbumListPageTest.java new file mode 100644 index 000000000000..b97410036005 --- /dev/null +++ b/page-object/src/test/java/com/iluwatar/pageobject/AlbumListPageTest.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.pageobject; + + +import com.gargoylesoftware.htmlunit.WebClient; +import com.iluwatar.pageobject.pages.AlbumListPage; +import com.iluwatar.pageobject.pages.AlbumPage; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + + +public class AlbumListPageTest { + + private AlbumListPage albumListPage = new AlbumListPage(new WebClient()); + + @Before + public void setUp() { + albumListPage.navigateToPage(); + } + + @Test + public void testSelectAlbum() { + AlbumPage albumPage = albumListPage.selectAlbum("21"); + albumPage.navigateToPage(); + assertTrue(albumPage.isAt()); + } + +} diff --git a/page-object/src/test/java/com/iluwatar/pageobject/AlbumPageTest.java b/page-object/src/test/java/com/iluwatar/pageobject/AlbumPageTest.java new file mode 100644 index 000000000000..01461cc8da41 --- /dev/null +++ b/page-object/src/test/java/com/iluwatar/pageobject/AlbumPageTest.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.pageobject; + +import com.gargoylesoftware.htmlunit.WebClient; +import com.iluwatar.pageobject.pages.AlbumListPage; +import com.iluwatar.pageobject.pages.AlbumPage; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +public class AlbumPageTest { + + private AlbumPage albumPage = new AlbumPage(new WebClient()); + + @Before + public void setUp() { + albumPage.navigateToPage(); + } + + @Test + public void testSaveAlbum() { + + AlbumPage albumPageAfterChanges = albumPage + .changeAlbumTitle("25") + .changeArtist("Adele Laurie Blue Adkins") + .changeAlbumYear(2015) + .changeAlbumRating("B") + .changeNumberOfSongs(20) + .saveChanges(); + + assertTrue(albumPageAfterChanges.isAt()); + + } + + @Test + public void testCancelChanges() { + AlbumListPage albumListPage = albumPage.cancelChanges(); + albumListPage.navigateToPage(); + assertTrue(albumListPage.isAt()); + } + +} diff --git a/page-object/src/test/java/com/iluwatar/pageobject/LoginPageTest.java b/page-object/src/test/java/com/iluwatar/pageobject/LoginPageTest.java new file mode 100644 index 000000000000..659df062933e --- /dev/null +++ b/page-object/src/test/java/com/iluwatar/pageobject/LoginPageTest.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 com.iluwatar.pageobject; + +import com.gargoylesoftware.htmlunit.WebClient; +import com.iluwatar.pageobject.pages.AlbumListPage; +import com.iluwatar.pageobject.pages.LoginPage; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +public class LoginPageTest { + + private LoginPage loginPage = new LoginPage(new WebClient()); + + @Before + public void setUp() { + loginPage.navigateToPage(); + } + + @Test + public void testLogin() { + AlbumListPage albumListPage = loginPage + .enterUsername("admin") + .enterPassword("password") + .login(); + albumListPage.navigateToPage(); + assertTrue(albumListPage.isAt()); + } + +} diff --git a/page-object/src/test/java/com/iluwatar/pageobject/pages/AlbumListPage.java b/page-object/src/test/java/com/iluwatar/pageobject/pages/AlbumListPage.java new file mode 100644 index 000000000000..fb322ff53d0a --- /dev/null +++ b/page-object/src/test/java/com/iluwatar/pageobject/pages/AlbumListPage.java @@ -0,0 +1,96 @@ +/** + * 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.pageobject.pages; + +import com.gargoylesoftware.htmlunit.WebClient; +import com.gargoylesoftware.htmlunit.html.HtmlAnchor; +import com.gargoylesoftware.htmlunit.html.HtmlPage; + +import java.io.IOException; +import java.util.List; + +/** + * Page Object encapsulating the Album List page (album-list.html) + */ +public class AlbumListPage extends Page { + + private static final String ALBUM_LIST_HTML_FILE = "album-list.html"; + private static final String PAGE_URL = "file:" + AUT_PATH + ALBUM_LIST_HTML_FILE; + + private HtmlPage page; + + + /** + * Constructor + */ + public AlbumListPage(WebClient webClient) { + super(webClient); + } + + + /** + * Navigates to the Album List Page + * + * @return {@link AlbumListPage} + */ + public AlbumListPage navigateToPage() { + try { + page = this.webClient.getPage(PAGE_URL); + } catch (IOException e) { + e.printStackTrace(); + } + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isAt() { + return "Album List".equals(page.getTitleText()); + } + + /** + * Selects an album by the given album title + * + * @param albumTitle the title of the album to click + * @return the album page + */ + public AlbumPage selectAlbum(String albumTitle) { + // uses XPath to find list of html anchor tags with the class album in it + List albumLinks = (List) page.getByXPath("//tr[@class='album']//a"); + for (HtmlAnchor anchor : albumLinks) { + if (anchor.getTextContent().equals(albumTitle)) { + try { + anchor.click(); + return new AlbumPage(webClient); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + throw new IllegalArgumentException("No links with the album title: " + albumTitle); + } + + +} diff --git a/page-object/src/test/java/com/iluwatar/pageobject/pages/AlbumPage.java b/page-object/src/test/java/com/iluwatar/pageobject/pages/AlbumPage.java new file mode 100644 index 000000000000..4dffb93dd997 --- /dev/null +++ b/page-object/src/test/java/com/iluwatar/pageobject/pages/AlbumPage.java @@ -0,0 +1,174 @@ +/** + * 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.pageobject.pages; + +import com.gargoylesoftware.htmlunit.WebClient; +import com.gargoylesoftware.htmlunit.html.HtmlNumberInput; +import com.gargoylesoftware.htmlunit.html.HtmlOption; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import com.gargoylesoftware.htmlunit.html.HtmlSelect; +import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput; +import com.gargoylesoftware.htmlunit.html.HtmlTextInput; + +import java.io.IOException; + +/** + * Page Object encapsulating the Album Page (album-page.html) + */ +public class AlbumPage extends Page { + + private static final String ALBUM_PAGE_HTML_FILE = "album-page.html"; + private static final String PAGE_URL = "file:" + AUT_PATH + ALBUM_PAGE_HTML_FILE; + + private HtmlPage page; + + + /** + * Constructor + */ + public AlbumPage(WebClient webClient) { + super(webClient); + } + + + /** + * Navigates to the album page + * + * @return {@link AlbumPage} + */ + public AlbumPage navigateToPage() { + try { + page = this.webClient.getPage(PAGE_URL); + } catch (IOException e) { + e.printStackTrace(); + } + return this; + } + + + /** + * {@inheritDoc} + */ + @Override + public boolean isAt() { + return "Album Page".equals(page.getTitleText()); + } + + + /** + * Sets the album title input text field + * + * @param albumTitle the new album title value to set + * @return {@link AlbumPage} + */ + public AlbumPage changeAlbumTitle(String albumTitle) { + HtmlTextInput albumTitleInputTextField = (HtmlTextInput) page.getElementById("albumTitle"); + albumTitleInputTextField.setText(albumTitle); + return this; + } + + + /** + * Sets the artist input text field + * + * @param artist the new artist value to set + * @return {@link AlbumPage} + */ + public AlbumPage changeArtist(String artist) { + HtmlTextInput artistInputTextField = (HtmlTextInput) page.getElementById("albumArtist"); + artistInputTextField.setText(artist); + return this; + } + + + /** + * Selects the select's option value based on the year value given + * + * @param year the new year value to set + * @return {@link AlbumPage} + */ + public AlbumPage changeAlbumYear(int year) { + HtmlSelect albumYearSelectOption = (HtmlSelect) page.getElementById("albumYear"); + HtmlOption yearOption = albumYearSelectOption.getOptionByValue(Integer.toString(year)); + albumYearSelectOption.setSelectedAttribute(yearOption, true); + return this; + } + + + /** + * Sets the album rating input text field + * + * @param albumRating the new album rating value to set + * @return {@link AlbumPage} + */ + public AlbumPage changeAlbumRating(String albumRating) { + HtmlTextInput albumRatingInputTextField = (HtmlTextInput) page.getElementById("albumRating"); + albumRatingInputTextField.setText(albumRating); + return this; + } + + /** + * Sets the number of songs number input field + * + * @param numberOfSongs the new number of songs value to be set + * @return {@link AlbumPage} + */ + public AlbumPage changeNumberOfSongs(int numberOfSongs) { + HtmlNumberInput numberOfSongsNumberField = (HtmlNumberInput) page.getElementById("numberOfSongs"); + numberOfSongsNumberField.setText(Integer.toString(numberOfSongs)); + return this; + } + + + /** + * Cancel changes made by clicking the cancel button + * + * @return {@link AlbumListPage} + */ + public AlbumListPage cancelChanges() { + HtmlSubmitInput cancelButton = (HtmlSubmitInput) page.getElementById("cancelButton"); + try { + cancelButton.click(); + } catch (IOException e) { + e.printStackTrace(); + } + return new AlbumListPage(webClient); + } + + + /** + * Saves changes made by clicking the save button + * + * @return {@link AlbumPage} + */ + public AlbumPage saveChanges() { + HtmlSubmitInput saveButton = (HtmlSubmitInput) page.getElementById("saveButton"); + try { + saveButton.click(); + } catch (IOException e) { + e.printStackTrace(); + } + return this; + } + +} diff --git a/page-object/src/test/java/com/iluwatar/pageobject/pages/LoginPage.java b/page-object/src/test/java/com/iluwatar/pageobject/pages/LoginPage.java new file mode 100644 index 000000000000..d4bd0850ced1 --- /dev/null +++ b/page-object/src/test/java/com/iluwatar/pageobject/pages/LoginPage.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.pageobject.pages; + +import com.gargoylesoftware.htmlunit.WebClient; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import com.gargoylesoftware.htmlunit.html.HtmlPasswordInput; +import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput; +import com.gargoylesoftware.htmlunit.html.HtmlTextInput; +import java.io.IOException; + +/** + * Page Object encapsulating the Login Page (login.html) + */ +public class LoginPage extends Page { + + private static final String LOGIN_PAGE_HTML_FILE = "login.html"; + private static final String PAGE_URL = "file:" + AUT_PATH + LOGIN_PAGE_HTML_FILE; + + private HtmlPage page; + + /** + * Constructor + * + * @param webClient {@link WebClient} + */ + public LoginPage(WebClient webClient) { + super(webClient); + } + + /** + * Navigates to the Login page + * + * @return {@link LoginPage} + */ + public LoginPage navigateToPage() { + try { + page = this.webClient.getPage(PAGE_URL); + } catch (IOException e) { + e.printStackTrace(); + } + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isAt() { + return "Login".equals(page.getTitleText()); + } + + + /** + * Enters the username into the username input text field + * + * @param username the username to enter + * @return {@link LoginPage} + */ + public LoginPage enterUsername(String username) { + HtmlTextInput usernameInputTextField = (HtmlTextInput) page.getElementById("username"); + usernameInputTextField.setText(username); + return this; + } + + + /** + * Enters the password into the password input password field + * + * @param password the password to enter + * @return {@link LoginPage} + */ + public LoginPage enterPassword(String password) { + HtmlPasswordInput passwordInputPasswordField = (HtmlPasswordInput) page.getElementById("password"); + passwordInputPasswordField.setText(password); + return this; + } + + + /** + * Clicking on the login button to 'login' + * + * @return {@link AlbumListPage} + * - this is the page that user gets navigated to once successfully logged in + */ + public AlbumListPage login() { + HtmlSubmitInput loginButton = (HtmlSubmitInput) page.getElementById("loginButton"); + try { + loginButton.click(); + } catch (IOException e) { + e.printStackTrace(); + } + return new AlbumListPage(webClient); + } + +} diff --git a/page-object/src/test/java/com/iluwatar/pageobject/pages/Page.java b/page-object/src/test/java/com/iluwatar/pageobject/pages/Page.java new file mode 100644 index 000000000000..b0b328e7c378 --- /dev/null +++ b/page-object/src/test/java/com/iluwatar/pageobject/pages/Page.java @@ -0,0 +1,57 @@ +/** + * 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.pageobject.pages; + +import com.gargoylesoftware.htmlunit.WebClient; + +/** + * Encapsulation for a generic 'Page' + */ +public abstract class Page { + + /** + * Application Under Test path + * This directory location is where html web pages are located + */ + public static final String AUT_PATH = "src/main/resources/sample-ui/"; + + protected final WebClient webClient; + + /** + * Constructor + * + * @param webClient {@link WebClient} + */ + public Page(WebClient webClient) { + this.webClient = webClient; + } + + /** + * Checks that the current page is actually the page this page object represents + * + * @return true if so, otherwise false + */ + public abstract boolean isAt(); + + +} diff --git a/poison-pill/README.md b/poison-pill/README.md index eb37c5ada9f1..8f673ad49cf4 100644 --- a/poison-pill/README.md +++ b/poison-pill/README.md @@ -3,10 +3,12 @@ layout: pattern title: Poison Pill folder: poison-pill permalink: /patterns/poison-pill/ +pumlid: JSZ14SCm20NHLf82BExfXiYCJGOX3NpYzkDZRllsgTwjTgcmnmciV145N-rGdFMkbEZJ8OxMvo2rkXWSzE4lRxka7huj1YGyQN3UGMjgpdkh6Gdwlrl5QAk6_G00 categories: Other tags: - Java - Difficulty-Intermediate + - Reactive --- ## Intent diff --git a/poison-pill/etc/poison-pill.urm.puml b/poison-pill/etc/poison-pill.urm.puml new file mode 100644 index 000000000000..f05c40692226 --- /dev/null +++ b/poison-pill/etc/poison-pill.urm.puml @@ -0,0 +1,72 @@ +@startuml +package com.iluwatar.poison.pill { + interface MqPublishPoint { + + put(Message) {abstract} + } + interface Message { + + POISON_PILL : Message {static} + + addHeader(Headers, String) {abstract} + + getBody() : String {abstract} + + getHeader(Headers) : String {abstract} + + getHeaders() : Map {abstract} + + setBody(String) {abstract} + } + class App { + + App() + + main(args : String[]) {static} + } + interface MessageQueue { + } + class Producer { + - isStopped : boolean + - name : String + - queue : MqPublishPoint + + Producer(name : String, queue : MqPublishPoint) + + send(body : String) + + stop() + } + class Consumer { + - name : String + - queue : MqSubscribePoint + + Consumer(name : String, queue : MqSubscribePoint) + + consume() + } + class SimpleMessageQueue { + - queue : BlockingQueue + + SimpleMessageQueue(bound : int) + + put(msg : Message) + + take() : Message + } + interface MqSubscribePoint { + + take() : Message {abstract} + } + class SimpleMessage { + - body : String + - headers : Map + + SimpleMessage() + + addHeader(header : Headers, value : String) + + getBody() : String + + getHeader(header : Headers) : String + + getHeaders() : Map + + setBody(body : String) + } + enum Headers { + + DATE {static} + + SENDER {static} + + valueOf(name : String) : Headers {static} + + values() : Headers[] {static} + } +} +SimpleMessageQueue --> "-queue" Message +Headers ..+ Message +Consumer --> "-queue" MqSubscribePoint +Producer --> "-queue" MqPublishPoint +SimpleMessage --+ Message +Producer --+ Message +Message --> "-POISON_PILL" Message +Consumer --+ Message +MessageQueue --|> MqPublishPoint +MessageQueue --|> MqSubscribePoint +SimpleMessageQueue ..|> MessageQueue +SimpleMessage ..|> Message +@enduml \ No newline at end of file diff --git a/poison-pill/pom.xml b/poison-pill/pom.xml index b76578122e2f..432f1954754a 100644 --- a/poison-pill/pom.xml +++ b/poison-pill/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT poison-pill diff --git a/pom.xml b/pom.xml index 8430bcebefe3..b8440f66483b 100644 --- a/pom.xml +++ b/pom.xml @@ -1,19 +1,15 @@ - - 4.0.0 - - com.iluwatar - java-design-patterns - 1.12.0-SNAPSHOT - pom - - 2014 - - - UTF-8 - 5.0.1.Final - 4.2.4.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 - - - abstract-factory - builder - factory-method - prototype - singleton - adapter - bridge - composite - dao - 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 - factory-kit - feature-toggle - value-object - monad - mute-idiom + + 4.0.0 + com.iluwatar + java-design-patterns + 1.14.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 + 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 + 2.22 + 1.4.1 + 4.0 + 3.3.0 + + + 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 + promise + page-object + event-asynchronous - - - - org.hibernate - hibernate-core - ${hibernate.version} - - - org.hibernate - hibernate-entitymanager - ${hibernate.version} - - - org.springframework - spring-test - ${spring.version} - - - org.springframework.data - spring-data-jpa - ${spring-data.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 - - - + + + + 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 + + + com.google.inject + guice + ${guice.version} + + + org.mongodb + mongo-java-driver + ${mongo-java-driver.version} + + + - - - - - - org.eclipse.m2e - lifecycle-mapping - 1.0.0 - - - - - - org.jacoco - - jacoco-maven-plugin - - - [0.6.2,) - - - prepare-agent - - - - - - - - - - - - + + + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + org.jacoco + + jacoco-maven-plugin + + + [0.6.2,) + + + prepare-agent + + + + + + + + + + com.github.markusmo3.urm + + + urm-maven-plugin + + + [1.4.1,) + + + map + + + + + + + + + + + + - - - - 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 - - - - - prepare-agent - - prepare-agent - - - - + + + + org.apache.maven.plugins + maven-compiler-plugin + ${compiler.version} + + 1.8 + 1.8 + + + + org.jacoco + jacoco-maven-plugin + ${jacoco.version} + + + prepare-agent + + prepare-agent + + + + - - - 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-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.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.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 - - - + com.mycila + license-maven-plugin + 2.11 + +

com/mycila/maven/plugin/license/templates/MIT.txt
+ + Ilkka Seppälä + + true + + + + install-format + install + + format + + + - - com.mycila - license-maven-plugin - 2.11 - -
com/mycila/maven/plugin/license/templates/MIT.txt
- - Ilkka Seppälä - - true -
- - - install-format - install - - format - - - -
- - + + com.github.markusmo3.urm + urm-maven-plugin + ${urm.version} + + + process-classes + + map + + + + + ${project.basedir}/etc + + com.iluwatar + + + + java-design-patterns + + + + + - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.6 - - - + + + + org.apache.maven.plugins + maven-pmd-plugin + 3.6 + + + diff --git a/private-class-data/README.md b/private-class-data/README.md index 981208fa3f0e..061cc9e7713a 100644 --- a/private-class-data/README.md +++ b/private-class-data/README.md @@ -3,6 +3,7 @@ layout: pattern title: Private Class Data folder: private-class-data permalink: /patterns/private-class-data/ +pumlid: RShR3SCm243HLTe1RFwx3S4eeSB4uf6itmpGlwkZ-nOZhS7b-ZeoLtm07E--InwrLR3JQScMdSu9edLZeiCNBso3GtPh2pFPBM1YF07BvSBaHeeHRJm_SD8VxkMphvhw0m00 categories: Other tags: - Java diff --git a/private-class-data/etc/private-class-data.urm.puml b/private-class-data/etc/private-class-data.urm.puml new file mode 100644 index 000000000000..ad35c0bddeb9 --- /dev/null +++ b/private-class-data/etc/private-class-data.urm.puml @@ -0,0 +1,34 @@ +@startuml +package com.iluwatar.privateclassdata { + class StewData { + - numCarrots : int + - numMeat : int + - numPeppers : int + - numPotatoes : int + + StewData(numPotatoes : int, numCarrots : int, numMeat : int, numPeppers : int) + + getNumCarrots() : int + + getNumMeat() : int + + getNumPeppers() : int + + getNumPotatoes() : int + } + class App { + + App() + + main(args : String[]) {static} + } + class Stew { + - numCarrots : int + - numMeat : int + - numPeppers : int + - numPotatoes : int + + Stew(numPotatoes : int, numCarrots : int, numMeat : int, numPeppers : int) + + mix() + + taste() + } + class ImmutableStew { + - data : StewData + + ImmutableStew(numPotatoes : int, numCarrots : int, numMeat : int, numPeppers : int) + + mix() + } +} +ImmutableStew --> "-data" StewData +@enduml \ No newline at end of file diff --git a/private-class-data/pom.xml b/private-class-data/pom.xml index 8771976647e1..1dc545cb162a 100644 --- a/private-class-data/pom.xml +++ b/private-class-data/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT private-class-data diff --git a/producer-consumer/README.md b/producer-consumer/README.md index c666ab12e2c4..b3cb56af12aa 100644 --- a/producer-consumer/README.md +++ b/producer-consumer/README.md @@ -3,11 +3,13 @@ layout: pattern title: Producer Consumer folder: producer-consumer permalink: /patterns/producer-consumer/ +pumlid: PSjB3iCW303HgxG70Ezx6zTO2HKso9_a-c7VtUX9y-vA8nkdZTSPiVm3O7ZNeyUPttGscXgiKMaAz94t1XhyyCBIsFkXPM44cpe8-WvODbiIMzcdfspXe7-jQL9NodW0 categories: Concurrency tags: - Java - Difficulty-Intermediate - I/O + - Reactive --- ## Intent diff --git a/producer-consumer/etc/producer-consumer.urm.puml b/producer-consumer/etc/producer-consumer.urm.puml new file mode 100644 index 000000000000..8b793d75b847 --- /dev/null +++ b/producer-consumer/etc/producer-consumer.urm.puml @@ -0,0 +1,37 @@ +@startuml +package com.iluwatar.producer.consumer { + class Consumer { + - name : String + - queue : ItemQueue + + Consumer(name : String, queue : ItemQueue) + + consume() + } + class Item { + - id : int + - producer : String + + Item(producer : String, id : int) + + getId() : int + + getProducer() : String + } + class App { + + App() + + main(args : String[]) {static} + } + class Producer { + - itemId : int + - name : String + - queue : ItemQueue + + Producer(name : String, queue : ItemQueue) + + produce() + } + class ItemQueue { + - queue : BlockingQueue + + ItemQueue() + + put(item : Item) + + take() : Item + } +} +Consumer --> "-queue" ItemQueue +Producer --> "-queue" ItemQueue +ItemQueue --> "-queue" Item +@enduml \ No newline at end of file diff --git a/producer-consumer/pom.xml b/producer-consumer/pom.xml index c4ea8f1df60d..5ac018f512fc 100644 --- a/producer-consumer/pom.xml +++ b/producer-consumer/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT producer-consumer diff --git a/promise/README.md b/promise/README.md new file mode 100644 index 000000000000..638bb3ef5327 --- /dev/null +++ b/promise/README.md @@ -0,0 +1,46 @@ +--- +layout: pattern +title: Promise +folder: promise +permalink: /patterns/promise/ +categories: Concurrency +tags: + - Java + - Functional + - Reactive + - Difficulty-Intermediate +--- + +## Also known as +CompletableFuture + +## Intent +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. Promises are a way to write async code that still appears as though it is executing +in a synchronous way. + +![alt text](./etc/promise.png "Promise") + +## Applicability +Promise pattern is applicable in concurrent programming when some work needs to be done asynchronously +and: + +* code maintainablity and readability suffers due to callback hell. +* you need to compose promises and need better error handling for asynchronous tasks. +* you want to use functional style of programming. + + +## Real world examples + +* [java.util.concurrent.CompletableFuture](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html) +* [Guava ListenableFuture](https://github.com/google/guava/wiki/ListenableFutureExplained) + +## Related Patterns + * Async Method Invocation + * Callback + +## Credits + +* [You are missing the point to Promises](https://gist.github.com/domenic/3889970) +* [Functional style callbacks using CompleteableFuture](https://www.infoq.com/articles/Functional-Style-Callbacks-Using-CompletableFuture) diff --git a/promise/etc/promise.png b/promise/etc/promise.png new file mode 100644 index 000000000000..cdb43eb6b49a Binary files /dev/null and b/promise/etc/promise.png differ diff --git a/promise/etc/promise.ucls b/promise/etc/promise.ucls new file mode 100644 index 000000000000..79a11e7d5012 --- /dev/null +++ b/promise/etc/promise.ucls @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/promise/etc/promise.urm.puml b/promise/etc/promise.urm.puml new file mode 100644 index 000000000000..cd6e732529fc --- /dev/null +++ b/promise/etc/promise.urm.puml @@ -0,0 +1,77 @@ +@startuml +package com.iluwatar.promise { + ~class PromiseSupport { + - COMPLETED : int {static} + - FAILED : int {static} + - RUNNING : int {static} + - exception : Exception + - lock : Object + - state : int + - value : T + ~ PromiseSupport() + + cancel(mayInterruptIfRunning : boolean) : boolean + ~ fulfill(value : T) + ~ fulfillExceptionally(exception : Exception) + + get() : T + + get(timeout : long, unit : TimeUnit) : T + + isCancelled() : boolean + + isDone() : boolean + } + -class ConsumeAction { + - action : Consumer + - dest : Promise + - src : Promise + - ConsumeAction(src : Promise, dest : Promise, action : Consumer) + + run() + } + class Promise { + - exceptionHandler : Consumer + - fulfillmentAction : Runnable + + Promise() + + fulfill(value : T) + + fulfillExceptionally(exception : Exception) + + fulfillInAsync(task : Callable, executor : Executor) : Promise + - handleException(exception : Exception) + + onError(exceptionHandler : Consumer) : Promise + - postFulfillment() + + thenAccept(action : Consumer) : Promise + + thenApply(func : Function) : Promise + } + class App { + - DEFAULT_URL : String {static} + - executor : ExecutorService + - stopLatch : CountDownLatch + - App() + - calculateLineCount() + - calculateLowestFrequencyChar() + - characterFrequency() : Promise> + - countLines() : Promise + - download(urlString : String) : Promise + - lowestFrequencyChar() : Promise + + main(args : String[]) {static} + - promiseUsage() + - stop() + - taskCompleted() + } + -class TransformAction { + - dest : Promise + - func : Function + - src : Promise + - TransformAction(src : Promise, dest : Promise, func : Function) + + run() + } + class Utility { + + Utility() + + characterFrequency(fileLocation : String) : Map {static} + + countLines(fileLocation : String) : Integer {static} + + downloadFile(urlString : String) : String {static} + + lowestFrequencyChar(charFrequency : Map) : Character {static} + } +} +TransformAction --+ Promise +TransformAction --> "-src" Promise +ConsumeAction --+ Promise +ConsumeAction --> "-src" Promise +Utility --+ Map +Promise --|> PromiseSupport +@enduml \ No newline at end of file diff --git a/promise/pom.xml b/promise/pom.xml new file mode 100644 index 000000000000..30a719f13131 --- /dev/null +++ b/promise/pom.xml @@ -0,0 +1,47 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.14.0-SNAPSHOT + + promise + + + junit + junit + 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 000000000000..672c20bfa969 --- /dev/null +++ b/promise/src/main/java/com/iluwatar/promise/App.java @@ -0,0 +1,176 @@ +/** + * 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.promise; +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 String DEFAULT_URL = "https://raw.githubusercontent.com/iluwatar/java-design-patterns/Promise/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 -> { + System.out.println("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 -> { + System.out.println("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 000000000000..e7e56837bc1a --- /dev/null +++ b/promise/src/main/java/com/iluwatar/promise/Promise.java @@ -0,0 +1,193 @@ +/** + * 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.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 000000000000..048586e23f95 --- /dev/null +++ b/promise/src/main/java/com/iluwatar/promise/PromiseSupport.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.promise; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * 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 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 { + if (state == COMPLETED) { + return value; + } else if (state == FAILED) { + throw new ExecutionException(exception); + } else { + synchronized (lock) { + lock.wait(); + if (state == COMPLETED) { + return value; + } else { + throw new ExecutionException(exception); + } + } + } + } + + @Override + public T get(long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + if (state == COMPLETED) { + return value; + } else if (state == FAILED) { + throw new ExecutionException(exception); + } else { + synchronized (lock) { + lock.wait(unit.toMillis(timeout)); + if (state == COMPLETED) { + return value; + } else if (state == FAILED) { + throw new ExecutionException(exception); + } else { + throw new TimeoutException(); + } + } + } + } +} \ 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 000000000000..d451600a359f --- /dev/null +++ b/promise/src/main/java/com/iluwatar/promise/Utility.java @@ -0,0 +1,123 @@ +/** + * 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.promise; + +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; + +public class Utility { + + /** + * 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 { + System.out.println("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"); + } + System.out.println("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 000000000000..1d1cb061df2a --- /dev/null +++ b/promise/src/test/java/com/iluwatar/promise/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.promise; + +import java.util.concurrent.ExecutionException; + +import org.junit.Test; + +/** + * + * 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 000000000000..45c4c1d368f6 --- /dev/null +++ b/promise/src/test/java/com/iluwatar/promise/PromiseTest.java @@ -0,0 +1,263 @@ +/** + * 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.promise; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +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 org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +/** + * Tests Promise class. + */ +public class PromiseTest { + + private Executor executor; + private Promise promise; + @Rule public ExpectedException exception = ExpectedException.none(); + + @Before + 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/README.md b/property/README.md index 0ac5c7a6c682..c1502f75cfd2 100644 --- a/property/README.md +++ b/property/README.md @@ -3,6 +3,7 @@ layout: pattern title: Property folder: property permalink: /patterns/property/ +pumlid: FSV13OCm30NGLTe1YEziumOBKYMEPN-3s9wUUdlltRJst2Izlmx0OYLolihUSEGdGxnEXIXAdODQpul1Jby-UTaasgwBCI2kGOFZ1pAV9ewR1FMVaZwAvUWF categories: Creational tags: - Java diff --git a/property/etc/property.urm.puml b/property/etc/property.urm.puml new file mode 100644 index 000000000000..6ff0e5c52ed6 --- /dev/null +++ b/property/etc/property.urm.puml @@ -0,0 +1,54 @@ +@startuml +package com.iluwatar.property { + class App { + + App() + + main(args : String[]) {static} + } + interface Prototype { + + get(Stats) : Integer {abstract} + + has(Stats) : boolean {abstract} + + remove(Stats) {abstract} + + set(Stats, Integer) {abstract} + } + class Character { + - name : String + - properties : Map + - prototype : Prototype + - type : Type + + Character() + + Character(name : String, prototype : Character) + + Character(type : Type, prototype : Prototype) + + get(stat : Stats) : Integer + + has(stat : Stats) : boolean + + name() : String + + remove(stat : Stats) + + set(stat : Stats, val : Integer) + + toString() : String + + type() : Type + } + enum Type { + + MAGE {static} + + ROGUE {static} + + WARRIOR {static} + + valueOf(name : String) : Type {static} + + values() : Type[] {static} + } + enum Stats { + + AGILITY {static} + + ARMOR {static} + + ATTACK_POWER {static} + + ENERGY {static} + + INTELLECT {static} + + RAGE {static} + + SPIRIT {static} + + STRENGTH {static} + + valueOf(name : String) : Stats {static} + + values() : Stats[] {static} + } +} +App --+ Character +Character --> "-prototype" Prototype +Character --> "-type" Type +Type ..+ Character +Character ..|> Prototype +@enduml \ No newline at end of file diff --git a/property/pom.xml b/property/pom.xml index c753261d1ad0..af0c9f2886a9 100644 --- a/property/pom.xml +++ b/property/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT property diff --git a/prototype/README.md b/prototype/README.md index 632daca931aa..fe9e17917685 100644 --- a/prototype/README.md +++ b/prototype/README.md @@ -3,6 +3,7 @@ layout: pattern title: Prototype folder: prototype permalink: /patterns/prototype/ +pumlid: HSV13OCm30NGLM00udktCS62eCInxE-YRj_UUdjlRLfx7fBUbmkmU14vF-Lik7BF4AzJ8OfIvw3Mys6mqyrltWw9Tkfc38XhqE3uWSmd9Zuc9AZ_bVHHB4V_0W00 categories: Creational tags: - Java diff --git a/prototype/etc/prototype.urm.puml b/prototype/etc/prototype.urm.puml new file mode 100644 index 000000000000..7ab26dd1b3e4 --- /dev/null +++ b/prototype/etc/prototype.urm.puml @@ -0,0 +1,81 @@ +@startuml +package com.iluwatar.prototype { + class OrcWarlord { + + OrcWarlord() + + clone() : Warlord + + toString() : String + } + class OrcBeast { + + OrcBeast() + + clone() : Beast + + toString() : String + } + abstract class Beast { + + Beast() + + clone() : Beast {abstract} + } + class ElfMage { + + ElfMage() + + clone() : Mage + + toString() : String + } + abstract class Mage { + + Mage() + + clone() : Mage {abstract} + } + abstract class Prototype { + + Prototype() + + clone() : Object {abstract} + } + interface HeroFactory { + + createBeast() : Beast {abstract} + + createMage() : Mage {abstract} + + createWarlord() : Warlord {abstract} + } + class ElfWarlord { + + ElfWarlord() + + clone() : Warlord + + toString() : String + } + class OrcMage { + + OrcMage() + + clone() : Mage + + toString() : String + } + class ElfBeast { + + ElfBeast() + + clone() : Beast + + toString() : String + } + abstract class Warlord { + + Warlord() + + clone() : Warlord {abstract} + } + class HeroFactoryImpl { + - beast : Beast + - mage : Mage + - warlord : Warlord + + HeroFactoryImpl(mage : Mage, warlord : Warlord, beast : Beast) + + createBeast() : Beast + + createMage() : Mage + + createWarlord() : Warlord + } + class App { + + App() + + main(args : String[]) {static} + } +} +HeroFactoryImpl --> "-beast" Beast +HeroFactoryImpl --> "-warlord" Warlord +HeroFactoryImpl --> "-mage" Mage +OrcWarlord --|> Warlord +OrcBeast --|> Beast +Beast --|> Prototype +ElfMage --|> Mage +Mage --|> Prototype +ElfWarlord --|> Warlord +OrcMage --|> Mage +ElfBeast --|> Beast +Warlord --|> Prototype +HeroFactoryImpl ..|> HeroFactory +@enduml \ No newline at end of file diff --git a/prototype/pom.xml b/prototype/pom.xml index 7fb43402ff40..d0c03c0b6f57 100644 --- a/prototype/pom.xml +++ b/prototype/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT prototype diff --git a/proxy/README.md b/proxy/README.md index a3e03708e328..a3cdbf788d7c 100644 --- a/proxy/README.md +++ b/proxy/README.md @@ -3,6 +3,7 @@ layout: pattern title: Proxy folder: proxy permalink: /patterns/proxy/ +pumlid: 9SR13OCm30NGLM00udktCS62eCI9x6yesrEfx_Jcehd69c5rEe3X7oBZE-q5HwpXOhahH95oRrHgt0msEldYPHClkow30J5rQko_qB3-VKYG_qjXBOrezGK0 categories: Structural tags: - Java @@ -40,6 +41,7 @@ are several common situations in which the Proxy pattern is applicable * [java.lang.reflect.Proxy](http://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Proxy.html) * [Apache Commons Proxy](https://commons.apache.org/proper/commons-proxy/) +* Mocking frameworks Mockito, Powermock, EasyMock ## Credits diff --git a/proxy/etc/proxy.urm.puml b/proxy/etc/proxy.urm.puml new file mode 100644 index 000000000000..1dc58087c7c3 --- /dev/null +++ b/proxy/etc/proxy.urm.puml @@ -0,0 +1,24 @@ +@startuml +package com.iluwatar.proxy { + class WizardTowerProxy { + - NUM_WIZARDS_ALLOWED : int {static} + - numWizards : int + + WizardTowerProxy() + + enter(wizard : Wizard) + } + class WizardTower { + + WizardTower() + + enter(wizard : Wizard) + } + class App { + + App() + + main(args : String[]) {static} + } + class Wizard { + - name : String + + Wizard(name : String) + + toString() : String + } +} +WizardTowerProxy --|> WizardTower +@enduml \ No newline at end of file diff --git a/proxy/pom.xml b/proxy/pom.xml index 9936be34bc2c..7484fc47e751 100644 --- a/proxy/pom.xml +++ b/proxy/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT proxy diff --git a/publish-subscribe/README.md b/publish-subscribe/README.md index 6a5b2dfa8f51..3265e42eae04 100644 --- a/publish-subscribe/README.md +++ b/publish-subscribe/README.md @@ -3,6 +3,7 @@ layout: pattern title: Publish Subscribe folder: publish-subscribe permalink: /patterns/publish-subscribe/ +pumlid: PSZB3SCm203GLTe1RExT1XCKKs5YyMdMR--zFRsd66aTNAwFcRdZ1U1uzrDorgXWfykIBJjT2qJhnaI7Dtwm7HnoMjkOoMu12-C7s3LKOhQe4UGo63ZfVtlvwhkMVW40 categories: Integration tags: - Java diff --git a/publish-subscribe/etc/publish-subscribe.urm.puml b/publish-subscribe/etc/publish-subscribe.urm.puml new file mode 100644 index 000000000000..1272f1f6db29 --- /dev/null +++ b/publish-subscribe/etc/publish-subscribe.urm.puml @@ -0,0 +1,8 @@ +@startuml +package com.iluwatar.publish.subscribe { + class App { + + App() + + main(args : String[]) {static} + } +} +@enduml \ No newline at end of file diff --git a/publish-subscribe/pom.xml b/publish-subscribe/pom.xml index 1ff97ca4703d..de5b6068b08e 100644 --- a/publish-subscribe/pom.xml +++ b/publish-subscribe/pom.xml @@ -28,7 +28,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT publish-subscribe diff --git a/reactor/README.md b/reactor/README.md index b9ba98948064..7fd3982ad327 100644 --- a/reactor/README.md +++ b/reactor/README.md @@ -3,6 +3,7 @@ layout: pattern title: Reactor folder: reactor permalink: /patterns/reactor/ +pumlid: DSR14OGm20NGLjO23FVj1f7Hx2Ga0nzjVxtuJc-f9YrtJM-V4vZn9NA-or5nvfQXBiEWXYAZKsrvCzZfnnUlkqOzR9qCg5jGvtX2hYmOJWfvNz9xcTdR7m00 categories: Concurrency tags: - Java diff --git a/reactor/etc/reactor.urm.puml b/reactor/etc/reactor.urm.puml new file mode 100644 index 000000000000..ecf2f40f72f4 --- /dev/null +++ b/reactor/etc/reactor.urm.puml @@ -0,0 +1,151 @@ +@startuml +package com.iluwatar.reactor.app { + class App { + - channels : List + - dispatcher : Dispatcher + - reactor : NioReactor + + App(dispatcher : Dispatcher) + + main(args : String[]) {static} + + start() + + stop() + - tcpChannel(port : int, handler : ChannelHandler) : AbstractNioChannel + - udpChannel(port : int, handler : ChannelHandler) : AbstractNioChannel + } + class LoggingHandler { + - ACK : byte[] {static} + + LoggingHandler() + - doLogging(data : ByteBuffer) {static} + + handleChannelRead(channel : AbstractNioChannel, readObject : Object, key : SelectionKey) + - sendReply(channel : AbstractNioChannel, incomingPacket : DatagramPacket, key : SelectionKey) {static} + - sendReply(channel : AbstractNioChannel, key : SelectionKey) {static} + } + ~class TcpLoggingClient { + - clientName : String + - serverPort : int + + TcpLoggingClient(clientName : String, serverPort : int) + + run() + - sendLogRequests(writer : PrintWriter, inputStream : InputStream) + } + ~class UdpLoggingClient { + - clientName : String + - remoteAddress : InetSocketAddress + + UdpLoggingClient(clientName : String, port : int) + + run() + } + class AppClient { + - service : ExecutorService + + AppClient() + - artificialDelayOf(millis : long) {static} + + main(args : String[]) {static} + + start() + + stop() + } +} +package com.iluwatar.reactor.framework { + abstract class AbstractNioChannel { + - channel : SelectableChannel + - channelToPendingWrites : Map> + - handler : ChannelHandler + - reactor : NioReactor + + AbstractNioChannel(handler : ChannelHandler, channel : SelectableChannel) + + bind() {abstract} + # doWrite(Object, SelectionKey) {abstract} + ~ flush(key : SelectionKey) + + getHandler() : ChannelHandler + + getInterestedOps() : int {abstract} + + getJavaChannel() : SelectableChannel + + read(SelectionKey) : Object {abstract} + ~ setReactor(reactor : NioReactor) + + write(data : Object, key : SelectionKey) + } + ~class ChangeKeyOpsCommand { + - interestedOps : int + - key : SelectionKey + + ChangeKeyOpsCommand(this$0 : SelectionKey, key : int) + + run() + + toString() : String + } + interface ChannelHandler { + + handleChannelRead(AbstractNioChannel, Object, SelectionKey) {abstract} + } + class NioDatagramChannel { + - port : int + + NioDatagramChannel(port : int, handler : ChannelHandler) + + bind() + # doWrite(pendingWrite : Object, key : SelectionKey) + + getInterestedOps() : int + + getJavaChannel() : DatagramChannel + + read(key : SelectionKey) : DatagramPacket + + write(data : Object, key : SelectionKey) + } + class ThreadPoolDispatcher { + - executorService : ExecutorService + + ThreadPoolDispatcher(poolSize : int) + + onChannelReadEvent(channel : AbstractNioChannel, readObject : Object, key : SelectionKey) + + stop() + } + class NioServerSocketChannel { + - port : int + + NioServerSocketChannel(port : int, handler : ChannelHandler) + + bind() + # doWrite(pendingWrite : Object, key : SelectionKey) + + getInterestedOps() : int + + getJavaChannel() : ServerSocketChannel + + read(key : SelectionKey) : ByteBuffer + } + class SameThreadDispatcher { + + SameThreadDispatcher() + + onChannelReadEvent(channel : AbstractNioChannel, readObject : Object, key : SelectionKey) + + stop() + } + interface Dispatcher { + + onChannelReadEvent(AbstractNioChannel, Object, SelectionKey) {abstract} + + stop() {abstract} + } + class DatagramPacket { + - data : ByteBuffer + - receiver : SocketAddress + - sender : SocketAddress + + DatagramPacket(data : ByteBuffer) + + getData() : ByteBuffer + + getReceiver() : SocketAddress + + getSender() : SocketAddress + + setReceiver(receiver : SocketAddress) + + setSender(sender : SocketAddress) + } + class NioReactor { + - dispatcher : Dispatcher + - pendingCommands : Queue + - reactorMain : ExecutorService + - selector : Selector + + NioReactor(dispatcher : Dispatcher) + + changeOps(key : SelectionKey, interestedOps : int) + - dispatchReadEvent(key : SelectionKey, readObject : Object) + - eventLoop() + - onChannelAcceptable(key : SelectionKey) + - onChannelReadable(key : SelectionKey) + - onChannelWritable(key : SelectionKey) {static} + - processKey(key : SelectionKey) + - processPendingCommands() + + registerChannel(channel : AbstractNioChannel) : NioReactor + + start() + + stop() + } +} +AbstractNioChannel --> "-handler" ChannelHandler +UdpLoggingClient ..+ AppClient +TcpLoggingClient ..+ AppClient +AbstractNioChannel --> "-reactor" NioReactor +NioReactor --> "-dispatcher" Dispatcher +App --> "-reactor" NioReactor +App --> "-channels" AbstractNioChannel +DatagramPacket ..+ NioDatagramChannel +LoggingHandler --+ NioDatagramChannel +App --> "-dispatcher" Dispatcher +ChangeKeyOpsCommand --+ NioReactor +NioDatagramChannel --|> AbstractNioChannel +LoggingHandler ..|> ChannelHandler +ThreadPoolDispatcher ..|> Dispatcher +NioServerSocketChannel --|> AbstractNioChannel +SameThreadDispatcher ..|> Dispatcher +@enduml \ No newline at end of file diff --git a/reactor/pom.xml b/reactor/pom.xml index 80a15f9d39a2..5bb577c42421 100644 --- a/reactor/pom.xml +++ b/reactor/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT reactor diff --git a/reader-writer-lock/README.md b/reader-writer-lock/README.md index 91d16892c4a5..556b9cd8541f 100644 --- a/reader-writer-lock/README.md +++ b/reader-writer-lock/README.md @@ -3,9 +3,13 @@ layout: pattern title: Reader Writer Lock folder: reader-writer-lock permalink: /patterns/reader-writer-lock/ -categories: Concurrent +pumlid: RSZB4S8m303HLg00MtUw4R8cCP5bZpwuVL80jttxx4gIZTFaSKOiVm4OxdhqEFETpaPJWpKgpG5TScEWmGU_M1fxFxGiZ61JXu5-1nXZOolR-gqYaoxWe3-xfeswSiWF +categories: Concurrency tags: -- Java + - Java + - Difficulty-Intermediate + - I/O + - Performance --- **Intent:** diff --git a/reader-writer-lock/etc/reader-writer-lock.urm.puml b/reader-writer-lock/etc/reader-writer-lock.urm.puml new file mode 100644 index 000000000000..1760cf3e46d0 --- /dev/null +++ b/reader-writer-lock/etc/reader-writer-lock.urm.puml @@ -0,0 +1,58 @@ +@startuml +package com.iluwatar.reader.writer.lock { + class ReaderWriterLock { + - currentReaderCount : int + - globalMutex : Set + - readerLock : ReadLock + - readerMutex : Object + - writerLock : WriteLock + + ReaderWriterLock() + - doesReaderOwnThisLock() : boolean + - doesWriterOwnThisLock() : boolean + - isLockFree() : boolean + + readLock() : Lock + - waitUninterruptibly(o : Object) {static} + + writeLock() : Lock + } + -class WriteLock { + - WriteLock() + + lock() + + lockInterruptibly() + + newCondition() : Condition + + tryLock() : boolean + + tryLock(time : long, unit : TimeUnit) : boolean + + unlock() + } + class App { + + App() + + main(args : String[]) {static} + } + -class ReadLock { + - ReadLock() + + lock() + + lockInterruptibly() + + newCondition() : Condition + + tryLock() : boolean + + tryLock(time : long, unit : TimeUnit) : boolean + + unlock() + } + class Writer { + - name : String + - writeLock : Lock + + Writer(name : String, writeLock : Lock) + + run() + + write() + } + class Reader { + - name : String + - readLock : Lock + + Reader(name : String, readLock : Lock) + + read() + + run() + } +} +ReaderWriterLock --> "-readerLock" ReadLock +ReadLock --+ ReaderWriterLock +ReaderWriterLock --> "-writerLock" WriteLock +WriteLock --+ ReaderWriterLock +@enduml \ No newline at end of file diff --git a/reader-writer-lock/pom.xml b/reader-writer-lock/pom.xml index 0391ea29164b..f10acb8d717d 100644 --- a/reader-writer-lock/pom.xml +++ b/reader-writer-lock/pom.xml @@ -30,7 +30,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT reader-writer-lock diff --git a/repository/README.md b/repository/README.md index 67b3ea44e828..501f39f9c60a 100644 --- a/repository/README.md +++ b/repository/README.md @@ -3,6 +3,7 @@ layout: pattern title: Repository folder: repository permalink: /patterns/repository/ +pumlid: JSV13OCm30NGLM00udktCS42eyI9xE-YRjyUUtjlLQij3qblomNCU14vF-LKNBbdYDTX44EfevEsV1ZiTFERjqD2Jzic0-8Mr3b-89SvGZ7yGuBwrvBUoypUlW00 categories: Persistence Tier tags: - Java diff --git a/repository/etc/repository.urm.puml b/repository/etc/repository.urm.puml new file mode 100644 index 000000000000..ad1759033bae --- /dev/null +++ b/repository/etc/repository.urm.puml @@ -0,0 +1,56 @@ +@startuml +package com.iluwatar.repository { + class PersonSpecifications { + + PersonSpecifications() + } + class NameEqualSpec { + + name : String + + NameEqualSpec(name : String) + + toPredicate(root : Root, query : CriteriaQuery, cb : CriteriaBuilder) : Predicate + } + class App { + + App() + + main(args : String[]) {static} + } + interface PersonRepository { + + findByName(String) : Person {abstract} + } + class AgeBetweenSpec { + - from : int + - to : int + + AgeBetweenSpec(from : int, to : int) + + toPredicate(root : Root, query : CriteriaQuery, cb : CriteriaBuilder) : Predicate + } + class AppConfig { + + AppConfig() + + dataSource() : DataSource + + entityManagerFactory() : LocalContainerEntityManagerFactoryBean + - jpaProperties() : Properties {static} + + main(args : String[]) {static} + + transactionManager() : JpaTransactionManager + } + class Person { + - age : int + - id : Long + - name : String + - surname : String + + Person() + + Person(name : String, surname : String, age : int) + + equals(obj : Object) : boolean + + getAge() : int + + getId() : Long + + getName() : String + + getSurname() : String + + hashCode() : int + + setAge(age : int) + + setId(id : Long) + + setName(name : String) + + setSurname(surname : String) + + toString() : String + } +} +App --+ PersonSpecifications +AppConfig --+ PersonSpecifications +NameEqualSpec ..+ PersonSpecifications +AgeBetweenSpec ..+ PersonSpecifications +@enduml \ No newline at end of file diff --git a/repository/pom.xml b/repository/pom.xml index b8b81ee05abd..45f735b7a92d 100644 --- a/repository/pom.xml +++ b/repository/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT repository diff --git a/resource-acquisition-is-initialization/README.md b/resource-acquisition-is-initialization/README.md index 821f220d74de..df7ee294b211 100644 --- a/resource-acquisition-is-initialization/README.md +++ b/resource-acquisition-is-initialization/README.md @@ -3,6 +3,7 @@ layout: pattern title: Resource Acquisition Is Initialization folder: resource-acquisition-is-initialization permalink: /patterns/resource-acquisition-is-initialization/ +pumlid: ZShR3S8m343HLUW0YV_PnhXMQvGumOzMOdhA1lqxkhgBABLSEQqzzeZfJm33isuIUxxIsMXei4QbqK5QdXXeyCO3oyekcvQ94MpgqD4lWB6FDEA2z4bn2HbQn8leHMponNy13hgvrhHUP_Rs0m00 categories: Other tags: - Java diff --git a/resource-acquisition-is-initialization/etc/resource-acquisition-is-initialization.urm.puml b/resource-acquisition-is-initialization/etc/resource-acquisition-is-initialization.urm.puml new file mode 100644 index 000000000000..81560c7f5289 --- /dev/null +++ b/resource-acquisition-is-initialization/etc/resource-acquisition-is-initialization.urm.puml @@ -0,0 +1,16 @@ +@startuml +package com.iluwatar.resource.acquisition.is.initialization { + class App { + + App() + + main(args : String[]) {static} + } + class SlidingDoor { + + SlidingDoor() + + close() + } + class TreasureChest { + + TreasureChest() + + close() + } +} +@enduml \ No newline at end of file diff --git a/resource-acquisition-is-initialization/pom.xml b/resource-acquisition-is-initialization/pom.xml index 63971d97c5f4..ef5de75326da 100644 --- a/resource-acquisition-is-initialization/pom.xml +++ b/resource-acquisition-is-initialization/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT resource-acquisition-is-initialization diff --git a/semaphore/README.md b/semaphore/README.md new file mode 100644 index 000000000000..071e061a7c00 --- /dev/null +++ b/semaphore/README.md @@ -0,0 +1,34 @@ +--- +layout: pattern +title: Semaphore +folder: semaphore +permalink: /patterns/semaphore/ +pumlid: HSV14SCm20J0Lk82BFxf1ikCfOn06ZZizfDVVhjRjphobFJnQi2ADv7pKwwEbaU6U9q6CPGwbVh8Xy5E7xvvFoNwPVjYGDo2bEC72b5URRgGeFvNqhMirF45 +categories: Concurrency +tags: + - Java + - Difficulty-Intermediate +--- + +## Also known as +Counting Semaphore + +## Intent +Create a lock which mediates access to a pool of resources. +Only a limited number of threads, specified at the creation +of the semaphore, can access the resources at any given time. +A semaphore which only allows one concurrent access to a resource +is called a binary semaphore. + +![alt text](./etc/semaphore.png "Semaphore") + +## Applicability +Use a Semaphore when + +* you have a pool of resources to allocate to different threads +* concurrent access to a resource could lead to a race condition + +## Credits + +* [Semaphore(programming)] (http://en.wikipedia.org/wiki/Semaphore_(programming)) +* [Semaphores] (http://tutorials.jenkov.com/java-concurrency/semaphores.html) diff --git a/semaphore/etc/semaphore.png b/semaphore/etc/semaphore.png new file mode 100644 index 000000000000..a2ff6d0c4f5b Binary files /dev/null and b/semaphore/etc/semaphore.png differ diff --git a/semaphore/etc/semaphore.urm.puml b/semaphore/etc/semaphore.urm.puml new file mode 100644 index 000000000000..a94ff5a7cace --- /dev/null +++ b/semaphore/etc/semaphore.urm.puml @@ -0,0 +1,58 @@ +@startuml +package com.iluwatar.semaphore { + class Fruit { + - type : FruitType + + Fruit(type : FruitType) + + getType() : FruitType + + toString() : String + } + class App { + + App() + + main(args : String[]) {static} + } + class Semaphore { + - counter : int + - licenses : int + + Semaphore(licenses : int) + + acquire() + + getAvailableLicenses() : int + + getNumLicenses() : int + + release() + } + class FruitShop { + - available : boolean[] + - bowls : FruitBowl[] + - semaphore : Semaphore + + FruitShop() + + countFruit() : int + + returnBowl(bowl : FruitBowl) + + takeBowl() : FruitBowl + } + interface Lock { + + acquire() {abstract} + + release() {abstract} + } + class FruitBowl { + - fruit : ArrayList + + FruitBowl() + + countFruit() : int + + put(f : Fruit) + + take() : Fruit + + toString() : String + } + enum FruitType { + + APPLE {static} + + LEMON {static} + + ORANGE {static} + + valueOf(name : String) : FruitType {static} + + values() : FruitType[] {static} + } +} +FruitShop --+ Fruit +Fruit --> "-type" FruitType +FruitType ..+ Fruit +FruitBowl --+ Fruit +FruitBowl --> "-fruit" Fruit +FruitShop --> "-semaphore" Semaphore +Semaphore ..|> Lock +@enduml \ No newline at end of file diff --git a/semaphore/pom.xml b/semaphore/pom.xml new file mode 100644 index 000000000000..4ee0edb9f7c2 --- /dev/null +++ b/semaphore/pom.xml @@ -0,0 +1,42 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.14.0-SNAPSHOT + + semaphore + + + junit + junit + test + + + diff --git a/semaphore/src/main/java/com/iluwatar/semaphore/App.java b/semaphore/src/main/java/com/iluwatar/semaphore/App.java new file mode 100644 index 000000000000..272de37b0e25 --- /dev/null +++ b/semaphore/src/main/java/com/iluwatar/semaphore/App.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.semaphore; + +/** + * A Semaphore mediates access by a group of threads to a pool of resources. + *

+ * In this example a group of customers are taking fruit from a fruit shop. + * There is a bowl each of apples, oranges and lemons. Only one customer can + * access a bowl simultaneously. A Semaphore is used to indicate how many + * resources are currently available and must be acquired in order for a bowl + * to be given to a customer. Customers continually try to take fruit until + * there is no fruit left in the shop. + */ +public class App { + + /** + * main method + */ + public static void main(String[] args) { + FruitShop shop = new FruitShop(); + new Customer("Peter", shop).start(); + new Customer("Paul", shop).start(); + new Customer("Mary", shop).start(); + new Customer("John", shop).start(); + new Customer("Ringo", shop).start(); + new Customer("George", shop).start(); + } + +} diff --git a/semaphore/src/main/java/com/iluwatar/semaphore/Customer.java b/semaphore/src/main/java/com/iluwatar/semaphore/Customer.java new file mode 100644 index 000000000000..0a4713438db5 --- /dev/null +++ b/semaphore/src/main/java/com/iluwatar/semaphore/Customer.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.semaphore; + +/** + * A Customer attempts to repeatedly take Fruit from the FruitShop by + * taking Fruit from FruitBowl instances. + */ +public class Customer extends Thread { + + /** + * Name of the Customer. + */ + private final String name; + + /** + * The FruitShop he is using. + */ + private final FruitShop fruitShop; + + /** + * Their bowl of Fruit. + */ + private final FruitBowl fruitBowl; + + /** + * Customer constructor + */ + public Customer(String name, FruitShop fruitShop) { + this.name = name; + this.fruitShop = fruitShop; + this.fruitBowl = new FruitBowl(); + } + + /** + * The Customer repeatedly takes Fruit from the FruitShop until no Fruit + * remains. + */ + public void run() { + + while (fruitShop.countFruit() > 0) { + FruitBowl bowl = fruitShop.takeBowl(); + Fruit fruit; + + if (bowl != null && (fruit = bowl.take()) != null) { + System.out.println(name + " took an " + fruit); + fruitBowl.put(fruit); + fruitShop.returnBowl(bowl); + } + } + + System.out.println(name + " took " + fruitBowl); + + } + +} diff --git a/semaphore/src/main/java/com/iluwatar/semaphore/Fruit.java b/semaphore/src/main/java/com/iluwatar/semaphore/Fruit.java new file mode 100644 index 000000000000..0a4224d20110 --- /dev/null +++ b/semaphore/src/main/java/com/iluwatar/semaphore/Fruit.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.semaphore; + +/** + * Fruit is a resource stored in a FruitBowl. + */ +public class Fruit { + + 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: + return "Apple"; + case LEMON: + return "Lemon"; + default: + return ""; + } + } + +} diff --git a/semaphore/src/main/java/com/iluwatar/semaphore/FruitBowl.java b/semaphore/src/main/java/com/iluwatar/semaphore/FruitBowl.java new file mode 100644 index 000000000000..ac2b87adacaa --- /dev/null +++ b/semaphore/src/main/java/com/iluwatar/semaphore/FruitBowl.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.semaphore; + +import java.util.ArrayList; + +/** + * A FruitBowl contains Fruit. + */ +public class FruitBowl { + + private ArrayList fruit = new ArrayList<>(); + + /** + * + * @return The amount of Fruit left in the bowl. + */ + public int countFruit() { + return fruit.size(); + } + + /** + * Put an item of Fruit into the bowl. + * + * @param f fruit + */ + public void put(Fruit f) { + fruit.add(f); + } + + /** + * Take an item of Fruit out of the bowl. + * @return The Fruit taken out of the bowl, or null if empty. + */ + public Fruit take() { + if (fruit.isEmpty()) { + return null; + } else { + return fruit.remove(0); + } + } + + /** + * toString method + */ + public String toString() { + int apples = 0; + int oranges = 0; + int lemons = 0; + + for (Fruit f : fruit) { + switch (f.getType()) { + case APPLE: + apples++; + break; + case ORANGE: + oranges++; + break; + case LEMON: + lemons++; + break; + default: + } + } + + return apples + " Apples, " + oranges + " Oranges, and " + lemons + " Lemons"; + } +} diff --git a/semaphore/src/main/java/com/iluwatar/semaphore/FruitShop.java b/semaphore/src/main/java/com/iluwatar/semaphore/FruitShop.java new file mode 100644 index 000000000000..315b46986ef4 --- /dev/null +++ b/semaphore/src/main/java/com/iluwatar/semaphore/FruitShop.java @@ -0,0 +1,120 @@ +/** + * 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.semaphore; + +/** + * A FruitShop contains three FruitBowl instances and controls access to them. + */ +public class FruitShop { + + /** + * The FruitBowl instances stored in the class. + */ + private FruitBowl[] bowls = { + new FruitBowl(), + new FruitBowl(), + new FruitBowl() + }; + + /** + * Access flags for each of the FruitBowl instances. + */ + private boolean[] available = { + true, + true, + true + }; + + /** + * The Semaphore that controls access to the class resources. + */ + private Semaphore semaphore; + + /** + * FruitShop constructor + */ + public FruitShop() { + for (int i = 0; i < 100; i++) { + bowls[0].put(new Fruit(Fruit.FruitType.APPLE)); + bowls[1].put(new Fruit(Fruit.FruitType.ORANGE)); + bowls[2].put(new Fruit(Fruit.FruitType.LEMON)); + } + + semaphore = new Semaphore(3); + } + + /** + * + * @return The amount of Fruit left in the shop. + */ + public synchronized int countFruit() { + return bowls[0].countFruit() + bowls[1].countFruit() + bowls[2].countFruit(); + } + + /** + * Method called by Customer to get a FruitBowl from the shop. This method + * will try to acquire the Semaphore before returning the first available + * FruitBowl. + */ + public synchronized FruitBowl takeBowl() { + + FruitBowl bowl = null; + + try { + semaphore.acquire(); + + if (available[0]) { + bowl = bowls[0]; + available[0] = false; + } else if (available[1]) { + bowl = bowls[1]; + available[1] = false; + } else if (available[2]) { + bowl = bowls[2]; + available[2] = false; + } + + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + semaphore.release(); + } + return bowl; + } + + /** + * Method called by a Customer instance to return a FruitBowl to the shop. + * This method releases the Semaphore, making the FruitBowl available to + * another Customer. + */ + public synchronized void returnBowl(FruitBowl bowl) { + if (bowl == bowls[0]) { + available[0] = true; + } else if (bowl == bowls[1]) { + available[1] = true; + } else if (bowl == bowls[2]) { + available [2] = true; + } + } + +} diff --git a/semaphore/src/main/java/com/iluwatar/semaphore/Lock.java b/semaphore/src/main/java/com/iluwatar/semaphore/Lock.java new file mode 100644 index 000000000000..4b651685c476 --- /dev/null +++ b/semaphore/src/main/java/com/iluwatar/semaphore/Lock.java @@ -0,0 +1,34 @@ +/** + * 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.semaphore; + +/** + * Lock is an interface for a lock which can be acquired and released. + */ +public interface Lock { + + void acquire() throws InterruptedException; + + void release(); + +} diff --git a/semaphore/src/main/java/com/iluwatar/semaphore/Semaphore.java b/semaphore/src/main/java/com/iluwatar/semaphore/Semaphore.java new file mode 100644 index 000000000000..2e4a54c7c1ac --- /dev/null +++ b/semaphore/src/main/java/com/iluwatar/semaphore/Semaphore.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.semaphore; + +/** + * Semaphore is an implementation of a semaphore lock. + */ +public class Semaphore implements Lock { + + private final int licenses; + /** + * The number of concurrent resource accesses which are allowed. + */ + private int counter; + + public Semaphore(int licenses) { + this.licenses = licenses; + this.counter = licenses; + } + + /** + * Returns the number of licenses managed by the Semaphore + */ + public int getNumLicenses() { + return licenses; + } + + /** + * Returns the number of available licenses + */ + public int getAvailableLicenses() { + return counter; + } + + /** + * Method called by a thread to acquire the lock. If there are no resources + * available this will wait until the lock has been released to re-attempt + * the acquire. + */ + public synchronized void acquire() throws InterruptedException { + while (counter == 0) { + wait(); + } + counter = counter - 1; + } + + /** + * Method called by a thread to release the lock. + */ + public synchronized void release() { + if (counter < licenses) { + counter = counter + 1; + notify(); + } + } + +} diff --git a/semaphore/src/test/java/com/iluwatar/semaphore/AppTest.java b/semaphore/src/test/java/com/iluwatar/semaphore/AppTest.java new file mode 100644 index 000000000000..4b286588d358 --- /dev/null +++ b/semaphore/src/test/java/com/iluwatar/semaphore/AppTest.java @@ -0,0 +1,34 @@ +/** + * 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.semaphore; + +import org.junit.Test; +import java.io.IOException; + +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 new file mode 100644 index 000000000000..be31f77ab096 --- /dev/null +++ b/semaphore/src/test/java/com/iluwatar/semaphore/FruitBowlTest.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.semaphore; + +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * Test taking from and putting Fruit into a FruitBowl + */ +public class FruitBowlTest { + + @Test + public void fruitBowlTest() { + FruitBowl fbowl = new FruitBowl(); + + assertEquals(fbowl.countFruit(), 0); + + for (int i = 1; i <= 10; i++) { + fbowl.put(new Fruit(Fruit.FruitType.LEMON)); + assertEquals(fbowl.countFruit(), i); + } + + for (int i = 9; i >= 0; i--) { + assertNotNull(fbowl.take()); + assertEquals(fbowl.countFruit(), i); + } + + assertNull(fbowl.take()); + } +} diff --git a/semaphore/src/test/java/com/iluwatar/semaphore/SemaphoreTest.java b/semaphore/src/test/java/com/iluwatar/semaphore/SemaphoreTest.java new file mode 100644 index 000000000000..14587a485c4a --- /dev/null +++ b/semaphore/src/test/java/com/iluwatar/semaphore/SemaphoreTest.java @@ -0,0 +1,56 @@ +/** + * 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.semaphore; + +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * Test case for acquiring and releasing a Semaphore + */ +public class SemaphoreTest { + + @Test + public void acquireReleaseTest() { + Semaphore sphore = new Semaphore(3); + + assertEquals(sphore.getAvailableLicenses(), 3); + + for (int i = 2; i >= 0; i--) { + try { + sphore.acquire(); + assertEquals(sphore.getAvailableLicenses(), i); + } catch (InterruptedException e) { + fail(e.toString()); + } + } + + for (int i = 1; i <= 3; i++) { + sphore.release(); + assertEquals(sphore.getAvailableLicenses(), i); + } + + sphore.release(); + assertEquals(sphore.getAvailableLicenses(), 3); + } +} diff --git a/servant/README.md b/servant/README.md index 3e82ab2cf904..d14d35edfcd7 100644 --- a/servant/README.md +++ b/servant/README.md @@ -3,6 +3,7 @@ layout: pattern title: Servant folder: servant permalink: /patterns/servant/ +pumlid: DSkn4O0m20NGLNG0G-ys63cDbv0SV7HzRUnUy-QYkSOkONKwWU4haV6JZe8pjd2nt1MYIBatAZKU1XjTVFEoYvT3by60c3erzW_qdPiL9CY_KrXB8rfz0G00 categories: Structural tags: - Java diff --git a/servant/etc/servant.urm.puml b/servant/etc/servant.urm.puml new file mode 100644 index 000000000000..b7391f5dd21d --- /dev/null +++ b/servant/etc/servant.urm.puml @@ -0,0 +1,55 @@ +@startuml +package com.iluwatar.servant { + class Servant { + + name : String + + Servant(name : String) + + checkIfYouWillBeHanged(tableGuests : List) : boolean + + feed(r : Royalty) + + giveCompliments(r : Royalty) + + giveWine(r : Royalty) + } + class Queen { + - complimentReceived : boolean + - isDrunk : boolean + - isFlirty : boolean + - isHappy : boolean + - isHungry : boolean + + Queen() + + changeMood() + + getDrink() + + getFed() + + getMood() : boolean + + receiveCompliments() + + setFlirtiness(f : boolean) + } + class App { + ~ jenkins : Servant {static} + ~ travis : Servant {static} + + App() + + main(args : String[]) {static} + + scenario(servant : Servant, compliment : int) {static} + } + class King { + - complimentReceived : boolean + - isDrunk : boolean + - isHappy : boolean + - isHungry : boolean + + King() + + changeMood() + + getDrink() + + getFed() + + getMood() : boolean + + receiveCompliments() + } + ~interface Royalty { + + changeMood() {abstract} + + getDrink() {abstract} + + getFed() {abstract} + + getMood() : boolean {abstract} + + receiveCompliments() {abstract} + } +} +App --> "-jenkins" Servant +Queen ..|> Royalty +King ..|> Royalty +@enduml \ No newline at end of file diff --git a/servant/pom.xml b/servant/pom.xml index a102ad348077..1e99bc09b2b5 100644 --- a/servant/pom.xml +++ b/servant/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT servant diff --git a/service-layer/README.md b/service-layer/README.md index 9b685d4e3f4b..af393947f343 100644 --- a/service-layer/README.md +++ b/service-layer/README.md @@ -3,6 +3,7 @@ layout: pattern title: Service Layer folder: service-layer permalink: /patterns/service-layer/ +pumlid: LOl93SCm3C1MQGUmzUysgY8aAcJ5q96WszVV_aW2V8gHriRb-ZWoPxm07E--Inxrhc2dqv8jEvq3HEl6H8SFNjWs3jcjJSnaju21iG3MSmbnK_mkuwJ_qij7dpNq1m00 categories: Architectural tags: - Java diff --git a/service-layer/etc/service-layer.urm.puml b/service-layer/etc/service-layer.urm.puml new file mode 100644 index 000000000000..880384b9f357 --- /dev/null +++ b/service-layer/etc/service-layer.urm.puml @@ -0,0 +1,158 @@ +@startuml +package com.iluwatar.servicelayer.hibernate { + class HibernateUtil { + - sessionFactory : SessionFactory {static} + - HibernateUtil() + + dropSession() {static} + + getSessionFactory() : SessionFactory {static} + } +} +package com.iluwatar.servicelayer.common { + abstract class BaseEntity { + - version : Long + + BaseEntity() + + getId() : Long {abstract} + + getName() : String {abstract} + + setId(Long) {abstract} + + setName(String) {abstract} + } + interface Dao { + + delete(E extends BaseEntity) {abstract} + + find(Long) : E extends BaseEntity {abstract} + + findAll() : List {abstract} + + merge(E extends BaseEntity) : E extends BaseEntity {abstract} + + persist(E extends BaseEntity) {abstract} + } + abstract class DaoBaseImpl { + # persistentClass : Class + + DaoBaseImpl() + + delete(entity : E extends BaseEntity) + + find(id : Long) : E extends BaseEntity + + findAll() : List + # getSession() : Session + + merge(entity : E extends BaseEntity) : E extends BaseEntity + + persist(entity : E extends BaseEntity) + } +} +package com.iluwatar.servicelayer.magic { + interface MagicService { + + findAllSpellbooks() : List {abstract} + + findAllSpells() : List {abstract} + + findAllWizards() : List {abstract} + + findWizardsWithSpell(String) : List {abstract} + + findWizardsWithSpellbook(String) : List {abstract} + } + class MagicServiceImpl { + - spellDao : SpellDao + - spellbookDao : SpellbookDao + - wizardDao : WizardDao + + MagicServiceImpl(wizardDao : WizardDao, spellbookDao : SpellbookDao, spellDao : SpellDao) + + findAllSpellbooks() : List + + findAllSpells() : List + + findAllWizards() : List + + findWizardsWithSpell(name : String) : List + + findWizardsWithSpellbook(name : String) : List + } +} +package com.iluwatar.servicelayer.wizard { + interface WizardDao { + + findByName(String) : Wizard {abstract} + } + class Wizard { + - id : Long + - name : String + - spellbooks : Set + + Wizard() + + Wizard(name : String) + + addSpellbook(spellbook : Spellbook) + + getId() : Long + + getName() : String + + getSpellbooks() : Set + + setId(id : Long) + + setName(name : String) + + setSpellbooks(spellbooks : Set) + + toString() : String + } + class WizardDaoImpl { + + WizardDaoImpl() + + findByName(name : String) : Wizard + } +} +package com.iluwatar.servicelayer.app { + class App { + + App() + + initData() {static} + + main(args : String[]) {static} + + queryData() {static} + } +} +package com.iluwatar.servicelayer.spellbook { + interface SpellbookDao { + + findByName(String) : Spellbook {abstract} + } + class SpellbookDaoImpl { + + SpellbookDaoImpl() + + findByName(name : String) : Spellbook + } + class Spellbook { + - id : Long + - name : String + - spells : Set + - wizards : Set + + Spellbook() + + Spellbook(name : String) + + addSpell(spell : Spell) + + getId() : Long + + getName() : String + + getSpells() : Set + + getWizards() : Set + + setId(id : Long) + + setName(name : String) + + setSpells(spells : Set) + + setWizards(wizards : Set) + + toString() : String + } +} +package com.iluwatar.servicelayer.spell { + class SpellDaoImpl { + + SpellDaoImpl() + + findByName(name : String) : Spell + } + interface SpellDao { + + findByName(String) : Spell {abstract} + } + class Spell { + - id : Long + - name : String + - spellbook : Spellbook + + Spell() + + Spell(name : String) + + getId() : Long + + getName() : String + + getSpellbook() : Spellbook + + setId(id : Long) + + setName(name : String) + + setSpellbook(spellbook : Spellbook) + + toString() : String + } +} +MagicServiceImpl --> "-wizardDao" WizardDao +MagicServiceImpl --> "-spellbookDao" SpellbookDao +MagicServiceImpl --> "-spellDao" SpellDao +Spellbook --> "-spells" Spell +Spellbook --> "-wizards" Wizard +SpellDaoImpl ..|> SpellDao +SpellDaoImpl --|> DaoBaseImpl +SpellbookDao --|> Dao +WizardDao --|> Dao +SpellbookDaoImpl ..|> SpellbookDao +SpellbookDaoImpl --|> DaoBaseImpl +MagicServiceImpl ..|> MagicService +SpellDao --|> Dao +Spell --|> BaseEntity +Spellbook --|> BaseEntity +Wizard --|> BaseEntity +WizardDaoImpl ..|> WizardDao +WizardDaoImpl --|> DaoBaseImpl +DaoBaseImpl ..|> Dao +@enduml \ No newline at end of file diff --git a/service-layer/pom.xml b/service-layer/pom.xml index c5e4eb8d9691..830ced4f603a 100644 --- a/service-layer/pom.xml +++ b/service-layer/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT service-layer diff --git a/service-locator/README.md b/service-locator/README.md index af4d8c3ac822..479c9ed0f942 100644 --- a/service-locator/README.md +++ b/service-locator/README.md @@ -3,6 +3,7 @@ layout: pattern title: Service Locator folder: service-locator permalink: /patterns/service-locator/ +pumlid: NSjB3iCm203HgxG7iDdtDeIWX0fZYqzo_MRTtUX9ynOZhPtBzNLchlW0EDxza3nhgs2dQScMdUO0qRenqU6B5xQTGmvh2pFPBM1WF07FSmbnqqcOqu6J_gsNZxvgw0y0 categories: Structural tags: - Java @@ -32,6 +33,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/etc/service-locator.urm.puml b/service-locator/etc/service-locator.urm.puml new file mode 100644 index 000000000000..9e41e7f40555 --- /dev/null +++ b/service-locator/etc/service-locator.urm.puml @@ -0,0 +1,38 @@ +@startuml +package com.iluwatar.servicelocator { + class ServiceLocator { + - serviceCache : ServiceCache {static} + - ServiceLocator() + + getService(serviceJndiName : String) : Service {static} + } + class App { + + App() + + main(args : String[]) {static} + } + class ServiceImpl { + - id : int + - serviceName : String + + ServiceImpl(serviceName : String) + + execute() + + getId() : int + + getName() : String + } + class InitContext { + + InitContext() + + lookup(serviceName : String) : Object + } + class ServiceCache { + - serviceCache : Map + + ServiceCache() + + addService(newService : Service) + + getService(serviceName : String) : Service + } + interface Service { + + execute() {abstract} + + getId() : int {abstract} + + getName() : String {abstract} + } +} +ServiceLocator --> "-serviceCache" ServiceCache +ServiceImpl ..|> Service +@enduml \ No newline at end of file diff --git a/service-locator/pom.xml b/service-locator/pom.xml index f07dc93f860a..894908f927a5 100644 --- a/service-locator/pom.xml +++ b/service-locator/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT service-locator diff --git a/singleton/README.md b/singleton/README.md index 2a481f5c8e10..4032ffaed03b 100644 --- a/singleton/README.md +++ b/singleton/README.md @@ -3,6 +3,7 @@ layout: pattern title: Singleton folder: singleton permalink: /patterns/singleton/ +pumlid: HSV14SCm20J0Lk82BFxf1ikCh0n26ZZizfDVVhjRjwfvIhg-Bc35cyZvAQtZoYD3l4w364gTWxhcms2d3z-ydnAzsRuO4BUWmV43HRUcWcaagF-Lz55M3lq2 categories: Creational tags: - Java @@ -31,6 +32,16 @@ Use the Singleton pattern when ## Real world examples * [java.lang.Runtime#getRuntime()](http://docs.oracle.com/javase/8/docs/api/java/lang/Runtime.html#getRuntime%28%29) +* [java.awt.Desktop#getDesktop()](http://docs.oracle.com/javase/8/docs/api/java/awt/Desktop.html#getDesktop--) +* [java.lang.System#getSecurityManager()](http://docs.oracle.com/javase/8/docs/api/java/lang/System.html#getSecurityManager--) + + +## Consequences + +* Violates Single Responsibility Principle (SRP) by controlling their own creation and lifecycle. +* Encourages using a global shared instance which prevents an object and resources used by this object from being deallocated. +* Creates tightly coupled code that is difficult to test. +* Makes it almost impossible to subclass a Singleton. ## Credits diff --git a/singleton/etc/singleton.urm.puml b/singleton/etc/singleton.urm.puml new file mode 100644 index 000000000000..a130917729d2 --- /dev/null +++ b/singleton/etc/singleton.urm.puml @@ -0,0 +1,42 @@ +@startuml +package com.iluwatar.singleton { + class App { + + App() + + main(args : String[]) {static} + } + class ThreadSafeLazyLoadedIvoryTower { + - instance : ThreadSafeLazyLoadedIvoryTower {static} + - ThreadSafeLazyLoadedIvoryTower() + + getInstance() : ThreadSafeLazyLoadedIvoryTower {static} + } + class InitializingOnDemandHolderIdiom { + - InitializingOnDemandHolderIdiom() + + getInstance() : InitializingOnDemandHolderIdiom {static} + } + class ThreadSafeDoubleCheckLocking { + - instance : ThreadSafeDoubleCheckLocking {static} + - ThreadSafeDoubleCheckLocking() + + getInstance() : ThreadSafeDoubleCheckLocking {static} + } + class IvoryTower { + - INSTANCE : IvoryTower {static} + - IvoryTower() + + getInstance() : IvoryTower {static} + } + -class HelperHolder { + - INSTANCE : InitializingOnDemandHolderIdiom {static} + - HelperHolder() + } + enum EnumIvoryTower { + + INSTANCE {static} + + toString() : String + + valueOf(name : String) : EnumIvoryTower {static} + + values() : EnumIvoryTower[] {static} + } +} +IvoryTower --> "-INSTANCE" IvoryTower +ThreadSafeDoubleCheckLocking --> "-instance" ThreadSafeDoubleCheckLocking +ThreadSafeLazyLoadedIvoryTower --> "-instance" ThreadSafeLazyLoadedIvoryTower +HelperHolder ..+ InitializingOnDemandHolderIdiom +HelperHolder --> "-INSTANCE" InitializingOnDemandHolderIdiom +@enduml \ No newline at end of file diff --git a/singleton/pom.xml b/singleton/pom.xml index 8ab7e0686196..2ba9fe6d4b7f 100644 --- a/singleton/pom.xml +++ b/singleton/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT singleton diff --git a/singleton/src/main/java/com/iluwatar/singleton/InitializingOnDemandHolderIdiom.java b/singleton/src/main/java/com/iluwatar/singleton/InitializingOnDemandHolderIdiom.java index 84444ec2ee03..6536978fc906 100644 --- a/singleton/src/main/java/com/iluwatar/singleton/InitializingOnDemandHolderIdiom.java +++ b/singleton/src/main/java/com/iluwatar/singleton/InitializingOnDemandHolderIdiom.java @@ -22,35 +22,38 @@ */ package com.iluwatar.singleton; -import java.io.Serializable; - /** - * The Initialize-on-demand-holder idiom is a secure way of creating lazy initialized singleton - * object in Java. refer to "The CERT Oracle Secure Coding Standard for Java" By Dhruv Mohindra, - * Robert C. Seacord p.378 - *

- * Singleton objects usually are heavy to create and sometimes need to serialize them. This class - * also shows how to preserve singleton in serialized version of singleton. + * The Initialize-on-demand-holder idiom is a secure way of creating a lazy initialized singleton + * object in Java. + *

+ * The technique is as lazy as possible and works in all known versions of Java. It takes advantage + * of language guarantees about class initialization, and will therefore work correctly in all + * Java-compliant compilers and virtual machines. + *

+ * The inner class is referenced no earlier (and therefore loaded no earlier by the class loader) than + * the moment that getInstance() is called. Thus, this solution is thread-safe without requiring special + * language constructs (i.e. volatile or synchronized). * - * @author mortezaadi@gmail.com */ -public final class InitializingOnDemandHolderIdiom implements Serializable { - - private static final long serialVersionUID = 1L; +public final class InitializingOnDemandHolderIdiom { + /** + * Private constructor. + */ private InitializingOnDemandHolderIdiom() {} + /** + * @return Singleton instance + */ public static InitializingOnDemandHolderIdiom getInstance() { return HelperHolder.INSTANCE; } - protected Object readResolve() { - return getInstance(); - } - + /** + * Provides the lazy-loaded Singleton instance. + */ private static class HelperHolder { - public static final InitializingOnDemandHolderIdiom INSTANCE = + private static final InitializingOnDemandHolderIdiom INSTANCE = new InitializingOnDemandHolderIdiom(); } - } diff --git a/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLocking.java b/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLocking.java index 50203609c9a3..a8fd9648a0e9 100644 --- a/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLocking.java +++ b/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLocking.java @@ -53,11 +53,20 @@ private ThreadSafeDoubleCheckLocking() { public static ThreadSafeDoubleCheckLocking getInstance() { // local variable increases performance by 25 percent // Joshua Bloch "Effective Java, Second Edition", p. 283-284 + ThreadSafeDoubleCheckLocking result = instance; + // Check if singleton instance is initialized. If it is initialized then we can return the instance. if (result == null) { + // It is not initialized but we cannot be sure because some other thread might have initialized it + // in the meanwhile. So to make sure we need to lock on an object to get mutual exclusion. synchronized (ThreadSafeDoubleCheckLocking.class) { + // Again assign the instance to local variable to check if it was initialized by some other thread + // while current thread was blocked to enter the locked zone. If it was initialized then we can + // return the previously created instance just like the previous null check. result = instance; if (result == null) { + // The instance is still not initialized so we can safely (no other thread can enter this zone) + // create an instance and make it our singleton instance. instance = result = new ThreadSafeDoubleCheckLocking(); } } diff --git a/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTower.java b/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTower.java index 4723257864a4..c7c75143c3aa 100644 --- a/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTower.java +++ b/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTower.java @@ -38,7 +38,7 @@ private ThreadSafeLazyLoadedIvoryTower() {} /** * The instance gets created only when it is called for first time. Lazy-loading */ - public static synchronized ThreadSafeLazyLoadedIvoryTower getInstance() { + public static synchronized ThreadSafeLazyLoadedIvoryTower getInstance() { if (instance == null) { instance = new ThreadSafeLazyLoadedIvoryTower(); diff --git a/specification/README.md b/specification/README.md index df6a4c3ebd31..dc47f4970e4c 100644 --- a/specification/README.md +++ b/specification/README.md @@ -3,12 +3,16 @@ layout: pattern title: Specification folder: specification permalink: /patterns/specification/ +pumlid: LSX14i8m20NGg-W16lRU1YcsE0d9mCTUNxVkthoxkVJQjQBVJc3bWoZuQeVXh6UbXao7EfhCGTRhOd3Gcp-yxPfs-BOOqF2amVa3vLAnbmd3ffD2_gTLZBPgz2y0 categories: Behavioral tags: - Java - Difficulty-Beginner --- +## Also known as +Filter, Criteria + ## Intent Specification pattern separates the statement of how to match a candidate, from the candidate object that it is matched against. As well as its @@ -23,6 +27,10 @@ Use the Specification pattern when * you need to select a subset of objects based on some criteria, and to refresh the selection at various times * you need to check that only suitable objects are used for a certain role (validation) +## Related patterns + +* Repository + ## Credits * [Martin Fowler - Specifications](http://martinfowler.com/apsupp/spec.pdf) diff --git a/specification/etc/specification.urm.puml b/specification/etc/specification.urm.puml new file mode 100644 index 000000000000..2f194d340653 --- /dev/null +++ b/specification/etc/specification.urm.puml @@ -0,0 +1,106 @@ +@startuml +package com.iluwatar.specification.creature { + abstract class AbstractCreature { + - color : Color + - movement : Movement + - name : String + - size : Size + + AbstractCreature(name : String, size : Size, movement : Movement, color : Color) + + getColor() : Color + + getMovement() : Movement + + getName() : String + + getSize() : Size + + toString() : String + } + class Troll { + + Troll() + } + class Octopus { + + Octopus() + } + class Shark { + + Shark() + } + class Goblin { + + Goblin() + } + class KillerBee { + + KillerBee() + } + interface Creature { + + getColor() : Color {abstract} + + getMovement() : Movement {abstract} + + getName() : String {abstract} + + getSize() : Size {abstract} + } + class Dragon { + + Dragon() + } +} +package com.iluwatar.specification.property { + enum Size { + + LARGE {static} + + NORMAL {static} + + SMALL {static} + - title : String + + toString() : String + + valueOf(name : String) : Size {static} + + values() : Size[] {static} + } + enum Color { + + DARK {static} + + GREEN {static} + + LIGHT {static} + + RED {static} + - title : String + + toString() : String + + valueOf(name : String) : Color {static} + + values() : Color[] {static} + } + enum Movement { + + FLYING {static} + + SWIMMING {static} + + WALKING {static} + - title : String + + toString() : String + + valueOf(name : String) : Movement {static} + + values() : Movement[] {static} + } +} +package com.iluwatar.specification.app { + class App { + + App() + + main(args : String[]) {static} + } +} +package com.iluwatar.specification.selector { + class MovementSelector { + - m : Movement + + MovementSelector(m : Movement) + + test(t : Creature) : boolean + } + class ColorSelector { + - c : Color + + ColorSelector(c : Color) + + test(t : Creature) : boolean + } + class SizeSelector { + - s : Size + + SizeSelector(s : Size) + + test(t : Creature) : boolean + } +} +SizeSelector --> "-s" Size +AbstractCreature --> "-color" Color +MovementSelector --> "-m" Movement +AbstractCreature --> "-movement" Movement +AbstractCreature --> "-size" Size +ColorSelector --> "-c" Color +AbstractCreature ..|> Creature +Troll --|> AbstractCreature +Octopus --|> AbstractCreature +Shark --|> AbstractCreature +Goblin --|> AbstractCreature +KillerBee --|> AbstractCreature +Dragon --|> AbstractCreature +@enduml \ No newline at end of file diff --git a/specification/pom.xml b/specification/pom.xml index c5468891962c..49e56f3c1f35 100644 --- a/specification/pom.xml +++ b/specification/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT specification diff --git a/state/README.md b/state/README.md index f5cb189fde44..34ad8074c5fe 100644 --- a/state/README.md +++ b/state/README.md @@ -3,6 +3,7 @@ layout: pattern title: State folder: state permalink: /patterns/state/ +pumlid: 9SRH3O0m20N0LNG0ox_RO2LQqz867hg-9jxNpKLpZLt2wdG2mrSYuoST1MTiuMAvAqIHSczKQZmCDhhuvcKNBuSkWm4nTMhiNyZ141BaVocifH6jlW00 categories: Behavioral tags: - Java @@ -25,6 +26,11 @@ Use the State pattern in either of the following cases * an object's behavior depends on its state, and it must change its behavior at run-time depending on that state * operations have large, multipart conditional statements that depend on the object's state. This state is usually represented by one or more enumerated constants. Often, several operations will contain this same conditional structure. The State pattern puts each branch of the conditional in a separate class. This lets you treat the object's state as an object in its own right that can vary independently from other objects. +## Real world examples + +* [javax.faces.lifecycle.Lifecycle#execute()](http://docs.oracle.com/javaee/7/api/javax/faces/lifecycle/Lifecycle.html#execute-javax.faces.context.FacesContext-) controlled by [FacesServlet](http://docs.oracle.com/javaee/7/api/javax/faces/webapp/FacesServlet.html), the behavior is dependent on current phase of lifecycle. +* [JDiameter - Diameter State Machine](https://github.com/npathai/jdiameter/blob/master/core/jdiameter/api/src/main/java/org/jdiameter/api/app/State.java) + ## Credits * [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) diff --git a/state/etc/state.urm.puml b/state/etc/state.urm.puml new file mode 100644 index 000000000000..378c11cd1f5e --- /dev/null +++ b/state/etc/state.urm.puml @@ -0,0 +1,37 @@ +@startuml +package com.iluwatar.state { + class PeacefulState { + - mammoth : Mammoth + + PeacefulState(mammoth : Mammoth) + + observe() + + onEnterState() + } + interface State { + + observe() {abstract} + + onEnterState() {abstract} + } + class App { + + App() + + main(args : String[]) {static} + } + class AngryState { + - mammoth : Mammoth + + AngryState(mammoth : Mammoth) + + observe() + + onEnterState() + } + class Mammoth { + - state : State + + Mammoth() + - changeStateTo(newState : State) + + observe() + + timePasses() + + toString() : String + } +} +AngryState --> "-mammoth" Mammoth +PeacefulState --> "-mammoth" Mammoth +Mammoth --> "-state" State +PeacefulState ..|> State +AngryState ..|> State +@enduml \ No newline at end of file diff --git a/state/pom.xml b/state/pom.xml index 2411eb2a72fc..ec932532d624 100644 --- a/state/pom.xml +++ b/state/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT state diff --git a/step-builder/README.md b/step-builder/README.md index bc636e37ae06..65d356c2e1a0 100644 --- a/step-builder/README.md +++ b/step-builder/README.md @@ -3,6 +3,7 @@ layout: pattern title: Step Builder folder: step-builder permalink: /patterns/step-builder/ +pumlid: LOZ93SCm3C1MQGQmzUysYYqaAcJ5q96i7t_x8KXkh4soKvfypeZfNm33fnuSP-xfPEtI88tQhW4i-M2WmGzlB9sS3oqJ8yZKOQ0lWOLPzcJfAoZQtwXfeyuSyW80 categories: Creational tags: - Java diff --git a/step-builder/etc/step-builder.urm.puml b/step-builder/etc/step-builder.urm.puml new file mode 100644 index 000000000000..129ecbf30cb1 --- /dev/null +++ b/step-builder/etc/step-builder.urm.puml @@ -0,0 +1,91 @@ +@startuml +package com.iluwatar.stepbuilder { + interface AbilityStep { + + noAbilities() : BuildStep {abstract} + + noMoreAbilities() : BuildStep {abstract} + + withAbility(String) : AbilityStep {abstract} + } + interface BuildStep { + + build() : Character {abstract} + } + interface NameStep { + + name(String) : ClassStep {abstract} + } + -class CharacterSteps { + - abilities : List + - fighterClass : String + - name : String + - spell : String + - weapon : String + - wizardClass : String + - CharacterSteps() + + build() : Character + + fighterClass(fighterClass : String) : WeaponStep + + name(name : String) : ClassStep + + noAbilities() : BuildStep + + noMoreAbilities() : BuildStep + + noSpell() : BuildStep + + noWeapon() : BuildStep + + withAbility(ability : String) : AbilityStep + + withSpell(spell : String) : AbilityStep + + withWeapon(weapon : String) : AbilityStep + + wizardClass(wizardClass : String) : SpellStep + } + class App { + + App() + + main(args : String[]) {static} + } + class CharacterStepBuilder { + - CharacterStepBuilder() + + newBuilder() : NameStep {static} + } + class Character { + - abilities : List + - fighterClass : String + - name : String + - spell : String + - weapon : String + - wizardClass : String + + Character(name : String) + + getAbilities() : List + + getFighterClass() : String + + getName() : String + + getSpell() : String + + getWeapon() : String + + getWizardClass() : String + + setAbilities(abilities : List) + + setFighterClass(fighterClass : String) + + setName(name : String) + + setSpell(spell : String) + + setWeapon(weapon : String) + + setWizardClass(wizardClass : String) + + toString() : String + } + interface SpellStep { + + noSpell() : BuildStep {abstract} + + withSpell(String) : AbilityStep {abstract} + } + interface ClassStep { + + fighterClass(String) : WeaponStep {abstract} + + wizardClass(String) : SpellStep {abstract} + } + interface WeaponStep { + + noWeapon() : BuildStep {abstract} + + withWeapon(String) : AbilityStep {abstract} + } +} +App --+ CharacterStepBuilder +WeaponStep ..+ CharacterStepBuilder +ClassStep ..+ CharacterStepBuilder +SpellStep ..+ CharacterStepBuilder +CharacterSteps ..+ CharacterStepBuilder +AbilityStep ..+ CharacterStepBuilder +NameStep ..+ CharacterStepBuilder +BuildStep ..+ CharacterStepBuilder +CharacterSteps ..|> NameStep +CharacterSteps ..|> ClassStep +CharacterSteps ..|> WeaponStep +CharacterSteps ..|> SpellStep +CharacterSteps ..|> AbilityStep +CharacterSteps ..|> BuildStep +@enduml \ No newline at end of file diff --git a/step-builder/pom.xml b/step-builder/pom.xml index 8d926d118d3c..cd53119ffc0f 100644 --- a/step-builder/pom.xml +++ b/step-builder/pom.xml @@ -30,7 +30,7 @@ java-design-patterns com.iluwatar - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT step-builder diff --git a/strategy/README.md b/strategy/README.md index f07397f678ee..697b6cc88c6a 100644 --- a/strategy/README.md +++ b/strategy/README.md @@ -3,6 +3,7 @@ layout: pattern title: Strategy folder: strategy permalink: /patterns/strategy/ +pumlid: FSV13OCm30NGLM00udktCS4AGOaJsTz5tRwSkBstLiqj3WbhombC_n0PtwbKdB67Y-MX44NAerDjSJFOwE8lRuTuBRfD1iJKgRC_88SnfFn8aD-ai9vczFO7 categories: Behavioral tags: - Java diff --git a/strategy/etc/strategy.urm.puml b/strategy/etc/strategy.urm.puml new file mode 100644 index 000000000000..db86b9ed6a0e --- /dev/null +++ b/strategy/etc/strategy.urm.puml @@ -0,0 +1,33 @@ +@startuml +package com.iluwatar.strategy { + class DragonSlayer { + - strategy : DragonSlayingStrategy + + DragonSlayer(strategy : DragonSlayingStrategy) + + changeStrategy(strategy : DragonSlayingStrategy) + + goToBattle() + } + interface DragonSlayingStrategy { + + execute() {abstract} + } + class App { + + App() + + main(args : String[]) {static} + } + class MeleeStrategy { + + MeleeStrategy() + + execute() + } + class ProjectileStrategy { + + ProjectileStrategy() + + execute() + } + class SpellStrategy { + + SpellStrategy() + + execute() + } +} +DragonSlayer --> "-strategy" DragonSlayingStrategy +MeleeStrategy ..|> DragonSlayingStrategy +ProjectileStrategy ..|> DragonSlayingStrategy +SpellStrategy ..|> DragonSlayingStrategy +@enduml \ No newline at end of file diff --git a/strategy/pom.xml b/strategy/pom.xml index 934ff8775b41..e57f42e08f0c 100644 --- a/strategy/pom.xml +++ b/strategy/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT strategy diff --git a/template-method/README.md b/template-method/README.md index ad972f06bce5..65381d0ce12e 100644 --- a/template-method/README.md +++ b/template-method/README.md @@ -3,6 +3,7 @@ layout: pattern title: Template method folder: template-method permalink: /patterns/template-method/ +pumlid: NSZ13SCW30NGLPe1mFTkuu0Lg6n0vZjPlpttzlIEFef6bN1zDM3jDv7paw-E5cTiyJ87P22NQTGr7WOxVVZcL6NtQwJ5WFZOPBn_88WjPKWoGPkL1EN_ShZb5QPV categories: Behavioral tags: - Java diff --git a/template-method/etc/template-method.urm.puml b/template-method/etc/template-method.urm.puml new file mode 100644 index 000000000000..c2c6045fe404 --- /dev/null +++ b/template-method/etc/template-method.urm.puml @@ -0,0 +1,36 @@ +@startuml +package com.iluwatar.templatemethod { + class HitAndRunMethod { + + HitAndRunMethod() + # confuseTarget(target : String) + # pickTarget() : String + # stealTheItem(target : String) + } + class HalflingThief { + - method : StealingMethod + + HalflingThief(method : StealingMethod) + + changeMethod(method : StealingMethod) + + steal() + } + abstract class StealingMethod { + + StealingMethod() + # confuseTarget(String) {abstract} + # pickTarget() : String {abstract} + + steal() + # stealTheItem(String) {abstract} + } + class SubtleMethod { + + SubtleMethod() + # confuseTarget(target : String) + # pickTarget() : String + # stealTheItem(target : String) + } + class App { + + App() + + main(args : String[]) {static} + } +} +HalflingThief --> "-method" StealingMethod +HitAndRunMethod --|> StealingMethod +SubtleMethod --|> StealingMethod +@enduml \ No newline at end of file diff --git a/template-method/pom.xml b/template-method/pom.xml index af0b19cb419b..2098a801ffc7 100644 --- a/template-method/pom.xml +++ b/template-method/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT template-method diff --git a/thread-pool/README.md b/thread-pool/README.md index 9806fa8e0b31..473494ef9349 100644 --- a/thread-pool/README.md +++ b/thread-pool/README.md @@ -3,6 +3,7 @@ layout: pattern title: Thread Pool folder: thread-pool permalink: /patterns/thread-pool/ +pumlid: JSV14SCW30J0Lk82GFzq8uF6a1624IUx_UIPt-xHhMXK2TTN0zP-4pa_-UfeSSOMBzCWXbpceAxnCDZfmpUdAhjVbXO3uhPfyFw1q5oufZMdag3yFuUFl6Be5m00 categories: Concurrency tags: - Java diff --git a/thread-pool/etc/thread-pool.urm.puml b/thread-pool/etc/thread-pool.urm.puml new file mode 100644 index 000000000000..421ea1dcedf3 --- /dev/null +++ b/thread-pool/etc/thread-pool.urm.puml @@ -0,0 +1,35 @@ +@startuml +package com.iluwatar.threadpool { + abstract class Task { + - ID_GENERATOR : AtomicInteger {static} + - id : int + - timeMs : int + + Task(timeMs : int) + + getId() : int + + getTimeMs() : int + + toString() : String + } + class CoffeeMakingTask { + - TIME_PER_CUP : int {static} + + CoffeeMakingTask(numCups : int) + + toString() : String + } + class Worker { + - task : Task + + Worker(task : Task) + + run() + } + class PotatoPeelingTask { + - TIME_PER_POTATO : int {static} + + PotatoPeelingTask(numPotatoes : int) + + toString() : String + } + class App { + + App() + + main(args : String[]) {static} + } +} +Worker --> "-task" Task +CoffeeMakingTask --|> Task +PotatoPeelingTask --|> Task +@enduml \ No newline at end of file diff --git a/thread-pool/pom.xml b/thread-pool/pom.xml index 824dcd7af0da..e1f9a4e49fd3 100644 --- a/thread-pool/pom.xml +++ b/thread-pool/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT thread-pool diff --git a/tolerant-reader/README.md b/tolerant-reader/README.md index be0085f2c571..5d1cf80fd22a 100644 --- a/tolerant-reader/README.md +++ b/tolerant-reader/README.md @@ -3,6 +3,7 @@ layout: pattern title: Tolerant Reader folder: tolerant-reader permalink: /patterns/tolerant-reader/ +pumlid: NSZ14SCm20NHLf829ExfXaYChGn26lZ4xSVdtFRjSrZJx9AkZnFOyI9olkenSEOxGxmjWnXgMvE6viLWfmz_kNI9SLZP38XRqEIuWx1Kd0t5XVjjGVj_DNtMdLD_ categories: Integration tags: - Java diff --git a/tolerant-reader/etc/tolerant-reader.urm.puml b/tolerant-reader/etc/tolerant-reader.urm.puml new file mode 100644 index 000000000000..f203f8257b52 --- /dev/null +++ b/tolerant-reader/etc/tolerant-reader.urm.puml @@ -0,0 +1,38 @@ +@startuml +package com.iluwatar.tolerantreader { + class RainbowFish { + - age : int + - lengthMeters : int + - name : String + - serialVersionUID : long {static} + - weightTons : int + + RainbowFish(name : String, age : int, lengthMeters : int, weightTons : int) + + getAge() : int + + getLengthMeters() : int + + getName() : String + + getWeightTons() : int + } + class App { + + App() + + main(args : String[]) {static} + } + class RainbowFishV2 { + - angry : boolean + - hungry : boolean + - serialVersionUID : long {static} + - sleeping : boolean + + RainbowFishV2(name : String, age : int, lengthMeters : int, weightTons : int) + + RainbowFishV2(name : String, age : int, lengthMeters : int, weightTons : int, sleeping : boolean, hungry : boolean, angry : boolean) + + getAngry() : boolean + + getHungry() : boolean + + getSleeping() : boolean + } + class RainbowFishSerializer { + - RainbowFishSerializer() + + readV1(filename : String) : RainbowFish {static} + + writeV1(rainbowFish : RainbowFish, filename : String) {static} + + writeV2(rainbowFish : RainbowFishV2, filename : String) {static} + } +} +RainbowFishV2 --|> RainbowFish +@enduml \ No newline at end of file diff --git a/tolerant-reader/pom.xml b/tolerant-reader/pom.xml index 249788da0ed1..9668b5fb3716 100644 --- a/tolerant-reader/pom.xml +++ b/tolerant-reader/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT tolerant-reader diff --git a/twin/README.md b/twin/README.md index 3795236bb528..092032a55f77 100644 --- a/twin/README.md +++ b/twin/README.md @@ -3,6 +3,7 @@ layout: pattern title: Twin folder: twin permalink: /patterns/twin/ +pumlid: 7SR13OCm30NGLUW0n7UsCS42eyH4zdUpFbNVwNtKQij3qjjo0ICs8kTPJiMLUuPuVGnYAFNff2qdWvrk_l9wIEXfws10t88wno-4gKQ2-az9xsLaRoy0 categories: Creational tags: - Java diff --git a/twin/etc/twin.urm.puml b/twin/etc/twin.urm.puml new file mode 100644 index 000000000000..b4bd52468e00 --- /dev/null +++ b/twin/etc/twin.urm.puml @@ -0,0 +1,25 @@ +@startuml +package com.iluwatar.twin { + abstract class GameItem { + + GameItem() + + click() {abstract} + + doDraw() {abstract} + + draw() + } + class BallItem { + - isSuspended : boolean + - twin : BallThread + + BallItem() + + click() + + doDraw() + + move() + + setTwin(twin : BallThread) + } + class App { + + App() + + main(args : String[]) {static} + - waiting() {static} + } +} +BallItem --|> GameItem +@enduml \ No newline at end of file diff --git a/twin/pom.xml b/twin/pom.xml index 010bf37b2e80..56fe7bd8ab08 100644 --- a/twin/pom.xml +++ b/twin/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT twin diff --git a/value-object/README.md b/value-object/README.md index 83223d8a2377..a8e707b055d3 100644 --- a/value-object/README.md +++ b/value-object/README.md @@ -3,6 +3,7 @@ layout: pattern title: Value Object folder: value-object permalink: /patterns/value-object/ +pumlid: LSZ13SCm20NGLTe1RExTXX2KECBOmfza_VRQszDxDnVBNJFiTG9pVOY2dteqdBdbqf3XK4ULqQbPFWmEklZcikjgXvV9W8Olwhn-e9ijjOpjKW4fv2zgHgypktq1 categories: Creational tags: - Java diff --git a/value-object/etc/value-object.urm.puml b/value-object/etc/value-object.urm.puml new file mode 100644 index 000000000000..6c81de017ee8 --- /dev/null +++ b/value-object/etc/value-object.urm.puml @@ -0,0 +1,21 @@ +@startuml +package com.iluwatar.value.object { + class HeroStat { + - intelligence : int + - luck : int + - strength : int + - HeroStat(strength : int, intelligence : int, luck : int) + + equals(obj : Object) : boolean + + getIntelligence() : int + + getLuck() : int + + getStrength() : int + + hashCode() : int + + toString() : String + + valueOf(strength : int, intelligence : int, luck : int) : HeroStat {static} + } + class App { + + App() + + main(args : String[]) {static} + } +} +@enduml \ No newline at end of file diff --git a/value-object/pom.xml b/value-object/pom.xml index 66b2342eb90b..156d5b969825 100644 --- a/value-object/pom.xml +++ b/value-object/pom.xml @@ -30,7 +30,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT value-object diff --git a/visitor/README.md b/visitor/README.md index c1e24a624c23..23ffc3af7caf 100644 --- a/visitor/README.md +++ b/visitor/README.md @@ -3,6 +3,7 @@ layout: pattern title: Visitor folder: visitor permalink: /patterns/visitor/ +pumlid: DSR14OGm20NGLhG0mtsxmSWeJa8oyD7sTo_xJczLgoqFIM_B1Spu43c_vLHSkMU8rs4GGwcZaxPy6UnqyyFR8Q6dRPC1SGlg7B_Gew4OJeBwVqdlPMPlNm00 categories: Behavioral tags: - Java @@ -27,6 +28,9 @@ Use the Visitor pattern when ## Real world examples * [Apache Wicket](https://github.com/apache/wicket) component tree, see [MarkupContainer](https://github.com/apache/wicket/blob/b60ec64d0b50a611a9549809c9ab216f0ffa3ae3/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java) +* [javax.lang.model.element.AnnotationValue](http://docs.oracle.com/javase/8/docs/api/javax/lang/model/element/AnnotationValue.html) and [AnnotationValueVisitor](http://docs.oracle.com/javase/8/docs/api/javax/lang/model/element/AnnotationValueVisitor.html) +* [javax.lang.model.element.Element](http://docs.oracle.com/javase/8/docs/api/javax/lang/model/element/Element.html) and [Element Visitor](http://docs.oracle.com/javase/8/docs/api/javax/lang/model/element/ElementVisitor.html) +* [java.nio.file.FileVisitor](http://docs.oracle.com/javase/8/docs/api/java/nio/file/FileVisitor.html) ## Credits diff --git a/visitor/etc/visitor.urm.puml b/visitor/etc/visitor.urm.puml new file mode 100644 index 000000000000..1f28e4e2f316 --- /dev/null +++ b/visitor/etc/visitor.urm.puml @@ -0,0 +1,57 @@ +@startuml +package com.iluwatar.visitor { + class Commander { + + Commander(children : Unit[]) + + accept(visitor : UnitVisitor) + + toString() : String + } + interface UnitVisitor { + + visitCommander(Commander) {abstract} + + visitSergeant(Sergeant) {abstract} + + visitSoldier(Soldier) {abstract} + } + class CommanderVisitor { + + CommanderVisitor() + + visitCommander(commander : Commander) + + visitSergeant(sergeant : Sergeant) + + visitSoldier(soldier : Soldier) + } + class Soldier { + + Soldier(children : Unit[]) + + accept(visitor : UnitVisitor) + + toString() : String + } + class App { + + App() + + main(args : String[]) {static} + } + abstract class Unit { + - children : Unit[] + + Unit(children : Unit[]) + + accept(visitor : UnitVisitor) + } + class SoldierVisitor { + + SoldierVisitor() + + visitCommander(commander : Commander) + + visitSergeant(sergeant : Sergeant) + + visitSoldier(soldier : Soldier) + } + class SergeantVisitor { + + SergeantVisitor() + + visitCommander(commander : Commander) + + visitSergeant(sergeant : Sergeant) + + visitSoldier(soldier : Soldier) + } + class Sergeant { + + Sergeant(children : Unit[]) + + accept(visitor : UnitVisitor) + + toString() : String + } +} +Commander --|> Unit +CommanderVisitor ..|> UnitVisitor +Soldier --|> Unit +SoldierVisitor ..|> UnitVisitor +SergeantVisitor ..|> UnitVisitor +Sergeant --|> Unit +@enduml \ No newline at end of file diff --git a/visitor/pom.xml b/visitor/pom.xml index 1b594b1791c3..e1d3ca6a1d43 100644 --- a/visitor/pom.xml +++ b/visitor/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.12.0-SNAPSHOT + 1.14.0-SNAPSHOT visitor