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 55% 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..f9056f74c 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,53 @@ 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.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; /** * 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>>(); + private List> resourcesEnrichers = new ArrayList>(); - @Autowired(required = false) - private List>> resourcesProcessors = 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) @@ -60,8 +81,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); } } }