diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/PersistentEntityResource.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/PersistentEntityResource.java index a9edff4c7..145dcb78e 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/PersistentEntityResource.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/PersistentEntityResource.java @@ -27,10 +27,11 @@ import com.fasterxml.jackson.annotation.JsonIgnore; * A Spring HATEOAS {@link Resource} subclass that holds a reference to the entity's {@link PersistentEntity} metadata. * * @author Jon Brisbin + * @author Oliver Gierke */ public class PersistentEntityResource extends Resource { - @JsonIgnore private final PersistentEntity entity; + private final PersistentEntity entity; public static PersistentEntityResource wrap(PersistentEntity entity, T obj) { return new PersistentEntityResource(entity, obj); @@ -45,6 +46,7 @@ public class PersistentEntityResource extends Resource { this.entity = entity; } + @JsonIgnore public PersistentEntity getPersistentEntity() { return entity; } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/PersistentEntityResourceHandlerMethodArgumentResolver.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/PersistentEntityResourceHandlerMethodArgumentResolver.java index b2589633c..4bc1b1a3d 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/PersistentEntityResourceHandlerMethodArgumentResolver.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/PersistentEntityResourceHandlerMethodArgumentResolver.java @@ -1,35 +1,79 @@ +/* + * Copyright 2012-2013 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; import java.util.List; import javax.servlet.http.HttpServletRequest; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.MethodParameter; +import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.util.Assert; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; /** + * Custom {@link HandlerMethodArgumentResolver} to create {@link PersistentEntityResource} instances. + * * @author Jon Brisbin + * @author Oliver Gierke */ public class PersistentEntityResourceHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver { - @Autowired private RepositoryRestRequestHandlerMethodArgumentResolver repoRequestResolver; + private static final String ERROR_MESSAGE = "Could not read an object of type %s from the request! Converter %s returned null!"; + private static final String NO_CONVERTER_FOUND = "No suitable HttpMessageConverter found to read request body into object of type %s from request with content type of %s!"; + + private final RepositoryRestRequestHandlerMethodArgumentResolver repoRequestResolver; private final List> messageConverters; - public PersistentEntityResourceHandlerMethodArgumentResolver(List> messageConverters) { + /** + * Creates a new {@link PersistentEntityResourceHandlerMethodArgumentResolver} for the given + * {@link HttpMessageConverter}s and {@link RepositoryRestRequestHandlerMethodArgumentResolver}.. + * + * @param messageConverters must not be {@literal null}. + * @param repositoryRequestResolver must not be {@literal null}. + */ + public PersistentEntityResourceHandlerMethodArgumentResolver(List> messageConverters, + RepositoryRestRequestHandlerMethodArgumentResolver repositoryRequestResolver) { + + Assert.notEmpty(messageConverters, "MessageConverters must not be null or empty!"); + Assert.notNull(repositoryRequestResolver, "RepositoryRestRequestHandlerMethodArgumentResolver must not be empty!"); + this.messageConverters = messageConverters; + this.repoRequestResolver = repositoryRequestResolver; } + /* + * (non-Javadoc) + * @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter) + */ @Override public boolean supportsParameter(MethodParameter parameter) { return PersistentEntityResource.class.isAssignableFrom(parameter.getParameterType()); } + /* + * (non-Javadoc) + * @see org.springframework.web.method.support.HandlerMethodArgumentResolver#resolveArgument(org.springframework.core.MethodParameter, org.springframework.web.method.support.ModelAndViewContainer, org.springframework.web.context.request.NativeWebRequest, org.springframework.web.bind.support.WebDataBinderFactory) + */ @Override @SuppressWarnings({ "unchecked", "rawtypes" }) public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, @@ -37,19 +81,27 @@ public class PersistentEntityResourceHandlerMethodArgumentResolver implements Ha RepositoryRestRequest repoRequest = (RepositoryRestRequest) repoRequestResolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory); - final ServletServerHttpRequest request = new ServletServerHttpRequest( - webRequest.getNativeRequest(HttpServletRequest.class)); + HttpServletRequest nativeRequest = webRequest.getNativeRequest(HttpServletRequest.class); + ServletServerHttpRequest request = new ServletServerHttpRequest(nativeRequest); + + Class domainType = repoRequest.getPersistentEntity().getType(); + MediaType contentType = request.getHeaders().getContentType(); + for (HttpMessageConverter converter : messageConverters) { - Class domainType = repoRequest.getPersistentEntity().getType(); - if (!converter.canRead(domainType, request.getHeaders().getContentType())) { + + if (!converter.canRead(domainType, contentType)) { continue; } Object obj = converter.read(domainType, request); + + if (obj == null) { + throw new HttpMessageNotReadableException(String.format(ERROR_MESSAGE, domainType, converter)); + } + return new PersistentEntityResource(repoRequest.getPersistentEntity(), obj); } - return null; + throw new HttpMessageNotReadableException(String.format(NO_CONVERTER_FOUND, domainType, contentType)); } - } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java index 85c563cc0..a151d2f7a 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java @@ -254,10 +254,11 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon */ @Bean public PersistentEntityResourceHandlerMethodArgumentResolver persistentEntityArgumentResolver() { + List> messageConverters = defaultMessageConverters(); configureHttpMessageConverters(messageConverters); - return new PersistentEntityResourceHandlerMethodArgumentResolver(messageConverters); + return new PersistentEntityResourceHandlerMethodArgumentResolver(messageConverters, repoRequestArgumentResolver()); } /** @@ -432,7 +433,8 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon return new DefaultRepositoryInvokerFactory(repositories(), defaultConversionService()); } - private List> defaultMessageConverters() { + @Bean + public List> defaultMessageConverters() { List> messageConverters = new ArrayList>(); 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 4a0cdcf85..fbd590f52 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 @@ -17,10 +17,13 @@ package org.springframework.data.rest.webmvc; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; +import static org.junit.Assume.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import java.util.Collections; import java.util.List; +import java.util.Map; import org.junit.Before; import org.junit.Test; @@ -40,6 +43,7 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.ResultMatcher; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; @@ -272,5 +276,34 @@ public abstract class AbstractWebIntegrationTests { } } + @Test + public void postsPayloadToResource() throws Exception { + + Map payloads = getPayloadToPost(); + assumeFalse(payloads.isEmpty()); + + MockHttpServletResponse response = request("/"); + + for (String rel : expectedRootLinkRels()) { + + String payload = payloads.get(rel); + + if (payload != null) { + Link link = assertHasLinkWithRel(rel, response); + + MockHttpServletRequestBuilder request = post(link.getHref()).// + content(payload).// + contentType(MediaType.APPLICATION_JSON); + + mvc.perform(request). // + andExpect(status().isCreated()); + } + } + } + protected abstract Iterable expectedRootLinkRels(); + + protected Map getPayloadToPost() throws Exception { + return Collections.emptyMap(); + } } 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 91416907c..989742070 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 @@ -23,7 +23,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.nio.charset.Charset; import java.nio.file.Files; import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.Map; import org.junit.Before; import org.junit.Test; @@ -72,6 +74,15 @@ public class JpaWebTests extends AbstractWebIntegrationTests { return Arrays.asList("people"); } + /* + * (non-Javadoc) + * @see org.springframework.data.rest.webmvc.AbstractWebIntegrationTests#getPayloadToPost() + */ + @Override + protected Map getPayloadToPost() throws Exception { + return Collections.singletonMap("people", readFile("person.json")); + } + /** * @see DATAREST-99 */ diff --git a/spring-data-rest-webmvc/src/test/resources/org/springframework/data/rest/webmvc/jpa/person.json b/spring-data-rest-webmvc/src/test/resources/org/springframework/data/rest/webmvc/jpa/person.json new file mode 100644 index 000000000..2a1f8c3c5 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/resources/org/springframework/data/rest/webmvc/jpa/person.json @@ -0,0 +1,3 @@ +{ "firstName" : "Dave", + "lastName" : "Matthews" +} \ No newline at end of file