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.
This commit is contained in:
Oliver Gierke
2012-08-22 23:56:18 +02:00
parent ca907e146c
commit e3049fe9fb
5 changed files with 241 additions and 245 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
.DS_Store
.gradle/
bin/
build/
*.i*
.classpath

View File

@@ -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;

View File

@@ -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<Class<?>> RESOURCE_TYPES = new HashSet<Class<?>>(2, 1);
private static final Set<Class<?>> PARAMETER_TYPES = new HashSet<Class<?>>(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<PostProcessorWrapper> processorWrapper;
private final List<EnricherWrapper> 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<ResourceProcessor<?>> resourceProcessors, List<ResourcesProcessor<?>> resourcesProcessors) {
public ResourceEnricherHandlerMethodReturnValueHandler(HandlerMethodReturnValueHandler delegate,
List<ResourceEnricher<?>> 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<EnricherWrapper>();
this.processorWrapper = new ArrayList<PostProcessorWrapper>();
for (ResourceEnricher<?> enricher : enrichers) {
for (ResourceProcessor<? extends Resource<?>> 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<? extends Resources<?>> 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<Object>(newBody, source.getHeaders(), source.getStatusCode());
} else {
HttpEntity<?> source = (HttpEntity<?>) originalValue;
return new HttpEntity<Object>(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<? extends Resource<?>> 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<? extends Resource<?>> 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<Resource<?>>) processor).process((Resource<?>) object);
public void invokeEnricher(Object object) {
((ResourceEnricher<ResourceSupport>) 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<? extends Resources<?>> 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<? extends Resources<?>> 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<Resources<?>>) 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);
}
}

View File

@@ -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}.
* <p>
* 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<ResourceProcessor<? extends Resource<?>>> resourceProcessors = new ArrayList<ResourceProcessor<? extends Resource<?>>>();
@Autowired(required = false)
private List<ResourcesProcessor<? extends Resources<?>>> resourcesProcessors = new ArrayList<ResourcesProcessor<? extends Resources<?>>>();
private List<ResourceEnricher<?>> resourcesEnrichers = new ArrayList<ResourceEnricher<?>>();
/*
* (non-Javadoc)
@@ -60,8 +54,7 @@ public class ResourcePostProcessorInvokingHandlerAdapter extends RequestMappingH
// Set up ResourceProcessingHandlerMethodResolver to delegate to originally configured ones
List<HandlerMethodReturnValueHandler> newHandlers = new ArrayList<HandlerMethodReturnValueHandler>();
newHandlers.add(new ResourceProcessingHandlerMethodReturnValueHandler(oldHandlers, resourceProcessors,
resourcesProcessors));
newHandlers.add(new ResourceEnricherHandlerMethodReturnValueHandler(oldHandlers, resourcesEnrichers));
// Configure the new handler to be used
this.setReturnValueHandlers(newHandlers);

View File

@@ -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<ResourceProcessor<?>> resourceProcessors;
List<ResourcesProcessor<?>> resourcesProcessors;
List<ResourceEnricher<?>> resourceEnrichers;
Resource<String> source = new Resource<String>("foo");
Resource<String> result = StringResourceProcessor.RESULT;
Resource<String> source;
Resource<String> result;
@Before
public void setUp() {
resourceProcessors = new ArrayList<ResourceProcessor<?>>();
resourcesProcessors = new ArrayList<ResourcesProcessor<?>>();
resourceEnrichers = new ArrayList<ResourceEnricher<?>>();
resetResources();
}
private void resetResources() {
source = new Resource<String>("foo");
result = new Resource<String>("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<Resource<String>> input = new HttpEntity<Resource<String>>(source);
HttpEntity<Resource<String>> output = new HttpEntity<Resource<String>>(result);
assertProcessorInvokedForMethod("stringResourceEntity", input, output);
resetResources();
input = new HttpEntity<Resource<String>>(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<Resource<String>> input = new ResponseEntity<Resource<String>>(source, HttpStatus.OK);
ResponseEntity<Resource<String>> output = new ResponseEntity<Resource<String>>(result, HttpStatus.OK);
assertProcessorInvokedForMethod("stringResourceEntity", input, output);
resetResources();
input = new ResponseEntity<Resource<String>>(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<Resource<String>> sources = new Resources<Resource<String>>(Collections.singleton(source));
HttpEntity<Resources<Resource<String>>> input = new HttpEntity<Resources<Resource<String>>>(sources);
HttpEntity<Resources<Resource<String>>> output = new HttpEntity<Resources<Resource<String>>>(
StringResourcesProcessor.RESULT);
new Resources<Resource<String>>(Collections.singleton(result), StringResourcesEnricher.LINK));
assertProcessorInvokedForMethod("stringResourceEntity", input, output);
resetResources();
input = new HttpEntity<Resources<Resource<String>>>(new Resources<Resource<String>>(Collections.singleton(source)));
output = new HttpEntity<Resources<Resource<String>>>(
new Resources<Resource<String>>(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<Resource<String>> stringOutput = new HttpEntity<Resource<String>>(result);
HttpEntity<StringResource> specializedInput = new HttpEntity<StringResource>(new StringResource("foo"));
HttpEntity<StringResource> stringOutput = new HttpEntity<StringResource>(new StringResource("foo",
StringResourceEnricher.LINK));
assertProcessorInvokedForMethod("stringResourceEntity", specializedInput, stringOutput);
specializedInput = new HttpEntity<StringResource>(new StringResource("foo"));
assertProcessorInvokedForMethod("resourceEntity", specializedInput, stringOutput);
specializedInput = new HttpEntity<StringResource>(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<Resource<String>> input = new HttpEntity<Resource<String>>(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<StringResource> input = new HttpEntity<StringResource>(new StringResource("foo"));
HttpEntity<StringResource> output = new HttpEntity<StringResource>(SpecializedStringResourceProcessor.RESULT);
HttpEntity<StringResource> output = new HttpEntity<StringResource>(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<Resource<Long>> input = new HttpEntity<Resource<Long>>(new Resource<Long>(50L));
HttpEntity<LongResource> specializedInput = new HttpEntity<LongResource>(new LongResource(50L));
HttpEntity<Resource<Long>> output = new HttpEntity<Resource<Long>>(LongResourceProcessor.RESULT);
assertProcessorInvokedForMethod("resourceEntity", specializedInput, output);
assertProcessorInvokedForMethod("numberResourceEntity", input, output);
assertProcessorInvokedForMethod("resourceEntity", specializedInput, new HttpEntity<LongResource>(new LongResource(
50L, LongResourceEnricher.LINK)));
assertProcessorInvokedForMethod("numberResourceEntity", input, new HttpEntity<Resource<Long>>(new Resource<Long>(
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<Resources<?>> resourcesResponseEntity();
}
/**
* {@link ResourceProcessor} to process {@link String}s.
*
* @author Oliver Gierke
*/
static class StringResourceProcessor implements ResourceProcessor<Resource<String>> {
static final Resource<String> RESULT = new Resource<String>("bar");
@Override
public Resource<String> process(Resource<String> resource) {
return RESULT;
}
}
static class StringResourcesProcessor implements ResourcesProcessor<Resources<Resource<String>>> {
static final Resources<Resource<String>> RESULT = new Resources<Resource<String>>(
Collections.singleton(StringResourceProcessor.RESULT));
@Override
public Resources<?> process(Resources<Resource<String>> resources) {
return RESULT;
}
}
/**
* {@link ResourceProcessor} to process {@link Long} values.
*
* @author Oliver Gierke
*/
static class LongResourceProcessor implements ResourceProcessor<Resource<Long>> {
static final Resource<Long> RESULT = new Resource<Long>(10L);
@Override
public Resource<?> process(Resource<Long> resource) {
return RESULT;
}
}
static class StringResource extends Resource<String> {
public StringResource(String value) {
super(value);
public StringResource(String value, Link... links) {
super(value, links);
}
}
static class LongResource extends Resource<Long> {
public LongResource(Long value) {
super(value);
public LongResource(Long value, Link... links) {
super(value, links);
}
}
static class SpecializedStringResourceProcessor implements ResourceProcessor<StringResource> {
static class StringResourceEnricher implements ResourceEnricher<Resource<String>> {
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<String> resource) {
resource.add(LINK);
}
}
static class StringResourcesEnricher implements ResourceEnricher<Resources<Resource<String>>> {
static final Link LINK = new Link("string-resources");
@Override
public void enrich(Resources<Resource<String>> resource) {
resource.add(LINK);
}
}
static class LongResourceEnricher implements ResourceEnricher<Resource<Long>> {
static final Link LINK = new Link("long-resource");
@Override
public void enrich(Resource<Long> resource) {
resource.add(LINK);
}
}
static class SpecializedStringResourceEnricher implements ResourceEnricher<StringResource> {
static final Link LINK = new Link("specialized-string");
@Override
public void enrich(StringResource resource) {
resource.add(LINK);
}
}
}