diff --git a/src/main/java/org/springframework/hateoas/mvc/AnnotatedParametersParameterAccessor.java b/src/main/java/org/springframework/hateoas/mvc/AnnotatedParametersParameterAccessor.java index 45ed083bb..68eae1d5d 100644 --- a/src/main/java/org/springframework/hateoas/mvc/AnnotatedParametersParameterAccessor.java +++ b/src/main/java/org/springframework/hateoas/mvc/AnnotatedParametersParameterAccessor.java @@ -56,7 +56,7 @@ class AnnotatedParametersParameterAccessor { * @param invocation must not be {@literal null}. * @return */ - public List getBoundParameters(MethodInvocation invocation) { + public List getBoundParameters(MethodInvocation invocation, ConversionService conversionService) { Assert.notNull(invocation, "MethodInvocation must not be null!"); @@ -70,7 +70,7 @@ public List getBoundParameters(MethodInvocation invocation Object verifiedValue = verifyParameterValue(parameter, value); if (verifiedValue != null) { - result.add(createParameter(parameter, verifiedValue, attribute)); + result.add(createParameter(parameter, verifiedValue, attribute, conversionService)); } } @@ -87,8 +87,8 @@ public List getBoundParameters(MethodInvocation invocation * @return */ protected BoundMethodParameter createParameter(MethodParameter parameter, Object value, - AnnotationAttribute attribute) { - return new BoundMethodParameter(parameter, value, attribute); + AnnotationAttribute attribute, ConversionService conversionService) { + return new BoundMethodParameter(parameter, value, attribute, conversionService); } /** @@ -138,6 +138,8 @@ static class BoundMethodParameter { private final AnnotationAttribute attribute; private final TypeDescriptor parameterTypeDescriptor; + private ConversionService overrideConversionService = null; + /** * Creates a new {@link BoundMethodParameter} * @@ -145,7 +147,8 @@ static class BoundMethodParameter { * @param value * @param attribute */ - public BoundMethodParameter(MethodParameter parameter, Object value, AnnotationAttribute attribute) { + public BoundMethodParameter(MethodParameter parameter, Object value, AnnotationAttribute attribute, + ConversionService overrideConversionService) { Assert.notNull(parameter, "MethodParameter must not be null!"); @@ -153,6 +156,7 @@ public BoundMethodParameter(MethodParameter parameter, Object value, AnnotationA this.value = value; this.attribute = attribute; this.parameterTypeDescriptor = TypeDescriptor.nested(parameter, 0); + this.overrideConversionService = overrideConversionService; } /** @@ -189,7 +193,7 @@ public Object getValue() { */ public String asString() { return value == null ? null - : (String) CONVERSION_SERVICE.convert(value, parameterTypeDescriptor, STRING_DESCRIPTOR); + : (String) getConversionService().convert(value, parameterTypeDescriptor, STRING_DESCRIPTOR); } /** @@ -200,5 +204,9 @@ public String asString() { public boolean isRequired() { return true; } + + private ConversionService getConversionService() { + return (this.overrideConversionService != null) ? this.overrideConversionService : CONVERSION_SERVICE; + } } } diff --git a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactory.java b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactory.java index 3a48f4d29..ad75e0ba2 100644 --- a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactory.java +++ b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactory.java @@ -31,6 +31,7 @@ import java.util.Map; import org.springframework.core.MethodParameter; +import org.springframework.core.convert.ConversionService; import org.springframework.hateoas.Link; import org.springframework.hateoas.MethodLinkBuilderFactory; import org.springframework.hateoas.TemplateVariable; @@ -72,8 +73,10 @@ public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory uriComponentsContributors = new ArrayList(); + private static ConversionService overrideDefaultConversionService = null; + private List uriComponentsContributors = new ArrayList(); + /** * Configures the {@link UriComponentsContributor} to be used when building {@link Link} instances from method * invocations. @@ -146,13 +149,13 @@ public ControllerLinkBuilder linkTo(Object invocationValue) { values.put(names.next(), encodePath(classMappingParameters.next())); } - for (BoundMethodParameter parameter : PATH_VARIABLE_ACCESSOR.getBoundParameters(invocation)) { + for (BoundMethodParameter parameter : PATH_VARIABLE_ACCESSOR.getBoundParameters(invocation, overrideDefaultConversionService)) { values.put(parameter.getVariableName(), encodePath(parameter.asString())); } List optionalEmptyParameters = new ArrayList(); - for (BoundMethodParameter parameter : REQUEST_PARAM_ACCESSOR.getBoundParameters(invocation)) { + for (BoundMethodParameter parameter : REQUEST_PARAM_ACCESSOR.getBoundParameters(invocation, overrideDefaultConversionService)) { bindRequestParameters(builder, parameter); @@ -268,6 +271,14 @@ private static void bindRequestParameters(UriComponentsBuilder builder, BoundMet } } + public static void setConversionService(ConversionService conversionService) { + overrideDefaultConversionService = conversionService; + } + + public static void clearConversionService() { + overrideDefaultConversionService = null; + } + /** * Custom extension of {@link AnnotatedParametersParameterAccessor} for {@link RequestParam} to allow {@literal null} * values handed in for optional request parameters. @@ -286,9 +297,9 @@ public RequestParamParameterAccessor() { */ @Override protected BoundMethodParameter createParameter(final MethodParameter parameter, Object value, - AnnotationAttribute attribute) { + AnnotationAttribute attribute, ConversionService conversionService) { - return new BoundMethodParameter(parameter, value, attribute) { + return new BoundMethodParameter(parameter, value, attribute, conversionService) { /* * (non-Javadoc) diff --git a/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactoryUnitTest.java b/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactoryUnitTest.java index 8989e885c..1740323b6 100644 --- a/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactoryUnitTest.java +++ b/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactoryUnitTest.java @@ -24,10 +24,13 @@ import java.util.LinkedHashMap; import java.util.Map; +import org.hamcrest.CoreMatchers; import org.joda.time.DateTime; import org.joda.time.format.ISODateTimeFormat; import org.junit.Test; import org.springframework.core.MethodParameter; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat.ISO; import org.springframework.hateoas.Link; @@ -102,6 +105,48 @@ public void usesDateTimeFormatForUriBinding() { assertThat(link.getHref(), endsWith("/sample/" + ISODateTimeFormat.date().print(now))); } + @Test + public void pluginCustomConversionService() { + + DateTime now = DateTime.now(); + + ControllerLinkBuilderFactory factory = new ControllerLinkBuilderFactory(); + + final TypeDescriptor[] actualSourceType = {null}; + final TypeDescriptor[] actualTargetType = {null}; + + factory.setConversionService(new ConversionService() { + @Override + public boolean canConvert(Class sourceType, Class targetType) { + return true; + } + + @Override + public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) { + return true; + } + + @Override + public T convert(Object source, Class targetType) { + return (T) "converted"; + } + + @Override + public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + actualSourceType[0] = sourceType; + actualTargetType[0] = targetType; + return "converted"; + } + }); + Link link = factory.linkTo(methodOn(SampleController.class).sampleMethod(now)).withSelfRel(); + assertThat(actualSourceType[0].getType(), CoreMatchers.>equalTo(DateTime.class)); + assertThat(actualTargetType[0].getType(), CoreMatchers.>equalTo(String.class)); + assertThat(link.getHref(), endsWith("/sample/converted")); + + // Clear things out to avoid breaking other test cases. + factory.clearConversionService(); + } + /** * @see #96 */