Skip to content

Commit a3aea75

Browse files
committed
#118 - Allow injecting custom ConversionService
Existing ConversionService is fixed as a static inside BoundMethodParameter. This introduces ability to inject an alternative ConversionService. Related issues: #352, #149
1 parent 05f687e commit a3aea75

File tree

3 files changed

+75
-11
lines changed

3 files changed

+75
-11
lines changed

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

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class AnnotatedParametersParameterAccessor {
5656
* @param invocation must not be {@literal null}.
5757
* @return
5858
*/
59-
public List<BoundMethodParameter> getBoundParameters(MethodInvocation invocation) {
59+
public List<BoundMethodParameter> getBoundParameters(MethodInvocation invocation, ConversionService conversionService) {
6060

6161
Assert.notNull(invocation, "MethodInvocation must not be null!");
6262

@@ -70,7 +70,7 @@ public List<BoundMethodParameter> getBoundParameters(MethodInvocation invocation
7070
Object verifiedValue = verifyParameterValue(parameter, value);
7171

7272
if (verifiedValue != null) {
73-
result.add(createParameter(parameter, verifiedValue, attribute));
73+
result.add(createParameter(parameter, verifiedValue, attribute, conversionService));
7474
}
7575
}
7676

@@ -87,8 +87,8 @@ public List<BoundMethodParameter> getBoundParameters(MethodInvocation invocation
8787
* @return
8888
*/
8989
protected BoundMethodParameter createParameter(MethodParameter parameter, Object value,
90-
AnnotationAttribute attribute) {
91-
return new BoundMethodParameter(parameter, value, attribute);
90+
AnnotationAttribute attribute, ConversionService conversionService) {
91+
return new BoundMethodParameter(parameter, value, attribute, conversionService);
9292
}
9393

9494
/**
@@ -138,21 +138,25 @@ static class BoundMethodParameter {
138138
private final AnnotationAttribute attribute;
139139
private final TypeDescriptor parameterTypeDescriptor;
140140

141+
private ConversionService overrideConversionService = null;
142+
141143
/**
142144
* Creates a new {@link BoundMethodParameter}
143145
*
144146
* @param parameter
145147
* @param value
146148
* @param attribute
147149
*/
148-
public BoundMethodParameter(MethodParameter parameter, Object value, AnnotationAttribute attribute) {
150+
public BoundMethodParameter(MethodParameter parameter, Object value, AnnotationAttribute attribute,
151+
ConversionService overrideConversionService) {
149152

150153
Assert.notNull(parameter, "MethodParameter must not be null!");
151154

152155
this.parameter = parameter;
153156
this.value = value;
154157
this.attribute = attribute;
155158
this.parameterTypeDescriptor = TypeDescriptor.nested(parameter, 0);
159+
this.overrideConversionService = overrideConversionService;
156160
}
157161

158162
/**
@@ -189,7 +193,7 @@ public Object getValue() {
189193
*/
190194
public String asString() {
191195
return value == null ? null
192-
: (String) CONVERSION_SERVICE.convert(value, parameterTypeDescriptor, STRING_DESCRIPTOR);
196+
: (String) getConversionService().convert(value, parameterTypeDescriptor, STRING_DESCRIPTOR);
193197
}
194198

195199
/**
@@ -200,5 +204,9 @@ public String asString() {
200204
public boolean isRequired() {
201205
return true;
202206
}
207+
208+
private ConversionService getConversionService() {
209+
return (this.overrideConversionService != null) ? this.overrideConversionService : CONVERSION_SERVICE;
210+
}
203211
}
204212
}

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

Lines changed: 16 additions & 5 deletions
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.convert.ConversionService;
3435
import org.springframework.hateoas.Link;
3536
import org.springframework.hateoas.MethodLinkBuilderFactory;
3637
import org.springframework.hateoas.TemplateVariable;
@@ -72,8 +73,10 @@ public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory<Co
7273
new AnnotationAttribute(PathVariable.class));
7374
private static final AnnotatedParametersParameterAccessor REQUEST_PARAM_ACCESSOR = new RequestParamParameterAccessor();
7475

75-
private List<UriComponentsContributor> uriComponentsContributors = new ArrayList<UriComponentsContributor>();
76+
private static ConversionService overrideDefaultConversionService = null;
7677

78+
private List<UriComponentsContributor> uriComponentsContributors = new ArrayList<UriComponentsContributor>();
79+
7780
/**
7881
* Configures the {@link UriComponentsContributor} to be used when building {@link Link} instances from method
7982
* invocations.
@@ -146,13 +149,13 @@ public ControllerLinkBuilder linkTo(Object invocationValue) {
146149
values.put(names.next(), encodePath(classMappingParameters.next()));
147150
}
148151

149-
for (BoundMethodParameter parameter : PATH_VARIABLE_ACCESSOR.getBoundParameters(invocation)) {
152+
for (BoundMethodParameter parameter : PATH_VARIABLE_ACCESSOR.getBoundParameters(invocation, overrideDefaultConversionService)) {
150153
values.put(parameter.getVariableName(), encodePath(parameter.asString()));
151154
}
152155

153156
List<String> optionalEmptyParameters = new ArrayList<String>();
154157

155-
for (BoundMethodParameter parameter : REQUEST_PARAM_ACCESSOR.getBoundParameters(invocation)) {
158+
for (BoundMethodParameter parameter : REQUEST_PARAM_ACCESSOR.getBoundParameters(invocation, overrideDefaultConversionService)) {
156159

157160
bindRequestParameters(builder, parameter);
158161

@@ -268,6 +271,14 @@ private static void bindRequestParameters(UriComponentsBuilder builder, BoundMet
268271
}
269272
}
270273

274+
public static void setConversionService(ConversionService conversionService) {
275+
overrideDefaultConversionService = conversionService;
276+
}
277+
278+
public static void clearConversionService() {
279+
overrideDefaultConversionService = null;
280+
}
281+
271282
/**
272283
* Custom extension of {@link AnnotatedParametersParameterAccessor} for {@link RequestParam} to allow {@literal null}
273284
* values handed in for optional request parameters.
@@ -286,9 +297,9 @@ public RequestParamParameterAccessor() {
286297
*/
287298
@Override
288299
protected BoundMethodParameter createParameter(final MethodParameter parameter, Object value,
289-
AnnotationAttribute attribute) {
300+
AnnotationAttribute attribute, ConversionService conversionService) {
290301

291-
return new BoundMethodParameter(parameter, value, attribute) {
302+
return new BoundMethodParameter(parameter, value, attribute, conversionService) {
292303

293304
/*
294305
* (non-Javadoc)

src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactoryUnitTest.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,13 @@
2424
import java.util.LinkedHashMap;
2525
import java.util.Map;
2626

27+
import org.hamcrest.CoreMatchers;
2728
import org.joda.time.DateTime;
2829
import org.joda.time.format.ISODateTimeFormat;
2930
import org.junit.Test;
3031
import org.springframework.core.MethodParameter;
32+
import org.springframework.core.convert.ConversionService;
33+
import org.springframework.core.convert.TypeDescriptor;
3134
import org.springframework.format.annotation.DateTimeFormat;
3235
import org.springframework.format.annotation.DateTimeFormat.ISO;
3336
import org.springframework.hateoas.Link;
@@ -102,6 +105,48 @@ public void usesDateTimeFormatForUriBinding() {
102105
assertThat(link.getHref(), endsWith("/sample/" + ISODateTimeFormat.date().print(now)));
103106
}
104107

108+
@Test
109+
public void pluginCustomConversionService() {
110+
111+
DateTime now = DateTime.now();
112+
113+
ControllerLinkBuilderFactory factory = new ControllerLinkBuilderFactory();
114+
115+
final TypeDescriptor[] actualSourceType = {null};
116+
final TypeDescriptor[] actualTargetType = {null};
117+
118+
factory.setConversionService(new ConversionService() {
119+
@Override
120+
public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
121+
return true;
122+
}
123+
124+
@Override
125+
public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
126+
return true;
127+
}
128+
129+
@Override
130+
public <T> T convert(Object source, Class<T> targetType) {
131+
return (T) "converted";
132+
}
133+
134+
@Override
135+
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
136+
actualSourceType[0] = sourceType;
137+
actualTargetType[0] = targetType;
138+
return "converted";
139+
}
140+
});
141+
Link link = factory.linkTo(methodOn(SampleController.class).sampleMethod(now)).withSelfRel();
142+
assertThat(actualSourceType[0].getType(), CoreMatchers.<Class<?>>equalTo(DateTime.class));
143+
assertThat(actualTargetType[0].getType(), CoreMatchers.<Class<?>>equalTo(String.class));
144+
assertThat(link.getHref(), endsWith("/sample/converted"));
145+
146+
// Clear things out to avoid breaking other test cases.
147+
factory.clearConversionService();
148+
}
149+
105150
/**
106151
* @see #96
107152
*/

0 commit comments

Comments
 (0)