2 * Copyright 2004-2005 the original author or authors.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package org
.codehaus
.groovy
.grails
.web
.servlet
;
18 import grails
.util
.GrailsUtil
;
19 import org
.codehaus
.groovy
.grails
.commons
.*;
20 import org
.codehaus
.groovy
.grails
.commons
.spring
.GrailsApplicationContext
;
21 import org
.codehaus
.groovy
.grails
.web
.context
.GrailsConfigUtils
;
22 import org
.codehaus
.groovy
.grails
.web
.servlet
.mvc
.GrailsWebRequest
;
23 import org
.codehaus
.groovy
.grails
.web
.servlet
.mvc
.SimpleGrailsController
;
24 import org
.codehaus
.groovy
.grails
.web
.util
.WebUtils
;
25 import org
.springframework
.beans
.BeansException
;
26 import org
.springframework
.beans
.factory
.NoSuchBeanDefinitionException
;
27 import org
.springframework
.beans
.factory
.access
.BootstrapException
;
28 import org
.springframework
.context
.i18n
.LocaleContext
;
29 import org
.springframework
.context
.i18n
.LocaleContextHolder
;
30 import org
.springframework
.util
.Assert
;
31 import org
.springframework
.web
.context
.WebApplicationContext
;
32 import org
.springframework
.web
.context
.request
.RequestContextHolder
;
33 import org
.springframework
.web
.context
.request
.WebRequestInterceptor
;
34 import org
.springframework
.web
.context
.support
.WebApplicationContextUtils
;
35 import org
.springframework
.web
.multipart
.MultipartException
;
36 import org
.springframework
.web
.multipart
.MultipartHttpServletRequest
;
37 import org
.springframework
.web
.multipart
.MultipartResolver
;
38 import org
.springframework
.web
.servlet
.*;
39 import org
.springframework
.web
.servlet
.handler
.WebRequestHandlerInterceptorAdapter
;
40 import org
.springframework
.web
.util
.NestedServletException
;
41 import org
.springframework
.web
.util
.UrlPathHelper
;
43 import javax
.servlet
.ServletException
;
44 import javax
.servlet
.http
.HttpServletRequest
;
45 import javax
.servlet
.http
.HttpServletResponse
;
46 import java
.util
.Iterator
;
47 import java
.util
.Locale
;
51 * <p>Servlet that handles incoming requests for Grails.
53 * <p>This servlet loads the Spring configuration based on the Grails application
54 * in the parent application context.
56 * @author Steven Devijver
57 * @author Graeme Rocher
61 public class GrailsDispatcherServlet
extends DispatcherServlet
{
62 private GrailsApplication application
;
63 private UrlPathHelper urlHelper
= new GrailsUrlPathHelper();
64 private SimpleGrailsController grailsController
;
65 protected HandlerInterceptor
[] interceptors
;
66 protected MultipartResolver multipartResolver
;
67 private static final String EXCEPTION_ATTRIBUTE
= "exception";
69 public GrailsDispatcherServlet() {
71 setDetectAllHandlerMappings(false);
75 protected void initFrameworkServlet() throws ServletException
, BeansException
{
76 super.initFrameworkServlet();
77 initMultipartResolver();
82 * Initialize the MultipartResolver used by this class.
83 * If no bean is defined with the given name in the BeanFactory
84 * for this namespace, no multipart handling is provided.
86 private void initMultipartResolver() throws BeansException
{
88 this.multipartResolver
= (MultipartResolver
)
89 getWebApplicationContext().getBean(MULTIPART_RESOLVER_BEAN_NAME
, MultipartResolver
.class);
90 if (logger
.isInfoEnabled()) {
91 logger
.info("Using MultipartResolver [" + this.multipartResolver
+ "]");
94 catch (NoSuchBeanDefinitionException ex
) {
95 // Default is no multipart resolver.
96 this.multipartResolver
= null;
97 if (logger
.isInfoEnabled()) {
98 logger
.info("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME
+
99 "': no multipart request handling provided");
104 protected WebApplicationContext
createWebApplicationContext(WebApplicationContext parent
) throws BeansException
{
105 WebApplicationContext wac
= WebApplicationContextUtils
.getWebApplicationContext(getServletContext());
106 WebApplicationContext webContext
;
107 // construct the SpringConfig for the container managed application
108 Assert
.notNull(parent
, "Grails requires a parent ApplicationContext, is the /WEB-INF/applicationContext.xml file missing?");
109 this.application
= (GrailsApplication
) parent
.getBean(GrailsApplication
.APPLICATION_ID
, GrailsApplication
.class);
112 if(wac
instanceof GrailsApplicationContext
) {
116 webContext
= GrailsConfigUtils
.configureWebApplicationContext(getServletContext(), parent
);
118 GrailsConfigUtils
.executeGrailsBootstraps(application
, webContext
, getServletContext());
119 } catch (Exception e
) {
120 GrailsUtil
.deepSanitize(e
);
121 if(e
instanceof BeansException
) throw (BeansException
)e
;
123 throw new BootstrapException("Error executing bootstraps", e
);
128 initGrailsController(webContext
);
129 this.interceptors
= establishInterceptors(webContext
);
134 private void initGrailsController(WebApplicationContext webContext
) {
135 if(webContext
.containsBean(SimpleGrailsController
.APPLICATION_CONTEXT_ID
)) {
136 this.grailsController
= (SimpleGrailsController
)webContext
.getBean(SimpleGrailsController
.APPLICATION_CONTEXT_ID
);
143 * Evalutes the given WebApplicationContext for all HandlerInterceptor and WebRequestInterceptor instances
145 * @param webContext The WebApplicationContext
146 * @return An array of HandlerInterceptor instances
148 protected HandlerInterceptor
[] establishInterceptors(WebApplicationContext webContext
) {
149 HandlerInterceptor
[] interceptors
;
150 String
[] interceptorNames
= webContext
.getBeanNamesForType(HandlerInterceptor
.class);
151 String
[] webRequestInterceptors
= webContext
.getBeanNamesForType( WebRequestInterceptor
.class);
152 interceptors
= new HandlerInterceptor
[interceptorNames
.length
+webRequestInterceptors
.length
];
154 // Merge the handler and web request interceptors into a single
155 // array. Note that we start with the web request interceptors
156 // to ensure that the OpenSessionInViewInterceptor (which is a
157 // web request interceptor) is invoked before the user-defined
158 // filters (which are attached to a handler interceptor). This
159 // should ensure that the Hibernate session is in the proper
160 // state if and when users access the database within their
163 for (int i
= 0; i
< webRequestInterceptors
.length
; i
++) {
164 interceptors
[j
++] = new WebRequestHandlerInterceptorAdapter((WebRequestInterceptor
)webContext
.getBean(webRequestInterceptors
[i
]));
166 for (int i
= 0; i
< interceptorNames
.length
; i
++) {
167 interceptors
[j
++] = (HandlerInterceptor
)webContext
.getBean(interceptorNames
[i
]);
172 public void destroy() {
173 WebApplicationContext webContext
= getWebApplicationContext();
174 GrailsApplication application
= (GrailsApplication
) webContext
.getBean(GrailsApplication
.APPLICATION_ID
, GrailsApplication
.class);
176 GrailsClass
[] bootstraps
= application
.getArtefacts(BootstrapArtefactHandler
.TYPE
);
177 for (int i
= 0; i
< bootstraps
.length
; i
++) {
178 ((GrailsBootstrapClass
)bootstraps
[i
]).callDestroy();
184 public void setApplication(GrailsApplication application
) {
185 this.application
= application
;
189 * @see org.springframework.web.servlet.DispatcherServlet#doDispatch(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
191 protected void doDispatch(final HttpServletRequest request
, HttpServletResponse response
) throws Exception
{
192 HttpServletRequest processedRequest
= request
;
193 HandlerExecutionChain mappedHandler
= null;
194 int interceptorIndex
= -1;
195 final LocaleResolver localeResolver
= (LocaleResolver
)request
.getAttribute(LOCALE_RESOLVER_ATTRIBUTE
);
198 // Expose current LocaleResolver and request as LocaleContext.
199 LocaleContext previousLocaleContext
= LocaleContextHolder
.getLocaleContext();
200 LocaleContextHolder
.setLocaleContext(new LocaleContext() {
201 public Locale
getLocale() {
203 return localeResolver
.resolveLocale(request
);
208 // If the request is an include we need to try to use the original wrapped sitemesh
209 // response, otherwise layouts won't work properly
210 if(WebUtils
.isIncludeRequest(request
)) {
211 response
= useWrappedOrOriginalResponse(response
);
214 GrailsWebRequest requestAttributes
= null;
215 GrailsWebRequest previousRequestAttributes
= null;
217 ModelAndView mv
= null;
219 Object exceptionAttribute
= request
.getAttribute(EXCEPTION_ATTRIBUTE
);
220 // only process multipart requests if an exception hasn't occured
221 if(exceptionAttribute
== null)
222 processedRequest
= checkMultipart(request
);
223 // Expose current RequestAttributes to current thread.
224 previousRequestAttributes
= (GrailsWebRequest
) RequestContextHolder
.currentRequestAttributes();
225 requestAttributes
= new GrailsWebRequest(processedRequest
, response
, getServletContext());
226 copyParamsFromPreviousRequest(previousRequestAttributes
, requestAttributes
);
228 // Update the current web request.
229 WebUtils
.storeGrailsWebRequest(requestAttributes
);
231 if (logger
.isDebugEnabled()) {
232 logger
.debug("Bound request context to thread: " + request
);
233 logger
.debug("Using response object: " + response
.getClass());
239 // Determine handler for the current request.
240 mappedHandler
= getHandler(processedRequest
, false);
241 if (mappedHandler
== null || mappedHandler
.getHandler() == null) {
242 noHandlerFound(processedRequest
, response
);
246 // Apply preHandle methods of registered interceptors.
247 if (mappedHandler
.getInterceptors() != null) {
248 for (int i
= 0; i
< mappedHandler
.getInterceptors().length
; i
++) {
249 HandlerInterceptor interceptor
= mappedHandler
.getInterceptors()[i
];
250 if (!interceptor
.preHandle(processedRequest
, response
, mappedHandler
.getHandler())) {
251 triggerAfterCompletion(mappedHandler
, interceptorIndex
, processedRequest
, response
, null);
254 interceptorIndex
= i
;
258 // Actually invoke the handler.
259 HandlerAdapter ha
= getHandlerAdapter(mappedHandler
.getHandler());
260 mv
= ha
.handle(processedRequest
, response
, mappedHandler
.getHandler());
262 // Apply postHandle methods of registered interceptors.
263 if (mappedHandler
.getInterceptors() != null) {
264 for (int i
= mappedHandler
.getInterceptors().length
- 1; i
>= 0; i
--) {
265 HandlerInterceptor interceptor
= mappedHandler
.getInterceptors()[i
];
266 interceptor
.postHandle(processedRequest
, response
, mappedHandler
.getHandler(), mv
);
270 catch (ModelAndViewDefiningException ex
) {
271 GrailsUtil
.deepSanitize(ex
);
272 if (logger
.isDebugEnabled())
273 logger
.debug("ModelAndViewDefiningException encountered", ex
);
274 mv
= ex
.getModelAndView();
276 catch (Exception ex
) {
277 GrailsUtil
.deepSanitize(ex
);
278 Object handler
= (mappedHandler
!= null ? mappedHandler
.getHandler() : null);
279 mv
= processHandlerException(request
, response
, handler
, ex
);
282 // Did the handler return a view to render?
283 if (mv
!= null && !mv
.wasCleared()) {
284 // If an exception occurs in here, like a bad closing tag,
285 // we have nothing to render.
288 render(mv
, processedRequest
, response
);
289 } catch (Exception e
) {
290 mv
= super.processHandlerException(processedRequest
, response
, mappedHandler
, e
);
291 render(mv
, processedRequest
, response
);
295 if (logger
.isDebugEnabled()) {
296 logger
.debug("Null ModelAndView returned to DispatcherServlet with name '" +
297 getServletName() + "': assuming HandlerAdapter completed request handling");
301 // Trigger after-completion for successful outcome.
302 triggerAfterCompletion(mappedHandler
, interceptorIndex
, processedRequest
, response
, null);
305 catch (Exception ex
) {
306 // Trigger after-completion for thrown exception.
307 triggerAfterCompletion(mappedHandler
, interceptorIndex
, processedRequest
, response
, ex
);
311 ServletException ex
= new NestedServletException("Handler processing failed", err
);
312 // Trigger after-completion for thrown exception.
313 triggerAfterCompletion(mappedHandler
, interceptorIndex
, processedRequest
, response
, ex
);
318 // Clean up any resources used by a multipart request.
319 if (processedRequest
instanceof MultipartHttpServletRequest
&& processedRequest
!= request
) {
320 if(multipartResolver
!= null)
321 this.multipartResolver
.cleanupMultipart((MultipartHttpServletRequest
) processedRequest
);
324 // Reset thread-bound RequestAttributes.
325 if(requestAttributes
!= null) {
327 requestAttributes
.requestCompleted();
328 WebUtils
.storeGrailsWebRequest(previousRequestAttributes
);
330 // Reset thread-bound LocaleContext.
331 LocaleContextHolder
.setLocaleContext(previousLocaleContext
);
334 if (logger
.isDebugEnabled()) {
335 logger
.debug("Cleared thread-bound request context: " + request
);
340 protected HttpServletResponse
useWrappedOrOriginalResponse(HttpServletResponse response
) {
341 HttpServletResponse r
= WrappedResponseHolder
.getWrappedResponse();
342 if(r
!= null) return r
;
346 protected void copyParamsFromPreviousRequest(GrailsWebRequest previousRequestAttributes
, GrailsWebRequest requestAttributes
) {
347 Map previousParams
= previousRequestAttributes
.getParams();
348 Map params
= requestAttributes
.getParams();
349 for (Iterator i
= previousParams
.keySet().iterator(); i
.hasNext();) {
350 String name
= (String
)i
.next();
351 params
.put(name
, previousParams
.get(name
));
356 * Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
357 * Will just invoke afterCompletion for all interceptors whose preHandle
358 * invocation has successfully completed and returned true.
359 * @param mappedHandler the mapped HandlerExecutionChain
360 * @param interceptorIndex index of last interceptor that successfully completed
361 * @param ex Exception thrown on handler execution, or <code>null</code> if none
362 * @see HandlerInterceptor#afterCompletion
364 protected void triggerAfterCompletion(
365 HandlerExecutionChain mappedHandler
, int interceptorIndex
,
366 HttpServletRequest request
, HttpServletResponse response
, Exception ex
)
369 // Apply afterCompletion methods of registered interceptors.
370 if (mappedHandler
!= null) {
371 if (mappedHandler
.getInterceptors() != null) {
372 for (int i
= interceptorIndex
; i
>= 0; i
--) {
373 HandlerInterceptor interceptor
= mappedHandler
.getInterceptors()[i
];
375 interceptor
.afterCompletion(request
, response
, mappedHandler
.getHandler(), ex
);
377 catch (Throwable ex2
) {
378 logger
.error("HandlerInterceptor.afterCompletion threw exception", ex2
);
386 * Convert the request into a multipart request, and make multipart resolver available.
387 * If no multipart resolver is set, simply use the existing request.
388 * @param request current HTTP request
389 * @return the processed request (multipart wrapper if necessary)
391 protected HttpServletRequest
checkMultipart(HttpServletRequest request
) throws MultipartException
{
392 if (this.multipartResolver
!= null && this.multipartResolver
.isMultipart(request
)) {
393 if (request
instanceof MultipartHttpServletRequest
) {
394 logger
.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +
395 "this typically results from an additional MultipartFilter in web.xml");
398 return this.multipartResolver
.resolveMultipart(request
);
401 // If not returned before: return original request.
405 * Overrides the default behaviour to establish the handler from the GrailsApplication instance
407 * @param request The request
408 * @param cache Whether to cache the Handler in the request
409 * @return The HandlerExecutionChain
413 protected HandlerExecutionChain
getHandler(HttpServletRequest request
, boolean cache
) throws Exception
{
414 String uri
= urlHelper
.getPathWithinApplication(request
);
415 if(logger
.isDebugEnabled()) {
416 logger
.debug("Looking up Grails controller for URI ["+uri
+"]");
418 GrailsControllerClass controllerClass
= (GrailsControllerClass
) application
.getArtefactForFeature(
419 ControllerArtefactHandler
.TYPE
, uri
);
420 if(controllerClass
!=null) {
421 HandlerInterceptor
[] interceptors
;
422 // if we're in a development environment we want to re-establish interceptors just in case they
423 // have changed at runtime
424 if(GrailsUtil
.isDevelopmentEnv()) {
425 interceptors
= establishInterceptors(getWebApplicationContext());
428 interceptors
= this.interceptors
;
430 if(grailsController
== null) {
431 initGrailsController(getWebApplicationContext());
433 return new HandlerExecutionChain(grailsController
, interceptors
);