Skip to content

Commit 0852752

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

File tree

7 files changed

+371
-45
lines changed

7 files changed

+371
-45
lines changed

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2014 the original author or authors.
2+
* Copyright 2012-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -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

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright 2017-2019 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+
* @since 1.0
30+
*/
31+
public class PropertyResolvingDiscoverer implements MappingDiscoverer {
32+
33+
private final MappingDiscoverer discoverer;
34+
private final PropertyResolver resolver;
35+
36+
public PropertyResolvingDiscoverer(MappingDiscoverer discoverer, PropertyResolver resolver) {
37+
38+
Assert.notNull(discoverer, "MappingDiscoverer must not be null!");
39+
Assert.notNull(resolver, "PropertyResolver must not be null!");
40+
41+
this.discoverer = discoverer;
42+
this.resolver = resolver;
43+
}
44+
45+
/**
46+
* Returns the mapping associated with the given type.
47+
*
48+
* @param type must not be {@literal null}.
49+
* @return the type-level mapping or {@literal null} in case none is present.
50+
*/
51+
@Override
52+
public String getMapping(Class<?> type) {
53+
return attemptToResolve(this.discoverer.getMapping(type));
54+
}
55+
56+
/**
57+
* Returns the mapping associated with the given {@link Method}. This will include the type-level mapping.
58+
*
59+
* @param method must not be {@literal null}.
60+
* @return the method mapping including the type-level one or {@literal null} if neither of them present.
61+
*/
62+
@Override
63+
public String getMapping(Method method) {
64+
return attemptToResolve(this.discoverer.getMapping(method));
65+
}
66+
67+
/**
68+
* Returns the mapping for the given {@link Method} invoked on the given type. This can be used to calculate the
69+
* mapping for a super type method being invoked on a sub-type with a type mapping.
70+
*
71+
* @param type must not be {@literal null}.
72+
* @param method must not be {@literal null}.
73+
* @return the method mapping including the type-level one or {@literal null} if neither of them present.
74+
*/
75+
@Override
76+
public String getMapping(Class<?> type, Method method) {
77+
return attemptToResolve(this.discoverer.getMapping(type, method));
78+
}
79+
80+
/**
81+
* Returns the HTTP verbs for the given {@link Method} invoked on the given type. This can be used to build hypermedia
82+
* templates.
83+
*
84+
* @param type
85+
* @param method
86+
* @return
87+
*/
88+
@Override
89+
public Collection<HttpMethod> getRequestMethod(Class<?> type, Method method) {
90+
return this.discoverer.getRequestMethod(type, method);
91+
}
92+
93+
/**
94+
* Use the {@link PropertyResolver} to substitute values into the link.
95+
*
96+
* @param value
97+
* @return
98+
*/
99+
private String attemptToResolve(String value) {
100+
return this.resolver.resolvePlaceholders(value);
101+
}
102+
}

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

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2018 the original author or authors.
2+
* Copyright 2012-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -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
*
@@ -368,14 +380,72 @@ 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

374386
public UriTemplate getMappingAsUriTemplate(Class<?> type, Method method) {
375387

376-
String mapping = delegate.getMapping(type, method);
388+
String mapping = getDelegate().getMapping(type, method);
377389
return templates.computeIfAbsent(mapping, UriTemplate::new);
378390
}
391+
392+
/**
393+
* Returns the mapping associated with the given type.
394+
*
395+
* @param type must not be {@literal null}.
396+
* @return the type-level mapping or {@literal null} in case none is present.
397+
*/
398+
@Override
399+
public String getMapping(Class<?> type) {
400+
return getDelegate().getMapping(type);
401+
}
402+
403+
/**
404+
* Returns the mapping associated with the given {@link Method}. This will include the type-level mapping.
405+
*
406+
* @param method must not be {@literal null}.
407+
* @return the method mapping including the type-level one or {@literal null} if neither of them present.
408+
*/
409+
@Override
410+
public String getMapping(Method method) {
411+
return getDelegate().getMapping(method);
412+
}
413+
414+
/**
415+
* Returns the mapping for the given {@link Method} invoked on the given type. This can be used to calculate the
416+
* mapping for a super type method being invoked on a sub-type with a type mapping.
417+
*
418+
* @param type must not be {@literal null}.
419+
* @param method must not be {@literal null}.
420+
* @return the method mapping including the type-level one or {@literal null} if neither of them present.
421+
*/
422+
@Override
423+
public String getMapping(Class<?> type, Method method) {
424+
return getDelegate().getMapping(type, method);
425+
}
426+
427+
/**
428+
* Returns the HTTP verbs for the given {@link Method} invoked on the given type. This can be used to build hypermedia
429+
* templates.
430+
*
431+
* @param type
432+
* @param method
433+
* @return a collection of {@link HttpMethod}s.
434+
*/
435+
@Override
436+
public Collection<HttpMethod> getRequestMethod(Class<?> type, Method method) {
437+
return getDelegate().getRequestMethod(type, method);
438+
}
439+
440+
/**
441+
* If {@link ControllerLinkBuilder} has a static {@link MappingDiscoverer} override, use it instead of the delegate.
442+
*/
443+
private MappingDiscoverer getDelegate() {
444+
445+
return (ControllerLinkBuilder.delegateDiscovererOverride != null)
446+
? ControllerLinkBuilder.delegateDiscovererOverride
447+
: this.delegate;
448+
}
379449
}
380450

381451
private static class CustomUriTemplateHandler extends DefaultUriTemplateHandler {
@@ -402,4 +472,4 @@ public UriComponents expandAndEncode(UriComponentsBuilder builder, Object[] uriV
402472
return super.expandAndEncode(builder, uriVariables);
403473
}
404474
}
405-
}
475+
}

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2017 the original author or authors.
2+
* Copyright 2012-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -65,17 +65,26 @@
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 MappingDiscoverer discoverer;
7778
private List<UriComponentsContributor> uriComponentsContributors = new ArrayList<>();
7879

80+
public ControllerLinkBuilderFactory() {
81+
this.discoverer = new AnnotationMappingDiscoverer(RequestMapping.class);
82+
}
83+
84+
public ControllerLinkBuilderFactory(MappingDiscoverer discoverer) {
85+
this.discoverer = discoverer;
86+
}
87+
7988
/**
8089
* Configures the {@link UriComponentsContributor} to be used when building {@link Link} instances from method
8190
* invocations.
@@ -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)