diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ControllerUtils.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ControllerUtils.java index 2183d96be..d1c479ecc 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ControllerUtils.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ControllerUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2014 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,15 +27,23 @@ import org.springframework.http.ResponseEntity; /** * @author Oliver Gierke + * @author Greg Turnquist */ public class ControllerUtils { - public static final Resource EMPTY_RESOURCE = new Resource(new Object()); public static final Resources> EMPTY_RESOURCES = new Resources>( Collections.> emptyList()); public static final Iterable> EMPTY_RESOURCE_LIST = Collections.emptyList(); public static final TypeDescriptor STRING_TYPE = TypeDescriptor.valueOf(String.class); + /** + * Wrap a resource as a {@link ResourceEntity} and attach given headers and status. + * @param headers + * @param resource + * @param status + * @param + * @return + */ public static ResponseEntity toResponseEntity(HttpHeaders headers, R resource, HttpStatus status) { @@ -46,4 +54,23 @@ public class ControllerUtils { return new ResponseEntity(resource, hdrs, status); } + + /** + * Return an empty response that is only comprised of a status + * @param status + * @return + */ + public static ResponseEntity toEmptyResponse(HttpStatus status) { + return toResponseEntity(null, EMPTY_RESOURCES, status); + } + + /** + * Return an empty response that is only comprised of headers and a status + * @param headers + * @param status + * @return + */ + public static ResponseEntity toEmptyResponse(HttpHeaders headers, HttpStatus status) { + return toResponseEntity(headers, EMPTY_RESOURCES, status); + } } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java index 88381f3ac..299a616e6 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2014 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. @@ -18,9 +18,11 @@ package org.springframework.data.rest.webmvc; import static org.springframework.data.rest.webmvc.ControllerUtils.*; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*; +import javax.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -58,10 +60,12 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.servlet.HandlerMapping; /** * @author Jon Brisbin * @author Oliver Gierke + * @author Greg Turnquist */ @RepositoryRestController @SuppressWarnings({ "unchecked" }) @@ -188,7 +192,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes } } - return ControllerUtils.toResponseEntity(null, EMPTY_RESOURCE, HttpStatus.NO_CONTENT); + return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); } @RequestMapping(value = BASE_MAPPING + "/{propertyId}", method = RequestMethod.GET, produces = { "application/json", @@ -291,11 +295,13 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes return ControllerUtils.toResponseEntity(null, new Resource(EMPTY_RESOURCE_LIST, links), HttpStatus.OK); } - @RequestMapping(value = BASE_MAPPING, method = { RequestMethod.POST, RequestMethod.PUT }, consumes = { - "application/json", "application/x-spring-data-compact+json", "text/uri-list" }) + @RequestMapping(value = BASE_MAPPING, // + method = { RequestMethod.POST, RequestMethod.PUT }, // + consumes = { "application/json", "application/x-spring-data-compact+json", "text/uri-list" }) @ResponseBody public ResponseEntity createPropertyReference(final RepositoryRestRequest repoRequest, - final @RequestBody Resource incoming, @PathVariable String id, @PathVariable String property) + final @RequestBody Resources incoming, @PathVariable String id, @PathVariable String property, + final HttpServletRequest request) throws NoSuchMethodException { final RepositoryInvoker invoker = repoRequest.getRepositoryInvoker(); @@ -310,12 +316,16 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes if (prop.property.isCollectionLike()) { - Collection coll = new ArrayList(); + Collection coll; + // Either load the exist collection to add to it (POST) if (HttpMethod.POST.equals(repoRequest.getRequestMethod())) { - coll.addAll((Collection) prop.propertyValue); + coll = (Collection) prop.propertyValue; + } else { // Or start from an empty collection to replace it (PUT) + coll = new ArrayList(); } + // Add to the existing collection for (Link l : incoming.getLinks()) { Object propVal = loadPropertyValue(prop.propertyType, l.getHref()); coll.add(propVal); @@ -325,12 +335,16 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes } else if (prop.property.isMap()) { - Map m = new HashMap(); + Map m; + // Either load the exist collection to add to it (POST) if (HttpMethod.POST.equals(repoRequest.getRequestMethod())) { - m.putAll((Map) prop.propertyValue); + m = (Map) prop.propertyValue; + } else { // Or start from an empty collection to replace it (PUT) + m = new HashMap(); } + // Add to the existing collection for (Link l : incoming.getLinks()) { Object propVal = loadPropertyValue(prop.propertyType, l.getHref()); m.put(l.getRel(), propVal); @@ -364,7 +378,10 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes doWithReferencedProperty(repoRequest, id, property, handler); - return ControllerUtils.toResponseEntity(null, EMPTY_RESOURCE, HttpStatus.CREATED); + final HttpHeaders headers = new HttpHeaders(); + headers.set("Location", String.valueOf(request.getRequestURL())); + + return ControllerUtils.toEmptyResponse(headers, HttpStatus.CREATED); } @RequestMapping(value = BASE_MAPPING + "/{propertyId}", method = RequestMethod.DELETE) @@ -389,25 +406,27 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes } if (prop.property.isCollectionLike()) { - Collection coll = new ArrayList(); - for (Object obj : (Collection) prop.propertyValue) { + Collection coll = (Collection) prop.propertyValue; + Iterator itr = coll.iterator(); + while (itr.hasNext()) { + Object obj = itr.next(); BeanWrapper propValWrapper = BeanWrapper.create(obj, null); String s = propValWrapper.getProperty(prop.entity.getIdProperty()).toString(); - if (!propertyId.equals(s)) { - coll.add(obj); + if (propertyId.equals(s)) { + itr.remove(); } } - prop.wrapper.setProperty(prop.property, coll); } else if (prop.property.isMap()) { - Map m = new HashMap(); - for (Map.Entry entry : ((Map) prop.propertyValue).entrySet()) { - BeanWrapper propValWrapper = BeanWrapper.create(entry.getValue(), null); + Map m = (Map) prop.propertyValue; + Iterator itr = m.keySet().iterator(); + while (itr.hasNext()) { + Object key = itr.next(); + BeanWrapper propValWrapper = BeanWrapper.create(m.get(key), null); String s = propValWrapper.getProperty(prop.entity.getIdProperty()).toString(); - if (!propertyId.equals(s)) { - m.put(entry.getKey(), entry.getValue()); + if (propertyId.equals(s)) { + itr.remove(); } } - prop.wrapper.setProperty(prop.property, m); } else { prop.wrapper.setProperty(prop.property, null); } @@ -422,7 +441,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes doWithReferencedProperty(repoRequest, id, property, handler); - return ControllerUtils.toResponseEntity(null, EMPTY_RESOURCE, HttpStatus.NO_CONTENT); + return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); } private Object loadPropertyValue(Class type, String href) { diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/convert/UriListHttpMessageConverter.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/convert/UriListHttpMessageConverter.java index 3b46199fc..36272d535 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/convert/UriListHttpMessageConverter.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/convert/UriListHttpMessageConverter.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014 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.data.rest.webmvc.convert; import java.io.BufferedReader; @@ -10,7 +25,8 @@ import java.util.Collections; import java.util.List; import org.springframework.hateoas.Link; -import org.springframework.hateoas.Resource; +import org.springframework.hateoas.ResourceSupport; +import org.springframework.hateoas.Resources; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; @@ -20,8 +36,9 @@ import org.springframework.http.converter.HttpMessageNotWritableException; /** * @author Jon Brisbin + * @author Greg Turnquist */ -public class UriListHttpMessageConverter implements HttpMessageConverter> { +public class UriListHttpMessageConverter implements HttpMessageConverter { private static final List MEDIA_TYPES = new ArrayList(); @@ -34,7 +51,7 @@ public class UriListHttpMessageConverter implements HttpMessageConverter read(Class> clazz, HttpInputMessage inputMessage) throws IOException, + public ResourceSupport read(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { List links = new ArrayList(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputMessage.getBody())); @@ -56,11 +73,11 @@ public class UriListHttpMessageConverter implements HttpMessageConverter(Collections.emptyList(), links); + return new Resources(Collections.emptyList(), links); } @Override - public void write(Resource resource, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, + public void write(ResourceSupport resource, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputMessage.getBody())); for (Link link : resource.getLinks()) { diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/AbstractWebIntegrationTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/AbstractWebIntegrationTests.java index f340d83a2..8173ae5cc 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/AbstractWebIntegrationTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/AbstractWebIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2014 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. @@ -50,6 +50,7 @@ import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilde import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; import org.springframework.web.context.WebApplicationContext; import com.jayway.jsonpath.InvalidPathException; @@ -57,6 +58,7 @@ import com.jayway.jsonpath.JsonPath; /** * @author Oliver Gierke + * @author Greg Turnquist */ @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @@ -134,6 +136,74 @@ public abstract class AbstractWebIntegrationTests { return assertHasLinkWithRel(rel, response); } + protected MockHttpServletResponse postAndGet(Link link, Object payload, MediaType mediaType) throws Exception { + + String href; + if (link.isTemplated()) { + href = link.expand().getHref(); + } else { + href = link.getHref(); + } + + MockHttpServletResponse response = mvc.perform(post(href).content(payload.toString()).contentType(mediaType)).// + andExpect(status().isCreated()).// + andExpect(header().string("Location", is(notNullValue()))).// + andReturn().getResponse(); + + String content = response.getContentAsString(); + + if (StringUtils.hasText(content)) { + return response; + } + + return request(response.getHeader("Location")); + } + + protected MockHttpServletResponse putAndGet(Link link, Object payload, MediaType mediaType) throws Exception { + + String href; + if (link.isTemplated()) { + href = link.expand().getHref(); + } else { + href = link.getHref(); + } + + MockHttpServletResponse response = mvc.perform(put(href).content(payload.toString()).contentType(mediaType)).// + andExpect(status().isCreated()).// + andExpect(header().string("Location", is(notNullValue()))).// + andReturn().getResponse(); + + String content = response.getContentAsString(); + + if (StringUtils.hasText(content)) { + return response; + } + + return request(response.getHeader("Location")); + } + + protected MockHttpServletResponse deleteAndGet(Link link, MediaType mediaType) throws Exception { + + String href; + if (link.isTemplated()) { + href = link.expand().getHref(); + } else { + href = link.getHref(); + } + + MockHttpServletResponse response = mvc.perform(delete(href).contentType(mediaType)).// + andExpect(status().isNoContent()).// + andReturn().getResponse(); + + String content = response.getContentAsString(); + + if (StringUtils.hasText(content)) { + return response; + } + + return request(response.getHeader("Location")); + } + protected Link assertHasLinkWithRel(String rel, MockHttpServletResponse response) throws Exception { String content = response.getContentAsString(); diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaWebTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaWebTests.java index fd42ef761..f1e5650be 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaWebTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaWebTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2014 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. @@ -20,11 +20,14 @@ import static org.junit.Assert.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.Scanner; +import net.minidev.json.JSONArray; + import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -39,15 +42,23 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.jayway.jsonpath.JsonPath; +import org.springframework.util.StringUtils; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriTemplate; + /** * Web integration tests specific to JPA. * * @author Oliver Gierke + * @author Greg Turnquist */ @Transactional @ContextConfiguration(classes = JpaRepositoryConfig.class) public class JpaWebTests extends AbstractWebIntegrationTests { + private static final MediaType TEXT_URI_LIST = MediaType.valueOf("text/uri-list"); static final String LINK_TO_SIBLINGS_OF = "$._embedded..[?(@.firstName == '%s')]._links.siblings.href[0]"; @Autowired TestDataPopulator loader; @@ -183,6 +194,203 @@ public class JpaWebTests extends AbstractWebIntegrationTests { mvc.perform(get(href)).andExpect(status().isOk()); } + + /** + * @see DATAREST-219 + */ + @Test + public void manipulatePropertyCollectionRestfullyWithMultiplePosts() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + + String bilbo = mapper.writeValueAsString(new Person("Bilbo", "Baggins")); + String frodo = mapper.writeValueAsString(new Person("Frodo", "Baggins")); + String merry = mapper.writeValueAsString(new Person("Merry", "Baggins")); + String pippin = mapper.writeValueAsString(new Person("Pippin", "Baggins")); + + Link peopleLink = discoverUnique("people"); + + MockHttpServletResponse bilboResponse = postAndGet(peopleLink, bilbo, MediaType.APPLICATION_JSON); + MockHttpServletResponse frodoResponse = postAndGet(peopleLink, frodo, MediaType.APPLICATION_JSON); + MockHttpServletResponse merryResponse = postAndGet(peopleLink, merry, MediaType.APPLICATION_JSON); + MockHttpServletResponse pippinResponse = postAndGet(peopleLink, pippin, MediaType.APPLICATION_JSON); + + Link bilboSelfLink = assertHasLinkWithRel(Link.REL_SELF, bilboResponse); + Link merrySelfLink = assertHasLinkWithRel(Link.REL_SELF, merryResponse); + Link pippinSelfLink = assertHasLinkWithRel(Link.REL_SELF, pippinResponse); + Link frodosSiblingsLink = assertHasLinkWithRel("siblings", frodoResponse); + + postAndGet(frodosSiblingsLink, bilboSelfLink.getHref(), TEXT_URI_LIST); + postAndGet(frodosSiblingsLink, merrySelfLink.getHref(), TEXT_URI_LIST); + postAndGet(frodosSiblingsLink, pippinSelfLink.getHref(), TEXT_URI_LIST); + + MockHttpServletResponse frodosLatestSiblings = request(frodosSiblingsLink); + String[] persons = ((JSONArray) JsonPath.read(frodosLatestSiblings.getContentAsString(), "$._embedded.persons[*].firstName")).toArray(new String[]{}); + assertThat(persons.length, equalTo(3)); + assertThat(Arrays.asList(persons), hasItems("Bilbo", "Merry", "Pippin")); + } + + /** + * @see DATAREST-219 + */ + @Test + public void manipulatePropertyCollectionRestfullyWithSinglePost() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + + String bilbo = mapper.writeValueAsString(new Person("Bilbo", "Baggins")); + String frodo = mapper.writeValueAsString(new Person("Frodo", "Baggins")); + String merry = mapper.writeValueAsString(new Person("Merry", "Baggins")); + String pippin = mapper.writeValueAsString(new Person("Pippin", "Baggins")); + + Link peopleLink = discoverUnique("people"); + + MockHttpServletResponse bilboResponse = postAndGet(peopleLink, bilbo, MediaType.APPLICATION_JSON); + MockHttpServletResponse frodoResponse = postAndGet(peopleLink, frodo, MediaType.APPLICATION_JSON); + MockHttpServletResponse merryResponse = postAndGet(peopleLink, merry, MediaType.APPLICATION_JSON); + MockHttpServletResponse pippinResponse = postAndGet(peopleLink, pippin, MediaType.APPLICATION_JSON); + + final Link bilboSelfLink = assertHasLinkWithRel(Link.REL_SELF, bilboResponse); + final Link merrySelfLink = assertHasLinkWithRel(Link.REL_SELF, merryResponse); + final Link pippinSelfLink = assertHasLinkWithRel(Link.REL_SELF, pippinResponse); + Link frodosSiblingsLink = assertHasLinkWithRel("siblings", frodoResponse); + + postAndGet(frodosSiblingsLink, + StringUtils.arrayToDelimitedString(new Object[]{bilboSelfLink.getHref(), + merrySelfLink.getHref(), pippinSelfLink.getHref()}, "\n"), + TEXT_URI_LIST); + + MockHttpServletResponse frodosLatestSiblings = request(frodosSiblingsLink); + String[] persons = ((JSONArray) JsonPath.read(frodosLatestSiblings.getContentAsString(), "$._embedded.persons[*].firstName")).toArray(new String[]{}); + assertThat(persons.length, equalTo(3)); + assertThat(Arrays.asList(persons), hasItems("Bilbo", "Merry", "Pippin")); + } + + /** + * @see DATAREST-219 + */ + @Test + public void manipulatePropertyCollectionRestfullyWithMultiplePuts() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + + String bilbo = mapper.writeValueAsString(new Person("Bilbo", "Baggins")); + String frodo = mapper.writeValueAsString(new Person("Frodo", "Baggins")); + String merry = mapper.writeValueAsString(new Person("Merry", "Baggins")); + String pippin = mapper.writeValueAsString(new Person("Pippin", "Baggins")); + + Link peopleLink = discoverUnique("people"); + + MockHttpServletResponse bilboResponse = postAndGet(peopleLink, bilbo, MediaType.APPLICATION_JSON); + MockHttpServletResponse frodoResponse = postAndGet(peopleLink, frodo, MediaType.APPLICATION_JSON); + MockHttpServletResponse merryResponse = postAndGet(peopleLink, merry, MediaType.APPLICATION_JSON); + MockHttpServletResponse pippinResponse = postAndGet(peopleLink, pippin, MediaType.APPLICATION_JSON); + + Link bilboSelfLink = assertHasLinkWithRel(Link.REL_SELF, bilboResponse); + Link merrySelfLink = assertHasLinkWithRel(Link.REL_SELF, merryResponse); + Link pippinSelfLink = assertHasLinkWithRel(Link.REL_SELF, pippinResponse); + Link frodosSiblingsLink = assertHasLinkWithRel("siblings", frodoResponse); + + putAndGet(frodosSiblingsLink, bilboSelfLink.getHref(), TEXT_URI_LIST); + putAndGet(frodosSiblingsLink, merrySelfLink.getHref(), TEXT_URI_LIST); + putAndGet(frodosSiblingsLink, pippinSelfLink.getHref(), TEXT_URI_LIST); + + MockHttpServletResponse frodosLatestSiblings = request(frodosSiblingsLink); + String firstName = JsonPath.read(frodosLatestSiblings.getContentAsString(), "$._embedded.person.firstName"); + assertThat(firstName, equalTo("Pippin")); + + postAndGet(frodosSiblingsLink, merrySelfLink.getHref(), TEXT_URI_LIST); + + frodosLatestSiblings = request(frodosSiblingsLink); + String[] persons = ((JSONArray) JsonPath.read(frodosLatestSiblings.getContentAsString(), + "$._embedded.persons[*].firstName")).toArray(new String[]{}); + assertThat(persons.length, equalTo(2)); + assertThat(Arrays.asList(persons), hasItems("Merry", "Pippin")); + } + + /** + * @see DATAREST-219 + */ + @Test + public void manipulatePropertyCollectionRestfullyWithSinglePut() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + + String bilbo = mapper.writeValueAsString(new Person("Bilbo", "Baggins")); + String frodo = mapper.writeValueAsString(new Person("Frodo", "Baggins")); + String merry = mapper.writeValueAsString(new Person("Merry", "Baggins")); + String pippin = mapper.writeValueAsString(new Person("Pippin", "Baggins")); + + Link peopleLink = discoverUnique("people"); + + MockHttpServletResponse bilboResponse = postAndGet(peopleLink, bilbo, MediaType.APPLICATION_JSON); + MockHttpServletResponse frodoResponse = postAndGet(peopleLink, frodo, MediaType.APPLICATION_JSON); + MockHttpServletResponse merryResponse = postAndGet(peopleLink, merry, MediaType.APPLICATION_JSON); + MockHttpServletResponse pippinResponse = postAndGet(peopleLink, pippin, MediaType.APPLICATION_JSON); + + Link bilboSelfLink = assertHasLinkWithRel(Link.REL_SELF, bilboResponse); + Link merrySelfLink = assertHasLinkWithRel(Link.REL_SELF, merryResponse); + Link pippinSelfLink = assertHasLinkWithRel(Link.REL_SELF, pippinResponse); + Link frodosSiblingsLink = assertHasLinkWithRel("siblings", frodoResponse); + + putAndGet(frodosSiblingsLink, + StringUtils.arrayToDelimitedString(new Object[]{bilboSelfLink.getHref(), + merrySelfLink.getHref()}, "\n"), + TEXT_URI_LIST); + putAndGet(frodosSiblingsLink, pippinSelfLink.getHref(), TEXT_URI_LIST); + + MockHttpServletResponse frodosLatestSiblings = request(frodosSiblingsLink); + String firstName = JsonPath.read(frodosLatestSiblings.getContentAsString(), "$._embedded.person.firstName"); + assertThat(firstName, equalTo("Pippin")); + + postAndGet(frodosSiblingsLink, merrySelfLink.getHref(), TEXT_URI_LIST); + + frodosLatestSiblings = request(frodosSiblingsLink); + String[] persons = ((JSONArray) JsonPath.read(frodosLatestSiblings.getContentAsString(), + "$._embedded.persons[*].firstName")).toArray(new String[]{}); + assertThat(persons.length, equalTo(2)); + assertThat(Arrays.asList(persons), hasItems("Merry", "Pippin")); + } + + /** + * @see DATAREST-219 + */ + @Test + public void manipulatePropertyCollectionRestfullyWithDelete() throws Exception { + + ObjectMapper mapper = new ObjectMapper(); + + String bilbo = mapper.writeValueAsString(new Person("Bilbo", "Baggins")); + String frodo = mapper.writeValueAsString(new Person("Frodo", "Baggins")); + String merry = mapper.writeValueAsString(new Person("Merry", "Baggins")); + String pippin = mapper.writeValueAsString(new Person("Pippin", "Baggins")); + + Link peopleLink = discoverUnique("people"); + + MockHttpServletResponse bilboResponse = postAndGet(peopleLink, bilbo, MediaType.APPLICATION_JSON); + MockHttpServletResponse frodoResponse = postAndGet(peopleLink, frodo, MediaType.APPLICATION_JSON); + MockHttpServletResponse merryResponse = postAndGet(peopleLink, merry, MediaType.APPLICATION_JSON); + MockHttpServletResponse pippinResponse = postAndGet(peopleLink, pippin, MediaType.APPLICATION_JSON); + + Link bilboSelfLink = assertHasLinkWithRel(Link.REL_SELF, bilboResponse); + Link merrySelfLink = assertHasLinkWithRel(Link.REL_SELF, merryResponse); + Link pippinSelfLink = assertHasLinkWithRel(Link.REL_SELF, pippinResponse); + Link frodosSiblingsLink = assertHasLinkWithRel("siblings", frodoResponse); + + postAndGet(frodosSiblingsLink, bilboSelfLink.getHref(), TEXT_URI_LIST); + postAndGet(frodosSiblingsLink, merrySelfLink.getHref(), TEXT_URI_LIST); + postAndGet(frodosSiblingsLink, pippinSelfLink.getHref(), TEXT_URI_LIST); + + String pippinId = new UriTemplate("/people/{id}").match(pippinSelfLink.getHref()).get("id"); + deleteAndGet(new Link(frodosSiblingsLink.getHref() + "/" + pippinId), TEXT_URI_LIST); + + MockHttpServletResponse frodosLatestSiblings = request(frodosSiblingsLink); + String[] persons = ((JSONArray) JsonPath.read(frodosLatestSiblings.getContentAsString(), "$._embedded.persons[*].firstName")).toArray(new String[]{}); + assertThat(persons.length, equalTo(2)); + assertThat(Arrays.asList(persons), hasItems("Bilbo", "Merry")); + } + + private String readFile(String name) throws Exception { ClassPathResource file = new ClassPathResource(name, getClass());