From 4843a81fff3d164884efd9d554f3c04803f9c818 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Tue, 10 Jun 2014 16:57:11 +0200 Subject: [PATCH] DATAREST-316 - UriStringDeserializer now guards against UriTemplate. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We now defensively guard against URI templates submitted for related resources and expand the incoming URI string source to avoid URI.create(…) to fail. Also, RepositoryPropertyReferenceController.loadPropertyValue(…) now also uses UriTemplate to guard against Uri templates provided for property references. --- ...RepositoryPropertyReferenceController.java | 9 +- .../json/PersistentEntityJackson2Module.java | 7 +- .../json/UriStringDeserializerUnitTests.java | 85 +++++++++++++++++++ 3 files changed, 94 insertions(+), 7 deletions(-) create mode 100644 spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/UriStringDeserializerUnitTests.java 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 24cf2904d..22e49f2e2 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 @@ -319,7 +319,7 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro // Add to the existing collection for (Link l : incoming.getLinks()) { - Object propVal = loadPropertyValue(prop.propertyType, l.getHref()); + Object propVal = loadPropertyValue(prop.propertyType, l); coll.add(propVal); } @@ -336,7 +336,7 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro // Add to the existing collection for (Link l : incoming.getLinks()) { - Object propVal = loadPropertyValue(prop.propertyType, l.getHref()); + Object propVal = loadPropertyValue(prop.propertyType, l); m.put(l.getRel(), propVal); } @@ -354,7 +354,7 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro "Must send only 1 link to update a property reference that isn't a List or a Map."); } - Object propVal = loadPropertyValue(prop.propertyType, incoming.getLinks().get(0).getHref()); + Object propVal = loadPropertyValue(prop.propertyType, incoming.getLinks().get(0)); prop.wrapper.setProperty(prop.property, propVal); } @@ -431,8 +431,9 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); } - private Object loadPropertyValue(Class type, String href) { + private Object loadPropertyValue(Class type, Link link) { + String href = link.expand().getHref(); String id = href.substring(href.lastIndexOf('/') + 1); return conversionService.convert(id, type); } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2Module.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2Module.java index b1843bbdc..a959be711 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2Module.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2Module.java @@ -40,6 +40,7 @@ import org.springframework.data.rest.webmvc.PersistentEntityResource; import org.springframework.data.rest.webmvc.support.RepositoryLinkBuilder; import org.springframework.hateoas.Link; import org.springframework.hateoas.Resource; +import org.springframework.hateoas.UriTemplate; import org.springframework.util.Assert; import com.fasterxml.jackson.core.JsonGenerationException; @@ -355,7 +356,7 @@ public class PersistentEntityJackson2Module extends SimpleModule { * * @author Oliver Gierke */ - private static class UriStringDeserializer extends StdDeserializer { + static class UriStringDeserializer extends StdDeserializer { private static final long serialVersionUID = -2175900204153350125L; @@ -384,10 +385,10 @@ public class PersistentEntityJackson2Module extends SimpleModule { @Override public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { - String uriString = jp.getValueAsString(); + URI uri = new UriTemplate(jp.getValueAsString()).expand(); TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(property.getActualType()); - return converter.convert(URI.create(uriString), URI_DESCRIPTOR, typeDescriptor); + return converter.convert(uri, URI_DESCRIPTOR, typeDescriptor); } } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/UriStringDeserializerUnitTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/UriStringDeserializerUnitTests.java new file mode 100644 index 000000000..e9e4571e3 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/UriStringDeserializerUnitTests.java @@ -0,0 +1,85 @@ +/* + * 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.json; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import java.net.URI; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.rest.core.UriToEntityConverter; +import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module.UriStringDeserializer; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; + +/** + * Unit tests for {@link UriStringDeserializer}. + * + * @author Oliver Gierke + */ +@RunWith(MockitoJUnitRunner.class) +public class UriStringDeserializerUnitTests { + + @Mock UriToEntityConverter converter; + @Mock PersistentProperty property; + + @Mock JsonParser parser; + @Mock DeserializationContext context; + + UriStringDeserializer deserializer; + + @Before + public void setUp() { + this.deserializer = new UriStringDeserializer(property, converter); + } + + /** + * @see DATAREST-316 + */ + @Test + public void extractsUriToForwardToConverter() throws Exception { + assertConverterInvokedWithUri("/foo/32", URI.create("/foo/32")); + } + + /** + * @see DATAREST-316 + */ + @Test + public void extractsUriFromTemplateToForwardToConverter() throws Exception { + assertConverterInvokedWithUri("/foo/32{?projection}", URI.create("/foo/32")); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private void assertConverterInvokedWithUri(String source, URI expected) throws Exception { + + when(property.getActualType()).thenReturn((Class) Object.class); + when(parser.getValueAsString()).thenReturn(source); + + deserializer.deserialize(parser, context); + + verify(converter) + .convert(eq(expected), Mockito.any(TypeDescriptor.class), eq(TypeDescriptor.valueOf(Object.class))); + } +}