1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springframework.web.servlet.mvc.method.annotation;
18
19 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 import org.springframework.core.MethodParameter;
26 import org.springframework.core.ResolvableType;
27 import org.springframework.http.HttpStatus;
28 import org.springframework.util.ClassUtils;
29 import org.springframework.util.StringUtils;
30 import org.springframework.web.bind.annotation.ResponseStatus;
31 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 import org.springframework.web.method.support.ModelAndViewContainer;
37 import org.springframework.web.servlet.View;
38 import org.springframework.web.util.NestedServletException;
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
56
57 private static final Method CALLABLE_METHOD = ClassUtils.getMethod(Callable.class, "call");
58
59
60 private HttpStatus responseStatus;
61
62 private String responseReason;
63
64 private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
65
66
67
68
69
70 public ServletInvocableHandlerMethod(Object handler, Method method) {
71 super(handler, method);
72 initResponseStatus();
73 }
74
75
76
77
78 public ServletInvocableHandlerMethod(HandlerMethod handlerMethod) {
79 super(handlerMethod);
80 initResponseStatus();
81 }
82
83 private void initResponseStatus() {
84 ResponseStatus annotation = getMethodAnnotation(ResponseStatus.class);
85 if (annotation != null) {
86 this.responseStatus = annotation.value();
87 this.responseReason = annotation.reason();
88 }
89 }
90
91
92
93
94
95
96 public void setHandlerMethodReturnValueHandlers(HandlerMethodReturnValueHandlerComposite returnValueHandlers) {
97 this.returnValueHandlers = returnValueHandlers;
98 }
99
100
101
102
103
104
105
106
107 public void invokeAndHandle(ServletWebRequest webRequest,
108 ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
109
110 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
111 setResponseStatus(webRequest);
112
113 if (returnValue == null) {
114 if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {
115 mavContainer.setRequestHandled(true);
116 return;
117 }
118 }
119 else if (StringUtils.hasText(this.responseReason)) {
120 mavContainer.setRequestHandled(true);
121 return;
122 }
123
124 mavContainer.setRequestHandled(false);
125 try {
126 this.returnValueHandlers.handleReturnValue(
127 returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
128 }
129 catch (Exception ex) {
130 if (logger.isTraceEnabled()) {
131 logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
132 }
133 throw ex;
134 }
135 }
136
137
138
139
140 private void setResponseStatus(ServletWebRequest webRequest) throws IOException {
141 if (this.responseStatus == null) {
142 return;
143 }
144 if (StringUtils.hasText(this.responseReason)) {
145 webRequest.getResponse().sendError(this.responseStatus.value(), this.responseReason);
146 }
147 else {
148 webRequest.getResponse().setStatus(this.responseStatus.value());
149 }
150
151 webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, this.responseStatus);
152 }
153
154
155
156
157
158
159 private boolean isRequestNotModified(ServletWebRequest webRequest) {
160 return webRequest.isNotModified();
161 }
162
163
164
165
166 private boolean hasResponseStatus() {
167 return (this.responseStatus != null);
168 }
169
170 private String getReturnValueHandlingErrorMessage(String message, Object returnValue) {
171 StringBuilder sb = new StringBuilder(message);
172 if (returnValue != null) {
173 sb.append(" [type=").append(returnValue.getClass().getName()).append("]");
174 }
175 sb.append(" [value=").append(returnValue).append("]");
176 return getDetailedErrorMessage(sb.toString());
177 }
178
179
180
181
182
183
184
185 ServletInvocableHandlerMethod wrapConcurrentResult(Object result) {
186 return new ConcurrentResultHandlerMethod(result, new ConcurrentResultMethodParameter(result));
187 }
188
189
190
191
192
193
194
195
196 private class ConcurrentResultHandlerMethod extends ServletInvocableHandlerMethod {
197
198 private final MethodParameter returnType;
199
200 public ConcurrentResultHandlerMethod(final Object result, ConcurrentResultMethodParameter returnType) {
201 super(new Callable<Object>() {
202 @Override
203 public Object call() throws Exception {
204 if (result instanceof Exception) {
205 throw (Exception) result;
206 }
207 else if (result instanceof Throwable) {
208 throw new NestedServletException("Async processing failed", (Throwable) result);
209 }
210 return result;
211 }
212 }, CALLABLE_METHOD);
213 setHandlerMethodReturnValueHandlers(ServletInvocableHandlerMethod.this.returnValueHandlers);
214 this.returnType = returnType;
215 }
216
217
218
219
220 @Override
221 public Class<?> getBeanType() {
222 return ServletInvocableHandlerMethod.this.getBeanType();
223 }
224
225
226
227
228
229 @Override
230 public MethodParameter getReturnValueType(Object returnValue) {
231 return this.returnType;
232 }
233
234
235
236
237 @Override
238 public <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) {
239 return ServletInvocableHandlerMethod.this.getMethodAnnotation(annotationType);
240 }
241 }
242
243
244
245
246
247
248
249 private class ConcurrentResultMethodParameter extends HandlerMethodParameter {
250
251 private final Object returnValue;
252
253 private final ResolvableType returnType;
254
255 public ConcurrentResultMethodParameter(Object returnValue) {
256 super(-1);
257 this.returnValue = returnValue;
258 this.returnType = ResolvableType.forType(super.getGenericParameterType()).getGeneric(0);
259 }
260
261 @Override
262 public Class<?> getParameterType() {
263 return (this.returnValue != null ? this.returnValue.getClass() : this.returnType.getRawClass());
264 }
265
266 @Override
267 public Type getGenericParameterType() {
268 return this.returnType.getType();
269 }
270 }
271
272 }