Skip to content

Commit 3e9a4d8

Browse files
Josh Ghilonigregturn
Josh Ghiloni
authored andcommitted
#361 - Add support for resolving values in request mappings.
Original pull-request: #391
1 parent 9590d1f commit 3e9a4d8

8 files changed

+354
-43
lines changed

src/main/java/org/springframework/hateoas/core/AnnotationMappingDiscoverer.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public String getMapping(Class<?> type) {
8181
return mapping.length == 0 ? null : mapping[0];
8282
}
8383

84-
/*
84+
/*
8585
* (non-Javadoc)
8686
* @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.reflect.Method)
8787
*/
@@ -92,7 +92,7 @@ public String getMapping(Method method) {
9292
return getMapping(method.getDeclaringClass(), method);
9393
}
9494

95-
/*
95+
/*
9696
* (non-Javadoc)
9797
* @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.Class, java.lang.reflect.Method)
9898
*/

src/main/java/org/springframework/hateoas/core/MappingDiscoverer.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
* given type or method.
2626
*
2727
* @author Oliver Gierke
28+
* @author Josh Ghiloni
2829
* @author Greg Turnquist
2930
*/
3031
public interface MappingDiscoverer {
@@ -41,7 +42,7 @@ public interface MappingDiscoverer {
4142
* Returns the mapping associated with the given {@link Method}. This will include the type-level mapping.
4243
*
4344
* @param method must not be {@literal null}.
44-
* @return the method mapping including the type-level one or {@literal null} if neither of them present.
45+
* @return the method mapping including the type-level one or {@literal null} if neither are present.
4546
*/
4647
String getMapping(Method method);
4748

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright 2017 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+
* http://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.core;
17+
18+
import java.lang.reflect.Method;
19+
import java.util.Collection;
20+
21+
import org.springframework.core.env.PropertyResolver;
22+
import org.springframework.http.HttpMethod;
23+
import org.springframework.util.Assert;
24+
25+
/**
26+
* Take any other {@link MappingDiscoverer} and wrap it with an attempt to resolve properties via {@link PropertyResolver}.
27+
*
28+
* @author Greg Turnquist
29+
*/
30+
public class PropertyResolvingDiscoverer implements MappingDiscoverer {
31+
32+
private final MappingDiscoverer discoverer;
33+
private final PropertyResolver resolver;
34+
35+
public PropertyResolvingDiscoverer(MappingDiscoverer discoverer, PropertyResolver resolver) {
36+
37+
Assert.notNull(discoverer, "MappingDiscoverer must not be null!");
38+
Assert.notNull(resolver, "PropertyResolver must not be null!");
39+
40+
this.discoverer = discoverer;
41+
this.resolver = resolver;
42+
}
43+
44+
/**
45+
* Returns the mapping associated with the given type.
46+
*
47+
* @param type must not be {@literal null}.
48+
* @return the type-level mapping or {@literal null} in case none is present.
49+
*/
50+
@Override
51+
public String getMapping(Class<?> type) {
52+
return attemptToResolve(this.resolver, this.discoverer.getMapping(type));
53+
}
54+
55+
/**
56+
* Returns the mapping associated with the given {@link Method}. This will include the type-level mapping.
57+
*
58+
* @param method must not be {@literal null}.
59+
* @return the method mapping including the type-level one or {@literal null} if neither of them present.
60+
*/
61+
@Override
62+
public String getMapping(Method method) {
63+
return attemptToResolve(this.resolver, this.discoverer.getMapping(method));
64+
}
65+
66+
/**
67+
* Returns the mapping for the given {@link Method} invoked on the given type. This can be used to calculate the
68+
* mapping for a super type method being invoked on a sub-type with a type mapping.
69+
*
70+
* @param type must not be {@literal null}.
71+
* @param method must not be {@literal null}.
72+
* @return the method mapping including the type-level one or {@literal null} if neither of them present.
73+
*/
74+
@Override
75+
public String getMapping(Class<?> type, Method method) {
76+
return attemptToResolve(resolver, this.discoverer.getMapping(type, method));
77+
}
78+
79+
@Override
80+
public Collection<HttpMethod> getRequestMethod(Class<?> type, Method method) {
81+
return this.discoverer.getRequestMethod(type, method);
82+
}
83+
84+
/**
85+
* Use the {@link PropertyResolver} to substitute values into the link.
86+
*
87+
* @param resolver
88+
* @param value
89+
* @return
90+
*/
91+
private String attemptToResolve(PropertyResolver resolver, String value) {
92+
return resolver.resolvePlaceholders(value);
93+
}
94+
}

src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java

+70-7
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import static org.springframework.hateoas.mvc.ForwardedHeader.*;
1919

2020
import lombok.RequiredArgsConstructor;
21-
import lombok.experimental.Delegate;
2221

2322
import java.lang.reflect.Method;
2423
import java.net.URI;
@@ -39,6 +38,7 @@
3938
import org.springframework.hateoas.core.DummyInvocationUtils.MethodInvocation;
4039
import org.springframework.hateoas.core.LinkBuilderSupport;
4140
import org.springframework.hateoas.core.MappingDiscoverer;
41+
import org.springframework.http.HttpMethod;
4242
import org.springframework.http.MediaType;
4343
import org.springframework.plugin.core.OrderAwarePluginRegistry;
4444
import org.springframework.plugin.core.PluginRegistry;
@@ -63,6 +63,7 @@
6363
* @author Kevin Conaway
6464
* @author Andrew Naydyonock
6565
* @author Oliver Trosien
66+
* @author Josh Ghiloni
6667
* @author Greg Turnquist
6768
*/
6869
public class ControllerLinkBuilder extends LinkBuilderSupport<ControllerLinkBuilder> {
@@ -82,6 +83,8 @@ public class ControllerLinkBuilder extends LinkBuilderSupport<ControllerLinkBuil
8283
.create(factories);
8384
}
8485

86+
private static MappingDiscoverer delegateDiscovererOverride = null;
87+
8588
private final TemplateVariables variables;
8689

8790
/**
@@ -102,17 +105,26 @@ public class ControllerLinkBuilder extends LinkBuilderSupport<ControllerLinkBuil
102105
* @param uriComponents must not be {@literal null}.
103106
*/
104107
ControllerLinkBuilder(UriComponents uriComponents) {
105-
this(uriComponents, TemplateVariables.NONE, null);
108+
this(uriComponents, TemplateVariables.NONE, null, null);
106109
}
107110

108-
ControllerLinkBuilder(UriComponents uriComponents, TemplateVariables variables, MethodInvocation invocation) {
111+
ControllerLinkBuilder(UriComponents uriComponents, TemplateVariables variables, MethodInvocation invocation, MappingDiscoverer discoverer) {
109112

110113
super(uriComponents);
111114

112115
this.variables = variables;
116+
ControllerLinkBuilder.delegateDiscovererOverride = discoverer;
113117
this.addAffordances(findAffordances(invocation, uriComponents));
114118
}
115119

120+
public static void setDelegateDiscoverer(MappingDiscoverer delegateDiscovererOverride) {
121+
ControllerLinkBuilder.delegateDiscovererOverride = delegateDiscovererOverride;
122+
}
123+
124+
public static void clearDelegateDiscoverer() {
125+
ControllerLinkBuilder.delegateDiscovererOverride = null;
126+
}
127+
116128
/**
117129
* Creates a new {@link ControllerLinkBuilder} with a base of the mapping annotated to the given controller class.
118130
*
@@ -249,7 +261,7 @@ public static <T> T methodOn(Class<T> controller, Object... parameters) {
249261
return DummyInvocationUtils.methodOn(controller, parameters);
250262
}
251263

252-
/*
264+
/*
253265
* (non-Javadoc)
254266
* @see org.springframework.hateoas.UriComponentsLinkBuilder#getThis()
255267
*/
@@ -368,14 +380,65 @@ private static Collection<Affordance> findAffordances(MethodInvocation invocatio
368380
@RequiredArgsConstructor
369381
private static class CachingAnnotationMappingDiscoverer implements MappingDiscoverer {
370382

371-
private final @Delegate AnnotationMappingDiscoverer delegate;
383+
private final AnnotationMappingDiscoverer delegate;
372384
private final Map<String, UriTemplate> templates = new ConcurrentReferenceHashMap<>();
373385

386+
/**
387+
* If {@link ControllerLinkBuilder} has a static {@link MappingDiscoverer}, use it instead of the delegate.
388+
* @return
389+
*/
390+
private MappingDiscoverer getDelegate() {
391+
return (delegateDiscovererOverride != null) ? delegateDiscovererOverride : this.delegate;
392+
}
393+
374394
public UriTemplate getMappingAsUriTemplate(Class<?> type, Method method) {
375395

376-
String mapping = delegate.getMapping(type, method);
396+
String mapping = getDelegate().getMapping(type, method);
377397
return templates.computeIfAbsent(mapping, UriTemplate::new);
378398
}
399+
400+
/**
401+
* Returns the mapping associated with the given type.
402+
*
403+
* @param type must not be {@literal null}.
404+
* @return the type-level mapping or {@literal null} in case none is present.
405+
*/
406+
@Override
407+
public String getMapping(Class<?> type) {
408+
return getDelegate().getMapping(type);
409+
}
410+
411+
/**
412+
* Returns the mapping associated with the given {@link Method}. This will include the type-level mapping.
413+
*
414+
* @param method must not be {@literal null}.
415+
* @return the method mapping including the type-level one or {@literal null} if neither of them present.
416+
*/
417+
@Override
418+
public String getMapping(Method method) {
419+
return getDelegate().getMapping(method);
420+
}
421+
422+
/**
423+
* Returns the mapping for the given {@link Method} invoked on the given type. This can be used to calculate the
424+
* mapping for a super type method being invoked on a sub-type with a type mapping.
425+
*
426+
* @param type must not be {@literal null}.
427+
* @param method must not be {@literal null}.
428+
* @return the method mapping including the type-level one or {@literal null} if neither of them present.
429+
*/
430+
@Override
431+
public String getMapping(Class<?> type, Method method) {
432+
return getDelegate().getMapping(type, method);
433+
}
434+
435+
/**
436+
* Returns the {@link HttpMethod} invoked on the given type and {@link Method}.
437+
*/
438+
@Override
439+
public Collection<HttpMethod> getRequestMethod(Class<?> type, Method method) {
440+
return getDelegate().getRequestMethod(type, method);
441+
}
379442
}
380443

381444
private static class CustomUriTemplateHandler extends DefaultUriTemplateHandler {
@@ -402,4 +465,4 @@ public UriComponents expandAndEncode(UriComponentsBuilder builder, Object[] uriV
402465
return super.expandAndEncode(builder, uriVariables);
403466
}
404467
}
405-
}
468+
}

src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactory.java

+13-5
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,25 @@
6565
* @author Oemer Yildiz
6666
* @author Kevin Conaway
6767
* @author Andrew Naydyonock
68+
* @author Josh Ghiloni
6869
* @author Greg Turnquist
6970
*/
7071
public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory<ControllerLinkBuilder> {
7172

72-
private static final MappingDiscoverer DISCOVERER = new AnnotationMappingDiscoverer(RequestMapping.class);
7373
private static final AnnotatedParametersParameterAccessor PATH_VARIABLE_ACCESSOR = new AnnotatedParametersParameterAccessor(
7474
new AnnotationAttribute(PathVariable.class));
7575
private static final AnnotatedParametersParameterAccessor REQUEST_PARAM_ACCESSOR = new RequestParamParameterAccessor();
7676

77-
private List<UriComponentsContributor> uriComponentsContributors = new ArrayList<>();
77+
private MappingDiscoverer discoverer;
78+
private List<UriComponentsContributor> uriComponentsContributors = new ArrayList<UriComponentsContributor>();
79+
80+
public ControllerLinkBuilderFactory() {
81+
this.discoverer = new AnnotationMappingDiscoverer(RequestMapping.class);
82+
}
83+
84+
public ControllerLinkBuilderFactory(MappingDiscoverer discoverer) {
85+
this.discoverer = discoverer;
86+
}
7887

7988
/**
8089
* Configures the {@link UriComponentsContributor} to be used when building {@link Link} instances from method
@@ -137,8 +146,7 @@ public ControllerLinkBuilder linkTo(Object invocationValue) {
137146
Iterator<Object> classMappingParameters = invocations.getObjectParameters();
138147
Method method = invocation.getMethod();
139148

140-
String mapping = DISCOVERER.getMapping(invocation.getTargetType(), method);
141-
149+
String mapping = this.discoverer.getMapping(invocation.getTargetType(), method);
142150
UriComponentsBuilder builder = ControllerLinkBuilder.getBuilder().path(mapping);
143151

144152
UriTemplate template = new UriTemplate(mapping);
@@ -186,7 +194,7 @@ public ControllerLinkBuilder linkTo(Object invocationValue) {
186194
variables = variables.concat(variable);
187195
}
188196

189-
return new ControllerLinkBuilder(components, variables, invocation);
197+
return new ControllerLinkBuilder(components, variables, invocation, discoverer);
190198
}
191199

192200
/*

0 commit comments

Comments
 (0)