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 }