From 04af0e30f88aed2d5453318f5fa18c9482b3eb43 Mon Sep 17 00:00:00 2001 From: Tomasz Nurkiewicz Date: Thu, 27 Nov 2014 15:03:18 +0100 Subject: [PATCH] [#13] POST/PUT/OPTIONS are also retried, removing a lot of duplicated code --- .../fluent/HttpMethodBuilder.groovy | 1 + .../response/executor/HttpEntityUtils.groovy | 23 ------ .../executor/LocationFindingExecutor.groovy | 34 ++++----- .../executor/LocationReceiving.groovy | 4 ++ ...ResponseTypeRelatedRequestsExecutor.groovy | 43 +---------- .../response/executor/RestExecutor.groovy | 72 +++++++++++++++++++ .../options/AllowHeaderReceiving.groovy | 3 + .../options/OptionsAllowHeaderExecutor.groovy | 36 ++++++---- .../options/OptionsMethodBuilder.groovy | 2 +- .../fluent/post/PostMethodBuilder.groovy | 4 +- .../fluent/put/PutMethodBuilder.groovy | 4 +- .../executor/HttpEntityUtilsSpec.groovy | 6 +- 12 files changed, 127 insertions(+), 105 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 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 19fd7a85..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 @@ -2,6 +2,7 @@ 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 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..6fb25d55 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, Object) } 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)) + } + + @Override + ListenableFuture forLocationAsync() { + ListenableFuture future = restExecutor.exchangeAsync(httpMethod, params) + 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 470c6892..a3bfe010 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 @@ -7,9 +7,6 @@ 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. @@ -28,16 +25,12 @@ 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 - protected final RetryExecutor retryExecutor ResponseTypeRelatedRequestsExecutor(Map params, RestOperations restOperations, RetryExecutor retryExecutor, Class responseType) { - this.restOperations = restOperations this.params = params - this.responseType = responseType - this.retryExecutor = retryExecutor + this.restExecutor = new RestExecutor(restOperations, retryExecutor, responseType) } protected abstract SpringHttpMethod getHttpMethod() @@ -47,38 +40,8 @@ abstract class ResponseTypeRelatedRequestsExecutor { } ListenableFuture> exchangeAsync() { - if (params.url) { - return callUrlWithRetry() - } else if (params.urlTemplate) { - return callUrlTemplateWithRetry() - } - throw new InvalidHttpMethodParametersException(params) - } - - private ListenableFuture> 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) - } + return restExecutor.exchangeAsync(httpMethod, params) } - private ListenableFuture> callUrlWithRetry() { - return runWithRetry { - return restOperations.exchange( - new URI(appendPathToHost(params.host as String, params.url as URI)), - httpMethod, - getHttpEntityFrom(params), - responseType) - } - } - - private ListenableFuture> runWithRetry(Closure block) { - return retryExecutor.getWithRetry(block) - } - } 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..ed9010f1 --- /dev/null +++ b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/common/response/executor/RestExecutor.groovy @@ -0,0 +1,72 @@ +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 + private final Class responseType + + RestExecutor(RestOperations restOperations, RetryExecutor retryExecutor, Class responseType) { + this.restOperations = restOperations + this.retryExecutor = retryExecutor + this.responseType = responseType + } + + ResponseEntity exchange(HttpMethod httpMethod, Map params) { + return exchangeAsync(httpMethod, params).get() + } + + ListenableFuture> exchangeAsync(HttpMethod httpMethod, Map params) { + if (params.url) { + return callUrlWithRetry(httpMethod, params) + } else if (params.urlTemplate) { + return callUrlTemplateWithRetry(httpMethod, params) + } + throw new InvalidHttpMethodParametersException(params) + } + + protected ListenableFuture> callUrlTemplateWithRetry(HttpMethod httpMethod, Map params) { + return retryExecutor.getWithRetry { + 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) { + return retryExecutor.getWithRetry { + restOperations.exchange( + new URI(appendPathToHost(params.host as String, params.url as URI)), + httpMethod, + getHttpEntityFrom(params), + responseType) + } + } + + private 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/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..b741d8f7 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, Object) + } + + @Override + ListenableFuture allowAsync() { + ListenableFuture future = restExecutor.exchangeAsync(OPTIONS, params) + 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)) + } + + 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/OptionsMethodBuilder.groovy b/micro-infra-spring-base/src/main/groovy/com/ofg/infrastructure/web/resttemplate/fluent/options/OptionsMethodBuilder.groovy index 10e52305..78f20e4a 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 @@ -37,7 +37,7 @@ class OptionsMethodBuilder implements this.restOperations = restOperations params.host = host withHeaders = new AllowContainingWithHeaders(this, params, predefinedHeaders) - allowHeaderExecutor = new OptionsAllowHeaderExecutor(params, restOperations) + allowHeaderExecutor = new OptionsAllowHeaderExecutor(restOperations, null, params) this.retryExecutor = retryExecutor } 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 ad38b494..c52845ea 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 @@ -30,14 +30,12 @@ class PostMethodBuilder extends LocationFindingExecutor implements public static final String EMPTY_HOST = '' - private final RetryExecutor retryExecutor @Delegate private final BodyContainingWithHeaders withHeaders PostMethodBuilder(String host, RestOperations restOperations, PredefinedHttpHeaders predefinedHeaders, RetryExecutor retryExecutor) { - super(restOperations) + super(restOperations, retryExecutor) params.host = host withHeaders = new BodyContainingWithHeaders(this, params, predefinedHeaders) - this.retryExecutor = retryExecutor } PostMethodBuilder(RestOperations restOperations) { 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 0ae87038..d6815fac 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 @@ -32,13 +32,11 @@ class PutMethodBuilder extends LocationFindingExecutor implements public static final String EMPTY_HOST = '' @Delegate private final BodyContainingWithHeaders withHeaders - private final RetryExecutor retryExecutor PutMethodBuilder(String host, RestOperations restOperations, PredefinedHttpHeaders predefinedHeaders, RetryExecutor retryExecutor) { - super(restOperations) + super(restOperations, retryExecutor) params.host = host withHeaders = new BodyContainingWithHeaders(this, params, predefinedHeaders) - this.retryExecutor = retryExecutor } PutMethodBuilder(RestOperations restOperations) { 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..d44c08dd 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 @@ -10,7 +10,7 @@ class HttpEntityUtilsSpec extends Specification { def 'should fail to instantiate a utility class'() { when: - HttpEntityUtils.newInstance() + RestExecutor.newInstance() then: thrown(UnsupportedOperationException) } @@ -21,7 +21,7 @@ class HttpEntityUtilsSpec extends Specification { 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 +31,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