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

Commit

Permalink
[#13] POST/PUT/OPTIONS are also retried, removing a lot of duplicated…
Browse files Browse the repository at this point in the history
… code
  • Loading branch information
nurkiewicz committed Dec 1, 2014
1 parent 84355ae commit 04af0e3
Show file tree
Hide file tree
Showing 12 changed files with 127 additions and 105 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

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, 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<String, ?>))
}
throw new InvalidHttpMethodParametersException(params)
return getLocation(restExecutor.exchange(httpMethod, params))
}

@Override
ListenableFuture<URI> forLocationAsync() {
ListenableFuture<ResponseEntity> future = restExecutor.exchangeAsync(httpMethod, params)
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
Expand Up @@ -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.
Expand All @@ -28,16 +25,12 @@ 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
protected final RetryExecutor retryExecutor

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

protected abstract SpringHttpMethod getHttpMethod()
Expand All @@ -47,38 +40,8 @@ abstract class ResponseTypeRelatedRequestsExecutor<T> {
}

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

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

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

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

}

Original file line number Diff line number Diff line change
@@ -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<T> {
private final RestOperations restOperations
private final RetryExecutor retryExecutor
private final Class<T> responseType

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

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

ListenableFuture<ResponseEntity<T>> 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<ResponseEntity<T>> 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<String, ?>)
}
}


protected ListenableFuture<ResponseEntity<T>> 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<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,5 +1,6 @@
package com.ofg.infrastructure.web.resttemplate.fluent.options

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

/**
Expand All @@ -14,4 +15,6 @@ interface AllowHeaderReceiving {
*/
Set<HttpMethod> allow()

ListenableFuture<Set<HttpMethod>> allowAsync()

}
Original file line number Diff line number Diff line change
@@ -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

/**
Expand All @@ -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<HttpMethod> allowAsync() {
ListenableFuture<ResponseEntity> future = restExecutor.exchangeAsync(OPTIONS, params)
return Futures.transform(future, {ResponseEntity entity -> extractAllow(entity)} as Function)
}

@Override
Set<HttpMethod> 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<String, ?>)
return response.headers.getAllow()
}
throw new InvalidHttpMethodParametersException(params)
return extractAllow(restExecutor.exchange(OPTIONS, params))
}

private Set<HttpMethod> extractAllow(ResponseEntity entity) {
return entity.headers.getAllow()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class HttpEntityUtilsSpec extends Specification {

def 'should fail to instantiate a utility class'() {
when:
HttpEntityUtils.newInstance()
RestExecutor.newInstance()
then:
thrown(UnsupportedOperationException)
}
Expand All @@ -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
Expand All @@ -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
Expand Down

0 comments on commit 04af0e3

Please sign in to comment.