From e3049fe9fbe2df5c636e9b4bbd5dab7e3a74e9bc Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 22 Aug 2012 23:56:18 +0200 Subject: [PATCH 1/2] DATAREST-40 - Moved to ResourceEnricherInterface. Dropped usage of separate interface for ResourceProcessor and ResourcesProcessor and go with general ResourceEnricher interface. The custom HandlerAdapter now also invokes enrichers for Resources' content elements before invoking enrichers for the Resources instance itself. --- .gitignore | 1 + .../webmvc/RepositoryRestHandlerAdapter.java | 2 +- ...icherHandlerMethodReturnValueHandler.java} | 278 +++++++++--------- ...sourceEnricherInvokingHandlerAdapter.java} | 21 +- ...lerMethodReturnValueHandlerUnitTests.java} | 184 ++++++------ 5 files changed, 241 insertions(+), 245 deletions(-) rename spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/{ResourceProcessingHandlerMethodReturnValueHandler.java => ResourceEnricherHandlerMethodReturnValueHandler.java} (52%) rename spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/{ResourcePostProcessorInvokingHandlerAdapter.java => ResourceEnricherInvokingHandlerAdapter.java} (67%) rename spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/{ResourceProcessingHandlerMethodReturnValueHandlerUnitTests.java => ResourceEnricherHandlerMethodReturnValueHandlerUnitTests.java} (65%) diff --git a/.gitignore b/.gitignore index 4f2acb339..ff9926128 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .DS_Store .gradle/ +bin/ build/ *.i* .classpath diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerAdapter.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerAdapter.java index 0ead69b1e..eb665fa65 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerAdapter.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerAdapter.java @@ -16,7 +16,7 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl * * @author Jon Brisbin */ -public class RepositoryRestHandlerAdapter extends ResourcePostProcessorInvokingHandlerAdapter { +public class RepositoryRestHandlerAdapter extends ResourceEnricherInvokingHandlerAdapter { @Autowired private ResourcesReturnValueHandler resourcesReturnValueHandler; diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceProcessingHandlerMethodReturnValueHandler.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceEnricherHandlerMethodReturnValueHandler.java similarity index 52% rename from spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceProcessingHandlerMethodReturnValueHandler.java rename to spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceEnricherHandlerMethodReturnValueHandler.java index 7e8805008..8e53b77fe 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceProcessingHandlerMethodReturnValueHandler.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceEnricherHandlerMethodReturnValueHandler.java @@ -16,25 +16,22 @@ package org.springframework.data.rest.webmvc; import static org.springframework.data.util.ClassTypeInformation.*; + import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashSet; import java.util.List; -import java.util.Set; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.MethodParameter; import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; import org.springframework.hateoas.Resource; -import org.springframework.hateoas.ResourceProcessor; +import org.springframework.hateoas.ResourceEnricher; +import org.springframework.hateoas.ResourceSupport; import org.springframework.hateoas.Resources; -import org.springframework.hateoas.ResourcesProcessor; import org.springframework.http.HttpEntity; -import org.springframework.http.ResponseEntity; import org.springframework.util.Assert; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; @@ -42,58 +39,53 @@ import org.springframework.web.method.support.ModelAndViewContainer; /** * {@link HandlerMethodReturnValueHandler} to post-process the objects returned from controller methods using the - * configured {@link ResourceProcessor}s / {@link ResourcesProcessor}s. + * configured {@link ResourceEnricher}s. * * @author Oliver Gierke */ -public class ResourceProcessingHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler { +public class ResourceEnricherHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler { - private static final Set> RESOURCE_TYPES = new HashSet>(2, 1); - private static final Set> PARAMETER_TYPES = new HashSet>(3, 1); - - static { - - RESOURCE_TYPES.add(Resource.class); - RESOURCE_TYPES.add(Resources.class); - PARAMETER_TYPES.addAll(RESOURCE_TYPES); - PARAMETER_TYPES.add(HttpEntity.class); - } + private static final TypeInformation RESOURCE_TYPE = from(Resource.class); + private static final TypeInformation RESOURCES_TYPE = from(Resources.class); private final HandlerMethodReturnValueHandler delegate; - private final List processorWrapper; + private final List enrichers; /** - * Creates a new {@link ResourceProcessingHandlerMethodReturnValueHandler} using the given delegate to eventually + * Creates a new {@link ResourceEnricherHandlerMethodReturnValueHandler} using the given delegate to eventually * delegate calls to {@link #handleReturnValue(Object, MethodParameter, ModelAndViewContainer, NativeWebRequest)} to. - * Will consider the given {@link ResourceProcessor} / {@link ResourcesProcessor} to post-process the controller - * methods return value to before invoking the delegate. + * Will consider the given {@link ResourceEnricher} to post-process the controller methods return value to before + * invoking the delegate. * * @param delegate the {@link HandlerMethodReturnValueHandler} to evenually delegate calls to, must not be * {@literal null}. - * @param resourceProcessors the {@link ResourceProcessor}s to be considered, must not be {@literal null}. - * @param resourcesProcessors the {@link ResourcesProcessor}s to be considered, must not be {@literal null}. + * @param enrichers the {@link ResourceEnricher}s to be considered, must not be {@literal null}. */ - @Autowired - public ResourceProcessingHandlerMethodReturnValueHandler(HandlerMethodReturnValueHandler delegate, - List> resourceProcessors, List> resourcesProcessors) { + public ResourceEnricherHandlerMethodReturnValueHandler(HandlerMethodReturnValueHandler delegate, + List> enrichers) { Assert.notNull(delegate, "Delegate must not be null!"); - Assert.notNull(resourceProcessors, "ResourceProcessors must not be null!"); - Assert.notNull(resourcesProcessors, "ResourcesProcessors must not be null!"); + Assert.notNull(enrichers, "ResourceEnrichers must not be null!"); this.delegate = delegate; + this.enrichers = new ArrayList(); - this.processorWrapper = new ArrayList(); + for (ResourceEnricher enricher : enrichers) { - for (ResourceProcessor> processor : resourceProcessors) { - this.processorWrapper.add(new ResourceProcessorWrapper(processor)); + TypeInformation componentType = from(enricher.getClass()).getSuperTypeInformation(ResourceEnricher.class) + .getComponentType(); + Class rawType = componentType.getType(); + + if (Resource.class.isAssignableFrom(rawType)) { + this.enrichers.add(new ResourceEnricherWrapper(enricher)); + } else if (Resources.class.isAssignableFrom(rawType)) { + this.enrichers.add(new ResourcesEnricherWrapper(enricher)); + } else { + this.enrichers.add(new DefaultEnricherWrapper(enricher)); + } } - for (ResourcesProcessor> processor : resourcesProcessors) { - this.processorWrapper.add(new ResourcesProcessorWrapper(processor)); - } - - Collections.sort(this.processorWrapper, AnnotationAwareOrderComparator.INSTANCE); + Collections.sort(this.enrichers, AnnotationAwareOrderComparator.INSTANCE); } /* @@ -125,7 +117,7 @@ public class ResourceProcessingHandlerMethodReturnValueHandler implements Handle return; } - // We have a Resource or Resources - find suitable processors + // We have a Resource or Resources - find suitable enrichers TypeInformation targetType = ClassTypeInformation.fromReturnTypeOf(returnType.getMethod()); // Unbox HttpEntity @@ -139,15 +131,35 @@ public class ResourceProcessingHandlerMethodReturnValueHandler implements Handle targetType = returnValueTypeInformation; } - Object processedValue = value; + // For Resources implementations, process elements first + if (RESOURCES_TYPE.isAssignableFrom(targetType)) { - for (PostProcessorWrapper wrapper : this.processorWrapper) { - if (wrapper.supports(targetType, processedValue)) { - processedValue = wrapper.invokeProcessor(processedValue); + Resources resources = (Resources) value; + TypeInformation elementTargetType = targetType.getSuperTypeInformation(Resources.class).getComponentType(); + + for (Object element : resources) { + + TypeInformation elementTypeInformation = from(element.getClass()); + if (!elementTargetType.getType().equals(elementTypeInformation.getType())) { + elementTargetType = elementTypeInformation; + } + + for (EnricherWrapper wrapper : this.enrichers) { + if (wrapper.supports(elementTargetType, element)) { + wrapper.invokeEnricher(element); + } + } } } - delegate.handleReturnValue(rewrapResult(processedValue, returnValue), returnType, mavContainer, webRequest); + // Process actual value + for (EnricherWrapper wrapper : this.enrichers) { + if (wrapper.supports(targetType, value)) { + wrapper.invokeEnricher(value); + } + } + + delegate.handleReturnValue(returnValue, returnType, mavContainer, webRequest); } /** @@ -157,117 +169,76 @@ public class ResourceProcessingHandlerMethodReturnValueHandler implements Handle * @return */ private boolean isResourceType(Object value) { - - if (value == null) { - return false; - } - - for (Class type : RESOURCE_TYPES) { - if (type.isAssignableFrom(value.getClass())) { - return true; - } - } - - return false; + return value instanceof ResourceSupport; } /** - * Re-wraps the result of the post-processing work into an {@link HttpEntity} or {@link ResponseEntity} if the - * original value was one of those two types. Copies headers and status code from the original value but uses the new - * body. - * - * @param newBody the post-processed value. - * @param originalValue the original input value. - * @return - */ - static Object rewrapResult(Object newBody, Object originalValue) { - - if (!(originalValue instanceof HttpEntity)) { - return newBody; - } - - if (originalValue instanceof ResponseEntity) { - ResponseEntity source = (ResponseEntity) originalValue; - return new ResponseEntity(newBody, source.getHeaders(), source.getStatusCode()); - } else { - HttpEntity source = (HttpEntity) originalValue; - return new HttpEntity(newBody, source.getHeaders()); - } - } - - /** - * Interface to unify interaction with {@link ResourceProcessor} and {@link ResourcesProcessor}. The {@link Ordered} - * rank should be determined by the underlying processor. + * Interface to unify interaction with {@link ResourceEnricher}s. The {@link Ordered} rank should be determined by the + * underlying processor. * * @author Oliver Gierke */ - private interface PostProcessorWrapper extends Ordered { + private interface EnricherWrapper extends Ordered { /** * Returns whether the underlying processor supports the given {@link TypeInformation}. It might also aditionally - * inspect the object that would eventually be handed to the processor. + * inspect the object that would eventually be handed to the enricher. * * @param typeInformation the type of object to be post processed, will never be {@literal null}. - * @param value the object that would be passed into the processor eventually, can be {@literal null}. + * @param value the object that would be passed into the enricher eventually, can be {@literal null}. * @return */ boolean supports(TypeInformation typeInformation, Object value); /** - * Performs the actual invocation of the processor. Implementations can be sure + * Performs the actual invocation of the enricher. Implementations can be sure * {@link #supports(TypeInformation, Object)} has been called before and returned {@literal true}. * * @param object - * @return */ - Object invokeProcessor(Object object); + void invokeEnricher(Object object); } /** - * {@link PostProcessorWrapper} to invoke {@link ResourceProcessor} instances. + * Default implementation of {@link EnricherWrapper} to generically deal with {@link ResourceSupport} types. * * @author Oliver Gierke */ - private static class ResourceProcessorWrapper implements PostProcessorWrapper { + private static class DefaultEnricherWrapper implements EnricherWrapper { - private final ResourceProcessor> processor; + private final ResourceEnricher enricher; private final TypeInformation targetType; /** - * Creates a new {@link ResourceProcessorWrapper} for the given {@link ResourceProcessor}. + * Creates a ne {@link DefaultEnricherWrapper} with the given {@link ResourceEnricher}. * - * @param processor must not be {@literal null}. + * @param enricher must not be {@literal null}. */ - public ResourceProcessorWrapper(ResourceProcessor> processor) { + public DefaultEnricherWrapper(ResourceEnricher enricher) { - Assert.notNull(processor); + Assert.notNull(enricher); - this.processor = processor; - this.targetType = from(processor.getClass()).getSuperTypeInformation(ResourceProcessor.class).getComponentType(); + this.enricher = enricher; + this.targetType = from(enricher.getClass()).getSuperTypeInformation(ResourceEnricher.class).getComponentType(); } - /* + /* * (non-Javadoc) - * @see org.springframework.data.rest.webmvc.ResourceProcessingHandlerMethodReturnValueHandler.PostProcessorWrapper#supports(org.springframework.data.util.TypeInformation, java.lang.Object) + * @see org.springframework.data.rest.webmvc.ResourceEnricherHandlerMethodReturnValueHandler.PostProcessorWrapper#supports(org.springframework.data.util.TypeInformation, java.lang.Object) */ @Override public boolean supports(TypeInformation typeInformation, Object value) { - - if (value == null || !Resource.class.isAssignableFrom(value.getClass())) { - return false; - } - - return targetType.isAssignableFrom(typeInformation) || isValueTypeMatch((Resource) value, targetType); + return targetType.isAssignableFrom(typeInformation); } - /* + /* * (non-Javadoc) - * @see org.springframework.data.rest.webmvc.ResourceProcessingHandlerMethodReturnValueHandler.PostProcessorWrapper#invokeProcessor(java.lang.Object) + * @see org.springframework.data.rest.webmvc.ResourceEnricherHandlerMethodReturnValueHandler.PostProcessorWrapper#invokeProcessor(java.lang.Object) */ @Override @SuppressWarnings("unchecked") - public Object invokeProcessor(Object object) { - return ((ResourceProcessor>) processor).process((Resource) object); + public void invokeEnricher(Object object) { + ((ResourceEnricher) enricher).enrich((ResourceSupport) object); } /* @@ -276,7 +247,48 @@ public class ResourceProcessingHandlerMethodReturnValueHandler implements Handle */ @Override public int getOrder() { - return CustomOrderAwareComparator.INSTANCE.getOrder(processor); + return CustomOrderAwareComparator.INSTANCE.getOrder(enricher); + } + + /** + * Returns the target type the underlying {@link ResourceEnricher} wants to get invoked for. + * + * @return the targetType + */ + public TypeInformation getTargetType() { + return targetType; + } + } + + /** + * {@link EnricherWrapper} to deal with {@link ResourceEnricher}s for {@link Resource}s. Will fall back to peeking + * into the {@link Resource}'s content for type resolution. + * + * @author Oliver Gierke + */ + private static class ResourceEnricherWrapper extends DefaultEnricherWrapper { + + /** + * Creates a new {@link ResourceEnricherWrapper} for the given {@link ResourceEnricher}. + * + * @param processor must not be {@literal null}. + */ + public ResourceEnricherWrapper(ResourceEnricher processor) { + super(processor); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.webmvc.ResourceEnricherHandlerMethodReturnValueHandler.PostProcessorWrapper#supports(org.springframework.data.util.TypeInformation, java.lang.Object) + */ + @Override + public boolean supports(TypeInformation typeInformation, Object value) { + + if (!RESOURCE_TYPE.isAssignableFrom(typeInformation)) { + return false; + } + + return super.supports(typeInformation, value) || isValueTypeMatch((Resource) value, getTargetType()); } /** @@ -305,58 +317,34 @@ public class ResourceProcessingHandlerMethodReturnValueHandler implements Handle } /** - * {@link PostProcessorWrapper} for {@link ResourcesProcessor}s. + * {@link EnricherWrapper} for {@link ResourceEnricher}s targeting {@link Resources}. Will peek into the content of + * the {@link Resources} for type matching decisions if needed. * * @author Oliver Gierke */ - private static class ResourcesProcessorWrapper implements PostProcessorWrapper { - - private final ResourcesProcessor> processor; - private final TypeInformation targetType; + private static class ResourcesEnricherWrapper extends DefaultEnricherWrapper { /** - * Creates a new {@link ResourcesProcessorWrapper} for the given {@link ResourcesProcessor}. + * Creates a new {@link ResourcesEnricherWrapper} for the given {@link ResourceEnricher}. * - * @param processor must not be {@literal null}. + * @param enricher must not be {@literal null}. */ - public ResourcesProcessorWrapper(ResourcesProcessor> processor) { - - Assert.notNull(processor); - - this.processor = processor; - this.targetType = from(processor.getClass()).getSuperTypeInformation(ResourcesProcessor.class).getComponentType(); + public ResourcesEnricherWrapper(ResourceEnricher enricher) { + super(enricher); } - /* (non-Javadoc) + /* + * (non-Javadoc) * @see org.springframework.data.rest.webmvc.PostProcessorWrapper#supports(org.springframework.data.util.TypeInformation, java.lang.Object) */ @Override public boolean supports(TypeInformation typeInformation, Object value) { - if (value == null || !Resources.class.isAssignableFrom(value.getClass())) { + if (!RESOURCES_TYPE.isAssignableFrom(typeInformation)) { return false; } - return targetType.isAssignableFrom(typeInformation) || isValueTypeMatch((Resources) value, targetType); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.rest.webmvc.ResourceProcessingHandlerMethodReturnValueHandler.PostProcessorWrapper#invokeProcessor(java.lang.Object) - */ - @Override - @SuppressWarnings("unchecked") - public Object invokeProcessor(Object object) { - return ((ResourcesProcessor>) processor).process((Resources) object); - } - - /* - * (non-Javadoc) - * @see org.springframework.core.Ordered#getOrder() - */ - @Override - public int getOrder() { - return CustomOrderAwareComparator.INSTANCE.getOrder(processor); + return super.supports(typeInformation, value) || isValueTypeMatch((Resources) value, getTargetType()); } /** @@ -386,7 +374,7 @@ public class ResourceProcessingHandlerMethodReturnValueHandler implements Handle } TypeInformation resourceTypeInformation = target.getSuperTypeInformation(Resources.class).getComponentType(); - return ResourceProcessorWrapper.isValueTypeMatch((Resource) element, resourceTypeInformation); + return ResourceEnricherWrapper.isValueTypeMatch((Resource) element, resourceTypeInformation); } } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourcePostProcessorInvokingHandlerAdapter.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceEnricherInvokingHandlerAdapter.java similarity index 67% rename from spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourcePostProcessorInvokingHandlerAdapter.java rename to spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceEnricherInvokingHandlerAdapter.java index b14b41789..b9f703729 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourcePostProcessorInvokingHandlerAdapter.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceEnricherInvokingHandlerAdapter.java @@ -19,32 +19,26 @@ import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.hateoas.Resource; -import org.springframework.hateoas.ResourceProcessor; -import org.springframework.hateoas.Resources; -import org.springframework.hateoas.ResourcesProcessor; +import org.springframework.hateoas.ResourceEnricher; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; /** * Special {@link RequestMappingHandlerAdapter} that tweaks the {@link HandlerMethodReturnValueHandlerComposite} to be - * proxied by a {@link ResourceProcessingHandlerMethodReturnValueHandler} which will invoke the - * {@link ResourceProcessor}s/{@link ResourcesProcessor}s found in the application context and eventually delegate to - * the originally configured {@link HandlerMethodReturnValueHandler}. + * proxied by a {@link ResourceEnricherHandlerMethodReturnValueHandler} which will invoke the {@link ResourceEnricher}s + * found in the application context and eventually delegate to the originally configured + * {@link HandlerMethodReturnValueHandler}. *

* This is a separate component as it might make sense to deploy it in a standalone SpringMVC application to enable post * processing. It would actually make most sense in Spring HATEOAS project. * * @author Oliver Gierke */ -public class ResourcePostProcessorInvokingHandlerAdapter extends RequestMappingHandlerAdapter { +public class ResourceEnricherInvokingHandlerAdapter extends RequestMappingHandlerAdapter { @Autowired(required = false) - private List>> resourceProcessors = new ArrayList>>(); - - @Autowired(required = false) - private List>> resourcesProcessors = new ArrayList>>(); + private List> resourcesEnrichers = new ArrayList>(); /* * (non-Javadoc) @@ -60,8 +54,7 @@ public class ResourcePostProcessorInvokingHandlerAdapter extends RequestMappingH // Set up ResourceProcessingHandlerMethodResolver to delegate to originally configured ones List newHandlers = new ArrayList(); - newHandlers.add(new ResourceProcessingHandlerMethodReturnValueHandler(oldHandlers, resourceProcessors, - resourcesProcessors)); + newHandlers.add(new ResourceEnricherHandlerMethodReturnValueHandler(oldHandlers, resourcesEnrichers)); // Configure the new handler to be used this.setReturnValueHandlers(newHandlers); diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/ResourceProcessingHandlerMethodReturnValueHandlerUnitTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/ResourceEnricherHandlerMethodReturnValueHandlerUnitTests.java similarity index 65% rename from spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/ResourceProcessingHandlerMethodReturnValueHandlerUnitTests.java rename to spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/ResourceEnricherHandlerMethodReturnValueHandlerUnitTests.java index a00014f1a..d28046b4f 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/ResourceProcessingHandlerMethodReturnValueHandlerUnitTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/ResourceEnricherHandlerMethodReturnValueHandlerUnitTests.java @@ -17,6 +17,7 @@ package org.springframework.data.rest.test.webmvc; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; +import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; import java.lang.reflect.Method; @@ -32,11 +33,11 @@ import org.mockito.Mockito; import org.mockito.internal.matchers.Equals; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.core.MethodParameter; -import org.springframework.data.rest.webmvc.ResourceProcessingHandlerMethodReturnValueHandler; +import org.springframework.data.rest.webmvc.ResourceEnricherHandlerMethodReturnValueHandler; +import org.springframework.hateoas.Link; import org.springframework.hateoas.Resource; -import org.springframework.hateoas.ResourceProcessor; +import org.springframework.hateoas.ResourceEnricher; import org.springframework.hateoas.Resources; -import org.springframework.hateoas.ResourcesProcessor; import org.springframework.http.HttpEntity; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -45,10 +46,12 @@ import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.method.support.ModelAndViewContainer; /** + * Unit tests for {@link ResourceEnricherHandlerMethodReturnValueHandler}. + * * @author Oliver Gierke */ @RunWith(MockitoJUnitRunner.class) -public class ResourceProcessingHandlerMethodReturnValueHandlerUnitTests { +public class ResourceEnricherHandlerMethodReturnValueHandlerUnitTests { @Mock HandlerMethodReturnValueHandler delegate; @@ -56,16 +59,20 @@ public class ResourceProcessingHandlerMethodReturnValueHandlerUnitTests { @Mock MethodParameter parameter; - List> resourceProcessors; - List> resourcesProcessors; + List> resourceEnrichers; - Resource source = new Resource("foo"); - Resource result = StringResourceProcessor.RESULT; + Resource source; + Resource result; @Before public void setUp() { - resourceProcessors = new ArrayList>(); - resourcesProcessors = new ArrayList>(); + resourceEnrichers = new ArrayList>(); + resetResources(); + } + + private void resetResources() { + source = new Resource("foo"); + result = new Resource("foo", StringResourceEnricher.LINK); } @Test @@ -81,8 +88,8 @@ public class ResourceProcessingHandlerMethodReturnValueHandlerUnitTests { private void assertSupport(boolean value) { when(delegate.supportsReturnType(Mockito.any(MethodParameter.class))).thenReturn(value); - HandlerMethodReturnValueHandler handler = new ResourceProcessingHandlerMethodReturnValueHandler(delegate, - resourceProcessors, resourcesProcessors); + HandlerMethodReturnValueHandler handler = new ResourceEnricherHandlerMethodReturnValueHandler(delegate, + resourceEnrichers); assertThat(handler.supportsReturnType(parameter), is(value)); } @@ -90,65 +97,81 @@ public class ResourceProcessingHandlerMethodReturnValueHandlerUnitTests { @Test public void invokesStringPostProcessorForSimpleStringResource() throws Exception { - resourceProcessors.add(new StringResourceProcessor()); - resourceProcessors.add(new LongResourceProcessor()); + resourceEnrichers.add(new StringResourceEnricher()); + resourceEnrichers.add(new LongResourceEnricher()); HttpEntity> input = new HttpEntity>(source); HttpEntity> output = new HttpEntity>(result); assertProcessorInvokedForMethod("stringResourceEntity", input, output); + resetResources(); + input = new HttpEntity>(source); assertProcessorInvokedForMethod("resourceEntity", input, output); } @Test public void invokesStringPostProcessorForSimpleStringResourceInResponseEntity() throws Exception { - resourceProcessors.add(new StringResourceProcessor()); - resourceProcessors.add(new LongResourceProcessor()); + resourceEnrichers.add(new StringResourceEnricher()); + resourceEnrichers.add(new LongResourceEnricher()); ResponseEntity> input = new ResponseEntity>(source, HttpStatus.OK); ResponseEntity> output = new ResponseEntity>(result, HttpStatus.OK); assertProcessorInvokedForMethod("stringResourceEntity", input, output); + + resetResources(); + input = new ResponseEntity>(source, HttpStatus.OK); assertProcessorInvokedForMethod("resourceEntity", input, output); } @Test public void invokesStringPostProcessorForSimpleStringResources() throws Exception { - resourceProcessors.add(new StringResourceProcessor()); - resourceProcessors.add(new LongResourceProcessor()); - resourcesProcessors.add(new StringResourcesProcessor()); + resourceEnrichers.add(new StringResourceEnricher()); + resourceEnrichers.add(new LongResourceEnricher()); + resourceEnrichers.add(new StringResourcesEnricher()); Resources> sources = new Resources>(Collections.singleton(source)); + HttpEntity>> input = new HttpEntity>>(sources); HttpEntity>> output = new HttpEntity>>( - StringResourcesProcessor.RESULT); + new Resources>(Collections.singleton(result), StringResourcesEnricher.LINK)); assertProcessorInvokedForMethod("stringResourceEntity", input, output); + + resetResources(); + input = new HttpEntity>>(new Resources>(Collections.singleton(source))); + output = new HttpEntity>>( + new Resources>(Collections.singleton(result), StringResourcesEnricher.LINK)); assertProcessorInvokedForMethod("resourceEntity", input, output); } @Test public void invokesStringPostProcessorForSpecializedStringResource() throws Exception { - resourceProcessors.add(new StringResourceProcessor()); - resourceProcessors.add(new LongResourceProcessor()); + resourceEnrichers.add(new StringResourceEnricher()); + resourceEnrichers.add(new LongResourceEnricher()); - HttpEntity> stringOutput = new HttpEntity>(result); HttpEntity specializedInput = new HttpEntity(new StringResource("foo")); + HttpEntity stringOutput = new HttpEntity(new StringResource("foo", + StringResourceEnricher.LINK)); assertProcessorInvokedForMethod("stringResourceEntity", specializedInput, stringOutput); + + specializedInput = new HttpEntity(new StringResource("foo")); assertProcessorInvokedForMethod("resourceEntity", specializedInput, stringOutput); + + specializedInput = new HttpEntity(new StringResource("foo")); assertProcessorInvokedForMethod("specializedStringResourceEntity", specializedInput, stringOutput); } @Test public void doesNotInvokeSpecializedStringPostProcessorForSimpleStringResource() throws Exception { - resourceProcessors.add(new SpecializedStringResourceProcessor()); - resourceProcessors.add(new LongResourceProcessor()); + resourceEnrichers.add(new SpecializedStringResourceEnricher()); + resourceEnrichers.add(new LongResourceEnricher()); HttpEntity> input = new HttpEntity>(source); @@ -158,11 +181,12 @@ public class ResourceProcessingHandlerMethodReturnValueHandlerUnitTests { @Test public void invokesSpecializedStringPostProcessor() throws Exception { - resourceProcessors.add(new SpecializedStringResourceProcessor()); - resourceProcessors.add(new LongResourceProcessor()); + resourceEnrichers.add(new SpecializedStringResourceEnricher()); + resourceEnrichers.add(new LongResourceEnricher()); HttpEntity input = new HttpEntity(new StringResource("foo")); - HttpEntity output = new HttpEntity(SpecializedStringResourceProcessor.RESULT); + HttpEntity output = new HttpEntity(new StringResource("foo", + SpecializedStringResourceEnricher.LINK)); assertProcessorInvokedForMethod("specializedStringResourceEntity", input, output); } @@ -170,22 +194,23 @@ public class ResourceProcessingHandlerMethodReturnValueHandlerUnitTests { @Test public void invokesLongPostProcessorForLongResource() throws Exception { - resourceProcessors.add(new StringResourceProcessor()); - resourceProcessors.add(new LongResourceProcessor()); + resourceEnrichers.add(new StringResourceEnricher()); + resourceEnrichers.add(new LongResourceEnricher()); HttpEntity> input = new HttpEntity>(new Resource(50L)); HttpEntity specializedInput = new HttpEntity(new LongResource(50L)); - HttpEntity> output = new HttpEntity>(LongResourceProcessor.RESULT); - assertProcessorInvokedForMethod("resourceEntity", specializedInput, output); - assertProcessorInvokedForMethod("numberResourceEntity", input, output); + assertProcessorInvokedForMethod("resourceEntity", specializedInput, new HttpEntity(new LongResource( + 50L, LongResourceEnricher.LINK))); + assertProcessorInvokedForMethod("numberResourceEntity", input, new HttpEntity>(new Resource( + 50L, LongResourceEnricher.LINK))); } private void assertProcessorInvokedForMethod(String methodName, Object returnValue, Object processedValue) throws Exception { - HandlerMethodReturnValueHandler handler = new ResourceProcessingHandlerMethodReturnValueHandler(delegate, - resourceProcessors, resourcesProcessors); + HandlerMethodReturnValueHandler handler = new ResourceEnricherHandlerMethodReturnValueHandler(delegate, + resourceEnrichers); Method method = Controller.class.getMethod(methodName); MethodParameter returnType = new MethodParameter(method, -1); @@ -267,69 +292,58 @@ public class ResourceProcessingHandlerMethodReturnValueHandlerUnitTests { ResponseEntity> resourcesResponseEntity(); } - - /** - * {@link ResourceProcessor} to process {@link String}s. - * - * @author Oliver Gierke - */ - static class StringResourceProcessor implements ResourceProcessor> { - - static final Resource RESULT = new Resource("bar"); - - @Override - public Resource process(Resource resource) { - return RESULT; - } - } - - static class StringResourcesProcessor implements ResourcesProcessor>> { - - static final Resources> RESULT = new Resources>( - Collections.singleton(StringResourceProcessor.RESULT)); - - @Override - public Resources process(Resources> resources) { - return RESULT; - } - } - - /** - * {@link ResourceProcessor} to process {@link Long} values. - * - * @author Oliver Gierke - */ - static class LongResourceProcessor implements ResourceProcessor> { - - static final Resource RESULT = new Resource(10L); - - @Override - public Resource process(Resource resource) { - return RESULT; - } - } - + static class StringResource extends Resource { - public StringResource(String value) { - super(value); + public StringResource(String value, Link... links) { + super(value, links); } } static class LongResource extends Resource { - public LongResource(Long value) { - super(value); + public LongResource(Long value, Link... links) { + super(value, links); } } - static class SpecializedStringResourceProcessor implements ResourceProcessor { + static class StringResourceEnricher implements ResourceEnricher> { - static final StringResource RESULT = new StringResource("foobar"); + static final Link LINK = new Link("string-resource"); @Override - public Resource process(StringResource resource) { - return RESULT; + public void enrich(Resource resource) { + resource.add(LINK); + } + } + + static class StringResourcesEnricher implements ResourceEnricher>> { + + static final Link LINK = new Link("string-resources"); + + @Override + public void enrich(Resources> resource) { + resource.add(LINK); + } + } + + static class LongResourceEnricher implements ResourceEnricher> { + + static final Link LINK = new Link("long-resource"); + + @Override + public void enrich(Resource resource) { + resource.add(LINK); + } + } + + static class SpecializedStringResourceEnricher implements ResourceEnricher { + + static final Link LINK = new Link("specialized-string"); + + @Override + public void enrich(StringResource resource) { + resource.add(LINK); } } } From 7fdeefec528449a60e53c9514076f770b85882e8 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Thu, 23 Aug 2012 12:12:43 +0200 Subject: [PATCH 2/2] DATAREST-40 - Added copy constructor to ease setup of HandlerAdapter. ResourceEnricherInvokingHandlerAdapter now has a copy constructor to allow setting it up from a default RequestMappingHandlerAdapter. The reason we introduce this is that WebMvcConfigurationSupport unfortunately does not allow customizing the handler adapter class to be used as its instantiations is buried in a method with more setup code. With the copy constructor introduced one can simply take the configured instance WebMvcConfigurationSupport provides and pipe it into the custom ResourceEnricherInvokingHandlerAdapter. --- ...esourceEnricherInvokingHandlerAdapter.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceEnricherInvokingHandlerAdapter.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceEnricherInvokingHandlerAdapter.java index b9f703729..f9056f74c 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceEnricherInvokingHandlerAdapter.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceEnricherInvokingHandlerAdapter.java @@ -20,6 +20,10 @@ import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.hateoas.ResourceEnricher; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.util.Assert; +import org.springframework.web.bind.support.WebBindingInitializer; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; @@ -40,6 +44,29 @@ public class ResourceEnricherInvokingHandlerAdapter extends RequestMappingHandle @Autowired(required = false) private List> resourcesEnrichers = new ArrayList>(); + /** + * Empty constructor to setup a {@link ResourceEnricherInvokingHandlerAdapter}. + */ + public ResourceEnricherInvokingHandlerAdapter() { + + } + + /** + * Copy constructor to copy configuration of {@link HttpMessageConverter}s, {@link WebBindingInitializer}, custom + * {@link HandlerMethodArgumentResolver}s and custom {@link HandlerMethodReturnValueHandler}s. + * + * @param original must not be {@literal null}. + */ + public ResourceEnricherInvokingHandlerAdapter(RequestMappingHandlerAdapter original) { + + Assert.notNull(original); + + setMessageConverters(original.getMessageConverters()); + setWebBindingInitializer(original.getWebBindingInitializer()); + setCustomArgumentResolvers(original.getCustomArgumentResolvers()); + setCustomReturnValueHandlers(original.getCustomReturnValueHandlers()); + } + /* * (non-Javadoc) * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#afterPropertiesSet()