Skip to content

Commit 39dc555

Browse files
Josh Ghilonigregturn
Josh Ghiloni
authored andcommitted
#361 - Add support for resolving values in request mappings
1 parent 31c8b21 commit 39dc555

7 files changed

+285
-25
lines changed

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

+40-5
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,17 @@
2222
import java.lang.reflect.Method;
2323
import java.util.regex.Pattern;
2424

25+
import org.springframework.core.env.PropertyResolver;
2526
import org.springframework.util.Assert;
27+
import org.springframework.util.StringUtils;
2628

2729
/**
2830
* {@link MappingDiscoverer} implementation that inspects mappings from a particular annotation.
2931
*
3032
* @author Oliver Gierke
3133
* @author Mark Paluch
34+
* @author Josh Ghiloni
35+
* @author Greg Turnquist
3236
*/
3337
public class AnnotationMappingDiscoverer implements MappingDiscoverer {
3438

@@ -67,31 +71,46 @@ public AnnotationMappingDiscoverer(Class<? extends Annotation> annotation, Strin
6771
*/
6872
@Override
6973
public String getMapping(Class<?> type) {
74+
return getMapping(type, (PropertyResolver) null);
75+
}
76+
77+
@Override
78+
public String getMapping(Class<?> type, PropertyResolver resolver) {
7079

7180
Assert.notNull(type, "Type must not be null!");
7281

7382
String[] mapping = getMappingFrom(findMergedAnnotation(type, annotationType));
7483

75-
return mapping.length == 0 ? null : mapping[0];
84+
return attemptToResolve(resolver, mapping.length == 0 ? null : mapping[0]);
7685
}
7786

78-
/*
87+
/*
7988
* (non-Javadoc)
8089
* @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.reflect.Method)
8190
*/
8291
@Override
8392
public String getMapping(Method method) {
93+
return getMapping(method, null);
94+
}
95+
96+
@Override
97+
public String getMapping(Method method, PropertyResolver resolver) {
8498

8599
Assert.notNull(method, "Method must not be null!");
86-
return getMapping(method.getDeclaringClass(), method);
100+
return getMapping(method.getDeclaringClass(), method, resolver);
87101
}
88102

89-
/*
103+
/*
90104
* (non-Javadoc)
91105
* @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.Class, java.lang.reflect.Method)
92106
*/
93107
@Override
94108
public String getMapping(Class<?> type, Method method) {
109+
return getMapping(type, method, null);
110+
}
111+
112+
@Override
113+
public String getMapping(Class<?> type, Method method, PropertyResolver resolver) {
95114

96115
Assert.notNull(type, "Type must not be null!");
97116
Assert.notNull(method, "Method must not be null!");
@@ -103,7 +122,7 @@ public String getMapping(Class<?> type, Method method) {
103122
return typeMapping;
104123
}
105124

106-
return typeMapping == null || "/".equals(typeMapping) ? mapping[0] : join(typeMapping, mapping[0]);
125+
return attemptToResolve(resolver, typeMapping == null || "/".equals(typeMapping) ? mapping[0] : join(typeMapping, mapping[0]));
107126
}
108127

109128
private String[] getMappingFrom(Annotation annotation) {
@@ -122,6 +141,22 @@ private String[] getMappingFrom(Annotation annotation) {
122141
"Unsupported type for the mapping attribute! Support String and String[] but got %s!", value.getClass()));
123142
}
124143

144+
/**
145+
* If there is a {@link PropertyResolver}, use it to substitute values into the link.
146+
*
147+
* @param resolver
148+
* @param value
149+
* @return
150+
*/
151+
private String attemptToResolve(PropertyResolver resolver, String value) {
152+
153+
if (resolver != null && StringUtils.hasText(value)) {
154+
return resolver.resolvePlaceholders(value);
155+
} else {
156+
return value;
157+
}
158+
}
159+
125160
/**
126161
* Joins the given mappings making sure exactly one slash.
127162
*

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

+35-1
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,14 @@
1717

1818
import java.lang.reflect.Method;
1919

20+
import org.springframework.core.env.PropertyResolver;
21+
2022
/**
2123
* Strategy interface to discover a URI mapping for either a given type or method.
2224
*
2325
* @author Oliver Gierke
26+
* @author Josh Ghiloni
27+
* @author Greg Turnquist
2428
*/
2529
public interface MappingDiscoverer {
2630

@@ -32,14 +36,33 @@ public interface MappingDiscoverer {
3236
*/
3337
String getMapping(Class<?> type);
3438

39+
/**
40+
* Returns the mapping associated with the given type and {@link PropertyResolver}.
41+
*
42+
* @param type
43+
* @param resolver
44+
* @return the type-level mapping or {@literal null} in case none is present.
45+
*/
46+
String getMapping(Class<?> type, PropertyResolver resolver);
47+
3548
/**
3649
* Returns the mapping associated with the given {@link Method}. This will include the type-level mapping.
3750
*
3851
* @param method must not be {@literal null}.
39-
* @return the method mapping including the type-level one or {@literal null} if neither of them present.
52+
* @return the method mapping including the type-level one or {@literal null} if neither are present.
4053
*/
4154
String getMapping(Method method);
4255

56+
/**
57+
* Returns the mapping associated with the given {@link Method} and {@link PropertyResolver}. This will
58+
* include the type-level mapping.
59+
*
60+
* @param method
61+
* @param resolver
62+
* @return the method mapping including the type-level one or {@literal null} if neither are present.
63+
*/
64+
String getMapping(Method method, PropertyResolver resolver);
65+
4366
/**
4467
* Returns the mapping for the given {@link Method} invoked on the given type. This can be used to calculate the
4568
* mapping for a super type method being invoked on a sub-type with a type mapping.
@@ -49,4 +72,15 @@ public interface MappingDiscoverer {
4972
* @return the method mapping including the type-level one or {@literal null} if neither of them present.
5073
*/
5174
String getMapping(Class<?> type, Method method);
75+
76+
/**
77+
* Returns the mapping for the given {@link Method} invoked on the given type with the given {@link PropertyResolver}.
78+
* This can be used to calculate the mapping for a super type method being invoked on a sub-type with a type mapping.
79+
*
80+
* @param type
81+
* @param method
82+
* @param resolver
83+
* @return the method mapping including the type-level one or {@literal null} if neither of them present.
84+
*/
85+
String getMapping(Class<?> type, Method method, PropertyResolver resolver);
5286
}

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

+38-11
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,16 @@
1717

1818
import static org.springframework.util.StringUtils.*;
1919

20-
import lombok.RequiredArgsConstructor;
21-
import lombok.experimental.Delegate;
22-
2320
import java.lang.reflect.Method;
2421
import java.net.URI;
2522
import java.util.Map;
2623

2724
import javax.servlet.http.HttpServletRequest;
2825

26+
import lombok.RequiredArgsConstructor;
27+
import lombok.experimental.Delegate;
28+
29+
import org.springframework.core.env.PropertyResolver;
2930
import org.springframework.hateoas.Link;
3031
import org.springframework.hateoas.TemplateVariables;
3132
import org.springframework.hateoas.core.AnnotationMappingDiscoverer;
@@ -53,6 +54,8 @@
5354
* @author Kevin Conaway
5455
* @author Andrew Naydyonock
5556
* @author Oliver Trosien
57+
* @author Josh Ghiloni
58+
* @author Greg Turnquist
5659
*/
5760
public class ControllerLinkBuilder extends LinkBuilderSupport<ControllerLinkBuilder> {
5861

@@ -99,7 +102,11 @@ public class ControllerLinkBuilder extends LinkBuilderSupport<ControllerLinkBuil
99102
* @return
100103
*/
101104
public static ControllerLinkBuilder linkTo(Class<?> controller) {
102-
return linkTo(controller, new Object[0]);
105+
return linkTo(controller, (PropertyResolver) null);
106+
}
107+
108+
public static ControllerLinkBuilder linkTo(Class<?> controller, PropertyResolver resolver) {
109+
return linkTo(controller, resolver, new Object[0]);
103110
}
104111

105112
/**
@@ -112,11 +119,15 @@ public static ControllerLinkBuilder linkTo(Class<?> controller) {
112119
* @return
113120
*/
114121
public static ControllerLinkBuilder linkTo(Class<?> controller, Object... parameters) {
122+
return linkTo(controller, (PropertyResolver) null, parameters);
123+
}
124+
125+
public static ControllerLinkBuilder linkTo(Class<?> controller, PropertyResolver resolver, Object... parameters) {
115126

116127
Assert.notNull(controller, "Controller must not be null!");
117128
Assert.notNull(parameters, "Parameters must not be null!");
118129

119-
String mapping = DISCOVERER.getMapping(controller);
130+
String mapping = DISCOVERER.getMapping(controller, resolver);
120131

121132
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(mapping == null ? "/" : mapping);
122133
UriComponents uriComponents = HANDLER.expandAndEncode(builder, parameters);
@@ -134,11 +145,15 @@ public static ControllerLinkBuilder linkTo(Class<?> controller, Object... parame
134145
* @return
135146
*/
136147
public static ControllerLinkBuilder linkTo(Class<?> controller, Map<String, ?> parameters) {
148+
return linkTo(controller, null, parameters);
149+
}
150+
151+
public static ControllerLinkBuilder linkTo(Class<?> controller, PropertyResolver resolver, Map<String, ?> parameters) {
137152

138153
Assert.notNull(controller, "Controller must not be null!");
139154
Assert.notNull(parameters, "Parameters must not be null!");
140155

141-
String mapping = DISCOVERER.getMapping(controller);
156+
String mapping = DISCOVERER.getMapping(controller, resolver);
142157

143158
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(mapping == null ? "/" : mapping);
144159
UriComponents uriComponents = HANDLER.expandAndEncode(builder, parameters);
@@ -150,18 +165,26 @@ public static ControllerLinkBuilder linkTo(Class<?> controller, Map<String, ?> p
150165
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(Method, Object...)
151166
*/
152167
public static ControllerLinkBuilder linkTo(Method method, Object... parameters) {
153-
return linkTo(method.getDeclaringClass(), method, parameters);
168+
return linkTo(method, null, parameters);
169+
}
170+
171+
public static ControllerLinkBuilder linkTo(Method method, PropertyResolver resolver, Object... parameters) {
172+
return linkTo(method.getDeclaringClass(), method, resolver, parameters);
154173
}
155174

156175
/*
157176
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(Class<?>, Method, Object...)
158177
*/
159178
public static ControllerLinkBuilder linkTo(Class<?> controller, Method method, Object... parameters) {
179+
return linkTo(controller, method, null, parameters);
180+
}
181+
182+
public static ControllerLinkBuilder linkTo(Class<?> controller, Method method, PropertyResolver resolver, Object... parameters) {
160183

161184
Assert.notNull(controller, "Controller type must not be null!");
162185
Assert.notNull(method, "Method must not be null!");
163186

164-
UriTemplate template = DISCOVERER.getMappingAsUriTemplate(controller, method);
187+
UriTemplate template = DISCOVERER.getMappingAsUriTemplate(controller, method, resolver);
165188
URI uri = template.expand(parameters);
166189

167190
return new ControllerLinkBuilder(getBuilder()).slash(uri);
@@ -190,7 +213,11 @@ public static ControllerLinkBuilder linkTo(Class<?> controller, Method method, O
190213
* @return
191214
*/
192215
public static ControllerLinkBuilder linkTo(Object invocationValue) {
193-
return FACTORY.linkTo(invocationValue);
216+
return linkTo(invocationValue, null);
217+
}
218+
219+
public static ControllerLinkBuilder linkTo(Object invocationValue, PropertyResolver resolver) {
220+
return FACTORY.linkTo(invocationValue, resolver);
194221
}
195222

196223
/**
@@ -301,9 +328,9 @@ private static class CachingAnnotationMappingDiscoverer implements MappingDiscov
301328
private final @Delegate AnnotationMappingDiscoverer delegate;
302329
private final Map<String, UriTemplate> templates = new ConcurrentReferenceHashMap<String, UriTemplate>();
303330

304-
public UriTemplate getMappingAsUriTemplate(Class<?> type, Method method) {
331+
public UriTemplate getMappingAsUriTemplate(Class<?> type, Method method, PropertyResolver resolver) {
305332

306-
String mapping = delegate.getMapping(type, method);
333+
String mapping = delegate.getMapping(type, method, resolver);
307334

308335
UriTemplate template = templates.get(mapping);
309336

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

+34-6
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.util.Map;
3232

3333
import org.springframework.core.MethodParameter;
34+
import org.springframework.core.env.PropertyResolver;
3435
import org.springframework.hateoas.Link;
3536
import org.springframework.hateoas.MethodLinkBuilderFactory;
3637
import org.springframework.hateoas.TemplateVariable;
@@ -64,6 +65,8 @@
6465
* @author Oemer Yildiz
6566
* @author Kevin Conaway
6667
* @author Andrew Naydyonock
68+
* @author Josh Ghiloni
69+
* @author Greg Turnquist
6770
*/
6871
public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory<ControllerLinkBuilder> {
6972

@@ -91,7 +94,12 @@ public void setUriComponentsContributors(List<? extends UriComponentsContributor
9194
*/
9295
@Override
9396
public ControllerLinkBuilder linkTo(Class<?> controller) {
94-
return ControllerLinkBuilder.linkTo(controller);
97+
return linkTo(controller, (PropertyResolver) null);
98+
}
99+
100+
public ControllerLinkBuilder linkTo(Class<?> controller, PropertyResolver resolver) {
101+
return ControllerLinkBuilder.linkTo(controller, resolver);
102+
95103
}
96104

97105
/*
@@ -100,7 +108,11 @@ public ControllerLinkBuilder linkTo(Class<?> controller) {
100108
*/
101109
@Override
102110
public ControllerLinkBuilder linkTo(Class<?> controller, Object... parameters) {
103-
return ControllerLinkBuilder.linkTo(controller, parameters);
111+
return linkTo(controller, (PropertyResolver) null, parameters);
112+
}
113+
114+
public ControllerLinkBuilder linkTo(Class<?> controller, PropertyResolver resolver, Object... parameters) {
115+
return ControllerLinkBuilder.linkTo(controller, resolver, parameters);
104116
}
105117

106118
/*
@@ -109,7 +121,11 @@ public ControllerLinkBuilder linkTo(Class<?> controller, Object... parameters) {
109121
*/
110122
@Override
111123
public ControllerLinkBuilder linkTo(Class<?> controller, Map<String, ?> parameters) {
112-
return ControllerLinkBuilder.linkTo(controller, parameters);
124+
return linkTo(controller, null, parameters);
125+
}
126+
127+
public ControllerLinkBuilder linkTo(Class<?> controller, PropertyResolver resolver, Map<String, ?> parameters) {
128+
return ControllerLinkBuilder.linkTo(controller, resolver, parameters);
113129
}
114130

115131
/*
@@ -118,7 +134,11 @@ public ControllerLinkBuilder linkTo(Class<?> controller, Map<String, ?> paramete
118134
*/
119135
@Override
120136
public ControllerLinkBuilder linkTo(Class<?> controller, Method method, Object... parameters) {
121-
return ControllerLinkBuilder.linkTo(controller, method, parameters);
137+
return linkTo(controller, method, null, parameters);
138+
}
139+
140+
public ControllerLinkBuilder linkTo(Class<?> controller, Method method, PropertyResolver resolver, Object... parameters) {
141+
return ControllerLinkBuilder.linkTo(controller, method, resolver, parameters);
122142
}
123143

124144
/*
@@ -127,6 +147,10 @@ public ControllerLinkBuilder linkTo(Class<?> controller, Method method, Object..
127147
*/
128148
@Override
129149
public ControllerLinkBuilder linkTo(Object invocationValue) {
150+
return linkTo(invocationValue,null);
151+
}
152+
153+
public ControllerLinkBuilder linkTo(Object invocationValue, PropertyResolver resolver) {
130154

131155
Assert.isInstanceOf(LastInvocationAware.class, invocationValue);
132156
LastInvocationAware invocations = (LastInvocationAware) invocationValue;
@@ -135,7 +159,7 @@ public ControllerLinkBuilder linkTo(Object invocationValue) {
135159
Iterator<Object> classMappingParameters = invocations.getObjectParameters();
136160
Method method = invocation.getMethod();
137161

138-
String mapping = DISCOVERER.getMapping(invocation.getTargetType(), method);
162+
String mapping = DISCOVERER.getMapping(invocation.getTargetType(), method, resolver);
139163
UriComponentsBuilder builder = ControllerLinkBuilder.getBuilder().path(mapping);
140164

141165
UriTemplate template = new UriTemplate(mapping);
@@ -192,7 +216,11 @@ public ControllerLinkBuilder linkTo(Object invocationValue) {
192216
*/
193217
@Override
194218
public ControllerLinkBuilder linkTo(Method method, Object... parameters) {
195-
return ControllerLinkBuilder.linkTo(method, parameters);
219+
return linkTo(method, null, parameters);
220+
}
221+
222+
public ControllerLinkBuilder linkTo(Method method, PropertyResolver resolver, Object... parameters) {
223+
return ControllerLinkBuilder.linkTo(method, resolver, parameters);
196224
}
197225

198226
/**

0 commit comments

Comments
 (0)