diff --git a/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaRepositoryConfig.java b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaRepositoryConfig.java index 35fa4663e..4f1cb5399 100644 --- a/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaRepositoryConfig.java +++ b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaRepositoryConfig.java @@ -58,10 +58,10 @@ static class BooksHtmlController { void someMethod(@PathVariable String id) {} } - @RepositoryRestController + @RepositoryRestController(path = {"orders", "orders/v2"}) static class OrdersJsonController { - @RequestMapping(value = "/orders/search/sort", method = RequestMethod.POST, produces = "application/hal+json") + @RequestMapping(value = {"/search/sort","/search/sorted"}, method = RequestMethod.POST, produces = "application/hal+json") void someMethodWithArgs(Sort sort, Pageable pageable, DefaultedPageable defaultedPageable) {} } } diff --git a/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaWebTests.java b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaWebTests.java index fcaa14196..28373be54 100755 --- a/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaWebTests.java +++ b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaWebTests.java @@ -702,10 +702,13 @@ void exectuesCustomQuerySearchThatTakesAMappedSortProperty() throws Exception { andExpect(client.hasLinkWithRel(IanaLinkRelations.SELF)); } - @Test // DATAREST-910 + @Test // DATAREST-910 DATAREST-2088 void callUnmappedCustomRepositoryController() throws Exception { - + mvc.perform(post("/orders/v3/search/sort")).andExpect(status().isNotFound()); mvc.perform(post("/orders/search/sort")).andExpect(status().isOk()); + mvc.perform(post("/orders/search/sorted")).andExpect(status().isOk()); + mvc.perform(post("/orders/v2/search/sort")).andExpect(status().isOk()); + mvc.perform(post("/orders/v2/search/sorted")).andExpect(status().isOk()); mvc.perform(post("/orders/search/sort?sort=type&page=1&size=10")).andExpect(status().isOk()); } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BasePathAwareController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BasePathAwareController.java index 32104955a..cdd8f4a8a 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BasePathAwareController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BasePathAwareController.java @@ -21,6 +21,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.core.annotation.AliasFor; import org.springframework.stereotype.Component; /** @@ -34,4 +35,9 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE }) public @interface BasePathAwareController { + @AliasFor("path") + String[] value() default {}; + + @AliasFor("value") + String[] path() default {}; } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BasePathAwareHandlerMapping.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BasePathAwareHandlerMapping.java index 0a5005791..5d1472d66 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BasePathAwareHandlerMapping.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BasePathAwareHandlerMapping.java @@ -19,11 +19,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Set; -import java.util.function.Predicate; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; @@ -41,6 +38,8 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; +import static org.springframework.core.annotation.AnnotatedElementUtils.*; + /** * A {@link RequestMappingHandlerMapping} that augments the request mappings * @@ -51,6 +50,8 @@ public class BasePathAwareHandlerMapping extends RequestMappingHandlerMapping { private static final String AT_REQUEST_MAPPING_ON_TYPE = "Spring Data REST controller %s must not use @RequestMapping on class level as this would cause double registration with Spring MVC!"; private final RepositoryRestConfiguration configuration; + private final String baseUri; + /** * Creates a new {@link BasePathAwareHandlerMapping} using the given {@link RepositoryRestConfiguration}. * @@ -61,16 +62,7 @@ public BasePathAwareHandlerMapping(RepositoryRestConfiguration configuration) { Assert.notNull(configuration, "RepositoryRestConfiguration must not be null!"); this.configuration = configuration; - - String baseUri = configuration.getBasePath().toString(); - - if (StringUtils.hasText(baseUri)) { - - Map>> prefixes = new HashMap<>(); - prefixes.put(baseUri, it -> true); - - this.setPathPrefixes(prefixes); - } + this.baseUri = configuration.getBasePath().toString(); } /* @@ -128,11 +120,24 @@ protected RequestMappingInfo getMappingForMethod(Method method, Class handler ProducesRequestCondition producesCondition = customize(info.getProducesCondition()); Set mediaTypes = producesCondition.getProducibleMediaTypes(); + BasePathAwareController mergedAnnotation = findMergedAnnotation(handlerType, BasePathAwareController.class); + if (mergedAnnotation != null) { + info = appendPathPrefix(info, mergedAnnotation.value()); + } + info = appendPathPrefix(info, new String[]{this.baseUri}); return info.mutate() .produces(mediaTypes.stream().map(MediaType::toString).toArray(String[]::new)) .build(); } + private RequestMappingInfo appendPathPrefix(RequestMappingInfo info, String[] pathPrefix) { + if (pathPrefix.length > 0) { + String[] paths = this.resolveEmbeddedValuesInPatterns(pathPrefix); + return info.mutate().paths(paths).build().combine(info); + } + return info; + } + /** * Customize the given {@link ProducesRequestCondition}. Default implementation returns the condition as is. * diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java index ac1419e94..fe6e513fc 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java @@ -21,6 +21,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.core.annotation.AliasFor; import org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerMapping; @@ -46,4 +47,9 @@ @Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE }) @BasePathAwareController public @interface RepositoryRestController { + @AliasFor("path") + String[] value() default {}; + + @AliasFor("value") + String[] path() default {}; }