View Javadoc

1   package com.atlassian.johnson.spring.web.servlet;
2   
3   import com.atlassian.johnson.Johnson;
4   import com.atlassian.johnson.JohnsonEventContainer;
5   import com.atlassian.johnson.event.Event;
6   import com.atlassian.johnson.event.EventLevel;
7   import com.atlassian.johnson.event.EventType;
8   import com.atlassian.johnson.spring.web.SpringEventType;
9   import com.atlassian.johnson.spring.web.context.JohnsonContextLoaderListener;
10  import org.slf4j.Logger;
11  import org.slf4j.LoggerFactory;
12  import org.springframework.core.Conventions;
13  import org.springframework.web.context.WebApplicationContext;
14  import org.springframework.web.servlet.DispatcherServlet;
15  
16  import javax.servlet.ServletConfig;
17  import javax.servlet.ServletContext;
18  
19  /**
20   * Extends the standard Spring {@code DispatcherServlet} to make it Johnson-aware. When using this class, if the root
21   * Spring context fails to start the dispatcher will not attempt to create/start its child context.
22   * <p/>
23   * The goal of this class is to prevent the web application from being shutdown if SpringMVC cannot be started. By
24   * default, if the dispatcher's {@code WebApplicationContext} cannot be started for any reason, an exception is thrown
25   * which is propagated up to the container. When this happens, the entire web application is terminated. This precludes
26   * the use of Johnson, which requires that the web application be up so that it can serve its status pages.
27   */
28  public class JohnsonDispatcherServlet extends DispatcherServlet
29  {
30      public static final String BYPASSED_PREFIX = Conventions.getQualifiedAttributeName(JohnsonDispatcherServlet.class, "bypassed");
31  
32      private static final Logger LOG = LoggerFactory.getLogger(JohnsonDispatcherServlet.class);
33  
34      /**
35       * Performs standard SpringMVC {@code DispatcherServlet} teardown and ensures any attributes added to the servlet
36       * context for this dispatcher are removed.
37       */
38      @Override
39      public void destroy()
40      {
41          try
42          {
43              super.destroy();
44          }
45          finally
46          {
47              getServletContext().removeAttribute(getBypassedAttributeName());
48          }
49      }
50  
51      /**
52       * Overrides the standard SpringMVC {@code DispatcherServlet} initialisation to make it Johnson-aware, allowing it
53       * to be automatically bypassed, when paired with a {@link JohnsonContextLoaderListener}, or add an {@link Event}
54       * if initialisation fails.
55       * <p/>
56       * This implementation will never throw an exception. Unlike the base implementation, though, it may return
57       * {@code null} if initialisation is bypassed (due to the main {@code WebApplicationContext} being bypassed
58       * or failing to initialise) or if initialisation fails.
59       *
60       * @return the initialised context, or {@code null} if initialisation is bypassed or fails
61       */
62      @Override
63      protected WebApplicationContext initWebApplicationContext()
64      {
65          ServletConfig servletConfig = getServletConfig();
66          ServletContext servletContext = getServletContext();
67          JohnsonEventContainer container = Johnson.getEventContainer(servletContext);
68          String eventType = SpringEventType.getServletEventType(servletConfig);
69  
70          Object attribute = servletContext.getAttribute(JohnsonContextLoaderListener.BYPASSED_ATTRIBUTE);
71          //First, check to see if the WebApplicationContext was bypassed. If it was, it's possible, based on
72          //configuration, that no event was added. However, we must bypass SpringMVC initialisation as well,
73          //because no parent context will be available.
74          if (Boolean.TRUE == attribute) //Fully bypassed, without even trying to start
75          {
76              LOG.error("Bypassing SpringMVC dispatcher [{}] initialisation; Spring initialisation was bypassed",
77                          getServletName());
78              servletContext.setAttribute(getBypassedAttributeName(), Boolean.TRUE);
79  
80              if (SpringEventType.addEventOnBypass(servletConfig))
81              {
82                  String message = "SpringMVC dispatcher [" + getServletName() +
83                          "] will not be started because the Spring WebApplicationContext was not started";
84                  container.addEvent(new Event(EventType.get(eventType), message, EventLevel.get(EventLevel.FATAL)));
85              }
86  
87              return null;
88          }
89          //If WebApplicationContext initialisation wasn't bypassed, check to see if it failed. SpringMVC initialisation
90          //is guaranteed to fail if the primary WebApplicationContext failed, so we'll want to bypass it.
91          if (attribute instanceof Event)
92          {
93              Event event = (Event) attribute;
94  
95              LOG.error("Bypassing SpringMVC dispatcher [{}] initialisation; Spring initialisation failed: {}",
96                      getServletName(), event.getDesc());
97              servletContext.setAttribute(getBypassedAttributeName(), Boolean.TRUE);
98  
99              if (SpringEventType.addEventOnBypass(servletConfig))
100             {
101                 String message = "SpringMVC dispatcher [" + getServletName() +
102                         "] will not be started because the Spring WebApplicationContext failed to start";
103                 container.addEvent(new Event(EventType.get(eventType), message, event.getLevel()));
104             }
105 
106             return null;
107         }
108 
109         //If we make it here, the Spring WebApplicationContext should have started successfully. That means it's safe
110         //to try and start this SpringMVC dispatcher.
111         WebApplicationContext context = null;
112         try
113         {
114             LOG.debug("Attempting to initialise the Spring ApplicationContext");
115             context = super.initWebApplicationContext();
116         }
117         catch (Throwable t)
118         {
119             String message = "SpringMVC dispatcher [" + getServletName() + "] could not be started";
120             LOG.error(message, t);
121 
122             Event event = createEvent(eventType, message, t);
123             if (event == null)
124             {
125                 //When derived classes misbehave on creating the event, fallback internally on our own implementation
126                 event = createDefaultEvent(eventType, message, t);
127             }
128             servletContext.setAttribute(getBypassedAttributeName(), event);
129 
130             container.addEvent(event);
131         }
132         return context;
133     }
134 
135     /**
136      * May be overridden in derived classes to allow them to override the default event type or message based on
137      * application-specific understanding of the exception that was thrown.
138      * <p/>
139      * For cases where derived classes are not able to offer a more specific event type or message, they are
140      * encouraged to fall back on the behaviour of this superclass method.
141      *
142      * @param defaultEventType the default event type to use if no more specific type is appropriate
143      * @param defaultMessage   the default message to use if no more specific message is available
144      * @param t                the exception thrown while attempting to initialise the WebApplicationContext
145      * @return the event to add to Johnson, which may not be {@code null}
146      */
147     protected Event createEvent(String defaultEventType, String defaultMessage, Throwable t)
148     {
149         return createDefaultEvent(defaultEventType, defaultMessage, t);
150     }
151 
152     /**
153      * Allows derived classes to override the name of the attribute which is added to the {@code ServletContext} when
154      * this dispatcher is bypassed (whether due to the {@link JohnsonContextLoaderListener} being bypassed or due to
155      * the initialisation of the dispatcher failing).
156      * <p/>
157      * The default attribute name is: "{@link #BYPASSED_PREFIX}:{@code getServletName()}"
158      *
159      * @return the name for the bypassed context attribute
160      */
161     protected String getBypassedAttributeName()
162     {
163         return BYPASSED_PREFIX + ":" + getServletName();
164     }
165 
166     /**
167      * A fail-safe event creator which cannot be overridden, allowing {@link #initWebApplicationContext()} something
168      * with reliable semantics to fall back on when {@link #createEvent(String, String, Throwable)} overrides return
169      * {@code null} against its contract.
170      *
171      * @param eventType the event type to use
172      * @param message   the message to use
173      * @param t         the exception thrown while attempting to initialise the WebApplicationContext
174      * @return the event to add to Johnson, which will never be {@code null}
175      */
176     private Event createDefaultEvent(String eventType, String message, Throwable t)
177     {
178         return new Event(EventType.get(eventType), message, Event.toString(t), EventLevel.get(EventLevel.FATAL));
179     }
180 }