From c1e940c5f3445365027fe292c681dd70552eb2d1 Mon Sep 17 00:00:00 2001 From: Dietrich Schulten Date: Thu, 19 Jun 2014 08:01:43 +0200 Subject: [PATCH] #201 - Traverson can now take a customized RestTemplate. Traverson now has an additional constructor taking a RestOperations instance which will be used instead of the default one. This allows more fine grained control over the HTTP request infrastructure used when performing link traversals. Some internal polishing (moved from RestTemplate to RestOperations). Original pull request: #203. --- .../hateoas/client/Traverson.java | 54 +++++++++++++------ .../hateoas/client/TraversonTests.java | 35 ++++++++++++ 2 files changed, 72 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/springframework/hateoas/client/Traverson.java b/src/main/java/org/springframework/hateoas/client/Traverson.java index 6888f576..edef0fe9 100644 --- a/src/main/java/org/springframework/hateoas/client/Traverson.java +++ b/src/main/java/org/springframework/hateoas/client/Traverson.java @@ -44,6 +44,7 @@ import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.plugin.core.OrderAwarePluginRegistry; import org.springframework.util.Assert; +import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; import com.fasterxml.jackson.databind.DeserializationFeature; @@ -62,7 +63,7 @@ import com.jayway.jsonpath.JsonPath; public class Traverson { private final URI baseUri; - private final RestTemplate template; + private final RestOperations operations; private final LinkDiscoverers discoverers; private final List mediaTypes; @@ -74,20 +75,32 @@ public class Traverson { * @param mediaType must not be {@literal null} or empty. */ public Traverson(URI baseUri, MediaType... mediaTypes) { + this(null, baseUri, mediaTypes); + } + + /** + * Creates a new {@link Traverson} with custom rest operations, interacting with the given base URI and using the + * given {@link MediaType}s to interact with the service. The custom rest operations will be prepared with message + * converters for the given media types. + * + * @param operations allowing to customize the http requests with interceptors, client configurations etc. + * @param baseUri must not be {@literal null}. + * @param mediaType must not be {@literal null} or empty. + */ + public Traverson(RestOperations operations, URI baseUri, MediaType... mediaTypes) { Assert.notNull(baseUri, "Base URI must not be null!"); - Assert.notEmpty(mediaTypes, "At least one media must be given!"); + Assert.notEmpty(mediaTypes, "At least one media type must be given!"); this.mediaTypes = Arrays.asList(mediaTypes); - this.template = prepareTemplate(this.mediaTypes); - this.baseUri = baseUri; + this.operations = operations == null ? createDefaultTemplate(this.mediaTypes) : operations; LinkDiscoverer discoverer = new HalLinkDiscoverer(); this.discoverers = new LinkDiscoverers(OrderAwarePluginRegistry.create(Arrays.asList(discoverer))); } - private final RestTemplate prepareTemplate(List mediaTypes) { + private static final RestOperations createDefaultTemplate(List mediaTypes) { List> converters = new ArrayList>(); converters.add(new StringHttpMessageConverter(Charset.forName("UTF-8"))); @@ -98,10 +111,16 @@ public class Traverson { RestTemplate template = new RestTemplate(); template.setMessageConverters(converters); + return template; } - private final HttpMessageConverter getHalConverter() { + /** + * Creates a new {@link HttpMessageConverter} to support HAL. + * + * @return + */ + private static final HttpMessageConverter getHalConverter() { ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new Jackson2HalModule()); @@ -128,14 +147,14 @@ public class Traverson { private HttpEntity prepareRequest(HttpHeaders headers) { - HttpHeaders toSent = new HttpHeaders(); - toSent.putAll(headers); + HttpHeaders toSend = new HttpHeaders(); + toSend.putAll(headers); if (headers.getAccept().isEmpty()) { - toSent.setAccept(mediaTypes); + toSend.setAccept(mediaTypes); } - return new HttpEntity(headers); + return new HttpEntity(toSend); } /** @@ -167,8 +186,8 @@ public class Traverson { } /** - * Adds the given template parameters to the traversal. If a link discovered by the traversal is templated, the - * given parameters will be used to expand the template into a resolvable URI. + * Adds the given operations parameters to the traversal. If a link discovered by the traversal is templated, the + * given parameters will be used to expand the operations into a resolvable URI. * * @param parameters can be {@literal null}. * @return @@ -200,7 +219,7 @@ public class Traverson { public T toObject(Class type) { Assert.notNull(type, "Target type must not be null!"); - return template.exchange(traverseToFinalUrl(), GET, prepareRequest(headers), type).getBody(); + return operations.exchange(traverseToFinalUrl(), GET, prepareRequest(headers), type).getBody(); } /** @@ -213,7 +232,7 @@ public class Traverson { public T toObject(ParameterizedTypeReference type) { Assert.notNull(type, "Target type must not be null!"); - return template.exchange(traverseToFinalUrl(), GET, prepareRequest(headers), type).getBody(); + return operations.exchange(traverseToFinalUrl(), GET, prepareRequest(headers), type).getBody(); } /** @@ -227,7 +246,8 @@ public class Traverson { Assert.hasText(jsonPath, "JSON path must not be null or empty!"); - String forObject = template.exchange(traverseToFinalUrl(), GET, prepareRequest(headers), String.class).getBody(); + String forObject = operations.exchange(traverseToFinalUrl(), GET, prepareRequest(headers), String.class) + .getBody(); return JsonPath.read(forObject, jsonPath); } @@ -240,7 +260,7 @@ public class Traverson { public ResponseEntity toEntity(Class type) { Assert.notNull(type, "Target type must not be null!"); - return template.exchange(traverseToFinalUrl(), GET, prepareRequest(headers), type); + return operations.exchange(traverseToFinalUrl(), GET, prepareRequest(headers), type); } private String traverseToFinalUrl() { @@ -258,7 +278,7 @@ public class Traverson { HttpEntity request = prepareRequest(headers); UriTemplate uriTemplate = new UriTemplate(uri); - ResponseEntity responseEntity = template.exchange(uriTemplate.expand(templateParameters), GET, request, + ResponseEntity responseEntity = operations.exchange(uriTemplate.expand(templateParameters), GET, request, String.class); MediaType contentType = responseEntity.getHeaders().getContentType(); String responseBody = responseEntity.getBody(); diff --git a/src/test/java/org/springframework/hateoas/client/TraversonTests.java b/src/test/java/org/springframework/hateoas/client/TraversonTests.java index 90bd9fed..9fb40ee6 100644 --- a/src/test/java/org/springframework/hateoas/client/TraversonTests.java +++ b/src/test/java/org/springframework/hateoas/client/TraversonTests.java @@ -21,6 +21,7 @@ import static org.junit.Assert.*; import java.io.IOException; import java.net.URI; +import java.util.Arrays; import org.junit.After; import org.junit.Before; @@ -30,7 +31,12 @@ import org.springframework.hateoas.Link; import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.Resource; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpRequest; import org.springframework.http.MediaType; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.web.client.RestTemplate; /** * Integration tests for {@link Traverson}. @@ -159,6 +165,23 @@ public class TraversonTests { havingHeader("Link", hasItem(expectedHeader)); } + /** + * @see #201, #203 + */ + @Test + public void allowsCustomizingRestTemplate() { + + CountingInterceptor interceptor = new CountingInterceptor(); + + RestTemplate restTemplate = new RestTemplate(); + restTemplate.setInterceptors(Arrays. asList(interceptor)); + + this.traverson = new Traverson(restTemplate, baseUri, MediaTypes.HAL_JSON); + + traverson.follow("movies", "movie", "actor"). toObject("$.name"); + assertThat(interceptor.intercepted, is(4)); + } + private void setUpActors() { Resource actor = new Resource(new Actor("Keanu Reaves")); @@ -171,4 +194,16 @@ public class TraversonTests { server.mockResourceFor(resource); server.finishMocking(); } + + static class CountingInterceptor implements ClientHttpRequestInterceptor { + + int intercepted; + + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) + throws IOException { + this.intercepted++; + return execution.execute(request, body); + } + }; }