001/* 002 * Copyright 2002-2017 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.servlet.mvc.method.annotation; 018 019import java.io.IOException; 020import java.lang.annotation.Annotation; 021import java.lang.reflect.Method; 022import java.lang.reflect.Type; 023import java.util.concurrent.Callable; 024 025import org.springframework.core.MethodParameter; 026import org.springframework.core.ResolvableType; 027import org.springframework.http.HttpStatus; 028import org.springframework.util.ClassUtils; 029import org.springframework.util.StringUtils; 030import org.springframework.web.bind.annotation.ResponseStatus; 031import org.springframework.web.context.request.ServletWebRequest; 032import org.springframework.web.method.HandlerMethod; 033import org.springframework.web.method.support.HandlerMethodReturnValueHandler; 034import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite; 035import org.springframework.web.method.support.InvocableHandlerMethod; 036import org.springframework.web.method.support.ModelAndViewContainer; 037import org.springframework.web.servlet.View; 038import org.springframework.web.util.NestedServletException; 039 040/** 041 * Extends {@link InvocableHandlerMethod} with the ability to handle return 042 * values through a registered {@link HandlerMethodReturnValueHandler} and 043 * also supports setting the response status based on a method-level 044 * {@code @ResponseStatus} annotation. 045 * 046 * <p>A {@code null} return value (including void) may be interpreted as the 047 * end of request processing in combination with a {@code @ResponseStatus} 048 * annotation, a not-modified check condition 049 * (see {@link ServletWebRequest#checkNotModified(long)}), or 050 * a method argument that provides access to the response stream. 051 * 052 * @author Rossen Stoyanchev 053 * @author Juergen Hoeller 054 * @since 3.1 055 */ 056public class ServletInvocableHandlerMethod extends InvocableHandlerMethod { 057 058 private static final Method CALLABLE_METHOD = ClassUtils.getMethod(Callable.class, "call"); 059 060 private HandlerMethodReturnValueHandlerComposite returnValueHandlers; 061 062 063 /** 064 * Creates an instance from the given handler and method. 065 */ 066 public ServletInvocableHandlerMethod(Object handler, Method method) { 067 super(handler, method); 068 } 069 070 /** 071 * Create an instance from a {@code HandlerMethod}. 072 */ 073 public ServletInvocableHandlerMethod(HandlerMethod handlerMethod) { 074 super(handlerMethod); 075 } 076 077 078 /** 079 * Register {@link HandlerMethodReturnValueHandler} instances to use to 080 * handle return values. 081 */ 082 public void setHandlerMethodReturnValueHandlers(HandlerMethodReturnValueHandlerComposite returnValueHandlers) { 083 this.returnValueHandlers = returnValueHandlers; 084 } 085 086 087 /** 088 * Invoke the method and handle the return value through one of the 089 * configured {@link HandlerMethodReturnValueHandler}s. 090 * @param webRequest the current request 091 * @param mavContainer the ModelAndViewContainer for this request 092 * @param providedArgs "given" arguments matched by type (not resolved) 093 */ 094 public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, 095 Object... providedArgs) throws Exception { 096 097 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); 098 setResponseStatus(webRequest); 099 100 if (returnValue == null) { 101 if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) { 102 mavContainer.setRequestHandled(true); 103 return; 104 } 105 } 106 else if (StringUtils.hasText(getResponseStatusReason())) { 107 mavContainer.setRequestHandled(true); 108 return; 109 } 110 111 mavContainer.setRequestHandled(false); 112 try { 113 this.returnValueHandlers.handleReturnValue( 114 returnValue, getReturnValueType(returnValue), mavContainer, webRequest); 115 } 116 catch (Exception ex) { 117 if (logger.isTraceEnabled()) { 118 logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex); 119 } 120 throw ex; 121 } 122 } 123 124 /** 125 * Set the response status according to the {@link ResponseStatus} annotation. 126 */ 127 private void setResponseStatus(ServletWebRequest webRequest) throws IOException { 128 HttpStatus status = getResponseStatus(); 129 if (status == null) { 130 return; 131 } 132 133 String reason = getResponseStatusReason(); 134 if (StringUtils.hasText(reason)) { 135 webRequest.getResponse().sendError(status.value(), reason); 136 } 137 else { 138 webRequest.getResponse().setStatus(status.value()); 139 } 140 141 // To be picked up by RedirectView 142 webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, status); 143 } 144 145 /** 146 * Does the given request qualify as "not modified"? 147 * @see ServletWebRequest#checkNotModified(long) 148 * @see ServletWebRequest#checkNotModified(String) 149 */ 150 private boolean isRequestNotModified(ServletWebRequest webRequest) { 151 return webRequest.isNotModified(); 152 } 153 154 private String getReturnValueHandlingErrorMessage(String message, Object returnValue) { 155 StringBuilder sb = new StringBuilder(message); 156 if (returnValue != null) { 157 sb.append(" [type=").append(returnValue.getClass().getName()).append("]"); 158 } 159 sb.append(" [value=").append(returnValue).append("]"); 160 return getDetailedErrorMessage(sb.toString()); 161 } 162 163 /** 164 * Create a nested ServletInvocableHandlerMethod subclass that returns the 165 * the given value (or raises an Exception if the value is one) rather than 166 * actually invoking the controller method. This is useful when processing 167 * async return values (e.g. Callable, DeferredResult, ListenableFuture). 168 */ 169 ServletInvocableHandlerMethod wrapConcurrentResult(Object result) { 170 return new ConcurrentResultHandlerMethod(result, new ConcurrentResultMethodParameter(result)); 171 } 172 173 174 /** 175 * A nested subclass of {@code ServletInvocableHandlerMethod} that uses a 176 * simple {@link Callable} instead of the original controller as the handler in 177 * order to return the fixed (concurrent) result value given to it. Effectively 178 * "resumes" processing with the asynchronously produced return value. 179 */ 180 private class ConcurrentResultHandlerMethod extends ServletInvocableHandlerMethod { 181 182 private final MethodParameter returnType; 183 184 public ConcurrentResultHandlerMethod(final Object result, ConcurrentResultMethodParameter returnType) { 185 super(new Callable<Object>() { 186 @Override 187 public Object call() throws Exception { 188 if (result instanceof Exception) { 189 throw (Exception) result; 190 } 191 else if (result instanceof Throwable) { 192 throw new NestedServletException("Async processing failed", (Throwable) result); 193 } 194 return result; 195 } 196 }, CALLABLE_METHOD); 197 198 setHandlerMethodReturnValueHandlers(ServletInvocableHandlerMethod.this.returnValueHandlers); 199 this.returnType = returnType; 200 } 201 202 /** 203 * Bridge to actual controller type-level annotations. 204 */ 205 @Override 206 public Class<?> getBeanType() { 207 return ServletInvocableHandlerMethod.this.getBeanType(); 208 } 209 210 /** 211 * Bridge to actual return value or generic type within the declared 212 * async return type, e.g. Foo instead of {@code DeferredResult<Foo>}. 213 */ 214 @Override 215 public MethodParameter getReturnValueType(Object returnValue) { 216 return this.returnType; 217 } 218 219 /** 220 * Bridge to controller method-level annotations. 221 */ 222 @Override 223 public <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) { 224 return ServletInvocableHandlerMethod.this.getMethodAnnotation(annotationType); 225 } 226 227 /** 228 * Bridge to controller method-level annotations. 229 */ 230 @Override 231 public <A extends Annotation> boolean hasMethodAnnotation(Class<A> annotationType) { 232 return ServletInvocableHandlerMethod.this.hasMethodAnnotation(annotationType); 233 } 234 } 235 236 237 /** 238 * MethodParameter subclass based on the actual return value type or if 239 * that's null falling back on the generic type within the declared async 240 * return type, e.g. Foo instead of {@code DeferredResult<Foo>}. 241 */ 242 private class ConcurrentResultMethodParameter extends HandlerMethodParameter { 243 244 private final Object returnValue; 245 246 private final ResolvableType returnType; 247 248 public ConcurrentResultMethodParameter(Object returnValue) { 249 super(-1); 250 this.returnValue = returnValue; 251 this.returnType = ResolvableType.forType(super.getGenericParameterType()).getGeneric(0); 252 } 253 254 public ConcurrentResultMethodParameter(ConcurrentResultMethodParameter original) { 255 super(original); 256 this.returnValue = original.returnValue; 257 this.returnType = original.returnType; 258 } 259 260 @Override 261 public Class<?> getParameterType() { 262 if (this.returnValue != null) { 263 return this.returnValue.getClass(); 264 } 265 if (!ResolvableType.NONE.equals(this.returnType)) { 266 return this.returnType.resolve(Object.class); 267 } 268 return super.getParameterType(); 269 } 270 271 @Override 272 public Type getGenericParameterType() { 273 return this.returnType.getType(); 274 } 275 276 @Override 277 public ConcurrentResultMethodParameter clone() { 278 return new ConcurrentResultMethodParameter(this); 279 } 280 } 281 282}