Skip to content

Commit c5f4bfa

Browse files
lycaodrotbohm
authored andcommitted
#361 - Implemented property resolving in link creation.
We now pipe the detected mapping through the PropertyResolver exposed by the ApplicationContext's Environment. Original pull request: #1328.
1 parent 3a7798a commit c5f4bfa

File tree

7 files changed

+225
-1
lines changed

7 files changed

+225
-1
lines changed
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright 2019-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.hateoas.server.mvc;
17+
18+
import static java.util.Optional.ofNullable;
19+
20+
import org.springframework.core.env.PropertyResolver;
21+
import org.springframework.hateoas.server.core.MappingDiscoverer;
22+
import org.springframework.http.HttpMethod;
23+
import org.springframework.lang.Nullable;
24+
import org.springframework.web.context.ContextLoader;
25+
26+
import java.lang.reflect.Method;
27+
import java.util.Collection;
28+
29+
/**
30+
* Property resolving adapter of {@link MappingDiscoverer}.
31+
*
32+
* @author Lars Michele
33+
*/
34+
public class PropertyResolvingMappingDiscoverer implements MappingDiscoverer {
35+
36+
private final MappingDiscoverer delegate;
37+
38+
private PropertyResolvingMappingDiscoverer(MappingDiscoverer delegate) {
39+
this.delegate = delegate;
40+
}
41+
42+
public static PropertyResolvingMappingDiscoverer of(MappingDiscoverer delegate) {
43+
return new PropertyResolvingMappingDiscoverer(delegate);
44+
}
45+
46+
/*
47+
* (non-Javadoc)
48+
* @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.Class)
49+
*/
50+
@Nullable
51+
@Override
52+
public String getMapping(Class<?> type) {
53+
return ofNullable(delegate.getMapping(type)).map(getPropertyResolver()::resolvePlaceholders).orElse(null);
54+
}
55+
56+
/*
57+
* (non-Javadoc)
58+
* @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.reflect.Method)
59+
*/
60+
@Nullable
61+
@Override
62+
public String getMapping(Method method) {
63+
return ofNullable(delegate.getMapping(method)).map(getPropertyResolver()::resolvePlaceholders).orElse(null);
64+
}
65+
66+
/*
67+
* (non-Javadoc)
68+
* @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.Class, java.lang.reflect.Method)
69+
*/
70+
@Nullable
71+
@Override
72+
public String getMapping(Class<?> type, Method method) {
73+
return ofNullable(delegate.getMapping(type, method)).map(getPropertyResolver()::resolvePlaceholders).orElse(null);
74+
}
75+
76+
/*
77+
* (non-Javadoc)
78+
* @see org.springframework.hateoas.core.MappingDiscoverer#getRequestMethod(java.lang.Class, java.lang.reflect.Method)
79+
*/
80+
@Override
81+
public Collection<HttpMethod> getRequestMethod(Class<?> type, Method method) {
82+
return delegate.getRequestMethod(type, method);
83+
}
84+
85+
private static PropertyResolver getPropertyResolver() {
86+
return ContextLoader.getCurrentWebApplicationContext().getEnvironment();
87+
}
88+
}

src/main/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilder.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,13 @@
4747
* @author Andrew Naydyonock
4848
* @author Oliver Trosien
4949
* @author Greg Turnquist
50+
* @author Lars Michele
5051
*/
5152
@SuppressWarnings("deprecation")
5253
public class WebMvcLinkBuilder extends TemplateVariableAwareLinkBuilderSupport<WebMvcLinkBuilder> {
5354

5455
private static final MappingDiscoverer DISCOVERER = CachingMappingDiscoverer
55-
.of(new AnnotationMappingDiscoverer(RequestMapping.class));
56+
.of(PropertyResolvingMappingDiscoverer.of(new AnnotationMappingDiscoverer(RequestMapping.class)));
5657
private static final WebMvcLinkBuilderFactory FACTORY = new WebMvcLinkBuilderFactory();
5758
private static final CustomUriTemplateHandler HANDLER = new CustomUriTemplateHandler();
5859

src/test/java/org/springframework/hateoas/TestUtils.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import javax.servlet.http.HttpServletRequest;
2424

2525
import org.junit.jupiter.api.BeforeEach;
26+
import org.springframework.context.annotation.Configuration;
2627
import org.springframework.mock.web.MockFilterChain;
2728
import org.springframework.mock.web.MockHttpServletRequest;
2829
import org.springframework.mock.web.MockHttpServletResponse;
@@ -86,4 +87,7 @@ public static void assertNotEqualAndDifferentHashCode(Object left, Object right)
8687
assertThat(left.hashCode()).isNotEqualTo(right.hashCode());
8788
assertThat(left.toString()).isNotEqualTo(right.toString());
8889
}
90+
91+
@Configuration
92+
public static class Config {}
8993
}

src/test/java/org/springframework/hateoas/server/core/ControllerEntityLinksUnitTest.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,30 +26,46 @@
2626

2727
import java.util.Arrays;
2828

29+
import org.junit.jupiter.api.BeforeEach;
2930
import org.junit.jupiter.api.Test;
3031
import org.junit.jupiter.api.extension.ExtendWith;
3132
import org.mockito.Mock;
3233
import org.mockito.junit.jupiter.MockitoExtension;
34+
import org.springframework.beans.factory.annotation.Autowired;
3335
import org.springframework.hateoas.TestUtils;
3436
import org.springframework.hateoas.server.EntityLinks;
3537
import org.springframework.hateoas.server.ExposesResourceFor;
3638
import org.springframework.hateoas.server.LinkBuilder;
3739
import org.springframework.hateoas.server.LinkBuilderFactory;
3840
import org.springframework.hateoas.server.TypedEntityLinks;
3941
import org.springframework.hateoas.server.TypedEntityLinks.ExtendedTypedEntityLinks;
42+
import org.springframework.mock.web.MockServletContext;
4043
import org.springframework.stereotype.Controller;
44+
import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig;
4145
import org.springframework.web.bind.annotation.RequestMapping;
46+
import org.springframework.web.context.ContextLoader;
47+
import org.springframework.web.context.WebApplicationContext;
4248

4349
/**
4450
* Unit tests for {@link ControllerEntityLinks}.
4551
*
4652
* @author Oliver Gierke
4753
*/
4854
@ExtendWith(MockitoExtension.class)
55+
@SpringJUnitWebConfig(classes = TestUtils.Config.class)
4956
class ControllerEntityLinksUnitTest extends TestUtils {
5057

5158
@Mock LinkBuilderFactory<LinkBuilder> linkBuilderFactory;
5259

60+
@Autowired
61+
WebApplicationContext context;
62+
63+
@BeforeEach
64+
void contextLoading() {
65+
ContextLoader contextLoader = new ContextLoader(context);
66+
contextLoader.initWebApplicationContext(new MockServletContext());
67+
}
68+
5369
@Test
5470
void rejectsUnannotatedController() {
5571

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.hateoas.server.mvc;
17+
18+
import static org.assertj.core.api.Assertions.*;
19+
20+
import java.lang.reflect.Method;
21+
22+
import org.junit.jupiter.api.BeforeEach;
23+
import org.junit.jupiter.api.Test;
24+
import org.springframework.beans.factory.annotation.Autowired;
25+
import org.springframework.hateoas.TestUtils;
26+
import org.springframework.hateoas.server.core.AnnotationMappingDiscoverer;
27+
import org.springframework.mock.web.MockServletContext;
28+
import org.springframework.test.context.TestPropertySource;
29+
import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig;
30+
import org.springframework.web.bind.annotation.RequestMapping;
31+
import org.springframework.web.context.ContextLoader;
32+
import org.springframework.web.context.WebApplicationContext;
33+
34+
/**
35+
* Unit tests for {@link PropertyResolvingMappingDiscoverer}.
36+
*
37+
* @author Lars Michele
38+
*/
39+
@SpringJUnitWebConfig(classes = TestUtils.Config.class)
40+
@TestPropertySource(properties = {"test.parent=resolvedparent", "test.child=resolvedchild"})
41+
class PropertyResolvingMappingDiscovererUnitTest extends TestUtils {
42+
43+
@Autowired
44+
WebApplicationContext context;
45+
46+
@BeforeEach
47+
void contextLoading() {
48+
ContextLoader contextLoader = new ContextLoader(context);
49+
contextLoader.initWebApplicationContext(new MockServletContext());
50+
}
51+
52+
/**
53+
* @see #361
54+
*/
55+
@Test
56+
void resolvesVariablesInMappings() throws NoSuchMethodException {
57+
Method method = ResolveMethodEndpointController.class.getMethod("method");
58+
AnnotationMappingDiscoverer annotationMappingDiscoverer = new AnnotationMappingDiscoverer(RequestMapping.class);
59+
60+
// Test plain AnnotationMappingDiscoverer first
61+
assertThat(annotationMappingDiscoverer.getMapping(ResolveEndpointController.class)).isEqualTo("/${test.parent}");
62+
assertThat(annotationMappingDiscoverer.getMapping(ResolveMethodEndpointController.class, method))
63+
.isEqualTo("/${test.parent}/${test.child}");
64+
65+
PropertyResolvingMappingDiscoverer propertyResolvingMappingDiscoverer = PropertyResolvingMappingDiscoverer
66+
.of(annotationMappingDiscoverer);
67+
68+
assertThat(propertyResolvingMappingDiscoverer.getMapping(ResolveEndpointController.class)).isEqualTo("/resolvedparent");
69+
assertThat(propertyResolvingMappingDiscoverer.getMapping(method)).isEqualTo("/resolvedparent/resolvedchild");
70+
assertThat(propertyResolvingMappingDiscoverer.getMapping(ResolveMethodEndpointController.class, method))
71+
.isEqualTo("/resolvedparent/resolvedchild");
72+
}
73+
74+
@RequestMapping("/${test.parent}")
75+
interface ResolveEndpointController {}
76+
77+
@RequestMapping("/${test.parent}")
78+
interface ResolveMethodEndpointController {
79+
80+
@RequestMapping("/${test.child}")
81+
void method();
82+
}
83+
}

src/test/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilderFactoryUnitTest.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525

2626
import org.joda.time.DateTime;
2727
import org.joda.time.format.ISODateTimeFormat;
28+
import org.junit.jupiter.api.BeforeEach;
2829
import org.junit.jupiter.api.Test;
30+
import org.springframework.beans.factory.annotation.Autowired;
2931
import org.springframework.core.MethodParameter;
3032
import org.springframework.format.annotation.DateTimeFormat;
3133
import org.springframework.format.annotation.DateTimeFormat.ISO;
@@ -36,11 +38,15 @@
3638
import org.springframework.hateoas.server.mvc.WebMvcLinkBuilderUnitTest.PersonControllerImpl;
3739
import org.springframework.hateoas.server.mvc.WebMvcLinkBuilderUnitTest.PersonsAddressesController;
3840
import org.springframework.http.HttpEntity;
41+
import org.springframework.mock.web.MockServletContext;
42+
import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig;
3943
import org.springframework.util.LinkedMultiValueMap;
4044
import org.springframework.util.MultiValueMap;
4145
import org.springframework.web.bind.annotation.PathVariable;
4246
import org.springframework.web.bind.annotation.RequestMapping;
4347
import org.springframework.web.bind.annotation.RequestParam;
48+
import org.springframework.web.context.ContextLoader;
49+
import org.springframework.web.context.WebApplicationContext;
4450
import org.springframework.web.util.UriComponentsBuilder;
4551

4652
/**
@@ -51,8 +57,18 @@
5157
* @author Kamill Sokol
5258
* @author Ross Turner
5359
*/
60+
@SpringJUnitWebConfig(classes = TestUtils.Config.class)
5461
class WebMvcLinkBuilderFactoryUnitTest extends TestUtils {
5562

63+
@Autowired
64+
WebApplicationContext context;
65+
66+
@BeforeEach
67+
void contextLoading() {
68+
ContextLoader contextLoader = new ContextLoader(context);
69+
contextLoader.initWebApplicationContext(new MockServletContext());
70+
}
71+
5672
WebMvcLinkBuilderFactory factory = new WebMvcLinkBuilderFactory();
5773

5874
@Test

src/test/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilderUnitTest.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,27 @@
2323
import java.util.List;
2424
import java.util.Optional;
2525

26+
import org.junit.jupiter.api.BeforeEach;
2627
import org.junit.jupiter.api.Test;
28+
import org.springframework.beans.factory.annotation.Autowired;
2729
import org.springframework.hateoas.IanaLinkRelations;
2830
import org.springframework.hateoas.Link;
2931
import org.springframework.hateoas.TemplateVariable;
3032
import org.springframework.hateoas.TemplateVariable.VariableType;
3133
import org.springframework.hateoas.TestUtils;
3234
import org.springframework.http.HttpEntity;
3335
import org.springframework.http.ResponseEntity;
36+
import org.springframework.mock.web.MockServletContext;
37+
import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig;
3438
import org.springframework.util.MultiValueMap;
3539
import org.springframework.web.bind.annotation.GetMapping;
3640
import org.springframework.web.bind.annotation.PathVariable;
3741
import org.springframework.web.bind.annotation.RequestBody;
3842
import org.springframework.web.bind.annotation.RequestMapping;
3943
import org.springframework.web.bind.annotation.RequestParam;
4044
import org.springframework.web.bind.annotation.RestController;
45+
import org.springframework.web.context.ContextLoader;
46+
import org.springframework.web.context.WebApplicationContext;
4147
import org.springframework.web.util.UriComponents;
4248
import org.springframework.web.util.UriComponentsBuilder;
4349

@@ -53,8 +59,18 @@
5359
* @author Oliver Trosien
5460
* @author Greg Turnquist
5561
*/
62+
@SpringJUnitWebConfig(classes = TestUtils.Config.class)
5663
class WebMvcLinkBuilderUnitTest extends TestUtils {
5764

65+
@Autowired
66+
WebApplicationContext context;
67+
68+
@BeforeEach
69+
void contextLoading() {
70+
ContextLoader contextLoader = new ContextLoader(context);
71+
contextLoader.initWebApplicationContext(new MockServletContext());
72+
}
73+
5874
@Test
5975
void createsLinkToControllerRoot() {
6076

0 commit comments

Comments
 (0)