/* * Copyright 2002-2008 the original author or authors. * * 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; import java.io.IOException; import java.security.Principal; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.BeanUtils; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextException; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.SourceFilteringListener; import org.springframework.web.context.ConfigurableWebApplicationContext; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.ServletRequestHandledEvent; import org.springframework.web.context.support.WebApplicationContextUtils; import org.springframework.web.context.support.XmlWebApplicationContext; import org.springframework.web.util.NestedServletException; import org.springframework.web.util.WebUtils; /** * Base servlet for Spring's web framework. Provides integration with * a Spring application context, in a JavaBean-based overall solution. * * <p>This class offers the following functionality: * <ul> * <li>Manages a {@link org.springframework.web.context.WebApplicationContext} * instance per servlet. The servlet's configuration is determined by beans * in the servlet's namespace. * <li>Publishes events on request processing, whether or not a request is * successfully handled. * </ul> * * <p>Subclasses must implement {@link #doService} to handle requests. Because this extends * {@link HttpServletBean} rather than HttpServlet directly, bean properties are * automatically mapped onto it. Subclasses can override {@link #initFrameworkServlet()} * for custom initialization. * * <p>Detects a "contextClass" parameter at the servlet init-param level, * falling back to the default context class, * {@link org.springframework.web.context.support.XmlWebApplicationContext}, * if not found. Note that, with the default FrameworkServlet, * a custom context class needs to implement the * {@link org.springframework.web.context.ConfigurableWebApplicationContext} SPI. * * <p>Passes a "contextConfigLocation" servlet init-param to the context instance, * parsing it into potentially multiple file paths which can be separated by any * number of commas and spaces, like "test-servlet.xml, myServlet.xml". * If not explicitly specified, the context implementation is supposed to build a * default location from the namespace of the servlet. * * <p>Note: In case of multiple config locations, later bean definitions will * override ones defined in earlier loaded files, at least when using Spring's * default ApplicationContext implementation. This can be leveraged to * deliberately override certain bean definitions via an extra XML file. * * <p>The default namespace is "'servlet-name'-servlet", e.g. "test-servlet" for a * servlet-name "test" (leading to a "/WEB-INF/test-servlet.xml" default location * with XmlWebApplicationContext). The namespace can also be set explicitly via * the "namespace" servlet init-param. * * @author Rod Johnson * @author Juergen Hoeller * @author Sam Brannen * @see #doService * @see #setContextClass * @see #setContextConfigLocation * @see #setNamespace */ public abstract class FrameworkServlet extends HttpServletBean implements ApplicationListener { /** * Suffix for WebApplicationContext namespaces. If a servlet of this class is * given the name "test" in a context, the namespace used by the servlet will * resolve to "test-servlet". */ public static final String DEFAULT_NAMESPACE_SUFFIX = "-servlet"; /** * Default context class for FrameworkServlet. * @see org.springframework.web.context.support.XmlWebApplicationContext */ public static final Class DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class; /** * Prefix for the ServletContext attribute for the WebApplicationContext. * The completion is the servlet name. */ public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT."; /** ServletContext attribute to find the WebApplicationContext in */ private String contextAttribute; /** WebApplicationContext implementation class to create */ private Class contextClass = DEFAULT_CONTEXT_CLASS; /** Namespace for this servlet */ private String namespace; /** Explicit context config location */ private String contextConfigLocation; /** Should we publish the context as a ServletContext attribute? */ private boolean publishContext = true; /** Should we publish a ServletRequestHandledEvent at the end of each request? */ private boolean publishEvents = true; /** Should we dispatch an HTTP OPTIONS request to {@link #doService}? */ private boolean dispatchOptionsRequest = false; /** Should we dispatch an HTTP TRACE request to {@link #doService}? */ private boolean dispatchTraceRequest = false; /** WebApplicationContext for this servlet */ private WebApplicationContext webApplicationContext; /** Flag used to detect whether onRefresh has already been called */ private boolean refreshEventReceived = false; /** * Set the name of the ServletContext attribute which should be used to retrieve the * {@link WebApplicationContext} that this servlet is supposed to use. */ public void setContextAttribute(String contextAttribute) { this.contextAttribute = contextAttribute; } /** * Return the name of the ServletContext attribute which should be used to retrieve the * {@link WebApplicationContext} that this servlet is supposed to use. */ public String getContextAttribute() { return this.contextAttribute; } /** * Set a custom context class. This class must be of type * {@link org.springframework.web.context.WebApplicationContext}. * <p>When using the default FrameworkServlet implementation, * the context class must also implement the * {@link org.springframework.web.context.ConfigurableWebApplicationContext} * interface. * @see #createWebApplicationContext */ public void setContextClass(Class contextClass) { this.contextClass = contextClass; } /** * Return the custom context class. */ public Class getContextClass() { return this.contextClass; } /** * Set a custom namespace for this servlet, * to be used for building a default context config location. */ public void setNamespace(String namespace) { this.namespace = namespace; } /** * Return the namespace for this servlet, falling back to default scheme if * no custom namespace was set: e.g. "test-servlet" for a servlet named "test". */ public String getNamespace() { return (this.namespace != null ? this.namespace : getServletName() + DEFAULT_NAMESPACE_SUFFIX); } /** * Set the context config location explicitly, instead of relying on the default * location built from the namespace. This location string can consist of * multiple locations separated by any number of commas and spaces. */ public void setContextConfigLocation(String contextConfigLocation) { this.contextConfigLocation = contextConfigLocation; } /** * Return the explicit context config location, if any. */ public String getContextConfigLocation() { return this.contextConfigLocation; } /** * Set whether to publish this servlet's context as a ServletContext attribute, * available to all objects in the web container. Default is "true". * <p>This is especially handy during testing, although it is debatable whether * it's good practice to let other application objects access the context this way. */ public void setPublishContext(boolean publishContext) { this.publishContext = publishContext; } /** * Set whether this servlet should publish a ServletRequestHandledEvent at the end * of each request. Default is "true"; can be turned off for a slight performance * improvement, provided that no ApplicationListeners rely on such events. * @see org.springframework.web.context.support.ServletRequestHandledEvent */ public void setPublishEvents(boolean publishEvents) { this.publishEvents = publishEvents; } /** * Set whether this servlet should dispatch an HTTP OPTIONS request to * the {@link #doService} method. * <p>Default is "false", applying {@link javax.servlet.http.HttpServlet}'s * default behavior (i.e. enumerating all standard HTTP request methods * as a response to the OPTIONS request). * <p>Turn this flag on if you prefer OPTIONS requests to go through the * regular dispatching chain, just like other HTTP requests. This usually * means that your controllers will receive those requests; make sure * that those endpoints are actually able to handle an OPTIONS request. * <p>Note that HttpServlet's default OPTIONS processing will be applied * in any case. Your controllers are simply available to override the * default headers and optionally generate a response body. */ public void setDispatchOptionsRequest(boolean dispatchOptionsRequest) { this.dispatchOptionsRequest = dispatchOptionsRequest; } /** * Set whether this servlet should dispatch an HTTP TRACE request to * the {@link #doService} method. * <p>Default is "false", applying {@link javax.servlet.http.HttpServlet}'s * default behavior (i.e. reflecting the message received back to the client). * <p>Turn this flag on if you prefer TRACE requests to go through the * regular dispatching chain, just like other HTTP requests. This usually * means that your controllers will receive those requests; make sure * that those endpoints are actually able to handle a TRACE request. * <p>Note that HttpServlet's default TRACE processing will be applied * in any case. Your controllers are simply available to override the * default headers and the default body, calling <code>response.reset()</code> * if necessary. */ public void setDispatchTraceRequest(boolean dispatchTraceRequest) { this.dispatchTraceRequest = dispatchTraceRequest; } /** * Overridden method of {@link HttpServletBean}, invoked after any bean properties * have been set. Creates this servlet's WebApplicationContext. */ protected final void initServletBean() throws ServletException, BeansException { getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'"); if (this.logger.isInfoEnabled()) { this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started"); } long startTime = System.currentTimeMillis(); try { this.webApplicationContext = initWebApplicationContext(); initFrameworkServlet(); } catch (ServletException ex) { this.logger.error("Context initialization failed", ex); throw ex; } catch (BeansException ex) { this.logger.error("Context initialization failed", ex); throw ex; } if (this.logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - startTime; this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " + elapsedTime + " ms"); } } /** * Initialize and publish the WebApplicationContext for this servlet. * <p>Delegates to {@link #createWebApplicationContext} for actual creation * of the context. Can be overridden in subclasses. * @return the WebApplicationContext instance * @throws BeansException if the context couldn't be initialized * @see #setContextClass * @see #setContextConfigLocation */ protected WebApplicationContext initWebApplicationContext() throws BeansException { WebApplicationContext wac = findWebApplicationContext(); if (wac == null) { // No fixed context defined for this servlet - create a local one. WebApplicationContext parent = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); wac = createWebApplicationContext(parent); } if (!this.refreshEventReceived) { // Apparently not a ConfigurableApplicationContext with refresh support: // triggering initial onRefresh manually here. onRefresh(wac); } if (this.publishContext) { // Publish the context as a servlet context attribute. String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); if (this.logger.isDebugEnabled()) { this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() + "' as ServletContext attribute with name [" + attrName + "]"); } } return wac; } /** * Retrieve a <code>WebApplicationContext</code> from the <code>ServletContext</code> * attribute with the {@link #setContextAttribute configured name}. The * <code>WebApplicationContext</code> must have already been loaded and stored in the * <code>ServletContext</code> before this servlet gets initialized (or invoked). * <p>Subclasses may override this method to provide a different * <code>WebApplicationContext</code> retrieval strategy. * @return the WebApplicationContext for this servlet, or <code>null</code> if not found * @see #getContextAttribute() */ protected WebApplicationContext findWebApplicationContext() { String attrName = getContextAttribute(); if (attrName == null) { return null; } WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName); if (wac == null) { throw new IllegalStateException("No WebApplicationContext found: initializer not registered?"); } return wac; } /** * Instantiate the WebApplicationContext for this servlet, either a default * {@link org.springframework.web.context.support.XmlWebApplicationContext} * or a {@link #setContextClass custom context class}, if set. * <p>This implementation expects custom contexts to implement the * {@link org.springframework.web.context.ConfigurableWebApplicationContext} * interface. Can be overridden in subclasses. * <p>Do not forget to register this servlet instance as application listener on the * created context (for triggering its {@link #onRefresh callback}, and to call * {@link org.springframework.context.ConfigurableApplicationContext#refresh()} * before returning the context instance. * @param parent the parent ApplicationContext to use, or <code>null</code> if none * @return the WebApplicationContext for this servlet * @throws BeansException if the context couldn't be initialized * @see org.springframework.web.context.support.XmlWebApplicationContext */ protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) throws BeansException { if (this.logger.isDebugEnabled()) { this.logger.debug("Servlet with name '" + getServletName() + "' will try to create custom WebApplicationContext context of class '" + getContextClass().getName() + "'" + ", using parent context [" + parent + "]"); } if (!ConfigurableWebApplicationContext.class.isAssignableFrom(getContextClass())) { throw new ApplicationContextException( "Fatal initialization error in servlet with name '" + getServletName() + "': custom WebApplicationContext class [" + getContextClass().getName() + "] is not of type ConfigurableWebApplicationContext"); } ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(getContextClass()); wac.setParent(parent); wac.setServletContext(getServletContext()); wac.setServletConfig(getServletConfig()); wac.setNamespace(getNamespace()); wac.setConfigLocation(getContextConfigLocation()); wac.addApplicationListener(new SourceFilteringListener(wac, this)); postProcessWebApplicationContext(wac); wac.refresh(); return wac; } /** * Post-process the given WebApplicationContext before it is refreshed * and activated as context for this servlet. * <p>The default implementation is empty. <code>refresh()</code> will * be called automatically after this method returns. * @param wac the configured WebApplicationContext (not refreshed yet) * @see #createWebApplicationContext * @see ConfigurableWebApplicationContext#refresh() */ protected void postProcessWebApplicationContext(ConfigurableWebApplicationContext wac) { } /** * Return the ServletContext attribute name for this servlet's WebApplicationContext. * <p>The default implementation returns * <code>SERVLET_CONTEXT_PREFIX + servlet name</code>. * @see #SERVLET_CONTEXT_PREFIX * @see #getServletName */ public String getServletContextAttributeName() { return SERVLET_CONTEXT_PREFIX + getServletName(); } /** * Return this servlet's WebApplicationContext. */ public final WebApplicationContext getWebApplicationContext() { return this.webApplicationContext; } /** * This method will be invoked after any bean properties have been set and * the WebApplicationContext has been loaded. The default implementation is empty; * subclasses may override this method to perform any initialization they require. * @throws ServletException in case of an initialization exception * @throws BeansException if thrown by ApplicationContext methods */ protected void initFrameworkServlet() throws ServletException, BeansException { } /** * Refresh this servlet's application context, as well as the * dependent state of the servlet. * @throws BeansException in case of errors * @see #getWebApplicationContext() * @see org.springframework.context.ConfigurableApplicationContext#refresh() */ public void refresh() throws BeansException { WebApplicationContext wac = getWebApplicationContext(); if (!(wac instanceof ConfigurableApplicationContext)) { throw new IllegalStateException("WebApplicationContext does not support refresh: " + wac); } ((ConfigurableApplicationContext) wac).refresh(); } /** * ApplicationListener endpoint that receives events from this servlet's * WebApplicationContext. * <p>The default implementation calls {@link #onRefresh} in case of a * {@link org.springframework.context.event.ContextRefreshedEvent}, * triggering a refresh of this servlet's context-dependent state. * @param event the incoming ApplicationContext event */ public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ContextRefreshedEvent) { this.refreshEventReceived = true; onRefresh(((ContextRefreshedEvent) event).getApplicationContext()); } } /** * Template method which can be overridden to add servlet-specific refresh work. * Called after successful context refresh. * <p>This implementation is empty. * @param context the current WebApplicationContext * @throws BeansException in case of errors * @see #refresh() */ protected void onRefresh(ApplicationContext context) throws BeansException { // For subclasses: do nothing by default. } /** * Delegate GET requests to processRequest/doService. * <p>Will also be invoked by HttpServlet's default implementation of <code>doHead</code>, * with a <code>NoBodyResponse</code> that just captures the content length. * @see #doService * @see #doHead */ protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } /** * Delegate POST requests to {@link #processRequest}. * @see #doService */ protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } /** * Delegate PUT requests to {@link #processRequest}. * @see #doService */ protected final void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } /** * Delegate DELETE requests to {@link #processRequest}. * @see #doService */ protected final void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } /** * Delegate OPTIONS requests to {@link #processRequest}, if desired. * <p>Applies HttpServlet's standard OPTIONS processing first. * @see #doService */ protected void doOptions(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { super.doOptions(request, response); if (this.dispatchOptionsRequest) { processRequest(request, response); } } /** * Delegate TRACE requests to {@link #processRequest}, if desired. * <p>Applies HttpServlet's standard TRACE processing first. * @see #doService */ protected void doTrace(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { super.doTrace(request, response); if (this.dispatchTraceRequest) { processRequest(request, response); } } /** * Process this request, publishing an event regardless of the outcome. * <p>The actual event handling is performed by the abstract * {@link #doService} template method. */ protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { long startTime = System.currentTimeMillis(); Throwable failureCause = null; try { doService(request, response); } catch (ServletException ex) { failureCause = ex; throw ex; } catch (IOException ex) { failureCause = ex; throw ex; } catch (Throwable ex) { failureCause = ex; throw new NestedServletException("Request processing failed", ex); } finally { if (failureCause != null) { this.logger.debug("Could not complete request", failureCause); } else { this.logger.debug("Successfully completed request"); } if (this.publishEvents) { // Whether or not we succeeded, publish an event. long processingTime = System.currentTimeMillis() - startTime; this.webApplicationContext.publishEvent( new ServletRequestHandledEvent(this, request.getRequestURI(), request.getRemoteAddr(), request.getMethod(), getServletConfig().getServletName(), WebUtils.getSessionId(request), getUsernameForRequest(request), processingTime, failureCause)); } } } /** * Determine the username for the given request. * <p>The default implementation takes the name of the UserPrincipal, if any. * Can be overridden in subclasses. * @param request current HTTP request * @return the username, or <code>null</code> if none found * @see javax.servlet.http.HttpServletRequest#getUserPrincipal() */ protected String getUsernameForRequest(HttpServletRequest request) { Principal userPrincipal = request.getUserPrincipal(); return (userPrincipal != null ? userPrincipal.getName() : null); } /** * Subclasses must implement this method to do the work of request handling, * receiving a centralized callback for GET, POST, PUT and DELETE. * <p>The contract is essentially the same as that for the commonly overridden * <code>doGet</code> or <code>doPost</code> methods of HttpServlet. * <p>This class intercepts calls to ensure that exception handling and * event publication takes place. * @param request current HTTP request * @param response current HTTP response * @throws Exception in case of any kind of processing failure * @see javax.servlet.http.HttpServlet#doGet * @see javax.servlet.http.HttpServlet#doPost */ protected abstract void doService(HttpServletRequest request, HttpServletResponse response) throws Exception; /** * Close the WebApplicationContext of this servlet. * @see org.springframework.context.ConfigurableApplicationContext#close() */ public void destroy() { getServletContext().log("Destroying Spring FrameworkServlet '" + getServletName() + "'"); if (this.webApplicationContext instanceof ConfigurableApplicationContext) { ((ConfigurableApplicationContext) this.webApplicationContext).close(); } } }