From b22c8bc32f6157a1c16cdda5c47c59484c36261d Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sat, 11 Dec 2021 16:34:05 +0200 Subject: [PATCH 01/21] docs: add Kevinyl3 as a contributor for review (#1926) * docs: update README.md [skip ci] * docs: update .all-contributorsrc [skip ci] Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ README.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 925f8fc36..3aaf8696a 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1730,6 +1730,15 @@ "contributions": [ "doc" ] + }, + { + "login": "Kevinyl3", + "name": "Kevin", + "avatar_url": "https://avatars.githubusercontent.com/u/47126749?v=4", + "profile": "http://no website", + "contributions": [ + "review" + ] } ], "contributorsPerLine": 7, diff --git a/README.md b/README.md index 2701103c4..ce4578984 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) [![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) -[![All Contributors](https://img.shields.io/badge/all_contributors-190-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-191-orange.svg?style=flat-square)](#contributors-)
@@ -320,6 +320,7 @@ This project is licensed under the terms of the MIT license.
Abhinav Vashisth

đź“– +
Kevin

👀 From 9063336687c06fe1100742d8b2c149cc2dbb13cd Mon Sep 17 00:00:00 2001 From: Shrirang Date: Sat, 11 Dec 2021 20:29:36 +0530 Subject: [PATCH 02/21] feature: Claim check pattern azure (#1897) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Archietecture Diagram added * Added pom.xml * Architecture Diagram Updated * Added Microservices and kafka * ReadME File Added * ReadME file Updated * #1329 ReadMe file updated and working pattern code added * #1329 readme file updated * #1329 readme file updated and java documentation added * #1329 repository merged * Update claim-check-pattern/ReadME.md #1329 Real world description updated Co-authored-by: Subhrodip Mohanta * #1329 Update claim-check-pattern/ReadME.md Co-authored-by: Subhrodip Mohanta * #1329 Applicability section in ReadMe file updated Co-authored-by: Subhrodip Mohanta * #1329 Tutorial section in ReadMe file updated Co-authored-by: Subhrodip Mohanta * #1329 Storage Data section in ReadMe File updated Co-authored-by: Subhrodip Mohanta * #1329 workflow section Update claim-check-pattern/ReadME.md Co-authored-by: Subhrodip Mohanta * #1329 Update claim-check-pattern/pom.xml Co-authored-by: Subhrodip Mohanta * #1329 deleted mvnw.cmd file * #1329 deleted drawio file * #1329 deleted mvnw file * #1329 deleted mvnw file * #1329 mvnw.cmd file deleted * #1329 Update claim-check-pattern/usage-cost-processor/src/main/java/com/callusage/domain/MessageHeader.java Co-authored-by: Subhrodip Mohanta * #1329 Update claim-check-pattern/usage-cost-processor/src/main/java/com/callusage/domain/UsageCostDetail.java Co-authored-by: Subhrodip Mohanta * #1329 Update claim-check-pattern/usage-cost-processor/src/main/java/com/callusage/domain/UsageDetail.java Co-authored-by: Subhrodip Mohanta * #1329 deleted mvnw file * #1329 deleted mvnw.cmd file * #1329 Update claim-check-pattern/usage-detail-sender/src/main/java/com/callusage/domain/Message.java Co-authored-by: Subhrodip Mohanta * #1329 Update claim-check-pattern/ReadME.md Co-authored-by: Subhrodip Mohanta * #1329 pom file dependencies fixed, readmeflie updated, removed unused imports * #1329 Readfile updated, class javadoc added * #1329 UML class diagrams added, readme file updated * #1329 lombok annotations used on model classes, common dependencies moved to parent pom * #1329 test cases added * include claim-check-pattern in parent pom * #1329 code smells fixed * #1329 code smells removed * #1329 security issues fixed * #1329 updated pom files and refactored packages name * #1329 checkstyle warning fixed * #1329 code coverage increased * #1329 minor changed. * checkpoint created with common utility * Claim-Check-Pattern | Shrirang97 | Implemented using Java Azure Functions * Claim-Check-Pattern | Shrirang97 | Updated Functions logic * Update MessageHandlerUtility.java * Update UsageCostProcessorFunction.java * claim-check-pattern | Shrirang97 | Added test cases * claim-check-pattern | Shrirang97 | Test cases for Azure functions fixed * Claim-Check-Pattern | Shrirang | Used string as request body * claim-check-pattern | Shrirang | Working test cases * claim-check-pattern | Shrirang | Issue fixed while deserializing * claim-check-pattern | Shrirang | removed unused import * claim-check-pattern | Shrirang | fixed refactoring * claim-chek-pattern | Shrirang | added lombok | fixed dependencies & test cases * Delete .DS_Store * claim-check-pattern | Shrirang | Fixed unrelated file * Update BookService.java * Update BookService.java * claim-check-pattern | Shrirang | Fixed unrelated files * claim-check-pattern | Shrirang | Fixed review comments * Update UsageCostProcessorFunction.java * Update ReadME.md * Rename ReadME.md to README.md * claim-check-pattern | Shrirang | Incorporated review comments. * Update cloud-claim-check-pattern/README.md Co-authored-by: Ilkka Seppälä * Update cloud-claim-check-pattern/README.md Co-authored-by: Ilkka Seppälä * Update cloud-claim-check-pattern/README.md Co-authored-by: Ilkka Seppälä * Update cloud-claim-check-pattern/README.md Co-authored-by: Ilkka Seppälä * Update cloud-claim-check-pattern/README.md Co-authored-by: Ilkka Seppälä * Update cloud-claim-check-pattern/README.md Co-authored-by: Ilkka Seppälä * Update cloud-claim-check-pattern/README.md Co-authored-by: Ilkka Seppälä * Updated readme file * Read me file updated | Added more description Co-authored-by: Subhrodip Mohanta Co-authored-by: Subhrodip Mohanta Co-authored-by: Ilkka Seppälä --- cloud-claim-check-pattern/.gitignore | 38 ++++ cloud-claim-check-pattern/README.md | 82 ++++++++ .../call-usage-app/.gitignore | 38 ++++ .../etc/call-usage-app.urm.puml | 117 +++++++++++ .../call-usage-app/host.json | 7 + .../call-usage-app/pom.xml | 150 +++++++++++++++ .../functions/UsageCostProcessorFunction.java | 168 ++++++++++++++++ .../claimcheckpattern/domain/Message.java | 43 +++++ .../claimcheckpattern/domain/MessageBody.java | 43 +++++ .../domain/MessageHeader.java | 47 +++++ .../domain/MessageReference.java | 45 +++++ .../domain/UsageCostDetail.java | 43 +++++ .../claimcheckpattern/domain/UsageDetail.java | 44 +++++ .../UsageDetailPublisherFunction.java | 146 ++++++++++++++ .../utility/EventHandlerUtility.java | 66 +++++++ .../utility/MessageHandlerUtility.java | 127 ++++++++++++ .../HttpResponseMessageMock.java | 105 ++++++++++ .../UsageCostProcessorFunctionTest.java | 181 ++++++++++++++++++ .../UsageDetailPublisherFunctionTest.java | 119 ++++++++++++ .../utility/EventHandlerUtilityTest.java | 70 +++++++ .../utility/MessageHandlerUtilityTest.java | 114 +++++++++++ .../org.mockito.plugins.MockMaker | 1 + .../subscriptionValidationEvent.json | 15 ++ .../src/test/resources/usageDetailEvent.json | 15 ++ .../etc/Claim-Check-Pattern.png | Bin 0 -> 53447 bytes .../etc/claim-check-pattern.urm.puml | 117 +++++++++++ .../etc/class-diagram.png | Bin 0 -> 110789 bytes cloud-claim-check-pattern/pom.xml | 69 +++++++ 28 files changed, 2010 insertions(+) create mode 100644 cloud-claim-check-pattern/.gitignore create mode 100644 cloud-claim-check-pattern/README.md create mode 100644 cloud-claim-check-pattern/call-usage-app/.gitignore create mode 100644 cloud-claim-check-pattern/call-usage-app/etc/call-usage-app.urm.puml create mode 100644 cloud-claim-check-pattern/call-usage-app/host.json create mode 100644 cloud-claim-check-pattern/call-usage-app/pom.xml create mode 100644 cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/consumer/callcostprocessor/functions/UsageCostProcessorFunction.java create mode 100644 cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/domain/Message.java create mode 100644 cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/domain/MessageBody.java create mode 100644 cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/domain/MessageHeader.java create mode 100644 cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/domain/MessageReference.java create mode 100644 cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/domain/UsageCostDetail.java create mode 100644 cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/domain/UsageDetail.java create mode 100644 cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/producer/calldetails/functions/UsageDetailPublisherFunction.java create mode 100644 cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/utility/EventHandlerUtility.java create mode 100644 cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/utility/MessageHandlerUtility.java create mode 100644 cloud-claim-check-pattern/call-usage-app/src/test/java/com/iluwatar/claimcheckpattern/HttpResponseMessageMock.java create mode 100644 cloud-claim-check-pattern/call-usage-app/src/test/java/com/iluwatar/claimcheckpattern/consumer/callcostprocessor/functions/UsageCostProcessorFunctionTest.java create mode 100644 cloud-claim-check-pattern/call-usage-app/src/test/java/com/iluwatar/claimcheckpattern/producer/calldetails/functions/UsageDetailPublisherFunctionTest.java create mode 100644 cloud-claim-check-pattern/call-usage-app/src/test/java/com/iluwatar/claimcheckpattern/utility/EventHandlerUtilityTest.java create mode 100644 cloud-claim-check-pattern/call-usage-app/src/test/java/com/iluwatar/claimcheckpattern/utility/MessageHandlerUtilityTest.java create mode 100644 cloud-claim-check-pattern/call-usage-app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker create mode 100644 cloud-claim-check-pattern/call-usage-app/src/test/resources/subscriptionValidationEvent.json create mode 100644 cloud-claim-check-pattern/call-usage-app/src/test/resources/usageDetailEvent.json create mode 100644 cloud-claim-check-pattern/etc/Claim-Check-Pattern.png create mode 100644 cloud-claim-check-pattern/etc/claim-check-pattern.urm.puml create mode 100644 cloud-claim-check-pattern/etc/class-diagram.png create mode 100644 cloud-claim-check-pattern/pom.xml diff --git a/cloud-claim-check-pattern/.gitignore b/cloud-claim-check-pattern/.gitignore new file mode 100644 index 000000000..f508cf94f --- /dev/null +++ b/cloud-claim-check-pattern/.gitignore @@ -0,0 +1,38 @@ +# Build output +target/ +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +# IDE +.idea/ +*.iml +.settings/ +.project +.classpath + +# macOS +.DS_Store + +# Azure Functions +local.settings.json +bin/ +obj/ diff --git a/cloud-claim-check-pattern/README.md b/cloud-claim-check-pattern/README.md new file mode 100644 index 000000000..0259f5782 --- /dev/null +++ b/cloud-claim-check-pattern/README.md @@ -0,0 +1,82 @@ +--- +layout: pattern +title: Claim Check Pattern +folder: cloud-claim-check-pattern +permalink: /patterns/cloud-claim-check-pattern/ +categories: Cloud +tags: + - Cloud distributed + - Microservices +--- + +## Name + +[Claim Check Pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/claim-check) + +## Also known as + +[Reference-Based Messaging](https://www.enterpriseintegrationpatterns.com/patterns/messaging/StoreInLibrary.html) + +## Intent + +- Reduce the load of data transfer through the Internet. Instead of sending actual data directly, just send the message reference. +- Improve data security. As only message reference is shared, no data is exposed to the Internet. + +## Explanation + +Real-World Example + +> Suppose if you want to build a photo processing system. A photo processing system takes an image as input, processes it, and outputs a different set of images. Consider system has one persistent storage, one input component, ten processing components, messaging platform. Once a photo is given to the input component, it stores that image on persistent storage. It then creates ten different messages/events with the same image location and publishes them to the messaging platform. The messaging platform triggers ten different processing components. The ten processing components extract information about image location from the received event and then read an image from persistent storage. They generate ten different images from the original image and drop these images again to persistent storage. + +In Plain words + +> Split a large message into a claim check and a payload. Send the claim check to the messaging platform and store the payload to an external service. This pattern allows large messages to be processed while protecting the message bus and the client from being overwhelmed or slowed down. This pattern also helps to reduce costs, as storage is usually cheaper than resource units used by the messaging platform.([ref](https://docs.microsoft.com/en-us/azure/architecture/patterns/claim-check)) + +## Architecture Diagram + +![alt text](./etc/Claim-Check-Pattern.png "Claim Check Pattern") + +## Applicability + +Use the Claim Check Pattern when + +- Huge processing data causes a lot of bandwidth consumption to transfer data through the Internet. +- To secure your data transfer by storing in common persistent storage. +- Using a cloud platform - Azure Functions or AWS Lambda, Azure EventGrid or AWS Event Bridge, Azure Blob Storage or AWS S3 Bucket. +- Each service must be independent and idempotent. Output data is dropped to persistent storage by the service. +- Publish-subscribe messaging pattern needs to be used. + +## Consequences + +- This pattern is stateless. Any compute API will not store any data. +- You must have persistent storage and a reliable messaging platform. + +## Tutorials + +### Workflow + +Suppose a telecom company wants to build call cost calculator system which generate the call cost daily. At the end of each day, details of the calls made by the consumers are stored somewhere. The call calculator system will read this data and generate call cost data for each user. Consumers will be billed using this generated data in case of postpaid service. + +Producer class( `UsageDetailPublisherFunction` Azure Function) will generate call usage details (here we are generating call data in producer class itself. In real world scenario, it will read from storage). `UsageDetailPublisherFunction` creates a message. Message consists of message header and message body. Message header is basically an event grid event or claim or message reference. Message body contains actual data. `UsageDetailPublisherFunction` sends a message header to Event Grid topic `usage-detail` and drops an entire message to the blob storage. Event Grid then sent this message header to the `UsageCostProcessorFunction` Azure function. It will read the entire message from blob storage with the help of the header, will calculate call cost and drop the result to the blob storage. + +### Class Diagrams + +![alt text](./etc/class-diagram.png "Claim-Check-Class-Diagram") + +### Setup + +- Any operating system can be used macOS, Windows, Linux as everything is deployed on Azure. +- Install Java JDK 11 and set up Java environmental variables. +- Install Git. +- Install Visual Studio Code. +- Install [ Azure Functions extension](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions) to be able to deploy using Visual studio. + +### Storage Data + +The data is stored in the Azure blob storage in the container `callusageapp`. For every trigger, one GUID is created. Under the `GUID folder`, 2 files will be created `input.json` and `output.json`. +`Input.json` is dropped `producer` azure function which contains call usage details.` Output.json` contains call cost details which are dropped by the `consumer` azure function. + +## Credits + +- [Messaging Pattern - Claim Check](https://www.enterpriseintegrationpatterns.com/patterns/messaging/StoreInLibrary.html) +- [Azure Architecture Pattern - Claim Check Pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/claim-check) diff --git a/cloud-claim-check-pattern/call-usage-app/.gitignore b/cloud-claim-check-pattern/call-usage-app/.gitignore new file mode 100644 index 000000000..f508cf94f --- /dev/null +++ b/cloud-claim-check-pattern/call-usage-app/.gitignore @@ -0,0 +1,38 @@ +# Build output +target/ +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +# IDE +.idea/ +*.iml +.settings/ +.project +.classpath + +# macOS +.DS_Store + +# Azure Functions +local.settings.json +bin/ +obj/ diff --git a/cloud-claim-check-pattern/call-usage-app/etc/call-usage-app.urm.puml b/cloud-claim-check-pattern/call-usage-app/etc/call-usage-app.urm.puml new file mode 100644 index 000000000..0ffec48b1 --- /dev/null +++ b/cloud-claim-check-pattern/call-usage-app/etc/call-usage-app.urm.puml @@ -0,0 +1,117 @@ +@startuml +package com.iluwatar.claimcheckpattern.producer.calldetails.functions { + class UsageDetailPublisherFunction { + - eventHandlerUtility : EventHandlerUtility + - messageHandlerUtility : MessageHandlerUtility + + UsageDetailPublisherFunction() + + UsageDetailPublisherFunction(messageHandlerUtility : MessageHandlerUtility, eventHandlerUtility : EventHandlerUtility) + + run(request : HttpRequestMessage>, context : ExecutionContext) : HttpResponseMessage + } +} +package com.iluwatar.claimcheckpattern.domain { + class Message { + - messageBody : MessageBody + - messageHeader : MessageHeader + + Message() + + getMessageBody() : MessageBody + + getMessageHeader() : MessageHeader + + setMessageBody(messageBody : MessageBody) + + setMessageHeader(messageHeader : MessageHeader) + } + class MessageBody { + - data : List + + MessageBody() + + getData() : List + + setData(data : List) + } + class MessageHeader { + - data : Object + - dataVersion : String + - eventTime : String + - eventType : String + - id : String + - subject : String + - topic : String + + MessageHeader() + + getData() : Object + + getDataVersion() : String + + getEventTime() : String + + getEventType() : String + + getId() : String + + getSubject() : String + + getTopic() : String + + setData(data : Object) + + setDataVersion(dataVersion : String) + + setEventTime(eventTime : String) + + setEventType(eventType : String) + + setId(id : String) + + setSubject(subject : String) + + setTopic(topic : String) + } + class MessageReference { + - dataFileName : String + - dataLocation : String + + MessageReference() + + MessageReference(dataLocation : String, dataFileName : String) + + getDataFileName() : String + + getDataLocation() : String + + setDataFileName(dataFileName : String) + + setDataLocation(dataLocation : String) + } + class UsageCostDetail { + - callCost : double + - dataCost : double + - userId : String + + UsageCostDetail() + + getCallCost() : double + + getDataCost() : double + + getUserId() : String + + setCallCost(callCost : double) + + setDataCost(dataCost : double) + + setUserId(userId : String) + } + class UsageDetail { + - data : int + - duration : int + - userId : String + + UsageDetail() + + getData() : int + + getDuration() : int + + getUserId() : String + + setData(data : int) + + setDuration(duration : int) + + setUserId(userId : String) + } +} +package com.iluwatar.claimcheckpattern.utility { + class EventHandlerUtility { + - customEventClient : EventGridPublisherClient + + EventHandlerUtility() + + EventHandlerUtility(customEventClient : EventGridPublisherClient) + + publishEvent(customEvent : T, logger : Logger) + } + class MessageHandlerUtility { + - blobServiceClient : BlobServiceClient + + MessageHandlerUtility() + + MessageHandlerUtility(blobServiceClient : BlobServiceClient) + + dropToPersistantStorage(message : Message, logger : Logger) + + readFromPersistantStorage(messageReference : MessageReference, logger : Logger) : Message + } +} +package com.iluwatar.claimcheckpattern.consumer.callcostprocessor.functions { + class UsageCostProcessorFunction { + - messageHandlerUtilityForUsageCostDetail : MessageHandlerUtility + - messageHandlerUtilityForUsageDetail : MessageHandlerUtility + + UsageCostProcessorFunction() + + UsageCostProcessorFunction(messageHandlerUtilityForUsageDetail : MessageHandlerUtility, messageHandlerUtilityForUsageCostDetail : MessageHandlerUtility) + - calculateUsageCostDetails(usageDetailsList : List) : List + + run(request : HttpRequestMessage>, context : ExecutionContext) : HttpResponseMessage + } +} +UsageCostProcessorFunction --> "-messageHandlerUtilityForUsageDetail" MessageHandlerUtility +Message --> "-messageBody" MessageBody +UsageDetailPublisherFunction --> "-eventHandlerUtility" EventHandlerUtility +Builder ..+ HttpResponseMessage +UsageDetailPublisherFunction --> "-messageHandlerUtility" MessageHandlerUtility +Message --> "-messageHeader" MessageHeader +@enduml \ No newline at end of file diff --git a/cloud-claim-check-pattern/call-usage-app/host.json b/cloud-claim-check-pattern/call-usage-app/host.json new file mode 100644 index 000000000..4ac89572d --- /dev/null +++ b/cloud-claim-check-pattern/call-usage-app/host.json @@ -0,0 +1,7 @@ +{ + "version": "2.0", + "extensionBundle": { + "id": "Microsoft.Azure.Functions.ExtensionBundle", + "version": "[1.*, 2.0.0)" + } +} \ No newline at end of file diff --git a/cloud-claim-check-pattern/call-usage-app/pom.xml b/cloud-claim-check-pattern/call-usage-app/pom.xml new file mode 100644 index 000000000..a1bc7fc10 --- /dev/null +++ b/cloud-claim-check-pattern/call-usage-app/pom.xml @@ -0,0 +1,150 @@ + + + + 4.0.0 + + + + com.iluwatar + claim-check-pattern + 1.25.0-SNAPSHOT + + + call-usage-app + call-usage-app + jar + + + UTF-8 + 1.14.0 + 1.4.2 + CallUsageApp + + + + + + com.azure + azure-sdk-bom + 1.0.4 + pom + import + + + + + + + com.microsoft.azure.functions + azure-functions-java-library + ${azure.functions.java.library.version} + + + + com.azure + azure-messaging-eventgrid + + + + com.azure + azure-storage-blob + 12.13.0 + + + + org.slf4j + slf4j-simple + test + + + + + org.mockito + mockito-core + test + + + + + + + + + com.microsoft.azure + azure-functions-maven-plugin + ${azure.functions.maven.plugin.version} + + + ${functionAppName} + + java-functions-group + + java-functions-app-service-plan + + + westus + + + + + + + + + windows + 11 + + + + FUNCTIONS_EXTENSION_VERSION + ~3 + + + + + + package-functions + + package + + + + + + + maven-clean-plugin + 3.1.0 + + + + obj + + + + + + + diff --git a/cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/consumer/callcostprocessor/functions/UsageCostProcessorFunction.java b/cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/consumer/callcostprocessor/functions/UsageCostProcessorFunction.java new file mode 100644 index 000000000..652095727 --- /dev/null +++ b/cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/consumer/callcostprocessor/functions/UsageCostProcessorFunction.java @@ -0,0 +1,168 @@ +/* + * The MIT License + * Copyright © 2014-2021 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.claimcheckpattern.consumer.callcostprocessor.functions; + +import com.azure.core.util.BinaryData; +import com.azure.core.util.serializer.TypeReference; +import com.azure.messaging.eventgrid.EventGridEvent; +import com.azure.messaging.eventgrid.systemevents.SubscriptionValidationEventData; +import com.azure.messaging.eventgrid.systemevents.SubscriptionValidationResponse; +import com.iluwatar.claimcheckpattern.domain.Message; +import com.iluwatar.claimcheckpattern.domain.MessageBody; +import com.iluwatar.claimcheckpattern.domain.MessageHeader; +import com.iluwatar.claimcheckpattern.domain.MessageReference; +import com.iluwatar.claimcheckpattern.domain.UsageCostDetail; +import com.iluwatar.claimcheckpattern.domain.UsageDetail; +import com.iluwatar.claimcheckpattern.utility.MessageHandlerUtility; +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpMethod; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; +import com.microsoft.azure.functions.annotation.AuthorizationLevel; +import com.microsoft.azure.functions.annotation.FunctionName; +import com.microsoft.azure.functions.annotation.HttpTrigger; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * Azure Functions with HTTP Trigger. + * This is Consumer class. + */ +public class UsageCostProcessorFunction { + + private MessageHandlerUtility messageHandlerUtilityForUsageDetail; + private MessageHandlerUtility messageHandlerUtilityForUsageCostDetail; + + public UsageCostProcessorFunction() { + this.messageHandlerUtilityForUsageDetail = new MessageHandlerUtility<>(); + this.messageHandlerUtilityForUsageCostDetail = new MessageHandlerUtility<>(); + } + + public UsageCostProcessorFunction( + MessageHandlerUtility messageHandlerUtilityForUsageDetail, + MessageHandlerUtility messageHandlerUtilityForUsageCostDetail) { + this.messageHandlerUtilityForUsageDetail = messageHandlerUtilityForUsageDetail; + this.messageHandlerUtilityForUsageCostDetail = messageHandlerUtilityForUsageCostDetail; + } + + /** + * Azure function which gets triggered when event grid event send event to it. + * After receiving event, it read input file from blob storage, calculate call cost details. + * It creates new message with cost details and drop message to blob storage. + * @param request represents HttpRequestMessage + * @param context represents ExecutionContext + * @return HttpResponseMessage + */ + @FunctionName("UsageCostProcessorFunction") + public HttpResponseMessage run(@HttpTrigger(name = "req", methods = { HttpMethod.GET, + HttpMethod.POST }, authLevel = AuthorizationLevel.ANONYMOUS) + HttpRequestMessage> request, + final ExecutionContext context) { + try { + var eventGridEvents = EventGridEvent.fromString(request.getBody().get()); + for (var eventGridEvent : eventGridEvents) { + // Handle system events + if (eventGridEvent.getEventType() + .equals("Microsoft.EventGrid.SubscriptionValidationEvent")) { + SubscriptionValidationEventData subscriptionValidationEventData = eventGridEvent.getData() + .toObject(SubscriptionValidationEventData.class); + // Handle the subscription validation event + var responseData = new SubscriptionValidationResponse(); + responseData.setValidationResponse(subscriptionValidationEventData.getValidationCode()); + return request.createResponseBuilder(HttpStatus.OK).body(responseData).build(); + + } else if (eventGridEvent.getEventType().equals("UsageDetail")) { + // Get message header and reference + var messageReference = eventGridEvent.getData() + .toObject(MessageReference.class); + + // Read message from persistent storage + var message = this.messageHandlerUtilityForUsageDetail + .readFromPersistantStorage(messageReference, context.getLogger()); + + // Get Data and generate cost details + List usageDetailsList = BinaryData.fromObject( + message.getMessageBody().getData()) + .toObject(new TypeReference<>() { + }); + var usageCostDetailsList = calculateUsageCostDetails(usageDetailsList); + + // Create message body + var newMessageBody = new MessageBody(); + newMessageBody.setData(usageCostDetailsList); + + // Create message header + var newMessageReference = new MessageReference("callusageapp", + eventGridEvent.getId() + "/output.json"); + var newMessageHeader = new MessageHeader(); + newMessageHeader.setId(eventGridEvent.getId()); + newMessageHeader.setSubject("UsageCostProcessor"); + newMessageHeader.setTopic(""); + newMessageHeader.setEventType("UsageCostDetail"); + newMessageHeader.setEventTime(OffsetDateTime.now().toString()); + newMessageHeader.setData(newMessageReference); + newMessageHeader.setDataVersion("v1.0"); + + // Create entire message + var newMessage = new Message(); + newMessage.setMessageHeader(newMessageHeader); + newMessage.setMessageBody(newMessageBody); + + // Drop data to persistent storage + this.messageHandlerUtilityForUsageCostDetail.dropToPersistantStorage(newMessage, + context.getLogger()); + + context.getLogger().info("Message is dropped successfully"); + return request.createResponseBuilder(HttpStatus.OK) + .body("Message is dropped successfully").build(); + } + } + } catch (Exception e) { + context.getLogger().warning(e.getMessage()); + } + + return request.createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR).body(null).build(); + } + + private List calculateUsageCostDetails(List usageDetailsList) { + if (usageDetailsList == null) { + return null; + } + var usageCostDetailsList = new ArrayList(); + + usageDetailsList.forEach(usageDetail -> { + var usageCostDetail = new UsageCostDetail(); + usageCostDetail.setUserId(usageDetail.getUserId()); + usageCostDetail.setCallCost(usageDetail.getDuration() * 0.30); // 0.30₹ per minute + usageCostDetail.setDataCost(usageDetail.getData() * 0.20); // 0.20₹ per MB + + usageCostDetailsList.add(usageCostDetail); + }); + + return usageCostDetailsList; + } +} diff --git a/cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/domain/Message.java b/cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/domain/Message.java new file mode 100644 index 000000000..d85653dcd --- /dev/null +++ b/cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/domain/Message.java @@ -0,0 +1,43 @@ +/* + * The MIT License + * Copyright © 2014-2021 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.claimcheckpattern.domain; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * It is the message which gets dropped or read by Producer or Consumer Azure functions. + * It is stored in the json format. + * @param represents UsageDetail or UsageCostDetail + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Message { + private MessageHeader messageHeader; + + private MessageBody messageBody; + +} \ No newline at end of file diff --git a/cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/domain/MessageBody.java b/cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/domain/MessageBody.java new file mode 100644 index 000000000..e8284e04e --- /dev/null +++ b/cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/domain/MessageBody.java @@ -0,0 +1,43 @@ +/* + * The MIT License + * Copyright © 2014-2021 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.claimcheckpattern.domain; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * It is message body of the message. + * It stores actual data in our case UsageCostDetail or UsageDetail. + * @param represents UsageDetail or UsageCostDetail + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class MessageBody { + + private List data; + +} diff --git a/cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/domain/MessageHeader.java b/cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/domain/MessageHeader.java new file mode 100644 index 000000000..71e897ca9 --- /dev/null +++ b/cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/domain/MessageHeader.java @@ -0,0 +1,47 @@ +/* + * The MIT License + * Copyright © 2014-2021 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.claimcheckpattern.domain; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * This is message header or event which is sent to Event Grid. + * Its structure is same as Azure Event Grid Event Class. + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class MessageHeader { + + private String id; + private String subject; + private String topic; + private String eventType; + private String eventTime; + private Object data; + private String dataVersion; + +} \ No newline at end of file diff --git a/cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/domain/MessageReference.java b/cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/domain/MessageReference.java new file mode 100644 index 000000000..9eab15a3a --- /dev/null +++ b/cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/domain/MessageReference.java @@ -0,0 +1,45 @@ +/* + * The MIT License + * Copyright © 2014-2021 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.claimcheckpattern.domain; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * This is claim/message reference class. + * It contains the information about data where it is stored in persistent storage + * and file name. + * dataLocation is blob storage container name. + * dataFileName is file name in above container. + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class MessageReference { + + private String dataLocation; + private String dataFileName; + +} diff --git a/cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/domain/UsageCostDetail.java b/cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/domain/UsageCostDetail.java new file mode 100644 index 000000000..8930ea3f5 --- /dev/null +++ b/cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/domain/UsageCostDetail.java @@ -0,0 +1,43 @@ +/* + * The MIT License + * Copyright © 2014-2021 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.claimcheckpattern.domain; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * This is call cost details class. + * It stores userId of the caller, call duration cost and data cost. + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class UsageCostDetail { + + private String userId; + private double callCost; + private double dataCost; + +} diff --git a/cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/domain/UsageDetail.java b/cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/domain/UsageDetail.java new file mode 100644 index 000000000..c8f0395db --- /dev/null +++ b/cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/domain/UsageDetail.java @@ -0,0 +1,44 @@ +/* + * The MIT License + * Copyright © 2014-2021 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.claimcheckpattern.domain; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * This is call usage detail calls. + * It stores userId of the caller, call duration and data used. + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class UsageDetail { + + private String userId; + + private int duration; + + private int data; +} diff --git a/cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/producer/calldetails/functions/UsageDetailPublisherFunction.java b/cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/producer/calldetails/functions/UsageDetailPublisherFunction.java new file mode 100644 index 000000000..2f58f8f48 --- /dev/null +++ b/cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/producer/calldetails/functions/UsageDetailPublisherFunction.java @@ -0,0 +1,146 @@ +/* + * The MIT License + * Copyright © 2014-2021 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.claimcheckpattern.producer.calldetails.functions; + +import com.azure.messaging.eventgrid.EventGridEvent; +import com.azure.messaging.eventgrid.systemevents.SubscriptionValidationEventData; +import com.azure.messaging.eventgrid.systemevents.SubscriptionValidationResponse; +import com.iluwatar.claimcheckpattern.domain.Message; +import com.iluwatar.claimcheckpattern.domain.MessageBody; +import com.iluwatar.claimcheckpattern.domain.MessageHeader; +import com.iluwatar.claimcheckpattern.domain.MessageReference; +import com.iluwatar.claimcheckpattern.domain.UsageDetail; +import com.iluwatar.claimcheckpattern.utility.EventHandlerUtility; +import com.iluwatar.claimcheckpattern.utility.MessageHandlerUtility; +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpMethod; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; +import com.microsoft.azure.functions.annotation.AuthorizationLevel; +import com.microsoft.azure.functions.annotation.FunctionName; +import com.microsoft.azure.functions.annotation.HttpTrigger; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Random; +import java.util.UUID; + +/** + * Azure Functions with HTTP Trigger. + * This is Producer class. + */ +public class UsageDetailPublisherFunction { + + private MessageHandlerUtility messageHandlerUtility; + private EventHandlerUtility eventHandlerUtility; + + public UsageDetailPublisherFunction() { + this.messageHandlerUtility = new MessageHandlerUtility<>(); + this.eventHandlerUtility = new EventHandlerUtility<>(); + } + + public UsageDetailPublisherFunction(MessageHandlerUtility messageHandlerUtility, + EventHandlerUtility eventHandlerUtility) { + this.messageHandlerUtility = messageHandlerUtility; + this.eventHandlerUtility = eventHandlerUtility; + } + + /** + * Azure function which create message, drop it in persistent storage + * and publish the event to Event Grid topic. + * @param request represents HttpRequestMessage + * @param context represents ExecutionContext + * @return HttpResponseMessage + */ + @FunctionName("UsageDetailPublisherFunction") + public HttpResponseMessage run(@HttpTrigger(name = "req", methods = { + HttpMethod.POST }, authLevel = AuthorizationLevel.ANONYMOUS) + HttpRequestMessage> request, + final ExecutionContext context) { + try { + + var eventGridEvents = EventGridEvent.fromString(request.getBody().get()); + + for (EventGridEvent eventGridEvent : eventGridEvents) { + // Handle system events + if (eventGridEvent.getEventType() + .equals("Microsoft.EventGrid.SubscriptionValidationEvent")) { + SubscriptionValidationEventData subscriptionValidationEventData = eventGridEvent.getData() + .toObject(SubscriptionValidationEventData.class); + // Handle the subscription validation event + var responseData = new SubscriptionValidationResponse(); + responseData.setValidationResponse(subscriptionValidationEventData.getValidationCode()); + return request.createResponseBuilder(HttpStatus.OK).body(responseData).build(); + + } else if (eventGridEvent.getEventType().equals("UsageDetail")) { + // Create message body + var messageBody = new MessageBody(); + var usageDetailsList = new ArrayList(); + var random = new Random(); + for (int i = 0; i < 51; i++) { + var usageDetail = new UsageDetail(); + usageDetail.setUserId("userId" + i); + usageDetail.setData(random.nextInt(500)); + usageDetail.setDuration(random.nextInt(500)); + + usageDetailsList.add(usageDetail); + } + messageBody.setData(usageDetailsList); + + // Create message header + var messageHeader = new MessageHeader(); + messageHeader.setId(UUID.randomUUID().toString()); + messageHeader.setSubject("UsageDetailPublisher"); + messageHeader.setTopic("usagecostprocessorfunction-topic"); + messageHeader.setEventType("UsageDetail"); + messageHeader.setEventTime(OffsetDateTime.now().toString()); + var messageReference = new MessageReference("callusageapp", + messageHeader.getId() + "/input.json"); + messageHeader.setData(messageReference); + messageHeader.setDataVersion("v1.0"); + + // Create entire message + var message = new Message(); + message.setMessageHeader(messageHeader); + message.setMessageBody(messageBody); + + // Drop data to persistent storage + this.messageHandlerUtility.dropToPersistantStorage(message, context.getLogger()); + + // Publish event to event grid topic + eventHandlerUtility.publishEvent(messageHeader, context.getLogger()); + + context.getLogger().info("Message is dropped and event is published successfully"); + return request.createResponseBuilder(HttpStatus.OK).body(message).build(); + } + } + } catch (Exception e) { + context.getLogger().warning(e.getMessage()); + } + + return request.createResponseBuilder(HttpStatus.OK).body(null).build(); + } +} diff --git a/cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/utility/EventHandlerUtility.java b/cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/utility/EventHandlerUtility.java new file mode 100644 index 000000000..c037db10d --- /dev/null +++ b/cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/utility/EventHandlerUtility.java @@ -0,0 +1,66 @@ +/* + * The MIT License + * Copyright © 2014-2021 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.claimcheckpattern.utility; + +import com.azure.core.credential.AzureKeyCredential; +import com.azure.core.util.BinaryData; +import com.azure.messaging.eventgrid.EventGridPublisherClient; +import com.azure.messaging.eventgrid.EventGridPublisherClientBuilder; +import java.util.logging.Logger; + +/** + * This class is event publisher utility which published message header to Event Grid topic. + * @param represents UsageDetail or UsageCostDetail + */ +public class EventHandlerUtility { + + private EventGridPublisherClient customEventClient; + + /** Default constructor. + */ + public EventHandlerUtility() { + this.customEventClient = new EventGridPublisherClientBuilder() + .endpoint(System.getenv("EventGridURL")) + .credential(new AzureKeyCredential(System.getenv("EventGridKey"))) + .buildCustomEventPublisherClient(); + } + + /** + Parameterized constructor. + */ + public EventHandlerUtility(EventGridPublisherClient customEventClient) { + this.customEventClient = customEventClient; + } + + /** + Method for publishing event to Event Grid Topic. + */ + public void publishEvent(T customEvent, Logger logger) { + try { + customEventClient.sendEvent(BinaryData.fromObject(customEvent)); + } catch (Exception e) { + logger.info(e.getMessage()); + } + } +} diff --git a/cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/utility/MessageHandlerUtility.java b/cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/utility/MessageHandlerUtility.java new file mode 100644 index 000000000..40aac8ab9 --- /dev/null +++ b/cloud-claim-check-pattern/call-usage-app/src/main/java/com/iluwatar/claimcheckpattern/utility/MessageHandlerUtility.java @@ -0,0 +1,127 @@ +/* + * The MIT License + * Copyright © 2014-2021 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.claimcheckpattern.utility; + +import com.azure.core.util.BinaryData; +import com.azure.core.util.serializer.TypeReference; +import com.azure.storage.blob.BlobClient; +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.BlobServiceClient; +import com.azure.storage.blob.BlobServiceClientBuilder; +import com.iluwatar.claimcheckpattern.domain.Message; +import com.iluwatar.claimcheckpattern.domain.MessageReference; +import java.util.logging.Logger; + +/** + * This class read and drop message from Azure blob storage. + * @param represents UsageDetail or UsageCostDetail + */ +public class MessageHandlerUtility { + + private BlobServiceClient blobServiceClient; + + /** + * Parameterized constructor. + * @param blobServiceClient represents BlobServiceClient + */ + public MessageHandlerUtility(BlobServiceClient blobServiceClient) { + this.blobServiceClient = blobServiceClient; + } + + /** + * Default constructor. + */ + public MessageHandlerUtility() { + // Create a BlobServiceClient object which will be used to create a container + // client + this.blobServiceClient = new BlobServiceClientBuilder() + .connectionString(System.getenv("BlobStorageConnectionString")).buildClient(); + + } + + /** + * Read message from blob storage. + * @param messageReference represents MessageReference + * @param logger represents Logger + * @return Message + */ + public Message readFromPersistantStorage(MessageReference messageReference, Logger logger) { + Message message = null; + try { + + // Get container name from message reference + String containerName = messageReference.getDataLocation(); + + // Get blob name from message reference + String blobName = messageReference.getDataFileName(); + + // Get container client + BlobContainerClient containerClient = blobServiceClient.getBlobContainerClient(containerName); + + // Get a reference to a blob + BlobClient blobClient = containerClient.getBlobClient(blobName); + + // download the blob + message = blobClient.downloadContent().toObject(new TypeReference>() { + }); + } catch (Exception e) { + logger.info(e.getMessage()); + } + return message; + + } + + /** + * Drop message to blob storage. + * @param message represents Message + * @param logger represents Logger + */ + public void dropToPersistantStorage(Message message, Logger logger) { + try { + + // Get message reference + MessageReference messageReference = (MessageReference) message.getMessageHeader().getData(); + + // Create a unique name for the container + String containerName = messageReference.getDataLocation(); + + // Create the container and return a container client object + BlobContainerClient containerClient = this.blobServiceClient + .getBlobContainerClient(containerName); + if (!containerClient.exists()) { + containerClient.create(); + } + + // Get a reference to a blob + BlobClient blobClient = containerClient.getBlobClient(messageReference.getDataFileName()); + + // Upload the blob + blobClient.upload(BinaryData.fromObject(message)); + } catch (Exception e) { + logger.info(e.getMessage()); + } + + } + +} diff --git a/cloud-claim-check-pattern/call-usage-app/src/test/java/com/iluwatar/claimcheckpattern/HttpResponseMessageMock.java b/cloud-claim-check-pattern/call-usage-app/src/test/java/com/iluwatar/claimcheckpattern/HttpResponseMessageMock.java new file mode 100644 index 000000000..94cb48240 --- /dev/null +++ b/cloud-claim-check-pattern/call-usage-app/src/test/java/com/iluwatar/claimcheckpattern/HttpResponseMessageMock.java @@ -0,0 +1,105 @@ +/* + * The MIT License + * Copyright © 2014-2021 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.claimcheckpattern; + +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; +import com.microsoft.azure.functions.HttpStatusType; +import java.util.HashMap; +import java.util.Map; + +/** + * The mock for HttpResponseMessage, can be used in unit tests to verify if the + * returned response by HTTP trigger function is correct or not. + */ +public class HttpResponseMessageMock implements HttpResponseMessage { + private int httpStatusCode; + private HttpStatusType httpStatus; + private Object body; + private Map headers; + + public HttpResponseMessageMock(HttpStatusType status, Map headers, Object body) { + this.httpStatus = status; + this.httpStatusCode = status.value(); + this.headers = headers; + this.body = body; + } + + @Override + public HttpStatusType getStatus() { + return this.httpStatus; + } + + @Override + public int getStatusCode() { + return httpStatusCode; + } + + @Override + public String getHeader(String key) { + return this.headers.get(key); + } + + @Override + public Object getBody() { + return this.body; + } + + public static class HttpResponseMessageBuilderMock implements HttpResponseMessage.Builder { + private Object body; + private int httpStatusCode; + private Map headers = new HashMap<>(); + private HttpStatusType httpStatus; + + public Builder status(HttpStatus status) { + this.httpStatusCode = status.value(); + this.httpStatus = status; + return this; + } + + @Override + public Builder status(HttpStatusType httpStatusType) { + this.httpStatusCode = httpStatusType.value(); + this.httpStatus = httpStatusType; + return this; + } + + @Override + public HttpResponseMessage.Builder header(String key, String value) { + this.headers.put(key, value); + return this; + } + + @Override + public HttpResponseMessage.Builder body(Object body) { + this.body = body; + return this; + } + + @Override + public HttpResponseMessage build() { + return new HttpResponseMessageMock(this.httpStatus, this.headers, this.body); + } + } +} diff --git a/cloud-claim-check-pattern/call-usage-app/src/test/java/com/iluwatar/claimcheckpattern/consumer/callcostprocessor/functions/UsageCostProcessorFunctionTest.java b/cloud-claim-check-pattern/call-usage-app/src/test/java/com/iluwatar/claimcheckpattern/consumer/callcostprocessor/functions/UsageCostProcessorFunctionTest.java new file mode 100644 index 000000000..940a39af7 --- /dev/null +++ b/cloud-claim-check-pattern/call-usage-app/src/test/java/com/iluwatar/claimcheckpattern/consumer/callcostprocessor/functions/UsageCostProcessorFunctionTest.java @@ -0,0 +1,181 @@ +/* + * The MIT License + * Copyright © 2014-2021 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.claimcheckpattern.consumer.callcostprocessor.functions; + +import com.iluwatar.claimcheckpattern.HttpResponseMessageMock; +import com.iluwatar.claimcheckpattern.domain.Message; +import com.iluwatar.claimcheckpattern.domain.MessageBody; +import com.iluwatar.claimcheckpattern.domain.MessageHeader; +import com.iluwatar.claimcheckpattern.domain.MessageReference; +import com.iluwatar.claimcheckpattern.domain.UsageCostDetail; +import com.iluwatar.claimcheckpattern.domain.UsageDetail; +import com.iluwatar.claimcheckpattern.utility.MessageHandlerUtility; +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.stubbing.Answer; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.time.OffsetDateTime; +import java.util.*; +import java.util.logging.Logger; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +/** + * Unit test for Function class. + */ +@ExtendWith(MockitoExtension.class) +public class UsageCostProcessorFunctionTest { + + @Mock + MessageHandlerUtility mockMessageHandlerUtilityForUsageADetail; + @Mock + MessageHandlerUtility mockMessageHandlerUtilityForUsageCostDetail; + @Mock + ExecutionContext context; + + Message messageToDrop; + Message messageToRead; + MessageReference messageReference; + @InjectMocks + UsageCostProcessorFunction usageCostProcessorFunction; + + @BeforeEach + public void setUp() { + var messageBodyUsageDetail = new MessageBody(); + var usageDetailsList = new ArrayList(); + + var messageBodyUsageCostDetail = new MessageBody(); + var usageCostDetailsList = new ArrayList(); + for (int i = 0; i < 2; i++) { + var usageDetail = new UsageDetail(); + usageDetail.setUserId("userId" + i); + usageDetail.setData(i + 1); + usageDetail.setDuration(i + 1); + usageDetailsList.add(usageDetail); + + var usageCostDetail = new UsageCostDetail(); + usageCostDetail.setUserId(usageDetail.getUserId()); + usageCostDetail.setDataCost(usageDetail.getData() * 0.20); + usageCostDetail.setCallCost(usageDetail.getDuration() * 0.30); + usageCostDetailsList.add(usageCostDetail); + } + messageBodyUsageDetail.setData(usageDetailsList); + messageBodyUsageCostDetail.setData(usageCostDetailsList); + + // Create message header + var messageHeader = new MessageHeader(); + messageHeader.setId(UUID.randomUUID().toString()); + messageHeader.setSubject("UsageDetailPublisher"); + messageHeader.setTopic("usagecostprocessorfunction-topic"); + messageHeader.setEventType("UsageDetail"); + messageHeader.setEventTime(OffsetDateTime.now().toString()); + this.messageReference = new MessageReference("callusageapp", "d8284456-dfff-4bd4-9cef-ea99f70f4835/input.json"); + messageHeader.setData(messageReference); + messageHeader.setDataVersion("v1.0"); + + // Create entire message + messageToRead = new Message<>(); + messageToRead.setMessageHeader(messageHeader); + messageToRead.setMessageBody(messageBodyUsageDetail); + + messageToDrop = new Message<>(); + messageToDrop.setMessageHeader(messageHeader); + messageToDrop.setMessageBody(messageBodyUsageCostDetail); + + } + + /** + * Unit test for HttpTriggerJava method. + */ + @Test + public void shouldTriggerHttpAzureFunctionJavaWithSubscriptionValidationEventType() throws Exception { + + // Setup + @SuppressWarnings("unchecked") + final HttpRequestMessage> req = mock(HttpRequestMessage.class); + String fileAbsolutePath = getClass().getResource("/subscriptionValidationEvent.json").getPath() + .replaceAll("%20", " "), jsonBody = Files.readString(Paths.get(fileAbsolutePath)).replaceAll("\n", " "); + doReturn(Optional.of(jsonBody)).when(req).getBody(); + doAnswer(new Answer() { + @Override + public HttpResponseMessage.Builder answer(InvocationOnMock invocation) { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + } + }).when(req).createResponseBuilder(any(HttpStatus.class)); + + final ExecutionContext context = mock(ExecutionContext.class); + + // Invoke + final HttpResponseMessage ret = this.usageCostProcessorFunction.run(req, context); + + // Verify + assertEquals(ret.getStatus(), HttpStatus.OK); + } + + @Test + public void shouldTriggerHttpAzureFunctionJavaWithUsageDetailEventType() throws Exception { + // Setup + @SuppressWarnings("unchecked") + final HttpRequestMessage> req = mock(HttpRequestMessage.class); + String fileAbsolutePath = getClass().getResource("/usageDetailEvent.json").getPath().replaceAll("%20", " "), + jsonBody = Files.readString(Paths.get(fileAbsolutePath)).replaceAll("\n", " "); + doReturn(Optional.of(jsonBody)).when(req).getBody(); + doReturn(Logger.getGlobal()).when(context).getLogger(); + + when(this.mockMessageHandlerUtilityForUsageADetail.readFromPersistantStorage(any(MessageReference.class), + eq(Logger.getGlobal()))).thenReturn(messageToRead); + doAnswer(new Answer() { + @Override + public HttpResponseMessage.Builder answer(InvocationOnMock invocation) { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + } + }).when(req).createResponseBuilder(any(HttpStatus.class)); + + assertNotNull(this.mockMessageHandlerUtilityForUsageADetail); + assertEquals(this.messageToRead, this.mockMessageHandlerUtilityForUsageADetail + .readFromPersistantStorage(this.messageReference, Logger.getGlobal())); + + // Invoke + final HttpResponseMessage ret = this.usageCostProcessorFunction.run(req, context); + + // Verify + assertEquals(HttpStatus.OK, ret.getStatus()); + assertEquals("Message is dropped successfully", ret.getBody()); + } + +} diff --git a/cloud-claim-check-pattern/call-usage-app/src/test/java/com/iluwatar/claimcheckpattern/producer/calldetails/functions/UsageDetailPublisherFunctionTest.java b/cloud-claim-check-pattern/call-usage-app/src/test/java/com/iluwatar/claimcheckpattern/producer/calldetails/functions/UsageDetailPublisherFunctionTest.java new file mode 100644 index 000000000..60083a81b --- /dev/null +++ b/cloud-claim-check-pattern/call-usage-app/src/test/java/com/iluwatar/claimcheckpattern/producer/calldetails/functions/UsageDetailPublisherFunctionTest.java @@ -0,0 +1,119 @@ +/* + * The MIT License + * Copyright © 2014-2021 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.claimcheckpattern.producer.calldetails.functions; + +import com.iluwatar.claimcheckpattern.HttpResponseMessageMock; +import com.iluwatar.claimcheckpattern.domain.MessageHeader; +import com.iluwatar.claimcheckpattern.domain.UsageDetail; +import com.iluwatar.claimcheckpattern.utility.EventHandlerUtility; +import com.iluwatar.claimcheckpattern.utility.MessageHandlerUtility; +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.stubbing.Answer; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.*; +import java.util.logging.Logger; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +/** + * Unit test for Function class. + */ +@ExtendWith(MockitoExtension.class) +public class UsageDetailPublisherFunctionTest { + @Mock + MessageHandlerUtility mockMessageHandlerUtility; + @Mock + EventHandlerUtility mockEventHandlerUtility; + + @InjectMocks + UsageDetailPublisherFunction usageDetailPublisherFunction; + + /** + * Unit test for HttpTriggerJava method. + */ + @Test + public void shouldTriggerHttpAzureFunctionJavaWithSubscriptionValidationEventType() throws Exception { + + // Setup + @SuppressWarnings("unchecked") + final HttpRequestMessage> req = mock(HttpRequestMessage.class); + String fileAbsolutePath = getClass().getResource("/subscriptionValidationEvent.json").getPath() + .replaceAll("%20", " "), jsonBody = Files.readString(Paths.get(fileAbsolutePath)).replaceAll("\n", " "); + doReturn(Optional.of(jsonBody)).when(req).getBody(); + doAnswer(new Answer() { + @Override + public HttpResponseMessage.Builder answer(InvocationOnMock invocation) { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + } + }).when(req).createResponseBuilder(any(HttpStatus.class)); + + final ExecutionContext context = mock(ExecutionContext.class); + + // Invoke + final HttpResponseMessage ret = this.usageDetailPublisherFunction.run(req, context); + + // Verify + assertEquals(ret.getStatus(), HttpStatus.OK); + } + + @Test + public void shouldTriggerHttpAzureFunctionJavaWithUsageDetailEventType() throws Exception { + + // Setup + @SuppressWarnings("unchecked") + final HttpRequestMessage> req = mock(HttpRequestMessage.class); + String fileAbsolutePath = getClass().getResource("/usageDetailEvent.json").getPath().replaceAll("%20", " "), + jsonBody = Files.readString(Paths.get(fileAbsolutePath)).replaceAll("\n", " "); + doReturn(Optional.of(jsonBody)).when(req).getBody(); + doAnswer(new Answer() { + @Override + public HttpResponseMessage.Builder answer(InvocationOnMock invocation) { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + } + }).when(req).createResponseBuilder(any(HttpStatus.class)); + + final ExecutionContext context = mock(ExecutionContext.class); + doReturn(Logger.getGlobal()).when(context).getLogger(); + + // Invoke + final HttpResponseMessage ret = this.usageDetailPublisherFunction.run(req, context); + + // Verify + assertEquals(ret.getStatus(), HttpStatus.OK); + } + +} diff --git a/cloud-claim-check-pattern/call-usage-app/src/test/java/com/iluwatar/claimcheckpattern/utility/EventHandlerUtilityTest.java b/cloud-claim-check-pattern/call-usage-app/src/test/java/com/iluwatar/claimcheckpattern/utility/EventHandlerUtilityTest.java new file mode 100644 index 000000000..f16f60f6d --- /dev/null +++ b/cloud-claim-check-pattern/call-usage-app/src/test/java/com/iluwatar/claimcheckpattern/utility/EventHandlerUtilityTest.java @@ -0,0 +1,70 @@ +/* + * The MIT License + * Copyright © 2014-2021 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.claimcheckpattern.utility; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +import com.azure.core.util.BinaryData; +import com.azure.messaging.eventgrid.EventGridPublisherClient; +import com.iluwatar.claimcheckpattern.domain.Message; +import com.iluwatar.claimcheckpattern.domain.UsageDetail; +import java.util.logging.Logger; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class EventHandlerUtilityTest { + + @Mock + EventGridPublisherClient mockCustomEventClient; + + @InjectMocks + EventHandlerUtility> eventHandlerUtility; + + @BeforeEach + public void setUp() { + + System.setProperty("EventGridURL", "https://www.dummyEndpoint.com/api/events"); + System.setProperty("EventGridKey", "EventGridURL"); + } + + @Test + void shouldPublishEvent() { + doNothing().when(mockCustomEventClient).sendEvent(any(BinaryData.class)); + eventHandlerUtility.publishEvent(null, Logger.getLogger("logger")); + verify(mockCustomEventClient, times(1)).sendEvent(any(BinaryData.class)); + + } + + @Test + void shouldPublishEventWithNullLogger() { + eventHandlerUtility.publishEvent(null, null); + verify(mockCustomEventClient, times(1)).sendEvent(any(BinaryData.class)); + } +} diff --git a/cloud-claim-check-pattern/call-usage-app/src/test/java/com/iluwatar/claimcheckpattern/utility/MessageHandlerUtilityTest.java b/cloud-claim-check-pattern/call-usage-app/src/test/java/com/iluwatar/claimcheckpattern/utility/MessageHandlerUtilityTest.java new file mode 100644 index 000000000..4faab105d --- /dev/null +++ b/cloud-claim-check-pattern/call-usage-app/src/test/java/com/iluwatar/claimcheckpattern/utility/MessageHandlerUtilityTest.java @@ -0,0 +1,114 @@ +/* + * The MIT License + * Copyright © 2014-2021 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.claimcheckpattern.utility; + +import com.azure.storage.blob.BlobClient; +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.BlobServiceClient; +import com.iluwatar.claimcheckpattern.domain.*; +import com.iluwatar.claimcheckpattern.utility.MessageHandlerUtility; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.UUID; +import java.util.logging.Logger; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class MessageHandlerUtilityTest { + @Mock + private BlobClient mockBlobClient; + + @Mock + private BlobContainerClient mockContainerClient; + + @Mock + private BlobServiceClient mockBlobServiceClient; + + @InjectMocks + private MessageHandlerUtility messageHandlerUtility; + + private Message messageToPublish; + private MessageReference messageReference; + + @BeforeEach + public void setUp() { + System.setProperty("BlobStorageConnectionString", "https://www.dummyEndpoint.com/api/blobs"); + + var messageBody = new MessageBody(); + var usageDetailsList = new ArrayList(); + var random = new Random(); + for (int i = 0; i < 51; i++) { + var usageDetail = new UsageDetail(); + usageDetail.setUserId("userId" + i); + usageDetail.setData(random.nextInt(500)); + usageDetail.setDuration(random.nextInt(500)); + + usageDetailsList.add(usageDetail); + } + messageBody.setData(usageDetailsList); + + // Create message header + var messageHeader = new MessageHeader(); + messageHeader.setId(UUID.randomUUID().toString()); + messageHeader.setSubject("UsageDetailPublisher"); + messageHeader.setTopic("usagecostprocessorfunction-topic"); + messageHeader.setEventType("UsageDetail"); + messageHeader.setEventTime(OffsetDateTime.now().toString()); + this.messageReference = new MessageReference("callusageapp", messageHeader.getId() + "/input.json"); + messageHeader.setData(messageReference); + messageHeader.setDataVersion("v1.0"); + + // Create entire message + this.messageToPublish = new Message<>(); + this.messageToPublish.setMessageHeader(messageHeader); + this.messageToPublish.setMessageBody(messageBody); + + when(mockContainerClient.getBlobClient(anyString())).thenReturn(mockBlobClient); + when(mockBlobServiceClient.getBlobContainerClient(anyString())).thenReturn(mockContainerClient); + } + + @Test + void shouldDropMessageToPersistantStorage() { + messageHandlerUtility.dropToPersistantStorage(messageToPublish, Logger.getLogger("logger")); + verify(mockBlobServiceClient, times(1)).getBlobContainerClient(anyString()); + // verify(mockContainerClient, times(0)).exists(); + } + + @Test + void shouldReadMessageFromPersistantStorage() { + + messageHandlerUtility.readFromPersistantStorage(messageReference, Logger.getLogger("logger")); + verify(mockBlobServiceClient, times(1)).getBlobContainerClient(anyString()); + } +} diff --git a/cloud-claim-check-pattern/call-usage-app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/cloud-claim-check-pattern/call-usage-app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 000000000..ca6ee9cea --- /dev/null +++ b/cloud-claim-check-pattern/call-usage-app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline \ No newline at end of file diff --git a/cloud-claim-check-pattern/call-usage-app/src/test/resources/subscriptionValidationEvent.json b/cloud-claim-check-pattern/call-usage-app/src/test/resources/subscriptionValidationEvent.json new file mode 100644 index 000000000..52bd6ee6a --- /dev/null +++ b/cloud-claim-check-pattern/call-usage-app/src/test/resources/subscriptionValidationEvent.json @@ -0,0 +1,15 @@ +[ + { + "data": { + "validationCode": "C12F266E-79D9-4C0A-9922-5EF6201A34C2", + "validationUrl": "https://rp-centralindia.eventgrid.azure.net:553/eventsubscriptions/usagedetailpublisherfunction-subscription/validate?idu003dC12F266E-79D9-4C0A-9922-5EF6201A34C2u0026tu003d2021-10-26T08:10:52.4999377Zu0026apiVersionu003d2020-10-15-previewu0026tokenu003d30kEVoL8rAOWzQv0buurhrKnbP%2bGMtHObbA%2bax6wb4Y%3d" + }, + "dataVersion": "2", + "eventTime": "2021-10-26T08:10:52.4999377Z", + "eventType": "Microsoft.EventGrid.SubscriptionValidationEvent", + "id": "e2a8466b-3dc0-46b7-bb7d-b999e51a2848", + "metadataVersion": "1", + "subject": "", + "topic": "/subscriptions/0fef643d-a6b1-48f9-a256-53fbd0d22f48/resourceGroups/resource-group-ccp/providers/Microsoft.EventGrid/domains/event-grid-domains-ccp/topics/usagedetailpublisherfunction-topic" + } +] diff --git a/cloud-claim-check-pattern/call-usage-app/src/test/resources/usageDetailEvent.json b/cloud-claim-check-pattern/call-usage-app/src/test/resources/usageDetailEvent.json new file mode 100644 index 000000000..137649e29 --- /dev/null +++ b/cloud-claim-check-pattern/call-usage-app/src/test/resources/usageDetailEvent.json @@ -0,0 +1,15 @@ +[ + { + "data": { + "dataFileName": "d8284456-dfff-4bd4-9cef-ea99f70f4835/input.json", + "dataLocation": "callusageapp" + }, + "dataVersion": "v1.0", + "eventTime": "2021-10-25T19:17:15.7468501Z", + "eventType": "UsageDetail", + "id": "d8284456-dfff-4bd4-9cef-ea99f70f4835", + "metadataVersion": "1", + "subject": "UsageDetailPublisher", + "topic": "/subscriptions/0fef643d-a6b1-48f9-a256-ea99f70f4835/resourceGroups/resource-group-ccp/providers/Microsoft.EventGrid/domains/event-grid-domains-ccp/topics/usagecostprocessorfunction-topic" + } +] diff --git a/cloud-claim-check-pattern/etc/Claim-Check-Pattern.png b/cloud-claim-check-pattern/etc/Claim-Check-Pattern.png new file mode 100644 index 0000000000000000000000000000000000000000..a2d8040afaf6c79495f50128bf17a0272cfe7370 GIT binary patch literal 53447 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 literal 0 HcmV?d00001 diff --git a/cloud-claim-check-pattern/etc/claim-check-pattern.urm.puml b/cloud-claim-check-pattern/etc/claim-check-pattern.urm.puml new file mode 100644 index 000000000..14df8b68b --- /dev/null +++ b/cloud-claim-check-pattern/etc/claim-check-pattern.urm.puml @@ -0,0 +1,117 @@ +@startuml +class UsageDetailPublisherFunction [[java:com.iluwatar.producer.calldetails.functions]] { + -messageHandlerUtility: MessageHandlerUtility + -eventHandlerUtility: EventHandlerUtility + +run(): HttpResponseMessage +} + +class UsageCostProcessorFunction [[java:com.iluwatar.consumer.callcostprocessor.functions]] { + -messageHandlerUtilityForUsageDetail: MessageHandlerUtility + -messageHandlerUtilityForUsageCostDetail: MessageHandlerUtility + +run(): HttpResponseMessage +} + +class "MessageHandlerUtility" as MessageHandlerUtility_T [[java:com.iluwatar.claimcheckpattern.utility]] { + +readFromPersistantStorage(messageReference: MessageReference, logger: Logger): Message + +dropToPersistantStorage(message: Message, logger: Logger): void +} + +class "EventHandlerUtility" as EventHandlerUtility_T [[java:com.callusage.utility.PersistentLocalStorageUtility]] { + +publishEvent(customEvent: T, logger: Logger): void +} + +class "Message" as Message_T [[java:com.iluwatar.claimcheckpattern.domain]] { + -messageHeader: MessageHeader + -messageData: MessageData + +Message(messageHeader: MessageHeader, messageData: MessageData) + +getMessageData(): MessageData + +getMessageHeader(): MessageHeader +} + + +class MessageHeader [[java:com.iluwatar.claimcheckpattern.domain]] { + -id: String + -subject: String + -topic: String + -eventType: String + -eventTime: String + -data: Object + -dataVersion: String + +getId(): String + +setId(id: String): void + +getSubject(): String + +setSubject(subject: String): void + +getTopic(): String + +setTopic(topic: String): void + +getEventType(): String + +setEventType(eventType: String): void + +getEventTime(): String + +setEventTime(eventTime: String): void + +getData(): Object + +setData(data: Object): void + +getDataVersion(): String + +setDataVersion(dataVersion:String): void + +} + + +class "MessageBody" as MessageBody_T [[java:com.iluwatar.claimcheckpattern.domain]] { + -data: List[] T + +getData(): List[] T + +setData(data:List[] T): void +} + +class MessageReference [[java:com.iluwatar.claimcheckpattern.domain]] { + -dataLocation: String + -dataFileName: String + +getDataLocation(): String + +setDataLocation(dataLocation:String): void + +getDataFileName(): String + +setDataFileName(dataFileName:String): void +} + +class UsageDetail [[java:com.iluwatar.claimcheckpattern.domain]] { + -userId: String + -duration: int + -data: int + +getUserId(): String + +setUserId(userId: String): void + +getDuration(): long + +setDuration(duration: long): void + +getData(): long + +setData(data: long): void +} + +class UsageCostDetail [[java:com.iluwatar.claimcheckpattern.domain]] { + -userId: String + -callCost: double + -dataCost: double + +getUserId(): String + +setUserId(userId: String): void + +getCallCost(): double + +setCallCost(callCost:double): void + +getDataCost(): double + +setDataCost(dataCost:double) : void +} + + + + + + + +Message_T "1" *-- "1" MessageHeader : has +Message_T "1" *-- "1" MessageBody_T : has +MessageHeader "1" *-- "1" MessageReference : has as data object +MessageBody_T "1" *-- "1" UsageDetail: has +MessageBody_T "1" *-- "1" UsageCostDetail: has + +EventHandlerUtility_T "1" *-- "1" MessageHeader: has +MessageHandlerUtility_T "1" *-- "1" Message_T: has + +UsageDetailPublisherFunction "1" *-- "1" MessageHandlerUtility_T : has +UsageDetailPublisherFunction "1" *-- "1" EventHandlerUtility_T : has + +UsageCostProcessorFunction "1" *-- "1" MessageHandlerUtility_T : has +UsageCostProcessorFunction "1" *-- "1" MessageHandlerUtility_T : has +@enduml \ No newline at end of file diff --git a/cloud-claim-check-pattern/etc/class-diagram.png b/cloud-claim-check-pattern/etc/class-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..d627b1b70c21f4fad1079a0c01ebe385965da2b4 GIT binary patch literal 110789 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 literal 0 HcmV?d00001 diff --git a/cloud-claim-check-pattern/pom.xml b/cloud-claim-check-pattern/pom.xml new file mode 100644 index 000000000..3f41053c0 --- /dev/null +++ b/cloud-claim-check-pattern/pom.xml @@ -0,0 +1,69 @@ + + + + + + com.iluwatar + java-design-patterns + 1.25.0-SNAPSHOT + + + 4.0.0 + claim-check-pattern + pom + + + call-usage-app + + + + com.google.code.gson + gson + 2.8.8 + + + org.junit.jupiter + junit-jupiter + 5.8.1 + test + + + org.mockito + mockito-junit-jupiter + 4.0.0 + test + + + + org.mockito + mockito-inline + 4.0.0 + test + + + + \ No newline at end of file From 5d78a77b97f974f26deab72b6c8c2e21a2cdf43f Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sat, 11 Dec 2021 17:08:30 +0200 Subject: [PATCH 03/21] docs: add Shrirang97 as a contributor for review, code (#1927) * docs: update README.md [skip ci] * docs: update .all-contributorsrc [skip ci] Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 10 ++++++++++ README.md | 3 ++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 3aaf8696a..675121ffe 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1739,6 +1739,16 @@ "contributions": [ "review" ] + }, + { + "login": "Shrirang97", + "name": "Shrirang", + "avatar_url": "https://avatars.githubusercontent.com/u/28738668?v=4", + "profile": "https://github.com/Shrirang97", + "contributions": [ + "review", + "code" + ] } ], "contributorsPerLine": 7, diff --git a/README.md b/README.md index ce4578984..002d99bc9 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) [![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) -[![All Contributors](https://img.shields.io/badge/all_contributors-191-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-192-orange.svg?style=flat-square)](#contributors-)
@@ -321,6 +321,7 @@ This project is licensed under the terms of the MIT license.
Abhinav Vashisth

đź“–
Kevin

đź‘€ +
Shrirang

👀 💻 From b1242629c86022adf6d6b168a61c7b50a7bfc707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilkka=20Sepp=C3=A4l=C3=A4?= Date: Thu, 16 Dec 2021 16:29:06 +0200 Subject: [PATCH 04/21] fix: Grammatical fixes for Adapter (#1783) * Grammatical fixes * Update adapter/README.md Co-authored-by: Subhrodip Mohanta --- adapter/README.md | 30 +++++++++---------- .../iluwatar/adapter/AdapterPatternTest.java | 2 +- .../java/com/iluwatar/adapter/AppTest.java | 4 +-- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/adapter/README.md b/adapter/README.md index df8efe1b7..11fb7581b 100644 --- a/adapter/README.md +++ b/adapter/README.md @@ -18,10 +18,10 @@ couldn't otherwise because of incompatible interfaces. ## Explanation -Real world example +Real-world example -> Consider that you have some pictures in your memory card and you need to transfer them to your computer. In order to transfer them you need some kind of adapter that is compatible with your computer ports so that you can attach memory card to your computer. In this case card reader is an adapter. -> Another example would be the famous power adapter; a three legged plug can't be connected to a two pronged outlet, it needs to use a power adapter that makes it compatible with the two pronged outlet. +> Consider that you have some pictures on your memory card and you need to transfer them to your computer. To transfer them, you need some kind of adapter that is compatible with your computer ports so that you can attach a memory card to your computer. In this case card reader is an adapter. +> Another example would be the famous power adapter; a three-legged plug can't be connected to a two-pronged outlet, it needs to use a power adapter that makes it compatible with the two-pronged outlets. > Yet another example would be a translator translating words spoken by one person to another In plain words @@ -36,7 +36,7 @@ Wikipedia says Consider a captain that can only use rowing boats and cannot sail at all. -First we have interfaces `RowingBoat` and `FishingBoat` +First, we have interfaces `RowingBoat` and `FishingBoat` ```java public interface RowingBoat { @@ -68,7 +68,7 @@ public class Captain { } ``` -Now let's say the pirates are coming and our captain needs to escape but there is only fishing boat available. We need to create an adapter that allows the captain to operate the fishing boat with his rowing boat skills. +Now let's say the pirates are coming and our captain needs to escape but there is only a fishing boat available. We need to create an adapter that allows the captain to operate the fishing boat with his rowing boat skills. ```java @Slf4j @@ -100,10 +100,10 @@ captain.row(); ## Applicability Use the Adapter pattern when -* you want to use an existing class, and its interface does not match the one you need -* you want to create a reusable class that cooperates with unrelated or unforeseen classes, that is, classes that don't necessarily have compatible interfaces -* you need to use several existing subclasses, but it's impractical to adapt their interface by subclassing every one. An object adapter can adapt the interface of its parent class. -* most of the applications using third party libraries use adapters as a middle layer between the application and the 3rd party library to decouple the application from the library. If another library has to be used only an adapter for the new library is required without having to change the application code. +* You want to use an existing class, and its interface does not match the one you need +* You want to create a reusable class that cooperates with unrelated or unforeseen classes, that is, classes that don't necessarily have compatible interfaces +* You need to use several existing subclasses, but it's impractical to adapt their interface by subclassing everyone. An object adapter can adapt the interface of its parent class. +* Most of the applications using third-party libraries use adapters as a middle layer between the application and the 3rd party library to decouple the application from the library. If another library has to be used only an adapter for the new library is required without having to change the application code. ## Tutorials @@ -114,17 +114,17 @@ Use the Adapter pattern when ## Consequences Class and object adapters have different trade-offs. A class adapter -* adapts Adaptee to Target by committing to a concrete Adaptee class. As a consequence, a class adapter won’t work when we want to adapt a class and all its subclasses. -* let’s Adapter override some of Adaptee’s behavior, since Adapter is a subclass of Adaptee. -* introduces only one object, and no additional pointer indirection is needed to get to the adaptee. +* Adapts Adaptee to Target by committing to a concrete Adaptee class. As a consequence, a class adapter won’t work when we want to adapt a class and all its subclasses. +* Let’s Adapter override some of Adaptee’s behavior since Adapter is a subclass of Adaptee. +* Introduces only one object, and no additional pointer indirection is needed to get to the adaptee. An object adapter -* let’s a single Adapter work with many Adaptees—that is, the Adaptee itself and all of its subclasses (if any). The Adapter can also add functionality to all Adaptees at once. -* makes it harder to override Adaptee behavior. It will require subclassing Adaptee and making Adapter refer to the subclass rather than the Adaptee itself. +* Lets a single Adapter work with many Adaptees—that is, the Adaptee itself and all of its subclasses (if any). The Adapter can also add functionality to all Adaptees at once. +* Makes it harder to override Adaptee behavior. It will require subclassing Adaptee and making the Adapter refer to the subclass rather than the Adaptee itself. -## Known uses +## Real-world examples * [java.util.Arrays#asList()](http://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#asList%28T...%29) * [java.util.Collections#list()](https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#list-java.util.Enumeration-) diff --git a/adapter/src/test/java/com/iluwatar/adapter/AdapterPatternTest.java b/adapter/src/test/java/com/iluwatar/adapter/AdapterPatternTest.java index fc55cd69e..661e91319 100644 --- a/adapter/src/test/java/com/iluwatar/adapter/AdapterPatternTest.java +++ b/adapter/src/test/java/com/iluwatar/adapter/AdapterPatternTest.java @@ -33,7 +33,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; /** - * Test class + * Tests for the adapter pattern. */ class AdapterPatternTest { diff --git a/adapter/src/test/java/com/iluwatar/adapter/AppTest.java b/adapter/src/test/java/com/iluwatar/adapter/AppTest.java index 8b224e451..802a743dc 100644 --- a/adapter/src/test/java/com/iluwatar/adapter/AppTest.java +++ b/adapter/src/test/java/com/iluwatar/adapter/AppTest.java @@ -33,9 +33,7 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; class AppTest { /** - * Issue: Add at least one assertion to this test case. - * - * Solution: Inserted assertion to check whether the execution of the main method in {@link App} + * Check whether the execution of the main method in {@link App} * throws an exception. */ From fee898cd27dd64d3daa846cc15ad377b8bcce1aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilkka=20Sepp=C3=A4l=C3=A4?= Date: Thu, 16 Dec 2021 16:30:25 +0200 Subject: [PATCH 05/21] fix: Add language to claim check frontmatter (#1928) Co-authored-by: Subhrodip Mohanta --- cloud-claim-check-pattern/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/cloud-claim-check-pattern/README.md b/cloud-claim-check-pattern/README.md index 0259f5782..81f49e068 100644 --- a/cloud-claim-check-pattern/README.md +++ b/cloud-claim-check-pattern/README.md @@ -4,6 +4,7 @@ title: Claim Check Pattern folder: cloud-claim-check-pattern permalink: /patterns/cloud-claim-check-pattern/ categories: Cloud +language: en tags: - Cloud distributed - Microservices From 600227d2e45273488bc8d611848b67115f7f39d6 Mon Sep 17 00:00:00 2001 From: interactwithankush Date: Thu, 16 Dec 2021 20:05:13 +0530 Subject: [PATCH 06/21] fix: Sonar report - fix blocker and critical ones (#1899) * update SpatialPartitionBubbles - fix Sonar blocker issue * fix Sonar critical issue - Define constant instead of duplicating the literal * fix Sonar critical issue - remove unnecessary default constructor * fix Sonar critical issue - Define constant instead of duplicating the literal * fix Sonar critical issue - Define constant instead of duplicating the literal * fix Sonar critical issue - Define constant instead of duplicating the literal * fix Sonar critical issue - fix checkstyle issue * fix Sonar critical issue - fix code smells * fix Sonar critical issue - fix code smells * fix Sonar critical issue - fix code smells * fix sonarbugs - adding test cases for Commander class * sonar fix - add assert commands in CommanderTest * sonar fix - add test cases for CommanderTest * sonar fix - add test cases for CommanderTest * sonar fix - add test cases for CommanderTest * sonar fix - add test cases for CommanderTest * sonar fix - add test cases for CommanderTest * sonar fix - add test cases for CommanderTest * sonar fix - add test cases for CommanderTest * sonar bug fix & test cases * sonar bug fix & test cases * sonar bug fix & test cases * sonar bug fix & test cases * sonar bug fix & test cases * Revert "sonar bug fix & test cases" This reverts commit 640dd55e35a9730e981d14665913f3d9b5b2d3b2. * sonar bug fix & test cases * sonar bug fix & test cases * sonar bug fix & test cases * sonar bug fix : avoid Thread.sleep * sonar bug fix : cleanup Thread.sleep * sonar bug fix: test commit * sonar bug fix: test commit Co-authored-by: Subhrodip Mohanta Co-authored-by: atayal --- .../java/com/iluwatar/command/Wizard.java | 3 - .../com/iluwatar/commander/Commander.java | 148 ++--- .../com/iluwatar/commander/CommanderTest.java | 526 ++++++++++++++++++ .../SpatialPartitionBubbles.java | 8 +- 4 files changed, 608 insertions(+), 77 deletions(-) create mode 100644 commander/src/test/java/com/iluwatar/commander/CommanderTest.java diff --git a/command/src/main/java/com/iluwatar/command/Wizard.java b/command/src/main/java/com/iluwatar/command/Wizard.java index 4ea84e8c3..aa1f338ea 100644 --- a/command/src/main/java/com/iluwatar/command/Wizard.java +++ b/command/src/main/java/com/iluwatar/command/Wizard.java @@ -36,9 +36,6 @@ public class Wizard { private final Deque undoStack = new LinkedList<>(); private final Deque redoStack = new LinkedList<>(); - public Wizard() { - } - /** * Cast spell. */ diff --git a/commander/src/main/java/com/iluwatar/commander/Commander.java b/commander/src/main/java/com/iluwatar/commander/Commander.java index 59cdc9b22..291704884 100644 --- a/commander/src/main/java/com/iluwatar/commander/Commander.java +++ b/commander/src/main/java/com/iluwatar/commander/Commander.java @@ -90,6 +90,13 @@ public class Commander { private static final Logger LOG = LoggerFactory.getLogger(Commander.class); //we could also have another db where it stores all orders + private static final String ORDER_ID = "Order {}"; + private static final String REQUEST_ID = " request Id: {}"; + private static final String ERROR_CONNECTING_MSG_SVC = + ": Error in connecting to messaging service "; + private static final String TRY_CONNECTING_MSG_SVC = + ": Trying to connect to messaging service.."; + Commander(EmployeeHandle empDb, PaymentService paymentService, ShippingService shippingService, MessagingService messagingService, QueueDatabase qdb, int numOfRetries, long retryDuration, long queueTime, long queueTaskTime, long paymentTime, @@ -118,17 +125,17 @@ public class Commander { Retry.Operation op = (l) -> { if (!l.isEmpty()) { if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) { - LOG.debug("Order " + order.id + ": Error in connecting to shipping service, " - + "trying again.."); + LOG.debug(ORDER_ID + ": Error in connecting to shipping service, " + + "trying again..", order.id); } else { - LOG.debug("Order " + order.id + ": Error in creating shipping request.."); + LOG.debug(ORDER_ID + ": Error in creating shipping request..", order.id); } throw l.remove(0); } String transactionId = shippingService.receiveRequest(order.item, order.user.address); //could save this transaction id in a db too - LOG.info("Order " + order.id + ": Shipping placed successfully, transaction id: " - + transactionId); + LOG.info(ORDER_ID + ": Shipping placed successfully, transaction id: {}", + order.id, transactionId); LOG.info("Order has been placed and will be shipped to you. Please wait while we make your" + " payment... "); sendPaymentRequest(order); @@ -138,19 +145,19 @@ public class Commander { LOG.info("Shipping is currently not possible to your address. We are working on the problem" + " and will get back to you asap."); finalSiteMsgShown = true; - LOG.info("Order " + order.id + ": Shipping not possible to address, trying to add problem " - + "to employee db.."); + LOG.info(ORDER_ID + ": Shipping not possible to address, trying to add problem " + + "to employee db..", order.id); employeeHandleIssue(o); } else if (ItemUnavailableException.class.isAssignableFrom(err.getClass())) { LOG.info("This item is currently unavailable. We will inform you as soon as the item " + "becomes available again."); finalSiteMsgShown = true; - LOG.info("Order " + order.id + ": Item " + order.item + " unavailable, trying to add " - + "problem to employee handle.."); + LOG.info(ORDER_ID + ": Item {}" + " unavailable, trying to add " + + "problem to employee handle..", order.id, order.item); employeeHandleIssue(o); } else { LOG.info("Sorry, there was a problem in creating your order. Please try later."); - LOG.error("Order " + order.id + ": Shipping service unavailable, order not placed.."); + LOG.error(ORDER_ID + ": Shipping service unavailable, order not placed..", order.id); finalSiteMsgShown = true; } }; @@ -164,7 +171,7 @@ public class Commander { if (order.paid.equals(PaymentStatus.TRYING)) { order.paid = PaymentStatus.NOT_DONE; sendPaymentFailureMessage(order); - LOG.error("Order " + order.id + ": Payment time for order over, failed and returning.."); + LOG.error(ORDER_ID + ": Payment time for order over, failed and returning..", order.id); } //if succeeded or failed, would have been dequeued, no attempt to make payment return; } @@ -173,17 +180,18 @@ public class Commander { Retry.Operation op = (l) -> { if (!l.isEmpty()) { if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) { - LOG.debug("Order " + order.id + ": Error in connecting to payment service," - + " trying again.."); + LOG.debug(ORDER_ID + ": Error in connecting to payment service," + + " trying again..", order.id); } else { - LOG.debug("Order " + order.id + ": Error in creating payment request.."); + LOG.debug(ORDER_ID + ": Error in creating payment request..", order.id); } throw l.remove(0); } if (order.paid.equals(PaymentStatus.TRYING)) { var transactionId = paymentService.receiveRequest(order.price); order.paid = PaymentStatus.DONE; - LOG.info("Order " + order.id + ": Payment successful, transaction Id: " + transactionId); + LOG.info(ORDER_ID + ": Payment successful, transaction Id: {}", + order.id, transactionId); if (!finalSiteMsgShown) { LOG.info("Payment made successfully, thank you for shopping with us!!"); finalSiteMsgShown = true; @@ -199,7 +207,7 @@ public class Commander { + "Meanwhile, your order has been converted to COD and will be shipped."); finalSiteMsgShown = true; } - LOG.error("Order " + order.id + ": Payment details incorrect, failed.."); + LOG.error(ORDER_ID + ": Payment details incorrect, failed..", order.id); o.paid = PaymentStatus.NOT_DONE; sendPaymentFailureMessage(o); } else { @@ -209,7 +217,7 @@ public class Commander { + "asap. Don't worry, your order has been placed and will be shipped."); finalSiteMsgShown = true; } - LOG.warn("Order " + order.id + ": Payment error, going to queue.."); + LOG.warn(ORDER_ID + ": Payment error, going to queue..", order.id); sendPaymentPossibleErrorMsg(o); } if (o.paid.equals(PaymentStatus.TRYING) && System @@ -234,7 +242,7 @@ public class Commander { if (System.currentTimeMillis() - qt.order.createdTime >= this.queueTime) { // since payment time is lesser than queuetime it would have already failed.. // additional check not needed - LOG.trace("Order " + qt.order.id + ": Queue time for order over, failed.."); + LOG.trace(ORDER_ID + ": Queue time for order over, failed..", qt.order.id); return; } else if (qt.taskType.equals(TaskType.PAYMENT) && !qt.order.paid.equals(PaymentStatus.TRYING) || qt.taskType.equals(TaskType.MESSAGING) && (qt.messageType == 1 @@ -242,30 +250,30 @@ public class Commander { || qt.order.messageSent.equals(MessageSent.PAYMENT_FAIL) || qt.order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) || qt.taskType.equals(TaskType.EMPLOYEE_DB) && qt.order.addedToEmployeeHandle) { - LOG.trace("Order " + qt.order.id + ": Not queueing task since task already done.."); + LOG.trace(ORDER_ID + ": Not queueing task since task already done..", qt.order.id); return; } var list = queue.exceptionsList; Thread t = new Thread(() -> { Retry.Operation op = (list1) -> { if (!list1.isEmpty()) { - LOG.warn("Order " + qt.order.id + ": Error in connecting to queue db, trying again.."); + LOG.warn(ORDER_ID + ": Error in connecting to queue db, trying again..", qt.order.id); throw list1.remove(0); } queue.add(qt); queueItems++; - LOG.info("Order " + qt.order.id + ": " + qt.getType() + " task enqueued.."); + LOG.info(ORDER_ID + ": {}" + " task enqueued..", qt.order.id, qt.getType()); tryDoingTasksInQueue(); }; Retry.HandleErrorIssue handleError = (qt1, err) -> { if (qt1.taskType.equals(TaskType.PAYMENT)) { qt1.order.paid = PaymentStatus.NOT_DONE; sendPaymentFailureMessage(qt1.order); - LOG.error("Order " + qt1.order.id + ": Unable to enqueue payment task," - + " payment failed.."); + LOG.error(ORDER_ID + ": Unable to enqueue payment task," + + " payment failed..", qt1.order.id); } - LOG.error("Order " + qt1.order.id + ": Unable to enqueue task of type " + qt1.getType() - + ", trying to add to employee handle.."); + LOG.error(ORDER_ID + ": Unable to enqueue task of type {}" + + ", trying to add to employee handle..", qt1.order.id, qt1.getType()); employeeHandleIssue(qt1.order); }; var r = new Retry<>(op, handleError, numOfRetries, retryDuration, @@ -328,7 +336,7 @@ public class Commander { private void sendSuccessMessage(Order order) { if (System.currentTimeMillis() - order.createdTime >= this.messageTime) { - LOG.trace("Order " + order.id + ": Message time for order over, returning.."); + LOG.trace(ORDER_ID + ": Message time for order over, returning..", order.id); return; } var list = messagingService.exceptionsList; @@ -354,8 +362,8 @@ public class Commander { && System.currentTimeMillis() - o.createdTime < messageTime) { var qt = new QueueTask(order, TaskType.MESSAGING, 2); updateQueue(qt); - LOG.info("Order " + order.id + ": Error in sending Payment Success message, trying to" - + " queue task and add to employee handle.."); + LOG.info(ORDER_ID + ": Error in sending Payment Success message, trying to" + + " queue task and add to employee handle..", order.id); employeeHandleIssue(order); } } @@ -364,11 +372,11 @@ public class Commander { return (l) -> { if (!l.isEmpty()) { if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) { - LOG.debug("Order " + order.id + ": Error in connecting to messaging service " - + "(Payment Success msg), trying again.."); + LOG.debug(ORDER_ID + ERROR_CONNECTING_MSG_SVC + + "(Payment Success msg), trying again..", order.id); } else { - LOG.debug("Order " + order.id + ": Error in creating Payment Success" - + " messaging request.."); + LOG.debug(ORDER_ID + ": Error in creating Payment Success" + + " messaging request..", order.id); } throw l.remove(0); } @@ -376,15 +384,15 @@ public class Commander { && !order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) { var requestId = messagingService.receiveRequest(2); order.messageSent = MessageSent.PAYMENT_SUCCESSFUL; - LOG.info("Order " + order.id + ": Payment Success message sent," - + " request Id: " + requestId); + LOG.info(ORDER_ID + ": Payment Success message sent," + + REQUEST_ID, order.id, requestId); } }; } private void sendPaymentFailureMessage(Order order) { if (System.currentTimeMillis() - order.createdTime >= this.messageTime) { - LOG.trace("Order " + order.id + ": Message time for order over, returning.."); + LOG.trace(ORDER_ID + ": Message time for order over, returning..", order.id); return; } var list = messagingService.exceptionsList; @@ -412,8 +420,8 @@ public class Commander { && System.currentTimeMillis() - o.createdTime < messageTime) { var qt = new QueueTask(order, TaskType.MESSAGING, 0); updateQueue(qt); - LOG.warn("Order " + order.id + ": Error in sending Payment Failure message, " - + "trying to queue task and add to employee handle.."); + LOG.warn(ORDER_ID + ": Error in sending Payment Failure message, " + + "trying to queue task and add to employee handle..", order.id); employeeHandleIssue(o); } } @@ -421,11 +429,11 @@ public class Commander { private void handlePaymentFailureRetryOperation(Order order, List l) throws Exception { if (!l.isEmpty()) { if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) { - LOG.debug("Order " + order.id + ": Error in connecting to messaging service " - + "(Payment Failure msg), trying again.."); + LOG.debug(ORDER_ID + ERROR_CONNECTING_MSG_SVC + + "(Payment Failure msg), trying again..", order.id); } else { - LOG.debug("Order " + order.id + ": Error in creating Payment Failure" - + " message request.."); + LOG.debug(ORDER_ID + ": Error in creating Payment Failure" + + " message request..", order.id); } throw l.remove(0); } @@ -433,8 +441,8 @@ public class Commander { && !order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) { var requestId = messagingService.receiveRequest(0); order.messageSent = MessageSent.PAYMENT_FAIL; - LOG.info("Order " + order.id + ": Payment Failure message sent successfully," - + " request Id: " + requestId); + LOG.info(ORDER_ID + ": Payment Failure message sent successfully," + + REQUEST_ID, order.id, requestId); } } @@ -469,7 +477,7 @@ public class Commander { var qt = new QueueTask(order, TaskType.MESSAGING, 1); updateQueue(qt); LOG.warn("Order " + order.id + ": Error in sending Payment Error message, " - + "trying to queue task and add to employee handle.."); + + "trying to queue task and add to employee handle.."); employeeHandleIssue(o); } } @@ -478,11 +486,11 @@ public class Commander { throws Exception { if (!l.isEmpty()) { if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) { - LOG.debug("Order " + order.id + ": Error in connecting to messaging service " - + "(Payment Error msg), trying again.."); + LOG.debug(ORDER_ID + ERROR_CONNECTING_MSG_SVC + + "(Payment Error msg), trying again..", order.id); } else { - LOG.debug("Order " + order.id + ": Error in creating Payment Error" - + " messaging request.."); + LOG.debug(ORDER_ID + ": Error in creating Payment Error" + + " messaging request..", order.id); } throw l.remove(0); } @@ -490,28 +498,28 @@ public class Commander { .equals(MessageSent.NONE_SENT)) { var requestId = messagingService.receiveRequest(1); order.messageSent = MessageSent.PAYMENT_TRYING; - LOG.info("Order " + order.id + ": Payment Error message sent successfully," - + " request Id: " + requestId); + LOG.info(ORDER_ID + ": Payment Error message sent successfully," + + REQUEST_ID, order.id, requestId); } } private void employeeHandleIssue(Order order) { if (System.currentTimeMillis() - order.createdTime >= this.employeeTime) { - LOG.trace("Order " + order.id + ": Employee handle time for order over, returning.."); + LOG.trace(ORDER_ID + ": Employee handle time for order over, returning..", order.id); return; } var list = employeeDb.exceptionsList; var t = new Thread(() -> { Retry.Operation op = (l) -> { if (!l.isEmpty()) { - LOG.warn("Order " + order.id + ": Error in connecting to employee handle," - + " trying again.."); + LOG.warn(ORDER_ID + ": Error in connecting to employee handle," + + " trying again..", order.id); throw l.remove(0); } if (!order.addedToEmployeeHandle) { employeeDb.receiveRequest(order); order.addedToEmployeeHandle = true; - LOG.info("Order " + order.id + ": Added order to employee database"); + LOG.info(ORDER_ID + ": Added order to employee database", order.id); } }; Retry.HandleErrorIssue handleError = (o, err) -> { @@ -519,8 +527,8 @@ public class Commander { .currentTimeMillis() - order.createdTime < employeeTime) { var qt = new QueueTask(order, TaskType.EMPLOYEE_DB, -1); updateQueue(qt); - LOG.warn("Order " + order.id + ": Error in adding to employee db," - + " trying to queue task.."); + LOG.warn(ORDER_ID + ": Error in adding to employee db," + + " trying to queue task..", order.id); } }; var r = new Retry<>(op, handleError, numOfRetries, retryDuration, @@ -538,51 +546,51 @@ public class Commander { if (queueItems != 0) { var qt = queue.peek(); //this should probably be cloned here //this is why we have retry for doTasksInQueue - LOG.trace("Order " + qt.order.id + ": Started doing task of type " + qt.getType()); + LOG.trace(ORDER_ID + ": Started doing task of type {}", qt.order.id, qt.getType()); if (qt.getFirstAttemptTime() == -1) { qt.setFirstAttemptTime(System.currentTimeMillis()); } if (System.currentTimeMillis() - qt.getFirstAttemptTime() >= queueTaskTime) { tryDequeue(); - LOG.trace("Order " + qt.order.id + ": This queue task of type " + qt.getType() - + " does not need to be done anymore (timeout), dequeue.."); + LOG.trace(ORDER_ID + ": This queue task of type {}" + + " does not need to be done anymore (timeout), dequeue..", qt.order.id, qt.getType()); } else { if (qt.taskType.equals(TaskType.PAYMENT)) { if (!qt.order.paid.equals(PaymentStatus.TRYING)) { tryDequeue(); - LOG.trace("Order " + qt.order.id + ": This payment task already done, dequeueing.."); + LOG.trace(ORDER_ID + ": This payment task already done, dequeueing..", qt.order.id); } else { sendPaymentRequest(qt.order); - LOG.debug("Order " + qt.order.id + ": Trying to connect to payment service.."); + LOG.debug(ORDER_ID + ": Trying to connect to payment service..", qt.order.id); } } else if (qt.taskType.equals(TaskType.MESSAGING)) { if (qt.order.messageSent.equals(MessageSent.PAYMENT_FAIL) || qt.order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) { tryDequeue(); - LOG.trace("Order " + qt.order.id + ": This messaging task already done, dequeue.."); + LOG.trace(ORDER_ID + ": This messaging task already done, dequeue..", qt.order.id); } else if (qt.messageType == 1 && (!qt.order.messageSent.equals(MessageSent.NONE_SENT) || !qt.order.paid.equals(PaymentStatus.TRYING))) { tryDequeue(); - LOG.trace("Order " + qt.order.id + ": This messaging task does not need to be done," - + " dequeue.."); + LOG.trace(ORDER_ID + ": This messaging task does not need to be done," + + " dequeue..", qt.order.id); } else if (qt.messageType == 0) { sendPaymentFailureMessage(qt.order); - LOG.debug("Order " + qt.order.id + ": Trying to connect to messaging service.."); + LOG.debug(ORDER_ID + TRY_CONNECTING_MSG_SVC, qt.order.id); } else if (qt.messageType == 1) { sendPaymentPossibleErrorMsg(qt.order); - LOG.debug("Order " + qt.order.id + ": Trying to connect to messaging service.."); + LOG.debug(ORDER_ID + TRY_CONNECTING_MSG_SVC, qt.order.id); } else if (qt.messageType == 2) { sendSuccessMessage(qt.order); - LOG.debug("Order " + qt.order.id + ": Trying to connect to messaging service.."); + LOG.debug(ORDER_ID + TRY_CONNECTING_MSG_SVC, qt.order.id); } } else if (qt.taskType.equals(TaskType.EMPLOYEE_DB)) { if (qt.order.addedToEmployeeHandle) { tryDequeue(); - LOG.trace("Order " + qt.order.id + ": This employee handle task already done," - + " dequeue.."); + LOG.trace(ORDER_ID + ": This employee handle task already done," + + " dequeue..", qt.order.id); } else { employeeHandleIssue(qt.order); - LOG.debug("Order " + qt.order.id + ": Trying to connect to employee handle.."); + LOG.debug(ORDER_ID + ": Trying to connect to employee handle..", qt.order.id); } } } diff --git a/commander/src/test/java/com/iluwatar/commander/CommanderTest.java b/commander/src/test/java/com/iluwatar/commander/CommanderTest.java new file mode 100644 index 000000000..094cf7cf6 --- /dev/null +++ b/commander/src/test/java/com/iluwatar/commander/CommanderTest.java @@ -0,0 +1,526 @@ +package com.iluwatar.commander; + +import com.iluwatar.commander.employeehandle.EmployeeDatabase; +import com.iluwatar.commander.employeehandle.EmployeeHandle; +import com.iluwatar.commander.exceptions.DatabaseUnavailableException; +import com.iluwatar.commander.exceptions.ItemUnavailableException; +import com.iluwatar.commander.exceptions.PaymentDetailsErrorException; +import com.iluwatar.commander.exceptions.ShippingNotPossibleException; +import com.iluwatar.commander.messagingservice.MessagingDatabase; +import com.iluwatar.commander.messagingservice.MessagingService; +import com.iluwatar.commander.paymentservice.PaymentDatabase; +import com.iluwatar.commander.paymentservice.PaymentService; +import com.iluwatar.commander.queue.QueueDatabase; +import com.iluwatar.commander.shippingservice.ShippingDatabase; +import com.iluwatar.commander.shippingservice.ShippingService; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.util.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +class CommanderTest { + + private final int numOfRetries = 1; + private final long retryDuration = 1_000; + private long queueTime = 1_00; + private long queueTaskTime = 1_000; + private long paymentTime = 6_000; + private long messageTime = 5_000; + private long employeeTime = 2_000; + + private static final List exceptionList = new ArrayList<>(); + + static { + exceptionList.add(new DatabaseUnavailableException()); + exceptionList.add(new ShippingNotPossibleException()); + exceptionList.add(new ItemUnavailableException()); + exceptionList.add(new PaymentDetailsErrorException()); + exceptionList.add(new IllegalStateException()); + } + + private Commander buildCommanderObject() { + return buildCommanderObject(false); + } + + private Commander buildCommanderObject(boolean nonPaymentException) { + PaymentService paymentService = new PaymentService + (new PaymentDatabase(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + + ShippingService shippingService; + MessagingService messagingService; + if (nonPaymentException) { + shippingService = new ShippingService(new ShippingDatabase(), new DatabaseUnavailableException()); + messagingService = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException()); + + } else { + shippingService = new ShippingService(new ShippingDatabase(), new DatabaseUnavailableException()); + messagingService = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException()); + + } + var employeeHandle = new EmployeeHandle + (new EmployeeDatabase(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var qdb = new QueueDatabase + (new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException()); + return new Commander(employeeHandle, paymentService, shippingService, + messagingService, qdb, numOfRetries, retryDuration, + queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + } + + private Commander buildCommanderObjectVanilla() { + PaymentService paymentService = new PaymentService + (new PaymentDatabase(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var shippingService = new ShippingService(new ShippingDatabase()); + var messagingService = new MessagingService(new MessagingDatabase()); + var employeeHandle = new EmployeeHandle + (new EmployeeDatabase(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var qdb = new QueueDatabase + (new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException()); + return new Commander(employeeHandle, paymentService, shippingService, + messagingService, qdb, numOfRetries, retryDuration, + queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + } + + private Commander buildCommanderObjectUnknownException() { + PaymentService paymentService = new PaymentService + (new PaymentDatabase(), new IllegalStateException()); + var shippingService = new ShippingService(new ShippingDatabase()); + var messagingService = new MessagingService(new MessagingDatabase()); + var employeeHandle = new EmployeeHandle + (new EmployeeDatabase(), new IllegalStateException()); + var qdb = new QueueDatabase + (new DatabaseUnavailableException(), new IllegalStateException()); + return new Commander(employeeHandle, paymentService, shippingService, + messagingService, qdb, numOfRetries, retryDuration, + queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + } + + private Commander buildCommanderObjectNoPaymentException1() { + PaymentService paymentService = new PaymentService + (new PaymentDatabase()); + var shippingService = new ShippingService(new ShippingDatabase()); + var messagingService = new MessagingService(new MessagingDatabase()); + var employeeHandle = new EmployeeHandle + (new EmployeeDatabase(), new IllegalStateException()); + var qdb = new QueueDatabase + (new DatabaseUnavailableException(), new IllegalStateException()); + return new Commander(employeeHandle, paymentService, shippingService, + messagingService, qdb, numOfRetries, retryDuration, + queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + } + + private Commander buildCommanderObjectNoPaymentException2() { + PaymentService paymentService = new PaymentService + (new PaymentDatabase()); + var shippingService = new ShippingService(new ShippingDatabase()); + var messagingService = new MessagingService(new MessagingDatabase(), new IllegalStateException()); + var employeeHandle = new EmployeeHandle + (new EmployeeDatabase(), new IllegalStateException()); + var qdb = new QueueDatabase + (new DatabaseUnavailableException(), new IllegalStateException()); + return new Commander(employeeHandle, paymentService, shippingService, + messagingService, qdb, numOfRetries, retryDuration, + queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + } + + private Commander buildCommanderObjectNoPaymentException3() { + PaymentService paymentService = new PaymentService + (new PaymentDatabase()); + var shippingService = new ShippingService(new ShippingDatabase()); + var messagingService = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException()); + var employeeHandle = new EmployeeHandle + (new EmployeeDatabase(), new IllegalStateException()); + var qdb = new QueueDatabase + (new DatabaseUnavailableException(), new IllegalStateException()); + return new Commander(employeeHandle, paymentService, shippingService, + messagingService, qdb, numOfRetries, retryDuration, + queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + } + + private Commander buildCommanderObjectWithDB() { + return buildCommanderObjectWithoutDB(false, false, new IllegalStateException()); + } + + private Commander buildCommanderObjectWithDB(boolean includeException, boolean includeDBException, Exception e) { + var l = includeDBException ? new DatabaseUnavailableException() : e; + PaymentService paymentService; + ShippingService shippingService; + MessagingService messagingService; + EmployeeHandle employeeHandle; + if (includeException) { + paymentService = new PaymentService + (new PaymentDatabase(), l); + shippingService = new ShippingService(new ShippingDatabase(), l); + messagingService = new MessagingService(new MessagingDatabase(), l); + employeeHandle = new EmployeeHandle + (new EmployeeDatabase(), l); + } else { + paymentService = new PaymentService + (null); + shippingService = new ShippingService(null); + messagingService = new MessagingService(null); + employeeHandle = new EmployeeHandle + (null); + } + + + return new Commander(employeeHandle, paymentService, shippingService, + messagingService, null, numOfRetries, retryDuration, + queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + } + + private Commander buildCommanderObjectWithoutDB() { + return buildCommanderObjectWithoutDB(false, false, new IllegalStateException()); + } + + private Commander buildCommanderObjectWithoutDB(boolean includeException, boolean includeDBException, Exception e) { + var l = includeDBException ? new DatabaseUnavailableException() : e; + PaymentService paymentService; + ShippingService shippingService; + MessagingService messagingService; + EmployeeHandle employeeHandle; + if (includeException) { + paymentService = new PaymentService + (null, l); + shippingService = new ShippingService(null, l); + messagingService = new MessagingService(null, l); + employeeHandle = new EmployeeHandle + (null, l); + } else { + paymentService = new PaymentService + (null); + shippingService = new ShippingService(null); + messagingService = new MessagingService(null); + employeeHandle = new EmployeeHandle + (null); + } + + + return new Commander(employeeHandle, paymentService, shippingService, + messagingService, null, numOfRetries, retryDuration, + queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + } + + @Test + void testPlaceOrderVanilla() throws Exception { + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + Commander c = buildCommanderObjectVanilla(); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } + } + } + + @Test + void testPlaceOrder() throws Exception { + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + Commander c = buildCommanderObject(true); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } + } + } + + @Test + void testPlaceOrder2() throws Exception { + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + Commander c = buildCommanderObject(false); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } + } + } + + @Test + void testPlaceOrderNoException1() throws Exception { + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + Commander c = buildCommanderObjectNoPaymentException1(); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } + } + } + + @Test + void testPlaceOrderNoException2() throws Exception { + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + Commander c = buildCommanderObjectNoPaymentException2(); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } + } + } + + @Test + void testPlaceOrderNoException3() throws Exception { + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + Commander c = buildCommanderObjectNoPaymentException3(); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } + } + } + + @Test + void testPlaceOrderNoException4() throws Exception { + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + Commander c = buildCommanderObjectNoPaymentException3(); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + c.placeOrder(order); + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } + } + } + + @Test + void testPlaceOrderUnknownException() throws Exception { + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + messageTime *= d; + employeeTime *= d; + queueTime *= d; + Commander c = buildCommanderObjectUnknownException(); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } + } + } + + @Test + void testPlaceOrderShortDuration() throws Exception { + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + messageTime *= d; + employeeTime *= d; + queueTime *= d; + Commander c = buildCommanderObject(true); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } + } + } + + @Test + void testPlaceOrderShortDuration2() throws Exception { + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + messageTime *= d; + employeeTime *= d; + queueTime *= d; + Commander c = buildCommanderObject(false); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } + } + } + + @Test + void testPlaceOrderNoExceptionShortMsgDuration() throws Exception { + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + messageTime *= d; + employeeTime *= d; + queueTime *= d; + Commander c = buildCommanderObjectNoPaymentException1(); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } + } + } + + @Test + void testPlaceOrderNoExceptionShortQueueDuration() throws Exception { + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + messageTime *= d; + employeeTime *= d; + queueTime *= d; + Commander c = buildCommanderObjectUnknownException(); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } + } + } + + @Test + void testPlaceOrderWithDatabase() throws Exception { + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + messageTime *= d; + employeeTime *= d; + queueTime *= d; + Commander c = buildCommanderObjectWithDB(); + var order = new Order(new User("K", null), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } + } + } + + @Test + void testPlaceOrderWithDatabaseAndExceptions() throws Exception { + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + messageTime *= d; + employeeTime *= d; + queueTime *= d; + + for (Exception e : exceptionList) { + + Commander c = buildCommanderObjectWithDB(true, true, e); + var order = new Order(new User("K", null), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } + + c = buildCommanderObjectWithDB(true, false, e); + order = new Order(new User("K", null), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } + + c = buildCommanderObjectWithDB(false, false, e); + order = new Order(new User("K", null), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } + + c = buildCommanderObjectWithDB(false, true, e); + order = new Order(new User("K", null), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } + } + } + } + + @Test + void testPlaceOrderWithoutDatabase() throws Exception { + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + messageTime *= d; + employeeTime *= d; + queueTime *= d; + Commander c = buildCommanderObjectWithoutDB(); + var order = new Order(new User("K", null), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } + } + } + + @Test + void testPlaceOrderWithoutDatabaseAndExceptions() throws Exception { + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + messageTime *= d; + employeeTime *= d; + queueTime *= d; + + for (Exception e : exceptionList) { + + Commander c = buildCommanderObjectWithoutDB(true, true, e); + var order = new Order(new User("K", null), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } + + c = buildCommanderObjectWithoutDB(true, false, e); + order = new Order(new User("K", null), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } + + c = buildCommanderObjectWithoutDB(false, false, e); + order = new Order(new User("K", null), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } + + c = buildCommanderObjectWithoutDB(false, true, e); + order = new Order(new User("K", null), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } + } + } + } + +} diff --git a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/SpatialPartitionBubbles.java b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/SpatialPartitionBubbles.java index 5faef509d..897466a99 100644 --- a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/SpatialPartitionBubbles.java +++ b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/SpatialPartitionBubbles.java @@ -34,11 +34,11 @@ import java.util.HashMap; public class SpatialPartitionBubbles extends SpatialPartitionGeneric { private final HashMap bubbles; - private final QuadTree quadTree; + private final QuadTree bubblesQuadTree; - SpatialPartitionBubbles(HashMap bubbles, QuadTree quadTree) { + SpatialPartitionBubbles(HashMap bubbles, QuadTree bubblesQuadTree) { this.bubbles = bubbles; - this.quadTree = quadTree; + this.bubblesQuadTree = bubblesQuadTree; } void handleCollisionsUsingQt(Bubble b) { @@ -46,7 +46,7 @@ public class SpatialPartitionBubbles extends SpatialPartitionGeneric { // centre of bubble and length = radius of bubble var rect = new Rect(b.coordinateX, b.coordinateY, 2D * b.radius, 2D * b.radius); var quadTreeQueryResult = new ArrayList(); - this.quadTree.query(rect, quadTreeQueryResult); + this.bubblesQuadTree.query(rect, quadTreeQueryResult); //handling these collisions b.handleCollision(quadTreeQueryResult, this.bubbles); } From 4dcc20b733d3db5b9a152997c215cbd4857d326b Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Thu, 16 Dec 2021 20:06:21 +0530 Subject: [PATCH 07/21] docs: add interactwithankush as a contributor for code (#1931) * docs: update README.md [skip ci] * docs: update .all-contributorsrc [skip ci] Co-authored-by: Subhrodip Mohanta Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ README.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 675121ffe..e19f72f05 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1749,6 +1749,15 @@ "review", "code" ] + }, + { + "login": "interactwithankush", + "name": "interactwithankush", + "avatar_url": "https://avatars.githubusercontent.com/u/18613127?v=4", + "profile": "https://github.com/interactwithankush", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/README.md b/README.md index 002d99bc9..d87fb381f 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) [![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) -[![All Contributors](https://img.shields.io/badge/all_contributors-192-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-193-orange.svg?style=flat-square)](#contributors-)
@@ -322,6 +322,7 @@ This project is licensed under the terms of the MIT license.
Abhinav Vashisth

đź“–
Kevin

đź‘€
Shrirang

đź‘€ đź’» +
interactwithankush

đź’» From 69883196d29c1c39dec325f088ee834e11e382e5 Mon Sep 17 00:00:00 2001 From: CharlieYu <791314183@qq.com> Date: Fri, 24 Dec 2021 00:10:17 +0800 Subject: [PATCH 08/21] improvement: Optimized NioReactor stop() (Reactor Pattern) (#1930) * Optimized NioReactor stop() * Optimized ThreadPoolDispatcher stop() --- .../java/com/iluwatar/reactor/framework/NioReactor.java | 6 ++++-- .../iluwatar/reactor/framework/ThreadPoolDispatcher.java | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/reactor/src/main/java/com/iluwatar/reactor/framework/NioReactor.java b/reactor/src/main/java/com/iluwatar/reactor/framework/NioReactor.java index 2db01bf88..afcaf0015 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/framework/NioReactor.java +++ b/reactor/src/main/java/com/iluwatar/reactor/framework/NioReactor.java @@ -96,9 +96,11 @@ public class NioReactor { * @throws IOException if any I/O error occurs. */ public void stop() throws InterruptedException, IOException { - reactorMain.shutdownNow(); + reactorMain.shutdown(); selector.wakeup(); - reactorMain.awaitTermination(4, TimeUnit.SECONDS); + if (!reactorMain.awaitTermination(4, TimeUnit.SECONDS)) { + reactorMain.shutdownNow(); + } selector.close(); LOGGER.info("Reactor stopped"); } diff --git a/reactor/src/main/java/com/iluwatar/reactor/framework/ThreadPoolDispatcher.java b/reactor/src/main/java/com/iluwatar/reactor/framework/ThreadPoolDispatcher.java index d8af72c96..7d4f610d1 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/framework/ThreadPoolDispatcher.java +++ b/reactor/src/main/java/com/iluwatar/reactor/framework/ThreadPoolDispatcher.java @@ -64,6 +64,8 @@ public class ThreadPoolDispatcher implements Dispatcher { @Override public void stop() throws InterruptedException { executorService.shutdown(); - executorService.awaitTermination(4, TimeUnit.SECONDS); + if (executorService.awaitTermination(4, TimeUnit.SECONDS)) { + executorService.shutdownNow(); + } } } From 4588e0993933e23c28bb773a76624b17449955f0 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Thu, 23 Dec 2021 18:11:48 +0200 Subject: [PATCH 09/21] docs: add yuhangbin as a contributor for code (#1934) * docs: update README.md [skip ci] * docs: update .all-contributorsrc [skip ci] Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ README.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index e19f72f05..cce81f6a9 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1758,6 +1758,15 @@ "contributions": [ "code" ] + }, + { + "login": "yuhangbin", + "name": "CharlieYu", + "avatar_url": "https://avatars.githubusercontent.com/u/17566866?v=4", + "profile": "https://github.com/yuhangbin", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/README.md b/README.md index d87fb381f..6bb03df11 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) [![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) -[![All Contributors](https://img.shields.io/badge/all_contributors-193-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-194-orange.svg?style=flat-square)](#contributors-)
@@ -323,6 +323,7 @@ This project is licensed under the terms of the MIT license.
Kevin

đź‘€
Shrirang

đź‘€ đź’»
interactwithankush

đź’» +
CharlieYu

đź’» From df73d80365c418bf7e9ad8f08005841cf70fc0e2 Mon Sep 17 00:00:00 2001 From: Leisterbecker Date: Thu, 23 Dec 2021 19:43:36 +0100 Subject: [PATCH 10/21] improvement: EventAggregator now considers Event-Type (#1933) * Implemented more granular registration of observers with HashMap; Key: Event, Value: List of Observers registered for the event; * Edited constructors and super calls of all EventEmitter extenders * Edited constructor calls in App.java * Edited EventEmitterTest * Added new white walkers event, scout emits at wednesday, varys observes and emits at saturday * Added white walkers event to KingsHandTest.java * Varys now passes events * Corrected some indentation levels and added curly braces to if statements * Corrected some styling * Switched lines in App.java, added javadoc to registerObserver * Fixed some indents, added param comments --- .../com/iluwatar/event/aggregator/App.java | 23 +++++++++++--- .../com/iluwatar/event/aggregator/Event.java | 1 + .../event/aggregator/EventEmitter.java | 31 ++++++++++++++----- .../iluwatar/event/aggregator/KingsHand.java | 5 ++- .../event/aggregator/LordBaelish.java | 4 +-- .../iluwatar/event/aggregator/LordVarys.java | 15 +++++++-- .../com/iluwatar/event/aggregator/Scout.java | 7 +++-- .../event/aggregator/EventEmitterTest.java | 15 ++++----- .../event/aggregator/KingsHandTest.java | 6 +++- .../iluwatar/event/aggregator/ScoutTest.java | 4 ++- 10 files changed, 81 insertions(+), 30 deletions(-) diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/App.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/App.java index fb7193b2e..ae9046a8c 100644 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/App.java +++ b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/App.java @@ -49,13 +49,28 @@ public class App { public static void main(String[] args) { var kingJoffrey = new KingJoffrey(); - var kingsHand = new KingsHand(kingJoffrey); + + var kingsHand = new KingsHand(); + kingsHand.registerObserver(kingJoffrey, Event.TRAITOR_DETECTED); + kingsHand.registerObserver(kingJoffrey, Event.STARK_SIGHTED); + kingsHand.registerObserver(kingJoffrey, Event.WARSHIPS_APPROACHING); + kingsHand.registerObserver(kingJoffrey, Event.WHITE_WALKERS_SIGHTED); + + var varys = new LordVarys(); + varys.registerObserver(kingsHand, Event.TRAITOR_DETECTED); + varys.registerObserver(kingsHand, Event.WHITE_WALKERS_SIGHTED); + + var scout = new Scout(); + scout.registerObserver(kingsHand, Event.WARSHIPS_APPROACHING); + scout.registerObserver(varys, Event.WHITE_WALKERS_SIGHTED); + + var baelish = new LordBaelish(kingsHand, Event.STARK_SIGHTED); var emitters = List.of( kingsHand, - new LordBaelish(kingsHand), - new LordVarys(kingsHand), - new Scout(kingsHand) + baelish, + varys, + scout ); Arrays.stream(Weekday.values()) diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Event.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Event.java index 61b9d713a..f79a7bb4e 100644 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Event.java +++ b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Event.java @@ -31,6 +31,7 @@ import lombok.RequiredArgsConstructor; @RequiredArgsConstructor public enum Event { + WHITE_WALKERS_SIGHTED("White walkers sighted"), STARK_SIGHTED("Stark sighted"), WARSHIPS_APPROACHING("Warships approaching"), TRAITOR_DETECTED("Traitor detected"); diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/EventEmitter.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/EventEmitter.java index 79093885d..9ac047df9 100644 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/EventEmitter.java +++ b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/EventEmitter.java @@ -23,31 +23,48 @@ package com.iluwatar.event.aggregator; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; /** * EventEmitter is the base class for event producers that can be observed. */ public abstract class EventEmitter { - private final List observers; + private final Map> observerLists; public EventEmitter() { - observers = new LinkedList<>(); + observerLists = new HashMap<>(); } - public EventEmitter(EventObserver obs) { + public EventEmitter(EventObserver obs, Event e) { this(); - registerObserver(obs); + registerObserver(obs, e); } - public final void registerObserver(EventObserver obs) { - observers.add(obs); + /** + * Registers observer for specific event in the related list. + * + * @param obs the observer that observers this emitter + * @param e the specific event for that observation occurs + * */ + public final void registerObserver(EventObserver obs, Event e) { + if (!observerLists.containsKey(e)) { + observerLists.put(e, new LinkedList<>()); + } + if (!observerLists.get(e).contains(obs)) { + observerLists.get(e).add(obs); + } } protected void notifyObservers(Event e) { - observers.forEach(obs -> obs.onEvent(e)); + if (observerLists.containsKey(e)) { + observerLists + .get(e) + .forEach(observer -> observer.onEvent(e)); + } } public abstract void timePasses(Weekday day); diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/KingsHand.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/KingsHand.java index 192e9681a..c7fe007f1 100644 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/KingsHand.java +++ b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/KingsHand.java @@ -31,8 +31,8 @@ public class KingsHand extends EventEmitter implements EventObserver { public KingsHand() { } - public KingsHand(EventObserver obs) { - super(obs); + public KingsHand(EventObserver obs, Event e) { + super(obs, e); } @Override @@ -42,6 +42,5 @@ public class KingsHand extends EventEmitter implements EventObserver { @Override public void timePasses(Weekday day) { - // NOP } } diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/LordBaelish.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/LordBaelish.java index b00a05330..ed046fd66 100644 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/LordBaelish.java +++ b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/LordBaelish.java @@ -31,8 +31,8 @@ public class LordBaelish extends EventEmitter { public LordBaelish() { } - public LordBaelish(EventObserver obs) { - super(obs); + public LordBaelish(EventObserver obs, Event e) { + super(obs, e); } @Override diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/LordVarys.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/LordVarys.java index b56635eab..9fb3d2625 100644 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/LordVarys.java +++ b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/LordVarys.java @@ -23,16 +23,19 @@ package com.iluwatar.event.aggregator; +import lombok.extern.slf4j.Slf4j; + /** * LordVarys produces events. */ -public class LordVarys extends EventEmitter { +@Slf4j +public class LordVarys extends EventEmitter implements EventObserver { public LordVarys() { } - public LordVarys(EventObserver obs) { - super(obs); + public LordVarys(EventObserver obs, Event e) { + super(obs, e); } @Override @@ -41,4 +44,10 @@ public class LordVarys extends EventEmitter { notifyObservers(Event.TRAITOR_DETECTED); } } + + + @Override + public void onEvent(Event e) { + notifyObservers(e); + } } diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Scout.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Scout.java index 88061e40e..8d47fff66 100644 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Scout.java +++ b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Scout.java @@ -31,8 +31,8 @@ public class Scout extends EventEmitter { public Scout() { } - public Scout(EventObserver obs) { - super(obs); + public Scout(EventObserver obs, Event e) { + super(obs, e); } @Override @@ -40,5 +40,8 @@ public class Scout extends EventEmitter { if (day == Weekday.TUESDAY) { notifyObservers(Event.WARSHIPS_APPROACHING); } + if (day == Weekday.WEDNESDAY) { + notifyObservers(Event.WHITE_WALKERS_SIGHTED); + } } } diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventEmitterTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventEmitterTest.java index 1378ead9e..02cda6752 100644 --- a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventEmitterTest.java +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventEmitterTest.java @@ -31,6 +31,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import java.util.Objects; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; import org.junit.jupiter.api.Test; @@ -46,7 +47,7 @@ abstract class EventEmitterTest { /** * Factory used to create a new instance of the test object with a default observer */ - private final Function factoryWithDefaultObserver; + private final BiFunction factoryWithDefaultObserver; /** * Factory used to create a new instance of the test object without passing a default observer @@ -67,7 +68,7 @@ abstract class EventEmitterTest { * Create a new event emitter test, using the given test object factories, special day and event */ EventEmitterTest(final Weekday specialDay, final Event event, - final Function factoryWithDefaultObserver, + final BiFunction factoryWithDefaultObserver, final Supplier factoryWithoutDefaultObserver) { this.specialDay = specialDay; @@ -129,8 +130,8 @@ abstract class EventEmitterTest { final var observer2 = mock(EventObserver.class); final var emitter = this.factoryWithoutDefaultObserver.get(); - emitter.registerObserver(observer1); - emitter.registerObserver(observer2); + emitter.registerObserver(observer1, event); + emitter.registerObserver(observer2, event); testAllDays(specialDay, event, emitter, observer1, observer2); } @@ -146,9 +147,9 @@ abstract class EventEmitterTest { final var observer1 = mock(EventObserver.class); final var observer2 = mock(EventObserver.class); - final var emitter = this.factoryWithDefaultObserver.apply(defaultObserver); - emitter.registerObserver(observer1); - emitter.registerObserver(observer2); + final var emitter = this.factoryWithDefaultObserver.apply(defaultObserver, event); + emitter.registerObserver(observer1, event); + emitter.registerObserver(observer2, event); testAllDays(specialDay, event, emitter, defaultObserver, observer1, observer2); } diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingsHandTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingsHandTest.java index 51229b155..819e66309 100644 --- a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingsHandTest.java +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingsHandTest.java @@ -55,7 +55,11 @@ class KingsHandTest extends EventEmitterTest { @Test void testPassThrough() throws Exception { final var observer = mock(EventObserver.class); - final var kingsHand = new KingsHand(observer); + final var kingsHand = new KingsHand(); + kingsHand.registerObserver(observer, Event.STARK_SIGHTED); + kingsHand.registerObserver(observer, Event.WARSHIPS_APPROACHING); + kingsHand.registerObserver(observer, Event.TRAITOR_DETECTED); + kingsHand.registerObserver(observer, Event.WHITE_WALKERS_SIGHTED); // The kings hand should not pass any events before he received one verifyZeroInteractions(observer); diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/ScoutTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/ScoutTest.java index 3a13869d2..0c5976c52 100644 --- a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/ScoutTest.java +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/ScoutTest.java @@ -34,7 +34,9 @@ class ScoutTest extends EventEmitterTest { * Create a new test instance, using the correct object factory */ public ScoutTest() { - super(Weekday.TUESDAY, Event.WARSHIPS_APPROACHING, Scout::new, Scout::new); + + super(Weekday.TUESDAY, Event.WARSHIPS_APPROACHING, Scout::new, Scout::new); + } } \ No newline at end of file From f670ae547b9bd03bcd06751caea3779d90d228b8 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Thu, 23 Dec 2021 20:45:14 +0200 Subject: [PATCH 11/21] docs: add Leisterbecker as a contributor for code (#1935) * docs: update README.md [skip ci] * docs: update .all-contributorsrc [skip ci] Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ README.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index cce81f6a9..c00cab607 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1767,6 +1767,15 @@ "contributions": [ "code" ] + }, + { + "login": "Leisterbecker", + "name": "Leisterbecker", + "avatar_url": "https://avatars.githubusercontent.com/u/20650323?v=4", + "profile": "https://github.com/Leisterbecker", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/README.md b/README.md index 6bb03df11..2c487373e 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) [![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) -[![All Contributors](https://img.shields.io/badge/all_contributors-194-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-195-orange.svg?style=flat-square)](#contributors-)
@@ -324,6 +324,7 @@ This project is licensed under the terms of the MIT license.
Shrirang

đź‘€ đź’»
interactwithankush

đź’»
CharlieYu

đź’» +
Leisterbecker

💻 From 8403fdacdde6f7c9ed09733311ca4c8c9859ff16 Mon Sep 17 00:00:00 2001 From: DragonDreamer Date: Sun, 2 Jan 2022 02:46:20 +0800 Subject: [PATCH 12/21] feature: Metadata Mapping pattern (#1932) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * metadata-mapping * Update README.md * add class diagram * update README.md * fix identation * Update pom.xml * fix indentation * fix ci * remove e.printstack * fix ci * update class diagram * fix ci * fix ci * fix sc * fix smells * Update DatabaseUtil.java * fix coverage * Update DatabaseUtil.java * Update DatabaseUtil.java * Update DatabaseUtil.java * Update metadata-mapping/README.md Co-authored-by: Ilkka Seppälä * fix review * fix review * Update App.java * Update App.java * fix review Co-authored-by: Ilkka Seppälä --- metadata-mapping/README.md | 182 ++++++++++++++++++ metadata-mapping/etc/metamapping.png | Bin 0 -> 49739 bytes metadata-mapping/etc/metamapping.puml | 32 +++ metadata-mapping/pom.xml | 87 +++++++++ .../java/com/iluwatar/metamapping/App.java | 72 +++++++ .../com/iluwatar/metamapping/model/User.java | 29 +++ .../metamapping/service/UserService.java | 114 +++++++++++ .../metamapping/utils/DatabaseUtil.java | 39 ++++ .../metamapping/utils/HibernateUtil.java | 45 +++++ .../iluwatar/metamapping/model/User.hbm.xml | 14 ++ .../src/main/resources/hibernate.cfg.xml | 20 ++ .../com/iluwatar/metamapping/AppTest.java | 20 ++ pom.xml | 7 + 13 files changed, 661 insertions(+) create mode 100644 metadata-mapping/README.md create mode 100644 metadata-mapping/etc/metamapping.png create mode 100644 metadata-mapping/etc/metamapping.puml create mode 100644 metadata-mapping/pom.xml create mode 100644 metadata-mapping/src/main/java/com/iluwatar/metamapping/App.java create mode 100644 metadata-mapping/src/main/java/com/iluwatar/metamapping/model/User.java create mode 100644 metadata-mapping/src/main/java/com/iluwatar/metamapping/service/UserService.java create mode 100644 metadata-mapping/src/main/java/com/iluwatar/metamapping/utils/DatabaseUtil.java create mode 100644 metadata-mapping/src/main/java/com/iluwatar/metamapping/utils/HibernateUtil.java create mode 100644 metadata-mapping/src/main/resources/com/iluwatar/metamapping/model/User.hbm.xml create mode 100644 metadata-mapping/src/main/resources/hibernate.cfg.xml create mode 100644 metadata-mapping/src/test/java/com/iluwatar/metamapping/AppTest.java diff --git a/metadata-mapping/README.md b/metadata-mapping/README.md new file mode 100644 index 000000000..5dcd932a7 --- /dev/null +++ b/metadata-mapping/README.md @@ -0,0 +1,182 @@ +--- +layout: pattern +title: Metadata Mapping +folder: metadata-mapping +permalink: /patterns/metadata-mapping/ +categories: Architectural +language: en +tags: + - Data access +--- + +## Intent + +Holds details of object-relational mapping in the metadata. + +## Explanation + +Real world example + +> Hibernate ORM Tool uses Metadata Mapping Pattern to specify the mapping between classes and tables either using XML or annotations in code. + +In plain words + +> Metadata Mapping specifies the mapping between classes and tables so that we could treat a table of any database like a Java class. + +Wikipedia says + +> Create a "virtual [object database](https://en.wikipedia.org/wiki/Object_database)" that can be used from within the programming language. + +**Programmatic Example** + +We give an example about visiting the information of `USER` table in `h2` database. Firstly, we create `USER` table with `h2`: + +```java +@Slf4j +public class DatabaseUtil { + private static final String DB_URL = "jdbc:h2:mem:metamapping"; + private static final String CREATE_SCHEMA_SQL = "DROP TABLE IF EXISTS `user`;" + + "CREATE TABLE `user` (\n" + + " `id` int(11) NOT NULL AUTO_INCREMENT,\n" + + " `username` varchar(255) NOT NULL,\n" + + " `password` varchar(255) NOT NULL,\n" + + " PRIMARY KEY (`id`)\n" + + ");"; + + /** + * Create database. + */ + static { + LOGGER.info("create h2 database"); + var source = new JdbcDataSource(); + source.setURL(DB_URL); + try (var statement = source.getConnection().createStatement()) { + statement.execute(CREATE_SCHEMA_SQL); + } catch (SQLException e) { + LOGGER.error("unable to create h2 data source", e); + } + } +} +``` + +Correspondingly, here's the basic `User` entity. + +```java +@Setter +@Getter +@ToString +public class User { + private Integer id; + private String username; + private String password; + + /** + * Get a user. + * @param username user name + * @param password user password + */ + public User(String username, String password) { + this.username = username; + this.password = password; + } +} +``` + +Then we write a `xml` file to show the mapping between the table and the object: + +```xml + + + + + + + + + + + + +``` + +We use `Hibernate` to resolve the mapping and connect to our database, here's its configuration: + +```xml + + + + + + jdbc:h2:mem:metamapping + org.h2.Driver + + 1 + + org.hibernate.dialect.H2Dialect + + false + + create-drop + + + +``` + +Then we can get access to the table just like an object with `Hibernate`, here's some CRUDs: + +```java +@Slf4j +public class UserService { + private static final SessionFactory factory = HibernateUtil.getSessionFactory(); + + /** + * List all users. + * @return list of users + */ + public List listUser() { + LOGGER.info("list all users."); + List users = new ArrayList<>(); + try (var session = factory.openSession()) { + var tx = session.beginTransaction(); + List userIter = session.createQuery("FROM User").list(); + for (var iterator = userIter.iterator(); iterator.hasNext();) { + users.add(iterator.next()); + } + tx.commit(); + } catch (HibernateException e) { + LOGGER.debug("fail to get users", e); + } + return users; + } + + // other CRUDs -> + ... + + public void close() { + HibernateUtil.shutdown(); + } +} +``` + +## Class diagram + +![metamapping](etc/metamapping.png) + +## Applicability + +Use the Metadata Mapping when: + +- you want reduce the amount of work needed to handle database mapping. + +## Known uses + +[Hibernate](https://hibernate.org/), [EclipseLink](https://www.eclipse.org/eclipselink/), [MyBatis](https://blog.mybatis.org/)...... + +## Credits + +- [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) + diff --git a/metadata-mapping/etc/metamapping.png b/metadata-mapping/etc/metamapping.png new file mode 100644 index 0000000000000000000000000000000000000000..b1e89f8af56f44afafb5d272a526d730797d7b39 GIT binary patch literal 49739 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 &FlB+1VM-~c9!w4PGQkJ;>Atumt><@{G*J4d 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 listUser() ++ int createUser(User) ++ void updateUser(Integer,User) ++ void deleteUser(Integer) ++ User getUser(Integer) ++ void close() +} +class com.iluwatar.metamapping.utils.DatabaseUtil { ++ {static} void createDataSource() +} +class com.iluwatar.metamapping.model.User { +- Integer id +- String username +- String password ++ User(String username, String password) +} +class com.iluwatar.metamapping.utils.HibernateUtil { ++ {static} SessionFactory getSessionFactory() ++ {static} void shutdown() +} +class com.iluwatar.metamapping.App { ++ {static} void main(String[]) ++ {static} List generateSampleUsers() +} + +com.iluwatar.metamapping.service.UserService <.. com.iluwatar.metamapping.App +com.iluwatar.metamapping.model.User <.. com.iluwatar.metamapping.service.UserService +com.iluwatar.metamapping.utils.HibernateUtil <.. com.iluwatar.metamapping.service.UserService +com.iluwatar.metamapping.utils.DatabaseUtil <-- com.iluwatar.metamapping.utils.HibernateUtil +@enduml \ No newline at end of file diff --git a/metadata-mapping/pom.xml b/metadata-mapping/pom.xml new file mode 100644 index 000000000..43c9621df --- /dev/null +++ b/metadata-mapping/pom.xml @@ -0,0 +1,87 @@ + + + + + java-design-patterns + com.iluwatar + 1.26.0-SNAPSHOT + + 4.0.0 + + metadata-mapping + + + org.junit.jupiter + junit-jupiter-engine + test + + + com.h2database + h2 + + + org.hibernate + hibernate-core + + + com.h2database + h2 + + + javax.xml.bind + jaxb-api + + + com.sun.xml.bind + jaxb-impl + + + com.sun.istack + istack-commons-runtime + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.metamapping.App + + + + + + + + + \ No newline at end of file diff --git a/metadata-mapping/src/main/java/com/iluwatar/metamapping/App.java b/metadata-mapping/src/main/java/com/iluwatar/metamapping/App.java new file mode 100644 index 000000000..ff0377590 --- /dev/null +++ b/metadata-mapping/src/main/java/com/iluwatar/metamapping/App.java @@ -0,0 +1,72 @@ +package com.iluwatar.metamapping; + +import com.iluwatar.metamapping.model.User; +import com.iluwatar.metamapping.service.UserService; +import com.iluwatar.metamapping.utils.DatabaseUtil; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.hibernate.service.ServiceRegistry; + +/** + * Metadata Mapping specifies the mapping + * between classes and tables so that + * we could treat a table of any database like a Java class. + * + *

With hibernate, we achieve list/create/update/delete/get operations: + * 1)Create the H2 Database in {@link DatabaseUtil}. + * 2)Hibernate resolve hibernate.cfg.xml and generate service like save/list/get/delete. + * For learning metadata mapping pattern, we go deeper into Hibernate here: + * a)read properties from hibernate.cfg.xml and mapping from *.hbm.xml + * b)create session factory to generate session interacting with database + * c)generate session with factory pattern + * d)create query object or use basic api with session, + * hibernate will convert all query to database query according to metadata + * 3)We encapsulate hibernate service in {@link UserService} for our use. + * @see org.hibernate.cfg.Configuration#configure(String) + * @see org.hibernate.cfg.Configuration#buildSessionFactory(ServiceRegistry) + * @see org.hibernate.internal.SessionFactoryImpl#openSession() + */ +@Slf4j +public class App { + /** + * Program entry point. + * + * @param args command line args. + * @throws Exception if any error occurs. + */ + public static void main(String[] args) throws Exception { + // get service + var userService = new UserService(); + // use create service to add users + for (var user: generateSampleUsers()) { + var id = userService.createUser(user); + LOGGER.info("Add user" + user + "at" + id + "."); + } + // use list service to get users + var users = userService.listUser(); + LOGGER.info(String.valueOf(users)); + // use get service to get a user + var user = userService.getUser(1); + LOGGER.info(String.valueOf(user)); + // change password of user 1 + user.setPassword("new123"); + // use update service to update user 1 + userService.updateUser(1, user); + // use delete service to delete user 2 + userService.deleteUser(2); + // close service + userService.close(); + } + + /** + * Generate users. + * + * @return list of users. + */ + public static List generateSampleUsers() { + final var user1 = new User("ZhangSan", "zhs123"); + final var user2 = new User("LiSi", "ls123"); + final var user3 = new User("WangWu", "ww123"); + return List.of(user1, user2, user3); + } +} \ No newline at end of file diff --git a/metadata-mapping/src/main/java/com/iluwatar/metamapping/model/User.java b/metadata-mapping/src/main/java/com/iluwatar/metamapping/model/User.java new file mode 100644 index 000000000..0bf2575b1 --- /dev/null +++ b/metadata-mapping/src/main/java/com/iluwatar/metamapping/model/User.java @@ -0,0 +1,29 @@ +package com.iluwatar.metamapping.model; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +/** + * User Entity. + */ +@Setter +@Getter +@ToString +public class User { + private Integer id; + private String username; + private String password; + + public User() {} + + /** + * Get a user. + * @param username user name + * @param password user password + */ + public User(String username, String password) { + this.username = username; + this.password = password; + } +} \ No newline at end of file diff --git a/metadata-mapping/src/main/java/com/iluwatar/metamapping/service/UserService.java b/metadata-mapping/src/main/java/com/iluwatar/metamapping/service/UserService.java new file mode 100644 index 000000000..1f85be0d5 --- /dev/null +++ b/metadata-mapping/src/main/java/com/iluwatar/metamapping/service/UserService.java @@ -0,0 +1,114 @@ +package com.iluwatar.metamapping.service; + +import com.iluwatar.metamapping.model.User; +import com.iluwatar.metamapping.utils.HibernateUtil; +import java.util.ArrayList; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.hibernate.HibernateException; +import org.hibernate.SessionFactory; + +/** + * Service layer for user. + */ +@Slf4j +public class UserService { + private static final SessionFactory factory = HibernateUtil.getSessionFactory(); + + /** + * List all users. + * @return list of users + */ + public List listUser() { + LOGGER.info("list all users."); + List users = new ArrayList<>(); + try (var session = factory.openSession()) { + var tx = session.beginTransaction(); + List userIter = session.createQuery("FROM User").list(); + for (var iterator = userIter.iterator(); iterator.hasNext();) { + users.add(iterator.next()); + } + tx.commit(); + } catch (HibernateException e) { + LOGGER.debug("fail to get users", e); + } + return users; + } + + /** + * Add a user. + * @param user user entity + * @return user id + */ + public int createUser(User user) { + LOGGER.info("create user: " + user.getUsername()); + var id = -1; + try (var session = factory.openSession()) { + var tx = session.beginTransaction(); + id = (Integer) session.save(user); + tx.commit(); + } catch (HibernateException e) { + LOGGER.debug("fail to create user", e); + } + LOGGER.info("create user " + user.getUsername() + " at " + id); + return id; + } + + /** + * Update user. + * @param id user id + * @param user new user entity + */ + public void updateUser(Integer id, User user) { + LOGGER.info("update user at " + id); + try (var session = factory.openSession()) { + var tx = session.beginTransaction(); + user.setId(id); + session.update(user); + tx.commit(); + } catch (HibernateException e) { + LOGGER.debug("fail to update user", e); + } + } + + /** + * Delete user. + * @param id user id + */ + public void deleteUser(Integer id) { + LOGGER.info("delete user at: " + id); + try (var session = factory.openSession()) { + var tx = session.beginTransaction(); + var user = session.get(User.class, id); + session.delete(user); + tx.commit(); + } catch (HibernateException e) { + LOGGER.debug("fail to delete user", e); + } + } + + /** + * Get user. + * @param id user id + * @return deleted user + */ + public User getUser(Integer id) { + LOGGER.info("get user at: " + id); + User user = null; + try (var session = factory.openSession()) { + var tx = session.beginTransaction(); + user = session.get(User.class, id); + tx.commit(); + } catch (HibernateException e) { + LOGGER.debug("fail to get user", e); + } + return user; + } + + /** + * Close hibernate. + */ + public void close() { + HibernateUtil.shutdown(); + } +} \ No newline at end of file diff --git a/metadata-mapping/src/main/java/com/iluwatar/metamapping/utils/DatabaseUtil.java b/metadata-mapping/src/main/java/com/iluwatar/metamapping/utils/DatabaseUtil.java new file mode 100644 index 000000000..b6d0200e8 --- /dev/null +++ b/metadata-mapping/src/main/java/com/iluwatar/metamapping/utils/DatabaseUtil.java @@ -0,0 +1,39 @@ +package com.iluwatar.metamapping.utils; + +import java.sql.SQLException; +import lombok.extern.slf4j.Slf4j; +import org.h2.jdbcx.JdbcDataSource; + +/** + * Create h2 database. + */ +@Slf4j +public class DatabaseUtil { + private static final String DB_URL = "jdbc:h2:mem:metamapping"; + private static final String CREATE_SCHEMA_SQL = "DROP TABLE IF EXISTS `user`;" + + "CREATE TABLE `user` (\n" + + " `id` int(11) NOT NULL AUTO_INCREMENT,\n" + + " `username` varchar(255) NOT NULL,\n" + + " `password` varchar(255) NOT NULL,\n" + + " PRIMARY KEY (`id`)\n" + + ");"; + + /** + * Hide constructor. + */ + private DatabaseUtil() {} + + /** + * Create database. + */ + static { + LOGGER.info("create h2 database"); + var source = new JdbcDataSource(); + source.setURL(DB_URL); + try (var statement = source.getConnection().createStatement()) { + statement.execute(CREATE_SCHEMA_SQL); + } catch (SQLException e) { + LOGGER.error("unable to create h2 data source", e); + } + } +} \ No newline at end of file diff --git a/metadata-mapping/src/main/java/com/iluwatar/metamapping/utils/HibernateUtil.java b/metadata-mapping/src/main/java/com/iluwatar/metamapping/utils/HibernateUtil.java new file mode 100644 index 000000000..ba405eb74 --- /dev/null +++ b/metadata-mapping/src/main/java/com/iluwatar/metamapping/utils/HibernateUtil.java @@ -0,0 +1,45 @@ +package com.iluwatar.metamapping.utils; + +import lombok.extern.slf4j.Slf4j; +import org.hibernate.SessionFactory; +import org.hibernate.cfg.Configuration; + +/** + * Manage hibernate. + */ +@Slf4j +public class HibernateUtil { + + private static final SessionFactory sessionFactory = buildSessionFactory(); + + /** + * Hide constructor. + */ + private HibernateUtil() {} + + /** + * Build session factory. + * @return session factory + */ + private static SessionFactory buildSessionFactory() { + // Create the SessionFactory from hibernate.cfg.xml + return new Configuration().configure().buildSessionFactory(); + } + + /** + * Get session factory. + * @return session factory + */ + public static SessionFactory getSessionFactory() { + return sessionFactory; + } + + /** + * Close session factory. + */ + public static void shutdown() { + // Close caches and connection pools + getSessionFactory().close(); + } + +} \ No newline at end of file diff --git a/metadata-mapping/src/main/resources/com/iluwatar/metamapping/model/User.hbm.xml b/metadata-mapping/src/main/resources/com/iluwatar/metamapping/model/User.hbm.xml new file mode 100644 index 000000000..cd63c552d --- /dev/null +++ b/metadata-mapping/src/main/resources/com/iluwatar/metamapping/model/User.hbm.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/metadata-mapping/src/main/resources/hibernate.cfg.xml b/metadata-mapping/src/main/resources/hibernate.cfg.xml new file mode 100644 index 000000000..18dc198e8 --- /dev/null +++ b/metadata-mapping/src/main/resources/hibernate.cfg.xml @@ -0,0 +1,20 @@ + + + + + + jdbc:h2:mem:metamapping + org.h2.Driver + + 1 + + org.hibernate.dialect.H2Dialect + + false + + create-drop + + + \ No newline at end of file diff --git a/metadata-mapping/src/test/java/com/iluwatar/metamapping/AppTest.java b/metadata-mapping/src/test/java/com/iluwatar/metamapping/AppTest.java new file mode 100644 index 000000000..127ddad0f --- /dev/null +++ b/metadata-mapping/src/test/java/com/iluwatar/metamapping/AppTest.java @@ -0,0 +1,20 @@ +package com.iluwatar.metamapping; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +/** + * Tests that metadata mapping example runs without errors. + */ +class AppTest { + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} + * throws an exception. + */ + @Test + void shouldExecuteMetaMappingWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); + } +} diff --git a/pom.xml b/pom.xml index 5c3634b95..e1573a737 100644 --- a/pom.xml +++ b/pom.xml @@ -75,6 +75,7 @@ 3.0 1.4.8 2.7 + 4.0.1 https://sonarcloud.io iluwatar @@ -227,6 +228,7 @@ lockable-object fanout-fanin domain-model + metadata-mapping @@ -377,6 +379,11 @@ commons-io ${commons-io.version} + + com.sun.istack + istack-commons-runtime + ${istack-commons-runtime.version} + From 2679f7aa6fe7fbebd28fafa9b988df26c0e7f36e Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sat, 1 Jan 2022 20:48:21 +0200 Subject: [PATCH 13/21] docs: add castleKing1997 as a contributor for code (#1939) * docs: update README.md [skip ci] * docs: update .all-contributorsrc [skip ci] Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ README.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index c00cab607..c59ff5b29 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1776,6 +1776,15 @@ "contributions": [ "code" ] + }, + { + "login": "castleKing1997", + "name": "DragonDreamer", + "avatar_url": "https://avatars.githubusercontent.com/u/35420129?v=4", + "profile": "http://rosaecrucis.cn", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/README.md b/README.md index 2c487373e..1658bccf3 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) [![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) -[![All Contributors](https://img.shields.io/badge/all_contributors-195-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-196-orange.svg?style=flat-square)](#contributors-)
@@ -325,6 +325,7 @@ This project is licensed under the terms of the MIT license.
interactwithankush

đź’»
CharlieYu

đź’»
Leisterbecker

đź’» +
DragonDreamer

đź’» From c66ca6720106e488e363accbf1406508a37ecc23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilkka=20Sepp=C3=A4l=C3=A4?= Date: Thu, 6 Jan 2022 18:43:16 +0200 Subject: [PATCH 14/21] #590 add explanation for event aggregator (#1936) --- event-aggregator/README.md | 140 ++++++++++++++++++++++++++++++++++++- 1 file changed, 139 insertions(+), 1 deletion(-) diff --git a/event-aggregator/README.md b/event-aggregator/README.md index 2e54ed904..f3b941497 100644 --- a/event-aggregator/README.md +++ b/event-aggregator/README.md @@ -9,6 +9,10 @@ tags: - Reactive --- +## Name + +Event Aggregator + ## Intent A system with lots of objects can lead to complexities when a client wants to subscribe to events. The client has to find and register for @@ -17,6 +21,136 @@ requires a separate subscription. An Event Aggregator acts as a single source of events for many objects. It registers for all the events of the many objects allowing clients to register with just the aggregator. +## Explanation + +Real-world example + +> King Joffrey sits on the iron throne and rules the seven kingdoms of Westeros. He receives most +> of his critical information from King's Hand, the second in command. King's hand has many +> close advisors himself, feeding him with relevant information about events occurring in the +> kingdom. + +In Plain Words + +> Event Aggregator is an event mediator that collects events from multiple sources and delivers +> them to registered observers. + +**Programmatic Example** + +In our programmatic example, we demonstrate the implementation of an event aggregator pattern. Some of +the objects are event listeners, some are event emitters, and the event aggregator does both. + +```java +public interface EventObserver { + void onEvent(Event e); +} + +public abstract class EventEmitter { + + private final Map> observerLists; + + public EventEmitter() { + observerLists = new HashMap<>(); + } + + public final void registerObserver(EventObserver obs, Event e) { + ... + } + + protected void notifyObservers(Event e) { + ... + } +} +``` + +`KingJoffrey` is listening to events from `KingsHand`. + +```java +@Slf4j +public class KingJoffrey implements EventObserver { + @Override + public void onEvent(Event e) { + LOGGER.info("Received event from the King's Hand: {}", e.toString()); + } +} +``` + +`KingsHand` is listening to events from his subordinates `LordBaelish`, `LordVarys`, and `Scout`. +Whatever he hears from them, he delivers to `KingJoffrey`. + +```java +public class KingsHand extends EventEmitter implements EventObserver { + + public KingsHand() { + } + + public KingsHand(EventObserver obs, Event e) { + super(obs, e); + } + + @Override + public void onEvent(Event e) { + notifyObservers(e); + } +} +``` + +For example, `LordVarys` finds a traitor every Sunday and notifies the `KingsHand`. + +```java +@Slf4j +public class LordVarys extends EventEmitter implements EventObserver { + @Override + public void timePasses(Weekday day) { + if (day == Weekday.SATURDAY) { + notifyObservers(Event.TRAITOR_DETECTED); + } + } +} +``` + +The following snippet demonstrates how the objects are constructed and wired together. + +```java + var kingJoffrey = new KingJoffrey(); + + var kingsHand = new KingsHand(); + kingsHand.registerObserver(kingJoffrey, Event.TRAITOR_DETECTED); + kingsHand.registerObserver(kingJoffrey, Event.STARK_SIGHTED); + kingsHand.registerObserver(kingJoffrey, Event.WARSHIPS_APPROACHING); + kingsHand.registerObserver(kingJoffrey, Event.WHITE_WALKERS_SIGHTED); + + var varys = new LordVarys(); + varys.registerObserver(kingsHand, Event.TRAITOR_DETECTED); + varys.registerObserver(kingsHand, Event.WHITE_WALKERS_SIGHTED); + + var scout = new Scout(); + scout.registerObserver(kingsHand, Event.WARSHIPS_APPROACHING); + scout.registerObserver(varys, Event.WHITE_WALKERS_SIGHTED); + + var baelish = new LordBaelish(kingsHand, Event.STARK_SIGHTED); + + var emitters = List.of( + kingsHand, + baelish, + varys, + scout + ); + + Arrays.stream(Weekday.values()) + .>map(day -> emitter -> emitter.timePasses(day)) + .forEachOrdered(emitters::forEach); +``` + +The console output after running the example. + +``` +18:21:52.955 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: Warships approaching +18:21:52.960 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: White walkers sighted +18:21:52.960 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: Stark sighted +18:21:52.960 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: Traitor detected +``` + ## Class diagram ![alt text](./etc/classes.png "Event Aggregator") @@ -26,9 +160,13 @@ Use the Event Aggregator pattern when * Event Aggregator is a good choice when you have lots of objects that are potential event sources. Rather than have the observer deal with registering with them all, you can centralize the registration logic to the Event - Aggregator. As well as simplifying registration, a Event Aggregator also + Aggregator. As well as simplifying registration, an Event Aggregator also simplifies the memory management issues in using observers. +## Related patterns + +* [Observer](https://java-design-patterns.com/patterns/observer/) + ## Credits * [Martin Fowler - Event Aggregator](http://martinfowler.com/eaaDev/EventAggregator.html) From 11f20593b2e78e0d5abf5b1e8907c200aa9533d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilkka=20Sepp=C3=A4l=C3=A4?= Date: Fri, 7 Jan 2022 09:46:59 +0200 Subject: [PATCH 15/21] Update throttling pattern (#1937) * Create component.urm.puml * Create App.java * Add files via upload * Add files via upload * Add files via upload * Add files via upload * Create AppTest.java * Add files via upload * Update README.md * Update README.md * Update pom.xml * Update App.java * Update BjornGraphicsComponent.java * Update BjornInputComponent.java * Update BjornPhysicsComponent.java * Update Component.java * Update App.java * Delete App.java * Delete BjornGraphicsComponent.java * Delete BjornInputComponent.java * Delete BjornPhysicsComponent.java * Delete Component.java * Delete GameObject.java * Delete GraphicsComponent.java * Delete InputComponent.java * Delete PhysicsComponent.java * Create App.java * Update App.java * Update App.java * Create BjornGraphicsComponent.java * Create BjornInputComponent.java * Create BjornPhysicsComponent.java * Create Component.java * Create GameObject.java * Create GraphicsComponent.java * Create InputComponent.java * Create PhysicsComponent.java * Delete AppTest.java * Delete UpdateTest.java * Create AppTest.java * Create UpdateTest.java * Update throttling pattern example * delete unwanted files Co-authored-by: YanchaoMiao <11710204@mail.sustech.edu.cn> --- thread-pool/README.md | 8 +- throttling/README.md | 173 ++++++++++-------- .../java/com/iluwatar/throttling/App.java | 34 ++-- .../{Tenant.java => BarCustomer.java} | 26 ++- .../{B2BService.java => Bartender.java} | 23 ++- .../com/iluwatar/throttling/CallsCount.java | 2 +- .../{TenantTest.java => BarCustomerTest.java} | 9 +- ...B2BServiceTest.java => BartenderTest.java} | 11 +- 8 files changed, 156 insertions(+), 130 deletions(-) rename throttling/src/main/java/com/iluwatar/throttling/{Tenant.java => BarCustomer.java} (81%) rename throttling/src/main/java/com/iluwatar/throttling/{B2BService.java => Bartender.java} (75%) rename throttling/src/test/java/com/iluwatar/throttling/{TenantTest.java => BarCustomerTest.java} (94%) rename throttling/src/test/java/com/iluwatar/throttling/{B2BServiceTest.java => BartenderTest.java} (89%) diff --git a/thread-pool/README.md b/thread-pool/README.md index d6cb11c1f..248c3a5d4 100644 --- a/thread-pool/README.md +++ b/thread-pool/README.md @@ -18,11 +18,11 @@ threads and eliminating the latency of creating new threads. ## Explanation -Real world example +Real-world example > We have a large number of relatively short tasks at hand. We need to peel huge amounts of potatoes -> and serve mighty amount of coffee cups. Creating a new thread for each task would be a waste so we -> establish a thread pool. +> and serve a mighty amount of coffee cups. Creating a new thread for each task would be a waste so +> we establish a thread pool. In plain words @@ -99,7 +99,7 @@ public class PotatoPeelingTask extends Task { } ``` -Next we present a runnable `Worker` class that the thread pool will utilize to handle all the potato +Next, we present a runnable `Worker` class that the thread pool will utilize to handle all the potato peeling and coffee making. ```java diff --git a/throttling/README.md b/throttling/README.md index 66c02a1c0..333b714f1 100644 --- a/throttling/README.md +++ b/throttling/README.md @@ -16,10 +16,12 @@ Ensure that a given client is not able to access service resources more than the ## Explanation -Real world example +Real-world example -> A large multinational corporation offers API to its customers. The API is rate-limited and each -> customer can only make certain amount of calls per second. +> A young human and an old dwarf walk into a bar. They start ordering beers from the bartender. +> The bartender immediately sees that the young human shouldn't consume too many drinks too fast +> and refuses to serve if enough time has not passed. For the old dwarf, the serving rate can +> be higher. In plain words @@ -33,30 +35,25 @@ In plain words **Programmatic Example** -Tenant class presents the clients of the API. CallsCount tracks the number of API calls per tenant. +`BarCustomer` class presents the clients of the `Bartender` API. `CallsCount` tracks the number of +calls per `BarCustomer`. ```java -public class Tenant { +public class BarCustomer { - private final String name; - private final int allowedCallsPerSecond; + @Getter + private final String name; + @Getter + private final int allowedCallsPerSecond; - public Tenant(String name, int allowedCallsPerSecond, CallsCount callsCount) { - if (allowedCallsPerSecond < 0) { - throw new InvalidParameterException("Number of calls less than 0 not allowed"); + public BarCustomer(String name, int allowedCallsPerSecond, CallsCount callsCount) { + if (allowedCallsPerSecond < 0) { + throw new InvalidParameterException("Number of calls less than 0 not allowed"); + } + this.name = name; + this.allowedCallsPerSecond = allowedCallsPerSecond; + callsCount.addTenant(name); } - this.name = name; - this.allowedCallsPerSecond = allowedCallsPerSecond; - callsCount.addTenant(name); - } - - public String getName() { - return name; - } - - public int getAllowedCallsPerSecond() { - return allowedCallsPerSecond; - } } @Slf4j @@ -76,14 +73,14 @@ public final class CallsCount { } public void reset() { - LOGGER.debug("Resetting the map."); tenantCallsCount.replaceAll((k, v) -> new AtomicLong(0)); + LOGGER.info("reset counters"); } } ``` -Next we introduce the service that the tenants are calling. To track the call count we use the -throttler timer. +Next, the service that the tenants are calling is introduced. To track the call count, a throttler +timer is used. ```java public interface Throttler { @@ -111,71 +108,103 @@ public class ThrottleTimerImpl implements Throttler { }, 0, throttlePeriod); } } +``` -class B2BService { +`Bartender` offers the `orderDrink` service to the `BarCustomer`s. The customers probably don't +know that the beer serving rate is limited by their appearances. - private static final Logger LOGGER = LoggerFactory.getLogger(B2BService.class); - private final CallsCount callsCount; +```java +class Bartender { - public B2BService(Throttler timer, CallsCount callsCount) { - this.callsCount = callsCount; - timer.start(); - } + private static final Logger LOGGER = LoggerFactory.getLogger(Bartender.class); + private final CallsCount callsCount; - public int dummyCustomerApi(Tenant tenant) { - var tenantName = tenant.getName(); - var count = callsCount.getCount(tenantName); - LOGGER.debug("Counter for {} : {} ", tenant.getName(), count); - if (count >= tenant.getAllowedCallsPerSecond()) { - LOGGER.error("API access per second limit reached for: {}", tenantName); - return -1; + public Bartender(Throttler timer, CallsCount callsCount) { + this.callsCount = callsCount; + timer.start(); } - callsCount.incrementCount(tenantName); - return getRandomCustomerId(); - } - private int getRandomCustomerId() { - return ThreadLocalRandom.current().nextInt(1, 10000); - } + public int orderDrink(BarCustomer barCustomer) { + var tenantName = barCustomer.getName(); + var count = callsCount.getCount(tenantName); + if (count >= barCustomer.getAllowedCallsPerSecond()) { + LOGGER.error("I'm sorry {}, you've had enough for today!", tenantName); + return -1; + } + callsCount.incrementCount(tenantName); + LOGGER.debug("Serving beer to {} : [{} consumed] ", barCustomer.getName(), count+1); + return getRandomCustomerId(); + } + + private int getRandomCustomerId() { + return ThreadLocalRandom.current().nextInt(1, 10000); + } } ``` -Now we are ready to see the full example in action. Tenant Adidas is rate-limited to 5 calls per -second and Nike to 6. +Now it is possible to see the full example in action. `BarCustomer` young human is rate-limited to 2 +calls per second and the old dwarf to 4. ```java - public static void main(String[] args) { +public static void main(String[] args) { var callsCount = new CallsCount(); - var adidas = new Tenant("Adidas", 5, callsCount); - var nike = new Tenant("Nike", 6, callsCount); + var human = new BarCustomer("young human", 2, callsCount); + var dwarf = new BarCustomer("dwarf soldier", 4, callsCount); var executorService = Executors.newFixedThreadPool(2); - executorService.execute(() -> makeServiceCalls(adidas, callsCount)); - executorService.execute(() -> makeServiceCalls(nike, callsCount)); - executorService.shutdown(); - - try { - executorService.awaitTermination(10, TimeUnit.SECONDS); - } catch (InterruptedException e) { - LOGGER.error("Executor Service terminated: {}", e.getMessage()); - } - } - private static void makeServiceCalls(Tenant tenant, CallsCount callsCount) { - var timer = new ThrottleTimerImpl(10, callsCount); - var service = new B2BService(timer, callsCount); + executorService.execute(() -> makeServiceCalls(human, callsCount)); + executorService.execute(() -> makeServiceCalls(dwarf, callsCount)); + + executorService.shutdown(); + try { + executorService.awaitTermination(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + LOGGER.error("Executor service terminated: {}", e.getMessage()); + } +} + +private static void makeServiceCalls(BarCustomer barCustomer, CallsCount callsCount) { + var timer = new ThrottleTimerImpl(1000, callsCount); + var service = new Bartender(timer, callsCount); // Sleep is introduced to keep the output in check and easy to view and analyze the results. - IntStream.range(0, 20).forEach(i -> { - service.dummyCustomerApi(tenant); - try { - Thread.sleep(1); - } catch (InterruptedException e) { - LOGGER.error("Thread interrupted: {}", e.getMessage()); - } + IntStream.range(0, 50).forEach(i -> { + service.orderDrink(barCustomer); + try { + Thread.sleep(100); + } catch (InterruptedException e) { + LOGGER.error("Thread interrupted: {}", e.getMessage()); + } }); - } +} ``` +An excerpt from the example's console output: + +``` +18:46:36.218 [Timer-0] INFO com.iluwatar.throttling.CallsCount - reset counters +18:46:36.218 [Timer-1] INFO com.iluwatar.throttling.CallsCount - reset counters +18:46:36.242 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [1 consumed] +18:46:36.242 [pool-1-thread-1] DEBUG com.iluwatar.throttling.Bartender - Serving beer to young human : [1 consumed] +18:46:36.342 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [2 consumed] +18:46:36.342 [pool-1-thread-1] DEBUG com.iluwatar.throttling.Bartender - Serving beer to young human : [2 consumed] +18:46:36.443 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today! +18:46:36.443 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [3 consumed] +18:46:36.544 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today! +18:46:36.544 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [4 consumed] +18:46:36.645 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today! +18:46:36.645 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today! +18:46:36.745 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today! +18:46:36.745 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today! +18:46:36.846 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today! +18:46:36.846 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today! +18:46:36.947 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today! +18:46:36.947 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today! +18:46:37.048 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today! +18:46:37.048 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today! +18:46:37.148 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today! +18:46:37.148 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today! +``` ## Class diagram @@ -185,7 +214,7 @@ second and Nike to 6. The Throttling pattern should be used: -* When a service access needs to be restricted to not have high impacts on the performance of the service. +* When service access needs to be restricted not to have high impact on the performance of the service. * When multiple clients are consuming the same service resources and restriction has to be made according to the usage per client. ## Credits diff --git a/throttling/src/main/java/com/iluwatar/throttling/App.java b/throttling/src/main/java/com/iluwatar/throttling/App.java index ab8aa8601..1db6ecf93 100644 --- a/throttling/src/main/java/com/iluwatar/throttling/App.java +++ b/throttling/src/main/java/com/iluwatar/throttling/App.java @@ -34,11 +34,11 @@ import lombok.extern.slf4j.Slf4j; * complete service by users or a particular tenant. This can allow systems to continue to function * and meet service level agreements, even when an increase in demand places load on resources. *

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

- * ({@link Tenant}) is the Tenant POJO class with which many tenants can be created ({@link - * B2BService}) is the service which is consumed by the tenants and is throttled. + * ({@link BarCustomer}) is the service tenant class having a name and the number of calls allowed. + * ({@link Bartender}) is the service which is consumed by the tenants and is throttled. */ @Slf4j public class App { @@ -50,33 +50,35 @@ public class App { */ public static void main(String[] args) { var callsCount = new CallsCount(); - var adidas = new Tenant("Adidas", 5, callsCount); - var nike = new Tenant("Nike", 6, callsCount); + var human = new BarCustomer("young human", 2, callsCount); + var dwarf = new BarCustomer("dwarf soldier", 4, callsCount); var executorService = Executors.newFixedThreadPool(2); - executorService.execute(() -> makeServiceCalls(adidas, callsCount)); - executorService.execute(() -> makeServiceCalls(nike, callsCount)); + executorService.execute(() -> makeServiceCalls(human, callsCount)); + executorService.execute(() -> makeServiceCalls(dwarf, callsCount)); executorService.shutdown(); try { - executorService.awaitTermination(10, TimeUnit.SECONDS); + if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) { + executorService.shutdownNow(); + } } catch (InterruptedException e) { - LOGGER.error("Executor Service terminated: {}", e.getMessage()); + executorService.shutdownNow(); } } /** - * Make calls to the B2BService dummy API. + * Make calls to the bartender. */ - private static void makeServiceCalls(Tenant tenant, CallsCount callsCount) { - var timer = new ThrottleTimerImpl(10, callsCount); - var service = new B2BService(timer, callsCount); + private static void makeServiceCalls(BarCustomer barCustomer, CallsCount callsCount) { + var timer = new ThrottleTimerImpl(1000, callsCount); + var service = new Bartender(timer, callsCount); // Sleep is introduced to keep the output in check and easy to view and analyze the results. - IntStream.range(0, 20).forEach(i -> { - service.dummyCustomerApi(tenant); + IntStream.range(0, 50).forEach(i -> { + service.orderDrink(barCustomer); try { - Thread.sleep(1); + Thread.sleep(100); } catch (InterruptedException e) { LOGGER.error("Thread interrupted: {}", e.getMessage()); } diff --git a/throttling/src/main/java/com/iluwatar/throttling/Tenant.java b/throttling/src/main/java/com/iluwatar/throttling/BarCustomer.java similarity index 81% rename from throttling/src/main/java/com/iluwatar/throttling/Tenant.java rename to throttling/src/main/java/com/iluwatar/throttling/BarCustomer.java index 60a08704f..b46670861 100644 --- a/throttling/src/main/java/com/iluwatar/throttling/Tenant.java +++ b/throttling/src/main/java/com/iluwatar/throttling/BarCustomer.java @@ -25,22 +25,26 @@ package com.iluwatar.throttling; import java.security.InvalidParameterException; -/** - * A Pojo class to create a basic Tenant with the allowed calls per second. - */ -public class Tenant { +import lombok.Getter; +/** + * BarCustomer is a tenant with a name and a number of allowed calls per second. + */ +public class BarCustomer { + + @Getter private final String name; + @Getter private final int allowedCallsPerSecond; /** * Constructor. * - * @param name Name of the tenant - * @param allowedCallsPerSecond The number of calls allowed for a particular tenant. + * @param name Name of the BarCustomer + * @param allowedCallsPerSecond The number of calls allowed for this particular tenant. * @throws InvalidParameterException If number of calls is less than 0, throws exception. */ - public Tenant(String name, int allowedCallsPerSecond, CallsCount callsCount) { + public BarCustomer(String name, int allowedCallsPerSecond, CallsCount callsCount) { if (allowedCallsPerSecond < 0) { throw new InvalidParameterException("Number of calls less than 0 not allowed"); } @@ -48,12 +52,4 @@ public class Tenant { this.allowedCallsPerSecond = allowedCallsPerSecond; callsCount.addTenant(name); } - - public String getName() { - return name; - } - - public int getAllowedCallsPerSecond() { - return allowedCallsPerSecond; - } } diff --git a/throttling/src/main/java/com/iluwatar/throttling/B2BService.java b/throttling/src/main/java/com/iluwatar/throttling/Bartender.java similarity index 75% rename from throttling/src/main/java/com/iluwatar/throttling/B2BService.java rename to throttling/src/main/java/com/iluwatar/throttling/Bartender.java index 70657afd8..e81d770d7 100644 --- a/throttling/src/main/java/com/iluwatar/throttling/B2BService.java +++ b/throttling/src/main/java/com/iluwatar/throttling/Bartender.java @@ -29,33 +29,32 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * A service which accepts a tenant and throttles the resource based on the time given to the - * tenant. + * Bartender is a service which accepts a BarCustomer (tenant) and throttles + * the resource based on the time given to the tenant. */ -class B2BService { +class Bartender { - private static final Logger LOGGER = LoggerFactory.getLogger(B2BService.class); + private static final Logger LOGGER = LoggerFactory.getLogger(Bartender.class); private final CallsCount callsCount; - public B2BService(Throttler timer, CallsCount callsCount) { + public Bartender(Throttler timer, CallsCount callsCount) { this.callsCount = callsCount; timer.start(); } /** - * Calls dummy customer api. - * + * Orders a drink from the bartender. * @return customer id which is randomly generated */ - public int dummyCustomerApi(Tenant tenant) { - var tenantName = tenant.getName(); + public int orderDrink(BarCustomer barCustomer) { + var tenantName = barCustomer.getName(); var count = callsCount.getCount(tenantName); - LOGGER.debug("Counter for {} : {} ", tenant.getName(), count); - if (count >= tenant.getAllowedCallsPerSecond()) { - LOGGER.error("API access per second limit reached for: {}", tenantName); + if (count >= barCustomer.getAllowedCallsPerSecond()) { + LOGGER.error("I'm sorry {}, you've had enough for today!", tenantName); return -1; } callsCount.incrementCount(tenantName); + LOGGER.debug("Serving beer to {} : [{} consumed] ", barCustomer.getName(), count + 1); return getRandomCustomerId(); } diff --git a/throttling/src/main/java/com/iluwatar/throttling/CallsCount.java b/throttling/src/main/java/com/iluwatar/throttling/CallsCount.java index 88a80d481..4196e10dd 100644 --- a/throttling/src/main/java/com/iluwatar/throttling/CallsCount.java +++ b/throttling/src/main/java/com/iluwatar/throttling/CallsCount.java @@ -69,7 +69,7 @@ public final class CallsCount { * Resets the count of all the tenants in the map. */ public void reset() { - LOGGER.debug("Resetting the map."); tenantCallsCount.replaceAll((k, v) -> new AtomicLong(0)); + LOGGER.info("reset counters"); } } diff --git a/throttling/src/test/java/com/iluwatar/throttling/TenantTest.java b/throttling/src/test/java/com/iluwatar/throttling/BarCustomerTest.java similarity index 94% rename from throttling/src/test/java/com/iluwatar/throttling/TenantTest.java rename to throttling/src/test/java/com/iluwatar/throttling/BarCustomerTest.java index 2ea33ec3d..9818fdf6b 100644 --- a/throttling/src/test/java/com/iluwatar/throttling/TenantTest.java +++ b/throttling/src/test/java/com/iluwatar/throttling/BarCustomerTest.java @@ -23,20 +23,21 @@ package com.iluwatar.throttling; -import static org.junit.jupiter.api.Assertions.assertThrows; +import org.junit.jupiter.api.Test; import java.security.InvalidParameterException; -import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertThrows; /** * TenantTest to test the creation of Tenant with valid parameters. */ -public class TenantTest { +public class BarCustomerTest { @Test void constructorTest() { assertThrows(InvalidParameterException.class, () -> { - new Tenant("FailTenant", -1, new CallsCount()); + new BarCustomer("sirBrave", -1, new CallsCount()); }); } } diff --git a/throttling/src/test/java/com/iluwatar/throttling/B2BServiceTest.java b/throttling/src/test/java/com/iluwatar/throttling/BartenderTest.java similarity index 89% rename from throttling/src/test/java/com/iluwatar/throttling/B2BServiceTest.java rename to throttling/src/test/java/com/iluwatar/throttling/BartenderTest.java index 93cf3efaa..b2d80fbd4 100644 --- a/throttling/src/test/java/com/iluwatar/throttling/B2BServiceTest.java +++ b/throttling/src/test/java/com/iluwatar/throttling/BartenderTest.java @@ -32,19 +32,18 @@ import org.junit.jupiter.api.Test; /** * B2BServiceTest class to test the B2BService */ -public class B2BServiceTest { +public class BartenderTest { private final CallsCount callsCount = new CallsCount(); @Test void dummyCustomerApiTest() { - var tenant = new Tenant("testTenant", 2, callsCount); + var tenant = new BarCustomer("pirate", 2, callsCount); // In order to assure that throttling limits will not be reset, we use an empty throttling implementation - var timer = (Throttler) () -> { - }; - var service = new B2BService(timer, callsCount); + var timer = (Throttler) () -> {}; + var service = new Bartender(timer, callsCount); - IntStream.range(0, 5).mapToObj(i -> tenant).forEach(service::dummyCustomerApi); + IntStream.range(0, 5).mapToObj(i -> tenant).forEach(service::orderDrink); var counter = callsCount.getCount(tenant.getName()); assertEquals(2, counter, "Counter limit must be reached"); } From 3cc9bc2dea31e4e86a96dc985a0164a3856fe86d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilkka=20Sepp=C3=A4l=C3=A4?= Date: Sat, 8 Jan 2022 14:27:11 +0200 Subject: [PATCH 16/21] refactoring: unit of work (#1940) Co-authored-by: Subhrodip Mohanta --- unit-of-work/README.md | 216 +++++++++--------- unit-of-work/etc/unit-of-work.ucls | 6 +- .../java/com/iluwatar/unitofwork/App.java | 24 +- ...StudentRepository.java => ArmsDealer.java} | 62 ++--- .../unitofwork/{Student.java => Weapon.java} | 6 +- ...udentDatabase.java => WeaponDatabase.java} | 10 +- ...epositoryTest.java => ArmsDealerTest.java} | 82 +++---- 7 files changed, 205 insertions(+), 201 deletions(-) rename unit-of-work/src/main/java/com/iluwatar/unitofwork/{StudentRepository.java => ArmsDealer.java} (55%) rename unit-of-work/src/main/java/com/iluwatar/unitofwork/{Student.java => Weapon.java} (93%) rename unit-of-work/src/main/java/com/iluwatar/unitofwork/{StudentDatabase.java => WeaponDatabase.java} (87%) rename unit-of-work/src/test/java/com/iluwatar/unitofwork/{StudentRepositoryTest.java => ArmsDealerTest.java} (51%) diff --git a/unit-of-work/README.md b/unit-of-work/README.md index df2d03df2..f60667810 100644 --- a/unit-of-work/README.md +++ b/unit-of-work/README.md @@ -12,20 +12,20 @@ tags: ## Intent -When a business transaction is completed, all the the updates are sent as one big unit of work to be +When a business transaction is completed, all the updates are sent as one big unit of work to be persisted in one go to minimize database round-trips. ## Explanation -Real world example +Real-world example -> We have a database containing student information. Administrators all over the country are -> constantly updating this information and it causes high load on the database server. To make the +> Arms dealer has a database containing weapon information. Merchants all over the town are +> constantly updating this information and it causes a high load on the database server. To make the > load more manageable we apply to Unit of Work pattern to send many small updates in batches. In plain words -> Unit of Work merges many small database updates in single batch to optimize the number of +> Unit of Work merges many small database updates in a single batch to optimize the number of > round-trips. [MartinFowler.com](https://martinfowler.com/eaaCatalog/unitOfWork.html) says @@ -35,37 +35,20 @@ In plain words **Programmatic Example** -Here's the `Student` entity that is being persisted to the database. +Here's the `Weapon` entity that is being persisted in the database. ```java -public class Student { - private final Integer id; - private final String name; - private final String address; - - public Student(Integer id, String name, String address) { - this.id = id; - this.name = name; - this.address = address; - } - - public String getName() { - return name; - } - - public Integer getId() { - return id; - } - - public String getAddress() { - return address; - } +@Getter +@RequiredArgsConstructor +public class Weapon { + private final Integer id; + private final String name; } ``` -The essence of the implementation is the `StudentRepository` implementing the Unit of Work pattern. +The essence of the implementation is the `ArmsDealer` implementing the Unit of Work pattern. It maintains a map of database operations (`context`) that need to be done and when `commit` is -called it applies them in single batch. +called it applies them in a single batch. ```java public interface IUnitOfWork { @@ -84,96 +67,117 @@ public interface IUnitOfWork { } @Slf4j -public class StudentRepository implements IUnitOfWork { +@RequiredArgsConstructor +public class ArmsDealer implements IUnitOfWork { - private final Map> context; - private final StudentDatabase studentDatabase; + private final Map> context; + private final WeaponDatabase weaponDatabase; - public StudentRepository(Map> context, StudentDatabase studentDatabase) { - this.context = context; - this.studentDatabase = studentDatabase; - } - - @Override - public void registerNew(Student student) { - LOGGER.info("Registering {} for insert in context.", student.getName()); - register(student, IUnitOfWork.INSERT); - } - - @Override - public void registerModified(Student student) { - LOGGER.info("Registering {} for modify in context.", student.getName()); - register(student, IUnitOfWork.MODIFY); - - } - - @Override - public void registerDeleted(Student student) { - LOGGER.info("Registering {} for delete in context.", student.getName()); - register(student, IUnitOfWork.DELETE); - } - - private void register(Student student, String operation) { - var studentsToOperate = context.get(operation); - if (studentsToOperate == null) { - studentsToOperate = new ArrayList<>(); - } - studentsToOperate.add(student); - context.put(operation, studentsToOperate); - } - - @Override - public void commit() { - if (context == null || context.size() == 0) { - return; - } - LOGGER.info("Commit started"); - if (context.containsKey(IUnitOfWork.INSERT)) { - commitInsert(); + @Override + public void registerNew(Weapon weapon) { + LOGGER.info("Registering {} for insert in context.", weapon.getName()); + register(weapon, UnitActions.INSERT.getActionValue()); } - if (context.containsKey(IUnitOfWork.MODIFY)) { - commitModify(); - } - if (context.containsKey(IUnitOfWork.DELETE)) { - commitDelete(); - } - LOGGER.info("Commit finished."); - } + @Override + public void registerModified(Weapon weapon) { + LOGGER.info("Registering {} for modify in context.", weapon.getName()); + register(weapon, UnitActions.MODIFY.getActionValue()); - private void commitInsert() { - var studentsToBeInserted = context.get(IUnitOfWork.INSERT); - for (var student : studentsToBeInserted) { - LOGGER.info("Saving {} to database.", student.getName()); - studentDatabase.insert(student); } - } - private void commitModify() { - var modifiedStudents = context.get(IUnitOfWork.MODIFY); - for (var student : modifiedStudents) { - LOGGER.info("Modifying {} to database.", student.getName()); - studentDatabase.modify(student); + @Override + public void registerDeleted(Weapon weapon) { + LOGGER.info("Registering {} for delete in context.", weapon.getName()); + register(weapon, UnitActions.DELETE.getActionValue()); } - } - private void commitDelete() { - var deletedStudents = context.get(IUnitOfWork.DELETE); - for (var student : deletedStudents) { - LOGGER.info("Deleting {} to database.", student.getName()); - studentDatabase.delete(student); + private void register(Weapon weapon, String operation) { + var weaponsToOperate = context.get(operation); + if (weaponsToOperate == null) { + weaponsToOperate = new ArrayList<>(); + } + weaponsToOperate.add(weapon); + context.put(operation, weaponsToOperate); + } + + /** + * All UnitOfWork operations are batched and executed together on commit only. + */ + @Override + public void commit() { + if (context == null || context.size() == 0) { + return; + } + LOGGER.info("Commit started"); + if (context.containsKey(UnitActions.INSERT.getActionValue())) { + commitInsert(); + } + + if (context.containsKey(UnitActions.MODIFY.getActionValue())) { + commitModify(); + } + if (context.containsKey(UnitActions.DELETE.getActionValue())) { + commitDelete(); + } + LOGGER.info("Commit finished."); + } + + private void commitInsert() { + var weaponsToBeInserted = context.get(UnitActions.INSERT.getActionValue()); + for (var weapon : weaponsToBeInserted) { + LOGGER.info("Inserting a new weapon {} to sales rack.", weapon.getName()); + weaponDatabase.insert(weapon); + } + } + + private void commitModify() { + var modifiedWeapons = context.get(UnitActions.MODIFY.getActionValue()); + for (var weapon : modifiedWeapons) { + LOGGER.info("Scheduling {} for modification work.", weapon.getName()); + weaponDatabase.modify(weapon); + } + } + + private void commitDelete() { + var deletedWeapons = context.get(UnitActions.DELETE.getActionValue()); + for (var weapon : deletedWeapons) { + LOGGER.info("Scrapping {}.", weapon.getName()); + weaponDatabase.delete(weapon); + } } - } } ``` -Finally, here's how we use the `StudentRepository` and `commit` the transaction. +Here is how the whole app is put together. ```java - studentRepository.registerNew(ram); - studentRepository.registerModified(shyam); - studentRepository.registerDeleted(gopi); - studentRepository.commit(); +// create some weapons +var enchantedHammer = new Weapon(1, "enchanted hammer"); +var brokenGreatSword = new Weapon(2, "broken great sword"); +var silverTrident = new Weapon(3, "silver trident"); + +// create repository +var weaponRepository = new ArmsDealer(new HashMap>(), new WeaponDatabase()); + +// perform operations on the weapons +weaponRepository.registerNew(enchantedHammer); +weaponRepository.registerModified(silverTrident); +weaponRepository.registerDeleted(brokenGreatSword); +weaponRepository.commit(); +``` + +Here is the console output. + +``` +21:39:21.984 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Registering enchanted hammer for insert in context. +21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Registering silver trident for modify in context. +21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Registering broken great sword for delete in context. +21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Commit started +21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Inserting a new weapon enchanted hammer to sales rack. +21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Scheduling silver trident for modification work. +21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Scrapping broken great sword. +21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Commit finished. ``` ## Class diagram @@ -186,7 +190,7 @@ Use the Unit Of Work pattern when * To optimize the time taken for database transactions. * To send changes to database as a unit of work which ensures atomicity of the transaction. -* To reduce number of database calls. +* To reduce the number of database calls. ## Tutorials diff --git a/unit-of-work/etc/unit-of-work.ucls b/unit-of-work/etc/unit-of-work.ucls index 98181f805..0a80d680d 100644 --- a/unit-of-work/etc/unit-of-work.ucls +++ b/unit-of-work/etc/unit-of-work.ucls @@ -1,7 +1,7 @@ - - @@ -38,7 +38,7 @@ - >(); - var studentDatabase = new StudentDatabase(); - var studentRepository = new StudentRepository(context, studentDatabase); + // create repository + var weaponRepository = new ArmsDealer(new HashMap>(), + new WeaponDatabase()); - studentRepository.registerNew(ram); - studentRepository.registerModified(shyam); - studentRepository.registerDeleted(gopi); - studentRepository.commit(); + // perform operations on the weapons + weaponRepository.registerNew(enchantedHammer); + weaponRepository.registerModified(silverTrident); + weaponRepository.registerDeleted(brokenGreatSword); + weaponRepository.commit(); } } diff --git a/unit-of-work/src/main/java/com/iluwatar/unitofwork/StudentRepository.java b/unit-of-work/src/main/java/com/iluwatar/unitofwork/ArmsDealer.java similarity index 55% rename from unit-of-work/src/main/java/com/iluwatar/unitofwork/StudentRepository.java rename to unit-of-work/src/main/java/com/iluwatar/unitofwork/ArmsDealer.java index 991aef12a..c222e47d4 100644 --- a/unit-of-work/src/main/java/com/iluwatar/unitofwork/StudentRepository.java +++ b/unit-of-work/src/main/java/com/iluwatar/unitofwork/ArmsDealer.java @@ -30,41 +30,41 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; /** - * {@link StudentRepository} Student database repository. supports unit of work for student data. + * {@link ArmsDealer} Weapon repository that supports unit of work for weapons. */ @Slf4j @RequiredArgsConstructor -public class StudentRepository implements IUnitOfWork { +public class ArmsDealer implements IUnitOfWork { - private final Map> context; - private final StudentDatabase studentDatabase; + private final Map> context; + private final WeaponDatabase weaponDatabase; @Override - public void registerNew(Student student) { - LOGGER.info("Registering {} for insert in context.", student.getName()); - register(student, UnitActions.INSERT.getActionValue()); + public void registerNew(Weapon weapon) { + LOGGER.info("Registering {} for insert in context.", weapon.getName()); + register(weapon, UnitActions.INSERT.getActionValue()); } @Override - public void registerModified(Student student) { - LOGGER.info("Registering {} for modify in context.", student.getName()); - register(student, UnitActions.MODIFY.getActionValue()); + public void registerModified(Weapon weapon) { + LOGGER.info("Registering {} for modify in context.", weapon.getName()); + register(weapon, UnitActions.MODIFY.getActionValue()); } @Override - public void registerDeleted(Student student) { - LOGGER.info("Registering {} for delete in context.", student.getName()); - register(student, UnitActions.DELETE.getActionValue()); + public void registerDeleted(Weapon weapon) { + LOGGER.info("Registering {} for delete in context.", weapon.getName()); + register(weapon, UnitActions.DELETE.getActionValue()); } - private void register(Student student, String operation) { - var studentsToOperate = context.get(operation); - if (studentsToOperate == null) { - studentsToOperate = new ArrayList<>(); + private void register(Weapon weapon, String operation) { + var weaponsToOperate = context.get(operation); + if (weaponsToOperate == null) { + weaponsToOperate = new ArrayList<>(); } - studentsToOperate.add(student); - context.put(operation, studentsToOperate); + weaponsToOperate.add(weapon); + context.put(operation, weaponsToOperate); } /** @@ -90,26 +90,26 @@ public class StudentRepository implements IUnitOfWork { } private void commitInsert() { - var studentsToBeInserted = context.get(UnitActions.INSERT.getActionValue()); - for (var student : studentsToBeInserted) { - LOGGER.info("Saving {} to database.", student.getName()); - studentDatabase.insert(student); + var weaponsToBeInserted = context.get(UnitActions.INSERT.getActionValue()); + for (var weapon : weaponsToBeInserted) { + LOGGER.info("Inserting a new weapon {} to sales rack.", weapon.getName()); + weaponDatabase.insert(weapon); } } private void commitModify() { - var modifiedStudents = context.get(UnitActions.MODIFY.getActionValue()); - for (var student : modifiedStudents) { - LOGGER.info("Modifying {} to database.", student.getName()); - studentDatabase.modify(student); + var modifiedWeapons = context.get(UnitActions.MODIFY.getActionValue()); + for (var weapon : modifiedWeapons) { + LOGGER.info("Scheduling {} for modification work.", weapon.getName()); + weaponDatabase.modify(weapon); } } private void commitDelete() { - var deletedStudents = context.get(UnitActions.DELETE.getActionValue()); - for (var student : deletedStudents) { - LOGGER.info("Deleting {} to database.", student.getName()); - studentDatabase.delete(student); + var deletedWeapons = context.get(UnitActions.DELETE.getActionValue()); + for (var weapon : deletedWeapons) { + LOGGER.info("Scrapping {}.", weapon.getName()); + weaponDatabase.delete(weapon); } } } diff --git a/unit-of-work/src/main/java/com/iluwatar/unitofwork/Student.java b/unit-of-work/src/main/java/com/iluwatar/unitofwork/Weapon.java similarity index 93% rename from unit-of-work/src/main/java/com/iluwatar/unitofwork/Student.java rename to unit-of-work/src/main/java/com/iluwatar/unitofwork/Weapon.java index b3de369b4..bf4a3e071 100644 --- a/unit-of-work/src/main/java/com/iluwatar/unitofwork/Student.java +++ b/unit-of-work/src/main/java/com/iluwatar/unitofwork/Weapon.java @@ -27,14 +27,12 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; /** - * {@link Student} is an entity. + * {@link Weapon} is an entity. */ @Getter @RequiredArgsConstructor -public class Student { +public class Weapon { private final Integer id; private final String name; - private final String address; - } diff --git a/unit-of-work/src/main/java/com/iluwatar/unitofwork/StudentDatabase.java b/unit-of-work/src/main/java/com/iluwatar/unitofwork/WeaponDatabase.java similarity index 87% rename from unit-of-work/src/main/java/com/iluwatar/unitofwork/StudentDatabase.java rename to unit-of-work/src/main/java/com/iluwatar/unitofwork/WeaponDatabase.java index c64c47e30..c9a8cee4b 100644 --- a/unit-of-work/src/main/java/com/iluwatar/unitofwork/StudentDatabase.java +++ b/unit-of-work/src/main/java/com/iluwatar/unitofwork/WeaponDatabase.java @@ -24,19 +24,19 @@ package com.iluwatar.unitofwork; /** - * Act as Database for student records. + * Act as database for weapon records. */ -public class StudentDatabase { +public class WeaponDatabase { - public void insert(Student student) { + public void insert(Weapon weapon) { //Some insert logic to DB } - public void modify(Student student) { + public void modify(Weapon weapon) { //Some modify logic to DB } - public void delete(Student student) { + public void delete(Weapon weapon) { //Some delete logic to DB } } diff --git a/unit-of-work/src/test/java/com/iluwatar/unitofwork/StudentRepositoryTest.java b/unit-of-work/src/test/java/com/iluwatar/unitofwork/ArmsDealerTest.java similarity index 51% rename from unit-of-work/src/test/java/com/iluwatar/unitofwork/StudentRepositoryTest.java rename to unit-of-work/src/test/java/com/iluwatar/unitofwork/ArmsDealerTest.java index 760d4a21b..65b65a623 100644 --- a/unit-of-work/src/test/java/com/iluwatar/unitofwork/StudentRepositoryTest.java +++ b/unit-of-work/src/test/java/com/iluwatar/unitofwork/ArmsDealerTest.java @@ -36,102 +36,102 @@ import java.util.Map; import org.junit.jupiter.api.Test; /** - * tests {@link StudentRepository} + * tests {@link ArmsDealer} */ -class StudentRepositoryTest { - private final Student student1 = new Student(1, "Ram", "street 9, cupertino"); - private final Student student2 = new Student(1, "Sham", "Z bridge, pune"); +class ArmsDealerTest { + private final Weapon weapon1 = new Weapon(1, "battle ram"); + private final Weapon weapon2 = new Weapon(1, "wooden lance"); - private final Map> context = new HashMap<>(); - private final StudentDatabase studentDatabase = mock(StudentDatabase.class); - private final StudentRepository studentRepository = new StudentRepository(context, studentDatabase);; + private final Map> context = new HashMap<>(); + private final WeaponDatabase weaponDatabase = mock(WeaponDatabase.class); + private final ArmsDealer armsDealer = new ArmsDealer(context, weaponDatabase);; @Test void shouldSaveNewStudentWithoutWritingToDb() { - studentRepository.registerNew(student1); - studentRepository.registerNew(student2); + armsDealer.registerNew(weapon1); + armsDealer.registerNew(weapon2); assertEquals(2, context.get(UnitActions.INSERT.getActionValue()).size()); - verifyNoMoreInteractions(studentDatabase); + verifyNoMoreInteractions(weaponDatabase); } @Test void shouldSaveDeletedStudentWithoutWritingToDb() { - studentRepository.registerDeleted(student1); - studentRepository.registerDeleted(student2); + armsDealer.registerDeleted(weapon1); + armsDealer.registerDeleted(weapon2); assertEquals(2, context.get(UnitActions.DELETE.getActionValue()).size()); - verifyNoMoreInteractions(studentDatabase); + verifyNoMoreInteractions(weaponDatabase); } @Test void shouldSaveModifiedStudentWithoutWritingToDb() { - studentRepository.registerModified(student1); - studentRepository.registerModified(student2); + armsDealer.registerModified(weapon1); + armsDealer.registerModified(weapon2); assertEquals(2, context.get(UnitActions.MODIFY.getActionValue()).size()); - verifyNoMoreInteractions(studentDatabase); + verifyNoMoreInteractions(weaponDatabase); } @Test void shouldSaveAllLocalChangesToDb() { - context.put(UnitActions.INSERT.getActionValue(), List.of(student1)); - context.put(UnitActions.MODIFY.getActionValue(), List.of(student1)); - context.put(UnitActions.DELETE.getActionValue(), List.of(student1)); + context.put(UnitActions.INSERT.getActionValue(), List.of(weapon1)); + context.put(UnitActions.MODIFY.getActionValue(), List.of(weapon1)); + context.put(UnitActions.DELETE.getActionValue(), List.of(weapon1)); - studentRepository.commit(); + armsDealer.commit(); - verify(studentDatabase, times(1)).insert(student1); - verify(studentDatabase, times(1)).modify(student1); - verify(studentDatabase, times(1)).delete(student1); + verify(weaponDatabase, times(1)).insert(weapon1); + verify(weaponDatabase, times(1)).modify(weapon1); + verify(weaponDatabase, times(1)).delete(weapon1); } @Test void shouldNotWriteToDbIfContextIsNull() { - var studentRepository = new StudentRepository(null, studentDatabase); + var weaponRepository = new ArmsDealer(null, weaponDatabase); - studentRepository.commit(); + weaponRepository.commit(); - verifyNoMoreInteractions(studentDatabase); + verifyNoMoreInteractions(weaponDatabase); } @Test void shouldNotWriteToDbIfNothingToCommit() { - var studentRepository = new StudentRepository(new HashMap<>(), studentDatabase); + var weaponRepository = new ArmsDealer(new HashMap<>(), weaponDatabase); - studentRepository.commit(); + weaponRepository.commit(); - verifyNoMoreInteractions(studentDatabase); + verifyNoMoreInteractions(weaponDatabase); } @Test void shouldNotInsertToDbIfNoRegisteredStudentsToBeCommitted() { - context.put(UnitActions.MODIFY.getActionValue(), List.of(student1)); - context.put(UnitActions.DELETE.getActionValue(), List.of(student1)); + context.put(UnitActions.MODIFY.getActionValue(), List.of(weapon1)); + context.put(UnitActions.DELETE.getActionValue(), List.of(weapon1)); - studentRepository.commit(); + armsDealer.commit(); - verify(studentDatabase, never()).insert(student1); + verify(weaponDatabase, never()).insert(weapon1); } @Test void shouldNotModifyToDbIfNotRegisteredStudentsToBeCommitted() { - context.put(UnitActions.INSERT.getActionValue(), List.of(student1)); - context.put(UnitActions.DELETE.getActionValue(), List.of(student1)); + context.put(UnitActions.INSERT.getActionValue(), List.of(weapon1)); + context.put(UnitActions.DELETE.getActionValue(), List.of(weapon1)); - studentRepository.commit(); + armsDealer.commit(); - verify(studentDatabase, never()).modify(student1); + verify(weaponDatabase, never()).modify(weapon1); } @Test void shouldNotDeleteFromDbIfNotRegisteredStudentsToBeCommitted() { - context.put(UnitActions.INSERT.getActionValue(), List.of(student1)); - context.put(UnitActions.MODIFY.getActionValue(), List.of(student1)); + context.put(UnitActions.INSERT.getActionValue(), List.of(weapon1)); + context.put(UnitActions.MODIFY.getActionValue(), List.of(weapon1)); - studentRepository.commit(); + armsDealer.commit(); - verify(studentDatabase, never()).delete(student1); + verify(weaponDatabase, never()).delete(weapon1); } } From 2d2dec98e839562f494392fb3091e8c5992303f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilkka=20Sepp=C3=A4l=C3=A4?= Date: Sat, 8 Jan 2022 14:29:30 +0200 Subject: [PATCH 17/21] enhancement: Add explanation for factory kit (#1941) Co-authored-by: Subhrodip Mohanta --- factory-kit/README.md | 106 +++++++++++++++++- .../java/com/iluwatar/factorykit/App.java | 16 ++- 2 files changed, 112 insertions(+), 10 deletions(-) diff --git a/factory-kit/README.md b/factory-kit/README.md index 346012c07..861ddb862 100644 --- a/factory-kit/README.md +++ b/factory-kit/README.md @@ -10,19 +10,115 @@ tags: --- ## Intent + Define a factory of immutable content with separated builder and factory interfaces. +## Explanation + +Real-world example + +> Imagine a magical weapon factory that can create any type of weapon wished for. When the factory +> is unboxed, the master recites the weapon types needed to prepare it. After that, any of those +> weapon types can be summoned in an instant. + +In plain words + +> Factory kit is a configurable object builder. + +**Programmatic Example** + +Let's first define the simple `Weapon` hierarchy. + +```java +public interface Weapon { +} + +public enum WeaponType { + SWORD, + AXE, + BOW, + SPEAR +} + +public class Sword implements Weapon { + @Override + public String toString() { + return "Sword"; + } +} + +// Axe, Bow, and Spear are defined similarly +``` + +Next, we define a functional interface that allows adding a builder with a name to the factory. + +```java +public interface Builder { + void add(WeaponType name, Supplier supplier); +} +``` + +The meat of the example is the `WeaponFactory` interface that effectively implements the factory +kit pattern. The method `#factory` is used to configure the factory with the classes it needs to +be able to construct. The method `#create` is then used to create object instances. + +```java +public interface WeaponFactory { + + static WeaponFactory factory(Consumer consumer) { + var map = new HashMap>(); + consumer.accept(map::put); + return name -> map.get(name).get(); + } + + Weapon create(WeaponType name); +} +``` + +Now, we can show how `WeaponFactory` can be used. + +```java +var factory = WeaponFactory.factory(builder -> { + builder.add(WeaponType.SWORD, Sword::new); + builder.add(WeaponType.AXE, Axe::new); + builder.add(WeaponType.SPEAR, Spear::new); + builder.add(WeaponType.BOW, Bow::new); +}); +var list = new ArrayList(); +list.add(factory.create(WeaponType.AXE)); +list.add(factory.create(WeaponType.SPEAR)); +list.add(factory.create(WeaponType.SWORD)); +list.add(factory.create(WeaponType.BOW)); +list.stream().forEach(weapon -> LOGGER.info("{}", weapon.toString())); +``` + +Here is the console output when the example is run. + +``` +21:15:49.709 [main] INFO com.iluwatar.factorykit.App - Axe +21:15:49.713 [main] INFO com.iluwatar.factorykit.App - Spear +21:15:49.713 [main] INFO com.iluwatar.factorykit.App - Sword +21:15:49.713 [main] INFO com.iluwatar.factorykit.App - Bow +``` + ## Class diagram + ![alt text](./etc/factory-kit.png "Factory Kit") ## Applicability + Use the Factory Kit pattern when -* a class can't anticipate the class of objects it must create -* you just want a new instance of a custom builder instead of the global one -* you explicitly want to define types of objects, that factory can build -* you want a separated builder and creator interface +* The factory class can't anticipate the types of objects it must create +* A new instance of a custom builder is needed instead of a global one +* The types of objects that the factory can build need to be defined outside the class +* The builder and creator interfaces need to be separated + +## Related patterns + +* [Builder](https://java-design-patterns.com/patterns/builder/) +* [Factory](https://java-design-patterns.com/patterns/factory/) ## Credits -* [Design Pattern Reloaded by Remi Forax: ](https://www.youtube.com/watch?v=-k2X7guaArU) +* [Design Pattern Reloaded by Remi Forax](https://www.youtube.com/watch?v=-k2X7guaArU) diff --git a/factory-kit/src/main/java/com/iluwatar/factorykit/App.java b/factory-kit/src/main/java/com/iluwatar/factorykit/App.java index e5f0b4285..7c636609a 100644 --- a/factory-kit/src/main/java/com/iluwatar/factorykit/App.java +++ b/factory-kit/src/main/java/com/iluwatar/factorykit/App.java @@ -23,14 +23,16 @@ package com.iluwatar.factorykit; +import java.util.ArrayList; + import lombok.extern.slf4j.Slf4j; /** - * Factory-kit is a creational pattern which defines a factory of immutable content with separated + * Factory kit is a creational pattern that defines a factory of immutable content with separated * builder and factory interfaces to deal with the problem of creating one of the objects specified - * directly in the factory-kit instance. + * directly in the factory kit instance. * - *

In the given example {@link WeaponFactory} represents the factory-kit, that contains four + *

In the given example {@link WeaponFactory} represents the factory kit, that contains four * {@link Builder}s for creating new objects of the classes implementing {@link Weapon} interface. * *

Each of them can be called with {@link WeaponFactory#create(WeaponType)} method, with @@ -52,7 +54,11 @@ public class App { builder.add(WeaponType.SPEAR, Spear::new); builder.add(WeaponType.BOW, Bow::new); }); - var axe = factory.create(WeaponType.AXE); - LOGGER.info(axe.toString()); + var list = new ArrayList(); + list.add(factory.create(WeaponType.AXE)); + list.add(factory.create(WeaponType.SPEAR)); + list.add(factory.create(WeaponType.SWORD)); + list.add(factory.create(WeaponType.BOW)); + list.stream().forEach(weapon -> LOGGER.info("{}", weapon.toString())); } } From 4f8007d674d724396be225dfa9659cf32d25d0ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilkka=20Sepp=C3=A4l=C3=A4?= Date: Sat, 8 Jan 2022 14:31:09 +0200 Subject: [PATCH 18/21] enhancement: Refactor and add explanation for value object (#1942) Co-authored-by: Subhrodip Mohanta --- value-object/README.md | 68 ++++++++++++++++++- .../java/com/iluwatar/value/object/App.java | 4 +- .../com/iluwatar/value/object/HeroStat.java | 28 ++------ 3 files changed, 74 insertions(+), 26 deletions(-) diff --git a/value-object/README.md b/value-object/README.md index 9b85d82b1..c98674381 100644 --- a/value-object/README.md +++ b/value-object/README.md @@ -10,19 +10,80 @@ tags: --- ## Intent + Provide objects which follow value semantics rather than reference semantics. -This means value objects' equality are not based on identity. Two value objects are +This means value objects' equality is not based on identity. Two value objects are equal when they have the same value, not necessarily being the same object. +## Explanation + +Real-world example + +> There is a class for hero statistics in a role-playing game. The statistics contain attributes +> such as strength, intelligence, and luck. The statistics of different heroes should be equal +> when all the attributes are equal. + +In plain words + +> Value objects are equal when their attributes have the same value + +Wikipedia says + +> In computer science, a value object is a small object that represents a simple entity whose +> equality is not based on identity: i.e. two value objects are equal when they have the same +> value, not necessarily being the same object. + +**Programmatic Example** + +Here is the `HeroStat` class that is the value object. Notice the use of +[Lombok's `@Value`](https://projectlombok.org/features/Value) annotation. + +```java +@Value(staticConstructor = "valueOf") +class HeroStat { + + int strength; + int intelligence; + int luck; +} +``` + +The example creates three different `HeroStat`s and compares their equality. + +```java +var statA = HeroStat.valueOf(10, 5, 0); +var statB = HeroStat.valueOf(10, 5, 0); +var statC = HeroStat.valueOf(5, 1, 8); + +LOGGER.info(statA.toString()); +LOGGER.info(statB.toString()); +LOGGER.info(statC.toString()); + +LOGGER.info("Is statA and statB equal : {}", statA.equals(statB)); +LOGGER.info("Is statA and statC equal : {}", statA.equals(statC)); +``` + +Here's the console output. + +``` +20:11:12.199 [main] INFO com.iluwatar.value.object.App - HeroStat(strength=10, intelligence=5, luck=0) +20:11:12.202 [main] INFO com.iluwatar.value.object.App - HeroStat(strength=10, intelligence=5, luck=0) +20:11:12.202 [main] INFO com.iluwatar.value.object.App - HeroStat(strength=5, intelligence=1, luck=8) +20:11:12.202 [main] INFO com.iluwatar.value.object.App - Is statA and statB equal : true +20:11:12.203 [main] INFO com.iluwatar.value.object.App - Is statA and statC equal : false +``` + ## Class diagram + ![alt text](./etc/value-object.png "Value Object") ## Applicability + Use the Value Object when -* You need to measure the objects' equality based on the objects' value +* The object's equality needs to be based on the object's value -## Real world examples +## Known uses * [java.util.Optional](https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html) * [java.time.LocalDate](https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html) @@ -31,6 +92,7 @@ Use the Value Object when ## Credits * [Patterns of Enterprise Application Architecture](http://www.martinfowler.com/books/eaa.html) +* [ValueObject](https://martinfowler.com/bliki/ValueObject.html) * [VALJOs - Value Java Objects : Stephen Colebourne's blog](http://blog.joda.org/2014/03/valjos-value-java-objects.html) * [Value Object : Wikipedia](https://en.wikipedia.org/wiki/Value_object) * [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94) diff --git a/value-object/src/main/java/com/iluwatar/value/object/App.java b/value-object/src/main/java/com/iluwatar/value/object/App.java index 49375e94c..cc952fd47 100644 --- a/value-object/src/main/java/com/iluwatar/value/object/App.java +++ b/value-object/src/main/java/com/iluwatar/value/object/App.java @@ -43,7 +43,7 @@ import lombok.extern.slf4j.Slf4j; public class App { /** - * This practice creates three HeroStats(Value object) and checks equality between those. + * This example creates three HeroStats (value objects) and checks equality between those. */ public static void main(String[] args) { var statA = HeroStat.valueOf(10, 5, 0); @@ -51,6 +51,8 @@ public class App { var statC = HeroStat.valueOf(5, 1, 8); LOGGER.info(statA.toString()); + LOGGER.info(statB.toString()); + LOGGER.info(statC.toString()); LOGGER.info("Is statA and statB equal : {}", statA.equals(statB)); LOGGER.info("Is statA and statC equal : {}", statA.equals(statC)); diff --git a/value-object/src/main/java/com/iluwatar/value/object/HeroStat.java b/value-object/src/main/java/com/iluwatar/value/object/HeroStat.java index 4d060ee0f..4620b4e4a 100644 --- a/value-object/src/main/java/com/iluwatar/value/object/HeroStat.java +++ b/value-object/src/main/java/com/iluwatar/value/object/HeroStat.java @@ -23,10 +23,7 @@ package com.iluwatar.value.object; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; +import lombok.Value; /** * HeroStat is a value object. @@ -35,23 +32,10 @@ import lombok.ToString; * http://docs.oracle.com/javase/8/docs/api/java/lang/doc-files/ValueBased.html * */ -@Getter -@ToString -@EqualsAndHashCode -@RequiredArgsConstructor -public class HeroStat { - - // Stats for a hero - - private final int strength; - private final int intelligence; - private final int luck; - - // Static factory method to create new instances. - public static HeroStat valueOf(int strength, int intelligence, int luck) { - return new HeroStat(strength, intelligence, luck); - } - - // The clone() method should not be public. Just don't override it. +@Value(staticConstructor = "valueOf") +class HeroStat { + int strength; + int intelligence; + int luck; } From c5492184b7f2331afea2a1f746d06ffd79b2ae0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilkka=20Sepp=C3=A4l=C3=A4?= Date: Sat, 8 Jan 2022 14:33:19 +0200 Subject: [PATCH 19/21] enhancement: check spelling and update topic (#1943) Co-authored-by: Subhrodip Mohanta --- trampoline/README.md | 40 ++++++++++--------- .../com/iluwatar/trampoline/Trampoline.java | 2 - .../iluwatar/trampoline/TrampolineApp.java | 5 +-- 3 files changed, 23 insertions(+), 24 deletions(-) diff --git a/trampoline/README.md b/trampoline/README.md index 6eb870227..eceaf3f1f 100644 --- a/trampoline/README.md +++ b/trampoline/README.md @@ -17,19 +17,19 @@ and to interleave the execution of functions without hard coding them together. ## Explanation Recursion is a frequently adopted technique for solving algorithmic problems in a divide and conquer -style. For example calculating fibonacci accumulating sum and factorials. In these kinds of problems -recursion is more straightforward than their loop counterpart. Furthermore recursion may need less -code and looks more concise. There is a saying that every recursion problem can be solved using -a loop with the cost of writing code that is more difficult to understand. +style. For example, calculating Fibonacci accumulating sum and factorials. In these kinds of +problems, recursion is more straightforward than its loop counterpart. Furthermore, recursion may +need less code and looks more concise. There is a saying that every recursion problem can be solved +using a loop with the cost of writing code that is more difficult to understand. -However recursion type solutions have one big caveat. For each recursive call it typically needs +However, recursion-type solutions have one big caveat. For each recursive call, it typically needs an intermediate value stored and there is a limited amount of stack memory available. Running out of stack memory creates a stack overflow error and halts the program execution. -Trampoline pattern is a trick that allows us define recursive algorithms in Java without blowing the +Trampoline pattern is a trick that allows defining recursive algorithms in Java without blowing the stack. -Real world example +Real-world example > A recursive Fibonacci calculation without the stack overflow problem using the Trampoline pattern. @@ -105,24 +105,26 @@ public interface Trampoline { Using the `Trampoline` to get Fibonacci values. ```java - public static Trampoline loop(int times, int prod) { +public static void main(String[] args) { + LOGGER.info("Start calculating war casualties"); + var result = loop(10, 1).result(); + LOGGER.info("The number of orcs perished in the war: {}", result); +} + +public static Trampoline loop(int times, int prod) { if (times == 0) { - return Trampoline.done(prod); + return Trampoline.done(prod); } else { - return Trampoline.more(() -> loop(times - 1, prod * times)); + return Trampoline.more(() -> loop(times - 1, prod * times)); } - } - - log.info("start pattern"); - var result = loop(10, 1).result(); - log.info("result {}", result); +} ``` Program output: ``` -start pattern -result 3628800 +19:22:24.462 [main] INFO com.iluwatar.trampoline.TrampolineApp - Start calculating war casualties +19:22:24.472 [main] INFO com.iluwatar.trampoline.TrampolineApp - The number of orcs perished in the war: 3628800 ``` ## Class diagram @@ -133,8 +135,8 @@ result 3628800 Use the Trampoline pattern when -* For implementing tail recursive function. This pattern allows to switch on a stackless operation. -* For interleaving the execution of two or more functions on the same thread. +* For implementing tail-recursive functions. This pattern allows to switch on a stackless operation. +* For interleaving execution of two or more functions on the same thread. ## Known uses diff --git a/trampoline/src/main/java/com/iluwatar/trampoline/Trampoline.java b/trampoline/src/main/java/com/iluwatar/trampoline/Trampoline.java index 1d2ea91d3..d60ef3602 100644 --- a/trampoline/src/main/java/com/iluwatar/trampoline/Trampoline.java +++ b/trampoline/src/main/java/com/iluwatar/trampoline/Trampoline.java @@ -107,6 +107,4 @@ public interface Trampoline { } }; } - - } diff --git a/trampoline/src/main/java/com/iluwatar/trampoline/TrampolineApp.java b/trampoline/src/main/java/com/iluwatar/trampoline/TrampolineApp.java index 32a3f1850..bfeed4d69 100644 --- a/trampoline/src/main/java/com/iluwatar/trampoline/TrampolineApp.java +++ b/trampoline/src/main/java/com/iluwatar/trampoline/TrampolineApp.java @@ -39,9 +39,9 @@ public class TrampolineApp { * Main program for showing pattern. It does loop with factorial function. */ public static void main(String[] args) { - LOGGER.info("start pattern"); + LOGGER.info("Start calculating war casualties"); var result = loop(10, 1).result(); - LOGGER.info("result {}", result); + LOGGER.info("The number of orcs perished in the war: {}", result); } @@ -55,5 +55,4 @@ public class TrampolineApp { return Trampoline.more(() -> loop(times - 1, prod * times)); } } - } From 07ee94d67163482a63308e468784990d0158856e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilkka=20Sepp=C3=A4l=C3=A4?= Date: Mon, 10 Jan 2022 13:34:19 +0200 Subject: [PATCH 20/21] refactoring: execute around idiom (#1945) * Refactor execute around the idiom * fix checkstyle errors Co-authored-by: Subhrodip Mohanta --- execute-around/README.md | 54 ++++++++++++------- .../java/com/iluwatar/execute/around/App.java | 18 +++++-- .../execute/around/SimpleFileWriter.java | 6 +++ 3 files changed, 54 insertions(+), 24 deletions(-) diff --git a/execute-around/README.md b/execute-around/README.md index 16d4803c3..06b0b2154 100644 --- a/execute-around/README.md +++ b/execute-around/README.md @@ -17,10 +17,10 @@ the user to specify only what to do with the resource. ## Explanation -Real world example +Real-world example -> We need to provide a class that can be used to write text strings to files. To make it easy for -> the user we let our service class open and close the file automatically, the user only has to +> A class needs to be provided for writing text strings to files. To make it easy for +> the user, the service class opens and closes the file automatically. The user only has to > specify what is written into which file. In plain words @@ -35,35 +35,50 @@ In plain words **Programmatic Example** -Let's introduce our file writer class. +`SimpleFileWriter` class implements the Execute Around idiom. It takes `FileWriterAction` as a +constructor argument allowing the user to specify what gets written into the file. ```java @FunctionalInterface public interface FileWriterAction { - void writeFile(FileWriter writer) throws IOException; - } +@Slf4j public class SimpleFileWriter { - - public SimpleFileWriter(String filename, FileWriterAction action) throws IOException { - try (var writer = new FileWriter(filename)) { - action.writeFile(writer); + public SimpleFileWriter(String filename, FileWriterAction action) throws IOException { + LOGGER.info("Opening the file"); + try (var writer = new FileWriter(filename)) { + LOGGER.info("Executing the action"); + action.writeFile(writer); + LOGGER.info("Closing the file"); + } } - } } ``` -To utilize the file writer the following code is needed. +The following code demonstrates how `SimpleFileWriter` is used. `Scanner` is used to print the file +contents after the writing finishes. ```java - FileWriterAction writeHello = writer -> { - writer.write("Hello"); - writer.append(" "); - writer.append("there!"); - }; - new SimpleFileWriter("testfile.txt", writeHello); +FileWriterAction writeHello = writer -> { + writer.write("Gandalf was here"); +}; +new SimpleFileWriter("testfile.txt", writeHello); + +var scanner = new Scanner(new File("testfile.txt")); +while (scanner.hasNextLine()) { +LOGGER.info(scanner.nextLine()); +} +``` + +Here's the console output. + +``` +21:18:07.185 [main] INFO com.iluwatar.execute.around.SimpleFileWriter - Opening the file +21:18:07.188 [main] INFO com.iluwatar.execute.around.SimpleFileWriter - Executing the action +21:18:07.189 [main] INFO com.iluwatar.execute.around.SimpleFileWriter - Closing the file +21:18:07.199 [main] INFO com.iluwatar.execute.around.App - Gandalf was here ``` ## Class diagram @@ -74,8 +89,7 @@ To utilize the file writer the following code is needed. Use the Execute Around idiom when -* You use an API that requires methods to be called in pairs such as open/close or -allocate/deallocate. +* An API requires methods to be called in pairs such as open/close or allocate/deallocate. ## Credits diff --git a/execute-around/src/main/java/com/iluwatar/execute/around/App.java b/execute-around/src/main/java/com/iluwatar/execute/around/App.java index f3de8d450..88eb11591 100644 --- a/execute-around/src/main/java/com/iluwatar/execute/around/App.java +++ b/execute-around/src/main/java/com/iluwatar/execute/around/App.java @@ -23,10 +23,14 @@ package com.iluwatar.execute.around; +import java.io.File; import java.io.IOException; +import java.util.Scanner; + +import lombok.extern.slf4j.Slf4j; /** - * The Execute Around idiom specifies some code to be executed before and after a method. Typically + * The Execute Around idiom specifies executable code before and after a method. Typically * the idiom is used when the API has methods to be executed in pairs, such as resource * allocation/deallocation or lock acquisition/release. * @@ -34,6 +38,7 @@ import java.io.IOException; * the user. The user specifies only what to do with the file by providing the {@link * FileWriterAction} implementation. */ +@Slf4j public class App { /** @@ -41,11 +46,16 @@ public class App { */ public static void main(String[] args) throws IOException { + // create the file writer and execute the custom action FileWriterAction writeHello = writer -> { - writer.write("Hello"); - writer.append(" "); - writer.append("there!"); + writer.write("Gandalf was here"); }; new SimpleFileWriter("testfile.txt", writeHello); + + // print the file contents + var scanner = new Scanner(new File("testfile.txt")); + while (scanner.hasNextLine()) { + LOGGER.info(scanner.nextLine()); + } } } diff --git a/execute-around/src/main/java/com/iluwatar/execute/around/SimpleFileWriter.java b/execute-around/src/main/java/com/iluwatar/execute/around/SimpleFileWriter.java index f84b7427d..0a427b81a 100644 --- a/execute-around/src/main/java/com/iluwatar/execute/around/SimpleFileWriter.java +++ b/execute-around/src/main/java/com/iluwatar/execute/around/SimpleFileWriter.java @@ -26,18 +26,24 @@ package com.iluwatar.execute.around; import java.io.FileWriter; import java.io.IOException; +import lombok.extern.slf4j.Slf4j; + /** * SimpleFileWriter handles opening and closing file for the user. The user only has to specify what * to do with the file resource through {@link FileWriterAction} parameter. */ +@Slf4j public class SimpleFileWriter { /** * Constructor. */ public SimpleFileWriter(String filename, FileWriterAction action) throws IOException { + LOGGER.info("Opening the file"); try (var writer = new FileWriter(filename)) { + LOGGER.info("Executing the action"); action.writeFile(writer); + LOGGER.info("Closing the file"); } } } From 7652b11bca5265ee311bda00adcf852a0c352530 Mon Sep 17 00:00:00 2001 From: Kevin Date: Tue, 18 Jan 2022 13:51:53 -0600 Subject: [PATCH 21/21] new pattern: Issue#1264: Implemented Composite-View Pattern (#1923) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * initial commit, created package, README, pom, and directory structure. * Issue#1264, continue working on JavaBeans, added getters, setters, and private fields. Created test file for JavaBeans. * set up junit for tests folder. * Issue#1264, set up local server and added web-application framework to composite-view to allow the JSP to run on a local Tomcat container. Wrote unit tests for Java-bean class, working on JSP pages. * Issue#1264, Added forwarding functionality to servlet and main composite view page. * Issue#1264, Finished composite view template in newsDisplay.jsp and created atomic sub-view components in businessNews.jsp, header.jsp, localNews.jsp, scienceNews.jsp, sportsNews.jsp, worldNews.jsp. Composite view page renders correctly, atomic views are inserted in and substituted in the template page depending on request parameters. * Issue#1264, Added all views, updated README.md with documentation. * Issue#1264, updated README.md, moved images folder into etc folder. * Issue#1264, removed build artifacts from tracked files. * Issue#1264, updated README.md * Issue#1264, updated README.md * Issue#1264, removed unused import, made AppServlet class final, changed to .equals() for string comparison. * Issue#1264, in AppServlet, put the output writing into try blocks to ensure writers are closed. * Issue#1264, added tests for Servlet, coverage up to 100%, used lombok to reduce boilerplate setters and getter, updated README.md with better grammar, appropriate tags and links to related patterns. Updated pom.xml to get rid of superfluous lines. * Issue#1264, made changes as requested in README.md. Co-authored-by: Ilkka Seppälä --- composite-view/README.md | 325 ++++++++++++++++++ composite-view/etc/composite-view.urm.puml | 29 ++ composite-view/etc/composite_view.png | Bin 0 -> 19092 bytes composite-view/etc/images/noparam.PNG | Bin 0 -> 72495 bytes composite-view/etc/images/threeparams.PNG | Bin 0 -> 78650 bytes composite-view/pom.xml | 80 +++++ .../iluwatar/compositeview/AppServlet.java | 64 ++++ .../compositeview/ClientPropertiesBean.java | 53 +++ .../compositeview/AppServletTest.java | 83 +++++ .../iluwatar/compositeview/JavaBeansTest.java | 71 ++++ composite-view/web/WEB-INF/web.xml | 14 + composite-view/web/businessNews.jsp | 33 ++ composite-view/web/header.jsp | 23 ++ composite-view/web/index.jsp | 20 ++ composite-view/web/localNews.jsp | 25 ++ composite-view/web/newsDisplay.jsp | 57 +++ composite-view/web/scienceNews.jsp | 34 ++ composite-view/web/sportsNews.jsp | 32 ++ composite-view/web/worldNews.jsp | 34 ++ pom.xml | 1 + 20 files changed, 978 insertions(+) create mode 100644 composite-view/README.md create mode 100644 composite-view/etc/composite-view.urm.puml create mode 100644 composite-view/etc/composite_view.png create mode 100644 composite-view/etc/images/noparam.PNG create mode 100644 composite-view/etc/images/threeparams.PNG create mode 100644 composite-view/pom.xml create mode 100644 composite-view/src/main/java/com/iluwatar/compositeview/AppServlet.java create mode 100644 composite-view/src/main/java/com/iluwatar/compositeview/ClientPropertiesBean.java create mode 100644 composite-view/src/test/java/com/iluwatar/compositeview/AppServletTest.java create mode 100644 composite-view/src/test/java/com/iluwatar/compositeview/JavaBeansTest.java create mode 100644 composite-view/web/WEB-INF/web.xml create mode 100644 composite-view/web/businessNews.jsp create mode 100644 composite-view/web/header.jsp create mode 100644 composite-view/web/index.jsp create mode 100644 composite-view/web/localNews.jsp create mode 100644 composite-view/web/newsDisplay.jsp create mode 100644 composite-view/web/scienceNews.jsp create mode 100644 composite-view/web/sportsNews.jsp create mode 100644 composite-view/web/worldNews.jsp diff --git a/composite-view/README.md b/composite-view/README.md new file mode 100644 index 000000000..e4ccced28 --- /dev/null +++ b/composite-view/README.md @@ -0,0 +1,325 @@ +--- +layout: pattern +title: Composite View +folder: composite-view +permalink: /patterns/composite-view/ +categories: Structural +language: en +tags: +- Enterprise Integration Pattern +- Presentation +--- + +## Name +**Composite View** + +## Intent +The purpose of the Composite View Pattern is to increase re-usability and flexibility when creating views for websites/webapps. +This pattern seeks to decouple the content of the page from its layout, allowing changes to be made to either the content +or layout of the page without impacting the other. This pattern also allows content to be easily reused across different views easily. + +## Explanation +Real World Example +> A news site wants to display the current date and news to different users +> based on that user's preferences. The news site will substitute in different news feed +> components depending on the user's interest, defaulting to local news. + +In Plain Words +> Composite View Pattern is having a main view being composed of smaller subviews. +> The layout of this composite view is based on a template. A View-manager then decides which +> subviews to include in this template. + +Wikipedia Says +> Composite views that are composed of multiple atomic subviews. Each component of +> the template may be included dynamically into the whole and the layout of the page may be managed independently of the content. +> This solution provides for the creation of a composite view based on the inclusion and substitution of +> modular dynamic and static template fragments. +> It promotes the reuse of atomic portions of the view by encouraging modular design. + +**Programmatic Example** + +Since this is a web development pattern, a server is required to demonstrate it. +This example uses Tomcat 10.0.13 to run the servlet, and this programmatic example will only work with Tomcat 10+. + +Firstly there is `AppServlet` which is an `HttpServlet` that runs on Tomcat 10+. +```java +public class AppServlet extends HttpServlet { + private String msgPartOne = "

This Server Doesn't Support"; + private String msgPartTwo = "Requests

\n" + + "

Use a GET request with boolean values for the following parameters

\n" + + "

'name'

\n

'bus'

\n

'sports'

\n

'sci'

\n

'world'

"; + + private String destination = "newsDisplay.jsp"; + + public AppServlet() { + + } + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + RequestDispatcher requestDispatcher = req.getRequestDispatcher(destination); + ClientPropertiesBean reqParams = new ClientPropertiesBean(req); + req.setAttribute("properties", reqParams); + requestDispatcher.forward(req, resp); + } + + @Override + public void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setContentType("text/html"); + PrintWriter out = resp.getWriter(); + out.println(msgPartOne + " Post " + msgPartTwo); + + } + + @Override + public void doDelete(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setContentType("text/html"); + PrintWriter out = resp.getWriter(); + out.println(msgPartOne + " Delete " + msgPartTwo); + + } + + @Override + public void doPut(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setContentType("text/html"); + PrintWriter out = resp.getWriter(); + out.println(msgPartOne + " Put " + msgPartTwo); + + } +} + +``` +This servlet is not part of the pattern, and simply forwards GET requests to the correct JSP. +PUT, POST, and DELETE requests are not supported and will simply show an error message. + +The view management in this example is done via a javabean class: `ClientPropertiesBean`, which stores user preferences. +```java +public class ClientPropertiesBean implements Serializable { + + private static final String WORLD_PARAM = "world"; + private static final String SCIENCE_PARAM = "sci"; + private static final String SPORTS_PARAM = "sport"; + private static final String BUSINESS_PARAM = "bus"; + private static final String NAME_PARAM = "name"; + + private static final String DEFAULT_NAME = "DEFAULT_NAME"; + private boolean worldNewsInterest; + private boolean sportsInterest; + private boolean businessInterest; + private boolean scienceNewsInterest; + private String name; + + public ClientPropertiesBean() { + worldNewsInterest = true; + sportsInterest = true; + businessInterest = true; + scienceNewsInterest = true; + name = DEFAULT_NAME; + + } + + public ClientPropertiesBean(HttpServletRequest req) { + worldNewsInterest = Boolean.parseBoolean(req.getParameter(WORLD_PARAM)); + sportsInterest = Boolean.parseBoolean(req.getParameter(SPORTS_PARAM)); + businessInterest = Boolean.parseBoolean(req.getParameter(BUSINESS_PARAM)); + scienceNewsInterest = Boolean.parseBoolean(req.getParameter(SCIENCE_PARAM)); + String tempName = req.getParameter(NAME_PARAM); + if (tempName == null || tempName == "") { + tempName = DEFAULT_NAME; + } + name = tempName; + } + // getters and setters generated by Lombok +} +``` +This javabean has a default constructor, and another that takes an `HttpServletRequest`. +This second constructor takes the request object, parses out the request parameters which contain the +user preferences for different types of news. + +The template for the news page is in `newsDisplay.jsp` +```html + + + + + + <%ClientPropertiesBean propertiesBean = (ClientPropertiesBean) request.getAttribute("properties");%> +

Welcome <%= propertiesBean.getName()%>

+ + + + + + <% if(propertiesBean.isWorldNewsInterest()) { %> + + <% } else { %> + + <% } %> + + + + <% if(propertiesBean.isBusinessInterest()) { %> + + <% } else { %> + + <% } %> + + <% if(propertiesBean.isSportsInterest()) { %> + + <% } else { %> + + <% } %> + + + + <% if(propertiesBean.isScienceNewsInterest()) { %> + + <% } else { %> + + <% } %> + + +
<%@include file="worldNews.jsp"%><%@include file="localNews.jsp"%>
<%@include file="businessNews.jsp"%><%@include file="localNews.jsp"%><%@include file="sportsNews.jsp"%><%@include file="localNews.jsp"%>
<%@include file="scienceNews.jsp"%><%@include file="localNews.jsp"%>
+ + +``` +This JSP page is the template. It declares a table with three rows, with one component in the first row, +two components in the second row, and one component in the third row. + +The scriplets in the file are part of the +view management strategy that include different atomic subviews based on the user preferences in the Javabean. + +Here are two examples of the mock atomic subviews used in the composite: +`businessNews.jsp` +```html + + + + + +

+ Generic Business News +

+ + + + + + + + + +
Stock prices up across the worldNew tech companies to invest in
Industry leaders unveil new projectPrice fluctuations and what they mean
+ + +``` +`localNews.jsp` +```html + + +
+

+ Generic Local News +

+
    +
  • + Mayoral elections coming up in 2 weeks +
  • +
  • + New parking meter rates downtown coming tomorrow +
  • +
  • + Park renovations to finish by the next year +
  • +
  • + Annual marathon sign ups available online +
  • +
+
+ + +``` +The results are as such: + +1) The user has put their name as `Tammy` in the request parameters and no preferences: +![alt text](etc/images/noparam.PNG) +2) The user has put their name as `Johnny` in the request parameters and has a preference for world, business, and science news: +![alt text](etc/images/threeparams.PNG) + +The different subviews such as `worldNews.jsp`, `businessNews.jsp`, etc. are included conditionally +based on the request parameters. + +**How To Use** + +To try this example, make sure you have Tomcat 10+ installed. +Set up your IDE to build a WAR file from the module and deploy that file to the server + +IntelliJ: + +Under `Run` and `edit configurations` Make sure Tomcat server is one of the run configurations. +Go to the deployment tab, and make sure there is one artifact being built called `composite-view:war exploded`. +If not present, add one. + +Ensure that the artifact is being built from the content of the `web` directory and the compilation results of the module. +Point the output of the artifact to a convenient place. Run the configuration and view the landing page, +follow instructions on that page to continue. + +## Class diagram + +![alt text](./etc/composite_view.png) + +The class diagram here displays the Javabean which is the view manager. +The views are JSP's held inside the web directory. + +## Applicability + +This pattern is applicable to most websites that require content to be displayed dynamically/conditionally. +If there are components that need to be re-used for multiple views, or if the project requires reusing a template, +or if it needs to include content depending on certain conditions, then this pattern is a good choice. + +## Known uses + +Most modern websites use composite views in some shape or form, as they have templates for views and small atomic components +that are included in the page dynamically. Most modern Javascript libraries, like React, support this design pattern +with components. + +## Consequences +**Pros** +* Easy to re-use components +* Change layout/content without affecting the other +* Reduce code duplication +* Code is more maintainable and modular + +**Cons** +* Overhead cost at runtime +* Slower response compared to directly embedding elements +* Increases potential for display errors + +## Related patterns +* [Composite (GoF)](https://java-design-patterns.com/patterns/composite/) +* [View Helper](https://www.oracle.com/java/technologies/viewhelper.html) + +## Credits +* [Core J2EE Patterns - Composite View](https://www.oracle.com/java/technologies/composite-view.html) +* [Composite View Design Pattern – Core J2EE Patterns](https://www.dineshonjava.com/composite-view-design-pattern/) + diff --git a/composite-view/etc/composite-view.urm.puml b/composite-view/etc/composite-view.urm.puml new file mode 100644 index 000000000..e92b13ea5 --- /dev/null +++ b/composite-view/etc/composite-view.urm.puml @@ -0,0 +1,29 @@ +@startuml +package com.iluwatar.compositeview { + class ClientPropertiesBean { + - BUSINESS_PARAM : String {static} + - DEFAULT_NAME : String {static} + - NAME_PARAM : String {static} + - SCIENCE_PARAM : String {static} + - SPORTS_PARAM : String {static} + - WORLD_PARAM : String {static} + - businessInterest : boolean + - name : String + - scienceNewsInterest : boolean + - sportsInterest : boolean + - worldNewsInterest : boolean + + ClientPropertiesBean() + + ClientPropertiesBean(req : HttpServletRequest) + + getName() : String + + isBusinessInterest() : boolean + + isScienceNewsInterest() : boolean + + isSportsInterest() : boolean + + isWorldNewsInterest() : boolean + + setBusinessInterest(businessInterest : boolean) + + setName(name : String) + + setScienceNewsInterest(scienceNewsInterest : boolean) + + setSportsInterest(sportsInterest : boolean) + + setWorldNewsInterest(worldNewsInterest : boolean) + } +} +@enduml \ No newline at end of file diff --git a/composite-view/etc/composite_view.png b/composite-view/etc/composite_view.png new file mode 100644 index 0000000000000000000000000000000000000000..66215d9416b8ae2422e3c4260a6da24f164e3765 GIT binary patch literal 19092 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

%7Lr|HB3qZ+=U@9^}6r_fI<$JNF~Pnx9O? z8I2i`8o093* z#lfjvaY#wv76ql3 ze|=ki!1oNi?8OBA(=V4J2u>9cAMB1yG+a*At;2Gz4>-s)%=tE$*feh_8*d&SsFM&R zKcLkKx6|e2G<&>ixkn{bQ}_G>im2;{TYUbTq43|o9|^3f){B!vE$HBP2Gu)xZ<{e2 z?NFduC`hg_Ik?1AaAc%RRj_2ZZkTn$**9v>9=QwsoyaT~s2t`pF#-mD3$^^k_o4D zSG0YY3m!nv10}4*IFV9=55*T&*Jwj|eMjqVMW^h$%o+jlV|D$Yh5484jLVCWN;A=E z%R@P28&)9vJkH4UpR=Mcl3clEn+3|Egz_VzS@OM8&87aERU; z?m$gFdrmG(U_AHlP z^3XRE7i+qAY>&`>0YnnPK6JNP{vo0J zhJsXr+Sf^4aZ92w#lI{i(`N}q$s;qS6h@p`bJ>&?>ZAwOJXkGOHGLJ=ULImkB1yv}=eVoaGm!Huf8(hVM2) zxwU6ivbUnHHCiP}{yYDL=HyTKf}wWylrVVT@pZ#i4R6G#iA5@5m6DJMC~OEdowO+x zeq|Uf?tac$oLNlSy6Io3T|P`{8y=jLi$%oA&bI)nt$*kL&-?nvuXc{F<(lIr-Vv7d z2fFq8>pLR-eTHh!G@%ymli03Zn%TH||C#hpm&q(y$2`#Vdif7xr&4{Y*AKZHO*Aa; z^VT|jK+TUEE-loRb8owJUB6#jQ7DCw+gxKP4FVViGPWxl#reNNE#BZW&~Wsh{3_(RY&B#~#ks7#BAFz682y-!gjpJ+`LCK=P%b!w=Z`!hj^b2>j?Q5Pf6d7bpF2oi%4}@%f{L`34 zFVoXvQ;Ud*RZBuq zT!R2456P=?0cUGb@5^VM>kn0XXO$IfW0^D7<|+%260mf`m0(O zl_)f-^GCetRl5CmNYH)D$5S~CIA|=Z-Wy%#$K z(_{WWbB)~TV)^~H^|NEvFWiL1XMzQ=4HmvBg+p=G(%z7nQO=)y)aTDeRq*@mcBq0`eusR1J4}An4BW0=DDT1L zA>bkAAp>lP>Up;bEQ-1XJP}8rOANf5TMWEW8~s2WJW^gBx6;nc*eJ@#$hdF`Q>LFM zXrnii)5-RvDWF4i3T4lpIwb|%sqO*Zz0L{THoNEe_uX@OLT??}AgLb@+=;Gn@WJ1| zdFqEJn(e6p?lWJg()=By!2K5Aqo>(G7pqF_*?<3Sip8b2>FYsz+EYIKt25{N&wusr X3WXK?%143cLos-|`njxgN@xNA1M$d) literal 0 HcmV?d00001 diff --git a/composite-view/etc/images/noparam.PNG b/composite-view/etc/images/noparam.PNG new file mode 100644 index 0000000000000000000000000000000000000000..2979cf2bc65756821c7434fa1892bed02e8225ef GIT binary patch literal 72495 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 ?QdGzbD_KUCQgDO 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 qHm{w{Pe0CEm>6EaQg+Gt;r{_?p+dU= literal 0 HcmV?d00001 diff --git a/composite-view/etc/images/threeparams.PNG b/composite-view/etc/images/threeparams.PNG new file mode 100644 index 0000000000000000000000000000000000000000..9338dedb8885a8636f7260d4ac2f8f992270b854 GIT binary patch literal 78650 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 + + + + java-design-patterns + com.iluwatar + 1.26.0-SNAPSHOT + + 4.0.0 + composite-view + + + org.junit.jupiter + junit-jupiter-engine + test + + + junit + junit + test + + + jakarta.servlet + jakarta.servlet-api + 5.0.0 + compile + + + org.mockito + mockito-core + 4.1.0 + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.compositeview.App + + + + + + + + + + \ No newline at end of file diff --git a/composite-view/src/main/java/com/iluwatar/compositeview/AppServlet.java b/composite-view/src/main/java/com/iluwatar/compositeview/AppServlet.java new file mode 100644 index 000000000..c15e44c9c --- /dev/null +++ b/composite-view/src/main/java/com/iluwatar/compositeview/AppServlet.java @@ -0,0 +1,64 @@ +package com.iluwatar.compositeview; + +import jakarta.servlet.RequestDispatcher; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; + +/** + * A servlet object that extends HttpServlet. + * Runs on Tomcat 10 and handles Http requests + */ + +public final class AppServlet extends HttpServlet { + private String msgPartOne = "

This Server Doesn't Support"; + private String msgPartTwo = "Requests

\n" + + "

Use a GET request with boolean values for the following parameters

\n" + + "

'name'

\n

'bus'

\n

'sports'

\n

'sci'

\n

'world'

"; + + private String destination = "newsDisplay.jsp"; + + public AppServlet() { + + } + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + RequestDispatcher requestDispatcher = req.getRequestDispatcher(destination); + ClientPropertiesBean reqParams = new ClientPropertiesBean(req); + req.setAttribute("properties", reqParams); + requestDispatcher.forward(req, resp); + } + + @Override + public void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setContentType("text/html"); + try (PrintWriter out = resp.getWriter()) { + out.println(msgPartOne + " Post " + msgPartTwo); + } + + } + + @Override + public void doDelete(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setContentType("text/html"); + try (PrintWriter out = resp.getWriter()) { + out.println(msgPartOne + " Delete " + msgPartTwo); + } + } + + @Override + public void doPut(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setContentType("text/html"); + try (PrintWriter out = resp.getWriter()) { + out.println(msgPartOne + " Put " + msgPartTwo); + } + } +} diff --git a/composite-view/src/main/java/com/iluwatar/compositeview/ClientPropertiesBean.java b/composite-view/src/main/java/com/iluwatar/compositeview/ClientPropertiesBean.java new file mode 100644 index 000000000..c8e694713 --- /dev/null +++ b/composite-view/src/main/java/com/iluwatar/compositeview/ClientPropertiesBean.java @@ -0,0 +1,53 @@ +package com.iluwatar.compositeview; + +import jakarta.servlet.http.HttpServletRequest; +import java.io.Serializable; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + + +/** + * A Java beans class that parses a http request and stores parameters. + * Java beans used in JSP's to dynamically include elements in view. + * DEFAULT_NAME = a constant, default name to be used for the default constructor + * worldNewsInterest = whether current request has world news interest + * sportsInterest = whether current request has a sportsInterest + * businessInterest = whether current request has a businessInterest + * scienceNewsInterest = whether current request has a scienceNewsInterest + */ +@Getter +@Setter +@NoArgsConstructor +public class ClientPropertiesBean implements Serializable { + + private static final String WORLD_PARAM = "world"; + private static final String SCIENCE_PARAM = "sci"; + private static final String SPORTS_PARAM = "sport"; + private static final String BUSINESS_PARAM = "bus"; + private static final String NAME_PARAM = "name"; + + private static final String DEFAULT_NAME = "DEFAULT_NAME"; + private boolean worldNewsInterest = true; + private boolean sportsInterest = true; + private boolean businessInterest = true; + private boolean scienceNewsInterest = true; + private String name = DEFAULT_NAME; + + /** + * Constructor that parses an HttpServletRequest and stores all the request parameters. + * + * @param req the HttpServletRequest object that is passed in + */ + public ClientPropertiesBean(HttpServletRequest req) { + worldNewsInterest = Boolean.parseBoolean(req.getParameter(WORLD_PARAM)); + sportsInterest = Boolean.parseBoolean(req.getParameter(SPORTS_PARAM)); + businessInterest = Boolean.parseBoolean(req.getParameter(BUSINESS_PARAM)); + scienceNewsInterest = Boolean.parseBoolean(req.getParameter(SCIENCE_PARAM)); + String tempName = req.getParameter(NAME_PARAM); + if (tempName == null || tempName.equals("")) { + tempName = DEFAULT_NAME; + } + name = tempName; + } +} diff --git a/composite-view/src/test/java/com/iluwatar/compositeview/AppServletTest.java b/composite-view/src/test/java/com/iluwatar/compositeview/AppServletTest.java new file mode 100644 index 000000000..38c1a1bc2 --- /dev/null +++ b/composite-view/src/test/java/com/iluwatar/compositeview/AppServletTest.java @@ -0,0 +1,83 @@ +package com.iluwatar.compositeview; + +import jakarta.servlet.RequestDispatcher; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import static org.junit.Assert.*; + +/* Written with reference from https://stackoverflow.com/questions/5434419/how-to-test-my-servlet-using-junit +and https://stackoverflow.com/questions/50211433/servlets-unit-testing + */ + +public class AppServletTest extends Mockito{ + private String msgPartOne = "

This Server Doesn't Support"; + private String msgPartTwo = "Requests

\n" + + "

Use a GET request with boolean values for the following parameters

\n" + + "

'name'

\n

'bus'

\n

'sports'

\n

'sci'

\n

'world'

"; + private String destination = "newsDisplay.jsp"; + + @Test + public void testDoGet() throws Exception { + HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); + HttpServletResponse mockResp = Mockito.mock(HttpServletResponse.class); + RequestDispatcher mockDispatcher = Mockito.mock(RequestDispatcher.class); + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + when(mockResp.getWriter()).thenReturn(printWriter); + when(mockReq.getRequestDispatcher(destination)).thenReturn(mockDispatcher); + AppServlet curServlet = new AppServlet(); + curServlet.doGet(mockReq, mockResp); + verify(mockReq, times(1)).getRequestDispatcher(destination); + verify(mockDispatcher).forward(mockReq, mockResp); + + + } + + @Test + public void testDoPost() throws Exception { + HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); + HttpServletResponse mockResp = Mockito.mock(HttpServletResponse.class); + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + when(mockResp.getWriter()).thenReturn(printWriter); + + AppServlet curServlet = new AppServlet(); + curServlet.doPost(mockReq, mockResp); + printWriter.flush(); + assertTrue(stringWriter.toString().contains(msgPartOne + " Post " + msgPartTwo)); + } + + @Test + public void testDoPut() throws Exception { + HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); + HttpServletResponse mockResp = Mockito.mock(HttpServletResponse.class); + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + when(mockResp.getWriter()).thenReturn(printWriter); + + AppServlet curServlet = new AppServlet(); + curServlet.doPut(mockReq, mockResp); + printWriter.flush(); + assertTrue(stringWriter.toString().contains(msgPartOne + " Put " + msgPartTwo)); + } + + @Test + public void testDoDelete() throws Exception { + HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); + HttpServletResponse mockResp = Mockito.mock(HttpServletResponse.class); + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + when(mockResp.getWriter()).thenReturn(printWriter); + + AppServlet curServlet = new AppServlet(); + curServlet.doDelete(mockReq, mockResp); + printWriter.flush(); + assertTrue(stringWriter.toString().contains(msgPartOne + " Delete " + msgPartTwo)); + } +} diff --git a/composite-view/src/test/java/com/iluwatar/compositeview/JavaBeansTest.java b/composite-view/src/test/java/com/iluwatar/compositeview/JavaBeansTest.java new file mode 100644 index 000000000..6583ab45d --- /dev/null +++ b/composite-view/src/test/java/com/iluwatar/compositeview/JavaBeansTest.java @@ -0,0 +1,71 @@ +package com.iluwatar.compositeview; + +import jakarta.servlet.http.HttpServletRequest; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static org.junit.Assert.*; + +public class JavaBeansTest { + @Test + public void testDefaultConstructor() { + ClientPropertiesBean newBean = new ClientPropertiesBean(); + assertEquals("DEFAULT_NAME", newBean.getName()); + assertTrue(newBean.isBusinessInterest()); + assertTrue(newBean.isScienceNewsInterest()); + assertTrue(newBean.isSportsInterest()); + assertTrue(newBean.isWorldNewsInterest()); + + } + + @Test + public void testNameGetterSetter() { + ClientPropertiesBean newBean = new ClientPropertiesBean(); + assertEquals("DEFAULT_NAME", newBean.getName()); + newBean.setName("TEST_NAME_ONE"); + assertEquals("TEST_NAME_ONE", newBean.getName()); + } + + @Test + public void testBusinessSetterGetter() { + ClientPropertiesBean newBean = new ClientPropertiesBean(); + assertTrue(newBean.isBusinessInterest()); + newBean.setBusinessInterest(false); + assertFalse(newBean.isBusinessInterest()); + } + + @Test + public void testScienceSetterGetter() { + ClientPropertiesBean newBean = new ClientPropertiesBean(); + assertTrue(newBean.isScienceNewsInterest()); + newBean.setScienceNewsInterest(false); + assertFalse(newBean.isScienceNewsInterest()); + } + + @Test + public void testSportsSetterGetter() { + ClientPropertiesBean newBean = new ClientPropertiesBean(); + assertTrue(newBean.isSportsInterest()); + newBean.setSportsInterest(false); + assertFalse(newBean.isSportsInterest()); + } + + @Test + public void testWorldSetterGetter() { + ClientPropertiesBean newBean = new ClientPropertiesBean(); + assertTrue(newBean.isWorldNewsInterest()); + newBean.setWorldNewsInterest(false); + assertFalse(newBean.isWorldNewsInterest()); + } + + @Test + public void testRequestConstructor(){ + HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); + ClientPropertiesBean newBean = new ClientPropertiesBean((mockReq)); + assertEquals("DEFAULT_NAME", newBean.getName()); + assertFalse(newBean.isWorldNewsInterest()); + assertFalse(newBean.isBusinessInterest()); + assertFalse(newBean.isScienceNewsInterest()); + assertFalse(newBean.isSportsInterest()); + } +} diff --git a/composite-view/web/WEB-INF/web.xml b/composite-view/web/WEB-INF/web.xml new file mode 100644 index 000000000..7cdd74c7b --- /dev/null +++ b/composite-view/web/WEB-INF/web.xml @@ -0,0 +1,14 @@ + + + + appServlet + com.iluwatar.compositeview.AppServlet + + + appServlet + /news + + \ No newline at end of file diff --git a/composite-view/web/businessNews.jsp b/composite-view/web/businessNews.jsp new file mode 100644 index 000000000..f9c67bc3c --- /dev/null +++ b/composite-view/web/businessNews.jsp @@ -0,0 +1,33 @@ +<%-- + Created by IntelliJ IDEA. + User: Kevin + Date: 11/29/2021 + Time: 2:51 PM + To change this template use File | Settings | File Templates. +--%> +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + + + +

+ Generic Business News +

+ + + + + + + + + +
Stock prices up across the worldNew tech companies to invest in
Industry leaders unveil new projectPrice fluctuations and what they mean
+ + diff --git a/composite-view/web/header.jsp b/composite-view/web/header.jsp new file mode 100644 index 000000000..07b24f878 --- /dev/null +++ b/composite-view/web/header.jsp @@ -0,0 +1,23 @@ +<%-- + Created by IntelliJ IDEA. + User: Kevin + Date: 11/29/2021 + Time: 1:28 PM + To change this template use File | Settings | File Templates. +--%> +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ page import="java.util.Date"%> + + + + + + <% String todayDateStr = (new Date().toString()); %> +

Today's Personalized Frontpage

+

<%=todayDateStr%>

+ + diff --git a/composite-view/web/index.jsp b/composite-view/web/index.jsp new file mode 100644 index 000000000..527db3b33 --- /dev/null +++ b/composite-view/web/index.jsp @@ -0,0 +1,20 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + + + +

Welcome To The Composite Patterns Mock News Site

+

Send a GET request to the "/news" path to see the composite view with mock news

+

Use the following parameters:

+

name: string name to be dynamically displayed

+

bus: boolean for whether you want to see the mock business news

+

world: boolean for whether you want to see the mock world news

+

sci: boolean for whether you want to see the mock world news

+

sport: boolean for whether you want to see the mock world news

+ + diff --git a/composite-view/web/localNews.jsp b/composite-view/web/localNews.jsp new file mode 100644 index 000000000..3ab3ea1e9 --- /dev/null +++ b/composite-view/web/localNews.jsp @@ -0,0 +1,25 @@ + +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + +
+

+ Generic Local News +

+
    +
  • + Mayoral elections coming up in 2 weeks +
  • +
  • + New parking meter rates downtown coming tomorrow +
  • +
  • + Park renovations to finish by the next year +
  • +
  • + Annual marathon sign ups available online +
  • +
+
+ + diff --git a/composite-view/web/newsDisplay.jsp b/composite-view/web/newsDisplay.jsp new file mode 100644 index 000000000..936a98e1d --- /dev/null +++ b/composite-view/web/newsDisplay.jsp @@ -0,0 +1,57 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ page import="com.iluwatar.compositeview.ClientPropertiesBean"%> + + + + + + <%ClientPropertiesBean propertiesBean = (ClientPropertiesBean) request.getAttribute("properties");%> +

Welcome <%= propertiesBean.getName()%>

+ + + + + + <% if(propertiesBean.isWorldNewsInterest()) { %> + + <% } else { %> + + <% } %> + + + + <% if(propertiesBean.isBusinessInterest()) { %> + + <% } else { %> + + <% } %> + + <% if(propertiesBean.isSportsInterest()) { %> + + <% } else { %> + + <% } %> + + + + <% if(propertiesBean.isScienceNewsInterest()) { %> + + <% } else { %> + + <% } %> + + +
<%@include file="worldNews.jsp"%><%@include file="localNews.jsp"%>
<%@include file="businessNews.jsp"%><%@include file="localNews.jsp"%><%@include file="sportsNews.jsp"%><%@include file="localNews.jsp"%>
<%@include file="scienceNews.jsp"%><%@include file="localNews.jsp"%>
+ + diff --git a/composite-view/web/scienceNews.jsp b/composite-view/web/scienceNews.jsp new file mode 100644 index 000000000..2c81d8ff5 --- /dev/null +++ b/composite-view/web/scienceNews.jsp @@ -0,0 +1,34 @@ +<%-- + Created by IntelliJ IDEA. + User: Kevin + Date: 11/29/2021 + Time: 4:18 PM + To change this template use File | Settings | File Templates. +--%> +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + +
+

+ Generic Science News +

+
    +
  • + New model of gravity proposed for dark matter +
  • +
  • + Genetic modifying technique proved on bacteria +
  • +
  • + Neurology study maps brain with new precision +
  • +
  • + Survey of rainforest discovers 15 new species +
  • +
  • + New signalling pathway for immune system discovered +
  • +
+
+ + diff --git a/composite-view/web/sportsNews.jsp b/composite-view/web/sportsNews.jsp new file mode 100644 index 000000000..3d3aa12e8 --- /dev/null +++ b/composite-view/web/sportsNews.jsp @@ -0,0 +1,32 @@ +<%-- + Created by IntelliJ IDEA. + User: Kevin + Date: 11/29/2021 + Time: 3:53 PM + To change this template use File | Settings | File Templates. +--%> +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + + + +

+ Generic Sports News +

+
+ International football match delayed due to weather, will be held next week +
+
+ New rising stars in winter sports, ten new athletes that will shake up the scene +
+
+ Biggest upset in basketball history, upstart team sweeps competition +
+ + diff --git a/composite-view/web/worldNews.jsp b/composite-view/web/worldNews.jsp new file mode 100644 index 000000000..a75060d76 --- /dev/null +++ b/composite-view/web/worldNews.jsp @@ -0,0 +1,34 @@ +<%-- + Created by IntelliJ IDEA. + User: Kevin + Date: 11/29/2021 + Time: 2:51 PM + To change this template use File | Settings | File Templates. +--%> +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + + + +

+ Generic World News +

+ + + + + + + + + + +
New trade talks happening at UN on Thursday
European Union to announce new resolution next week
UN delivers report on world economic status
+ + diff --git a/pom.xml b/pom.xml index e1573a737..e58146c72 100644 --- a/pom.xml +++ b/pom.xml @@ -228,6 +228,7 @@ lockable-object fanout-fanin domain-model + composite-view metadata-mapping