Introduced a MethodParameterConversionService to help convert query parameters that might be annotated with specific converters.

This commit is contained in:
Jon Brisbin
2012-08-20 08:23:58 -05:00
committed by Jon Brisbin
parent 349c3b9777
commit 2000d07b32
4 changed files with 84 additions and 36 deletions

View File

@@ -16,6 +16,6 @@ import org.springframework.core.convert.converter.Converter;
@Inherited
public @interface ConvertWith {
Class<? extends Converter<String[], ?>> value();
Class<? extends Converter<?, ?>> value();
}

View File

@@ -0,0 +1,58 @@
package org.springframework.data.rest.repository.invoke;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.rest.repository.annotation.ConvertWith;
import org.springframework.util.Assert;
/**
* A special conversion service that can convert {@link MethodParameter}s and their values to a target type, taking
* into account any specific conversion instructions annotated on the parameter with {@link ConvertWith}.
*
* @author Jon Brisbin
*/
public class MethodParameterConversionService {
private final ConversionService delegateConversionService;
public MethodParameterConversionService(ConversionService delegateConversionService) {
Assert.notNull(delegateConversionService, "Delegate ConversionService cannot be null.");
this.delegateConversionService = delegateConversionService;
}
public boolean canConvert(Class<?> sourceType, MethodParameter param) {
return canConvert(TypeDescriptor.valueOf(sourceType), param);
}
public boolean canConvert(TypeDescriptor sourceType, MethodParameter param) {
return (delegateConversionService.canConvert(sourceType, new TypeDescriptor(param))
|| param.hasParameterAnnotation(ConvertWith.class));
}
@SuppressWarnings({"unchecked"})
public <T> T convert(Object source, MethodParameter param) {
return convert(source, TypeDescriptor.forObject(source), param);
}
@SuppressWarnings({"unchecked"})
public <T> T convert(Object source, TypeDescriptor sourceType, MethodParameter param) {
TypeDescriptor targetType = new TypeDescriptor(param);
try {
if(param.hasParameterAnnotation(ConvertWith.class)) {
Converter<Object, T> converter = (Converter<Object, T>)param.getParameterAnnotation(ConvertWith.class)
.value()
.newInstance();
return converter.convert(source);
} else {
return (T)delegateConversionService.convert(source, sourceType, targetType);
}
} catch(Exception e) {
throw new ConversionFailedException(sourceType, targetType, source, e);
}
}
}

View File

@@ -4,7 +4,6 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
@@ -37,7 +36,6 @@ import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.domain.Page;
@@ -64,7 +62,6 @@ import org.springframework.data.rest.repository.RepositoryExporterSupport;
import org.springframework.data.rest.repository.RepositoryMetadata;
import org.springframework.data.rest.repository.RepositoryNotFoundException;
import org.springframework.data.rest.repository.UriToDomainObjectResolver;
import org.springframework.data.rest.repository.annotation.ConvertWith;
import org.springframework.data.rest.repository.annotation.RestResource;
import org.springframework.data.rest.repository.context.AfterDeleteEvent;
import org.springframework.data.rest.repository.context.AfterLinkDeleteEvent;
@@ -78,6 +75,7 @@ import org.springframework.data.rest.repository.context.BeforeRenderResourcesEve
import org.springframework.data.rest.repository.context.BeforeSaveEvent;
import org.springframework.data.rest.repository.context.RepositoryEvent;
import org.springframework.data.rest.repository.invoke.CrudMethod;
import org.springframework.data.rest.repository.invoke.MethodParameterConversionService;
import org.springframework.data.rest.repository.invoke.RepositoryQueryMethod;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.http.HttpHeaders;
@@ -137,29 +135,33 @@ public class RepositoryRestController
implements ApplicationContextAware,
InitializingBean {
public static final String LOCATION = "Location";
public static final String SELF = "self";
final static ThreadLocal<URI> BASE_URI = new ThreadLocal<URI>();
private static final Logger LOG = LoggerFactory.getLogger(RepositoryRestController.class);
public static final String LOCATION = "Location";
public static final String SELF = "self";
final static ThreadLocal<URI> BASE_URI = new ThreadLocal<URI>();
private static final Logger LOG = LoggerFactory.getLogger(RepositoryRestController.class);
private static final TypeDescriptor STRING_ARRAY_TYPE = TypeDescriptor.valueOf(String[].class);
/**
* We manage a list of possible {@link ConversionService}s to handle converting objects in the controller. This list
* is prioritized as well, so one can add a ConversionService at index 0 to make sure that ConversionService takes
* priority whenever an object of the type it can convert is needing conversion.
*/
private DelegatingConversionService conversionService = new DelegatingConversionService(
private DelegatingConversionService conversionService = new DelegatingConversionService(
new DefaultFormattingConversionService()
);
private MethodParameterConversionService methodParameterConversionService = new MethodParameterConversionService(
conversionService
);
/**
* Converters for reading and writing representations of objects.
*/
private List<HttpMessageConverter> httpMessageConverters = new ArrayList<HttpMessageConverter>();
private List<HttpMessageConverter> httpMessageConverters = new ArrayList<HttpMessageConverter>();
/**
* List of {@link MediaType}s we can support, given the list of {@link HttpMessageConverter}s currently configured.
*/
private SortedSet<String> availableMediaTypes = new TreeSet<String>();
private RepositoryRestConfiguration config = RepositoryRestConfiguration.DEFAULT;
private ObjectMapper objectMapper = new ObjectMapper();
private SortedSet<String> availableMediaTypes = new TreeSet<String>();
private RepositoryRestConfiguration config = RepositoryRestConfiguration.DEFAULT;
private ObjectMapper objectMapper = new ObjectMapper();
private RepositoryAwareMappingHttpMessageConverter mappingHttpMessageConverter;
private UriToDomainObjectResolver domainObjectResolver;
private ApplicationContext applicationContext;
@@ -576,8 +578,6 @@ public class RepositoryRestController
return notFoundResponse(request);
}
Annotation[][] annotations = queryMethod.method().getParameterAnnotations();
Class<?>[] paramTypes = queryMethod.paramTypes();
String[] paramNames = queryMethod.paramNames();
Object[] paramVals = new Object[paramTypes.length];
@@ -597,18 +597,7 @@ public class RepositoryRestController
continue;
}
TypeDescriptor stringTypeDesc = TypeDescriptor.valueOf(String[].class);
MethodParameter methodParam = new MethodParameter(queryMethod.method(), i);
TypeDescriptor targetTypeDesc = new TypeDescriptor(methodParam);
Class<? extends Converter<String[], ?>> converter = null;
for(Annotation anno : annotations[i]) {
if(ConvertWith.class.isAssignableFrom(anno.getClass())) {
converter = ((ConvertWith)anno).value();
break;
}
}
String firstVal = (queryVals.length > 0 ? queryVals[0] : null);
if(hasRepositoryMetadataFor(paramTypes[i])) {
RepositoryMetadata paramRepoMeta = repositoryMetadataFor(paramTypes[i]);
@@ -623,18 +612,12 @@ public class RepositoryRestController
}
paramVals[i] = o;
} else if(null != converter) {
try {
paramVals[i] = converter.newInstance().convert(queryVals);
} catch(InstantiationException e) {
throw new IllegalArgumentException(e);
}
} else if(String.class.isAssignableFrom(paramTypes[i])) {
// Param type is a String
paramVals[i] = firstVal;
} else if(conversionService.canConvert(stringTypeDesc, targetTypeDesc)) {
// There's a converter from String -> param type
paramVals[i] = conversionService.convert(queryVals, stringTypeDesc, targetTypeDesc);
} else if(methodParameterConversionService.canConvert(STRING_ARRAY_TYPE, methodParam)) {
// There's a converter from String[] -> param type
paramVals[i] = methodParameterConversionService.convert(queryVals, STRING_ARRAY_TYPE, methodParam);
} else {
// Param type isn't a "simple" type or no converter exists, try JSON
try {

View File

@@ -4,6 +4,7 @@ import java.util.ArrayList;
import java.util.List;
import org.springframework.core.convert.converter.Converter;
import org.springframework.util.StringUtils;
/**
* @author Jon Brisbin
@@ -12,7 +13,13 @@ public class StringToListOfLongsConverter implements Converter<String[], List<Lo
@Override public List<Long> convert(String[] source) {
List<Long> longs = new ArrayList<Long>();
for(String s : source) {
String strings;
if(source.length == 1) {
strings = source[0];
} else {
strings = StringUtils.arrayToCommaDelimitedString(source);
}
for(String s : StringUtils.commaDelimitedListToStringArray(strings)) {
longs.add(Long.parseLong(s));
}
return longs;