1 /*
2 * Copyright 2002-2008 the original author or authors.
3 *
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
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16
17 package org.springframework.web.servlet;
18
19 import java.io.IOException;
20 import java.security.Principal;
21
22 import javax.servlet.ServletException;
23 import javax.servlet.http.HttpServletRequest;
24 import javax.servlet.http.HttpServletResponse;
25
26 import org.springframework.beans.BeanUtils;
27 import org.springframework.beans.BeansException;
28 import org.springframework.context.ApplicationContext;
29 import org.springframework.context.ApplicationContextException;
30 import org.springframework.context.ApplicationEvent;
31 import org.springframework.context.ApplicationListener;
32 import org.springframework.context.ConfigurableApplicationContext;
33 import org.springframework.context.event.ContextRefreshedEvent;
34 import org.springframework.context.event.SourceFilteringListener;
35 import org.springframework.web.context.ConfigurableWebApplicationContext;
36 import org.springframework.web.context.WebApplicationContext;
37 import org.springframework.web.context.support.ServletRequestHandledEvent;
38 import org.springframework.web.context.support.WebApplicationContextUtils;
39 import org.springframework.web.context.support.XmlWebApplicationContext;
40 import org.springframework.web.util.NestedServletException;
41 import org.springframework.web.util.WebUtils;
42
43 /**
44 * Base servlet for Spring's web framework. Provides integration with
45 * a Spring application context, in a JavaBean-based overall solution.
46 *
47 * <p>This class offers the following functionality:
48 * <ul>
49 * <li>Manages a {@link org.springframework.web.context.WebApplicationContext}
50 * instance per servlet. The servlet's configuration is determined by beans
51 * in the servlet's namespace.
52 * <li>Publishes events on request processing, whether or not a request is
53 * successfully handled.
54 * </ul>
55 *
56 * <p>Subclasses must implement {@link #doService} to handle requests. Because this extends
57 * {@link HttpServletBean} rather than HttpServlet directly, bean properties are
58 * automatically mapped onto it. Subclasses can override {@link #initFrameworkServlet()}
59 * for custom initialization.
60 *
61 * <p>Detects a "contextClass" parameter at the servlet init-param level,
62 * falling back to the default context class,
63 * {@link org.springframework.web.context.support.XmlWebApplicationContext},
64 * if not found. Note that, with the default FrameworkServlet,
65 * a custom context class needs to implement the
66 * {@link org.springframework.web.context.ConfigurableWebApplicationContext} SPI.
67 *
68 * <p>Passes a "contextConfigLocation" servlet init-param to the context instance,
69 * parsing it into potentially multiple file paths which can be separated by any
70 * number of commas and spaces, like "test-servlet.xml, myServlet.xml".
71 * If not explicitly specified, the context implementation is supposed to build a
72 * default location from the namespace of the servlet.
73 *
74 * <p>Note: In case of multiple config locations, later bean definitions will
75 * override ones defined in earlier loaded files, at least when using Spring's
76 * default ApplicationContext implementation. This can be leveraged to
77 * deliberately override certain bean definitions via an extra XML file.
78 *
79 * <p>The default namespace is "'servlet-name'-servlet", e.g. "test-servlet" for a
80 * servlet-name "test" (leading to a "/WEB-INF/test-servlet.xml" default location
81 * with XmlWebApplicationContext). The namespace can also be set explicitly via
82 * the "namespace" servlet init-param.
83 *
84 * @author Rod Johnson
85 * @author Juergen Hoeller
86 * @author Sam Brannen
87 * @see #doService
88 * @see #setContextClass
89 * @see #setContextConfigLocation
90 * @see #setNamespace
91 */
92 public abstract class FrameworkServlet extends HttpServletBean implements ApplicationListener {
93
94 /**
95 * Suffix for WebApplicationContext namespaces. If a servlet of this class is
96 * given the name "test" in a context, the namespace used by the servlet will
97 * resolve to "test-servlet".
98 */
99 public static final String DEFAULT_NAMESPACE_SUFFIX = "-servlet";
100
101 /**
102 * Default context class for FrameworkServlet.
103 * @see org.springframework.web.context.support.XmlWebApplicationContext
104 */
105 public static final Class DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
106
107 /**
108 * Prefix for the ServletContext attribute for the WebApplicationContext.
109 * The completion is the servlet name.
110 */
111 public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT.";
112
113
114 /** ServletContext attribute to find the WebApplicationContext in */
115 private String contextAttribute;
116
117 /** WebApplicationContext implementation class to create */
118 private Class contextClass = DEFAULT_CONTEXT_CLASS;
119
120 /** Namespace for this servlet */
121 private String namespace;
122
123 /** Explicit context config location */
124 private String contextConfigLocation;
125
126 /** Should we publish the context as a ServletContext attribute? */
127 private boolean publishContext = true;
128
129 /** Should we publish a ServletRequestHandledEvent at the end of each request? */
130 private boolean publishEvents = true;
131
132 /** Should we dispatch an HTTP OPTIONS request to {@link #doService}? */
133 private boolean dispatchOptionsRequest = false;
134
135 /** Should we dispatch an HTTP TRACE request to {@link #doService}? */
136 private boolean dispatchTraceRequest = false;
137
138 /** WebApplicationContext for this servlet */
139 private WebApplicationContext webApplicationContext;
140
141 /** Flag used to detect whether onRefresh has already been called */
142 private boolean refreshEventReceived = false;
143
144
145 /**
146 * Set the name of the ServletContext attribute which should be used to retrieve the
147 * {@link WebApplicationContext} that this servlet is supposed to use.
148 */
149 public void setContextAttribute(String contextAttribute) {
150 this.contextAttribute = contextAttribute;
151 }
152
153 /**
154 * Return the name of the ServletContext attribute which should be used to retrieve the
155 * {@link WebApplicationContext} that this servlet is supposed to use.
156 */
157 public String getContextAttribute() {
158 return this.contextAttribute;
159 }
160
161 /**
162 * Set a custom context class. This class must be of type
163 * {@link org.springframework.web.context.WebApplicationContext}.
164 * <p>When using the default FrameworkServlet implementation,
165 * the context class must also implement the
166 * {@link org.springframework.web.context.ConfigurableWebApplicationContext}
167 * interface.
168 * @see #createWebApplicationContext
169 */
170 public void setContextClass(Class contextClass) {
171 this.contextClass = contextClass;
172 }
173
174 /**
175 * Return the custom context class.
176 */
177 public Class getContextClass() {
178 return this.contextClass;
179 }
180
181 /**
182 * Set a custom namespace for this servlet,
183 * to be used for building a default context config location.
184 */
185 public void setNamespace(String namespace) {
186 this.namespace = namespace;
187 }
188
189 /**
190 * Return the namespace for this servlet, falling back to default scheme if
191 * no custom namespace was set: e.g. "test-servlet" for a servlet named "test".
192 */
193 public String getNamespace() {
194 return (this.namespace != null ? this.namespace : getServletName() + DEFAULT_NAMESPACE_SUFFIX);
195 }
196
197 /**
198 * Set the context config location explicitly, instead of relying on the default
199 * location built from the namespace. This location string can consist of
200 * multiple locations separated by any number of commas and spaces.
201 */
202 public void setContextConfigLocation(String contextConfigLocation) {
203 this.contextConfigLocation = contextConfigLocation;
204 }
205
206 /**
207 * Return the explicit context config location, if any.
208 */
209 public String getContextConfigLocation() {
210 return this.contextConfigLocation;
211 }
212
213 /**
214 * Set whether to publish this servlet's context as a ServletContext attribute,
215 * available to all objects in the web container. Default is "true".
216 * <p>This is especially handy during testing, although it is debatable whether
217 * it's good practice to let other application objects access the context this way.
218 */
219 public void setPublishContext(boolean publishContext) {
220 this.publishContext = publishContext;
221 }
222
223 /**
224 * Set whether this servlet should publish a ServletRequestHandledEvent at the end
225 * of each request. Default is "true"; can be turned off for a slight performance
226 * improvement, provided that no ApplicationListeners rely on such events.
227 * @see org.springframework.web.context.support.ServletRequestHandledEvent
228 */
229 public void setPublishEvents(boolean publishEvents) {
230 this.publishEvents = publishEvents;
231 }
232
233 /**
234 * Set whether this servlet should dispatch an HTTP OPTIONS request to
235 * the {@link #doService} method.
236 * <p>Default is "false", applying {@link javax.servlet.http.HttpServlet}'s
237 * default behavior (i.e. enumerating all standard HTTP request methods
238 * as a response to the OPTIONS request).
239 * <p>Turn this flag on if you prefer OPTIONS requests to go through the
240 * regular dispatching chain, just like other HTTP requests. This usually
241 * means that your controllers will receive those requests; make sure
242 * that those endpoints are actually able to handle an OPTIONS request.
243 * <p>Note that HttpServlet's default OPTIONS processing will be applied
244 * in any case. Your controllers are simply available to override the
245 * default headers and optionally generate a response body.
246 */
247 public void setDispatchOptionsRequest(boolean dispatchOptionsRequest) {
248 this.dispatchOptionsRequest = dispatchOptionsRequest;
249 }
250
251 /**
252 * Set whether this servlet should dispatch an HTTP TRACE request to
253 * the {@link #doService} method.
254 * <p>Default is "false", applying {@link javax.servlet.http.HttpServlet}'s
255 * default behavior (i.e. reflecting the message received back to the client).
256 * <p>Turn this flag on if you prefer TRACE requests to go through the
257 * regular dispatching chain, just like other HTTP requests. This usually
258 * means that your controllers will receive those requests; make sure
259 * that those endpoints are actually able to handle a TRACE request.
260 * <p>Note that HttpServlet's default TRACE processing will be applied
261 * in any case. Your controllers are simply available to override the
262 * default headers and the default body, calling <code>response.reset()</code>
263 * if necessary.
264 */
265 public void setDispatchTraceRequest(boolean dispatchTraceRequest) {
266 this.dispatchTraceRequest = dispatchTraceRequest;
267 }
268
269
270 /**
271 * Overridden method of {@link HttpServletBean}, invoked after any bean properties
272 * have been set. Creates this servlet's WebApplicationContext.
273 */
274 protected final void initServletBean() throws ServletException, BeansException {
275 getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
276 if (this.logger.isInfoEnabled()) {
277 this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
278 }
279 long startTime = System.currentTimeMillis();
280
281 try {
282 this.webApplicationContext = initWebApplicationContext();
283 initFrameworkServlet();
284 }
285 catch (ServletException ex) {
286 this.logger.error("Context initialization failed", ex);
287 throw ex;
288 }
289 catch (BeansException ex) {
290 this.logger.error("Context initialization failed", ex);
291 throw ex;
292 }
293
294 if (this.logger.isInfoEnabled()) {
295 long elapsedTime = System.currentTimeMillis() - startTime;
296 this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
297 elapsedTime + " ms");
298 }
299 }
300
301 /**
302 * Initialize and publish the WebApplicationContext for this servlet.
303 * <p>Delegates to {@link #createWebApplicationContext} for actual creation
304 * of the context. Can be overridden in subclasses.
305 * @return the WebApplicationContext instance
306 * @throws BeansException if the context couldn't be initialized
307 * @see #setContextClass
308 * @see #setContextConfigLocation
309 */
310 protected WebApplicationContext initWebApplicationContext() throws BeansException {
311 WebApplicationContext wac = findWebApplicationContext();
312 if (wac == null) {
313 // No fixed context defined for this servlet - create a local one.
314 WebApplicationContext parent =
315 WebApplicationContextUtils.getWebApplicationContext(getServletContext());
316 wac = createWebApplicationContext(parent);
317 }
318
319 if (!this.refreshEventReceived) {
320 // Apparently not a ConfigurableApplicationContext with refresh support:
321 // triggering initial onRefresh manually here.
322 onRefresh(wac);
323 }
324
325 if (this.publishContext) {
326 // Publish the context as a servlet context attribute.
327 String attrName = getServletContextAttributeName();
328 getServletContext().setAttribute(attrName, wac);
329 if (this.logger.isDebugEnabled()) {
330 this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
331 "' as ServletContext attribute with name [" + attrName + "]");
332 }
333 }
334
335 return wac;
336 }
337
338 /**
339 * Retrieve a <code>WebApplicationContext</code> from the <code>ServletContext</code>
340 * attribute with the {@link #setContextAttribute configured name}. The
341 * <code>WebApplicationContext</code> must have already been loaded and stored in the
342 * <code>ServletContext</code> before this servlet gets initialized (or invoked).
343 * <p>Subclasses may override this method to provide a different
344 * <code>WebApplicationContext</code> retrieval strategy.
345 * @return the WebApplicationContext for this servlet, or <code>null</code> if not found
346 * @see #getContextAttribute()
347 */
348 protected WebApplicationContext findWebApplicationContext() {
349 String attrName = getContextAttribute();
350 if (attrName == null) {
351 return null;
352 }
353 WebApplicationContext wac =
354 WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
355 if (wac == null) {
356 throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
357 }
358 return wac;
359 }
360
361 /**
362 * Instantiate the WebApplicationContext for this servlet, either a default
363 * {@link org.springframework.web.context.support.XmlWebApplicationContext}
364 * or a {@link #setContextClass custom context class}, if set.
365 * <p>This implementation expects custom contexts to implement the
366 * {@link org.springframework.web.context.ConfigurableWebApplicationContext}
367 * interface. Can be overridden in subclasses.
368 * <p>Do not forget to register this servlet instance as application listener on the
369 * created context (for triggering its {@link #onRefresh callback}, and to call
370 * {@link org.springframework.context.ConfigurableApplicationContext#refresh()}
371 * before returning the context instance.
372 * @param parent the parent ApplicationContext to use, or <code>null</code> if none
373 * @return the WebApplicationContext for this servlet
374 * @throws BeansException if the context couldn't be initialized
375 * @see org.springframework.web.context.support.XmlWebApplicationContext
376 */
377 protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent)
378 throws BeansException {
379
380 if (this.logger.isDebugEnabled()) {
381 this.logger.debug("Servlet with name '" + getServletName() +
382 "' will try to create custom WebApplicationContext context of class '" +
383 getContextClass().getName() + "'" + ", using parent context [" + parent + "]");
384 }
385 if (!ConfigurableWebApplicationContext.class.isAssignableFrom(getContextClass())) {
386 throw new ApplicationContextException(
387 "Fatal initialization error in servlet with name '" + getServletName() +
388 "': custom WebApplicationContext class [" + getContextClass().getName() +
389 "] is not of type ConfigurableWebApplicationContext");
390 }
391
392 ConfigurableWebApplicationContext wac =
393 (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(getContextClass());
394 wac.setParent(parent);
395 wac.setServletContext(getServletContext());
396 wac.setServletConfig(getServletConfig());
397 wac.setNamespace(getNamespace());
398 wac.setConfigLocation(getContextConfigLocation());
399 wac.addApplicationListener(new SourceFilteringListener(wac, this));
400
401 postProcessWebApplicationContext(wac);
402 wac.refresh();
403
404 return wac;
405 }
406
407 /**
408 * Post-process the given WebApplicationContext before it is refreshed
409 * and activated as context for this servlet.
410 * <p>The default implementation is empty. <code>refresh()</code> will
411 * be called automatically after this method returns.
412 * @param wac the configured WebApplicationContext (not refreshed yet)
413 * @see #createWebApplicationContext
414 * @see ConfigurableWebApplicationContext#refresh()
415 */
416 protected void postProcessWebApplicationContext(ConfigurableWebApplicationContext wac) {
417 }
418
419 /**
420 * Return the ServletContext attribute name for this servlet's WebApplicationContext.
421 * <p>The default implementation returns
422 * <code>SERVLET_CONTEXT_PREFIX + servlet name</code>.
423 * @see #SERVLET_CONTEXT_PREFIX
424 * @see #getServletName
425 */
426 public String getServletContextAttributeName() {
427 return SERVLET_CONTEXT_PREFIX + getServletName();
428 }
429
430 /**
431 * Return this servlet's WebApplicationContext.
432 */
433 public final WebApplicationContext getWebApplicationContext() {
434 return this.webApplicationContext;
435 }
436
437
438 /**
439 * This method will be invoked after any bean properties have been set and
440 * the WebApplicationContext has been loaded. The default implementation is empty;
441 * subclasses may override this method to perform any initialization they require.
442 * @throws ServletException in case of an initialization exception
443 * @throws BeansException if thrown by ApplicationContext methods
444 */
445 protected void initFrameworkServlet() throws ServletException, BeansException {
446 }
447
448 /**
449 * Refresh this servlet's application context, as well as the
450 * dependent state of the servlet.
451 * @throws BeansException in case of errors
452 * @see #getWebApplicationContext()
453 * @see org.springframework.context.ConfigurableApplicationContext#refresh()
454 */
455 public void refresh() throws BeansException {
456 WebApplicationContext wac = getWebApplicationContext();
457 if (!(wac instanceof ConfigurableApplicationContext)) {
458 throw new IllegalStateException("WebApplicationContext does not support refresh: " + wac);
459 }
460 ((ConfigurableApplicationContext) wac).refresh();
461 }
462
463 /**
464 * ApplicationListener endpoint that receives events from this servlet's
465 * WebApplicationContext.
466 * <p>The default implementation calls {@link #onRefresh} in case of a
467 * {@link org.springframework.context.event.ContextRefreshedEvent},
468 * triggering a refresh of this servlet's context-dependent state.
469 * @param event the incoming ApplicationContext event
470 */
471 public void onApplicationEvent(ApplicationEvent event) {
472 if (event instanceof ContextRefreshedEvent) {
473 this.refreshEventReceived = true;
474 onRefresh(((ContextRefreshedEvent) event).getApplicationContext());
475 }
476 }
477
478 /**
479 * Template method which can be overridden to add servlet-specific refresh work.
480 * Called after successful context refresh.
481 * <p>This implementation is empty.
482 * @param context the current WebApplicationContext
483 * @throws BeansException in case of errors
484 * @see #refresh()
485 */
486 protected void onRefresh(ApplicationContext context) throws BeansException {
487 // For subclasses: do nothing by default.
488 }
489
490
491 /**
492 * Delegate GET requests to processRequest/doService.
493 * <p>Will also be invoked by HttpServlet's default implementation of <code>doHead</code>,
494 * with a <code>NoBodyResponse</code> that just captures the content length.
495 * @see #doService
496 * @see #doHead
497 */
498 protected final void doGet(HttpServletRequest request, HttpServletResponse response)
499 throws ServletException, IOException {
500
501 processRequest(request, response);
502 }
503
504 /**
505 * Delegate POST requests to {@link #processRequest}.
506 * @see #doService
507 */
508 protected final void doPost(HttpServletRequest request, HttpServletResponse response)
509 throws ServletException, IOException {
510
511 processRequest(request, response);
512 }
513
514 /**
515 * Delegate PUT requests to {@link #processRequest}.
516 * @see #doService
517 */
518 protected final void doPut(HttpServletRequest request, HttpServletResponse response)
519 throws ServletException, IOException {
520
521 processRequest(request, response);
522 }
523
524 /**
525 * Delegate DELETE requests to {@link #processRequest}.
526 * @see #doService
527 */
528 protected final void doDelete(HttpServletRequest request, HttpServletResponse response)
529 throws ServletException, IOException {
530
531 processRequest(request, response);
532 }
533
534 /**
535 * Delegate OPTIONS requests to {@link #processRequest}, if desired.
536 * <p>Applies HttpServlet's standard OPTIONS processing first.
537 * @see #doService
538 */
539 protected void doOptions(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
540 super.doOptions(request, response);
541 if (this.dispatchOptionsRequest) {
542 processRequest(request, response);
543 }
544 }
545
546 /**
547 * Delegate TRACE requests to {@link #processRequest}, if desired.
548 * <p>Applies HttpServlet's standard TRACE processing first.
549 * @see #doService
550 */
551 protected void doTrace(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
552 super.doTrace(request, response);
553 if (this.dispatchTraceRequest) {
554 processRequest(request, response);
555 }
556 }
557
558
559 /**
560 * Process this request, publishing an event regardless of the outcome.
561 * <p>The actual event handling is performed by the abstract
562 * {@link #doService} template method.
563 */
564 protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
565 throws ServletException, IOException {
566
567 long startTime = System.currentTimeMillis();
568 Throwable failureCause = null;
569
570 try {
571 doService(request, response);
572 }
573 catch (ServletException ex) {
574 failureCause = ex;
575 throw ex;
576 }
577 catch (IOException ex) {
578 failureCause = ex;
579 throw ex;
580 }
581 catch (Throwable ex) {
582 failureCause = ex;
583 throw new NestedServletException("Request processing failed", ex);
584 }
585
586 finally {
587 if (failureCause != null) {
588 this.logger.debug("Could not complete request", failureCause);
589 }
590 else {
591 this.logger.debug("Successfully completed request");
592 }
593 if (this.publishEvents) {
594 // Whether or not we succeeded, publish an event.
595 long processingTime = System.currentTimeMillis() - startTime;
596 this.webApplicationContext.publishEvent(
597 new ServletRequestHandledEvent(this,
598 request.getRequestURI(), request.getRemoteAddr(),
599 request.getMethod(), getServletConfig().getServletName(),
600 WebUtils.getSessionId(request), getUsernameForRequest(request),
601 processingTime, failureCause));
602 }
603 }
604 }
605
606 /**
607 * Determine the username for the given request.
608 * <p>The default implementation takes the name of the UserPrincipal, if any.
609 * Can be overridden in subclasses.
610 * @param request current HTTP request
611 * @return the username, or <code>null</code> if none found
612 * @see javax.servlet.http.HttpServletRequest#getUserPrincipal()
613 */
614 protected String getUsernameForRequest(HttpServletRequest request) {
615 Principal userPrincipal = request.getUserPrincipal();
616 return (userPrincipal != null ? userPrincipal.getName() : null);
617 }
618
619 /**
620 * Subclasses must implement this method to do the work of request handling,
621 * receiving a centralized callback for GET, POST, PUT and DELETE.
622 * <p>The contract is essentially the same as that for the commonly overridden
623 * <code>doGet</code> or <code>doPost</code> methods of HttpServlet.
624 * <p>This class intercepts calls to ensure that exception handling and
625 * event publication takes place.
626 * @param request current HTTP request
627 * @param response current HTTP response
628 * @throws Exception in case of any kind of processing failure
629 * @see javax.servlet.http.HttpServlet#doGet
630 * @see javax.servlet.http.HttpServlet#doPost
631 */
632 protected abstract void doService(HttpServletRequest request, HttpServletResponse response)
633 throws Exception;
634
635
636 /**
637 * Close the WebApplicationContext of this servlet.
638 * @see org.springframework.context.ConfigurableApplicationContext#close()
639 */
640 public void destroy() {
641 getServletContext().log("Destroying Spring FrameworkServlet '" + getServletName() + "'");
642 if (this.webApplicationContext instanceof ConfigurableApplicationContext) {
643 ((ConfigurableApplicationContext) this.webApplicationContext).close();
644 }
645 }
646
647 }