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 Dec 4, 2014
1 parent 40f18bf commit 0c32d27
Show file tree
Hide file tree
Showing 32 changed files with 466 additions and 160 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.6'

runtime "org.aspectj:aspectjweaver:$aspectjVersion"

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

import com.nurkiewicz.asyncretry.AsyncRetryExecutor
import com.nurkiewicz.asyncretry.RetryExecutor
import com.nurkiewicz.asyncretry.SyncRetryExecutor
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 +19,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 +45,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 +57,33 @@ class HttpMethodBuilder {
this.serviceUrl = serviceUrl
}

HttpMethodBuilder retryUsing(RetryExecutor 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)
}

}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package com.ofg.infrastructure.web.resttemplate.fluent.common.response.executor

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 groovy.transform.TypeChecked
import org.springframework.http.HttpEntity
import org.springframework.http.HttpMethod
import org.springframework.http.ResponseEntity
import org.springframework.web.client.RestOperations

import static com.ofg.infrastructure.web.resttemplate.fluent.common.response.executor.HttpEntityUtils.getHttpEntityFrom
import static com.ofg.infrastructure.web.resttemplate.fluent.common.response.executor.UrlParsingUtils.appendPathToHost

/**
Expand All @@ -17,30 +21,26 @@ abstract class LocationFindingExecutor implements LocationReceiving {

protected final Map params = [:]
protected final RestOperations restOperations
protected final RetryExecutor retryExecutor
private final RestExecutor restExecutor

LocationFindingExecutor(RestOperations restOperations) {
LocationFindingExecutor(RestOperations restOperations, RetryExecutor retryExecutor) {
this.restOperations = restOperations
this.retryExecutor = retryExecutor
this.restExecutor = new RestExecutor<>(restOperations, retryExecutor)
}

protected abstract HttpMethod getHttpMethod()

@Override
URI forLocation() {
if (params.url) {
return getLocation(restOperations.exchange(
new URI(appendPathToHost(params.host as String, params.url as URI)),
httpMethod,
getHttpEntityFrom(params),
params.request.class))
} else if (params.urlTemplate) {
return getLocation(restOperations.exchange(
appendPathToHost(params.host as String, params.urlTemplate as String),
httpMethod,
getHttpEntityFrom(params),
params.request.class,
params.urlVariablesArray as Object[] ?: params.urlVariablesMap as Map<String, ?>))
}
throw new InvalidHttpMethodParametersException(params)
return getLocation(restExecutor.exchange(httpMethod, params, params.request.class))
}

@Override
ListenableFuture<URI> forLocationAsync() {
ListenableFuture<ResponseEntity> future = restExecutor.exchangeAsync(httpMethod, params, params.request.class)
return Futures.transform(future, {ResponseEntity entity -> getLocation(entity)} as Function)
}

private static URI getLocation(HttpEntity entity) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.ofg.infrastructure.web.resttemplate.fluent.common.response.executor

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


/**
* Interface for HttpMethods that can return location from Http headers.
* It's a helper interface since you can always retrieve location from the
Expand All @@ -8,5 +11,6 @@ package com.ofg.infrastructure.web.resttemplate.fluent.common.response.executor
interface LocationReceiving {

URI forLocation()
ListenableFuture<URI> forLocationAsync()

}
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
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
import org.springframework.web.client.RestOperations

import static com.ofg.infrastructure.web.resttemplate.fluent.common.response.executor.HttpEntityUtils.getHttpEntityFrom
import static com.ofg.infrastructure.web.resttemplate.fluent.common.response.executor.UrlParsingUtils.appendPathToHost

/**
* Abstraction over {@link RestOperations} that for a {@link ResponseTypeRelatedRequestsExecutor#getHttpMethod()}
* checks whether user passed an URL or a template. Basing on this we create an execute a request.
Expand All @@ -26,35 +25,25 @@ import static com.ofg.infrastructure.web.resttemplate.fluent.common.response.exe
@TypeChecked
abstract class ResponseTypeRelatedRequestsExecutor<T> {

protected final RestOperations restOperations
protected final RestExecutor<T> restExecutor
protected final Map params
protected final Class<T> responseType
private final Class<T> responseType

ResponseTypeRelatedRequestsExecutor(Map params, RestOperations restOperations, Class<T> responseType) {
this.restOperations = restOperations
ResponseTypeRelatedRequestsExecutor(Map params, RestOperations restOperations, RetryExecutor retryExecutor, Class<T> responseType) {
this.params = params
this.responseType = responseType
this.restExecutor = new RestExecutor(restOperations, 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)
} else if (params.urlTemplate) {
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)
return exchangeAsync().get()
}

ListenableFuture<ResponseEntity<T>> exchangeAsync() {
return restExecutor.exchangeAsync(httpMethod, params, responseType)
}

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
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.CompileStatic
import org.springframework.http.HttpEntity
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpMethod
import org.springframework.http.ResponseEntity
import org.springframework.web.client.RestOperations

import static com.ofg.infrastructure.web.resttemplate.fluent.common.response.executor.UrlParsingUtils.appendPathToHost

/**
* Utility class that extracts {@link HttpEntity} from the provided map of passed parameters
*/
@CompileStatic
final class RestExecutor<T> {
private final RestOperations restOperations
private final RetryExecutor retryExecutor

RestExecutor(RestOperations restOperations, RetryExecutor retryExecutor) {
this.restOperations = restOperations
this.retryExecutor = retryExecutor
}

ResponseEntity<T> exchange(HttpMethod httpMethod, Map params, Class<T> responseType) {
return exchangeAsync(httpMethod, params, responseType).get()
}

ListenableFuture<ResponseEntity<T>> exchangeAsync(HttpMethod httpMethod, Map params, Class<T> responseType) {
if (params.url) {
return callUrlWithRetry(httpMethod, params, responseType)
} else if (params.urlTemplate) {
return callUrlTemplateWithRetry(httpMethod, params, responseType)
}
throw new InvalidHttpMethodParametersException(params)
}

protected ListenableFuture<ResponseEntity<T>> callUrlTemplateWithRetry(HttpMethod httpMethod, Map params, Class<T> responseType) {
return retryExecutor.getWithRetry {
//TODO Correlation ID
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, ?>)
}
}

protected ListenableFuture<ResponseEntity<T>> callUrlWithRetry(HttpMethod httpMethod, Map params, Class<T> responseType) {
return retryExecutor.getWithRetry {
//TODO Correlation ID
restOperations.exchange(
new URI(appendPathToHost(params.host as String, params.url as URI)),
httpMethod,
getHttpEntityFrom(params),
responseType)
}
}

static HttpEntity<Object> getHttpEntityFrom(Map params) {
if (params.httpEntity) {
return params.httpEntity as HttpEntity
}
HttpHeaders headers = params.headers as HttpHeaders
HttpEntity<?> httpEntity = new HttpEntity<Object>(params.request, headers)
return httpEntity
}
}
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,8 +1,11 @@
package com.ofg.infrastructure.web.resttemplate.fluent.common.response.receive

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

interface ResponseIgnoring {
/**
* When you do not care about the received response for your HTTP request
*/
void ignoringResponse()
ListenableFuture<Void> ignoringResponseAsync()
}
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
Loading

0 comments on commit 0c32d27

Please sign in to comment.