Added unit test for ResourceProcessorHandlerMethodReturnValueHandler as well as fix from pull request #61
This commit is contained in:
@@ -1,133 +0,0 @@
|
||||
package org.springframework.data.rest.webmvc;
|
||||
|
||||
import static org.springframework.data.util.ClassTypeInformation.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.rest.webmvc.support.JsonpResponse;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.hateoas.Resource;
|
||||
import org.springframework.hateoas.ResourceProcessor;
|
||||
import org.springframework.hateoas.Resources;
|
||||
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 Jon Brisbin
|
||||
*/
|
||||
public class PersistentEntityResourceProcessorReturnValueHandler implements HandlerMethodReturnValueHandler {
|
||||
|
||||
private final HandlerMethodReturnValueHandler delegate;
|
||||
private final List<Wrapper> processors = new ArrayList<Wrapper>();
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public PersistentEntityResourceProcessorReturnValueHandler(HandlerMethodReturnValueHandler delegate,
|
||||
List<ResourceProcessor<?>> processors) {
|
||||
this.delegate = delegate;
|
||||
for(ResourceProcessor<?> rp : processors) {
|
||||
TypeInformation<?> componentType = from(rp.getClass())
|
||||
.getSuperTypeInformation(ResourceProcessor.class)
|
||||
.getComponentType();
|
||||
if(Resources.class.isAssignableFrom(componentType.getType())) {
|
||||
this.processors.add(new ResourcesProcessorWrapper(componentType.getComponentType().getType(),
|
||||
(ResourceProcessor<Resources<?>>)rp));
|
||||
} else if(Resource.class.isAssignableFrom(componentType.getType())) {
|
||||
this.processors.add(new ResourceProcessorWrapper(componentType.getComponentType().getType(),
|
||||
(ResourceProcessor<Resource<?>>)rp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override public boolean supportsReturnType(MethodParameter returnType) {
|
||||
Class<?> controller = returnType.getMethod().getDeclaringClass();
|
||||
return RepositoryController.class.isAssignableFrom(controller)
|
||||
|| RepositoryEntityController.class.isAssignableFrom(controller)
|
||||
|| RepositoryPropertyReferenceController.class.isAssignableFrom(controller)
|
||||
|| RepositorySearchController.class.isAssignableFrom(controller);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@Override
|
||||
public void handleReturnValue(Object returnValue,
|
||||
MethodParameter methodParam,
|
||||
ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest nativeRequest) throws Exception {
|
||||
Class<?> returnValueType = returnValue.getClass();
|
||||
Class<?> entityType = null;
|
||||
|
||||
if(JsonpResponse.class.isAssignableFrom(returnValueType)) {
|
||||
entityType = ((JsonpResponse)returnValue).getResponseEntity().getBody().getClass();
|
||||
} else if(ResponseEntity.class.isAssignableFrom(returnValueType)) {
|
||||
entityType = ((ResponseEntity)returnValue).getBody().getClass();
|
||||
} else if(Resources.class.isAssignableFrom(returnValueType)) {
|
||||
Collection c = ((Resources)returnValue).getContent();
|
||||
Object o;
|
||||
if(null != c && !c.isEmpty() && null != (o = c.iterator().next())) {
|
||||
entityType = o.getClass();
|
||||
} else {
|
||||
if(delegate.supportsReturnType(methodParam)) {
|
||||
delegate.handleReturnValue(returnValue,
|
||||
methodParam,
|
||||
mavContainer,
|
||||
nativeRequest);
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else if(Resource.class.isAssignableFrom(returnValueType)) {
|
||||
entityType = ((Resource)returnValue).getContent().getClass();
|
||||
}
|
||||
|
||||
for(Wrapper w : processors) {
|
||||
if(w.type.isAssignableFrom(entityType)) {
|
||||
if(ResourcesProcessorWrapper.class.isAssignableFrom(w.getClass())
|
||||
&& Resources.class.isAssignableFrom(returnValueType)) {
|
||||
((ResourcesProcessorWrapper)w).processor.process((Resources<?>)returnValue);
|
||||
} else if(ResourceProcessorWrapper.class.isAssignableFrom(w.getClass())
|
||||
&& Resource.class.isAssignableFrom(returnValueType)) {
|
||||
((ResourceProcessorWrapper)w).processor.process((Resource<?>)returnValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(delegate.supportsReturnType(methodParam)) {
|
||||
delegate.handleReturnValue(returnValue,
|
||||
methodParam,
|
||||
mavContainer,
|
||||
nativeRequest);
|
||||
}
|
||||
}
|
||||
|
||||
static class Wrapper {
|
||||
Class<?> type;
|
||||
TypeInformation<?> typeInfo;
|
||||
|
||||
Wrapper(Class<?> type) {
|
||||
this.type = type;
|
||||
this.typeInfo = from(type);
|
||||
}
|
||||
}
|
||||
|
||||
static class ResourcesProcessorWrapper extends Wrapper {
|
||||
ResourceProcessor<Resources<?>> processor;
|
||||
|
||||
ResourcesProcessorWrapper(Class<?> type, ResourceProcessor<Resources<?>> processor) {
|
||||
super(type);
|
||||
this.processor = processor;
|
||||
}
|
||||
}
|
||||
|
||||
static class ResourceProcessorWrapper extends Wrapper {
|
||||
ResourceProcessor<Resource<?>> processor;
|
||||
|
||||
ResourceProcessorWrapper(Class<?> type, ResourceProcessor<Resource<?>> processor) {
|
||||
super(type);
|
||||
this.processor = processor;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -347,7 +347,7 @@ public class ResourceProcessorHandlerMethodReturnValueHandler implements Handler
|
||||
*/
|
||||
private static boolean isValueTypeMatch(Resource<?> resource, TypeInformation<?> target) {
|
||||
|
||||
if (resource == null || !target.getType().equals(resource.getClass())) {
|
||||
if (resource == null || !target.getType().isAssignableFrom(resource.getClass())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +84,6 @@ public class ResourceProcessorInvokingHandlerAdapter extends RequestMappingHandl
|
||||
|
||||
// Set up ResourceProcessingHandlerMethodResolver to delegate to originally configured ones
|
||||
List<HandlerMethodReturnValueHandler> newHandlers = new ArrayList<HandlerMethodReturnValueHandler>();
|
||||
newHandlers.add(new PersistentEntityResourceProcessorReturnValueHandler(oldHandlers, resourcesProcessors));
|
||||
newHandlers.add(new ResourceProcessorHandlerMethodReturnValueHandler(oldHandlers, resourcesProcessors));
|
||||
|
||||
// Configure the new handler to be used
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package org.springframework.data.rest.webmvc;
|
||||
|
||||
import org.hamcrest.BaseMatcher;
|
||||
import org.hamcrest.Description;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
class HttpEntityMatcher<T> extends BaseMatcher<HttpEntity<T>> {
|
||||
|
||||
private final HttpEntity<T> expected;
|
||||
|
||||
public HttpEntityMatcher(HttpEntity<T> expected) {
|
||||
Assert.notNull(expected, "HttpEntity cannot be null");
|
||||
this.expected = expected;
|
||||
}
|
||||
|
||||
public static <T> HttpEntityMatcher<T> httpEntity(HttpEntity<T> httpEntity) {
|
||||
return new HttpEntityMatcher<T>(httpEntity);
|
||||
}
|
||||
|
||||
@Override public boolean matches(Object item) {
|
||||
if(!(item instanceof HttpEntity)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(item instanceof ResponseEntity && expected instanceof ResponseEntity) {
|
||||
ResponseEntity<?> left = (ResponseEntity<?>)expected;
|
||||
ResponseEntity<?> right = (ResponseEntity<?>)item;
|
||||
|
||||
if(!left.getStatusCode().equals(right.getStatusCode())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
HttpEntity<?> left = expected;
|
||||
HttpEntity<?> right = (HttpEntity<?>)item;
|
||||
|
||||
return left.getBody().equals(right.getBody())
|
||||
&& left.getHeaders().equals(right.getHeaders());
|
||||
}
|
||||
|
||||
@Override public void describeTo(Description description) {
|
||||
description.appendText(expected.toString());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
/*
|
||||
* 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.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.data.rest.webmvc.HttpEntityMatcher.*;
|
||||
import static org.springframework.util.ReflectionUtils.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hamcrest.Matcher;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.hateoas.Resource;
|
||||
import org.springframework.hateoas.ResourceProcessor;
|
||||
import org.springframework.hateoas.Resources;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link org.springframework.data.rest.webmvc.ResourceProcessorHandlerMethodReturnValueHandler}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class ResourceProcessorHandlerMethodReturnValueHandlerUnitTests extends AbstractJMockTests {
|
||||
|
||||
static final Resource<String> FOO = new Resource<String>("foo");
|
||||
static final Resources<Resource<String>> FOOS = new Resources<Resource<String>>(
|
||||
Collections.singletonList(FOO)
|
||||
);
|
||||
static final StringResource FOO_RES = new StringResource("foo");
|
||||
static final HttpEntity<Resource<String>> FOO_ENTITY = new HttpEntity<Resource<String>>(FOO);
|
||||
static final ResponseEntity<Resource<String>> FOO_RESP_ENTITY = new ResponseEntity<Resource<String>>(
|
||||
FOO,
|
||||
HttpStatus.OK
|
||||
);
|
||||
static final HttpEntity<StringResource> FOO_RES_ENTITY = new HttpEntity<StringResource>(FOO_RES);
|
||||
static final Resource<String> BAR = new Resource<String>("bar");
|
||||
static final Resources<Resource<String>> BARS = new Resources<Resource<String>>(
|
||||
Collections.singletonList(BAR)
|
||||
);
|
||||
static final StringResource BAR_RES = new StringResource("bar");
|
||||
static final HttpEntity<Resource<String>> BAR_ENTITY = new HttpEntity<Resource<String>>(BAR);
|
||||
static final ResponseEntity<Resource<String>> BAR_RESP_ENTITY = new ResponseEntity<Resource<String>>(
|
||||
BAR,
|
||||
HttpStatus.OK
|
||||
);
|
||||
static final HttpEntity<StringResource> BAR_RES_ENTITY = new HttpEntity<StringResource>(BAR_RES);
|
||||
static final Resource<Long> LONG_10 = new Resource<Long>(10L);
|
||||
static final Resource<Long> LONG_20 = new Resource<Long>(20L);
|
||||
static final LongResource LONG_10_RES = new LongResource(10L);
|
||||
static final LongResource LONG_20_RES = new LongResource(20L);
|
||||
static final HttpEntity<Resource<Long>> LONG_10_ENTITY = new HttpEntity<Resource<Long>>(LONG_10);
|
||||
static final HttpEntity<LongResource> LONG_10_RES_ENTITY = new HttpEntity<LongResource>(LONG_10_RES);
|
||||
static final HttpEntity<Resource<Long>> LONG_20_ENTITY = new HttpEntity<Resource<Long>>(LONG_20);
|
||||
static final HttpEntity<LongResource> LONG_20_RES_ENTITY = new HttpEntity<LongResource>(LONG_20_RES);
|
||||
static final Map<String, MethodParameter> METHOD_PARAMS = new HashMap<String, MethodParameter>();
|
||||
|
||||
static {
|
||||
doWithMethods(Controller.class, new MethodCallback() {
|
||||
@Override public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
METHOD_PARAMS.put(method.getName(), new MethodParameter(method, -1));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
HandlerMethodReturnValueHandler delegate;
|
||||
List<ResourceProcessor<?>> resourceProcessors;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
delegate = context.mock(HandlerMethodReturnValueHandler.class);
|
||||
resourceProcessors = new ArrayList<ResourceProcessor<?>>();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsIfDelegateSupports() {
|
||||
assertSupport(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotSupportIfDelegateDoesNot() {
|
||||
assertSupport(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postProcessesStringResource() throws Exception {
|
||||
resourceProcessors.add(StringResourceProcessor.INSTANCE);
|
||||
resourceProcessors.add(LongResourceProcessor.INSTANCE);
|
||||
|
||||
invokeReturnValueHandler("stringResourceEntity", is(BAR), FOO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postProcessesStringResourceInResponseEntity() throws Exception {
|
||||
resourceProcessors.add(StringResourceProcessor.INSTANCE);
|
||||
resourceProcessors.add(LongResourceProcessor.INSTANCE);
|
||||
|
||||
invokeReturnValueHandler("stringResourceEntity", httpEntity(BAR_RESP_ENTITY), FOO_RESP_ENTITY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postProcessesStringResourceInWildcardResponseEntity() throws Exception {
|
||||
resourceProcessors.add(StringResourceProcessor.INSTANCE);
|
||||
resourceProcessors.add(LongResourceProcessor.INSTANCE);
|
||||
|
||||
invokeReturnValueHandler("resourceEntity", httpEntity(BAR_RESP_ENTITY), FOO_RESP_ENTITY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postProcessesStringResources() throws Exception {
|
||||
resourceProcessors.add(StringResourcesProcessor.INSTANCE);
|
||||
resourceProcessors.add(LongResourceProcessor.INSTANCE);
|
||||
|
||||
invokeReturnValueHandler("resources", is(BARS), FOOS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postProcessesSpecializedStringResource() throws Exception {
|
||||
resourceProcessors.add(SpecializedStringResourceProcessor.INSTANCE);
|
||||
resourceProcessors.add(LongResourceProcessor.INSTANCE);
|
||||
|
||||
invokeReturnValueHandler("stringResourceEntity", httpEntity(BAR_RES_ENTITY), FOO_RES_ENTITY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postProcessesSpecializedStringUsingStringResourceProcessor() throws Exception {
|
||||
resourceProcessors.add(StringResourceProcessor.INSTANCE);
|
||||
resourceProcessors.add(LongResourceProcessor.INSTANCE);
|
||||
|
||||
invokeReturnValueHandler("specializedStringResourceEntity", httpEntity(BAR_ENTITY), FOO_RES_ENTITY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postProcessesLongResource() throws Exception {
|
||||
resourceProcessors.add(StringResourceProcessor.INSTANCE);
|
||||
resourceProcessors.add(LongResourceProcessor.INSTANCE);
|
||||
|
||||
invokeReturnValueHandler("longResource", is(LONG_20), LONG_10);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postProcessesSpecializedLongResource() throws Exception {
|
||||
resourceProcessors.add(StringResourceProcessor.INSTANCE);
|
||||
resourceProcessors.add(SpecializedLongResourceProcessor.INSTANCE);
|
||||
|
||||
invokeReturnValueHandler("specializedLongResourceEntity", httpEntity(LONG_20_RES_ENTITY), LONG_10_RES_ENTITY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotPostProcesseLongResourceWithSpecializedLongResourceProcessor() throws Exception {
|
||||
resourceProcessors.add(StringResourceProcessor.INSTANCE);
|
||||
resourceProcessors.add(SpecializedLongResourceProcessor.INSTANCE);
|
||||
|
||||
invokeReturnValueHandler("numberResourceEntity", httpEntity(LONG_10_ENTITY), LONG_10_ENTITY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postProcessesSpecializedLongResourceUsingLongResourceProcessor() throws Exception {
|
||||
resourceProcessors.add(StringResourceProcessor.INSTANCE);
|
||||
resourceProcessors.add(LongResourceProcessor.INSTANCE);
|
||||
|
||||
invokeReturnValueHandler("resourceEntity", is(LONG_20), LONG_10_RES);
|
||||
}
|
||||
|
||||
// Helpers ---------------------------------------------------------//
|
||||
private void invokeReturnValueHandler(String method,
|
||||
final Matcher<?> matcher,
|
||||
Object returnValue) throws Exception {
|
||||
final MethodParameter methodParam = METHOD_PARAMS.get(method);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(delegate).handleReturnValue(with(matcher),
|
||||
with(methodParam),
|
||||
with(aNull(ModelAndViewContainer.class)),
|
||||
with(aNull(NativeWebRequest.class)));
|
||||
}});
|
||||
|
||||
HandlerMethodReturnValueHandler handler = new ResourceProcessorHandlerMethodReturnValueHandler(
|
||||
delegate,
|
||||
resourceProcessors
|
||||
);
|
||||
handler.handleReturnValue(returnValue, methodParam, null, null);
|
||||
}
|
||||
|
||||
private void assertSupport(final boolean value) {
|
||||
final MethodParameter parameter = context.mock(MethodParameter.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(delegate).supportsReturnType(parameter);
|
||||
will(returnValue(value));
|
||||
}});
|
||||
|
||||
HandlerMethodReturnValueHandler handler = new ResourceProcessorHandlerMethodReturnValueHandler(
|
||||
delegate,
|
||||
resourceProcessors
|
||||
);
|
||||
|
||||
assertThat(handler.supportsReturnType(parameter), is(value));
|
||||
}
|
||||
|
||||
enum StringResourceProcessor implements ResourceProcessor<Resource<String>> {
|
||||
INSTANCE;
|
||||
|
||||
@Override public Resource<String> process(Resource<String> resource) {
|
||||
return BAR;
|
||||
}
|
||||
}
|
||||
|
||||
enum LongResourceProcessor implements ResourceProcessor<Resource<Long>> {
|
||||
INSTANCE;
|
||||
|
||||
@Override public Resource<Long> process(Resource<Long> resource) {
|
||||
return LONG_20;
|
||||
}
|
||||
}
|
||||
|
||||
enum StringResourcesProcessor implements ResourceProcessor<Resources<Resource<String>>> {
|
||||
INSTANCE;
|
||||
|
||||
@Override public Resources<Resource<String>> process(Resources<Resource<String>> resource) {
|
||||
return BARS;
|
||||
}
|
||||
}
|
||||
|
||||
enum SpecializedStringResourceProcessor implements ResourceProcessor<StringResource> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public StringResource process(StringResource resource) {
|
||||
return BAR_RES;
|
||||
}
|
||||
}
|
||||
|
||||
enum SpecializedLongResourceProcessor implements ResourceProcessor<LongResource> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public LongResource process(LongResource resource) {
|
||||
return LONG_20_RES;
|
||||
}
|
||||
}
|
||||
|
||||
static interface Controller {
|
||||
|
||||
Resources<Resource<String>> resources();
|
||||
|
||||
Resource<String> resource();
|
||||
|
||||
Resource<Long> longResource();
|
||||
|
||||
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();
|
||||
|
||||
HttpEntity<LongResource> specializedLongResourceEntity();
|
||||
|
||||
ResponseEntity<Resource<?>> resourceResponseEntity();
|
||||
|
||||
ResponseEntity<Resources<?>> resourcesResponseEntity();
|
||||
}
|
||||
|
||||
static class StringResource extends Resource<String> {
|
||||
public StringResource(String value) {
|
||||
super(value);
|
||||
}
|
||||
}
|
||||
|
||||
static class LongResource extends Resource<Long> {
|
||||
public LongResource(Long value) {
|
||||
super(value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
field.name.required = Field {0}.{1} is required.
|
||||
no.userid = {0}s must be assigned initial userids.
|
||||
Reference in New Issue
Block a user