Skip to content

Commit 07ebc50

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: #2157 Original pull request: #2088.
1 parent 8daacad commit 07ebc50

File tree

6 files changed

+101
-30
lines changed

6 files changed

+101
-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
@@ -682,9 +682,13 @@ void exectuesCustomQuerySearchThatTakesAMappedSortProperty() throws Exception {
682682
andExpect(client.hasLinkWithRel(IanaLinkRelations.SELF));
683683
}
684684

685-
@Test // DATAREST-910 DATAREST-2088
685+
@Test // DATAREST-910, #2087
686686
void callUnmappedCustomRepositoryController() throws Exception {
687+
688+
// Invalid prefix
687689
mvc.perform(post("/orders/v3/search/sort")).andExpect(status().isNotFound());
690+
691+
// With mapped prefixes
688692
mvc.perform(post("/orders/search/sort")).andExpect(status().isOk());
689693
mvc.perform(post("/orders/search/sorted")).andExpect(status().isOk());
690694
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: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@
1515
*/
1616
package org.springframework.data.rest.webmvc;
1717

18+
import static org.springframework.core.annotation.AnnotatedElementUtils.*;
1819
import jakarta.servlet.http.HttpServletRequest;
1920
import jakarta.servlet.http.HttpServletRequestWrapper;
2021

2122
import java.lang.reflect.Method;
2223
import java.util.ArrayList;
24+
import java.util.Arrays;
2325
import java.util.Collections;
2426
import java.util.Enumeration;
2527
import java.util.List;
@@ -39,10 +41,9 @@
3941
import org.springframework.web.method.HandlerMethod;
4042
import org.springframework.web.servlet.mvc.condition.ProducesRequestCondition;
4143
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
44+
import org.springframework.web.servlet.mvc.method.RequestMappingInfo.Builder;
4245
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
4346

44-
import static org.springframework.core.annotation.AnnotatedElementUtils.*;
45-
4647
/**
4748
* A {@link RequestMappingHandlerMapping} that augments the request mappings
4849
*
@@ -100,6 +101,7 @@ protected boolean hasCorsConfigurationSource(Object handler) {
100101
}
101102

102103
@Override
104+
@SuppressWarnings("null")
103105
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
104106

105107
RequestMappingInfo info = super.getMappingForMethod(method, handlerType);
@@ -110,23 +112,17 @@ protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handler
110112

111113
ProducesRequestCondition producesCondition = customize(info.getProducesCondition());
112114
Set<MediaType> mediaTypes = producesCondition.getProducibleMediaTypes();
115+
String[] customPrefixes = getBasePathedPrefixes(handlerType);
116+
Builder builder = info.mutate();
113117

114-
BasePathAwareController mergedAnnotation = findMergedAnnotation(handlerType, BasePathAwareController.class);
115-
if (mergedAnnotation != null) {
116-
info = appendPathPrefix(info, mergedAnnotation.value());
118+
if ((customPrefixes.length != 0) || StringUtils.hasText(baseUri)) {
119+
builder = builder.paths(resolveEmbeddedValuesInPatterns(customPrefixes));
117120
}
118-
info = appendPathPrefix(info, new String[]{this.baseUri});
119-
return info.mutate()
120-
.produces(mediaTypes.stream().map(MediaType::toString).toArray(String[]::new))
121-
.build();
122-
}
123121

124-
private RequestMappingInfo appendPathPrefix(RequestMappingInfo info, String[] pathPrefix) {
125-
if (pathPrefix.length > 0) {
126-
String[] paths = this.resolveEmbeddedValuesInPatterns(pathPrefix);
127-
return info.mutate().paths(paths).build().combine(info);
128-
}
129-
return info;
122+
return builder //
123+
.produces(mediaTypes.stream().map(MediaType::toString).toArray(String[]::new)) //
124+
.build() //
125+
.combine(info);
130126
}
131127

132128
/**
@@ -174,6 +170,18 @@ protected boolean isHandlerInternal(Class<?> type) {
174170
return type.isAnnotationPresent(BasePathAwareController.class);
175171
}
176172

173+
private String[] getBasePathedPrefixes(Class<?> handlerType) {
174+
175+
Assert.notNull(handlerType, "Handler type must not be null");
176+
177+
BasePathAwareController mergedAnnotation = findMergedAnnotation(handlerType, BasePathAwareController.class);
178+
String[] customPrefixes = mergedAnnotation == null ? new String[0] : mergedAnnotation.value();
179+
180+
return customPrefixes.length == 0 //
181+
? new String[] { baseUri } //
182+
: Arrays.stream(customPrefixes).map(baseUri::concat).toArray(String[]::new);
183+
}
184+
177185
/**
178186
* {@link HttpServletRequest} that exposes the given media types for the {@code Accept} header.
179187
*
@@ -211,15 +219,17 @@ public CustomAcceptHeaderHttpServletRequest(HttpServletRequest request, List<Med
211219
@Override
212220
public String getHeader(String name) {
213221

214-
return HttpHeaders.ACCEPT.equalsIgnoreCase(name) && acceptMediaTypes != null //
222+
return HttpHeaders.ACCEPT.equalsIgnoreCase(name) && (acceptMediaTypes != null //
223+
)
215224
? StringUtils.collectionToCommaDelimitedString(acceptMediaTypes) //
216225
: super.getHeader(name);
217226
}
218227

219228
@Override
220229
public Enumeration<String> getHeaders(String name) {
221230

222-
return HttpHeaders.ACCEPT.equalsIgnoreCase(name) && acceptMediaTypes != null //
231+
return HttpHeaders.ACCEPT.equalsIgnoreCase(name) && (acceptMediaTypes != null //
232+
)
223233
? Collections.enumeration(acceptMediaTypeStrings) //
224234
: super.getHeaders(name);
225235
}

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: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import org.springframework.aop.support.AopUtils;
2727
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
2828
import org.springframework.stereotype.Controller;
29+
import org.springframework.util.ReflectionUtils;
30+
import org.springframework.web.bind.annotation.GetMapping;
2931
import org.springframework.web.bind.annotation.RequestMapping;
3032

3133
/**
@@ -36,11 +38,11 @@
3638
class BasePathAwareHandlerMappingUnitTests {
3739

3840
HandlerMappingStub mapping;
41+
RepositoryRestConfiguration configuration = mock(RepositoryRestConfiguration.class);
3942

4043
@BeforeEach
4144
void setUp() {
4245

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

4648
mapping = new HandlerMappingStub(configuration);
@@ -79,6 +81,20 @@ void doesNotRejectAtRequestMappingOnStandardMvcController() {
7981
.isThrownBy(() -> mapping.isHandler(ValidController.class));
8082
}
8183

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

84100
ProxyFactory factory = new ProxyFactory(source);
@@ -114,4 +130,11 @@ static class InvalidController {}
114130
@Controller
115131
@RequestMapping("/sample")
116132
static class ValidController {}
133+
134+
@BasePathAwareController("/controllerBase")
135+
static class PrefixedController {
136+
137+
@GetMapping("/method")
138+
void someMethod() {}
139+
}
117140
}

0 commit comments

Comments
 (0)