(NEW) Module "retry"
(NEW) Illustrative classes:
      - App: simulates a production application
      - BusinessOperation<T>: abstraction over any operation that can
        potentially fail
      - FindCustomer <: BusinessOperation<String>: illustrative
        operation that can throw an error
      - Retry <: BusinessOperation<T>: transparently implements the
        retry mechanism
      - Several "business" exceptions:
        - BusinessException: top-level
        - CustomerNotFoundException: can be ignored
        - DatabaseNotAvailableException: fatal error
(NEW) .puml and .png for UML
			
			
This commit is contained in:
		
							
								
								
									
										153
									
								
								retry/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								retry/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,153 @@
 | 
			
		||||
---
 | 
			
		||||
layout: pattern
 | 
			
		||||
title: Retry
 | 
			
		||||
folder: retry
 | 
			
		||||
permalink: /patterns/retry/
 | 
			
		||||
categories: other
 | 
			
		||||
tags:
 | 
			
		||||
  - java
 | 
			
		||||
  - difficulty-expert
 | 
			
		||||
  - performance
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
## Retry / resiliency
 | 
			
		||||
Enables an application to handle transient failures from external resources.
 | 
			
		||||
 | 
			
		||||
## Intent
 | 
			
		||||
Transparently retry certain operations that involve communication with external
 | 
			
		||||
resources, particularly over the network, isolating calling code from the 
 | 
			
		||||
retry implementation details.
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
## Explanation
 | 
			
		||||
The `Retry` pattern consists retrying operations on remote resources over the 
 | 
			
		||||
network a set number of times. It closely depends on both business and technical
 | 
			
		||||
requirements: how much time will the business allow the end user to wait while
 | 
			
		||||
the operation finishes? What are the performance characteristics of the 
 | 
			
		||||
remote resource during peak loads as well as our application as more threads
 | 
			
		||||
are waiting for the remote resource's availability? Among the errors returned
 | 
			
		||||
by the remote service, which can be safely ignored in order to retry? Is the 
 | 
			
		||||
operation [idempotent](https://en.wikipedia.org/wiki/Idempotence)?
 | 
			
		||||
 | 
			
		||||
Another concern is the impact on the calling code by implementing the retry 
 | 
			
		||||
mechanism. The retry mechanics should ideally be completely transparent to the
 | 
			
		||||
calling code (service interface remains unaltered). There are two general 
 | 
			
		||||
approaches to this problem: from an enterprise architecture standpoint 
 | 
			
		||||
(**strategic**), and a shared library standpoint (**tactical**).
 | 
			
		||||
 | 
			
		||||
*(As an aside, one interesting property is that, since implementations tend to 
 | 
			
		||||
be configurable at runtime, daily monitoring and operation of this capability 
 | 
			
		||||
is shifted over to operations support instead of the developers themselves.)*
 | 
			
		||||
 | 
			
		||||
From a strategic point of view, this would be solved by having requests
 | 
			
		||||
be redirected to a separate intermediary system, traditionally an 
 | 
			
		||||
[ESB](https://en.wikipedia.org/wiki/Enterprise_service_bus), but more recently
 | 
			
		||||
a [Service Mesh](https://medium.com/microservices-in-practice/service-mesh-for-microservices-2953109a3c9a).
 | 
			
		||||
 | 
			
		||||
From a tactical point of view, this would be solved by reusing shared libraries 
 | 
			
		||||
like [Hystrix](https://github.com/Netflix/Hystrix)[1]. This is the type of 
 | 
			
		||||
solution showcased in the simple example that accompanies this *README*.
 | 
			
		||||
 | 
			
		||||
In our hypothetical application, we have a generic interface for all 
 | 
			
		||||
operations on remote interfaces:
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
public interface BusinessOperation<T> {
 | 
			
		||||
  T perform() throws BusinessException;
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
And we have an implementation of this interface that finds our customers 
 | 
			
		||||
by looking up a database:
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
public final class FindCustomer implements BusinessOperation<String> {
 | 
			
		||||
  @Override
 | 
			
		||||
  public String perform() throws BusinessException {
 | 
			
		||||
    ...
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Our `FindCustomer` implementation can be configured to throw 
 | 
			
		||||
`BusinessException`s before returning the customer's ID, thereby simulating a
 | 
			
		||||
'flaky' service that intermittently fails. Some exceptions, like the 
 | 
			
		||||
`CustomerNotFoundException`, are deemed to be recoverable after some 
 | 
			
		||||
hypothetical analysis because the root cause of the error stems from "some
 | 
			
		||||
database locking issue". However, the `DatabaseNotAvailableException` is 
 | 
			
		||||
considered to be a definite showstopper - the application should not attempt
 | 
			
		||||
to recover from this error.
 | 
			
		||||
 | 
			
		||||
We can model a 'recoverable' scenario by instantiating `FindCustomer` like this:
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
final BusinessOperation<String> op = new FindCustomer(
 | 
			
		||||
    "12345",
 | 
			
		||||
    new CustomerNotFoundException("not found"),
 | 
			
		||||
    new CustomerNotFoundException("still not found"),
 | 
			
		||||
    new CustomerNotFoundException("don't give up yet!")
 | 
			
		||||
);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
In this configuration, `FindCustomer` will throw `CustomerNotFoundException`
 | 
			
		||||
three times, after which it will consistently return the customer's ID 
 | 
			
		||||
(`12345`).
 | 
			
		||||
 | 
			
		||||
In our hypothetical scenario, our analysts indicate that this operation 
 | 
			
		||||
typically fails 2-4 times for a given input during peak hours, and that each 
 | 
			
		||||
worker thread in the database subsystem typically needs 50ms to 
 | 
			
		||||
"recover from an error". Applying these policies would yield something like
 | 
			
		||||
this:
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
final BusinessOperation<String> op = new Retry<>(
 | 
			
		||||
    new FindCustomer(
 | 
			
		||||
        "1235",
 | 
			
		||||
        new CustomerNotFoundException("not found"),
 | 
			
		||||
        new CustomerNotFoundException("still not found"),
 | 
			
		||||
        new CustomerNotFoundException("don't give up yet!")
 | 
			
		||||
    ),
 | 
			
		||||
    5,
 | 
			
		||||
    100,
 | 
			
		||||
    e -> CustomerNotFoundException.class.isAssignableFrom(e.getClass())
 | 
			
		||||
);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Executing `op` *once* would automatically trigger at most 5 retry attempts,
 | 
			
		||||
with a 100 millisecond delay between attempts, ignoring any 
 | 
			
		||||
`CustomerNotFoundException` thrown while trying. In this particular scenario,
 | 
			
		||||
due to the configuration for `FindCustomer`, there will be 1 initial attempt 
 | 
			
		||||
and 3 additional retries before finally returning the desired result `12345`.
 | 
			
		||||
 | 
			
		||||
If our `FindCustomer` operation were instead to throw a fatal 
 | 
			
		||||
`DatabaseNotFoundException`, which we were instructed not to ignore, but 
 | 
			
		||||
more importantly we did *not* instruct our `Retry` to ignore, then the operation
 | 
			
		||||
would have failed immediately upon receiving the error, not matter how many 
 | 
			
		||||
attempts were left.
 | 
			
		||||
 | 
			
		||||
<br/><br/>
 | 
			
		||||
 | 
			
		||||
[1] Please note that *Hystrix* is a complete implementation of the *Circuit
 | 
			
		||||
Breaker* pattern, of which the *Retry* pattern can be considered a subset of.
 | 
			
		||||
 | 
			
		||||
## Applicability
 | 
			
		||||
Whenever an application needs to communicate with an external resource, 
 | 
			
		||||
particularly in a cloud environment, and if the business requirements allow it.
 | 
			
		||||
 | 
			
		||||
## Presentations
 | 
			
		||||
You can view Microsoft's article [here](https://docs.microsoft.com/en-us/azure/architecture/patterns/retry).
 | 
			
		||||
 | 
			
		||||
## Consequences
 | 
			
		||||
**Pros:** 
 | 
			
		||||
 | 
			
		||||
* Resiliency
 | 
			
		||||
* Provides hard data on external failures
 | 
			
		||||
 | 
			
		||||
**Cons:** 
 | 
			
		||||
 | 
			
		||||
* Complexity
 | 
			
		||||
* Operations maintenance
 | 
			
		||||
 | 
			
		||||
## Related Patterns
 | 
			
		||||
* [Circuit Breaker](https://martinfowler.com/bliki/CircuitBreaker.html)
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								retry/etc/retry.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								retry/etc/retry.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 51 KiB  | 
							
								
								
									
										38
									
								
								retry/etc/retry.urm.puml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								retry/etc/retry.urm.puml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
@startuml
 | 
			
		||||
package com.iluwatar.retry {
 | 
			
		||||
  class App {
 | 
			
		||||
    - LOG : Logger {static}
 | 
			
		||||
    - op : BusinessOperation<String> {static}
 | 
			
		||||
    + App()
 | 
			
		||||
    - errorNoRetry() {static}
 | 
			
		||||
    - errorWithRetry() {static}
 | 
			
		||||
    + main(args : String[]) {static}
 | 
			
		||||
    - noErrors() {static}
 | 
			
		||||
  }
 | 
			
		||||
  interface BusinessOperation<T> {
 | 
			
		||||
    + perform() : T {abstract}
 | 
			
		||||
  }
 | 
			
		||||
  class FindCustomer {
 | 
			
		||||
    - customerId : String
 | 
			
		||||
    - errors : Deque<BusinessException>
 | 
			
		||||
    + FindCustomer(customerId : String, errors : BusinessException[])
 | 
			
		||||
    + perform() : String
 | 
			
		||||
  }
 | 
			
		||||
  class Retry<T> {
 | 
			
		||||
    - attempts : AtomicInteger
 | 
			
		||||
    - delay : long
 | 
			
		||||
    - errors : List<Exception>
 | 
			
		||||
    - maxAttempts : int
 | 
			
		||||
    - op : BusinessOperation<T>
 | 
			
		||||
    - test : Predicate<Exception>
 | 
			
		||||
    + Retry<T>(op : BusinessOperation<T>, maxAttempts : int, delay : long, ignoreTests : Predicate<Exception>[])
 | 
			
		||||
    + attempts() : int
 | 
			
		||||
    + errors() : List<Exception>
 | 
			
		||||
    + perform() : T
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
Retry -->  "-op" BusinessOperation
 | 
			
		||||
App -->  "-op" BusinessOperation
 | 
			
		||||
FindCustomer ..|> BusinessOperation 
 | 
			
		||||
Retry ..|> BusinessOperation 
 | 
			
		||||
@enduml
 | 
			
		||||
							
								
								
									
										45
									
								
								retry/pom.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								retry/pom.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<!--
 | 
			
		||||
    The MIT License
 | 
			
		||||
    Copyright (c) 2014 Ilkka Seppälä
 | 
			
		||||
 | 
			
		||||
    Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
    of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
    in the Software without restriction, including without limitation the rights
 | 
			
		||||
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
    copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
    furnished to do so, subject to the following conditions:
 | 
			
		||||
 | 
			
		||||
    The above copyright notice and this permission notice shall be included in
 | 
			
		||||
    all copies or substantial portions of the Software.
 | 
			
		||||
 | 
			
		||||
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
			
		||||
    THE SOFTWARE.
 | 
			
		||||
-->
 | 
			
		||||
<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>java-design-patterns</artifactId>
 | 
			
		||||
        <version>1.17.0-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
    <artifactId>retry</artifactId>
 | 
			
		||||
    <packaging>jar</packaging>
 | 
			
		||||
    <dependencies>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>junit</groupId>
 | 
			
		||||
            <artifactId>junit</artifactId>
 | 
			
		||||
            <scope>test</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.hamcrest</groupId>
 | 
			
		||||
            <artifactId>hamcrest-core</artifactId>
 | 
			
		||||
            <scope>test</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
</project>
 | 
			
		||||
							
								
								
									
										107
									
								
								retry/src/main/java/com/iluwatar/retry/App.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								retry/src/main/java/com/iluwatar/retry/App.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,107 @@
 | 
			
		||||
/*
 | 
			
		||||
 * The MIT License (MIT)
 | 
			
		||||
 * 
 | 
			
		||||
 * Copyright (c) 2014-2016 Ilkka Seppälä
 | 
			
		||||
 * 
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 * 
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 * 
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package com.iluwatar.retry;
 | 
			
		||||
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The <em>Retry</em> pattern enables applications to handle potentially recoverable failures from 
 | 
			
		||||
 * the environment if the business requirements and nature of the failures allow it. By retrying 
 | 
			
		||||
 * failed operations on external dependencies, the application may maintain stability and minimize
 | 
			
		||||
 * negative impact on the user experience.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * In our example, we have the {@link BusinessOperation} interface as an abstraction over
 | 
			
		||||
 * all operations that our application performs involving remote systems. The calling code should
 | 
			
		||||
 * remain decoupled from implementations. 
 | 
			
		||||
 * <p>
 | 
			
		||||
 * {@link FindCustomer} is a business operation that looks up a customer's record and returns 
 | 
			
		||||
 * its ID. Imagine its job is performed by looking up the customer in our local database and 
 | 
			
		||||
 * returning its ID. We can pass {@link CustomerNotFoundException} as one of its 
 | 
			
		||||
 * {@link FindCustomer#FindCustomer(java.lang.String, com.iluwatar.retry.BusinessException...) 
 | 
			
		||||
 * constructor parameters} in order to simulate not finding the customer.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Imagine that, lately, this operation has experienced intermittent failures due to some weird 
 | 
			
		||||
 * corruption and/or locking in the data. After retrying a few times the customer is found. The 
 | 
			
		||||
 * database is still, however, expected to always be available.  While a definitive solution is 
 | 
			
		||||
 * found to the problem, our engineers advise us to retry the operation a set number
 | 
			
		||||
 * of times with a set delay between retries, although not too many retries otherwise the end user
 | 
			
		||||
 * will be left waiting for a long time, while delays that are too short will not allow the database
 | 
			
		||||
 * to recover from the load. 
 | 
			
		||||
 * <p>
 | 
			
		||||
 * To keep the calling code as decoupled as possible from this workaround, we have implemented the
 | 
			
		||||
 * retry mechanism as a {@link BusinessOperation} named {@link Retry}.
 | 
			
		||||
 * 
 | 
			
		||||
 * @author George Aristy (george.aristy@gmail.com)
 | 
			
		||||
 * @see <a href="https://docs.microsoft.com/en-us/azure/architecture/patterns/retry">Retry pattern (Microsoft Azure Docs)</a>
 | 
			
		||||
 * @since 1.17.0
 | 
			
		||||
 */
 | 
			
		||||
public final class App {
 | 
			
		||||
  private static final Logger LOG = LoggerFactory.getLogger(App.class);
 | 
			
		||||
  private static BusinessOperation<String> op;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Entry point.
 | 
			
		||||
   * 
 | 
			
		||||
   * @param args not used
 | 
			
		||||
   * @throws Exception not expected
 | 
			
		||||
   * @since 1.17.0
 | 
			
		||||
   */
 | 
			
		||||
  public static void main(String[] args) throws Exception {
 | 
			
		||||
    noErrors();
 | 
			
		||||
    errorNoRetry();
 | 
			
		||||
    errorWithRetry();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static void noErrors() throws Exception {
 | 
			
		||||
    op = new FindCustomer("123");
 | 
			
		||||
    op.perform();
 | 
			
		||||
    LOG.info("Sometimes the operation executes with no errors.");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static void errorNoRetry() throws Exception {
 | 
			
		||||
    op = new FindCustomer("123", new CustomerNotFoundException("not found"));
 | 
			
		||||
    try {
 | 
			
		||||
      op.perform();
 | 
			
		||||
    } catch (CustomerNotFoundException e) {
 | 
			
		||||
      LOG.info("Yet the operation will throw an error every once in a while.");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static void errorWithRetry() throws Exception {
 | 
			
		||||
    final Retry<String> retry = new Retry<>(
 | 
			
		||||
        new FindCustomer("123", new CustomerNotFoundException("not found")),
 | 
			
		||||
        3,  //3 attempts
 | 
			
		||||
        100, //100 ms delay between attempts
 | 
			
		||||
        e -> CustomerNotFoundException.class.isAssignableFrom(e.getClass())
 | 
			
		||||
    );
 | 
			
		||||
    op = retry;
 | 
			
		||||
    final String customerId = op.perform();
 | 
			
		||||
    LOG.info(String.format(
 | 
			
		||||
        "However, retrying the operation while ignoring a recoverable error will eventually yield "
 | 
			
		||||
        + "the result %s after a number of attempts %s", customerId, retry.attempts()
 | 
			
		||||
    ));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,48 @@
 | 
			
		||||
/*
 | 
			
		||||
 * The MIT License (MIT)
 | 
			
		||||
 * 
 | 
			
		||||
 * Copyright (c) 2014-2016 Ilkka Seppälä
 | 
			
		||||
 * 
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 * 
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 * 
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package com.iluwatar.retry;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The top-most type in our exception hierarchy that signifies that an unexpected error 
 | 
			
		||||
 * condition occurred. Its use is reserved as a "catch-all" for cases where no other subtype 
 | 
			
		||||
 * captures the specificity of the error condition in question. Calling code is not expected to 
 | 
			
		||||
 * be able to handle this error and should be reported to the maintainers immediately.
 | 
			
		||||
 *
 | 
			
		||||
 * @author George Aristy (george.aristy@gmail.com)
 | 
			
		||||
 * @since 1.17.0
 | 
			
		||||
 */
 | 
			
		||||
public class BusinessException extends Exception {
 | 
			
		||||
  private static final long serialVersionUID = 6235833142062144336L;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ctor
 | 
			
		||||
   * 
 | 
			
		||||
   * @param message the error message
 | 
			
		||||
   * @since 1.17.0
 | 
			
		||||
   */
 | 
			
		||||
  public BusinessException(String message) {
 | 
			
		||||
    super(message);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,46 @@
 | 
			
		||||
/*
 | 
			
		||||
 * The MIT License (MIT)
 | 
			
		||||
 * 
 | 
			
		||||
 * Copyright (c) 2014-2016 Ilkka Seppälä
 | 
			
		||||
 * 
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 * 
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 * 
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package com.iluwatar.retry;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Performs some business operation.
 | 
			
		||||
 *
 | 
			
		||||
 * @author George Aristy (george.aristy@gmail.com)
 | 
			
		||||
 * @param <T> the return type
 | 
			
		||||
 * @since 1.17.0
 | 
			
		||||
 */
 | 
			
		||||
@FunctionalInterface
 | 
			
		||||
public interface BusinessOperation<T> {
 | 
			
		||||
  /**
 | 
			
		||||
   * Performs some business operation, returning a value {@code T} if successful, otherwise throwing
 | 
			
		||||
   * an exception if an error occurs.
 | 
			
		||||
   * 
 | 
			
		||||
   * @return the return value
 | 
			
		||||
   * @throws BusinessException if the operation fails. Implementations are allowed to throw more
 | 
			
		||||
   *     specific subtypes depending on the error conditions
 | 
			
		||||
   * @since 1.17.0
 | 
			
		||||
   */
 | 
			
		||||
  T perform() throws BusinessException;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,48 @@
 | 
			
		||||
/*
 | 
			
		||||
 * The MIT License (MIT)
 | 
			
		||||
 * 
 | 
			
		||||
 * Copyright (c) 2014-2016 Ilkka Seppälä
 | 
			
		||||
 * 
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 * 
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 * 
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package com.iluwatar.retry;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Indicates that the customer was not found. 
 | 
			
		||||
 * <p>
 | 
			
		||||
 * The severity of this error is bounded by its context: was the search for the customer triggered
 | 
			
		||||
 * by an input from some end user, or were the search parameters pulled from your database?
 | 
			
		||||
 *
 | 
			
		||||
 * @author George Aristy (george.aristy@gmail.com)
 | 
			
		||||
 * @since 1.17.0
 | 
			
		||||
 */
 | 
			
		||||
public final class CustomerNotFoundException extends BusinessException {
 | 
			
		||||
  private static final long serialVersionUID = -6972888602621778664L;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ctor.
 | 
			
		||||
   * 
 | 
			
		||||
   * @param message the error message
 | 
			
		||||
   * @since 1.17.0
 | 
			
		||||
   */
 | 
			
		||||
  public CustomerNotFoundException(String message) {
 | 
			
		||||
    super(message);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,45 @@
 | 
			
		||||
/*
 | 
			
		||||
 * The MIT License (MIT)
 | 
			
		||||
 * 
 | 
			
		||||
 * Copyright (c) 2014-2016 Ilkka Seppälä
 | 
			
		||||
 * 
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 * 
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 * 
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package com.iluwatar.retry;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Catastrophic error indicating that we have lost connection to our database.
 | 
			
		||||
 *
 | 
			
		||||
 * @author George Aristy (george.aristy@gmail.com)
 | 
			
		||||
 * @since 1.17.0
 | 
			
		||||
 */
 | 
			
		||||
public final class DatabaseNotAvailableException extends BusinessException {
 | 
			
		||||
  private static final long serialVersionUID = -3750769625095997799L;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ctor.
 | 
			
		||||
   * 
 | 
			
		||||
   * @param message the error message
 | 
			
		||||
   * @since 1.17.0
 | 
			
		||||
   */
 | 
			
		||||
  public DatabaseNotAvailableException(String message) {
 | 
			
		||||
    super(message);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										65
									
								
								retry/src/main/java/com/iluwatar/retry/FindCustomer.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								retry/src/main/java/com/iluwatar/retry/FindCustomer.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,65 @@
 | 
			
		||||
/*
 | 
			
		||||
 * The MIT License (MIT)
 | 
			
		||||
 * 
 | 
			
		||||
 * Copyright (c) 2014-2016 Ilkka Seppälä
 | 
			
		||||
 * 
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 * 
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 * 
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package com.iluwatar.retry;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayDeque;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.Deque;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Finds a customer, returning its ID from our records.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * This is an imaginary operation that, for some imagined input, returns the ID for a customer. 
 | 
			
		||||
 * However, this is a "flaky" operation that is supposed to fail intermittently, but for the 
 | 
			
		||||
 * purposes of this example it fails in a programmed way depending on the constructor parameters.
 | 
			
		||||
 *
 | 
			
		||||
 * @author George Aristy (george.aristy@gmail.com)
 | 
			
		||||
 * @since 1.17.0
 | 
			
		||||
 */
 | 
			
		||||
public final class FindCustomer implements BusinessOperation<String> {
 | 
			
		||||
  private final String customerId;
 | 
			
		||||
  private final Deque<BusinessException> errors;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ctor.
 | 
			
		||||
   * 
 | 
			
		||||
   * @param customerId the final result of the remote operation
 | 
			
		||||
   * @param errors the errors to throw before returning {@code customerId}
 | 
			
		||||
   * @since 1.17.0
 | 
			
		||||
   */
 | 
			
		||||
  public FindCustomer(String customerId, BusinessException... errors) {
 | 
			
		||||
    this.customerId = customerId;
 | 
			
		||||
    this.errors = new ArrayDeque<>(Arrays.asList(errors));
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  @Override
 | 
			
		||||
  public String perform() throws BusinessException {
 | 
			
		||||
    if (!this.errors.isEmpty()) {
 | 
			
		||||
      throw this.errors.pop();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return this.customerId;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										114
									
								
								retry/src/main/java/com/iluwatar/retry/Retry.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								retry/src/main/java/com/iluwatar/retry/Retry.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,114 @@
 | 
			
		||||
/*
 | 
			
		||||
 * The MIT License (MIT)
 | 
			
		||||
 * 
 | 
			
		||||
 * Copyright (c) 2014-2016 Ilkka Seppälä
 | 
			
		||||
 * 
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 * 
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 * 
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package com.iluwatar.retry;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.concurrent.atomic.AtomicInteger;
 | 
			
		||||
import java.util.function.Predicate;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Decorates {@link BusinessOperation business operation} with "retry" capabilities.
 | 
			
		||||
 *
 | 
			
		||||
 * @author George Aristy (george.aristy@gmail.com)
 | 
			
		||||
 * @param <T> the remote op's return type
 | 
			
		||||
 * @since 1.17.0
 | 
			
		||||
 */
 | 
			
		||||
public final class Retry<T> implements BusinessOperation<T> {
 | 
			
		||||
  private final BusinessOperation<T> op;
 | 
			
		||||
  private final int maxAttempts;
 | 
			
		||||
  private final long delay;
 | 
			
		||||
  private final AtomicInteger attempts;
 | 
			
		||||
  private final Predicate<Exception> test;
 | 
			
		||||
  private final List<Exception> errors;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ctor.
 | 
			
		||||
   * 
 | 
			
		||||
   * @param op the {@link BusinessOperation} to retry
 | 
			
		||||
   * @param maxAttempts number of times to retry
 | 
			
		||||
   * @param delay delay (in milliseconds) between attempts
 | 
			
		||||
   * @param ignoreTests tests to check whether the remote exception can be ignored. No exceptions
 | 
			
		||||
   *     will be ignored if no tests are given
 | 
			
		||||
   * @since 1.17.0
 | 
			
		||||
   */
 | 
			
		||||
  @SafeVarargs
 | 
			
		||||
  public Retry(
 | 
			
		||||
      BusinessOperation<T> op, 
 | 
			
		||||
      int maxAttempts, 
 | 
			
		||||
      long delay, 
 | 
			
		||||
      Predicate<Exception>... ignoreTests
 | 
			
		||||
  ) {
 | 
			
		||||
    this.op = op;
 | 
			
		||||
    this.maxAttempts = maxAttempts;
 | 
			
		||||
    this.delay = delay;
 | 
			
		||||
    this.attempts = new AtomicInteger();
 | 
			
		||||
    this.test = Arrays.stream(ignoreTests).reduce(Predicate::or).orElse(e -> false);
 | 
			
		||||
    this.errors = new ArrayList<>();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The errors encountered while retrying, in the encounter order.
 | 
			
		||||
   * 
 | 
			
		||||
   * @return the errors encountered while retrying
 | 
			
		||||
   * @since 1.17.0
 | 
			
		||||
   */
 | 
			
		||||
  public List<Exception> errors() {
 | 
			
		||||
    return Collections.unmodifiableList(this.errors);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The number of retries performed.
 | 
			
		||||
   * 
 | 
			
		||||
   * @return the number of retries performed
 | 
			
		||||
   * @since 1.17.0
 | 
			
		||||
   */
 | 
			
		||||
  public int attempts() {
 | 
			
		||||
    return this.attempts.intValue();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public T perform() throws BusinessException {
 | 
			
		||||
    do {
 | 
			
		||||
      try {
 | 
			
		||||
        return this.op.perform();
 | 
			
		||||
      } catch (BusinessException e) {
 | 
			
		||||
        this.errors.add(e);
 | 
			
		||||
        
 | 
			
		||||
        if (this.attempts.incrementAndGet() >= this.maxAttempts || !this.test.test(e)) {
 | 
			
		||||
          throw e;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
          Thread.sleep(this.delay);
 | 
			
		||||
        } catch (InterruptedException f) {
 | 
			
		||||
          //ignore
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    } while (true);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										91
									
								
								retry/src/test/java/com/iluwatar/retry/FindCustomerTest.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								retry/src/test/java/com/iluwatar/retry/FindCustomerTest.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,91 @@
 | 
			
		||||
/*
 | 
			
		||||
 * The MIT License (MIT)
 | 
			
		||||
 * 
 | 
			
		||||
 * Copyright (c) 2014-2016 Ilkka Seppälä
 | 
			
		||||
 * 
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 * 
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 * 
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package com.iluwatar.retry;
 | 
			
		||||
 | 
			
		||||
import static org.hamcrest.CoreMatchers.is;
 | 
			
		||||
import static org.junit.Assert.*;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Unit tests for {@link FindCustomer}.
 | 
			
		||||
 *
 | 
			
		||||
 * @author George Aristy (george.aristy@gmail.com)
 | 
			
		||||
 * @since 1.17.0
 | 
			
		||||
 */
 | 
			
		||||
public class FindCustomerTest {
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the given result with no exceptions.
 | 
			
		||||
   * 
 | 
			
		||||
   * @since 1.17.0
 | 
			
		||||
   */
 | 
			
		||||
  @Test
 | 
			
		||||
  public void noExceptions() throws Exception {
 | 
			
		||||
    assertThat(
 | 
			
		||||
        new FindCustomer("123").perform(),
 | 
			
		||||
        is("123")
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Throws the given exception.
 | 
			
		||||
   * 
 | 
			
		||||
   * @throws Exception the expected exception
 | 
			
		||||
   * @since 1.17.0
 | 
			
		||||
   */
 | 
			
		||||
  @Test(expected = BusinessException.class)
 | 
			
		||||
  public void oneException() throws Exception {
 | 
			
		||||
    new FindCustomer("123", new BusinessException("test")).perform();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Should first throw the given exceptions, then return the given result.
 | 
			
		||||
   * 
 | 
			
		||||
   * @throws Exception not an expected exception
 | 
			
		||||
   * @since 1.17.0
 | 
			
		||||
   */
 | 
			
		||||
  @Test
 | 
			
		||||
  public void resultAfterExceptions() throws Exception {
 | 
			
		||||
    final BusinessOperation<String> op = new FindCustomer(
 | 
			
		||||
        "123", 
 | 
			
		||||
        new CustomerNotFoundException("not found"),
 | 
			
		||||
        new DatabaseNotAvailableException("not available")
 | 
			
		||||
    );
 | 
			
		||||
    try {
 | 
			
		||||
      op.perform();
 | 
			
		||||
    } catch (CustomerNotFoundException e) {
 | 
			
		||||
      //ignore
 | 
			
		||||
    }
 | 
			
		||||
    try {
 | 
			
		||||
      op.perform();
 | 
			
		||||
    } catch (DatabaseNotAvailableException e) {
 | 
			
		||||
      //ignore
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    assertThat(
 | 
			
		||||
        op.perform(),
 | 
			
		||||
        is("123")
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										117
									
								
								retry/src/test/java/com/iluwatar/retry/RetryTest.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								retry/src/test/java/com/iluwatar/retry/RetryTest.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,117 @@
 | 
			
		||||
/*
 | 
			
		||||
 * The MIT License (MIT)
 | 
			
		||||
 * 
 | 
			
		||||
 * Copyright (c) 2014-2016 Ilkka Seppälä
 | 
			
		||||
 * 
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 * 
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 * 
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package com.iluwatar.retry;
 | 
			
		||||
 | 
			
		||||
import static org.hamcrest.CoreMatchers.hasItem;
 | 
			
		||||
import static org.hamcrest.CoreMatchers.is;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import static org.junit.Assert.*;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Unit tests for {@link Retry}.
 | 
			
		||||
 *
 | 
			
		||||
 * @author George Aristy (george.aristy@gmail.com)
 | 
			
		||||
 * @since 1.17.0
 | 
			
		||||
 */
 | 
			
		||||
public class RetryTest {
 | 
			
		||||
  /**
 | 
			
		||||
   * Should contain all errors thrown.
 | 
			
		||||
   * 
 | 
			
		||||
   * @since 1.17.0
 | 
			
		||||
   */
 | 
			
		||||
  @Test
 | 
			
		||||
  public void errors() throws Exception {
 | 
			
		||||
    final BusinessException e = new BusinessException("unhandled");
 | 
			
		||||
    final Retry<String> retry = new Retry<>(
 | 
			
		||||
        () -> { throw e; },
 | 
			
		||||
        2,
 | 
			
		||||
        0
 | 
			
		||||
    );
 | 
			
		||||
    try {
 | 
			
		||||
      retry.perform();
 | 
			
		||||
    } catch (BusinessException ex) {
 | 
			
		||||
      //ignore
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    assertThat(
 | 
			
		||||
        retry.errors(),
 | 
			
		||||
        hasItem(e)
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * No exceptions will be ignored, hence final number of attempts should be 1 even if we're asking
 | 
			
		||||
   * it to attempt twice.
 | 
			
		||||
   * 
 | 
			
		||||
   * @since 1.17.0
 | 
			
		||||
   */
 | 
			
		||||
  @Test
 | 
			
		||||
  public void attempts() {
 | 
			
		||||
    final BusinessException e = new BusinessException("unhandled");
 | 
			
		||||
    final Retry<String> retry = new Retry<>(
 | 
			
		||||
        () -> { throw e; },
 | 
			
		||||
        2,
 | 
			
		||||
        0
 | 
			
		||||
    );
 | 
			
		||||
    try {
 | 
			
		||||
      retry.perform();
 | 
			
		||||
    } catch (BusinessException ex) {
 | 
			
		||||
      //ignore
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    assertThat(
 | 
			
		||||
        retry.attempts(),
 | 
			
		||||
        is(1)
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Final number of attempts should be equal to the number of attempts asked because we are 
 | 
			
		||||
   * asking it to ignore the exception that will be thrown.
 | 
			
		||||
   * 
 | 
			
		||||
   * @since 1.17.0
 | 
			
		||||
   */
 | 
			
		||||
  @Test
 | 
			
		||||
  public void ignore() throws Exception {
 | 
			
		||||
    final BusinessException e = new CustomerNotFoundException("customer not found");
 | 
			
		||||
    final Retry<String> retry = new Retry<>(
 | 
			
		||||
        () -> { throw e; },
 | 
			
		||||
        2,
 | 
			
		||||
        0,
 | 
			
		||||
        ex -> CustomerNotFoundException.class.isAssignableFrom(ex.getClass())
 | 
			
		||||
    );
 | 
			
		||||
    try {
 | 
			
		||||
      retry.perform();
 | 
			
		||||
    } catch (BusinessException ex) {
 | 
			
		||||
      //ignore
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    assertThat(
 | 
			
		||||
        retry.attempts(),
 | 
			
		||||
        is(2)
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user