Skip to content
This repository has been archived by the owner on May 11, 2021. It is now read-only.

Commit

Permalink
[#13] Automatic retry of selected calls using asyn-retry library
Browse files Browse the repository at this point in the history
  • Loading branch information
nurkiewicz committed Nov 26, 2014
1 parent 6491abe commit 67154ad
Show file tree
Hide file tree
Showing 18 changed files with 229 additions and 64 deletions.
1 change: 1 addition & 0 deletions micro-infra-spring-base/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ dependencies {
compile "org.aspectj:aspectjrt:$aspectjVersion"
compile "com.codahale.metrics:metrics-core:$metricsVersion"
compile "com.codahale.metrics:metrics-graphite:$metricsVersion"
compile 'com.nurkiewicz.asyncretry:asyncretry-jdk7:0.0.5'

runtime "org.aspectj:aspectjweaver:$aspectjVersion"

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.ofg.infrastructure.web.resttemplate.fluent

import com.nurkiewicz.asyncretry.AsyncRetryExecutor
import com.nurkiewicz.asyncretry.RetryExecutor
import com.ofg.infrastructure.web.resttemplate.fluent.common.response.receive.PredefinedHttpHeaders
import com.ofg.infrastructure.web.resttemplate.fluent.delete.DeleteMethod
import com.ofg.infrastructure.web.resttemplate.fluent.delete.DeleteMethodBuilder
Expand All @@ -16,6 +18,8 @@ import com.ofg.infrastructure.web.resttemplate.fluent.put.PutMethodBuilder
import groovy.transform.CompileStatic
import org.springframework.web.client.RestOperations

import java.util.concurrent.Executors

import static com.ofg.infrastructure.web.resttemplate.fluent.common.response.receive.PredefinedHttpHeaders.NO_PREDEFINED_HEADERS

/**
Expand All @@ -40,6 +44,7 @@ class HttpMethodBuilder {
*/
private final String serviceUrl
private final PredefinedHttpHeaders predefinedHeaders
private RetryExecutor retryExecutor = SyncRetryExecutor.INSTANCE

HttpMethodBuilder(RestOperations restOperations) {
this('', restOperations, NO_PREDEFINED_HEADERS)
Expand All @@ -51,28 +56,33 @@ class HttpMethodBuilder {
this.serviceUrl = serviceUrl
}

HttpMethodBuilder retry(AsyncRetryExecutor retryExecutor) {
this.retryExecutor = retryExecutor
return this
}

DeleteMethod delete() {
return new DeleteMethodBuilder(serviceUrl, restOperations, predefinedHeaders)
return new DeleteMethodBuilder(serviceUrl, restOperations, predefinedHeaders, retryExecutor)
}

GetMethod get() {
return new GetMethodBuilder(serviceUrl, restOperations, predefinedHeaders)
return new GetMethodBuilder(serviceUrl, restOperations, predefinedHeaders, retryExecutor)
}

HeadMethod head() {
return new HeadMethodBuilder(serviceUrl, restOperations, predefinedHeaders)
return new HeadMethodBuilder(serviceUrl, restOperations, predefinedHeaders, retryExecutor)
}

OptionsMethod options() {
return new OptionsMethodBuilder(serviceUrl, restOperations, predefinedHeaders)
return new OptionsMethodBuilder(serviceUrl, restOperations, predefinedHeaders, retryExecutor)
}

PostMethod post() {
return new PostMethodBuilder(serviceUrl, restOperations, predefinedHeaders)
return new PostMethodBuilder(serviceUrl, restOperations, predefinedHeaders, retryExecutor)
}

PutMethod put() {
return new PutMethodBuilder(serviceUrl, restOperations, predefinedHeaders)
return new PutMethodBuilder(serviceUrl, restOperations, predefinedHeaders, retryExecutor)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.ofg.infrastructure.web.resttemplate.fluent

import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
import com.nurkiewicz.asyncretry.AsyncRetryContext
import com.nurkiewicz.asyncretry.RetryExecutor
import com.nurkiewicz.asyncretry.function.RetryCallable
import com.nurkiewicz.asyncretry.function.RetryRunnable
import com.nurkiewicz.asyncretry.policy.RetryPolicy

import java.util.concurrent.Callable

/**
* TODO Move to async-retry library itself
*/
class SyncRetryExecutor implements RetryExecutor {

private static final AsyncRetryContext RETRY_CONTEXT = new AsyncRetryContext(RetryPolicy.DEFAULT)

private SyncRetryExecutor() {
}

public static SyncRetryExecutor INSTANCE = new SyncRetryExecutor()

@Override
ListenableFuture<Void> doWithRetry(RetryRunnable action) {
action.run(RETRY_CONTEXT)
return Futures.immediateFuture(null)
}

@Override
def <V> ListenableFuture<V> getWithRetry(Callable<V> task) {
V result = task.call()
return Futures.immediateFuture(result)
}

@Override
def <V> ListenableFuture<V> getWithRetry(RetryCallable<V> task) {
V result = task.call(RETRY_CONTEXT)
return Futures.immediateFuture(result)
}

@Override
def <V> ListenableFuture<V> getFutureWithRetry(RetryCallable<ListenableFuture<V>> task) {
return Futures.immediateFuture(task.call(RETRY_CONTEXT).get())
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.ofg.infrastructure.web.resttemplate.fluent.common.response.executor

import com.google.common.util.concurrent.ListenableFuture
import com.nurkiewicz.asyncretry.RetryExecutor
import groovy.transform.TypeChecked
import org.springframework.http.HttpMethod as SpringHttpMethod
import org.springframework.http.ResponseEntity
Expand Down Expand Up @@ -29,31 +31,53 @@ abstract class ResponseTypeRelatedRequestsExecutor<T> {
protected final RestOperations restOperations
protected final Map params
protected final Class<T> responseType
protected final RetryExecutor retryExecutor

ResponseTypeRelatedRequestsExecutor(Map params, RestOperations restOperations, Class<T> responseType) {
ResponseTypeRelatedRequestsExecutor(Map params, RestOperations restOperations, RetryExecutor retryExecutor, Class<T> responseType) {
this.restOperations = restOperations
this.params = params
this.responseType = responseType
this.retryExecutor = retryExecutor
}

protected abstract SpringHttpMethod getHttpMethod()

ResponseEntity<T> exchange() {
if (params.url) {
return restOperations.exchange(
new URI(appendPathToHost(params.host as String, params.url as URI)),
httpMethod,
getHttpEntityFrom(params),
responseType)
return exchangeAsync().get()
}

ListenableFuture<ResponseEntity<T>> exchangeAsync() {
if (params.url) {
return callUrlWithRetry()
} else if (params.urlTemplate) {
return callUrlTemplateWithRetry()
}
throw new InvalidHttpMethodParametersException(params)
}

private ListenableFuture<ResponseEntity<T>> callUrlTemplateWithRetry() {
return runWithRetry {
return restOperations.exchange(
appendPathToHost(params.host as String, params.urlTemplate as String),
httpMethod,
getHttpEntityFrom(params),
responseType,
params.urlVariablesArray as Object[] ?: params.urlVariablesMap as Map<String, ?>)
}
throw new InvalidHttpMethodParametersException(params)
}

private ListenableFuture<ResponseEntity<T>> callUrlWithRetry() {
return runWithRetry {
return restOperations.exchange(
new URI(appendPathToHost(params.host as String, params.url as URI)),
httpMethod,
getHttpEntityFrom(params),
responseType)
}
}

private ListenableFuture<ResponseEntity<T>> runWithRetry(Closure block) {
return retryExecutor.getWithRetry(block)
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
package com.ofg.infrastructure.web.resttemplate.fluent.common.response.receive

import com.google.common.util.concurrent.ListenableFuture


/**
* Interface that defines what is the type of the received response.
* It will return an object of provided class.
*/
interface ObjectReceiving {
abstract class ObjectReceiving {

public <T> T ofType(Class<T> responseType)
public <T> T ofType(Class<T> responseType) {
return ofTypeAsync(responseType).get()
}

public abstract <T> ListenableFuture<T> ofTypeAsync(Class<T> responseType)
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package com.ofg.infrastructure.web.resttemplate.fluent.common.response.receive

import com.google.common.util.concurrent.ListenableFuture
import org.springframework.http.ResponseEntity

/**
* Interface that defines what is the type of the received response.
* It will return a {@link ResponseEntity} of the provided class.
*/
interface ResponseEntityReceiving {
public <T> ResponseEntity<T> ofType(Class<T> responseType)
abstract class ResponseEntityReceiving {
public abstract <T> ListenableFuture<ResponseEntity<T>> ofTypeAsync(Class<T> responseType)
public <T> ResponseEntity<T> ofType(Class<T> responseType) {
return ofTypeAsync(responseType).get()
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
package com.ofg.infrastructure.web.resttemplate.fluent.delete

import com.nurkiewicz.asyncretry.RetryExecutor
import com.ofg.infrastructure.web.resttemplate.fluent.common.response.executor.ResponseTypeRelatedRequestsExecutor
import groovy.transform.CompileStatic
import org.springframework.http.HttpMethod
Expand All @@ -11,8 +13,8 @@ import static org.springframework.http.HttpMethod.DELETE
@CompileStatic
class DeleteExecuteForResponseTypeRelated extends ResponseTypeRelatedRequestsExecutor<Object> {

DeleteExecuteForResponseTypeRelated(Map params, RestOperations restOperations) {
super(params, restOperations, Object)
DeleteExecuteForResponseTypeRelated(Map params, RestOperations restOperations, RetryExecutor retryExecutor) {
super(params, restOperations, retryExecutor, Object)
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.ofg.infrastructure.web.resttemplate.fluent.delete

import com.nurkiewicz.asyncretry.RetryExecutor
import com.ofg.infrastructure.web.resttemplate.fluent.SyncRetryExecutor
import com.ofg.infrastructure.web.resttemplate.fluent.common.response.receive.BodylessWithHeaders
import com.ofg.infrastructure.web.resttemplate.fluent.common.response.receive.HeadersHaving
import com.ofg.infrastructure.web.resttemplate.fluent.common.response.receive.PredefinedHttpHeaders
Expand All @@ -20,16 +22,18 @@ class DeleteMethodBuilder implements DeleteMethod, UrlParameterizableDeleteMetho

private final Map params = [:]
private final RestOperations restOperations
private final RetryExecutor retryExecutor
@Delegate private final BodylessWithHeaders<ResponseReceivingDeleteMethod> withHeaders

DeleteMethodBuilder(String host, RestOperations restOperations, PredefinedHttpHeaders predefinedHeaders) {
DeleteMethodBuilder(String host, RestOperations restOperations, PredefinedHttpHeaders predefinedHeaders, RetryExecutor retryExecutor) {
this.restOperations = restOperations
params.host = host
withHeaders = new BodylessWithHeaders<ResponseReceivingDeleteMethod>(this, params, predefinedHeaders)
this.retryExecutor = retryExecutor
}

DeleteMethodBuilder(RestOperations restOperations) {
this(EMPTY_HOST, restOperations, NO_PREDEFINED_HEADERS)
this(EMPTY_HOST, restOperations, NO_PREDEFINED_HEADERS, SyncRetryExecutor.INSTANCE)
}

@Override
Expand Down Expand Up @@ -70,7 +74,7 @@ class DeleteMethodBuilder implements DeleteMethod, UrlParameterizableDeleteMetho

@Override
ResponseEntity aResponseEntity() {
return new DeleteExecuteForResponseTypeRelated(params, restOperations).exchange()
return new DeleteExecuteForResponseTypeRelated(params, restOperations, retryExecutor).exchange()
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
package com.ofg.infrastructure.web.resttemplate.fluent.get

import com.nurkiewicz.asyncretry.RetryExecutor
import com.ofg.infrastructure.web.resttemplate.fluent.common.response.executor.ResponseTypeRelatedRequestsExecutor
import groovy.transform.CompileStatic
import org.springframework.http.HttpMethod
Expand All @@ -7,12 +9,13 @@ import org.springframework.web.client.RestOperations
import static org.springframework.http.HttpMethod.GET
/**
* Implementation of method execution for the {@link HttpMethod#GET} method.
* TODO Does this have to be a subclass?
*/
@CompileStatic
class GetExecuteForResponseTypeRelated<T> extends ResponseTypeRelatedRequestsExecutor<T> {

GetExecuteForResponseTypeRelated(Map params, RestOperations restOperations, Class<T> responseType) {
super(params, restOperations, responseType)
GetExecuteForResponseTypeRelated(Map params, RestOperations restOperations, RetryExecutor retryExecutor, Class<T> responseType) {
super(params, restOperations, retryExecutor, responseType)
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package com.ofg.infrastructure.web.resttemplate.fluent.get

import com.google.common.base.Function
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
import com.nurkiewicz.asyncretry.RetryExecutor
import com.ofg.infrastructure.web.resttemplate.fluent.SyncRetryExecutor
import com.ofg.infrastructure.web.resttemplate.fluent.common.response.receive.BodyContainingWithHeaders
import com.ofg.infrastructure.web.resttemplate.fluent.common.response.receive.HeadersHaving
import com.ofg.infrastructure.web.resttemplate.fluent.common.response.receive.ObjectReceiving
Expand All @@ -22,16 +27,18 @@ class GetMethodBuilder implements GetMethod, UrlParameterizableGetMethod, Respon

private final Map params = [:]
private final RestOperations restOperations
private final RetryExecutor retryExecutor
@Delegate private final BodyContainingWithHeaders withHeaders

GetMethodBuilder(RestOperations restOperations) {
this(EMPTY_HOST, restOperations, NO_PREDEFINED_HEADERS)
this(EMPTY_HOST, restOperations, NO_PREDEFINED_HEADERS, SyncRetryExecutor.INSTANCE)
}

GetMethodBuilder(String host, RestOperations restOperations, PredefinedHttpHeaders predefinedHeaders) {
GetMethodBuilder(String host, RestOperations restOperations, PredefinedHttpHeaders predefinedHeaders, RetryExecutor retryExecutor) {
this.restOperations = restOperations
params.host = host
withHeaders = new BodyContainingWithHeaders(this, params, predefinedHeaders)
this.retryExecutor = retryExecutor
}

@Override
Expand Down Expand Up @@ -74,8 +81,12 @@ class GetMethodBuilder implements GetMethod, UrlParameterizableGetMethod, Respon
ObjectReceiving anObject() {
return new ObjectReceiving() {
@Override
public <T> T ofType(Class<T> responseType) {
return new GetExecuteForResponseTypeRelated<T>(params, restOperations, responseType).exchange()?.body
public <T> ListenableFuture<T> ofTypeAsync(Class<T> responseType) {
GetExecuteForResponseTypeRelated<T> get = get(responseType)
ListenableFuture<ResponseEntity<T>> future = get.exchangeAsync()
return Futures.transform(future, { ResponseEntity input ->
return input?.body
} as Function)
}
}
}
Expand All @@ -84,12 +95,16 @@ class GetMethodBuilder implements GetMethod, UrlParameterizableGetMethod, Respon
ResponseEntityReceiving aResponseEntity() {
return new ResponseEntityReceiving() {
@Override
public <T> ResponseEntity<T> ofType(Class<T> responseType) {
return new GetExecuteForResponseTypeRelated<T>(params, restOperations, responseType).exchange()
public <T> ListenableFuture<ResponseEntity<T>> ofTypeAsync(Class<T> responseType) {
return get(responseType).exchangeAsync()
}
}
}

private GetExecuteForResponseTypeRelated get(Class responseType) {
return new GetExecuteForResponseTypeRelated(params, restOperations, retryExecutor, responseType)
}

@Override
void ignoringResponse() {
aResponseEntity().ofType(Object)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
package com.ofg.infrastructure.web.resttemplate.fluent.head

import com.nurkiewicz.asyncretry.RetryExecutor
import com.ofg.infrastructure.web.resttemplate.fluent.common.response.executor.ResponseTypeRelatedRequestsExecutor
import groovy.transform.CompileStatic
import org.springframework.http.HttpMethod
Expand All @@ -11,8 +13,8 @@ import static org.springframework.http.HttpMethod.HEAD
@CompileStatic
class HeadExecuteForResponseTypeRelated extends ResponseTypeRelatedRequestsExecutor<Object> {

HeadExecuteForResponseTypeRelated(Map params, RestOperations restOperations) {
super(params, restOperations, Object)
HeadExecuteForResponseTypeRelated(Map params, RestOperations restOperations, RetryExecutor retryExecutor) {
super(params, restOperations, retryExecutor, Object)
}

@Override
Expand Down
Loading

0 comments on commit 67154ad

Please sign in to comment.