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