diff --git a/src/main/java/org/springframework/hateoas/client/Rels.java b/src/main/java/org/springframework/hateoas/client/Rels.java index df745a17..82c45b82 100644 --- a/src/main/java/org/springframework/hateoas/client/Rels.java +++ b/src/main/java/org/springframework/hateoas/client/Rels.java @@ -93,7 +93,15 @@ class Rels { */ @Override public Link findInResponse(String response, MediaType mediaType) { - return discoverers.getLinkDiscovererFor(mediaType).findLinkWithRel(rel, response); + + LinkDiscoverer discoverer = discoverers.getLinkDiscovererFor(mediaType); + + if (discoverer == null) { + throw new IllegalStateException(String.format("Did not find LinkDiscoverer supporting media type %s!", + mediaType)); + } + + return discoverer.findLinkWithRel(rel, response); } } diff --git a/src/main/java/org/springframework/hateoas/client/Traverson.java b/src/main/java/org/springframework/hateoas/client/Traverson.java index edef0fe9..a8164438 100644 --- a/src/main/java/org/springframework/hateoas/client/Traverson.java +++ b/src/main/java/org/springframework/hateoas/client/Traverson.java @@ -62,11 +62,19 @@ import com.jayway.jsonpath.JsonPath; */ public class Traverson { + private static final LinkDiscoverers DEFAULT_LINK_DISCOVERERS; + + static { + LinkDiscoverer discoverer = new HalLinkDiscoverer(); + DEFAULT_LINK_DISCOVERERS = new LinkDiscoverers(OrderAwarePluginRegistry.create(Arrays.asList(discoverer))); + } + private final URI baseUri; - private final RestOperations operations; - private final LinkDiscoverers discoverers; private final List mediaTypes; + private RestOperations operations; + private LinkDiscoverers discoverers; + /** * Creates a new {@link Traverson} interacting with the given base URI and using the given {@link MediaType}s to * interact with the service. @@ -75,29 +83,15 @@ 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 type must be given!"); this.mediaTypes = Arrays.asList(mediaTypes); this.baseUri = baseUri; - this.operations = operations == null ? createDefaultTemplate(this.mediaTypes) : operations; + this.discoverers = DEFAULT_LINK_DISCOVERERS; - LinkDiscoverer discoverer = new HalLinkDiscoverer(); - this.discoverers = new LinkDiscoverers(OrderAwarePluginRegistry.create(Arrays.asList(discoverer))); + setRestOperations(createDefaultTemplate(this.mediaTypes)); } private static final RestOperations createDefaultTemplate(List mediaTypes) { @@ -134,6 +128,34 @@ public class Traverson { return converter; } + /** + * Configures the {@link RestOperations} to use. If {@literal null} is provided a default {@link RestTemplate} will be + * used. + * + * @param operations + * @return + */ + public Traverson setRestOperations(RestOperations operations) { + + this.operations = operations == null ? createDefaultTemplate(mediaTypes) : operations; + return this; + } + + /** + * Sets the {@link LinkDiscoverers} to use. By default a single {@link HalLinkDiscoverer} is registered. If + * {@literal null} is provided the default is reapplied. + * + * @param discoverer can be {@literal null}. + * @return + */ + public Traverson setLinkDiscoverers(List discoverer) { + + this.discoverers = discoverers == null ? DEFAULT_LINK_DISCOVERERS : new LinkDiscoverers( + OrderAwarePluginRegistry.create(discoverer)); + + return this; + } + /** * Sets up a {@link TraversalBuilder} to follow the given rels. * diff --git a/src/test/java/org/springframework/hateoas/client/Server.java b/src/test/java/org/springframework/hateoas/client/Server.java index 9d88e9c4..d1a6b06f 100644 --- a/src/test/java/org/springframework/hateoas/client/Server.java +++ b/src/test/java/org/springframework/hateoas/client/Server.java @@ -30,6 +30,7 @@ import org.springframework.hateoas.Resource; import org.springframework.hateoas.Resources; import org.springframework.hateoas.core.EvoInflectorRelProvider; import org.springframework.hateoas.hal.Jackson2HalModule; +import org.springframework.http.MediaType; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -66,6 +67,20 @@ public class Server implements Closeable { havingPathEqualTo("/"). // respond(). // withBody(""); + + // For GitHubLinkDiscoverer tests + + onRequest(). // + havingPathEqualTo("/github"). // + respond(). // + withBody("{ \"foo_url\" : \"" + rootResource() + "/github/4711\"}"). // + withContentType(MediaType.APPLICATION_JSON_VALUE); + + onRequest(). // + havingPathEqualTo("/github/4711"). // + respond(). // + withBody("{ \"key\" : \"value\"}"). // + withContentType(MediaType.APPLICATION_JSON_VALUE); } public String rootResource() { diff --git a/src/test/java/org/springframework/hateoas/client/TraversonTests.java b/src/test/java/org/springframework/hateoas/client/TraversonTests.java index 9fb40ee6..a84e60ce 100644 --- a/src/test/java/org/springframework/hateoas/client/TraversonTests.java +++ b/src/test/java/org/springframework/hateoas/client/TraversonTests.java @@ -30,6 +30,7 @@ import org.springframework.core.ParameterizedTypeReference; import org.springframework.hateoas.Link; import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.Resource; +import org.springframework.hateoas.core.JsonPathLinkDiscoverer; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpRequest; import org.springframework.http.MediaType; @@ -176,12 +177,26 @@ public class TraversonTests { RestTemplate restTemplate = new RestTemplate(); restTemplate.setInterceptors(Arrays. asList(interceptor)); - this.traverson = new Traverson(restTemplate, baseUri, MediaTypes.HAL_JSON); + this.traverson = new Traverson(baseUri, MediaTypes.HAL_JSON); + this.traverson.setRestOperations(restTemplate); traverson.follow("movies", "movie", "actor"). toObject("$.name"); assertThat(interceptor.intercepted, is(4)); } + /** + * @see #185 + */ + @Test + public void usesCustomLinkDiscoverer() { + + this.traverson = new Traverson(URI.create(server.rootResource() + "/github"), MediaType.APPLICATION_JSON); + this.traverson.setLinkDiscoverers(Arrays.asList(new GitHubLinkDiscoverer())); + + String value = this.traverson.follow("foo").toObject("$.key"); + assertThat(value, is("value")); + } + private void setUpActors() { Resource actor = new Resource(new Actor("Keanu Reaves")); @@ -206,4 +221,11 @@ public class TraversonTests { return execution.execute(request, body); } }; + + static class GitHubLinkDiscoverer extends JsonPathLinkDiscoverer { + + public GitHubLinkDiscoverer() { + super("$.%s_url", MediaType.APPLICATION_JSON); + } + } }