All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod Maven / Gradle / Ivy

There is a newer version: 6.1.6
Show newest version
/*
 * Copyright 2002-2019 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
 *
 *      https://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.web.servlet.mvc.method.annotation;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.concurrent.Callable;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite;
import org.springframework.web.method.support.InvocableHandlerMethod;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.View;
import org.springframework.web.util.NestedServletException;

/**
 * Extends {@link InvocableHandlerMethod} with the ability to handle return
 * values through a registered {@link HandlerMethodReturnValueHandler} and
 * also supports setting the response status based on a method-level
 * {@code @ResponseStatus} annotation.
 *
 * 

A {@code null} return value (including void) may be interpreted as the * end of request processing in combination with a {@code @ResponseStatus} * annotation, a not-modified check condition * (see {@link ServletWebRequest#checkNotModified(long)}), or * a method argument that provides access to the response stream. * * @author Rossen Stoyanchev * @author Juergen Hoeller * @since 3.1 */ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod { private static final Method CALLABLE_METHOD = ClassUtils.getMethod(Callable.class, "call"); @Nullable private HandlerMethodReturnValueHandlerComposite returnValueHandlers; /** * Creates an instance from the given handler and method. */ public ServletInvocableHandlerMethod(Object handler, Method method) { super(handler, method); } /** * Create an instance from a {@code HandlerMethod}. */ public ServletInvocableHandlerMethod(HandlerMethod handlerMethod) { super(handlerMethod); } /** * Register {@link HandlerMethodReturnValueHandler} instances to use to * handle return values. */ public void setHandlerMethodReturnValueHandlers(HandlerMethodReturnValueHandlerComposite returnValueHandlers) { this.returnValueHandlers = returnValueHandlers; } /** * Invoke the method and handle the return value through one of the * configured {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers}. * @param webRequest the current request * @param mavContainer the ModelAndViewContainer for this request * @param providedArgs "given" arguments matched by type (not resolved) */ public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); setResponseStatus(webRequest); if (returnValue == null) { if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) { disableContentCachingIfNecessary(webRequest); mavContainer.setRequestHandled(true); return; } } else if (StringUtils.hasText(getResponseStatusReason())) { mavContainer.setRequestHandled(true); return; } mavContainer.setRequestHandled(false); Assert.state(this.returnValueHandlers != null, "No return value handlers"); try { this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(formatErrorForReturnValue(returnValue), ex); } throw ex; } } /** * Set the response status according to the {@link ResponseStatus} annotation. */ private void setResponseStatus(ServletWebRequest webRequest) throws IOException { HttpStatus status = getResponseStatus(); if (status == null) { return; } HttpServletResponse response = webRequest.getResponse(); if (response != null) { String reason = getResponseStatusReason(); if (StringUtils.hasText(reason)) { response.sendError(status.value(), reason); } else { response.setStatus(status.value()); } } // To be picked up by RedirectView webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, status); } /** * Does the given request qualify as "not modified"? * @see ServletWebRequest#checkNotModified(long) * @see ServletWebRequest#checkNotModified(String) */ private boolean isRequestNotModified(ServletWebRequest webRequest) { return webRequest.isNotModified(); } private void disableContentCachingIfNecessary(ServletWebRequest webRequest) { if (isRequestNotModified(webRequest)) { HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class); Assert.notNull(response, "Expected HttpServletResponse"); if (StringUtils.hasText(response.getHeader(HttpHeaders.ETAG))) { HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); Assert.notNull(request, "Expected HttpServletRequest"); } } } private String formatErrorForReturnValue(@Nullable Object returnValue) { return "Error handling return value=[" + returnValue + "]" + (returnValue != null ? ", type=" + returnValue.getClass().getName() : "") + " in " + toString(); } /** * Create a nested ServletInvocableHandlerMethod subclass that returns the * the given value (or raises an Exception if the value is one) rather than * actually invoking the controller method. This is useful when processing * async return values (e.g. Callable, DeferredResult, ListenableFuture). */ ServletInvocableHandlerMethod wrapConcurrentResult(Object result) { return new ConcurrentResultHandlerMethod(result, new ConcurrentResultMethodParameter(result)); } /** * A nested subclass of {@code ServletInvocableHandlerMethod} that uses a * simple {@link Callable} instead of the original controller as the handler in * order to return the fixed (concurrent) result value given to it. Effectively * "resumes" processing with the asynchronously produced return value. */ private class ConcurrentResultHandlerMethod extends ServletInvocableHandlerMethod { private final MethodParameter returnType; public ConcurrentResultHandlerMethod(final Object result, ConcurrentResultMethodParameter returnType) { super((Callable) () -> { if (result instanceof Exception) { throw (Exception) result; } else if (result instanceof Throwable) { throw new NestedServletException("Async processing failed", (Throwable) result); } return result; }, CALLABLE_METHOD); if (ServletInvocableHandlerMethod.this.returnValueHandlers != null) { setHandlerMethodReturnValueHandlers(ServletInvocableHandlerMethod.this.returnValueHandlers); } this.returnType = returnType; } /** * Bridge to actual controller type-level annotations. */ @Override public Class getBeanType() { return ServletInvocableHandlerMethod.this.getBeanType(); } /** * Bridge to actual return value or generic type within the declared * async return type, e.g. Foo instead of {@code DeferredResult}. */ @Override public MethodParameter getReturnValueType(@Nullable Object returnValue) { return this.returnType; } /** * Bridge to controller method-level annotations. */ @Override public A getMethodAnnotation(Class annotationType) { return ServletInvocableHandlerMethod.this.getMethodAnnotation(annotationType); } /** * Bridge to controller method-level annotations. */ @Override public boolean hasMethodAnnotation(Class annotationType) { return ServletInvocableHandlerMethod.this.hasMethodAnnotation(annotationType); } } /** * MethodParameter subclass based on the actual return value type or if * that's null falling back on the generic type within the declared async * return type, e.g. Foo instead of {@code DeferredResult}. */ private class ConcurrentResultMethodParameter extends HandlerMethodParameter { @Nullable private final Object returnValue; private final ResolvableType returnType; public ConcurrentResultMethodParameter(Object returnValue) { super(-1); this.returnValue = returnValue; this.returnType = (returnValue instanceof ReactiveTypeHandler.CollectedValuesList ? ((ReactiveTypeHandler.CollectedValuesList) returnValue).getReturnType() : ResolvableType.forType(super.getGenericParameterType()).getGeneric()); } public ConcurrentResultMethodParameter(ConcurrentResultMethodParameter original) { super(original); this.returnValue = original.returnValue; this.returnType = original.returnType; } @Override public Class getParameterType() { if (this.returnValue != null) { return this.returnValue.getClass(); } if (!ResolvableType.NONE.equals(this.returnType)) { return this.returnType.toClass(); } return super.getParameterType(); } @Override public Type getGenericParameterType() { return this.returnType.getType(); } @Override public boolean hasMethodAnnotation(Class annotationType) { // Ensure @ResponseBody-style handling for values collected from a reactive type // even if actual return type is ResponseEntity> return (super.hasMethodAnnotation(annotationType) || (annotationType == ResponseBody.class && this.returnValue instanceof ReactiveTypeHandler.CollectedValuesList)); } @Override public ConcurrentResultMethodParameter clone() { return new ConcurrentResultMethodParameter(this); } } }