#57 - Method reference link building now considers formatting annotations.

The String value handed into the UriTemplate created through mock method invocations now uses a DefaultFormattingConversionService to consider formatting annotations on the controller method parameters.
This commit is contained in:
Oliver Gierke
2013-04-30 16:12:00 +02:00
parent 6acf17e730
commit 64663a5648
4 changed files with 134 additions and 18 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012 the original author or authors.
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,44 +16,131 @@
package org.springframework.hateoas.mvc;
import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.Map;
import java.util.ArrayList;
import java.util.List;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.hateoas.core.AnnotationAttribute;
import org.springframework.hateoas.core.MethodParameters;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriTemplate;
/**
* Value object to allow accessing {@link MethodInvocation} parameters with the configured {@link AnnotationAttribute}.
*
* @author Oliver Gierke
*/
public class AnnotatedParametersParameterAccessor {
class AnnotatedParametersParameterAccessor {
private final AnnotationAttribute attribute;
/**
* Creates a new {@link AnnotatedParametersParameterAccessor} using the given {@link AnnotationAttribute}.
*
* @param attribute must not be {@literal null}.
*/
public AnnotatedParametersParameterAccessor(AnnotationAttribute attribute) {
Assert.notNull(attribute);
this.attribute = attribute;
}
public Map<String, Object> getBoundParameters(MethodInvocation invocation) {
/**
* Returns {@link BoundMethodParameter}s contained in the given {@link MethodInvocation}.
*
* @param invocation must not be {@literal null}.
* @return
*/
public List<BoundMethodParameter> getBoundParameters(MethodInvocation invocation) {
Assert.notNull(invocation, "MethodInvocation must not be null!");
MethodParameters parameters = new MethodParameters(invocation.getMethod());
Object[] arguments = invocation.getArguments();
Map<String, Object> result = new HashMap<String, Object>();
List<BoundMethodParameter> result = new ArrayList<BoundMethodParameter>();
for (MethodParameter parameter : parameters.getParametersWith(attribute.getAnnotationType())) {
Annotation annotation = parameter.getParameterAnnotation(attribute.getAnnotationType());
String annotationAttributeValue = attribute.getValueFrom(annotation);
String key = StringUtils.hasText(annotationAttributeValue) ? annotationAttributeValue : parameter
.getParameterName();
result.put(key, arguments[parameter.getParameterIndex()]);
result.add(new BoundMethodParameter(parameter, arguments[parameter.getParameterIndex()], attribute));
}
return result;
}
/**
* Represents a {@link MethodParameter} alongside the value it has been bound to.
*
* @author Oliver Gierke
*/
static class BoundMethodParameter {
private static final ConversionService CONVERSION_SERVICE = new DefaultFormattingConversionService();
private static final TypeDescriptor STRING_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
private final MethodParameter parameter;
private final Object value;
private final AnnotationAttribute attribute;
private final TypeDescriptor parameterTypeDecsriptor;
/**
* Creates a new {@link BoundMethodParameter}
*
* @param parameter
* @param value
* @param attribute
*/
public BoundMethodParameter(MethodParameter parameter, Object value, AnnotationAttribute attribute) {
Assert.notNull(parameter, "MethodParameter must not be null!");
this.parameter = parameter;
this.value = value;
this.attribute = attribute;
this.parameterTypeDecsriptor = TypeDescriptor.nested(parameter, 0);
}
/**
* Returns the name of the {@link UriTemplate} variable to be bound. The name will be derived from the configured
* {@link AnnotationAttribute} or the {@link MethodParameter} name as fallback.
*
* @return
*/
public String getVariableName() {
if (attribute == null) {
return parameter.getParameterName();
}
Annotation annotation = parameter.getParameterAnnotation(attribute.getAnnotationType());
String annotationAttributeValue = attribute.getValueFrom(annotation);
return StringUtils.hasText(annotationAttributeValue) ? annotationAttributeValue : parameter.getParameterName();
}
/**
* Returns the raw value bound to the {@link MethodParameter}.
*
* @return
*/
public Object getValue() {
return value;
}
/**
* Returns the bound value converted into a {@link String} based on default conversion service setup.
*
* @return
*/
public String asString() {
if (value == null) {
return null;
}
return (String) CONVERSION_SERVICE.convert(value, parameterTypeDecsriptor, STRING_DESCRIPTOR);
}
}
}

View File

@@ -24,7 +24,6 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.core.MethodParameter;
@@ -36,6 +35,7 @@ import org.springframework.hateoas.core.DummyInvocationUtils.LastInvocationAware
import org.springframework.hateoas.core.LinkBuilderSupport;
import org.springframework.hateoas.core.MappingDiscoverer;
import org.springframework.hateoas.core.MethodParameters;
import org.springframework.hateoas.mvc.AnnotatedParametersParameterAccessor.BoundMethodParameter;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -115,19 +115,21 @@ public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory<Co
}
}
values.putAll(PATH_VARIABLE_ACCESSOR.getBoundParameters(invocation));
for (BoundMethodParameter parameter : PATH_VARIABLE_ACCESSOR.getBoundParameters(invocation)) {
values.put(parameter.getVariableName(), parameter.asString());
}
for (Entry<String, Object> param : REQUEST_PARAM_ACCESSOR.getBoundParameters(invocation).entrySet()) {
for (BoundMethodParameter parameter : REQUEST_PARAM_ACCESSOR.getBoundParameters(invocation)) {
Object value = param.getValue();
String key = param.getKey();
Object value = parameter.getValue();
String key = parameter.getVariableName();
if (value instanceof Collection) {
for (Object element : (Collection<?>) value) {
builder.queryParam(key, element);
}
} else {
builder.queryParam(key, value);
builder.queryParam(key, parameter.asString());
}
}