From 4abd842a865c7f5f07687f72dfe5047e21cba9db Mon Sep 17 00:00:00 2001 From: Jon Brisbin Date: Tue, 7 Aug 2012 17:05:43 -0500 Subject: [PATCH] Added an annotation for doing parameter conversion @ConvertWith. You can now specify a Spring Core Converter class to use to convert the query parameter String[] values coming in on the query string to the type needed in the query method parameter. --- .../repository/annotation/ConvertWith.java | 21 ++++++++++ .../rest/webmvc/RepositoryRestController.java | 38 ++++++++++++++----- .../rest/webmvc/spec/QueryMethodsSpec.groovy | 2 +- .../rest/test/webmvc/PersonRepository.java | 11 +++++- .../webmvc/StringToListOfLongsConverter.java | 21 ++++++++++ 5 files changed, 82 insertions(+), 11 deletions(-) create mode 100644 spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/ConvertWith.java create mode 100644 spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/StringToListOfLongsConverter.java diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/ConvertWith.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/ConvertWith.java new file mode 100644 index 000000000..a9e17f88a --- /dev/null +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/ConvertWith.java @@ -0,0 +1,21 @@ +package org.springframework.data.rest.repository.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.core.convert.converter.Converter; + +/** + * @author Jon Brisbin + */ +@Target({ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface ConvertWith { + + Class> value(); + +} diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java index 8f326d06a..6e4c4778c 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java @@ -4,6 +4,7 @@ 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; @@ -32,6 +33,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.converter.Converter; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -57,6 +59,7 @@ import org.springframework.data.rest.repository.RepositoryExporter; 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.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; @@ -538,6 +541,8 @@ 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]; @@ -552,18 +557,24 @@ public class RepositoryRestController continue; } - String queryVal; - if(null == (queryVal = request.getServletRequest().getParameter(paramNames[i]))) { + String[] queryVals; + if(null == (queryVals = request.getServletRequest().getParameterValues(paramNames[i]))) { continue; } - if(String.class.isAssignableFrom(paramTypes[i])) { - // Param type is a String - paramVals[i] = queryVal; - } else if(hasRepositoryMetadataFor(paramTypes[i])) { + Class> 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]); // Complex parameter is a managed type - Serializable id = stringToSerializable(queryVal, + Serializable id = stringToSerializable(firstVal, (Class)paramRepoMeta.entityMetadata() .idAttribute() .type()); @@ -573,13 +584,22 @@ 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(String.class, paramTypes[i])) { // There's a converter from String -> param type - paramVals[i] = conversionService.convert(queryVal, paramTypes[i]); + paramVals[i] = conversionService.convert(firstVal, paramTypes[i]); } else { // Param type isn't a "simple" type or no converter exists, try JSON try { - paramVals[i] = objectMapper.readValue(queryVal, paramTypes[i]); + paramVals[i] = objectMapper.readValue(firstVal, paramTypes[i]); } catch(IOException e) { throw new IllegalArgumentException(e); } diff --git a/spring-data-rest-webmvc/src/test/groovy/org/springframework/data/rest/webmvc/spec/QueryMethodsSpec.groovy b/spring-data-rest-webmvc/src/test/groovy/org/springframework/data/rest/webmvc/spec/QueryMethodsSpec.groovy index d299625b4..bf056fd4a 100644 --- a/spring-data-rest-webmvc/src/test/groovy/org/springframework/data/rest/webmvc/spec/QueryMethodsSpec.groovy +++ b/spring-data-rest-webmvc/src/test/groovy/org/springframework/data/rest/webmvc/spec/QueryMethodsSpec.groovy @@ -29,7 +29,7 @@ class QueryMethodsSpec extends BaseSpec { then: response.statusCode == HttpStatus.OK - body.links.size() == 2 + body.links.size() == 3 } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/PersonRepository.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/PersonRepository.java index 94492dfa0..5b7b5c941 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/PersonRepository.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/PersonRepository.java @@ -4,12 +4,17 @@ import java.util.List; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; +import org.springframework.data.rest.repository.annotation.ConvertWith; import org.springframework.data.rest.repository.annotation.RestResource; /** - * @author Jon Brisbin + * Example {@link org.springframework.data.repository.CrudRepository} for dealing with a {@link Person}. Also uses the + * {@link RestResource} annotation to turn off the delete methods. + * + * @author Jon Brisbin */ @RestResource(path = "people", rel = "peeps") public interface PersonRepository extends PagingAndSortingRepository { @@ -25,4 +30,8 @@ public interface PersonRepository extends PagingAndSortingRepository findById(@Param("id") @ConvertWith(StringToListOfLongsConverter.class) List ids, Pageable pageable); + } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/StringToListOfLongsConverter.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/StringToListOfLongsConverter.java new file mode 100644 index 000000000..07fc30f22 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/StringToListOfLongsConverter.java @@ -0,0 +1,21 @@ +package org.springframework.data.rest.test.webmvc; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.core.convert.converter.Converter; + +/** + * @author Jon Brisbin + */ +public class StringToListOfLongsConverter implements Converter> { + + @Override public List convert(String[] source) { + List longs = new ArrayList(); + for(String s : source) { + longs.add(Long.parseLong(s)); + } + return longs; + } + +}