Skip to content

Commit 86ff61c

Browse files
committed
Polishing.
Added test case and re-enabled the base URI to be prepended even in case a controller path prefix is configured. That functionality had been lost with the originally submitted changes. Related ticket: #2087 Original pull request: #2088.
1 parent c73d381 commit 86ff61c

File tree

6 files changed

+104
-30
lines changed

6 files changed

+104
-30
lines changed

spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaRepositoryConfig.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,12 @@ static class BooksHtmlController {
5858
void someMethod(@PathVariable String id) {}
5959
}
6060

61-
@RepositoryRestController(path = {"orders", "orders/v2"})
61+
@RepositoryRestController({ "orders", "orders/v2" })
6262
static class OrdersJsonController {
6363

64-
@RequestMapping(value = {"/search/sort","/search/sorted"}, method = RequestMethod.POST, produces = "application/hal+json")
64+
@RequestMapping(path = { "/search/sort", "/search/sorted" }, //
65+
method = RequestMethod.POST, //
66+
produces = "application/hal+json")
6567
void someMethodWithArgs(Sort sort, Pageable pageable, DefaultedPageable defaultedPageable) {}
6668
}
6769
}

spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaWebTests.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -702,9 +702,13 @@ void exectuesCustomQuerySearchThatTakesAMappedSortProperty() throws Exception {
702702
andExpect(client.hasLinkWithRel(IanaLinkRelations.SELF));
703703
}
704704

705-
@Test // DATAREST-910 DATAREST-2088
705+
@Test // DATAREST-910, #2087
706706
void callUnmappedCustomRepositoryController() throws Exception {
707+
708+
// Invalid prefix
707709
mvc.perform(post("/orders/v3/search/sort")).andExpect(status().isNotFound());
710+
711+
// With mapped prefixes
708712
mvc.perform(post("/orders/search/sort")).andExpect(status().isOk());
709713
mvc.perform(post("/orders/search/sorted")).andExpect(status().isOk());
710714
mvc.perform(post("/orders/v2/search/sort")).andExpect(status().isOk());

spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BasePathAwareController.java

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,31 @@
2929
* REST configuration.
3030
*
3131
* @author Oliver Gierke
32+
* @author Yves Galante
3233
*/
3334
@Documented
3435
@Component
3536
@Retention(RetentionPolicy.RUNTIME)
3637
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
3738
public @interface BasePathAwareController {
38-
@AliasFor("path")
39-
String[] value() default {};
4039

41-
@AliasFor("value")
42-
String[] path() default {};
40+
/**
41+
* The root path to be prepended to all request mappings configured on handler methods.
42+
*
43+
* @return
44+
* @since 3.7.2
45+
* @see #path()
46+
*/
47+
@AliasFor("path")
48+
String[] value() default {};
49+
50+
/**
51+
* The root path to be prepended to all request mappings configured on handler methods.
52+
*
53+
* @return
54+
* @since 3.7.2
55+
* @see #value()
56+
*/
57+
@AliasFor("value")
58+
String[] path() default {};
4359
}

spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BasePathAwareHandlerMapping.java

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@
1515
*/
1616
package org.springframework.data.rest.webmvc;
1717

18+
import static org.springframework.core.annotation.AnnotatedElementUtils.*;
19+
1820
import java.lang.reflect.Method;
1921
import java.util.ArrayList;
22+
import java.util.Arrays;
2023
import java.util.Collections;
2124
import java.util.Enumeration;
2225
import java.util.List;
@@ -36,10 +39,9 @@
3639
import org.springframework.web.method.HandlerMethod;
3740
import org.springframework.web.servlet.mvc.condition.ProducesRequestCondition;
3841
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
42+
import org.springframework.web.servlet.mvc.method.RequestMappingInfo.Builder;
3943
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
4044

41-
import static org.springframework.core.annotation.AnnotatedElementUtils.*;
42-
4345
/**
4446
* A {@link RequestMappingHandlerMapping} that augments the request mappings
4547
*
@@ -109,6 +111,7 @@ protected boolean hasCorsConfigurationSource(Object handler) {
109111
* @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#getMappingForMethod(java.lang.reflect.Method, java.lang.Class)
110112
*/
111113
@Override
114+
@SuppressWarnings("null")
112115
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
113116

114117
RequestMappingInfo info = super.getMappingForMethod(method, handlerType);
@@ -119,23 +122,17 @@ protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handler
119122

120123
ProducesRequestCondition producesCondition = customize(info.getProducesCondition());
121124
Set<MediaType> mediaTypes = producesCondition.getProducibleMediaTypes();
125+
String[] customPrefixes = getBasePathedPrefixes(handlerType);
126+
Builder builder = info.mutate();
122127

123-
BasePathAwareController mergedAnnotation = findMergedAnnotation(handlerType, BasePathAwareController.class);
124-
if (mergedAnnotation != null) {
125-
info = appendPathPrefix(info, mergedAnnotation.value());
128+
if ((customPrefixes.length != 0) || StringUtils.hasText(baseUri)) {
129+
builder = builder.paths(resolveEmbeddedValuesInPatterns(customPrefixes));
126130
}
127-
info = appendPathPrefix(info, new String[]{this.baseUri});
128-
return info.mutate()
129-
.produces(mediaTypes.stream().map(MediaType::toString).toArray(String[]::new))
130-
.build();
131-
}
132131

133-
private RequestMappingInfo appendPathPrefix(RequestMappingInfo info, String[] pathPrefix) {
134-
if (pathPrefix.length > 0) {
135-
String[] paths = this.resolveEmbeddedValuesInPatterns(pathPrefix);
136-
return info.mutate().paths(paths).build().combine(info);
137-
}
138-
return info;
132+
return builder //
133+
.produces(mediaTypes.stream().map(MediaType::toString).toArray(String[]::new)) //
134+
.build() //
135+
.combine(info);
139136
}
140137

141138
/**
@@ -183,6 +180,18 @@ protected boolean isHandlerInternal(Class<?> type) {
183180
return type.isAnnotationPresent(BasePathAwareController.class);
184181
}
185182

183+
private String[] getBasePathedPrefixes(Class<?> handlerType) {
184+
185+
Assert.notNull(handlerType, "Handler type must not be null");
186+
187+
BasePathAwareController mergedAnnotation = findMergedAnnotation(handlerType, BasePathAwareController.class);
188+
String[] customPrefixes = mergedAnnotation == null ? new String[0] : mergedAnnotation.value();
189+
190+
return customPrefixes.length == 0 //
191+
? new String[] { baseUri } //
192+
: Arrays.stream(customPrefixes).map(baseUri::concat).toArray(String[]::new);
193+
}
194+
186195
/**
187196
* {@link HttpServletRequest} that exposes the given media types for the {@code Accept} header.
188197
*
@@ -224,7 +233,8 @@ public CustomAcceptHeaderHttpServletRequest(HttpServletRequest request, List<Med
224233
@Override
225234
public String getHeader(String name) {
226235

227-
return HttpHeaders.ACCEPT.equalsIgnoreCase(name) && acceptMediaTypes != null //
236+
return HttpHeaders.ACCEPT.equalsIgnoreCase(name) && (acceptMediaTypes != null //
237+
)
228238
? StringUtils.collectionToCommaDelimitedString(acceptMediaTypes) //
229239
: super.getHeader(name);
230240
}
@@ -236,7 +246,8 @@ public String getHeader(String name) {
236246
@Override
237247
public Enumeration<String> getHeaders(String name) {
238248

239-
return HttpHeaders.ACCEPT.equalsIgnoreCase(name) && acceptMediaTypes != null //
249+
return HttpHeaders.ACCEPT.equalsIgnoreCase(name) && (acceptMediaTypes != null //
250+
)
240251
? Collections.enumeration(acceptMediaTypeStrings) //
241252
: super.getHeaders(name);
242253
}

spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,32 @@
4040
* </ul>
4141
*
4242
* @author Oliver Gierke
43+
* @author Yves Galante
4344
*/
4445
@Documented
4546
@Component
4647
@Retention(RetentionPolicy.RUNTIME)
4748
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
4849
@BasePathAwareController
4950
public @interface RepositoryRestController {
50-
@AliasFor("path")
51-
String[] value() default {};
5251

53-
@AliasFor("value")
54-
String[] path() default {};
52+
/**
53+
* The root path to be prepended to all request mappings configured on handler methods.
54+
*
55+
* @return
56+
* @since 3.7.2
57+
* @see #path()
58+
*/
59+
@AliasFor("path")
60+
String[] value() default {};
61+
62+
/**
63+
* The root path to be prepended to all request mappings configured on handler methods.
64+
*
65+
* @return
66+
* @since 3.7.2
67+
* @see #value()
68+
*/
69+
@AliasFor("value")
70+
String[] path() default {};
5571
}

spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/BasePathAwareHandlerMappingUnitTests.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import static org.assertj.core.api.Assertions.*;
1919
import static org.mockito.Mockito.*;
2020

21+
import java.lang.reflect.Method;
2122
import java.net.URI;
2223

2324
import org.junit.jupiter.api.BeforeEach;
@@ -26,7 +27,10 @@
2627
import org.springframework.aop.support.AopUtils;
2728
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
2829
import org.springframework.stereotype.Controller;
30+
import org.springframework.util.ReflectionUtils;
31+
import org.springframework.web.bind.annotation.GetMapping;
2932
import org.springframework.web.bind.annotation.RequestMapping;
33+
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
3034

3135
/**
3236
* Unit tests for {@link BasePathAwareHandlerMapping}.
@@ -36,11 +40,11 @@
3640
class BasePathAwareHandlerMappingUnitTests {
3741

3842
HandlerMappingStub mapping;
43+
RepositoryRestConfiguration configuration = mock(RepositoryRestConfiguration.class);
3944

4045
@BeforeEach
4146
void setUp() {
4247

43-
RepositoryRestConfiguration configuration = mock(RepositoryRestConfiguration.class);
4448
doReturn(URI.create("")).when(configuration).getBasePath();
4549

4650
mapping = new HandlerMappingStub(configuration);
@@ -79,6 +83,20 @@ void doesNotRejectAtRequestMappingOnStandardMvcController() {
7983
.isThrownBy(() -> mapping.isHandler(ValidController.class));
8084
}
8185

86+
@Test // #2087
87+
void combinesBasePathAndControllerPrefixesCorrectly() throws Exception {
88+
89+
doReturn(URI.create("/base")).when(configuration).getBasePath();
90+
mapping = new HandlerMappingStub(configuration);
91+
92+
Method method = ReflectionUtils.findMethod(PrefixedController.class, "someMethod");
93+
RequestMappingInfo info = mapping.getMappingForMethod(method, PrefixedController.class);
94+
95+
String next = info.getPatternValues().iterator().next();
96+
97+
assertThat(next).isEqualTo("/base/controllerBase/method");
98+
}
99+
82100
private static Class<?> createProxy(Object source) {
83101

84102
ProxyFactory factory = new ProxyFactory(source);
@@ -114,4 +132,11 @@ static class InvalidController {}
114132
@Controller
115133
@RequestMapping("/sample")
116134
static class ValidController {}
135+
136+
@BasePathAwareController("/controllerBase")
137+
static class PrefixedController {
138+
139+
@GetMapping("/method")
140+
void someMethod() {}
141+
}
117142
}

0 commit comments

Comments
 (0)