From 572690b8886e01911b7b78a024551942f58b4f52 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Tue, 11 Mar 2014 17:41:17 +0100 Subject: [PATCH] DATAREST-155 - Introduce API to alter exposed backend ids. Introduced BackendIdConverter SPI to be able to register custom components that will be used during URI construction and URI parsing to determine the actual backend identifier. To register a custom BackendIdConverter simply declare a bean definition for it in the ApplicationContext. The Spring Data REST URI creation infrastructure (RepositoryEntityLinks in particular) will pick it up transparently. --- .../data/rest/core/util/UriUtils.java | 25 ----- .../rest/core/util/UriUtilsUnitTests.java | 44 --------- .../webmvc/RepositoryEntityController.java | 17 ++-- ...RepositoryPropertyReferenceController.java | 19 ++-- .../RepositoryRestMvcConfiguration.java | 39 ++++++-- ...MetadataHandlerMethodArgumentResolver.java | 27 +----- .../rest/webmvc/spi/BackendIdConverter.java | 83 ++++++++++++++++ .../data/rest/webmvc/support/BackendId.java | 32 +++++++ ...ackendIdHandlerMethodArgumentResolver.java | 96 +++++++++++++++++++ .../rest/webmvc/support/HttpRequestUtils.java | 39 -------- .../webmvc/support/RepositoryEntityLinks.java | 18 +++- .../data/rest/webmvc/util/UriUtils.java | 76 +++++++++++++++ ...itoryEntityControllerIntegrationTests.java | 2 +- .../data/rest/webmvc/jpa/Book.java | 6 +- .../data/rest/webmvc/jpa/BookIdConverter.java | 66 +++++++++++++ .../data/rest/webmvc/jpa/BookRepository.java | 2 +- .../rest/webmvc/jpa/JpaRepositoryConfig.java | 9 +- ...ethodArgumentResolverIntegrationTests.java | 81 ++++++++++++++++ ...RepositoryEntityLinksIntegrationTests.java | 11 +++ 19 files changed, 529 insertions(+), 163 deletions(-) delete mode 100644 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/UriUtils.java delete mode 100644 spring-data-rest-core/src/test/java/org/springframework/data/rest/core/util/UriUtilsUnitTests.java create mode 100644 spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/spi/BackendIdConverter.java create mode 100644 spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/BackendId.java create mode 100644 spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/BackendIdHandlerMethodArgumentResolver.java delete mode 100644 spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/HttpRequestUtils.java create mode 100644 spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/util/UriUtils.java create mode 100644 spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/BookIdConverter.java create mode 100644 spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/support/BackendIdConverterHandlerMethodArgumentResolverIntegrationTests.java diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/UriUtils.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/UriUtils.java deleted file mode 100644 index c7cb057d2..000000000 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/UriUtils.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.springframework.data.rest.core.util; - -import java.net.URI; - -import org.springframework.web.util.UriComponentsBuilder; - -/** - * Helper methods for dealing with URIs. - * - * @author Jon Brisbin - */ -public abstract class UriUtils { - - /** - * Create a new {@link URI} out of the components. - * - * @param baseUri The base URI these path segments are relative to. - * @param pathSegments The path segments to add to the given base URI. - * @return A new URI built from the given base URI and additional path segments. - */ - public static URI buildUri(URI baseUri, String... pathSegments) { - return UriComponentsBuilder.fromUri(baseUri).pathSegment(pathSegments).build().toUri(); - } - -} diff --git a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/util/UriUtilsUnitTests.java b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/util/UriUtilsUnitTests.java deleted file mode 100644 index 77b5686a2..000000000 --- a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/util/UriUtilsUnitTests.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.core.util; - -import static org.hamcrest.MatcherAssert.*; -import static org.hamcrest.Matchers.*; - -import java.net.URI; - -import org.junit.Test; - -/** - * Tests to verify that {@link UriUtils} can manipulate {@link URI}s. - * - * @author Jon Brisbin - */ -public class UriUtilsUnitTests { - - private static final String BASE_URI_STR = "http://localhost:8080/data"; - private static final URI BASE_URI = URI.create(BASE_URI_STR); - - private static final String PERSON_2LVL_STR = BASE_URI_STR + "/person/1"; - private static final URI PERSON_2LVL_URI = URI.create(PERSON_2LVL_STR); - - @Test - public void shouldBuildURIFromPathSegments() throws Exception { - - URI uri = UriUtils.buildUri(BASE_URI, "person", "1"); - assertThat(uri, is(PERSON_2LVL_URI)); - } -} diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryEntityController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryEntityController.java index 656429fa6..7f0ae63c9 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryEntityController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryEntityController.java @@ -18,6 +18,7 @@ package org.springframework.data.rest.webmvc; import static org.springframework.data.rest.core.support.DomainObjectMerger.NullHandlingPolicy.*; import static org.springframework.http.HttpMethod.*; +import java.io.Serializable; import java.net.URI; import java.util.ArrayList; import java.util.Collections; @@ -44,6 +45,7 @@ import org.springframework.data.rest.core.mapping.ResourceMetadata; import org.springframework.data.rest.core.mapping.SearchResourceMappings; import org.springframework.data.rest.core.support.DomainObjectMerger; import org.springframework.data.rest.core.support.DomainObjectMerger.NullHandlingPolicy; +import org.springframework.data.rest.webmvc.support.BackendId; import org.springframework.data.web.PagedResourcesAssembler; import org.springframework.hateoas.EntityLinks; import org.springframework.hateoas.Link; @@ -56,7 +58,6 @@ import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.HttpRequestMethodNotSupportedException; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; @@ -168,7 +169,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem */ @ResponseBody @RequestMapping(value = BASE_MAPPING, method = RequestMethod.POST) - public ResponseEntity postEntity(RootResourceInformation resourceInformation, + public ResponseEntity postCollectionResource(RootResourceInformation resourceInformation, PersistentEntityResource payload, PersistentEntityResourceAssembler assembler) throws HttpRequestMethodNotSupportedException { @@ -187,7 +188,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem */ @RequestMapping(value = BASE_MAPPING + "/{id}", method = RequestMethod.GET) public ResponseEntity> getItemResource(RootResourceInformation resourceInformation, - @PathVariable String id, PersistentEntityResourceAssembler assembler) + @BackendId Serializable id, PersistentEntityResourceAssembler assembler) throws HttpRequestMethodNotSupportedException { resourceInformation.verifySupportedMethod(HttpMethod.GET, ResourceType.ITEM); @@ -217,8 +218,8 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem * @throws HttpRequestMethodNotSupportedException */ @RequestMapping(value = BASE_MAPPING + "/{id}", method = RequestMethod.PUT) - public ResponseEntity putEntity(RootResourceInformation resourceInformation, - PersistentEntityResource payload, @PathVariable String id, PersistentEntityResourceAssembler assembler) + public ResponseEntity putItemResource(RootResourceInformation resourceInformation, + PersistentEntityResource payload, @BackendId Serializable id, PersistentEntityResourceAssembler assembler) throws HttpRequestMethodNotSupportedException { resourceInformation.verifySupportedMethod(HttpMethod.PUT, ResourceType.ITEM); @@ -248,8 +249,8 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem * @throws ResourceNotFoundException */ @RequestMapping(value = BASE_MAPPING + "/{id}", method = RequestMethod.PATCH) - public ResponseEntity patchEntity(RootResourceInformation resourceInformation, - PersistentEntityResource payload, @PathVariable String id, PersistentEntityResourceAssembler assembler) + public ResponseEntity patchItemResource(RootResourceInformation resourceInformation, + PersistentEntityResource payload, @BackendId Serializable id, PersistentEntityResourceAssembler assembler) throws HttpRequestMethodNotSupportedException, ResourceNotFoundException { resourceInformation.verifySupportedMethod(HttpMethod.PATCH, ResourceType.ITEM); @@ -273,7 +274,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem * @throws HttpRequestMethodNotSupportedException */ @RequestMapping(value = BASE_MAPPING + "/{id}", method = RequestMethod.DELETE) - public ResponseEntity deleteEntity(final RootResourceInformation resourceInformation, @PathVariable final String id) + public ResponseEntity deleteItemResource(RootResourceInformation resourceInformation, @BackendId Serializable id) throws ResourceNotFoundException, HttpRequestMethodNotSupportedException { resourceInformation.verifySupportedMethod(HttpMethod.DELETE, ResourceType.ITEM); 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 d46665897..2d2201134 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 @@ -18,6 +18,7 @@ package org.springframework.data.rest.webmvc; import static org.springframework.data.rest.webmvc.ControllerUtils.*; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*; +import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -44,6 +45,7 @@ import org.springframework.data.rest.core.invoke.RepositoryInvoker; import org.springframework.data.rest.core.mapping.ResourceMapping; import org.springframework.data.rest.core.mapping.ResourceMetadata; import org.springframework.data.rest.core.util.Function; +import org.springframework.data.rest.webmvc.support.BackendId; import org.springframework.data.web.PagedResourcesAssembler; import org.springframework.hateoas.Link; import org.springframework.hateoas.Resource; @@ -100,7 +102,7 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro @RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET) public ResponseEntity followPropertyReference(final RootResourceInformation repoRequest, - @PathVariable String id, @PathVariable String property, final PersistentEntityResourceAssembler assembler) + @BackendId Serializable id, @PathVariable String property, final PersistentEntityResourceAssembler assembler) throws Exception { final HttpHeaders headers = new HttpHeaders(); @@ -149,7 +151,7 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro @RequestMapping(value = BASE_MAPPING, method = RequestMethod.DELETE) public ResponseEntity deletePropertyReference(final RootResourceInformation repoRequest, - @PathVariable String id, @PathVariable String property) throws Exception { + @BackendId Serializable id, @PathVariable String property) throws Exception { final RepositoryInvoker repoMethodInvoker = repoRequest.getInvoker(); @@ -189,7 +191,7 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro @RequestMapping(value = BASE_MAPPING + "/{propertyId}", method = RequestMethod.GET) public ResponseEntity followPropertyReference(final RootResourceInformation repoRequest, - @PathVariable String id, @PathVariable String property, final @PathVariable String propertyId, + @BackendId Serializable id, @PathVariable String property, final @PathVariable String propertyId, final PersistentEntityResourceAssembler assembler) throws Exception { final HttpHeaders headers = new HttpHeaders(); @@ -242,7 +244,7 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro @RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET, produces = { "application/x-spring-data-compact+json", "text/uri-list" }) public ResponseEntity followPropertyReferenceCompact(RootResourceInformation repoRequest, - @PathVariable String id, @PathVariable String property, PersistentEntityResourceAssembler assembler) + @BackendId Serializable id, @PathVariable String property, PersistentEntityResourceAssembler assembler) throws Exception { ResponseEntity response = followPropertyReference(repoRequest, id, property, assembler); @@ -294,7 +296,7 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro @ResponseBody public ResponseEntity createPropertyReference( final RootResourceInformation resourceInformation, final HttpMethod requestMethod, - final @RequestBody Resources incoming, @PathVariable String id, @PathVariable String property) + final @RequestBody Resources incoming, @BackendId Serializable id, @PathVariable String property) throws Exception { final RepositoryInvoker invoker = resourceInformation.getInvoker(); @@ -372,7 +374,8 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro @RequestMapping(value = BASE_MAPPING + "/{propertyId}", method = RequestMethod.DELETE) @ResponseBody public ResponseEntity deletePropertyReferenceId(final RootResourceInformation repoRequest, - @PathVariable String id, @PathVariable String property, final @PathVariable String propertyId) throws Exception { + @BackendId Serializable id, @PathVariable String property, final @PathVariable String propertyId) + throws Exception { final RepositoryInvoker invoker = repoRequest.getInvoker(); @@ -434,8 +437,8 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro return conversionService.convert(id, type); } - private ResourceSupport doWithReferencedProperty(RootResourceInformation repoRequest, String id, String propertyPath, - Function handler, HttpMethod method) throws Exception { + private ResourceSupport doWithReferencedProperty(RootResourceInformation repoRequest, Serializable id, + String propertyPath, Function handler, HttpMethod method) throws Exception { RepositoryInvoker invoker = repoRequest.getInvoker(); 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 386ebbefe..60a67570f 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 @@ -36,7 +36,6 @@ import org.springframework.context.annotation.Lazy; import org.springframework.context.support.MessageSourceAccessor; import org.springframework.context.support.ReloadableResourceBundleMessageSource; import org.springframework.core.convert.support.ConfigurableConversionService; -import org.springframework.core.env.Environment; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.context.PersistentEntities; import org.springframework.data.repository.support.DomainClassConverter; @@ -64,6 +63,9 @@ import org.springframework.data.rest.webmvc.convert.UriListHttpMessageConverter; import org.springframework.data.rest.webmvc.json.Jackson2DatatypeHelper; import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module; import org.springframework.data.rest.webmvc.json.PersistentEntityToJsonSchemaConverter; +import org.springframework.data.rest.webmvc.spi.BackendIdConverter; +import org.springframework.data.rest.webmvc.spi.BackendIdConverter.DefaultIdConverter; +import org.springframework.data.rest.webmvc.support.BackendIdHandlerMethodArgumentResolver; import org.springframework.data.rest.webmvc.support.HttpMethodHandlerMethodArgumentResolver; import org.springframework.data.rest.webmvc.support.JpaHelper; import org.springframework.data.rest.webmvc.support.RepositoryEntityLinks; @@ -86,6 +88,8 @@ import org.springframework.hateoas.hal.Jackson2HalModule.HalHandlerInstantiator; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.plugin.core.OrderAwarePluginRegistry; +import org.springframework.plugin.core.PluginRegistry; import org.springframework.util.ClassUtils; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver; @@ -132,10 +136,11 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon RepositoryRestMvcConfiguration.class.getClassLoader()); @Autowired ListableBeanFactory beanFactory; - @Autowired Environment environment; - @Autowired(required = false) List> resourceProcessors = Collections.emptyList(); @Autowired(required = false) List> mappingContexts = Collections.emptyList(); + @Autowired(required = false) List> resourceProcessors = Collections.emptyList(); + @Autowired(required = false) List idConverters = Collections.emptyList(); + @Autowired(required = false) RelProvider relProvider; @Autowired(required = false) CurieProvider curieProvider; @@ -270,6 +275,12 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon return new ResourceMetadataHandlerMethodArgumentResolver(repositories(), resourceMappings()); } + @Bean + public BackendIdHandlerMethodArgumentResolver backendIdHandlerMethodArgumentResolver() { + return new BackendIdHandlerMethodArgumentResolver(backendIdConverterRegistry(), + resourceMetadataHandlerMethodArgumentResolver()); + } + /** * A special {@link org.springframework.hateoas.EntityLinks} implementation that takes repository and current * configuration into account when generating links. @@ -279,7 +290,8 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon */ @Bean public EntityLinks entityLinks() { - return new RepositoryEntityLinks(repositories(), resourceMappings(), config(), pageableResolver()); + return new RepositoryEntityLinks(repositories(), resourceMappings(), config(), pageableResolver(), + backendIdConverterRegistry()); } /** @@ -526,16 +538,25 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon return resolver; } + @Bean + public PluginRegistry> backendIdConverterRegistry() { + + List converters = new ArrayList(idConverters.size()); + converters.addAll(this.idConverters); + converters.add(DefaultIdConverter.INSTANCE); + + return OrderAwarePluginRegistry.create(converters); + } + private List defaultMethodArgumentResolvers() { PersistentEntityResourceAssemblerArgumentResolver peraResolver = new PersistentEntityResourceAssemblerArgumentResolver( repositories(), entityLinks(), config().projectionConfiguration(), new ProxyProjectionFactory(beanFactory)); - return Arrays - .asList(pageableResolver(), sortResolver(), serverHttpRequestMethodArgumentResolver(), - repoRequestArgumentResolver(), persistentEntityArgumentResolver(), - resourceMetadataHandlerMethodArgumentResolver(), HttpMethodHandlerMethodArgumentResolver.INSTANCE, - peraResolver); + return Arrays.asList(pageableResolver(), sortResolver(), serverHttpRequestMethodArgumentResolver(), + repoRequestArgumentResolver(), persistentEntityArgumentResolver(), + resourceMetadataHandlerMethodArgumentResolver(), HttpMethodHandlerMethodArgumentResolver.INSTANCE, + peraResolver, backendIdHandlerMethodArgumentResolver()); } private ObjectMapper basicObjectMapper() { diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/ResourceMetadataHandlerMethodArgumentResolver.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/ResourceMetadataHandlerMethodArgumentResolver.java index a374ecd7d..a06534c16 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/ResourceMetadataHandlerMethodArgumentResolver.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/ResourceMetadataHandlerMethodArgumentResolver.java @@ -18,19 +18,17 @@ package org.springframework.data.rest.webmvc.config; import static org.springframework.util.ClassUtils.*; import static org.springframework.util.StringUtils.*; -import javax.servlet.http.HttpServletRequest; - import org.springframework.core.MethodParameter; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.support.Repositories; import org.springframework.data.rest.core.mapping.ResourceMappings; import org.springframework.data.rest.core.mapping.ResourceMetadata; +import org.springframework.data.rest.webmvc.util.UriUtils; 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; -import org.springframework.web.util.UrlPathHelper; /** * {@link HandlerMethodArgumentResolver} to create {@link ResourceMetadata} instances. @@ -76,32 +74,15 @@ public class ResourceMetadataHandlerMethodArgumentResolver implements HandlerMet public ResourceMetadata resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { - HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); - String requestUri = new UrlPathHelper().getLookupPathForRequest(request); + String repositoryKey = UriUtils.findMappingVariable("repository", parameter, webRequest); - if (requestUri.startsWith("/")) { - requestUri = requestUri.substring(1); - } - - String[] parts = requestUri.split("/"); - - if (parts.length == 0) { - // Root request - return null; - } - - return findRepositoryInfoFor(parts[0]); - } - - private ResourceMetadata findRepositoryInfoFor(String pathSegment) { - - if (!hasText(pathSegment)) { + if (!hasText(repositoryKey)) { return null; } for (Class domainType : repositories) { ResourceMetadata mapping = mappings.getMappingFor(domainType); - if (mapping.getPath().matches(pathSegment) && mapping.isExported()) { + if (mapping.getPath().matches(repositoryKey) && mapping.isExported()) { return mapping; } } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/spi/BackendIdConverter.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/spi/BackendIdConverter.java new file mode 100644 index 000000000..fe8dccfec --- /dev/null +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/spi/BackendIdConverter.java @@ -0,0 +1,83 @@ +/* + * 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.spi; + +import java.io.Serializable; + +import org.springframework.plugin.core.Plugin; + +/** + * SPI to allow the customization of how entity ids are exposed in URIs generated. + * + * @author Oliver Gierke + */ +public interface BackendIdConverter extends Plugin> { + + /** + * Returns the id of the entity to be looked up eventually. + * + * @param id the source id as it was parsed from the incoming request, will never be {@literal null}. + * @param entityType the type of the object to be resolved, will never be {@literal null}. + * @return must not be {@literal null}. + */ + Serializable fromRequestId(String id, Class entityType); + + /** + * Returns the id to be used in the URI generated to point to an entity of the given type with the given id. + * + * @param id the entity's id, will never be {@literal null}. + * @param entityType the type of the entity to expose. + * @return + */ + String toRequestId(Serializable id, Class entityType); + + /** + * The default {@link BackendIdConverter} that will simply use ids as they are. + * + * @author Oliver Gierke + */ + public enum DefaultIdConverter implements BackendIdConverter { + + INSTANCE; + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.webmvc.support.BackendIdConverter#fromRequestId(java.lang.String, java.lang.Class) + */ + @Override + public Serializable fromRequestId(String id, Class entityType) { + return id; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.webmvc.support.BackendIdConverter#toRequestId(java.lang.Object, java.lang.Class) + */ + @Override + public String toRequestId(Serializable id, Class entityType) { + return id.toString(); + } + + /* + * (non-Javadoc) + * @see org.springframework.plugin.core.Plugin#supports(java.lang.Object) + */ + @Override + public boolean supports(Class delimiter) { + return true; + } + } +} diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/BackendId.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/BackendId.java new file mode 100644 index 000000000..ee3139361 --- /dev/null +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/BackendId.java @@ -0,0 +1,32 @@ +/* + * 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.support; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to bind the backend id of an entity. + * + * @author Oliver Gierke + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface BackendId { + +} diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/BackendIdHandlerMethodArgumentResolver.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/BackendIdHandlerMethodArgumentResolver.java new file mode 100644 index 000000000..e4a8c6589 --- /dev/null +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/BackendIdHandlerMethodArgumentResolver.java @@ -0,0 +1,96 @@ +/* + * 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.support; + +import java.io.Serializable; + +import org.springframework.core.MethodParameter; +import org.springframework.data.rest.core.mapping.ResourceMetadata; +import org.springframework.data.rest.webmvc.config.ResourceMetadataHandlerMethodArgumentResolver; +import org.springframework.data.rest.webmvc.spi.BackendIdConverter; +import org.springframework.data.rest.webmvc.spi.BackendIdConverter.DefaultIdConverter; +import org.springframework.data.rest.webmvc.util.UriUtils; +import org.springframework.plugin.core.PluginRegistry; +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; + +/** + * {@link HandlerMethodArgumentResolver} to resolve entity ids for injection int handler method arguments annotated with + * {@link BackendId}. + * + * @author Oliver Gierke + */ +public class BackendIdHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver { + + private final PluginRegistry> idConverters; + private final ResourceMetadataHandlerMethodArgumentResolver resourceMetadataResolver; + + /** + * Creates a new {@link BackendIdHandlerMethodArgumentResolver} for the given {@link BackendIdConverter}s and + * {@link ResourceMetadataHandlerMethodArgumentResolver}. + * + * @param idConverters the {@link BackendIdConverter}s registered in the system. + * @param resourceMetadataResolver the resolver to obtain {@link ResourceMetadata} from. + */ + public BackendIdHandlerMethodArgumentResolver(PluginRegistry> idConverters, + ResourceMetadataHandlerMethodArgumentResolver resourceMetadataResolver) { + + Assert.notNull(idConverters, "Id converters must not be null!"); + Assert.notNull(resourceMetadataResolver, "ResourceMetadata resolver must not be null!"); + + this.idConverters = idConverters; + this.resourceMetadataResolver = resourceMetadataResolver; + } + + /* + * (non-Javadoc) + * @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter) + */ + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.hasParameterAnnotation(BackendId.class); + } + + /* + * (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 + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, + NativeWebRequest request, WebDataBinderFactory binderFactory) throws Exception { + + Class parameterType = parameter.getParameterType(); + + if (!parameterType.equals(Serializable.class)) { + throw new IllegalArgumentException(String.format( + "Method parameter for @%s must be of type %s! Got %s for method %s.", BackendId.class.getSimpleName(), + Serializable.class.getSimpleName(), parameterType.getSimpleName(), parameter.getMethod())); + } + + ResourceMetadata metadata = resourceMetadataResolver.resolveArgument(parameter, mavContainer, request, + binderFactory); + + if (metadata == null) { + throw new IllegalArgumentException("Could not obtain ResourceMetadata for request " + request); + } + + BackendIdConverter pluginFor = idConverters.getPluginFor(metadata.getDomainType(), DefaultIdConverter.INSTANCE); + return pluginFor.fromRequestId(UriUtils.findMappingVariable("id", parameter, request), metadata.getDomainType()); + } +} diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/HttpRequestUtils.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/HttpRequestUtils.java deleted file mode 100644 index 03238268c..000000000 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/HttpRequestUtils.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.springframework.data.rest.webmvc.support; - -import javax.servlet.ServletContext; -import javax.servlet.ServletRegistration; - -import org.springframework.data.rest.webmvc.RepositoryRestDispatcherServlet; - -/** - * Helper class to HttpServletRequest helpers. - * - * @author Jon Brisbin - */ -public abstract class HttpRequestUtils { - - /** - * Strip a servlet registration mapping from the request URI. - * - * @param requestUri The request URI to strip. - * @param ctx The servlet context in which to search for registration mappings. - * @return The stripped request URI. - */ - public static String stripRegistrationMapping(String requestUri, ServletContext ctx) { - for (ServletRegistration reg : ctx.getServletRegistrations().values()) { - if (reg.getClassName().equals(RepositoryRestDispatcherServlet.class.getName()) - || reg.getName().equals("rest-exporter")) { - for (String mapping : reg.getMappings()) { - if (mapping.contains("*")) { - mapping = mapping.substring(0, mapping.indexOf('*')); - } - if (requestUri.startsWith(mapping)) { - return requestUri.replaceAll(mapping, ""); - } - } - } - } - return requestUri; - } - -} diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/RepositoryEntityLinks.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/RepositoryEntityLinks.java index 225d963fb..0e2429bda 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/RepositoryEntityLinks.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/RepositoryEntityLinks.java @@ -17,12 +17,16 @@ package org.springframework.data.rest.webmvc.support; import static org.springframework.hateoas.TemplateVariable.VariableType.*; +import java.io.Serializable; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.repository.support.Repositories; import org.springframework.data.rest.core.config.ProjectionDefinitionConfiguration; import org.springframework.data.rest.core.config.RepositoryRestConfiguration; import org.springframework.data.rest.core.mapping.ResourceMappings; import org.springframework.data.rest.core.mapping.ResourceMetadata; +import org.springframework.data.rest.webmvc.spi.BackendIdConverter; +import org.springframework.data.rest.webmvc.spi.BackendIdConverter.DefaultIdConverter; import org.springframework.data.web.HateoasPageableHandlerMethodArgumentResolver; import org.springframework.hateoas.EntityLinks; import org.springframework.hateoas.Link; @@ -31,6 +35,7 @@ import org.springframework.hateoas.TemplateVariable; import org.springframework.hateoas.TemplateVariables; import org.springframework.hateoas.UriTemplate; import org.springframework.hateoas.core.AbstractEntityLinks; +import org.springframework.plugin.core.PluginRegistry; import org.springframework.util.Assert; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; @@ -48,6 +53,7 @@ public class RepositoryEntityLinks extends AbstractEntityLinks { private final ResourceMappings mappings; private final RepositoryRestConfiguration config; private final HateoasPageableHandlerMethodArgumentResolver resolver; + private final PluginRegistry> idConverters; /** * Creates a new {@link RepositoryEntityLinks}. @@ -56,20 +62,24 @@ public class RepositoryEntityLinks extends AbstractEntityLinks { * @param mappings must not be {@literal null}. * @param config must not be {@literal null}. * @param resolver must not be {@literal null}. + * @param idConverters must not be {@literal null}. */ @Autowired public RepositoryEntityLinks(Repositories repositories, ResourceMappings mappings, - RepositoryRestConfiguration config, HateoasPageableHandlerMethodArgumentResolver resolver) { + RepositoryRestConfiguration config, HateoasPageableHandlerMethodArgumentResolver resolver, + PluginRegistry> idConverters) { Assert.notNull(repositories, "Repositories must not be null!"); Assert.notNull(mappings, "ResourceMappings must not be null!"); Assert.notNull(config, "RepositoryRestConfiguration must not be null!"); Assert.notNull(resolver, "HateoasPageableHandlerMethodArgumentResolver must not be null!"); + Assert.notNull(idConverters, "Id converter registry must not be null!"); this.repositories = repositories; this.mappings = mappings; this.config = config; this.resolver = resolver; + this.idConverters = idConverters; } /* @@ -134,8 +144,12 @@ public class RepositoryEntityLinks extends AbstractEntityLinks { @Override public Link linkToSingleResource(Class type, Object id) { + Assert.isInstanceOf(Serializable.class, id, "Id must be assignable to Serializable!"); + ResourceMetadata metadata = mappings.getMappingFor(type); - Link link = linkFor(type).slash(id).withRel(metadata.getItemResourceRel()); + String mappedId = idConverters.getPluginFor(type, DefaultIdConverter.INSTANCE).toRequestId((Serializable) id, type); + + Link link = linkFor(type).slash(mappedId).withRel(metadata.getItemResourceRel()); ProjectionDefinitionConfiguration projectionConfiguration = config.projectionConfiguration(); if (!projectionConfiguration.hasProjectionFor(type)) { diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/util/UriUtils.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/util/UriUtils.java new file mode 100644 index 000000000..28b39912e --- /dev/null +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/util/UriUtils.java @@ -0,0 +1,76 @@ +/* + * 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.util; + +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.core.MethodParameter; +import org.springframework.hateoas.UriTemplate; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.util.UrlPathHelper; + +/** + * Utility methods to work with requests and URIs. + * + * @author Oliver Gierke + */ +public abstract class UriUtils { + + private static final UrlPathHelper URL_PATH_HELPER = new UrlPathHelper(); + + private UriUtils() {} + + /** + * Returns the value for the mapping variable with the given name. + * + * @param variable must not be {@literal null} or empty. + * @param parameter + * @param request + * @return + */ + public static String findMappingVariable(String variable, MethodParameter parameter, NativeWebRequest request) { + + Assert.hasText(variable, "Variable name must not be null or empty!"); + Assert.notNull(parameter, "Method parameter must not be null!"); + Assert.notNull(request, "Request must not be null!"); + + String lookupPath = getCleanLookupPath(request); + RequestMapping annotation = parameter.getMethodAnnotation(RequestMapping.class); + + for (String mapping : annotation.value()) { + + Map variables = new org.springframework.web.util.UriTemplate(mapping).match(lookupPath); + String value = variables.get(variable); + + if (value != null) { + return value; + } + } + + return null; + } + + private static String getCleanLookupPath(NativeWebRequest request) { + + HttpServletRequest httpServletRequest = request.getNativeRequest(HttpServletRequest.class); + String lookupPath = URL_PATH_HELPER.getLookupPathForRequest(httpServletRequest); + return new UriTemplate(lookupPath).expand().toString(); + } +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryEntityControllerIntegrationTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryEntityControllerIntegrationTests.java index 401c65130..07881494a 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryEntityControllerIntegrationTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryEntityControllerIntegrationTests.java @@ -56,6 +56,6 @@ public class RepositoryEntityControllerIntegrationTests extends AbstractControll RootResourceInformation request = getResourceInformation(Address.class); - controller.postEntity(request, null, null); + controller.postCollectionResource(request, null, null); } } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/Book.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/Book.java index ae6885c19..78adeea89 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/Book.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/Book.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,13 +20,15 @@ import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Entity; +import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.ManyToMany; @Entity public class Book { - @Id String isbn; + @Id @GeneratedValue Long id; + String isbn; @ManyToMany(cascade = { CascadeType.MERGE })// Set authors; diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/BookIdConverter.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/BookIdConverter.java new file mode 100644 index 000000000..1945de8f3 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/BookIdConverter.java @@ -0,0 +1,66 @@ +/* + * 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.jpa; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.data.rest.webmvc.spi.BackendIdConverter; +import org.springframework.util.StringUtils; + +/** + * {@link BackendIdConverter} artificially transforming the actual book id into some magic {@link String} and back. + * + * @author Oliver Gierke + */ +public class BookIdConverter implements BackendIdConverter { + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.webmvc.support.BackendIdConverter#fromRequestId(java.lang.String, java.lang.Class) + */ + @Override + public Serializable fromRequestId(String id, Class entityType) { + return Long.parseLong(id.substring(0, id.indexOf('-'))); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.webmvc.support.BackendIdConverter#toRequestId(java.lang.Object, java.lang.Class) + */ + @Override + public String toRequestId(Serializable id, Class entityType) { + + Long longId = (Long) id; + List ids = new ArrayList(longId.intValue()); + + for (int i = 0; i < longId; i++) { + ids.add(longId); + } + + return StringUtils.collectionToDelimitedString(ids, "-"); + } + + /* + * (non-Javadoc) + * @see org.springframework.plugin.core.Plugin#supports(java.lang.Object) + */ + @Override + public boolean supports(Class delimiter) { + return Book.class.equals(delimiter); + } +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/BookRepository.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/BookRepository.java index 2647cf7b8..679b9b97d 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/BookRepository.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/BookRepository.java @@ -20,6 +20,6 @@ import org.springframework.data.repository.CrudRepository; /** * @author Oliver Gierke */ -public interface BookRepository extends CrudRepository { +public interface BookRepository extends CrudRepository { } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaRepositoryConfig.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaRepositoryConfig.java index ecca733c8..565f23ecf 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaRepositoryConfig.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaRepositoryConfig.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. @@ -21,6 +21,8 @@ import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.transaction.annotation.EnableTransactionManagement; /** + * Test configuration for JPA. + * * @author Jon Brisbin * @author Oliver Gierke */ @@ -29,6 +31,11 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; @EnableTransactionManagement public class JpaRepositoryConfig extends JpaInfrastructureConfig { + @Bean + public BookIdConverter bookIdConverter() { + return new BookIdConverter(); + } + @Bean public TestDataPopulator testDataPopulator() { return new TestDataPopulator(); diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/support/BackendIdConverterHandlerMethodArgumentResolverIntegrationTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/support/BackendIdConverterHandlerMethodArgumentResolverIntegrationTests.java new file mode 100644 index 000000000..c3f1f126e --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/support/BackendIdConverterHandlerMethodArgumentResolverIntegrationTests.java @@ -0,0 +1,81 @@ +/* + * 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.support; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import java.io.Serializable; +import java.lang.reflect.Method; + +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.MethodParameter; +import org.springframework.data.rest.webmvc.AbstractControllerIntegrationTests; +import org.springframework.data.rest.webmvc.jpa.JpaRepositoryConfig; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.util.ReflectionUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.context.request.ServletWebRequest; + +/** + * Integration tests for {@link BackendIdHandlerMethodArgumentResolver}. + * + * @author Oliver Gierke + */ +@ContextConfiguration(classes = JpaRepositoryConfig.class) +public class BackendIdConverterHandlerMethodArgumentResolverIntegrationTests extends AbstractControllerIntegrationTests { + + @Autowired BackendIdHandlerMethodArgumentResolver resolver; + + /** + * @see DATAREST-267, DATAREST-268 + */ + @Test + public void stripsUriTemplateVariablesFromUri() throws Exception { + + Method method = ReflectionUtils.findMethod(SampleController.class, "resolveId", Serializable.class); + MethodParameter parameter = new MethodParameter(method, 0); + NativeWebRequest request = new ServletWebRequest(new MockHttpServletRequest("GET", "/orders/5{?projection}")); + + Object resolvedId = resolver.resolveArgument(parameter, null, request, null); + + assertThat(resolvedId, is((Object) "5")); + } + + /** + * @see DATAREST-155 + */ + @Test + public void translatesUriToBackendId() throws Exception { + + Method method = ReflectionUtils.findMethod(SampleController.class, "resolveId", Serializable.class); + MethodParameter parameter = new MethodParameter(method, 0); + NativeWebRequest request = new ServletWebRequest(new MockHttpServletRequest("GET", "/books/5-5-5-5-5")); + + Object resolvedId = resolver.resolveArgument(parameter, null, request, null); + + assertThat(resolvedId, is((Object) 5L)); + } + + static class SampleController { + + @RequestMapping("/{repository}/{id}") + void resolveId(@BackendId Serializable backendId) {} + } +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/support/RepositoryEntityLinksIntegrationTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/support/RepositoryEntityLinksIntegrationTests.java index b87442518..73fa429e1 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/support/RepositoryEntityLinksIntegrationTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/support/RepositoryEntityLinksIntegrationTests.java @@ -22,6 +22,7 @@ import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.rest.core.config.RepositoryRestConfiguration; import org.springframework.data.rest.webmvc.AbstractControllerIntegrationTests; +import org.springframework.data.rest.webmvc.jpa.Book; import org.springframework.data.rest.webmvc.jpa.JpaRepositoryConfig; import org.springframework.data.rest.webmvc.jpa.Order; import org.springframework.data.rest.webmvc.jpa.Person; @@ -69,4 +70,14 @@ public class RepositoryEntityLinksIntegrationTests extends AbstractControllerInt assertThat(link.isTemplated(), is(true)); assertThat(link.getVariableNames(), hasItem(configuration.projectionConfiguration().getParameterName())); } + + /** + * @see DATAREST-155 + */ + @Test + public void usesCustomGeneratedBackendId() { + + Link link = entityLinks.linkToSingleResource(Book.class, 7L); + assertThat(link.getHref(), endsWith("/7-7-7-7-7-7-7")); + } }