Merge branch 'master' of github.com:SpringSource/spring-data-rest
This commit is contained in:
@@ -15,7 +15,7 @@ allprojects {
|
||||
}
|
||||
|
||||
repositories {
|
||||
//maven { url "http://repo.springsource.org/libs-snapshot" }
|
||||
maven { url "http://repo.springsource.org/libs-snapshot" }
|
||||
maven { url "http://repo.springsource.org/libs-milestone" }
|
||||
maven { url "http://repo.springsource.org/libs-release" }
|
||||
}
|
||||
@@ -83,7 +83,7 @@ configure(subprojects) { subproject ->
|
||||
testCompile "org.hamcrest:hamcrest-library:1.2.1"
|
||||
testCompile "org.springframework:spring-test:$springVersion"
|
||||
testRuntime "org.springframework:spring-context-support:$springVersion"
|
||||
|
||||
testCompile "org.mockito:mockito-core:1.8.5"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ cglibVersion = 2.2.2
|
||||
groovyVersion = 1.8.6
|
||||
|
||||
# Supporting libraries
|
||||
sdCommonsVersion = 1.3.2.RELEASE
|
||||
sdJpaVersion = 1.2.0.M1
|
||||
sdCommonsVersion = 1.4.0.BUILD-SNAPSHOT
|
||||
sdJpaVersion = 1.2.0.BUILD-SNAPSHOT
|
||||
jacksonVersion = 1.9.7
|
||||
hibernateVersion = 4.1.4.Final
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@ dependencies {
|
||||
|
||||
// Repository Exporter support
|
||||
compile project(":spring-data-rest-repository")
|
||||
|
||||
compile "org.springframework.hateoas:spring-hateoas:0.2.0.BUILD-SNAPSHOT"
|
||||
compile "org.springframework.data:spring-data-commons-core:$sdCommonsVersion"
|
||||
|
||||
runtime "org.hibernate:hibernate-entitymanager:$hibernateVersion"
|
||||
runtime "org.hsqldb:hsqldb:2.2.8"
|
||||
|
||||
@@ -16,7 +16,7 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class RepositoryRestHandlerAdapter extends RequestMappingHandlerAdapter {
|
||||
public class RepositoryRestHandlerAdapter extends ResourcePostProcessorInvokingHandlerAdapter {
|
||||
|
||||
@Autowired
|
||||
private ResourcesReturnValueHandler resourcesReturnValueHandler;
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.webmvc;
|
||||
|
||||
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.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}.
|
||||
* <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 {
|
||||
|
||||
@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<?>>>();
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#afterPropertiesSet()
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
|
||||
super.afterPropertiesSet();
|
||||
|
||||
// Retrieve actual handlers to use as delegate
|
||||
HandlerMethodReturnValueHandlerComposite oldHandlers = getReturnValueHandlers();
|
||||
|
||||
// Set up ResourceProcessingHandlerMethodResolver to delegate to originally configured ones
|
||||
List<HandlerMethodReturnValueHandler> newHandlers = new ArrayList<HandlerMethodReturnValueHandler>();
|
||||
newHandlers.add(new ResourceProcessingHandlerMethodReturnValueHandler(oldHandlers, resourceProcessors,
|
||||
resourcesProcessors));
|
||||
|
||||
// Configure the new handler to be used
|
||||
this.setReturnValueHandlers(newHandlers);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
/*
|
||||
* Copyright 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
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.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;
|
||||
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.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class ResourceProcessingHandlerMethodReturnValueHandler 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 final HandlerMethodReturnValueHandler delegate;
|
||||
private final List<PostProcessorWrapper> processorWrapper;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ResourceProcessingHandlerMethodReturnValueHandler} 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.
|
||||
*
|
||||
* @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}.
|
||||
*/
|
||||
@Autowired
|
||||
public ResourceProcessingHandlerMethodReturnValueHandler(HandlerMethodReturnValueHandler delegate,
|
||||
List<ResourceProcessor<?>> resourceProcessors, List<ResourcesProcessor<?>> resourcesProcessors) {
|
||||
|
||||
Assert.notNull(delegate, "Delegate must not be null!");
|
||||
Assert.notNull(resourceProcessors, "ResourceProcessors must not be null!");
|
||||
Assert.notNull(resourcesProcessors, "ResourcesProcessors must not be null!");
|
||||
|
||||
this.delegate = delegate;
|
||||
|
||||
this.processorWrapper = new ArrayList<PostProcessorWrapper>();
|
||||
|
||||
for (ResourceProcessor<? extends Resource<?>> processor : resourceProcessors) {
|
||||
this.processorWrapper.add(new ResourceProcessorWrapper(processor));
|
||||
}
|
||||
|
||||
for (ResourcesProcessor<? extends Resources<?>> processor : resourcesProcessors) {
|
||||
this.processorWrapper.add(new ResourcesProcessorWrapper(processor));
|
||||
}
|
||||
|
||||
Collections.sort(this.processorWrapper, AnnotationAwareOrderComparator.INSTANCE);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.method.support.HandlerMethodReturnValueHandler#supportsReturnType(org.springframework.core.MethodParameter)
|
||||
*/
|
||||
@Override
|
||||
public boolean supportsReturnType(MethodParameter returnType) {
|
||||
return delegate.supportsReturnType(returnType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.method.support.HandlerMethodReturnValueHandler#handleReturnValue(java.lang.Object, org.springframework.core.MethodParameter, org.springframework.web.method.support.ModelAndViewContainer, org.springframework.web.context.request.NativeWebRequest)
|
||||
*/
|
||||
@Override
|
||||
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest) throws Exception {
|
||||
|
||||
Object value = returnValue;
|
||||
|
||||
if (returnValue instanceof HttpEntity) {
|
||||
value = ((HttpEntity<?>) returnValue).getBody();
|
||||
}
|
||||
|
||||
// No post-processable type found - proceed with delegate
|
||||
if (!isResourceType(value)) {
|
||||
delegate.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
|
||||
return;
|
||||
}
|
||||
|
||||
// We have a Resource or Resources - find suitable processors
|
||||
TypeInformation<?> targetType = ClassTypeInformation.fromReturnTypeOf(returnType.getMethod());
|
||||
|
||||
// Unbox HttpEntity
|
||||
if (HttpEntity.class.isAssignableFrom(targetType.getType())) {
|
||||
targetType = targetType.getTypeArguments().get(0);
|
||||
}
|
||||
|
||||
TypeInformation<? extends Object> returnValueTypeInformation = ClassTypeInformation.from(value.getClass());
|
||||
// Returned value is actually of a more specific type, use this type information
|
||||
if (!targetType.getType().equals(returnValueTypeInformation.getType())) {
|
||||
targetType = returnValueTypeInformation;
|
||||
}
|
||||
|
||||
Object processedValue = value;
|
||||
|
||||
for (PostProcessorWrapper wrapper : this.processorWrapper) {
|
||||
if (wrapper.supports(targetType, processedValue)) {
|
||||
processedValue = wrapper.invokeProcessor(processedValue);
|
||||
}
|
||||
}
|
||||
|
||||
delegate.handleReturnValue(rewrapResult(processedValue, returnValue), returnType, mavContainer, webRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given value is a resource (i.e. implements {@link Resource) or {@link Resources}).
|
||||
*
|
||||
* @param value
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
private interface PostProcessorWrapper 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.
|
||||
*
|
||||
* @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}.
|
||||
* @return
|
||||
*/
|
||||
boolean supports(TypeInformation<?> typeInformation, Object value);
|
||||
|
||||
/**
|
||||
* Performs the actual invocation of the processor. Implementations can be sure
|
||||
* {@link #supports(TypeInformation, Object)} has been called before and returned {@literal true}.
|
||||
*
|
||||
* @param object
|
||||
* @return
|
||||
*/
|
||||
Object invokeProcessor(Object object);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link PostProcessorWrapper} to invoke {@link ResourceProcessor} instances.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
private static class ResourceProcessorWrapper implements PostProcessorWrapper {
|
||||
|
||||
private final ResourceProcessor<? extends Resource<?>> processor;
|
||||
private final TypeInformation<?> targetType;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ResourceProcessorWrapper} for the given {@link ResourceProcessor}.
|
||||
*
|
||||
* @param processor must not be {@literal null}.
|
||||
*/
|
||||
public ResourceProcessorWrapper(ResourceProcessor<? extends Resource<?>> processor) {
|
||||
|
||||
Assert.notNull(processor);
|
||||
|
||||
this.processor = processor;
|
||||
this.targetType = from(processor.getClass()).getSuperTypeInformation(ResourceProcessor.class).getComponentType();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.webmvc.ResourceProcessingHandlerMethodReturnValueHandler.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);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.webmvc.ResourceProcessingHandlerMethodReturnValueHandler.PostProcessorWrapper#invokeProcessor(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object invokeProcessor(Object object) {
|
||||
return ((ResourceProcessor<Resource<?>>) processor).process((Resource<?>) object);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.Ordered#getOrder()
|
||||
*/
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return CustomOrderAwareComparator.INSTANCE.getOrder(processor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given {@link Resource} matches the given target {@link TypeInformation}. We inspect the
|
||||
* {@link Resource}'s value to determine the match.
|
||||
*
|
||||
* @param resource
|
||||
* @param target must not be {@literal null}.
|
||||
* @return whether the given {@link Resource} can be assigned to the given target {@link TypeInformation}
|
||||
*/
|
||||
private static boolean isValueTypeMatch(Resource<?> resource, TypeInformation<?> target) {
|
||||
|
||||
if (resource == null || !target.getType().equals(resource.getClass())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Object content = resource.getContent();
|
||||
|
||||
if (content == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return target.getSuperTypeInformation(Resource.class).getComponentType().getType()
|
||||
.isAssignableFrom(content.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link PostProcessorWrapper} for {@link ResourcesProcessor}s.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
private static class ResourcesProcessorWrapper implements PostProcessorWrapper {
|
||||
|
||||
private final ResourcesProcessor<? extends Resources<?>> processor;
|
||||
private final TypeInformation<?> targetType;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ResourcesProcessorWrapper} for the given {@link ResourcesProcessor}.
|
||||
*
|
||||
* @param processor 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();
|
||||
}
|
||||
|
||||
/* (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())) {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given {@link Resources} instance matches the given {@link TypeInformation}. We predict this
|
||||
* by inspecting the first element of the content of the {@link Resources}.
|
||||
*
|
||||
* @param resources the {@link Resources} to inspect.
|
||||
* @param target that target {@link TypeInformation}.
|
||||
* @return
|
||||
*/
|
||||
private static boolean isValueTypeMatch(Resources<?> resources, TypeInformation<?> target) {
|
||||
|
||||
if (resources == null || !Resources.class.equals(resources.getClass())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Collection<?> content = resources.getContent();
|
||||
|
||||
if (content.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Object element = content.iterator().next();
|
||||
|
||||
if (!(element instanceof Resource)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TypeInformation<?> resourceTypeInformation = target.getSuperTypeInformation(Resources.class).getComponentType();
|
||||
return ResourceProcessorWrapper.isValueTypeMatch((Resource<?>) element, resourceTypeInformation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper extension of {@link AnnotationAwareOrderComparator} to make {@link #getOrder()} public to allow it being
|
||||
* used in a standalone fashion.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
private static class CustomOrderAwareComparator extends AnnotationAwareOrderComparator {
|
||||
|
||||
public static CustomOrderAwareComparator INSTANCE = new CustomOrderAwareComparator();
|
||||
|
||||
@Override
|
||||
protected int getOrder(Object obj) {
|
||||
return super.getOrder(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,335 @@
|
||||
/*
|
||||
* Copyright 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.test.webmvc;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
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.hateoas.Resource;
|
||||
import org.springframework.hateoas.ResourceProcessor;
|
||||
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;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class ResourceProcessingHandlerMethodReturnValueHandlerUnitTests {
|
||||
|
||||
@Mock
|
||||
HandlerMethodReturnValueHandler delegate;
|
||||
|
||||
@Mock
|
||||
MethodParameter parameter;
|
||||
|
||||
List<ResourceProcessor<?>> resourceProcessors;
|
||||
List<ResourcesProcessor<?>> resourcesProcessors;
|
||||
|
||||
Resource<String> source = new Resource<String>("foo");
|
||||
Resource<String> result = StringResourceProcessor.RESULT;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
resourceProcessors = new ArrayList<ResourceProcessor<?>>();
|
||||
resourcesProcessors = new ArrayList<ResourcesProcessor<?>>();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsIfDelegateSupports() {
|
||||
assertSupport(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotSupportIfDelegateDoesNot() {
|
||||
assertSupport(false);
|
||||
}
|
||||
|
||||
private void assertSupport(boolean value) {
|
||||
|
||||
when(delegate.supportsReturnType(Mockito.any(MethodParameter.class))).thenReturn(value);
|
||||
HandlerMethodReturnValueHandler handler = new ResourceProcessingHandlerMethodReturnValueHandler(delegate,
|
||||
resourceProcessors, resourcesProcessors);
|
||||
|
||||
assertThat(handler.supportsReturnType(parameter), is(value));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokesStringPostProcessorForSimpleStringResource() throws Exception {
|
||||
|
||||
resourceProcessors.add(new StringResourceProcessor());
|
||||
resourceProcessors.add(new LongResourceProcessor());
|
||||
|
||||
HttpEntity<Resource<String>> input = new HttpEntity<Resource<String>>(source);
|
||||
HttpEntity<Resource<String>> output = new HttpEntity<Resource<String>>(result);
|
||||
|
||||
assertProcessorInvokedForMethod("stringResourceEntity", input, output);
|
||||
assertProcessorInvokedForMethod("resourceEntity", input, output);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokesStringPostProcessorForSimpleStringResourceInResponseEntity() throws Exception {
|
||||
|
||||
resourceProcessors.add(new StringResourceProcessor());
|
||||
resourceProcessors.add(new LongResourceProcessor());
|
||||
|
||||
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);
|
||||
assertProcessorInvokedForMethod("resourceEntity", input, output);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokesStringPostProcessorForSimpleStringResources() throws Exception {
|
||||
|
||||
resourceProcessors.add(new StringResourceProcessor());
|
||||
resourceProcessors.add(new LongResourceProcessor());
|
||||
resourcesProcessors.add(new StringResourcesProcessor());
|
||||
|
||||
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);
|
||||
|
||||
assertProcessorInvokedForMethod("stringResourceEntity", input, output);
|
||||
assertProcessorInvokedForMethod("resourceEntity", input, output);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokesStringPostProcessorForSpecializedStringResource() throws Exception {
|
||||
|
||||
resourceProcessors.add(new StringResourceProcessor());
|
||||
resourceProcessors.add(new LongResourceProcessor());
|
||||
|
||||
HttpEntity<Resource<String>> stringOutput = new HttpEntity<Resource<String>>(result);
|
||||
HttpEntity<StringResource> specializedInput = new HttpEntity<StringResource>(new StringResource("foo"));
|
||||
|
||||
assertProcessorInvokedForMethod("stringResourceEntity", specializedInput, stringOutput);
|
||||
assertProcessorInvokedForMethod("resourceEntity", specializedInput, stringOutput);
|
||||
assertProcessorInvokedForMethod("specializedStringResourceEntity", specializedInput, stringOutput);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotInvokeSpecializedStringPostProcessorForSimpleStringResource() throws Exception {
|
||||
|
||||
resourceProcessors.add(new SpecializedStringResourceProcessor());
|
||||
resourceProcessors.add(new LongResourceProcessor());
|
||||
|
||||
HttpEntity<Resource<String>> input = new HttpEntity<Resource<String>>(source);
|
||||
|
||||
assertProcessorInvokedForMethod("stringResourceEntity", input, input);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokesSpecializedStringPostProcessor() throws Exception {
|
||||
|
||||
resourceProcessors.add(new SpecializedStringResourceProcessor());
|
||||
resourceProcessors.add(new LongResourceProcessor());
|
||||
|
||||
HttpEntity<StringResource> input = new HttpEntity<StringResource>(new StringResource("foo"));
|
||||
HttpEntity<StringResource> output = new HttpEntity<StringResource>(SpecializedStringResourceProcessor.RESULT);
|
||||
|
||||
assertProcessorInvokedForMethod("specializedStringResourceEntity", input, output);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokesLongPostProcessorForLongResource() throws Exception {
|
||||
|
||||
resourceProcessors.add(new StringResourceProcessor());
|
||||
resourceProcessors.add(new LongResourceProcessor());
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private void assertProcessorInvokedForMethod(String methodName, Object returnValue, Object processedValue)
|
||||
throws Exception {
|
||||
|
||||
HandlerMethodReturnValueHandler handler = new ResourceProcessingHandlerMethodReturnValueHandler(delegate,
|
||||
resourceProcessors, resourcesProcessors);
|
||||
|
||||
Method method = Controller.class.getMethod(methodName);
|
||||
MethodParameter returnType = new MethodParameter(method, -1);
|
||||
|
||||
handler.handleReturnValue(returnValue, returnType, null, null);
|
||||
|
||||
verify(delegate, times(1)).handleReturnValue(argThat(new HttpEntityMatcher(processedValue)), eq(returnType),
|
||||
eq((ModelAndViewContainer) null), eq((NativeWebRequest) null));
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
static class HttpEntityMatcher extends Equals {
|
||||
|
||||
public HttpEntityMatcher(Object wanted) {
|
||||
super(wanted);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.mockito.internal.matchers.Equals#matches(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean matches(Object actual) {
|
||||
|
||||
Object wanted = getWanted();
|
||||
|
||||
if (actual instanceof ResponseEntity && wanted instanceof ResponseEntity) {
|
||||
|
||||
ResponseEntity<?> left = (ResponseEntity<?>) wanted;
|
||||
ResponseEntity<?> right = (ResponseEntity<?>) actual;
|
||||
|
||||
if (!left.getStatusCode().equals(right.getStatusCode())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (actual instanceof HttpEntity && wanted instanceof HttpEntity) {
|
||||
|
||||
HttpEntity<?> left = (HttpEntity<?>) wanted;
|
||||
HttpEntity<?> right = (HttpEntity<?>) actual;
|
||||
|
||||
if (!left.getBody().equals(right.getBody())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!left.getHeaders().equals(right.getHeaders())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.matches(actual);
|
||||
}
|
||||
}
|
||||
|
||||
interface Controller {
|
||||
|
||||
Resources<Resource<String>> resources();
|
||||
|
||||
Resource<String> resource();
|
||||
|
||||
StringResource specializedResource();
|
||||
|
||||
Object object();
|
||||
|
||||
HttpEntity<Resource<?>> resourceEntity();
|
||||
|
||||
HttpEntity<Resources<?>> resourcesEntity();
|
||||
|
||||
HttpEntity<Object> objectEntity();
|
||||
|
||||
HttpEntity<Resource<String>> stringResourceEntity();
|
||||
|
||||
HttpEntity<Resource<? extends Number>> numberResourceEntity();
|
||||
|
||||
HttpEntity<StringResource> specializedStringResourceEntity();
|
||||
|
||||
ResponseEntity<Resource<?>> resourceResponseEntity();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
static class LongResource extends Resource<Long> {
|
||||
|
||||
public LongResource(Long value) {
|
||||
super(value);
|
||||
}
|
||||
}
|
||||
|
||||
static class SpecializedStringResourceProcessor implements ResourceProcessor<StringResource> {
|
||||
|
||||
static final StringResource RESULT = new StringResource("foobar");
|
||||
|
||||
@Override
|
||||
public Resource<?> process(StringResource resource) {
|
||||
return RESULT;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user