Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#255, #634 - Add key operations to Traverson #637

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 49 additions & 11 deletions src/main/java/org/springframework/hateoas/client/Traverson.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,7 @@
import org.springframework.hateoas.client.Rels.Rel;
import org.springframework.hateoas.hal.HalLinkDiscoverer;
import org.springframework.hateoas.hal.Jackson2HalModule;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.*;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
Expand All @@ -61,6 +58,7 @@
* @author Dietrich Schulten
* @author Greg Turnquist
* @author Tom Bunting
* @author Haroun Pacquee
* @since 0.11
*/
public class Traverson {
Expand Down Expand Up @@ -235,7 +233,7 @@ public TraversalBuilder follow(Hop hop) {
return new TraversalBuilder().follow(hop);
}

private HttpEntity<?> prepareRequest(HttpHeaders headers) {
private HttpEntity<?> prepareRequest(HttpHeaders headers, Object payload) {

HttpHeaders toSend = new HttpHeaders();
toSend.putAll(headers);
Expand All @@ -244,7 +242,11 @@ private HttpEntity<?> prepareRequest(HttpHeaders headers) {
toSend.setAccept(mediaTypes);
}

return new HttpEntity<Void>(toSend);
if(payload == null) {
return new HttpEntity<Void>(toSend);
} else {
return new HttpEntity<Object>(payload, toSend);
}
}

/**
Expand All @@ -257,6 +259,8 @@ public class TraversalBuilder {
private List<Hop> rels = new ArrayList<Hop>();
private Map<String, Object> templateParameters = new HashMap<String, Object>();
private HttpHeaders headers = new HttpHeaders();
private HttpMethod method = GET;
private Object payload;

private TraversalBuilder() {}

Expand Down Expand Up @@ -328,7 +332,7 @@ public TraversalBuilder withHeaders(HttpHeaders headers) {
public <T> T toObject(Class<T> type) {

Assert.notNull(type, "Target type must not be null!");
return operations.exchange(traverseToExpandedFinalUrl(), GET, prepareRequest(headers), type).getBody();
return operations.exchange(traverseToExpandedFinalUrl(), method, prepareRequest(headers, payload), type).getBody();
}

/**
Expand All @@ -341,7 +345,7 @@ public <T> T toObject(Class<T> type) {
public <T> T toObject(ParameterizedTypeReference<T> type) {

Assert.notNull(type, "Target type must not be null!");
return operations.exchange(traverseToExpandedFinalUrl(), GET, prepareRequest(headers), type).getBody();
return operations.exchange(traverseToExpandedFinalUrl(), method, prepareRequest(headers, payload), type).getBody();
}

/**
Expand All @@ -355,7 +359,7 @@ public <T> T toObject(String jsonPath) {

Assert.hasText(jsonPath, "JSON path must not be null or empty!");

String forObject = operations.exchange(traverseToExpandedFinalUrl(), GET, prepareRequest(headers), String.class)
String forObject = operations.exchange(traverseToExpandedFinalUrl(), method, prepareRequest(headers, payload), String.class)
.getBody();
return JsonPath.read(forObject, jsonPath);
}
Expand All @@ -369,7 +373,7 @@ public <T> T toObject(String jsonPath) {
public <T> ResponseEntity<T> toEntity(Class<T> type) {

Assert.notNull(type, "Target type must not be null!");
return operations.exchange(traverseToExpandedFinalUrl(), GET, prepareRequest(headers), type);
return operations.exchange(traverseToExpandedFinalUrl(), method, prepareRequest(headers, payload), type);
}

/**
Expand Down Expand Up @@ -419,7 +423,7 @@ private String getAndFindLinkWithRel(String uri, Iterator<Hop> rels) {
return uri;
}

HttpEntity<?> request = prepareRequest(headers);
HttpEntity<?> request = prepareRequest(headers, payload);
UriTemplate template = new UriTemplate(uri);

ResponseEntity<String> responseEntity = operations.exchange(template.expand(), GET, request, String.class);
Expand All @@ -445,5 +449,39 @@ private String getAndFindLinkWithRel(String uri, Iterator<Hop> rels) {
return getAndFindLinkWithRel(link.expand(thisHop.getMergedParameters(templateParameters)).getHref(), rels);
}
}

public <T> TraversalBuilder post(T payload, MediaType contentType) {
addWriteContent(payload, contentType);
this.method = POST;
return this;
}

public <T> TraversalBuilder put(T payload, MediaType contentType) {
addWriteContent(payload, contentType);
this.method = PUT;
return this;
}

private <T> void addWriteContent(T payload, MediaType contentType) {
Assert.notNull(payload, "Payload must not be null!");
Assert.notNull(contentType, "ContentType must not be null!");
this.payload = payload;
this.headers.setContentType(contentType);
}

public TraversalBuilder head() {
this.method = HEAD;
return this;
}

public TraversalBuilder delete() {
this.method = DELETE;
return this;
}

public TraversalBuilder get() {
this.method = GET;
return this;
}
}
}
29 changes: 29 additions & 0 deletions src/test/java/org/springframework/hateoas/client/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
*
* @author Oliver Gierke
* @author Greg Turnquist
* @author Haroun Pacquee
*/
public class Server implements Closeable {

Expand Down Expand Up @@ -96,6 +97,34 @@ public Server() {
withBody("{ \"_links\" : { \"self\" : { \"href\" : \"/{?template}\" }}}"). //
withContentType(MediaTypes.HAL_JSON.toString());

//For POST HttpMethod

onRequest(). //
havingPathEqualTo("/225"). //
havingMethodEqualTo("POST"). //
respond(). //
withStatus(201). //
withHeader("location", "1");


//For PUT HttpMethod

onRequest(). //
havingPathEqualTo("/225"). //
havingMethodEqualTo("PUT"). //
respond(). //
withStatus(204). //
withHeader("location", "1");

//For DELETE HttpMethod

onRequest(). //
havingPathEqualTo("/225"). //
havingMethodEqualTo("DELETE"). //
respond(). //
withStatus(202);


// Sample traversal of HAL docs based on Spring-a-Gram showcase
org.springframework.core.io.Resource springagramRoot = resourceLoader.getResource("classpath:springagram-root.json");
org.springframework.core.io.Resource springagramItems = resourceLoader.getResource("classpath:springagram-items.json");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,7 @@

import java.io.IOException;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;

import org.junit.After;
import org.junit.Before;
Expand All @@ -37,9 +33,7 @@
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.client.Traverson.TraversalBuilder;
import org.springframework.hateoas.core.JsonPathLinkDiscoverer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.MediaType;
import org.springframework.http.*;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
Expand All @@ -53,6 +47,7 @@
*
* @author Oliver Gierke
* @author Greg Turnquist
* @author Haroun Pacquee
* @since 0.11
*/
public class TraversonTest {
Expand Down Expand Up @@ -410,4 +405,76 @@ public GitHubLinkDiscoverer() {
super("$.%s_url", MediaType.APPLICATION_JSON);
}
}


/**
* @see #225
*/
@Test
public void doesPost() {

RestTemplate restTemplate = new RestTemplate();

List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
messageConverters.add(new MappingJackson2HttpMessageConverter());
restTemplate.setMessageConverters(messageConverters);

traverson = new Traverson(URI.create(server.rootResource() + "/225"), MediaTypes.HAL_JSON);
traverson.setRestOperations(restTemplate);

final Item item = new Item("img", "desc");
ResponseEntity<Void> voidResponseEntity = traverson.follow()
.post(item, MediaType.APPLICATION_JSON)
.toEntity(Void.class);

assertThat(voidResponseEntity.getStatusCode(), is(HttpStatus.CREATED));
assertThat(voidResponseEntity.getHeaders().get("location"), contains("1"));
}

/**
* @see #225
*/
@Test
public void doesPatch() {

RestTemplate restTemplate = new RestTemplate();

List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
messageConverters.add(new MappingJackson2HttpMessageConverter());
restTemplate.setMessageConverters(messageConverters);

traverson = new Traverson(URI.create(server.rootResource() + "/225"), MediaTypes.HAL_JSON);
traverson.setRestOperations(restTemplate);

ResponseEntity<Void> voidResponseEntity = traverson.follow()
.delete()
.toEntity(Void.class);

assertThat(voidResponseEntity.getStatusCode(), is(HttpStatus.ACCEPTED));
}

/**
* @see #225
*/
@Test
public void doesPut() {

RestTemplate restTemplate = new RestTemplate();

List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
messageConverters.add(new MappingJackson2HttpMessageConverter());
restTemplate.setMessageConverters(messageConverters);

traverson = new Traverson(URI.create(server.rootResource() + "/225"), MediaTypes.HAL_JSON);
traverson.setRestOperations(restTemplate);

final Item item = new Item("img", "desc");
ResponseEntity<Void> voidResponseEntity = traverson.follow()
.put(item, MediaType.APPLICATION_JSON)
.toEntity(Void.class);

assertThat(voidResponseEntity.getStatusCode(), is(HttpStatus.NO_CONTENT));
assertThat(voidResponseEntity.getHeaders().get("location"), contains("1"));
}

}