Compare commits

..

2 Commits

227 changed files with 504 additions and 3841 deletions

View File

@ -1721,70 +1721,6 @@
"contributions": [
"code"
]
},
{
"login": "vashisthabhinav",
"name": "Abhinav Vashisth",
"avatar_url": "https://avatars.githubusercontent.com/u/89785800?v=4",
"profile": "https://www.linkedin.com/in/abhinav-vashisth-06613b208/",
"contributions": [
"doc"
]
},
{
"login": "Kevinyl3",
"name": "Kevin",
"avatar_url": "https://avatars.githubusercontent.com/u/47126749?v=4",
"profile": "http://no website",
"contributions": [
"review"
]
},
{
"login": "Shrirang97",
"name": "Shrirang",
"avatar_url": "https://avatars.githubusercontent.com/u/28738668?v=4",
"profile": "https://github.com/Shrirang97",
"contributions": [
"review",
"code"
]
},
{
"login": "interactwithankush",
"name": "interactwithankush",
"avatar_url": "https://avatars.githubusercontent.com/u/18613127?v=4",
"profile": "https://github.com/interactwithankush",
"contributions": [
"code"
]
},
{
"login": "yuhangbin",
"name": "CharlieYu",
"avatar_url": "https://avatars.githubusercontent.com/u/17566866?v=4",
"profile": "https://github.com/yuhangbin",
"contributions": [
"code"
]
},
{
"login": "Leisterbecker",
"name": "Leisterbecker",
"avatar_url": "https://avatars.githubusercontent.com/u/20650323?v=4",
"profile": "https://github.com/Leisterbecker",
"contributions": [
"code"
]
},
{
"login": "castleKing1997",
"name": "DragonDreamer",
"avatar_url": "https://avatars.githubusercontent.com/u/35420129?v=4",
"profile": "http://rosaecrucis.cn",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,

View File

@ -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-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-196-orange.svg?style=flat-square)](#contributors-)
[![All Contributors](https://img.shields.io/badge/all_contributors-189-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
<br/>
@ -21,7 +21,7 @@ Read in different language : [**zh**](localization/zh/README.md), [**ko**](local
# Introduction
Design patterns are the best, formalized practices a programmer can use to
Design patterns are the best formalized practices a programmer can use to
solve common problems when designing an application or system.
Design patterns can speed up the development process by providing tested, proven
@ -34,11 +34,11 @@ are familiar with the patterns.
# Getting started
This site showcases Java Design Patterns. The solutions have been developed by
experienced programmers and architects from the open-source community. The
patterns can be browsed by their high-level descriptions or by looking at their
source code. The source code examples are well commented and can be thought of as
experienced programmers and architects from the open source community. The
patterns can be browsed by their high level descriptions or by looking at their
source code. The source code examples are well commented and can be thought as
programming tutorials on how to implement a specific pattern. We use the most
popular battle-proven open-source Java technologies.
popular battle-proven open source Java technologies.
Before you dive into the material, you should be familiar with various
[Software Design Principles](https://java-design-patterns.com/principles/).
@ -56,8 +56,8 @@ of the following approaches
- Using tags such as `Performance`, `Gang of Four` or `Data access`.
- Using pattern categories, `Creational`, `Behavioral`, and others.
Hopefully, you find the object-oriented solutions presented on this site useful
in your architectures and have as much fun learning them as we had while developing them.
Hopefully you find the object oriented solutions presented on this site useful
in your architectures and have as much fun learning them as we had developing them.
# How to contribute
@ -318,15 +318,6 @@ This project is licensed under the terms of the MIT license.
<td align="center"><a href="https://github.com/Fiordy"><img src="https://avatars.githubusercontent.com/u/53420573?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Fiordy</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=Fiordy" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/harshalkh"><img src="https://avatars.githubusercontent.com/u/37841724?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Harshal</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=harshalkh" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://www.linkedin.com/in/abhinav-vashisth-06613b208/"><img src="https://avatars.githubusercontent.com/u/89785800?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Abhinav Vashisth</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=vashisthabhinav" title="Documentation">📖</a></td>
<td align="center"><a href="http://no website"><img src="https://avatars.githubusercontent.com/u/47126749?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kevin</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3AKevinyl3" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://github.com/Shrirang97"><img src="https://avatars.githubusercontent.com/u/28738668?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Shrirang</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3AShrirang97" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/iluwatar/java-design-patterns/commits?author=Shrirang97" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/interactwithankush"><img src="https://avatars.githubusercontent.com/u/18613127?v=4?s=100" width="100px;" alt=""/><br /><sub><b>interactwithankush</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=interactwithankush" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/yuhangbin"><img src="https://avatars.githubusercontent.com/u/17566866?v=4?s=100" width="100px;" alt=""/><br /><sub><b>CharlieYu</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=yuhangbin" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/Leisterbecker"><img src="https://avatars.githubusercontent.com/u/20650323?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Leisterbecker</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=Leisterbecker" title="Code">💻</a></td>
<td align="center"><a href="http://rosaecrucis.cn"><img src="https://avatars.githubusercontent.com/u/35420129?v=4?s=100" width="100px;" alt=""/><br /><sub><b>DragonDreamer</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=castleKing1997" title="Code">💻</a></td>
</tr>
</table>
<!-- markdownlint-restore -->

View File

@ -28,7 +28,7 @@
<parent>
<artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>abstract-document</artifactId>
<dependencies>

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>abstract-factory</artifactId>
<dependencies>

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>active-object</artifactId>
<dependencies>

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>acyclic-visitor</artifactId>
<properties>

View File

@ -18,10 +18,10 @@ couldn't otherwise because of incompatible interfaces.
## Explanation
Real-world example
Real world example
> Consider that you have some pictures on your memory card and you need to transfer them to your computer. To transfer them, you need some kind of adapter that is compatible with your computer ports so that you can attach a memory card to your computer. In this case card reader is an adapter.
> 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.
> 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.
> 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 a fishing boat available. We need to create an adapter that allows the captain to operate the fishing boat with his rowing boat skills.
Now let's say the pirates are coming and our captain needs to escape but there is only fishing boat available. We need to create an adapter that allows the captain to operate the fishing boat with his rowing boat skills.
```java
@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 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.
* 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.
## 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 wont work when we want to adapt a class and all its subclasses.
* Lets Adapter override some of Adaptees 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 wont work when we want to adapt a class and all its subclasses.
* lets Adapter override some of Adaptees 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
* 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.
* 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 Adapter refer to the subclass rather than the Adaptee itself.
## Real-world examples
## Known uses
* [java.util.Arrays#asList()](http://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#asList%28T...%29)
* [java.util.Collections#list()](https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#list-java.util.Enumeration-)

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>adapter</artifactId>
<dependencies>

View File

@ -33,7 +33,7 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
/**
* Tests for the adapter pattern.
* Test class
*/
class AdapterPatternTest {

View File

@ -33,7 +33,9 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
class AppTest {
/**
* Check whether the execution of the main method in {@link App}
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
* throws an exception.
*/

View File

@ -27,7 +27,7 @@
<parent>
<artifactId>aggregator-microservices</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>aggregator-service</artifactId>

View File

@ -27,7 +27,7 @@
<parent>
<artifactId>aggregator-microservices</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>information-microservice</artifactId>

View File

@ -27,7 +27,7 @@
<parent>
<artifactId>aggregator-microservices</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>inventory-microservice</artifactId>

View File

@ -27,7 +27,7 @@
<parent>
<artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>aggregator-microservices</artifactId>

View File

@ -27,7 +27,7 @@
<parent>
<artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ambassador</artifactId>

View File

@ -27,7 +27,7 @@
<parent>
<artifactId>api-gateway</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>api-gateway-service</artifactId>

View File

@ -27,7 +27,7 @@
<parent>
<artifactId>api-gateway</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>image-microservice</artifactId>

View File

@ -27,7 +27,7 @@
<parent>
<artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>api-gateway</artifactId>

View File

@ -27,7 +27,7 @@
<parent>
<artifactId>api-gateway</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>price-microservice</artifactId>

View File

@ -27,7 +27,7 @@
<parent>
<artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>arrange-act-assert</artifactId>

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>async-method-invocation</artifactId>
<dependencies>

View File

@ -27,7 +27,7 @@
<parent>
<artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>balking</artifactId>

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>bridge</artifactId>
<dependencies>

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>builder</artifactId>
<dependencies>

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>business-delegate</artifactId>
<dependencies>

View File

@ -27,7 +27,7 @@
<parent>
<artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>bytecode</artifactId>

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>caching</artifactId>
<dependencies>

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>callback</artifactId>
<dependencies>

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>chain-of-responsibility</artifactId>
<dependencies>

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>circuit-breaker</artifactId>
<dependencies>

View File

@ -1,38 +0,0 @@
# 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/

View File

@ -1,83 +0,0 @@
---
layout: pattern
title: Claim Check Pattern
folder: cloud-claim-check-pattern
permalink: /patterns/cloud-claim-check-pattern/
categories: Cloud
language: en
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)

View File

@ -1,38 +0,0 @@
# 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/

View File

@ -1,117 +0,0 @@
@startuml
package com.iluwatar.claimcheckpattern.producer.calldetails.functions {
class UsageDetailPublisherFunction {
- eventHandlerUtility : EventHandlerUtility<MessageHeader>
- messageHandlerUtility : MessageHandlerUtility<UsageDetail>
+ UsageDetailPublisherFunction()
+ UsageDetailPublisherFunction(messageHandlerUtility : MessageHandlerUtility<UsageDetail>, eventHandlerUtility : EventHandlerUtility<MessageHeader>)
+ run(request : HttpRequestMessage<Optional<String>>, context : ExecutionContext) : HttpResponseMessage
}
}
package com.iluwatar.claimcheckpattern.domain {
class Message<T> {
- messageBody : MessageBody<T>
- messageHeader : MessageHeader
+ Message<T>()
+ getMessageBody() : MessageBody<T>
+ getMessageHeader() : MessageHeader
+ setMessageBody(messageBody : MessageBody<T>)
+ setMessageHeader(messageHeader : MessageHeader)
}
class MessageBody<T> {
- data : List<T>
+ MessageBody<T>()
+ getData() : List<T>
+ setData(data : List<T>)
}
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<T> {
- customEventClient : EventGridPublisherClient<BinaryData>
+ EventHandlerUtility<T>()
+ EventHandlerUtility<T>(customEventClient : EventGridPublisherClient<BinaryData>)
+ publishEvent(customEvent : T, logger : Logger)
}
class MessageHandlerUtility<T> {
- blobServiceClient : BlobServiceClient
+ MessageHandlerUtility<T>()
+ MessageHandlerUtility<T>(blobServiceClient : BlobServiceClient)
+ dropToPersistantStorage(message : Message<T>, logger : Logger)
+ readFromPersistantStorage(messageReference : MessageReference, logger : Logger) : Message<T>
}
}
package com.iluwatar.claimcheckpattern.consumer.callcostprocessor.functions {
class UsageCostProcessorFunction {
- messageHandlerUtilityForUsageCostDetail : MessageHandlerUtility<UsageCostDetail>
- messageHandlerUtilityForUsageDetail : MessageHandlerUtility<UsageDetail>
+ UsageCostProcessorFunction()
+ UsageCostProcessorFunction(messageHandlerUtilityForUsageDetail : MessageHandlerUtility<UsageDetail>, messageHandlerUtilityForUsageCostDetail : MessageHandlerUtility<UsageCostDetail>)
- calculateUsageCostDetails(usageDetailsList : List<UsageDetail>) : List<UsageCostDetail>
+ run(request : HttpRequestMessage<Optional<String>>, context : ExecutionContext) : HttpResponseMessage
}
}
UsageCostProcessorFunction --> "-messageHandlerUtilityForUsageDetail" MessageHandlerUtility
Message --> "-messageBody" MessageBody
UsageDetailPublisherFunction --> "-eventHandlerUtility" EventHandlerUtility
Builder ..+ HttpResponseMessage
UsageDetailPublisherFunction --> "-messageHandlerUtility" MessageHandlerUtility
Message --> "-messageHeader" MessageHeader
@enduml

View File

@ -1,7 +0,0 @@
{
"version": "2.0",
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[1.*, 2.0.0)"
}
}

View File

@ -1,150 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
The MIT License
Copyright © 2014-2021 Ilkka Seppälä
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>claim-check-pattern</artifactId>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>call-usage-app</artifactId>
<name>call-usage-app</name>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<azure.functions.maven.plugin.version>1.14.0</azure.functions.maven.plugin.version>
<azure.functions.java.library.version>1.4.2</azure.functions.java.library.version>
<functionAppName>CallUsageApp</functionAppName>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-sdk-bom</artifactId>
<version>1.0.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.microsoft.azure.functions</groupId>
<artifactId>azure-functions-java-library</artifactId>
<version>${azure.functions.java.library.version}</version>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-messaging-eventgrid</artifactId>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-storage-blob</artifactId>
<version>12.13.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<scope>test</scope>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-functions-maven-plugin</artifactId>
<version>${azure.functions.maven.plugin.version}</version>
<configuration>
<!-- function app name -->
<appName>${functionAppName}</appName>
<!-- function app resource group -->
<resourceGroup>java-functions-group</resourceGroup>
<!-- function app service plan name -->
<appServicePlanName>java-functions-app-service-plan</appServicePlanName>
<!-- function app region-->
<!-- refers https://github.com/microsoft/azure-maven-plugins/wiki/Azure-Functions:-Configuration-Details#supported-regions for all valid values -->
<region>westus</region>
<!-- function pricingTier, default to be consumption if not specified -->
<!-- refers https://github.com/microsoft/azure-maven-plugins/wiki/Azure-Functions:-Configuration-Details#supported-pricing-tiers for all valid values -->
<!-- <pricingTier></pricingTier> -->
<!-- Whether to disable application insights, default is false -->
<!-- refers https://github.com/microsoft/azure-maven-plugins/wiki/Azure-Functions:-Configuration-Details for all valid configurations for application insights-->
<!-- <disableAppInsights></disableAppInsights> -->
<runtime>
<!-- runtime os, could be windows, linux or docker-->
<os>windows</os>
<javaVersion>11</javaVersion>
</runtime>
<appSettings>
<property>
<name>FUNCTIONS_EXTENSION_VERSION</name>
<value>~3</value>
</property>
</appSettings>
</configuration>
<executions>
<execution>
<id>package-functions</id>
<goals>
<goal>package</goal>
</goals>
</execution>
</executions>
</plugin>
<!--Remove obj folder generated by .NET SDK in maven clean-->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<filesets>
<fileset>
<directory>obj</directory>
</fileset>
</filesets>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,168 +0,0 @@
/*
* 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<UsageDetail> messageHandlerUtilityForUsageDetail;
private MessageHandlerUtility<UsageCostDetail> messageHandlerUtilityForUsageCostDetail;
public UsageCostProcessorFunction() {
this.messageHandlerUtilityForUsageDetail = new MessageHandlerUtility<>();
this.messageHandlerUtilityForUsageCostDetail = new MessageHandlerUtility<>();
}
public UsageCostProcessorFunction(
MessageHandlerUtility<UsageDetail> messageHandlerUtilityForUsageDetail,
MessageHandlerUtility<UsageCostDetail> 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<Optional<String>> 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<UsageDetail> usageDetailsList = BinaryData.fromObject(
message.getMessageBody().getData())
.toObject(new TypeReference<>() {
});
var usageCostDetailsList = calculateUsageCostDetails(usageDetailsList);
// Create message body
var newMessageBody = new MessageBody<UsageCostDetail>();
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<UsageCostDetail>();
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<UsageCostDetail> calculateUsageCostDetails(List<UsageDetail> usageDetailsList) {
if (usageDetailsList == null) {
return null;
}
var usageCostDetailsList = new ArrayList<UsageCostDetail>();
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;
}
}

View File

@ -1,43 +0,0 @@
/*
* 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 <T> represents UsageDetail or UsageCostDetail
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Message<T> {
private MessageHeader messageHeader;
private MessageBody<T> messageBody;
}

View File

@ -1,43 +0,0 @@
/*
* 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 <T> represents UsageDetail or UsageCostDetail
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MessageBody<T> {
private List<T> data;
}

View File

@ -1,47 +0,0 @@
/*
* 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;
}

View File

@ -1,45 +0,0 @@
/*
* 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;
}

View File

@ -1,43 +0,0 @@
/*
* 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;
}

View File

@ -1,44 +0,0 @@
/*
* 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;
}

View File

@ -1,146 +0,0 @@
/*
* 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<UsageDetail> messageHandlerUtility;
private EventHandlerUtility<MessageHeader> eventHandlerUtility;
public UsageDetailPublisherFunction() {
this.messageHandlerUtility = new MessageHandlerUtility<>();
this.eventHandlerUtility = new EventHandlerUtility<>();
}
public UsageDetailPublisherFunction(MessageHandlerUtility<UsageDetail> messageHandlerUtility,
EventHandlerUtility<MessageHeader> 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<Optional<String>> 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<UsageDetail>();
var usageDetailsList = new ArrayList<UsageDetail>();
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<UsageDetail>();
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();
}
}

View File

@ -1,66 +0,0 @@
/*
* 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 <T> represents UsageDetail or UsageCostDetail
*/
public class EventHandlerUtility<T> {
private EventGridPublisherClient<BinaryData> 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<BinaryData> 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());
}
}
}

View File

@ -1,127 +0,0 @@
/*
* 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 <T> represents UsageDetail or UsageCostDetail
*/
public class MessageHandlerUtility<T> {
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<T> readFromPersistantStorage(MessageReference messageReference, Logger logger) {
Message<T> 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<Message<T>>() {
});
} 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<T> 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());
}
}
}

View File

@ -1,105 +0,0 @@
/*
* 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<String, String> headers;
public HttpResponseMessageMock(HttpStatusType status, Map<String, String> 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<String, String> 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);
}
}
}

View File

@ -1,181 +0,0 @@
/*
* 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<UsageDetail> mockMessageHandlerUtilityForUsageADetail;
@Mock
MessageHandlerUtility<UsageCostDetail> mockMessageHandlerUtilityForUsageCostDetail;
@Mock
ExecutionContext context;
Message<UsageCostDetail> messageToDrop;
Message<UsageDetail> messageToRead;
MessageReference messageReference;
@InjectMocks
UsageCostProcessorFunction usageCostProcessorFunction;
@BeforeEach
public void setUp() {
var messageBodyUsageDetail = new MessageBody<UsageDetail>();
var usageDetailsList = new ArrayList<UsageDetail>();
var messageBodyUsageCostDetail = new MessageBody<UsageCostDetail>();
var usageCostDetailsList = new ArrayList<UsageCostDetail>();
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<Optional<String>> 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<HttpResponseMessage.Builder>() {
@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<Optional<String>> 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<HttpResponseMessage.Builder>() {
@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());
}
}

View File

@ -1,119 +0,0 @@
/*
* 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<UsageDetail> mockMessageHandlerUtility;
@Mock
EventHandlerUtility<MessageHeader> mockEventHandlerUtility;
@InjectMocks
UsageDetailPublisherFunction usageDetailPublisherFunction;
/**
* Unit test for HttpTriggerJava method.
*/
@Test
public void shouldTriggerHttpAzureFunctionJavaWithSubscriptionValidationEventType() throws Exception {
// Setup
@SuppressWarnings("unchecked")
final HttpRequestMessage<Optional<String>> 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<HttpResponseMessage.Builder>() {
@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<Optional<String>> 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<HttpResponseMessage.Builder>() {
@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);
}
}

View File

@ -1,70 +0,0 @@
/*
* 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<BinaryData> mockCustomEventClient;
@InjectMocks
EventHandlerUtility<Message<UsageDetail>> 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));
}
}

View File

@ -1,114 +0,0 @@
/*
* 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<UsageDetail> messageHandlerUtility;
private Message<UsageDetail> messageToPublish;
private MessageReference messageReference;
@BeforeEach
public void setUp() {
System.setProperty("BlobStorageConnectionString", "https://www.dummyEndpoint.com/api/blobs");
var messageBody = new MessageBody<UsageDetail>();
var usageDetailsList = new ArrayList<UsageDetail>();
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());
}
}

View File

@ -1,15 +0,0 @@
[
{
"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"
}
]

View File

@ -1,15 +0,0 @@
[
{
"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"
}
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

View File

@ -1,117 +0,0 @@
@startuml
class UsageDetailPublisherFunction [[java:com.iluwatar.producer.calldetails.functions]] {
-messageHandlerUtility: MessageHandlerUtility<UsageDetail>
-eventHandlerUtility: EventHandlerUtility<MessageHeader>
+run(): HttpResponseMessage
}
class UsageCostProcessorFunction [[java:com.iluwatar.consumer.callcostprocessor.functions]] {
-messageHandlerUtilityForUsageDetail: MessageHandlerUtility<UsageDetail>
-messageHandlerUtilityForUsageCostDetail: MessageHandlerUtility<UsageCostDetail>
+run(): HttpResponseMessage
}
class "MessageHandlerUtility<T>" as MessageHandlerUtility_T [[java:com.iluwatar.claimcheckpattern.utility]] {
+readFromPersistantStorage(messageReference: MessageReference, logger: Logger): Message
+dropToPersistantStorage(message: Message, logger: Logger): void
}
class "EventHandlerUtility<T>" as EventHandlerUtility_T [[java:com.callusage.utility.PersistentLocalStorageUtility]] {
+publishEvent(customEvent: T, logger: Logger): void
}
class "Message<T>" as Message_T [[java:com.iluwatar.claimcheckpattern.domain]] {
-messageHeader: MessageHeader
-messageData: MessageData<T>
+Message(messageHeader: MessageHeader, messageData: MessageData<T>)
+getMessageData(): MessageData<T>
+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<T>" 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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

View File

@ -1,69 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
The MIT License
Copyright © 2014-2021 Ilkka Seppälä
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.25.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>claim-check-pattern</artifactId>
<packaging>pom</packaging>
<modules>
<module>call-usage-app</module>
</modules>
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.8</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>4.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>4.0.0</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>collection-pipeline</artifactId>
<dependencies>

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>combinator</artifactId>
<dependencies>

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>command</artifactId>
<dependencies>

View File

@ -36,6 +36,9 @@ public class Wizard {
private final Deque<Runnable> undoStack = new LinkedList<>();
private final Deque<Runnable> redoStack = new LinkedList<>();
public Wizard() {
}
/**
* Cast spell.
*/

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>commander</artifactId>
<dependencies>

View File

@ -90,13 +90,6 @@ 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,
@ -125,17 +118,17 @@ public class Commander {
Retry.Operation op = (l) -> {
if (!l.isEmpty()) {
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
LOG.debug(ORDER_ID + ": Error in connecting to shipping service, "
+ "trying again..", order.id);
LOG.debug("Order " + order.id + ": Error in connecting to shipping service, "
+ "trying again..");
} else {
LOG.debug(ORDER_ID + ": Error in creating shipping request..", order.id);
LOG.debug("Order " + order.id + ": Error in creating shipping request..");
}
throw l.remove(0);
}
String transactionId = shippingService.receiveRequest(order.item, order.user.address);
//could save this transaction id in a db too
LOG.info(ORDER_ID + ": Shipping placed successfully, transaction id: {}",
order.id, transactionId);
LOG.info("Order " + order.id + ": Shipping placed successfully, transaction id: "
+ transactionId);
LOG.info("Order has been placed and will be shipped to you. Please wait while we make your"
+ " payment... ");
sendPaymentRequest(order);
@ -145,19 +138,19 @@ public class Commander {
LOG.info("Shipping is currently not possible to your address. We are working on the problem"
+ " and will get back to you asap.");
finalSiteMsgShown = true;
LOG.info(ORDER_ID + ": Shipping not possible to address, trying to add problem "
+ "to employee db..", order.id);
LOG.info("Order " + order.id + ": Shipping not possible to address, trying to add problem "
+ "to employee db..");
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_ID + ": Item {}" + " unavailable, trying to add "
+ "problem to employee handle..", order.id, order.item);
LOG.info("Order " + order.id + ": Item " + order.item + " unavailable, trying to add "
+ "problem to employee handle..");
employeeHandleIssue(o);
} else {
LOG.info("Sorry, there was a problem in creating your order. Please try later.");
LOG.error(ORDER_ID + ": Shipping service unavailable, order not placed..", order.id);
LOG.error("Order " + order.id + ": Shipping service unavailable, order not placed..");
finalSiteMsgShown = true;
}
};
@ -171,7 +164,7 @@ public class Commander {
if (order.paid.equals(PaymentStatus.TRYING)) {
order.paid = PaymentStatus.NOT_DONE;
sendPaymentFailureMessage(order);
LOG.error(ORDER_ID + ": Payment time for order over, failed and returning..", order.id);
LOG.error("Order " + order.id + ": Payment time for order over, failed and returning..");
} //if succeeded or failed, would have been dequeued, no attempt to make payment
return;
}
@ -180,18 +173,17 @@ public class Commander {
Retry.Operation op = (l) -> {
if (!l.isEmpty()) {
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
LOG.debug(ORDER_ID + ": Error in connecting to payment service,"
+ " trying again..", order.id);
LOG.debug("Order " + order.id + ": Error in connecting to payment service,"
+ " trying again..");
} else {
LOG.debug(ORDER_ID + ": Error in creating payment request..", order.id);
LOG.debug("Order " + order.id + ": Error in creating payment request..");
}
throw l.remove(0);
}
if (order.paid.equals(PaymentStatus.TRYING)) {
var transactionId = paymentService.receiveRequest(order.price);
order.paid = PaymentStatus.DONE;
LOG.info(ORDER_ID + ": Payment successful, transaction Id: {}",
order.id, transactionId);
LOG.info("Order " + order.id + ": Payment successful, transaction Id: " + transactionId);
if (!finalSiteMsgShown) {
LOG.info("Payment made successfully, thank you for shopping with us!!");
finalSiteMsgShown = true;
@ -207,7 +199,7 @@ public class Commander {
+ "Meanwhile, your order has been converted to COD and will be shipped.");
finalSiteMsgShown = true;
}
LOG.error(ORDER_ID + ": Payment details incorrect, failed..", order.id);
LOG.error("Order " + order.id + ": Payment details incorrect, failed..");
o.paid = PaymentStatus.NOT_DONE;
sendPaymentFailureMessage(o);
} else {
@ -217,7 +209,7 @@ public class Commander {
+ "asap. Don't worry, your order has been placed and will be shipped.");
finalSiteMsgShown = true;
}
LOG.warn(ORDER_ID + ": Payment error, going to queue..", order.id);
LOG.warn("Order " + order.id + ": Payment error, going to queue..");
sendPaymentPossibleErrorMsg(o);
}
if (o.paid.equals(PaymentStatus.TRYING) && System
@ -242,7 +234,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_ID + ": Queue time for order over, failed..", qt.order.id);
LOG.trace("Order " + qt.order.id + ": Queue time for order over, failed..");
return;
} else if (qt.taskType.equals(TaskType.PAYMENT) && !qt.order.paid.equals(PaymentStatus.TRYING)
|| qt.taskType.equals(TaskType.MESSAGING) && (qt.messageType == 1
@ -250,30 +242,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_ID + ": Not queueing task since task already done..", qt.order.id);
LOG.trace("Order " + qt.order.id + ": Not queueing task since task already done..");
return;
}
var list = queue.exceptionsList;
Thread t = new Thread(() -> {
Retry.Operation op = (list1) -> {
if (!list1.isEmpty()) {
LOG.warn(ORDER_ID + ": Error in connecting to queue db, trying again..", qt.order.id);
LOG.warn("Order " + qt.order.id + ": Error in connecting to queue db, trying again..");
throw list1.remove(0);
}
queue.add(qt);
queueItems++;
LOG.info(ORDER_ID + ": {}" + " task enqueued..", qt.order.id, qt.getType());
LOG.info("Order " + qt.order.id + ": " + qt.getType() + " task enqueued..");
tryDoingTasksInQueue();
};
Retry.HandleErrorIssue<QueueTask> handleError = (qt1, err) -> {
if (qt1.taskType.equals(TaskType.PAYMENT)) {
qt1.order.paid = PaymentStatus.NOT_DONE;
sendPaymentFailureMessage(qt1.order);
LOG.error(ORDER_ID + ": Unable to enqueue payment task,"
+ " payment failed..", qt1.order.id);
LOG.error("Order " + qt1.order.id + ": Unable to enqueue payment task,"
+ " payment failed..");
}
LOG.error(ORDER_ID + ": Unable to enqueue task of type {}"
+ ", trying to add to employee handle..", qt1.order.id, qt1.getType());
LOG.error("Order " + qt1.order.id + ": Unable to enqueue task of type " + qt1.getType()
+ ", trying to add to employee handle..");
employeeHandleIssue(qt1.order);
};
var r = new Retry<>(op, handleError, numOfRetries, retryDuration,
@ -336,7 +328,7 @@ public class Commander {
private void sendSuccessMessage(Order order) {
if (System.currentTimeMillis() - order.createdTime >= this.messageTime) {
LOG.trace(ORDER_ID + ": Message time for order over, returning..", order.id);
LOG.trace("Order " + order.id + ": Message time for order over, returning..");
return;
}
var list = messagingService.exceptionsList;
@ -362,8 +354,8 @@ public class Commander {
&& System.currentTimeMillis() - o.createdTime < messageTime) {
var qt = new QueueTask(order, TaskType.MESSAGING, 2);
updateQueue(qt);
LOG.info(ORDER_ID + ": Error in sending Payment Success message, trying to"
+ " queue task and add to employee handle..", order.id);
LOG.info("Order " + order.id + ": Error in sending Payment Success message, trying to"
+ " queue task and add to employee handle..");
employeeHandleIssue(order);
}
}
@ -372,11 +364,11 @@ public class Commander {
return (l) -> {
if (!l.isEmpty()) {
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
LOG.debug(ORDER_ID + ERROR_CONNECTING_MSG_SVC
+ "(Payment Success msg), trying again..", order.id);
LOG.debug("Order " + order.id + ": Error in connecting to messaging service "
+ "(Payment Success msg), trying again..");
} else {
LOG.debug(ORDER_ID + ": Error in creating Payment Success"
+ " messaging request..", order.id);
LOG.debug("Order " + order.id + ": Error in creating Payment Success"
+ " messaging request..");
}
throw l.remove(0);
}
@ -384,15 +376,15 @@ public class Commander {
&& !order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) {
var requestId = messagingService.receiveRequest(2);
order.messageSent = MessageSent.PAYMENT_SUCCESSFUL;
LOG.info(ORDER_ID + ": Payment Success message sent,"
+ REQUEST_ID, order.id, requestId);
LOG.info("Order " + order.id + ": Payment Success message sent,"
+ " request Id: " + requestId);
}
};
}
private void sendPaymentFailureMessage(Order order) {
if (System.currentTimeMillis() - order.createdTime >= this.messageTime) {
LOG.trace(ORDER_ID + ": Message time for order over, returning..", order.id);
LOG.trace("Order " + order.id + ": Message time for order over, returning..");
return;
}
var list = messagingService.exceptionsList;
@ -420,8 +412,8 @@ public class Commander {
&& System.currentTimeMillis() - o.createdTime < messageTime) {
var qt = new QueueTask(order, TaskType.MESSAGING, 0);
updateQueue(qt);
LOG.warn(ORDER_ID + ": Error in sending Payment Failure message, "
+ "trying to queue task and add to employee handle..", order.id);
LOG.warn("Order " + order.id + ": Error in sending Payment Failure message, "
+ "trying to queue task and add to employee handle..");
employeeHandleIssue(o);
}
}
@ -429,11 +421,11 @@ public class Commander {
private void handlePaymentFailureRetryOperation(Order order, List<Exception> l) throws Exception {
if (!l.isEmpty()) {
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
LOG.debug(ORDER_ID + ERROR_CONNECTING_MSG_SVC
+ "(Payment Failure msg), trying again..", order.id);
LOG.debug("Order " + order.id + ": Error in connecting to messaging service "
+ "(Payment Failure msg), trying again..");
} else {
LOG.debug(ORDER_ID + ": Error in creating Payment Failure"
+ " message request..", order.id);
LOG.debug("Order " + order.id + ": Error in creating Payment Failure"
+ " message request..");
}
throw l.remove(0);
}
@ -441,8 +433,8 @@ public class Commander {
&& !order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) {
var requestId = messagingService.receiveRequest(0);
order.messageSent = MessageSent.PAYMENT_FAIL;
LOG.info(ORDER_ID + ": Payment Failure message sent successfully,"
+ REQUEST_ID, order.id, requestId);
LOG.info("Order " + order.id + ": Payment Failure message sent successfully,"
+ " request Id: " + requestId);
}
}
@ -477,7 +469,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);
}
}
@ -486,11 +478,11 @@ public class Commander {
throws Exception {
if (!l.isEmpty()) {
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
LOG.debug(ORDER_ID + ERROR_CONNECTING_MSG_SVC
+ "(Payment Error msg), trying again..", order.id);
LOG.debug("Order " + order.id + ": Error in connecting to messaging service "
+ "(Payment Error msg), trying again..");
} else {
LOG.debug(ORDER_ID + ": Error in creating Payment Error"
+ " messaging request..", order.id);
LOG.debug("Order " + order.id + ": Error in creating Payment Error"
+ " messaging request..");
}
throw l.remove(0);
}
@ -498,28 +490,28 @@ public class Commander {
.equals(MessageSent.NONE_SENT)) {
var requestId = messagingService.receiveRequest(1);
order.messageSent = MessageSent.PAYMENT_TRYING;
LOG.info(ORDER_ID + ": Payment Error message sent successfully,"
+ REQUEST_ID, order.id, requestId);
LOG.info("Order " + order.id + ": Payment Error message sent successfully,"
+ " request Id: " + requestId);
}
}
private void employeeHandleIssue(Order order) {
if (System.currentTimeMillis() - order.createdTime >= this.employeeTime) {
LOG.trace(ORDER_ID + ": Employee handle time for order over, returning..", order.id);
LOG.trace("Order " + order.id + ": Employee handle time for order over, returning..");
return;
}
var list = employeeDb.exceptionsList;
var t = new Thread(() -> {
Retry.Operation op = (l) -> {
if (!l.isEmpty()) {
LOG.warn(ORDER_ID + ": Error in connecting to employee handle,"
+ " trying again..", order.id);
LOG.warn("Order " + order.id + ": Error in connecting to employee handle,"
+ " trying again..");
throw l.remove(0);
}
if (!order.addedToEmployeeHandle) {
employeeDb.receiveRequest(order);
order.addedToEmployeeHandle = true;
LOG.info(ORDER_ID + ": Added order to employee database", order.id);
LOG.info("Order " + order.id + ": Added order to employee database");
}
};
Retry.HandleErrorIssue<Order> handleError = (o, err) -> {
@ -527,8 +519,8 @@ public class Commander {
.currentTimeMillis() - order.createdTime < employeeTime) {
var qt = new QueueTask(order, TaskType.EMPLOYEE_DB, -1);
updateQueue(qt);
LOG.warn(ORDER_ID + ": Error in adding to employee db,"
+ " trying to queue task..", order.id);
LOG.warn("Order " + order.id + ": Error in adding to employee db,"
+ " trying to queue task..");
}
};
var r = new Retry<>(op, handleError, numOfRetries, retryDuration,
@ -546,51 +538,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_ID + ": Started doing task of type {}", qt.order.id, qt.getType());
LOG.trace("Order " + qt.order.id + ": Started doing task of type " + qt.getType());
if (qt.getFirstAttemptTime() == -1) {
qt.setFirstAttemptTime(System.currentTimeMillis());
}
if (System.currentTimeMillis() - qt.getFirstAttemptTime() >= queueTaskTime) {
tryDequeue();
LOG.trace(ORDER_ID + ": This queue task of type {}"
+ " does not need to be done anymore (timeout), dequeue..", qt.order.id, qt.getType());
LOG.trace("Order " + qt.order.id + ": This queue task of type " + qt.getType()
+ " does not need to be done anymore (timeout), dequeue..");
} else {
if (qt.taskType.equals(TaskType.PAYMENT)) {
if (!qt.order.paid.equals(PaymentStatus.TRYING)) {
tryDequeue();
LOG.trace(ORDER_ID + ": This payment task already done, dequeueing..", qt.order.id);
LOG.trace("Order " + qt.order.id + ": This payment task already done, dequeueing..");
} else {
sendPaymentRequest(qt.order);
LOG.debug(ORDER_ID + ": Trying to connect to payment service..", qt.order.id);
LOG.debug("Order " + qt.order.id + ": Trying to connect to payment service..");
}
} else if (qt.taskType.equals(TaskType.MESSAGING)) {
if (qt.order.messageSent.equals(MessageSent.PAYMENT_FAIL)
|| qt.order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) {
tryDequeue();
LOG.trace(ORDER_ID + ": This messaging task already done, dequeue..", qt.order.id);
LOG.trace("Order " + qt.order.id + ": This messaging task already done, dequeue..");
} else if (qt.messageType == 1 && (!qt.order.messageSent.equals(MessageSent.NONE_SENT)
|| !qt.order.paid.equals(PaymentStatus.TRYING))) {
tryDequeue();
LOG.trace(ORDER_ID + ": This messaging task does not need to be done,"
+ " dequeue..", qt.order.id);
LOG.trace("Order " + qt.order.id + ": This messaging task does not need to be done,"
+ " dequeue..");
} else if (qt.messageType == 0) {
sendPaymentFailureMessage(qt.order);
LOG.debug(ORDER_ID + TRY_CONNECTING_MSG_SVC, qt.order.id);
LOG.debug("Order " + qt.order.id + ": Trying to connect to messaging service..");
} else if (qt.messageType == 1) {
sendPaymentPossibleErrorMsg(qt.order);
LOG.debug(ORDER_ID + TRY_CONNECTING_MSG_SVC, qt.order.id);
LOG.debug("Order " + qt.order.id + ": Trying to connect to messaging service..");
} else if (qt.messageType == 2) {
sendSuccessMessage(qt.order);
LOG.debug(ORDER_ID + TRY_CONNECTING_MSG_SVC, qt.order.id);
LOG.debug("Order " + qt.order.id + ": Trying to connect to messaging service..");
}
} else if (qt.taskType.equals(TaskType.EMPLOYEE_DB)) {
if (qt.order.addedToEmployeeHandle) {
tryDequeue();
LOG.trace(ORDER_ID + ": This employee handle task already done,"
+ " dequeue..", qt.order.id);
LOG.trace("Order " + qt.order.id + ": This employee handle task already done,"
+ " dequeue..");
} else {
employeeHandleIssue(qt.order);
LOG.debug(ORDER_ID + ": Trying to connect to employee handle..", qt.order.id);
LOG.debug("Order " + qt.order.id + ": Trying to connect to employee handle..");
}
}
}

View File

@ -1,526 +0,0 @@
package com.iluwatar.commander;
import com.iluwatar.commander.employeehandle.EmployeeDatabase;
import com.iluwatar.commander.employeehandle.EmployeeHandle;
import com.iluwatar.commander.exceptions.DatabaseUnavailableException;
import com.iluwatar.commander.exceptions.ItemUnavailableException;
import com.iluwatar.commander.exceptions.PaymentDetailsErrorException;
import com.iluwatar.commander.exceptions.ShippingNotPossibleException;
import com.iluwatar.commander.messagingservice.MessagingDatabase;
import com.iluwatar.commander.messagingservice.MessagingService;
import com.iluwatar.commander.paymentservice.PaymentDatabase;
import com.iluwatar.commander.paymentservice.PaymentService;
import com.iluwatar.commander.queue.QueueDatabase;
import com.iluwatar.commander.shippingservice.ShippingDatabase;
import com.iluwatar.commander.shippingservice.ShippingService;
import org.junit.jupiter.api.Test;
import org.junit.platform.commons.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertFalse;
class CommanderTest {
private final int numOfRetries = 1;
private final long retryDuration = 1_000;
private long queueTime = 1_00;
private long queueTaskTime = 1_000;
private long paymentTime = 6_000;
private long messageTime = 5_000;
private long employeeTime = 2_000;
private static final List<Exception> exceptionList = new ArrayList<>();
static {
exceptionList.add(new DatabaseUnavailableException());
exceptionList.add(new ShippingNotPossibleException());
exceptionList.add(new ItemUnavailableException());
exceptionList.add(new PaymentDetailsErrorException());
exceptionList.add(new IllegalStateException());
}
private Commander buildCommanderObject() {
return buildCommanderObject(false);
}
private Commander buildCommanderObject(boolean nonPaymentException) {
PaymentService paymentService = new PaymentService
(new PaymentDatabase(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException());
ShippingService shippingService;
MessagingService messagingService;
if (nonPaymentException) {
shippingService = new ShippingService(new ShippingDatabase(), new DatabaseUnavailableException());
messagingService = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException());
} else {
shippingService = new ShippingService(new ShippingDatabase(), new DatabaseUnavailableException());
messagingService = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException());
}
var employeeHandle = new EmployeeHandle
(new EmployeeDatabase(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var qdb = new QueueDatabase
(new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException());
return new Commander(employeeHandle, paymentService, shippingService,
messagingService, qdb, numOfRetries, retryDuration,
queueTime, queueTaskTime, paymentTime, messageTime, employeeTime);
}
private Commander buildCommanderObjectVanilla() {
PaymentService paymentService = new PaymentService
(new PaymentDatabase(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var shippingService = new ShippingService(new ShippingDatabase());
var messagingService = new MessagingService(new MessagingDatabase());
var employeeHandle = new EmployeeHandle
(new EmployeeDatabase(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var qdb = new QueueDatabase
(new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException());
return new Commander(employeeHandle, paymentService, shippingService,
messagingService, qdb, numOfRetries, retryDuration,
queueTime, queueTaskTime, paymentTime, messageTime, employeeTime);
}
private Commander buildCommanderObjectUnknownException() {
PaymentService paymentService = new PaymentService
(new PaymentDatabase(), new IllegalStateException());
var shippingService = new ShippingService(new ShippingDatabase());
var messagingService = new MessagingService(new MessagingDatabase());
var employeeHandle = new EmployeeHandle
(new EmployeeDatabase(), new IllegalStateException());
var qdb = new QueueDatabase
(new DatabaseUnavailableException(), new IllegalStateException());
return new Commander(employeeHandle, paymentService, shippingService,
messagingService, qdb, numOfRetries, retryDuration,
queueTime, queueTaskTime, paymentTime, messageTime, employeeTime);
}
private Commander buildCommanderObjectNoPaymentException1() {
PaymentService paymentService = new PaymentService
(new PaymentDatabase());
var shippingService = new ShippingService(new ShippingDatabase());
var messagingService = new MessagingService(new MessagingDatabase());
var employeeHandle = new EmployeeHandle
(new EmployeeDatabase(), new IllegalStateException());
var qdb = new QueueDatabase
(new DatabaseUnavailableException(), new IllegalStateException());
return new Commander(employeeHandle, paymentService, shippingService,
messagingService, qdb, numOfRetries, retryDuration,
queueTime, queueTaskTime, paymentTime, messageTime, employeeTime);
}
private Commander buildCommanderObjectNoPaymentException2() {
PaymentService paymentService = new PaymentService
(new PaymentDatabase());
var shippingService = new ShippingService(new ShippingDatabase());
var messagingService = new MessagingService(new MessagingDatabase(), new IllegalStateException());
var employeeHandle = new EmployeeHandle
(new EmployeeDatabase(), new IllegalStateException());
var qdb = new QueueDatabase
(new DatabaseUnavailableException(), new IllegalStateException());
return new Commander(employeeHandle, paymentService, shippingService,
messagingService, qdb, numOfRetries, retryDuration,
queueTime, queueTaskTime, paymentTime, messageTime, employeeTime);
}
private Commander buildCommanderObjectNoPaymentException3() {
PaymentService paymentService = new PaymentService
(new PaymentDatabase());
var shippingService = new ShippingService(new ShippingDatabase());
var messagingService = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException());
var employeeHandle = new EmployeeHandle
(new EmployeeDatabase(), new IllegalStateException());
var qdb = new QueueDatabase
(new DatabaseUnavailableException(), new IllegalStateException());
return new Commander(employeeHandle, paymentService, shippingService,
messagingService, qdb, numOfRetries, retryDuration,
queueTime, queueTaskTime, paymentTime, messageTime, employeeTime);
}
private Commander buildCommanderObjectWithDB() {
return buildCommanderObjectWithoutDB(false, false, new IllegalStateException());
}
private Commander buildCommanderObjectWithDB(boolean includeException, boolean includeDBException, Exception e) {
var l = includeDBException ? new DatabaseUnavailableException() : e;
PaymentService paymentService;
ShippingService shippingService;
MessagingService messagingService;
EmployeeHandle employeeHandle;
if (includeException) {
paymentService = new PaymentService
(new PaymentDatabase(), l);
shippingService = new ShippingService(new ShippingDatabase(), l);
messagingService = new MessagingService(new MessagingDatabase(), l);
employeeHandle = new EmployeeHandle
(new EmployeeDatabase(), l);
} else {
paymentService = new PaymentService
(null);
shippingService = new ShippingService(null);
messagingService = new MessagingService(null);
employeeHandle = new EmployeeHandle
(null);
}
return new Commander(employeeHandle, paymentService, shippingService,
messagingService, null, numOfRetries, retryDuration,
queueTime, queueTaskTime, paymentTime, messageTime, employeeTime);
}
private Commander buildCommanderObjectWithoutDB() {
return buildCommanderObjectWithoutDB(false, false, new IllegalStateException());
}
private Commander buildCommanderObjectWithoutDB(boolean includeException, boolean includeDBException, Exception e) {
var l = includeDBException ? new DatabaseUnavailableException() : e;
PaymentService paymentService;
ShippingService shippingService;
MessagingService messagingService;
EmployeeHandle employeeHandle;
if (includeException) {
paymentService = new PaymentService
(null, l);
shippingService = new ShippingService(null, l);
messagingService = new MessagingService(null, l);
employeeHandle = new EmployeeHandle
(null, l);
} else {
paymentService = new PaymentService
(null);
shippingService = new ShippingService(null);
messagingService = new MessagingService(null);
employeeHandle = new EmployeeHandle
(null);
}
return new Commander(employeeHandle, paymentService, shippingService,
messagingService, null, numOfRetries, retryDuration,
queueTime, queueTaskTime, paymentTime, messageTime, employeeTime);
}
@Test
void testPlaceOrderVanilla() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
Commander c = buildCommanderObjectVanilla();
var order = new Order(new User("K", "J"), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
@Test
void testPlaceOrder() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
Commander c = buildCommanderObject(true);
var order = new Order(new User("K", "J"), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
@Test
void testPlaceOrder2() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
Commander c = buildCommanderObject(false);
var order = new Order(new User("K", "J"), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
@Test
void testPlaceOrderNoException1() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
Commander c = buildCommanderObjectNoPaymentException1();
var order = new Order(new User("K", "J"), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
@Test
void testPlaceOrderNoException2() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
Commander c = buildCommanderObjectNoPaymentException2();
var order = new Order(new User("K", "J"), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
@Test
void testPlaceOrderNoException3() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
Commander c = buildCommanderObjectNoPaymentException3();
var order = new Order(new User("K", "J"), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
@Test
void testPlaceOrderNoException4() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
Commander c = buildCommanderObjectNoPaymentException3();
var order = new Order(new User("K", "J"), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
c.placeOrder(order);
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
@Test
void testPlaceOrderUnknownException() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
messageTime *= d;
employeeTime *= d;
queueTime *= d;
Commander c = buildCommanderObjectUnknownException();
var order = new Order(new User("K", "J"), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
@Test
void testPlaceOrderShortDuration() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
messageTime *= d;
employeeTime *= d;
queueTime *= d;
Commander c = buildCommanderObject(true);
var order = new Order(new User("K", "J"), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
@Test
void testPlaceOrderShortDuration2() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
messageTime *= d;
employeeTime *= d;
queueTime *= d;
Commander c = buildCommanderObject(false);
var order = new Order(new User("K", "J"), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
@Test
void testPlaceOrderNoExceptionShortMsgDuration() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
messageTime *= d;
employeeTime *= d;
queueTime *= d;
Commander c = buildCommanderObjectNoPaymentException1();
var order = new Order(new User("K", "J"), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
@Test
void testPlaceOrderNoExceptionShortQueueDuration() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
messageTime *= d;
employeeTime *= d;
queueTime *= d;
Commander c = buildCommanderObjectUnknownException();
var order = new Order(new User("K", "J"), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
@Test
void testPlaceOrderWithDatabase() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
messageTime *= d;
employeeTime *= d;
queueTime *= d;
Commander c = buildCommanderObjectWithDB();
var order = new Order(new User("K", null), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
@Test
void testPlaceOrderWithDatabaseAndExceptions() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
messageTime *= d;
employeeTime *= d;
queueTime *= d;
for (Exception e : exceptionList) {
Commander c = buildCommanderObjectWithDB(true, true, e);
var order = new Order(new User("K", null), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
c = buildCommanderObjectWithDB(true, false, e);
order = new Order(new User("K", null), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
c = buildCommanderObjectWithDB(false, false, e);
order = new Order(new User("K", null), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
c = buildCommanderObjectWithDB(false, true, e);
order = new Order(new User("K", null), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
}
@Test
void testPlaceOrderWithoutDatabase() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
messageTime *= d;
employeeTime *= d;
queueTime *= d;
Commander c = buildCommanderObjectWithoutDB();
var order = new Order(new User("K", null), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
@Test
void testPlaceOrderWithoutDatabaseAndExceptions() throws Exception {
for (double d = 0.1; d < 2; d = d + 0.1) {
paymentTime *= d;
queueTaskTime *= d;
messageTime *= d;
employeeTime *= d;
queueTime *= d;
for (Exception e : exceptionList) {
Commander c = buildCommanderObjectWithoutDB(true, true, e);
var order = new Order(new User("K", null), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
c = buildCommanderObjectWithoutDB(true, false, e);
order = new Order(new User("K", null), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
c = buildCommanderObjectWithoutDB(false, false, e);
order = new Order(new User("K", null), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
c = buildCommanderObjectWithoutDB(false, true, e);
order = new Order(new User("K", null), "pen", 1f);
for (Order.MessageSent ms : Order.MessageSent.values()) {
c.placeOrder(order);
assertFalse(StringUtils.isBlank(order.id));
}
}
}
}
}

View File

@ -27,7 +27,7 @@
<parent>
<artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>composite-entity</artifactId>

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>composite</artifactId>
<dependencies>

View File

@ -27,7 +27,7 @@
<parent>
<artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>converter</artifactId>
<modelVersion>4.0.0</modelVersion>

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>cqrs</artifactId>
<dependencies>

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>dao</artifactId>
<dependencies>

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>data-bus</artifactId>
<dependencies>

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>data-locality</artifactId>
<dependencies>

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>data-mapper</artifactId>
<dependencies>

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>data-transfer-object</artifactId>
<dependencies>

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>decorator</artifactId>
<dependencies>

View File

@ -27,7 +27,7 @@
<parent>
<artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>delegation</artifactId>

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>dependency-injection</artifactId>
<dependencies>

View File

@ -28,10 +28,10 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>dirty-flag</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
<name>dirty-flag</name>
<url>http://maven.apache.org</url>
<properties>

View File

@ -27,7 +27,7 @@
<parent>
<artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>domain-model</artifactId>

View File

@ -27,7 +27,7 @@
<parent>
<artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>double-buffer</artifactId>

View File

@ -27,7 +27,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>double-checked-locking</artifactId>
<dependencies>

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>double-dispatch</artifactId>
<dependencies>

View File

@ -29,7 +29,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>eip-message-channel</artifactId>
<dependencies>

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>eip-publish-subscribe</artifactId>
<dependencies>

View File

@ -29,7 +29,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>

View File

@ -29,7 +29,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>event-aggregator</artifactId>
<dependencies>

View File

@ -49,28 +49,13 @@ public class App {
public static void main(String[] args) {
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 kingsHand = new KingsHand(kingJoffrey);
var emitters = List.of(
kingsHand,
baelish,
varys,
scout
new LordBaelish(kingsHand),
new LordVarys(kingsHand),
new Scout(kingsHand)
);
Arrays.stream(Weekday.values())

View File

@ -31,7 +31,6 @@ 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");

View File

@ -23,48 +23,31 @@
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 Map<Event, List<EventObserver>> observerLists;
private final List<EventObserver> observers;
public EventEmitter() {
observerLists = new HashMap<>();
observers = new LinkedList<>();
}
public EventEmitter(EventObserver obs, Event e) {
public EventEmitter(EventObserver obs) {
this();
registerObserver(obs, e);
registerObserver(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);
}
public final void registerObserver(EventObserver obs) {
observers.add(obs);
}
protected void notifyObservers(Event e) {
if (observerLists.containsKey(e)) {
observerLists
.get(e)
.forEach(observer -> observer.onEvent(e));
}
observers.forEach(obs -> obs.onEvent(e));
}
public abstract void timePasses(Weekday day);

View File

@ -31,8 +31,8 @@ public class KingsHand extends EventEmitter implements EventObserver {
public KingsHand() {
}
public KingsHand(EventObserver obs, Event e) {
super(obs, e);
public KingsHand(EventObserver obs) {
super(obs);
}
@Override
@ -42,5 +42,6 @@ public class KingsHand extends EventEmitter implements EventObserver {
@Override
public void timePasses(Weekday day) {
// NOP
}
}

View File

@ -31,8 +31,8 @@ public class LordBaelish extends EventEmitter {
public LordBaelish() {
}
public LordBaelish(EventObserver obs, Event e) {
super(obs, e);
public LordBaelish(EventObserver obs) {
super(obs);
}
@Override

View File

@ -23,19 +23,16 @@
package com.iluwatar.event.aggregator;
import lombok.extern.slf4j.Slf4j;
/**
* LordVarys produces events.
*/
@Slf4j
public class LordVarys extends EventEmitter implements EventObserver {
public class LordVarys extends EventEmitter {
public LordVarys() {
}
public LordVarys(EventObserver obs, Event e) {
super(obs, e);
public LordVarys(EventObserver obs) {
super(obs);
}
@Override
@ -44,10 +41,4 @@ public class LordVarys extends EventEmitter implements EventObserver {
notifyObservers(Event.TRAITOR_DETECTED);
}
}
@Override
public void onEvent(Event e) {
notifyObservers(e);
}
}

View File

@ -31,8 +31,8 @@ public class Scout extends EventEmitter {
public Scout() {
}
public Scout(EventObserver obs, Event e) {
super(obs, e);
public Scout(EventObserver obs) {
super(obs);
}
@Override
@ -40,8 +40,5 @@ public class Scout extends EventEmitter {
if (day == Weekday.TUESDAY) {
notifyObservers(Event.WARSHIPS_APPROACHING);
}
if (day == Weekday.WEDNESDAY) {
notifyObservers(Event.WHITE_WALKERS_SIGHTED);
}
}
}

View File

@ -31,7 +31,6 @@ 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;
@ -47,7 +46,7 @@ abstract class EventEmitterTest<E extends EventEmitter> {
/**
* Factory used to create a new instance of the test object with a default observer
*/
private final BiFunction<EventObserver, Event, E> factoryWithDefaultObserver;
private final Function<EventObserver, E> factoryWithDefaultObserver;
/**
* Factory used to create a new instance of the test object without passing a default observer
@ -68,7 +67,7 @@ abstract class EventEmitterTest<E extends EventEmitter> {
* Create a new event emitter test, using the given test object factories, special day and event
*/
EventEmitterTest(final Weekday specialDay, final Event event,
final BiFunction<EventObserver, Event, E> factoryWithDefaultObserver,
final Function<EventObserver, E> factoryWithDefaultObserver,
final Supplier<E> factoryWithoutDefaultObserver) {
this.specialDay = specialDay;
@ -130,8 +129,8 @@ abstract class EventEmitterTest<E extends EventEmitter> {
final var observer2 = mock(EventObserver.class);
final var emitter = this.factoryWithoutDefaultObserver.get();
emitter.registerObserver(observer1, event);
emitter.registerObserver(observer2, event);
emitter.registerObserver(observer1);
emitter.registerObserver(observer2);
testAllDays(specialDay, event, emitter, observer1, observer2);
}
@ -147,9 +146,9 @@ abstract class EventEmitterTest<E extends EventEmitter> {
final var observer1 = mock(EventObserver.class);
final var observer2 = mock(EventObserver.class);
final var emitter = this.factoryWithDefaultObserver.apply(defaultObserver, event);
emitter.registerObserver(observer1, event);
emitter.registerObserver(observer2, event);
final var emitter = this.factoryWithDefaultObserver.apply(defaultObserver);
emitter.registerObserver(observer1);
emitter.registerObserver(observer2);
testAllDays(specialDay, event, emitter, defaultObserver, observer1, observer2);
}

View File

@ -55,11 +55,7 @@ class KingsHandTest extends EventEmitterTest<KingsHand> {
@Test
void testPassThrough() throws Exception {
final var observer = mock(EventObserver.class);
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);
final var kingsHand = new KingsHand(observer);
// The kings hand should not pass any events before he received one
verifyZeroInteractions(observer);

View File

@ -34,9 +34,7 @@ class ScoutTest extends EventEmitterTest<Scout> {
* 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);
}
}

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>event-asynchronous</artifactId>
<dependencies>

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.25.0-SNAPSHOT</version>
</parent>
<artifactId>event-driven-architecture</artifactId>
<dependencies>

Some files were not shown because too many files have changed in this diff Show More