From 0c32d279b1e08c92fa30e6df44b7319fd0351dc0 Mon Sep 17 00:00:00 2001 From: Tomasz Nurkiewicz Date: Wed, 26 Nov 2014 16:37:08 +0100 Subject: [PATCH] [#13] Automatic retry of selected calls using asyn-retry library --- micro-infra-spring-base/build.gradle | 1 + .../fluent/HttpMethodBuilder.groovy | 23 +++-- .../response/executor/HttpEntityUtils.groovy | 23 ----- .../executor/LocationFindingExecutor.groovy | 34 +++---- .../executor/LocationReceiving.groovy | 4 + ...ResponseTypeRelatedRequestsExecutor.groovy | 35 +++---- .../response/executor/RestExecutor.groovy | 71 ++++++++++++++ .../response/receive/ObjectReceiving.groovy | 11 ++- .../receive/ResponseEntityReceiving.groovy | 8 +- .../response/receive/ResponseIgnoring.groovy | 3 + ...DeleteExecuteForResponseTypeRelated.groovy | 6 +- .../fluent/delete/DeleteMethodBuilder.groovy | 28 +++++- .../ResponseReceivingDeleteMethod.groovy | 2 + .../GetExecuteForResponseTypeRelated.groovy | 7 +- .../fluent/get/GetMethodBuilder.groovy | 33 +++++-- .../HeadExecuteForResponseTypeRelated.groovy | 6 +- .../fluent/head/HeadMethodBuilder.groovy | 36 ++++++- .../head/ResponseReceivingHeadMethod.groovy | 4 + .../options/AllowHeaderReceiving.groovy | 3 + .../options/OptionsAllowHeaderExecutor.groovy | 36 ++++--- ...ptionsExecuteForResponseTypeRelated.groovy | 6 +- .../options/OptionsMethodBuilder.groovy | 32 ++++-- .../PostExecuteForResponseTypeRelated.groovy | 6 +- .../fluent/post/PostMethodBuilder.groovy | 33 +++++-- .../PutExecuteForResponseTypeRelated.groovy | 6 +- .../fluent/put/PutMethodBuilder.groovy | 30 ++++-- .../ServiceRestClientIntegrationSpec.groovy | 8 +- .../fluent/ServiceRestClientSpec.groovy | 98 +++++++++++++++++-- .../executor/HttpEntityUtilsSpec.groovy | 11 +-- .../OptionsHttpMethodBuilderSpec.groovy | 1 - .../put/PutHttpMethodBuilderSpec.groovy | 6 +- .../src/test/resources/logback.groovy | 15 +++ 32 files changed, 466 insertions(+), 160 deletions(-) delete mode 100644 micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/executor/HttpEntityUtils.groovy create mode 100644 micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/executor/RestExecutor.groovy create mode 100644 micro-infra-spring-base/src/test/resources/logback.groovy diff --git a/micro-infra-spring-base/build.gradle b/micro-infra-spring-base/build.gradle index 0395d488..99a6e873 100644 --- a/micro-infra-spring-base/build.gradle +++ b/micro-infra-spring-base/build.gradle @@ -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" diff --git a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/HttpMethodBuilder.groovy b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/HttpMethodBuilder.groovy index 8b4c7b5b..2802f2a8 100644 --- a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/HttpMethodBuilder.groovy +++ b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/HttpMethodBuilder.groovy @@ -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 @@ -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 /** @@ -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) @@ -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) } } diff --git a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/executor/HttpEntityUtils.groovy b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/executor/HttpEntityUtils.groovy deleted file mode 100644 index ab8cc7a0..00000000 --- a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/executor/HttpEntityUtils.groovy +++ /dev/null @@ -1,23 +0,0 @@ -package com.ofg.infrastructure.web.resttemplate.fluent.common.response.executor -import groovy.transform.CompileStatic -import org.springframework.http.HttpEntity -import org.springframework.http.HttpHeaders - -/** - * Utility class that extracts {@link HttpEntity} from the provided map of passed parameters - */ -@CompileStatic -final class HttpEntityUtils { - private HttpEntityUtils() { - throw new UnsupportedOperationException("Can't instantiate a utility class") - } - - public static HttpEntity getHttpEntityFrom(Map params) { - if (params.httpEntity) { - return params.httpEntity as HttpEntity - } - HttpHeaders headers = params.headers as HttpHeaders - HttpEntity httpEntity = new HttpEntity(params.request, headers) - return httpEntity - } -} diff --git a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/executor/LocationFindingExecutor.groovy b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/executor/LocationFindingExecutor.groovy index 7a2ef9cb..0f07b570 100644 --- a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/executor/LocationFindingExecutor.groovy +++ b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/executor/LocationFindingExecutor.groovy @@ -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 /** @@ -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)) - } - throw new InvalidHttpMethodParametersException(params) + return getLocation(restExecutor.exchange(httpMethod, params, params.request.class)) + } + + @Override + ListenableFuture forLocationAsync() { + ListenableFuture future = restExecutor.exchangeAsync(httpMethod, params, params.request.class) + return Futures.transform(future, {ResponseEntity entity -> getLocation(entity)} as Function) } private static URI getLocation(HttpEntity entity) { diff --git a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/executor/LocationReceiving.groovy b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/executor/LocationReceiving.groovy index 9ac68766..620f9b22 100644 --- a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/executor/LocationReceiving.groovy +++ b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/executor/LocationReceiving.groovy @@ -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 @@ -8,5 +11,6 @@ package com.ofg.infrastructure.web.resttemplate.fluent.common.response.executor interface LocationReceiving { URI forLocation() + ListenableFuture forLocationAsync() } \ No newline at end of file diff --git a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/executor/ResponseTypeRelatedRequestsExecutor.groovy b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/executor/ResponseTypeRelatedRequestsExecutor.groovy index d7761fd4..a6ba0c7a 100644 --- a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/executor/ResponseTypeRelatedRequestsExecutor.groovy +++ b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/executor/ResponseTypeRelatedRequestsExecutor.groovy @@ -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. @@ -26,35 +25,25 @@ import static com.ofg.infrastructure.web.resttemplate.fluent.common.response.exe @TypeChecked abstract class ResponseTypeRelatedRequestsExecutor { - protected final RestOperations restOperations + protected final RestExecutor restExecutor protected final Map params - protected final Class responseType + private final Class responseType - ResponseTypeRelatedRequestsExecutor(Map params, RestOperations restOperations, Class responseType) { - this.restOperations = restOperations + ResponseTypeRelatedRequestsExecutor(Map params, RestOperations restOperations, RetryExecutor retryExecutor, Class responseType) { this.params = params this.responseType = responseType + this.restExecutor = new RestExecutor(restOperations, retryExecutor) } protected abstract SpringHttpMethod getHttpMethod() ResponseEntity 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) - } - throw new InvalidHttpMethodParametersException(params) + return exchangeAsync().get() + } + + ListenableFuture> exchangeAsync() { + return restExecutor.exchangeAsync(httpMethod, params, responseType) } - + } diff --git a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/executor/RestExecutor.groovy b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/executor/RestExecutor.groovy new file mode 100644 index 00000000..7a55d31b --- /dev/null +++ b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/executor/RestExecutor.groovy @@ -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 { + private final RestOperations restOperations + private final RetryExecutor retryExecutor + + RestExecutor(RestOperations restOperations, RetryExecutor retryExecutor) { + this.restOperations = restOperations + this.retryExecutor = retryExecutor + } + + ResponseEntity exchange(HttpMethod httpMethod, Map params, Class responseType) { + return exchangeAsync(httpMethod, params, responseType).get() + } + + ListenableFuture> exchangeAsync(HttpMethod httpMethod, Map params, Class responseType) { + if (params.url) { + return callUrlWithRetry(httpMethod, params, responseType) + } else if (params.urlTemplate) { + return callUrlTemplateWithRetry(httpMethod, params, responseType) + } + throw new InvalidHttpMethodParametersException(params) + } + + protected ListenableFuture> callUrlTemplateWithRetry(HttpMethod httpMethod, Map params, Class 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) + } + } + + protected ListenableFuture> callUrlWithRetry(HttpMethod httpMethod, Map params, Class 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 getHttpEntityFrom(Map params) { + if (params.httpEntity) { + return params.httpEntity as HttpEntity + } + HttpHeaders headers = params.headers as HttpHeaders + HttpEntity httpEntity = new HttpEntity(params.request, headers) + return httpEntity + } +} diff --git a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/receive/ObjectReceiving.groovy b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/receive/ObjectReceiving.groovy index 6b462810..1e7ef4b7 100644 --- a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/receive/ObjectReceiving.groovy +++ b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/receive/ObjectReceiving.groovy @@ -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 ofType(Class responseType) + public T ofType(Class responseType) { + return ofTypeAsync(responseType).get() + } + + public abstract ListenableFuture ofTypeAsync(Class responseType) } \ No newline at end of file diff --git a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/receive/ResponseEntityReceiving.groovy b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/receive/ResponseEntityReceiving.groovy index f94da559..0b5493dc 100644 --- a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/receive/ResponseEntityReceiving.groovy +++ b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/receive/ResponseEntityReceiving.groovy @@ -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 ResponseEntity ofType(Class responseType) +abstract class ResponseEntityReceiving { + public abstract ListenableFuture> ofTypeAsync(Class responseType) + public ResponseEntity ofType(Class responseType) { + return ofTypeAsync(responseType).get() + } } \ No newline at end of file diff --git a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/receive/ResponseIgnoring.groovy b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/receive/ResponseIgnoring.groovy index 6be5db8d..986d239c 100644 --- a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/receive/ResponseIgnoring.groovy +++ b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/receive/ResponseIgnoring.groovy @@ -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 ignoringResponseAsync() } \ No newline at end of file diff --git a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/delete/DeleteExecuteForResponseTypeRelated.groovy b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/delete/DeleteExecuteForResponseTypeRelated.groovy index b9fb1960..ed5ba8de 100644 --- a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/delete/DeleteExecuteForResponseTypeRelated.groovy +++ b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/delete/DeleteExecuteForResponseTypeRelated.groovy @@ -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 @@ -11,8 +13,8 @@ import static org.springframework.http.HttpMethod.DELETE @CompileStatic class DeleteExecuteForResponseTypeRelated extends ResponseTypeRelatedRequestsExecutor { - DeleteExecuteForResponseTypeRelated(Map params, RestOperations restOperations) { - super(params, restOperations, Object) + DeleteExecuteForResponseTypeRelated(Map params, RestOperations restOperations, RetryExecutor retryExecutor) { + super(params, restOperations, retryExecutor, Object) } @Override diff --git a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/delete/DeleteMethodBuilder.groovy b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/delete/DeleteMethodBuilder.groovy index bcff978b..f6c6c591 100644 --- a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/delete/DeleteMethodBuilder.groovy +++ b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/delete/DeleteMethodBuilder.groovy @@ -1,5 +1,10 @@ package com.ofg.infrastructure.web.resttemplate.fluent.delete +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.nurkiewicz.asyncretry.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 @@ -20,16 +25,18 @@ class DeleteMethodBuilder implements DeleteMethod, UrlParameterizableDeleteMetho private final Map params = [:] private final RestOperations restOperations + private final RetryExecutor retryExecutor @Delegate private final BodylessWithHeaders 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(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 @@ -70,7 +77,22 @@ class DeleteMethodBuilder implements DeleteMethod, UrlParameterizableDeleteMetho @Override ResponseEntity aResponseEntity() { - return new DeleteExecuteForResponseTypeRelated(params, restOperations).exchange() + return delete().exchange() + } + + @Override + ListenableFuture ignoringResponseAsync() { + ListenableFuture future = aResponseEntityAsync() + return Futures.transform(future, {null} as Function) + } + + @Override + ListenableFuture aResponseEntityAsync() { + return delete().exchangeAsync() + } + + private DeleteExecuteForResponseTypeRelated delete() { + return new DeleteExecuteForResponseTypeRelated(params, restOperations, retryExecutor) } @Override diff --git a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/delete/ResponseReceivingDeleteMethod.groovy b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/delete/ResponseReceivingDeleteMethod.groovy index f6580925..8eb25560 100644 --- a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/delete/ResponseReceivingDeleteMethod.groovy +++ b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/delete/ResponseReceivingDeleteMethod.groovy @@ -1,5 +1,6 @@ package com.ofg.infrastructure.web.resttemplate.fluent.delete +import com.google.common.util.concurrent.ListenableFuture import com.ofg.infrastructure.web.resttemplate.fluent.common.response.executor.Executable import com.ofg.infrastructure.web.resttemplate.fluent.common.response.receive.HeadersHaving import com.ofg.infrastructure.web.resttemplate.fluent.common.response.receive.HttpEntitySending @@ -15,5 +16,6 @@ interface ResponseReceivingDeleteMethod extends HttpEntitySending, ResponseIgnoring { ResponseEntity aResponseEntity() + ListenableFuture aResponseEntityAsync() } diff --git a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/get/GetExecuteForResponseTypeRelated.groovy b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/get/GetExecuteForResponseTypeRelated.groovy index 003ed314..991fb3bd 100644 --- a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/get/GetExecuteForResponseTypeRelated.groovy +++ b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/get/GetExecuteForResponseTypeRelated.groovy @@ -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 @@ -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 extends ResponseTypeRelatedRequestsExecutor { - GetExecuteForResponseTypeRelated(Map params, RestOperations restOperations, Class responseType) { - super(params, restOperations, responseType) + GetExecuteForResponseTypeRelated(Map params, RestOperations restOperations, RetryExecutor retryExecutor, Class responseType) { + super(params, restOperations, retryExecutor, responseType) } @Override diff --git a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/get/GetMethodBuilder.groovy b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/get/GetMethodBuilder.groovy index 784ea237..7ed42619 100644 --- a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/get/GetMethodBuilder.groovy +++ b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/get/GetMethodBuilder.groovy @@ -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.nurkiewicz.asyncretry.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 @@ -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 @@ -74,8 +81,12 @@ class GetMethodBuilder implements GetMethod, UrlParameterizableGetMethod, Respon ObjectReceiving anObject() { return new ObjectReceiving() { @Override - public T ofType(Class responseType) { - return new GetExecuteForResponseTypeRelated(params, restOperations, responseType).exchange()?.body + public ListenableFuture ofTypeAsync(Class responseType) { + GetExecuteForResponseTypeRelated get = get(responseType) + ListenableFuture> future = get.exchangeAsync() + return Futures.transform(future, { ResponseEntity input -> + return input?.body + } as Function) } } } @@ -84,15 +95,25 @@ class GetMethodBuilder implements GetMethod, UrlParameterizableGetMethod, Respon ResponseEntityReceiving aResponseEntity() { return new ResponseEntityReceiving() { @Override - public ResponseEntity ofType(Class responseType) { - return new GetExecuteForResponseTypeRelated(params, restOperations, responseType).exchange() + public ListenableFuture> ofTypeAsync(Class responseType) { + return get(responseType).exchangeAsync() } } } + private GetExecuteForResponseTypeRelated get(Class responseType) { + return new GetExecuteForResponseTypeRelated(params, restOperations, retryExecutor, responseType) + } + @Override void ignoringResponse() { aResponseEntity().ofType(Object) } + @Override + ListenableFuture ignoringResponseAsync() { + ListenableFuture> future = aResponseEntity().ofTypeAsync(Object) + return Futures.transform(future, {ResponseEntity r -> null as Void} as Function) + } + } diff --git a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/head/HeadExecuteForResponseTypeRelated.groovy b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/head/HeadExecuteForResponseTypeRelated.groovy index 8214354d..4600a867 100644 --- a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/head/HeadExecuteForResponseTypeRelated.groovy +++ b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/head/HeadExecuteForResponseTypeRelated.groovy @@ -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 @@ -11,8 +13,8 @@ import static org.springframework.http.HttpMethod.HEAD @CompileStatic class HeadExecuteForResponseTypeRelated extends ResponseTypeRelatedRequestsExecutor { - HeadExecuteForResponseTypeRelated(Map params, RestOperations restOperations) { - super(params, restOperations, Object) + HeadExecuteForResponseTypeRelated(Map params, RestOperations restOperations, RetryExecutor retryExecutor) { + super(params, restOperations, retryExecutor, Object) } @Override diff --git a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/head/HeadMethodBuilder.groovy b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/head/HeadMethodBuilder.groovy index 57a2d798..9dd4f1ae 100644 --- a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/head/HeadMethodBuilder.groovy +++ b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/head/HeadMethodBuilder.groovy @@ -1,5 +1,10 @@ package com.ofg.infrastructure.web.resttemplate.fluent.head +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.nurkiewicz.asyncretry.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 @@ -21,16 +26,18 @@ class HeadMethodBuilder implements HeadMethod, UrlParameterizableHeadMethod, Res private final Map params = [:] private final RestOperations restOperations + private final RetryExecutor retryExecutor @Delegate private final BodylessWithHeaders withHeaders - HeadMethodBuilder(String host, RestOperations restOperations, PredefinedHttpHeaders predefinedHeaders) { + HeadMethodBuilder(String host, RestOperations restOperations, PredefinedHttpHeaders predefinedHeaders, RetryExecutor retryExecutor) { this.restOperations = restOperations params.host = host withHeaders = new BodylessWithHeaders(this, params, predefinedHeaders) + this.retryExecutor = retryExecutor } HeadMethodBuilder(RestOperations restOperations) { - this(EMPTY_HOST, restOperations, NO_PREDEFINED_HEADERS) + this(EMPTY_HOST, restOperations, NO_PREDEFINED_HEADERS, SyncRetryExecutor.INSTANCE) } @Override @@ -71,12 +78,27 @@ class HeadMethodBuilder implements HeadMethod, UrlParameterizableHeadMethod, Res @Override ResponseEntity aResponseEntity() { - return new HeadExecuteForResponseTypeRelated(params, restOperations).exchange() + return head().exchange() + } + + @Override + ListenableFuture aResponseEntityAsync() { + return head().exchangeAsync() } @Override HttpHeaders httpHeaders() { - return new HeadExecuteForResponseTypeRelated(params, restOperations).exchange()?.headers + return head().exchange()?.headers + } + + @Override + ListenableFuture httpHeadersAsync() { + ListenableFuture future = aResponseEntityAsync() + return Futures.transform(future, {ResponseEntity re -> re?.headers} as Function) + } + + private HeadExecuteForResponseTypeRelated head() { + return new HeadExecuteForResponseTypeRelated(params, restOperations, retryExecutor) } @Override @@ -84,4 +106,10 @@ class HeadMethodBuilder implements HeadMethod, UrlParameterizableHeadMethod, Res aResponseEntity() } + ListenableFuture ignoringResponseAsync() { + ListenableFuture future = aResponseEntityAsync() + return Futures.transform(future, {ResponseEntity re -> null} as Function) + } + + } diff --git a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/head/ResponseReceivingHeadMethod.groovy b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/head/ResponseReceivingHeadMethod.groovy index c600092b..ab42ffe6 100644 --- a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/head/ResponseReceivingHeadMethod.groovy +++ b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/head/ResponseReceivingHeadMethod.groovy @@ -1,4 +1,6 @@ package com.ofg.infrastructure.web.resttemplate.fluent.head + +import com.google.common.util.concurrent.ListenableFuture import com.ofg.infrastructure.web.resttemplate.fluent.common.response.executor.Executable import com.ofg.infrastructure.web.resttemplate.fluent.common.response.receive.HeadersHaving import com.ofg.infrastructure.web.resttemplate.fluent.common.response.receive.HttpEntitySending @@ -15,6 +17,8 @@ interface ResponseReceivingHeadMethod extends HttpEntitySending, ResponseIgnoring { ResponseEntity aResponseEntity() + ListenableFuture aResponseEntityAsync() HttpHeaders httpHeaders() + ListenableFuture httpHeadersAsync() } diff --git a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/options/AllowHeaderReceiving.groovy b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/options/AllowHeaderReceiving.groovy index 7e098b24..2207ae47 100644 --- a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/options/AllowHeaderReceiving.groovy +++ b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/options/AllowHeaderReceiving.groovy @@ -1,5 +1,6 @@ package com.ofg.infrastructure.web.resttemplate.fluent.options +import com.google.common.util.concurrent.ListenableFuture import org.springframework.http.HttpMethod /** @@ -14,4 +15,6 @@ interface AllowHeaderReceiving { */ Set allow() + ListenableFuture> allowAsync() + } \ No newline at end of file diff --git a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/options/OptionsAllowHeaderExecutor.groovy b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/options/OptionsAllowHeaderExecutor.groovy index 13c75e47..71b31c2b 100644 --- a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/options/OptionsAllowHeaderExecutor.groovy +++ b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/options/OptionsAllowHeaderExecutor.groovy @@ -1,12 +1,18 @@ package com.ofg.infrastructure.web.resttemplate.fluent.options + +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.common.response.executor.InvalidHttpMethodParametersException +import com.ofg.infrastructure.web.resttemplate.fluent.common.response.executor.RestExecutor import groovy.transform.PackageScope import groovy.transform.TypeChecked 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.RestExecutor.getHttpEntityFrom import static org.springframework.http.HttpMethod.OPTIONS /** @@ -18,25 +24,25 @@ import static org.springframework.http.HttpMethod.OPTIONS class OptionsAllowHeaderExecutor implements AllowHeaderReceiving { private final Map params - private final RestOperations restOperations + private final RestExecutor restExecutor - OptionsAllowHeaderExecutor(Map params, RestOperations restOperations) { + OptionsAllowHeaderExecutor(RestOperations restOperations, RetryExecutor retryExecutor, Map params) { this.params = params - this.restOperations = restOperations + this.restExecutor = new RestExecutor<>(restOperations, retryExecutor) + } + + @Override + ListenableFuture allowAsync() { + ListenableFuture future = restExecutor.exchangeAsync(OPTIONS, params, Object) + return Futures.transform(future, {ResponseEntity entity -> extractAllow(entity)} as Function) } @Override Set allow() { - if(params.url) { - ResponseEntity response = restOperations.exchange( - params.url as URI, OPTIONS, getHttpEntityFrom(params), Object) - return response.headers.getAllow() - } else if(params.urlTemplate) { - ResponseEntity response = restOperations.exchange( - "${params.host}${params.urlTemplate}", OPTIONS, getHttpEntityFrom(params), - Object, params.urlVariablesArray as Object[] ?: params.urlVariablesMap as Map) - return response.headers.getAllow() - } - throw new InvalidHttpMethodParametersException(params) + return extractAllow(restExecutor.exchange(OPTIONS, params, Object)) + } + + private Set extractAllow(ResponseEntity entity) { + return entity.headers.getAllow() } } diff --git a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/options/OptionsExecuteForResponseTypeRelated.groovy b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/options/OptionsExecuteForResponseTypeRelated.groovy index 22c1b5c8..7b3591e9 100644 --- a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/options/OptionsExecuteForResponseTypeRelated.groovy +++ b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/options/OptionsExecuteForResponseTypeRelated.groovy @@ -1,4 +1,6 @@ package com.ofg.infrastructure.web.resttemplate.fluent.options + +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 @@ -11,8 +13,8 @@ import static org.springframework.http.HttpMethod.OPTIONS @CompileStatic class OptionsExecuteForResponseTypeRelated extends ResponseTypeRelatedRequestsExecutor { - OptionsExecuteForResponseTypeRelated(Map params, RestOperations restOperations, Class responseType) { - super(params, restOperations, responseType) + OptionsExecuteForResponseTypeRelated(Map params, RestOperations restOperations, RetryExecutor retryExecutor, Class responseType) { + super(params, restOperations, retryExecutor, responseType) } @Override diff --git a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/options/OptionsMethodBuilder.groovy b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/options/OptionsMethodBuilder.groovy index 1e2b9f0e..d0087cf3 100644 --- a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/options/OptionsMethodBuilder.groovy +++ b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/options/OptionsMethodBuilder.groovy @@ -1,5 +1,10 @@ package com.ofg.infrastructure.web.resttemplate.fluent.options +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.nurkiewicz.asyncretry.SyncRetryExecutor import com.ofg.infrastructure.web.resttemplate.fluent.common.response.receive.HeadersHaving import com.ofg.infrastructure.web.resttemplate.fluent.common.response.receive.ObjectReceiving import com.ofg.infrastructure.web.resttemplate.fluent.common.response.receive.PredefinedHttpHeaders @@ -24,18 +29,20 @@ class OptionsMethodBuilder implements private final Map params = [:] private final RestOperations restOperations + private final RetryExecutor retryExecutor @Delegate private final AllowContainingWithHeaders withHeaders @Delegate private final OptionsAllowHeaderExecutor allowHeaderExecutor - OptionsMethodBuilder(String host, RestOperations restOperations, PredefinedHttpHeaders predefinedHeaders) { + OptionsMethodBuilder(String host, RestOperations restOperations, PredefinedHttpHeaders predefinedHeaders, RetryExecutor retryExecutor) { this.restOperations = restOperations params.host = host withHeaders = new AllowContainingWithHeaders(this, params, predefinedHeaders) - allowHeaderExecutor = new OptionsAllowHeaderExecutor(params, restOperations) + allowHeaderExecutor = new OptionsAllowHeaderExecutor(restOperations, retryExecutor, params) + this.retryExecutor = retryExecutor } OptionsMethodBuilder(RestOperations restOperations) { - this(EMPTY_HOST, restOperations, NO_PREDEFINED_HEADERS) + this(EMPTY_HOST, restOperations, NO_PREDEFINED_HEADERS, SyncRetryExecutor.INSTANCE) } @Override @@ -83,8 +90,9 @@ class OptionsMethodBuilder implements ObjectReceiving anObject() { return new ObjectReceiving() { @Override - public T ofType(Class responseType) { - return new OptionsExecuteForResponseTypeRelated(params, restOperations, responseType).exchange()?.body + public ListenableFuture ofTypeAsync(Class responseType) { + def future = options(responseType).exchangeAsync() + return Futures.transform(future, {ResponseEntity response -> response?.body} as Function) } } } @@ -93,15 +101,25 @@ class OptionsMethodBuilder implements ResponseEntityReceiving aResponseEntity() { return new ResponseEntityReceiving() { @Override - public ResponseEntity ofType(Class responseType) { - return new OptionsExecuteForResponseTypeRelated(params, restOperations, responseType).exchange() + public ListenableFuture> ofTypeAsync(Class responseType) { + return options(responseType).exchangeAsync() } } } + private OptionsExecuteForResponseTypeRelated options(Class responseType) { + return new OptionsExecuteForResponseTypeRelated(params, restOperations, retryExecutor, responseType) + } + @Override void ignoringResponse() { aResponseEntity().ofType(Object) } + @Override + ListenableFuture ignoringResponseAsync() { + ListenableFuture> future = aResponseEntity().ofTypeAsync(Object) + return Futures.transform(future, {null} as Function) + } + } diff --git a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/post/PostExecuteForResponseTypeRelated.groovy b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/post/PostExecuteForResponseTypeRelated.groovy index 2ebab34e..07efaa2a 100644 --- a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/post/PostExecuteForResponseTypeRelated.groovy +++ b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/post/PostExecuteForResponseTypeRelated.groovy @@ -1,4 +1,6 @@ package com.ofg.infrastructure.web.resttemplate.fluent.post + +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 @@ -11,8 +13,8 @@ import static org.springframework.http.HttpMethod.POST @CompileStatic class PostExecuteForResponseTypeRelated extends ResponseTypeRelatedRequestsExecutor { - PostExecuteForResponseTypeRelated(Map params, RestOperations restOperations, Class responseType) { - super(params, restOperations, responseType) + PostExecuteForResponseTypeRelated(Map params, RestOperations restOperations, RetryExecutor retryExecutor, Class responseType) { + super(params, restOperations, retryExecutor, responseType) } @Override diff --git a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/post/PostMethodBuilder.groovy b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/post/PostMethodBuilder.groovy index 17d6091a..31e1b973 100644 --- a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/post/PostMethodBuilder.groovy +++ b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/post/PostMethodBuilder.groovy @@ -1,5 +1,10 @@ package com.ofg.infrastructure.web.resttemplate.fluent.post +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.nurkiewicz.asyncretry.SyncRetryExecutor import com.ofg.infrastructure.web.resttemplate.fluent.common.response.executor.LocationFindingExecutor import com.ofg.infrastructure.web.resttemplate.fluent.common.response.receive.BodyContainingWithHeaders import com.ofg.infrastructure.web.resttemplate.fluent.common.response.receive.HeadersSetting @@ -24,17 +29,17 @@ class PostMethodBuilder extends LocationFindingExecutor implements UrlParameterizablePostMethod, HeadersSetting { public static final String EMPTY_HOST = '' - + @Delegate private final BodyContainingWithHeaders withHeaders - PostMethodBuilder(String host, RestOperations restOperations, PredefinedHttpHeaders predefinedHeaders) { - super(restOperations) + PostMethodBuilder(String host, RestOperations restOperations, PredefinedHttpHeaders predefinedHeaders, RetryExecutor retryExecutor) { + super(restOperations, retryExecutor) params.host = host withHeaders = new BodyContainingWithHeaders(this, params, predefinedHeaders) } PostMethodBuilder(RestOperations restOperations) { - this(EMPTY_HOST, restOperations, NO_PREDEFINED_HEADERS) + this(EMPTY_HOST, restOperations, NO_PREDEFINED_HEADERS, SyncRetryExecutor.INSTANCE) } @Override @@ -95,8 +100,9 @@ class PostMethodBuilder extends LocationFindingExecutor implements ObjectReceiving anObject() { return new ObjectReceiving() { @Override - public T ofType(Class responseType) { - return new PostExecuteForResponseTypeRelated(params, restOperations, responseType).exchange()?.body + public ListenableFuture ofTypeAsync(Class responseType) { + ListenableFuture> future = post(responseType).exchangeAsync() + return Futures.transform(future, {ResponseEntity response -> response?.body} as Function) } } } @@ -105,15 +111,24 @@ class PostMethodBuilder extends LocationFindingExecutor implements ResponseEntityReceiving aResponseEntity() { return new ResponseEntityReceiving() { @Override - public ResponseEntity ofType(Class responseType) { - return new PostExecuteForResponseTypeRelated(params, restOperations, responseType).exchange() + public ListenableFuture> ofTypeAsync(Class responseType) { + return post(responseType).exchangeAsync() } } } - + + private PostExecuteForResponseTypeRelated post(Class responseType) { + return new PostExecuteForResponseTypeRelated(params, restOperations, retryExecutor, responseType) + } + @Override void ignoringResponse() { aResponseEntity().ofType(Object) } + @Override + ListenableFuture ignoringResponseAsync() { + ListenableFuture> future = aResponseEntity().ofTypeAsync(Object) + return Futures.transform(future, {null} as Function) + } } diff --git a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/put/PutExecuteForResponseTypeRelated.groovy b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/put/PutExecuteForResponseTypeRelated.groovy index 27c4faee..f54a63be 100644 --- a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/put/PutExecuteForResponseTypeRelated.groovy +++ b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/put/PutExecuteForResponseTypeRelated.groovy @@ -1,4 +1,6 @@ package com.ofg.infrastructure.web.resttemplate.fluent.put + +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 @@ -12,8 +14,8 @@ import static org.springframework.http.HttpMethod.PUT @CompileStatic class PutExecuteForResponseTypeRelated extends ResponseTypeRelatedRequestsExecutor { - PutExecuteForResponseTypeRelated(Map params, RestOperations restOperations, Class responseType) { - super(params, restOperations, responseType) + PutExecuteForResponseTypeRelated(Map params, RestOperations restOperations, RetryExecutor retryExecutor, Class responseType) { + super(params, restOperations, retryExecutor, responseType) } @Override diff --git a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/put/PutMethodBuilder.groovy b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/put/PutMethodBuilder.groovy index 4984b6f3..98e2cf53 100644 --- a/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/put/PutMethodBuilder.groovy +++ b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/put/PutMethodBuilder.groovy @@ -1,5 +1,10 @@ package com.ofg.infrastructure.web.resttemplate.fluent.put +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.nurkiewicz.asyncretry.SyncRetryExecutor import com.ofg.infrastructure.web.resttemplate.fluent.common.response.executor.LocationFindingExecutor import com.ofg.infrastructure.web.resttemplate.fluent.common.response.receive.BodyContainingWithHeaders import com.ofg.infrastructure.web.resttemplate.fluent.common.response.receive.HeadersSetting @@ -28,14 +33,14 @@ class PutMethodBuilder extends LocationFindingExecutor implements @Delegate private final BodyContainingWithHeaders withHeaders - PutMethodBuilder(String host, RestOperations restOperations, PredefinedHttpHeaders predefinedHeaders) { - super(restOperations) + PutMethodBuilder(String host, RestOperations restOperations, PredefinedHttpHeaders predefinedHeaders, RetryExecutor retryExecutor) { + super(restOperations, retryExecutor) params.host = host withHeaders = new BodyContainingWithHeaders(this, params, predefinedHeaders) } PutMethodBuilder(RestOperations restOperations) { - this(EMPTY_HOST, restOperations, NO_PREDEFINED_HEADERS) + this(EMPTY_HOST, restOperations, NO_PREDEFINED_HEADERS, SyncRetryExecutor.INSTANCE) } @Override @@ -96,8 +101,9 @@ class PutMethodBuilder extends LocationFindingExecutor implements ObjectReceiving anObject() { return new ObjectReceiving() { @Override - public T ofType(Class responseType) { - return new PutExecuteForResponseTypeRelated(params, restOperations, responseType).exchange()?.body + public ListenableFuture ofTypeAsync(Class responseType) { + ListenableFuture future = put(responseType).exchangeAsync() + return Futures.transform(future, {ResponseEntity response -> response?.body} as Function) } } } @@ -106,15 +112,23 @@ class PutMethodBuilder extends LocationFindingExecutor implements ResponseEntityReceiving aResponseEntity() { return new ResponseEntityReceiving() { @Override - public ResponseEntity ofType(Class responseType) { - return new PutExecuteForResponseTypeRelated(params, restOperations, responseType).exchange() + public ListenableFuture> ofTypeAsync(Class responseType) { + return put(responseType).exchangeAsync() } } } - + + private PutExecuteForResponseTypeRelated put(Class responseType) { + return new PutExecuteForResponseTypeRelated(params, restOperations, retryExecutor, responseType) + } + @Override void ignoringResponse() { aResponseEntity().ofType(Object) } + ListenableFuture ignoringResponseAsync() { + ListenableFuture> future = aResponseEntity().ofTypeAsync(Object) + return Futures.transform(future, {null} as Function, Void>) + } } diff --git a/micro-infra-spring-base/src/test/groovy/com/ofg/infrastructure/web/resttemplate/fluent/ServiceRestClientIntegrationSpec.groovy b/micro-infra-spring-base/src/test/groovy/com/ofg/infrastructure/web/resttemplate/fluent/ServiceRestClientIntegrationSpec.groovy index 767bfeff..e2d15bbe 100644 --- a/micro-infra-spring-base/src/test/groovy/com/ofg/infrastructure/web/resttemplate/fluent/ServiceRestClientIntegrationSpec.groovy +++ b/micro-infra-spring-base/src/test/groovy/com/ofg/infrastructure/web/resttemplate/fluent/ServiceRestClientIntegrationSpec.groovy @@ -22,7 +22,13 @@ class ServiceRestClientIntegrationSpec extends MvcWiremockIntegrationSpec { def "should send a request to provided URL with appending host when calling service"() { when: - ResponseEntity result = serviceRestClient.forService(COLLABORATOR_NAME).get().onUrl(PATH).andExecuteFor().aResponseEntity().ofType(String) + ResponseEntity result = serviceRestClient + .forService(COLLABORATOR_NAME) + .get() + .onUrl(PATH) + .andExecuteFor() + .aResponseEntity() + .ofType(String) then: result.body == CONTEXT_SPECIFIC_FOOBAR } diff --git a/micro-infra-spring-base/src/test/groovy/com/ofg/infrastructure/web/resttemplate/fluent/ServiceRestClientSpec.groovy b/micro-infra-spring-base/src/test/groovy/com/ofg/infrastructure/web/resttemplate/fluent/ServiceRestClientSpec.groovy index de41d8ba..e5fbffb3 100644 --- a/micro-infra-spring-base/src/test/groovy/com/ofg/infrastructure/web/resttemplate/fluent/ServiceRestClientSpec.groovy +++ b/micro-infra-spring-base/src/test/groovy/com/ofg/infrastructure/web/resttemplate/fluent/ServiceRestClientSpec.groovy @@ -1,21 +1,35 @@ package com.ofg.infrastructure.web.resttemplate.fluent +import com.google.common.util.concurrent.ListenableFuture +import com.nurkiewicz.asyncretry.AsyncRetryExecutor import com.ofg.infrastructure.discovery.ServiceConfigurationResolver import com.ofg.infrastructure.discovery.ServiceResolver import com.ofg.infrastructure.discovery.ServiceUnavailableException import org.springframework.http.HttpEntity +import org.springframework.web.client.RestClientException import org.springframework.web.client.RestOperations +import spock.lang.AutoCleanup +import spock.lang.Shared import spock.lang.Specification +import java.util.concurrent.Executors +import java.util.concurrent.ScheduledExecutorService + +import static org.springframework.http.HttpMethod.DELETE import static org.springframework.http.HttpMethod.GET +import static org.springframework.http.HttpMethod.HEAD +import static org.springframework.http.HttpMethod.POST class ServiceRestClientSpec extends Specification { public static final String COLA_COLLABORATOR_NAME = 'cola' + public static final String SOME_SERVICE_URL = 'http://localhost:1234' RestOperations restOperations = Mock() ServiceResolver serviceResolver = Mock() ServiceConfigurationResolver configurationResolver = Mock() - + @AutoCleanup("shutdownNow") + @Shared + ScheduledExecutorService pool = Executors.newScheduledThreadPool(1) ServiceRestClient serviceRestClient = new ServiceRestClient(restOperations, serviceResolver, configurationResolver) def setup() { @@ -26,11 +40,10 @@ class ServiceRestClientSpec extends Specification { def 'should send a request to provided URL with appending host when calling service'() { given: - String serviceUrl = 'http://localhost:1234' String path = 'some/serviceUrl' - URI expectedUri = new URI("$serviceUrl/$path") + URI expectedUri = new URI("${SOME_SERVICE_URL}/$path") and: - serviceResolver.fetchUrl(COLA_COLLABORATOR_NAME) >> serviceUrl + serviceResolver.fetchUrl(COLA_COLLABORATOR_NAME) >> SOME_SERVICE_URL when: serviceRestClient.forService(COLA_COLLABORATOR_NAME).get().onUrl(path).ignoringResponse() then: @@ -39,10 +52,9 @@ class ServiceRestClientSpec extends Specification { def 'should send a request to provided URL with Content-Type set when calling service'() { given: - String serviceUrl = 'http://localhost:1234' String path = 'some/serviceUrl' and: - serviceResolver.fetchUrl(COLA_COLLABORATOR_NAME) >> serviceUrl + serviceResolver.fetchUrl(COLA_COLLABORATOR_NAME) >> SOME_SERVICE_URL when: serviceRestClient.forService(COLA_COLLABORATOR_NAME).get().onUrl(path).ignoringResponse() then: @@ -53,10 +65,9 @@ class ServiceRestClientSpec extends Specification { def 'should throw exception on creating Content-Type header when version property is required and is missing in dependency configuration'() { given: - String serviceUrl = 'http://localhost:1234' String path = 'some/serviceUrl' and: - serviceResolver.fetchUrl(COLA_COLLABORATOR_NAME) >> serviceUrl + serviceResolver.fetchUrl(COLA_COLLABORATOR_NAME) >> SOME_SERVICE_URL when: serviceRestClient.forService(COLA_COLLABORATOR_NAME).get().onUrl(path).ignoringResponse() then: @@ -67,7 +78,7 @@ class ServiceRestClientSpec extends Specification { def 'should send a request to provided URL with predefined headers set when calling service'() { given: - String serviceUrl = 'http://localhost:1234' + String serviceUrl = SOME_SERVICE_URL String path = 'some/serviceUrl' and: serviceResolver.fetchUrl(COLA_COLLABORATOR_NAME) >> serviceUrl @@ -100,4 +111,73 @@ class ServiceRestClientSpec extends Specification { then: 1 * restOperations.exchange(expectedUri, GET, _ as HttpEntity, _ as Class) } + + def 'should retry once in case of failure'() { + given: + AsyncRetryExecutor executor = new AsyncRetryExecutor(pool) + when: + serviceRestClient + .forExternalService() + .retryUsing(executor.withMaxRetries(1).withNoDelay()) + .get() + .onUrl(SOME_SERVICE_URL) + .ignoringResponse() + then: + 2 * restOperations.exchange(_, GET, _, _ as Class) >>> [] >> { + throw new RestClientException("Simulated") + } >> null + } + + def 'should retry once asynchronously in case of failure'() { + given: + AsyncRetryExecutor executor = new AsyncRetryExecutor(pool) + when: + ListenableFuture future = serviceRestClient + .forExternalService() + .retryUsing(executor.withMaxRetries(1).withNoDelay()) + .post() + .onUrl(SOME_SERVICE_URL) + .body('') + .andExecuteFor() + .aResponseEntity().ofTypeAsync(String) + future.get() + then: + 2 * restOperations.exchange(_, POST, _ as HttpEntity, _ as Class) >>> [] >> { + throw new RestClientException("Simulated") + } >> null + } + + def 'should fail when RestOperations failed and no retry mechanism'() { + given: + AsyncRetryExecutor executor = new AsyncRetryExecutor(pool) + when: + serviceRestClient + .forExternalService() + .retryUsing(executor.withMaxRetries(0)) + .delete() + .onUrl(SOME_SERVICE_URL) + .ignoringResponseAsync().get() + then: + 1 * restOperations.exchange(_, DELETE, _ as HttpEntity, _ as Class) >> { + throw new RestClientException("Simulated") + } + Exception e = thrown(Exception) + e.message.contains("Simulated") + } + + def 'should invoke HEAD async with retry in case first invocation fails'() { + given: + AsyncRetryExecutor executor = new AsyncRetryExecutor(pool) + when: + serviceRestClient + .forExternalService() + .retryUsing(executor.withMaxRetries(1).withNoDelay()) + .head() + .onUrl(SOME_SERVICE_URL) + .httpHeadersAsync().get() + then: + 2 * restOperations.exchange(_, HEAD, _ as HttpEntity, _ as Class) >>> [] >> { + throw new RestClientException("Simulated") + } >> null + } } diff --git a/micro-infra-spring-base/src/test/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/executor/HttpEntityUtilsSpec.groovy b/micro-infra-spring-base/src/test/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/executor/HttpEntityUtilsSpec.groovy index ff27c947..d544cb45 100644 --- a/micro-infra-spring-base/src/test/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/executor/HttpEntityUtilsSpec.groovy +++ b/micro-infra-spring-base/src/test/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/executor/HttpEntityUtilsSpec.groovy @@ -8,20 +8,13 @@ class HttpEntityUtilsSpec extends Specification { public static final int UNKOWN_HEADER_VALUE = -1 - def 'should fail to instantiate a utility class'() { - when: - HttpEntityUtils.newInstance() - then: - thrown(UnsupportedOperationException) - } - def 'should create HttpEntity from a filled argument map'() { given: long expectedExpires = 1000 String expectedBody = '''{"sample":"json"}''' Map args = [headers: new HttpHeaders(expires: expectedExpires), request: expectedBody] when: - HttpEntity httpEntity = HttpEntityUtils.getHttpEntityFrom(args) + HttpEntity httpEntity = RestExecutor.getHttpEntityFrom(args) then: expectedExpires == httpEntity.headers.getExpires() expectedBody == httpEntity.body @@ -31,7 +24,7 @@ class HttpEntityUtilsSpec extends Specification { given: Map args = [:] when: - HttpEntity httpEntity = HttpEntityUtils.getHttpEntityFrom(args) + HttpEntity httpEntity = RestExecutor.getHttpEntityFrom(args) then: httpEntity.headers.getExpires() == UNKOWN_HEADER_VALUE !httpEntity.body diff --git a/micro-infra-spring-base/src/test/groovy/com/ofg/infrastructure/web/resttemplate/fluent/options/OptionsHttpMethodBuilderSpec.groovy b/micro-infra-spring-base/src/test/groovy/com/ofg/infrastructure/web/resttemplate/fluent/options/OptionsHttpMethodBuilderSpec.groovy index 93d19cea..d5f2736b 100644 --- a/micro-infra-spring-base/src/test/groovy/com/ofg/infrastructure/web/resttemplate/fluent/options/OptionsHttpMethodBuilderSpec.groovy +++ b/micro-infra-spring-base/src/test/groovy/com/ofg/infrastructure/web/resttemplate/fluent/options/OptionsHttpMethodBuilderSpec.groovy @@ -14,7 +14,6 @@ import static org.springframework.http.HttpStatus.OK class OptionsHttpMethodBuilderSpec extends HttpMethodSpec { - public static final String RESPONSE_BODY = '''{"sample":"response"}''' public static final Class RESPONSE_TYPE = String def "should use only url template without provided service url to retrieve an object"() { diff --git a/micro-infra-spring-base/src/test/groovy/com/ofg/infrastructure/web/resttemplate/fluent/put/PutHttpMethodBuilderSpec.groovy b/micro-infra-spring-base/src/test/groovy/com/ofg/infrastructure/web/resttemplate/fluent/put/PutHttpMethodBuilderSpec.groovy index 10da39dc..f0a09cc4 100644 --- a/micro-infra-spring-base/src/test/groovy/com/ofg/infrastructure/web/resttemplate/fluent/put/PutHttpMethodBuilderSpec.groovy +++ b/micro-infra-spring-base/src/test/groovy/com/ofg/infrastructure/web/resttemplate/fluent/put/PutHttpMethodBuilderSpec.groovy @@ -27,9 +27,9 @@ class PutHttpMethodBuilderSpec extends HttpMethodSpec { .body(REQUEST_BODY) .forLocation() then: - 1 * restOperations.exchange(expectedLocation, - PUT, - { HttpEntity httpEntity -> httpEntity.body == REQUEST_BODY } as HttpEntity, + 1 * restOperations.exchange(expectedLocation, + PUT, + { HttpEntity httpEntity -> httpEntity.body == REQUEST_BODY } as HttpEntity, RESPONSE_TYPE) >> responseEntityWith(expectedLocation) actualLocation == expectedLocation } diff --git a/micro-infra-spring-base/src/test/resources/logback.groovy b/micro-infra-spring-base/src/test/resources/logback.groovy new file mode 100644 index 00000000..09d37a57 --- /dev/null +++ b/micro-infra-spring-base/src/test/resources/logback.groovy @@ -0,0 +1,15 @@ +import ch.qos.logback.classic.encoder.PatternLayoutEncoder +import ch.qos.logback.core.ConsoleAppender + +appender("CONSOLE", ConsoleAppender) { + encoder(PatternLayoutEncoder) { + pattern = "%d{HH:mm:ss.SSSZ} | %-5level | %X{correlationId} | %thread | %logger{1} | %m%n" + } +} + +//TODO: Fix console pollution during tests (it should be only logged to a test log file, but also available in Gradle test reports) +root(INFO, ["CONSOLE"]) + +logger("com.nurkiewicz.asyncretry", ALL) +logger("com.ofg", DEBUG) +logger("org.springframework.cloud.config.client", DEBUG)