/*
* Copyright 2012-2016 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 lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.lang.reflect.Field;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.ResourceProcessor;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.Resources;
import org.springframework.hateoas.mvc.HeaderLinksResponseEntity;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.util.ReflectionUtils;
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.
*
* @author Oliver Gierke
* @deprecated use the same type from Spring HATEOAS, will be removed in 2.7.
*/
@Deprecated
@RequiredArgsConstructor
public class ResourceProcessorHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
static final ResolvableType RESOURCE_TYPE = ResolvableType.forClass(Resource.class);
static final ResolvableType RESOURCES_TYPE = ResolvableType.forClass(Resources.class);
private static final ResolvableType HTTP_ENTITY_TYPE = ResolvableType.forClass(HttpEntity.class);
static final Field CONTENT_FIELD = ReflectionUtils.findField(Resources.class, "content");
static {
ReflectionUtils.makeAccessible(CONTENT_FIELD);
}
private final @NonNull HandlerMethodReturnValueHandler delegate;
private final @NonNull ResourceProcessorInvoker invoker;
private boolean rootLinksAsHeaders = false;
/**
* @param rootLinksAsHeaders the rootLinksAsHeaders to set
*/
public void setRootLinksAsHeaders(boolean rootLinksAsHeaders) {
this.rootLinksAsHeaders = rootLinksAsHeaders;
}
/*
* (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 (!ResourceSupport.class.isInstance(value)) {
delegate.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
return;
}
// We have a Resource or Resources - find suitable processors
ResolvableType targetType = ResolvableType.forMethodReturnType(returnType.getMethod());
// Unbox HttpEntity
if (HTTP_ENTITY_TYPE.isAssignableFrom(targetType)) {
targetType = targetType.getGeneric(0);
}
ResolvableType returnValueType = ResolvableType.forClass(value.getClass());
// Returned value is actually of a more specific type, use this type information
if (!getRawType(targetType).equals(getRawType(returnValueType))) {
targetType = returnValueType;
}
ResourceSupport result = invoker.invokeProcessorsFor((ResourceSupport) value, targetType);
delegate.handleReturnValue(rewrapResult(result, returnValue), returnType, mavContainer, webRequest);
}
/**
* 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
*/
Object rewrapResult(ResourceSupport newBody, Object originalValue) {
if (!(originalValue instanceof HttpEntity)) {
return newBody;
}
HttpEntity<ResourceSupport> entity = null;
if (originalValue instanceof ResponseEntity) {
ResponseEntity<?> source = (ResponseEntity<?>) originalValue;
entity = new ResponseEntity<ResourceSupport>(newBody, source.getHeaders(), source.getStatusCode());
} else {
HttpEntity<?> source = (HttpEntity<?>) originalValue;
entity = new HttpEntity<ResourceSupport>(newBody, source.getHeaders());
}
return addLinksToHeaderWrapper(entity);
}
private HttpEntity<?> addLinksToHeaderWrapper(HttpEntity<ResourceSupport> entity) {
return rootLinksAsHeaders ? HeaderLinksResponseEntity.wrap(entity) : entity;
}
private static Class<?> getRawType(ResolvableType type) {
Class<?> rawType = type.getRawClass();
return rawType == null ? Object.class : rawType;
}
}