#346 - Enhance Traverson.follow() to customize individual hops.

Introduced a dedicated value object to abstract a Hop within a relation traversal. The Hop consists of a relation name and optional local template parameters that are applied to this particular hop when traversing it.

Changed the implementation of Traverson to work with Hops instead of plain rels and merges the global template parameters with the hop-local ones before expanding the templates found.

Original pull request: #350.
This commit is contained in:
Greg Turnquist
2015-05-20 12:31:46 -05:00
committed by Oliver Gierke
parent 96166f9ede
commit 2c1797d9b7
12 changed files with 403 additions and 14 deletions

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.hateoas.client;
import java.util.HashMap;
import java.util.Map;
import lombok.Value;
/**
* Container for cuztomizations to a single traverson "hop"
*
* @author Greg Turnquist
*/
@Value(staticConstructor="rel")
public class Hop {
/**
* Name of this hop.
*/
private final String rel;
/**
* Collection of URI Template parameters.
*/
private final Map<String, String> params = new HashMap<String, String>();
/**
* Add one parameter to the map of parameters.
*
* @param name
* @param value
* @return fluent DSL (Lombok @Wither didn't seem to work)
*/
public Hop withParam(String name, String value) {
this.params.put(name, value);
return this;
}
/**
* Add a collection of parameters (does NOT clear out existing ones).
*
* @param newParams
* @return fluent DSL (Lombok @Wither didn't seem to work)
*/
public Hop withParams(Map<String, String> newParams) {
this.params.putAll(newParams);
return this;
}
/**
* Create a new {@link Map} starting with the supplied template parameters. Then add the ones for this hop. This
* allows a local hop to override global parameters.
*
* @param globalParameters
* @return a merged map of URI Template parameters
*/
public Map<String, Object> getMergedParameteres(Map<String, Object> globalParameters) {
Map<String, Object> mergedParameters = new HashMap<String, Object>();
mergedParameters.putAll(globalParameters);
mergedParameters.putAll(this.params);
return mergedParameters;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2014 the original author or authors.
* Copyright 2013-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@ import com.jayway.jsonpath.JsonPath;
* Helper class to find {@link Link} instances in representations.
*
* @author Oliver Gierke
* @author Greg Turnquist
* @since 0.11
*/
class Rels {
@@ -103,6 +104,16 @@ class Rels {
return discoverer.findLinkWithRel(rel, response);
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return this.rel;
}
}
/**

View File

@@ -58,6 +58,7 @@ import com.jayway.jsonpath.JsonPath;
* @see https://github.com/basti1302/traverson
* @author Oliver Gierke
* @author Dietrich Schulten
* @author Greg Turnquist
* @since 0.11
*/
public class Traverson {
@@ -201,6 +202,16 @@ public class Traverson {
return new TraversalBuilder().follow(rels);
}
/**
* Sets up a {@link TraversalBuilder} for a single rel with customized details.
*
* @param hop must not be {@literal null}
* @return
*/
public TraversalBuilder follow(Hop hop) {
return new TraversalBuilder().follow(hop);
}
private HttpEntity<?> prepareRequest(HttpHeaders headers) {
HttpHeaders toSend = new HttpHeaders();
@@ -220,7 +231,7 @@ public class Traverson {
*/
public class TraversalBuilder {
private List<String> rels = new ArrayList<String>();
private List<Hop> rels = new ArrayList<Hop>();
private Map<String, Object> templateParameters = new HashMap<String, Object>();
private HttpHeaders headers = new HttpHeaders();
@@ -233,11 +244,27 @@ public class Traverson {
* @param rels must not be {@literal null}.
* @return
*/
private TraversalBuilder follow(String... rels) {
public TraversalBuilder follow(String... rels) {
Assert.notNull(rels, "Rels must not be null!");
this.rels.addAll(Arrays.asList(rels));
for (String rel : rels) {
this.rels.add(Hop.rel(rel));
}
return this;
}
/**
* Follows the given rels one by one, which means a request per rel to discover the next resource with the rel in
* line.
*
* @param hop must not be {@literal null}
* @return
*/
public TraversalBuilder follow(Hop hop) {
Assert.notNull(hop, "Hop must not be null!");
this.rels.add(hop);
return this;
}
@@ -344,7 +371,7 @@ public class Traverson {
private Link traverseToLink(boolean expandFinalUrl) {
Assert.isTrue(rels.size() > 0, "At least one rel needs to be provided!");
return new Link(traverseToFinalUrl(expandFinalUrl), rels.get(rels.size() - 1));
return new Link(traverseToFinalUrl(expandFinalUrl), rels.get(rels.size() - 1).getRel());
}
private String traverseToFinalUrl(boolean expandFinalUrl) {
@@ -354,21 +381,21 @@ public class Traverson {
return expandFinalUrl ? uriTemplate.expand(templateParameters).toString() : uriTemplate.toString();
}
private String getAndFindLinkWithRel(String uri, Iterator<String> rels) {
private String getAndFindLinkWithRel(String uri, Iterator<Hop> rels) {
if (!rels.hasNext()) {
return uri;
}
HttpEntity<?> request = prepareRequest(headers);
UriTemplate uriTemplate = new UriTemplate(uri);
ResponseEntity<String> responseEntity = operations.exchange(uriTemplate.expand(templateParameters), GET, request,
String.class);
ResponseEntity<String> responseEntity = operations.exchange(uri, GET, request, String.class);
MediaType contentType = responseEntity.getHeaders().getContentType();
String responseBody = responseEntity.getBody();
Rel rel = Rels.getRelFor(rels.next(), discoverers);
Hop thisHop = rels.next();
Rel rel = Rels.getRelFor(thisHop.getRel(), discoverers);
Link link = rel.findInResponse(responseBody, contentType);
if (link == null) {
@@ -376,7 +403,14 @@ public class Traverson {
responseBody));
}
return getAndFindLinkWithRel(link.getHref(), rels);
/**
* Don't expand if the parameters are empty
*/
if (thisHop.getParams().isEmpty()) {
return getAndFindLinkWithRel(link.getHref(), rels);
} else {
return getAndFindLinkWithRel(link.expand(thisHop.getMergedParameteres(templateParameters)).getHref(), rels);
}
}
}
}