Skip to content

Commit

Permalink
#118 - Allow injecting custom ConversionService
Browse files Browse the repository at this point in the history
Existing ConversionService is fixed as a static inside BoundMethodParameter. This introduces ability to inject an alternative ConversionService.

Related issues: #352, #149
  • Loading branch information
gregturn committed Aug 28, 2017
1 parent 05f687e commit a3aea75
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 11 deletions.
Expand Up @@ -56,7 +56,7 @@ class AnnotatedParametersParameterAccessor {
* @param invocation must not be {@literal null}.
* @return
*/
public List<BoundMethodParameter> getBoundParameters(MethodInvocation invocation) {
public List<BoundMethodParameter> getBoundParameters(MethodInvocation invocation, ConversionService conversionService) {

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

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

if (verifiedValue != null) {
result.add(createParameter(parameter, verifiedValue, attribute));
result.add(createParameter(parameter, verifiedValue, attribute, conversionService));
}
}

Expand All @@ -87,8 +87,8 @@ public List<BoundMethodParameter> 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);
}

/**
Expand Down Expand Up @@ -138,21 +138,25 @@ static class BoundMethodParameter {
private final AnnotationAttribute attribute;
private final TypeDescriptor parameterTypeDescriptor;

private ConversionService overrideConversionService = null;

/**
* Creates a new {@link BoundMethodParameter}
*
* @param parameter
* @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!");

this.parameter = parameter;
this.value = value;
this.attribute = attribute;
this.parameterTypeDescriptor = TypeDescriptor.nested(parameter, 0);
this.overrideConversionService = overrideConversionService;
}

/**
Expand Down Expand Up @@ -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);
}

/**
Expand All @@ -200,5 +204,9 @@ public String asString() {
public boolean isRequired() {
return true;
}

private ConversionService getConversionService() {
return (this.overrideConversionService != null) ? this.overrideConversionService : CONVERSION_SERVICE;
}
}
}
Expand Up @@ -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;
Expand Down Expand Up @@ -72,8 +73,10 @@ public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory<Co
new AnnotationAttribute(PathVariable.class));
private static final AnnotatedParametersParameterAccessor REQUEST_PARAM_ACCESSOR = new RequestParamParameterAccessor();

private List<UriComponentsContributor> uriComponentsContributors = new ArrayList<UriComponentsContributor>();
private static ConversionService overrideDefaultConversionService = null;

private List<UriComponentsContributor> uriComponentsContributors = new ArrayList<UriComponentsContributor>();

/**
* Configures the {@link UriComponentsContributor} to be used when building {@link Link} instances from method
* invocations.
Expand Down Expand Up @@ -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<String> optionalEmptyParameters = new ArrayList<String>();

for (BoundMethodParameter parameter : REQUEST_PARAM_ACCESSOR.getBoundParameters(invocation)) {
for (BoundMethodParameter parameter : REQUEST_PARAM_ACCESSOR.getBoundParameters(invocation, overrideDefaultConversionService)) {

bindRequestParameters(builder, parameter);

Expand Down Expand Up @@ -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.
Expand All @@ -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)
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -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> T convert(Object source, Class<T> 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.<Class<?>>equalTo(DateTime.class));
assertThat(actualTargetType[0].getType(), CoreMatchers.<Class<?>>equalTo(String.class));
assertThat(link.getHref(), endsWith("/sample/converted"));

// Clear things out to avoid breaking other test cases.
factory.clearConversionService();
}

/**
* @see #96
*/
Expand Down

0 comments on commit a3aea75

Please sign in to comment.