ServletInvocableHandlerMethod.java 9.0 KB
Newer Older
1
/*
2
 * Copyright 2002-2014 the original author or authors.
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 *
 * 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.web.servlet.mvc.method.annotation;

import java.io.IOException;
20
import java.lang.annotation.Annotation;
21
import java.lang.reflect.Method;
22
import java.lang.reflect.Type;
23
import java.util.concurrent.Callable;
24

25 26
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
27
import org.springframework.http.HttpStatus;
28
import org.springframework.util.ClassUtils;
29 30 31
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.ServletWebRequest;
32
import org.springframework.web.method.HandlerMethod;
33
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
34
import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite;
35
import org.springframework.web.method.support.InvocableHandlerMethod;
36 37
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.View;
38
import org.springframework.web.util.NestedServletException;
39 40

/**
41 42 43 44
 * 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.
C
Chris Beams 已提交
45
 *
46 47 48 49 50
 * <p>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.
C
Chris Beams 已提交
51
 *
52 53 54 55 56
 * @author Rossen Stoyanchev
 * @since 3.1
 */
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {

57 58 59
	private static final Method CALLABLE_METHOD = ClassUtils.getMethod(Callable.class, "call");


60
	private HttpStatus responseStatus;
C
Chris Beams 已提交
61

62
	private String responseReason;
C
Chris Beams 已提交
63

64
	private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
65

66

67
	/**
68
	 * Creates an instance from the given handler and method.
69 70 71
	 */
	public ServletInvocableHandlerMethod(Object handler, Method method) {
		super(handler, method);
72 73 74 75 76 77 78 79 80 81
		initResponseStatus();
	}

	/**
	 * Create an instance from a {@code HandlerMethod}.
	 */
	public ServletInvocableHandlerMethod(HandlerMethod handlerMethod) {
		super(handlerMethod);
		initResponseStatus();
	}
82

83 84 85 86 87
	private void initResponseStatus() {
		ResponseStatus annot = getMethodAnnotation(ResponseStatus.class);
		if (annot != null) {
			this.responseStatus = annot.value();
			this.responseReason = annot.reason();
88 89 90 91
		}
	}

	/**
92 93 94 95 96 97 98 99 100 101
	 * Register {@link HandlerMethodReturnValueHandler} instances to use to
	 * handle return values.
	 */
	public void setHandlerMethodReturnValueHandlers(HandlerMethodReturnValueHandlerComposite returnValueHandlers) {
		this.returnValueHandlers = returnValueHandlers;
	}

	/**
	 * Invokes the method and handles the return value through a registered
	 * {@link HandlerMethodReturnValueHandler}.
C
Chris Beams 已提交
102
	 *
103 104 105
	 * @param webRequest the current request
	 * @param mavContainer the ModelAndViewContainer for this request
	 * @param providedArgs "given" arguments matched by type, not resolved
106
	 */
107 108 109 110
	public final void invokeAndHandle(ServletWebRequest webRequest,
			ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {

		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
111

112
		setResponseStatus(webRequest);
113

114
		if (returnValue == null) {
115
			if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {
116
				mavContainer.setRequestHandled(true);
117 118
				return;
			}
119
		}
120 121 122 123
		else if (StringUtils.hasText(this.responseReason)) {
			mavContainer.setRequestHandled(true);
			return;
		}
C
Chris Beams 已提交
124

125
		mavContainer.setRequestHandled(false);
126

127
		try {
128
			this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
129 130
		}
		catch (Exception ex) {
131 132 133 134 135 136
			if (logger.isTraceEnabled()) {
				logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
			}
			throw ex;
		}
	}
137

138 139 140 141
	/**
	 * Set the response status according to the {@link ResponseStatus} annotation.
	 */
	private void setResponseStatus(ServletWebRequest webRequest) throws IOException {
142 143 144
		if (this.responseStatus == null) {
			return;
		}
145

146 147
		if (StringUtils.hasText(this.responseReason)) {
			webRequest.getResponse().sendError(this.responseStatus.value(), this.responseReason);
148
		}
149 150 151 152 153 154
		else {
			webRequest.getResponse().setStatus(this.responseStatus.value());
		}

		// to be picked up by the RedirectView
		webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, this.responseStatus);
155 156 157
	}

	/**
R
Rossen Stoyanchev 已提交
158 159 160
	 * Does the given request qualify as "not modified"?
	 * @see ServletWebRequest#checkNotModified(long)
	 * @see ServletWebRequest#checkNotModified(String)
161
	 */
162 163
	private boolean isRequestNotModified(ServletWebRequest webRequest) {
		return webRequest.isNotModified();
164 165
	}

166
	/**
R
Rossen Stoyanchev 已提交
167
	 * Does this method have the response status instruction?
168
	 */
169 170
	private boolean hasResponseStatus() {
		return responseStatus != null;
171
	}
172

173 174 175 176 177 178 179 180 181 182
	private String getReturnValueHandlingErrorMessage(String message, Object returnValue) {
		StringBuilder sb = new StringBuilder(message);
		if (returnValue != null) {
			sb.append(" [type=" + returnValue.getClass().getName() + "] ");
		}
		sb.append("[value=" + returnValue + "]");
		return getDetailedErrorMessage(sb.toString());
	}

	/**
183 184 185 186
	 * Create a nested ServletInvocableHandlerMethod sub-class 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).
187
	 */
188 189
	ServletInvocableHandlerMethod wrapConcurrentResult(Object result) {
		return new ConcurrentResultHandlerMethod(result, new ConcurrentResultMethodParameter(result));
190 191
	}

192 193

	/**
194 195 196 197
	 * A nested sub-class 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.
198
	 */
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
	private class ConcurrentResultHandlerMethod extends ServletInvocableHandlerMethod {

		private final MethodParameter returnType;


		public ConcurrentResultHandlerMethod(final Object result, ConcurrentResultMethodParameter returnType) {
			super(new Callable<Object>() {
				@Override
				public Object call() throws Exception {
					if (result instanceof Exception) {
						throw (Exception) result;
					}
					else if (result instanceof Throwable) {
						throw new NestedServletException("Async processing failed", (Throwable) result);
					}
					return result;
				}
			}, CALLABLE_METHOD);
			setHandlerMethodReturnValueHandlers(ServletInvocableHandlerMethod.this.returnValueHandlers);
			this.returnType = returnType;
219 220
		}

221
		/**
222
		 * Bridge to actual controller type-level annotations.
223 224 225 226 227 228 229
		 */
		@Override
		public Class<?> getBeanType() {
			return ServletInvocableHandlerMethod.this.getBeanType();
		}

		/**
230 231 232 233 234 235 236 237 238 239
		 * Bridge to actual return value or generic type within the declared
		 * async return type, e.g. Foo instead of {@code DeferredResult<Foo>}.
		 */
		@Override
		public MethodParameter getReturnValueType(Object returnValue) {
			return this.returnType;
		}

		/**
		 * Bridge to controller method-level annotations.
240
		 */
241 242 243 244 245 246
		@Override
		public <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) {
			return ServletInvocableHandlerMethod.this.getMethodAnnotation(annotationType);
		}
	}

247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
	/**
	 * MethodParameter sub-class 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<Foo>}.
	 */
	private class ConcurrentResultMethodParameter extends HandlerMethodParameter {

		private final Object returnValue;

		private final ResolvableType returnType;


		public ConcurrentResultMethodParameter(Object returnValue) {
			super(-1);
			this.returnValue = returnValue;
			this.returnType = ResolvableType.forType(super.getGenericParameterType()).getGeneric(0);
		}

		@Override
		public Class<?> getParameterType() {
			return (returnValue != null ? returnValue.getClass() : this.returnType.getRawClass());
		}

		@Override
		public Type getGenericParameterType() {
			return this.returnType.getType();
		}
	}

276
}