diff --git a/build.gradle b/build.gradle
index e80719423..365d3a782 100644
--- a/build.gradle
+++ b/build.gradle
@@ -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"
}
}
diff --git a/gradle.properties b/gradle.properties
index e35f31c16..cffdfa76b 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -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
diff --git a/spring-data-rest-webmvc/build.gradle b/spring-data-rest-webmvc/build.gradle
index 824707db9..69fc25827 100644
--- a/spring-data-rest-webmvc/build.gradle
+++ b/spring-data-rest-webmvc/build.gradle
@@ -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"
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 844a56467..0ead69b1e 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 RequestMappingHandlerAdapter {
+public class RepositoryRestHandlerAdapter extends ResourcePostProcessorInvokingHandlerAdapter {
@Autowired
private ResourcesReturnValueHandler resourcesReturnValueHandler;
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/ResourcePostProcessorInvokingHandlerAdapter.java
new file mode 100644
index 000000000..b14b41789
--- /dev/null
+++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourcePostProcessorInvokingHandlerAdapter.java
@@ -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}.
+ *
+ * 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>> resourceProcessors = new ArrayList>>();
+
+ @Autowired(required = false)
+ private List>> resourcesProcessors = new ArrayList>>();
+
+ /*
+ * (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 newHandlers = new ArrayList();
+ newHandlers.add(new ResourceProcessingHandlerMethodReturnValueHandler(oldHandlers, resourceProcessors,
+ resourcesProcessors));
+
+ // Configure the new handler to be used
+ this.setReturnValueHandlers(newHandlers);
+ }
+}
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/ResourceProcessingHandlerMethodReturnValueHandler.java
new file mode 100644
index 000000000..7e8805008
--- /dev/null
+++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceProcessingHandlerMethodReturnValueHandler.java
@@ -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> 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 final HandlerMethodReturnValueHandler delegate;
+ private final List 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> resourceProcessors, List> 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();
+
+ 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