View Javadoc

1   package com.atlassian.johnson.spring.lifecycle;
2   
3   import com.atlassian.johnson.Johnson;
4   import com.atlassian.johnson.config.JohnsonConfig;
5   import com.atlassian.johnson.spring.web.servlet.JohnsonDispatcherServlet;
6   import org.slf4j.Logger;
7   import org.slf4j.LoggerFactory;
8   import org.springframework.web.context.WebApplicationContext;
9   import org.springframework.web.servlet.DispatcherServlet;
10  
11  import javax.servlet.ServletException;
12  import javax.servlet.http.HttpServlet;
13  import javax.servlet.http.HttpServletRequest;
14  import javax.servlet.http.HttpServletResponse;
15  import java.io.IOException;
16  import java.net.URLEncoder;
17  
18  /**
19   * Initializes SpringMVC on a separate thread, allowing the web application to handle basic requests while the
20   * application is still starting.
21   * <p>
22   * SpringMVC is started <i>standalone</i> by this servlet. It is assumed that no {@code ContextLoaderListener}
23   * has been registered and prepared a root {@code WebApplicationContext} and that the SpringMVC context should
24   * be used as the root.
25   * <p>
26   * When using this servlet, {@link LifecycleDelegatingFilterProxy} and {@link LifecycleHttpRequestHandlerServlet}
27   * should be used in place of Spring's {@code DelegatingFilterProxy} and {@code HttpRequestHandlerServlet}. The
28   * lifecycle versions automatically defer attempting to bind to their associated beans until the application has
29   * {@link LifecycleState#STARTED started}.
30   *
31   * @since 3.0
32   */
33  public class LifecycleDispatcherServlet extends HttpServlet {
34  
35      private static final String PROP_SYNCHRONOUS = "johnson.spring.lifecycle.synchronousStartup";
36  
37      private final Logger log = LoggerFactory.getLogger(getClass());
38  
39      private volatile DispatcherServlet delegate;
40      private volatile Thread startup;
41  
42      @Override
43      public void destroy() {
44          Thread startup = this.startup; //Shadowing intentional
45          if (startup != null) {
46              if (startup.isAlive()) {
47                  //Try to interrupt Spring startup so that the server can terminate
48                  startup.interrupt();
49              }
50  
51              try {
52                  //Wait for Spring startup to either complete or bomb out due to being interrupted. Note that,
53                  //because Spring may still complete its startup despite our attempt to interrupt it, we check
54                  //the delegate DispatcherServlet after cleaning up the startup thread
55                  startup.join();
56              } catch (InterruptedException e) {
57                  log.error("The SpringMVC startup thread could not be joined", e);
58              }
59          }
60  
61          DispatcherServlet delegate = this.delegate; //Shadowing intentional
62          if (delegate != null) {
63              //Shut down Spring
64              delegate.destroy();
65          }
66      }
67  
68      @Override
69      public void init() throws ServletException {
70          Thread thread = new Thread(new Runnable() {
71  
72              @Override
73              public void run() {
74                  JohnsonDispatcherServlet servlet = new JohnsonDispatcherServlet() {
75  
76                      @Override
77                      public String getServletContextAttributeName() {
78                          //Publish our context as the root web application context. This emulates what
79                          //ContextLoaderListener does and is required so that DelegatingFilterProxy and
80                          //HttpRequestHandlerServlet instances can find the context later
81                          return WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
82                      }
83                  };
84  
85                  LifecycleUtils.updateState(getServletContext(), LifecycleState.STARTING);
86                  try {
87                      //This allows webapps to configure the LifecycleDispatcherServlet as if it was the SpringMVC
88                      //DispatcherServlet, accepting all the same parameters, without this class having to manually
89                      //copy them all over
90                      servlet.init(getServletConfig());
91  
92                      delegate = servlet; //If we make it here, SpringMVC has started successfully. Enable the delegate
93                      LifecycleUtils.updateState(getServletContext(), LifecycleState.STARTED);
94                  } catch (Exception e) {
95                      LifecycleUtils.updateState(getServletContext(), LifecycleState.FAILED);
96                      log.error("SpringMVC could not be started", e);
97                  } finally {
98                      startup = null; //We're done running, so remove our reference
99                  }
100             }
101         });
102         thread.setDaemon(true);
103         thread.setName("spring-startup");
104 
105         (startup = thread).start();
106 
107         if (Boolean.getBoolean(PROP_SYNCHRONOUS)) {
108             try {
109                 thread.join();
110             } catch (InterruptedException e) {
111                 Thread.currentThread().interrupt();
112                 log.warn("Interrupted while waiting for synchronous Spring startup to complete");
113             }
114         }
115     }
116 
117     @Override
118     protected void service(HttpServletRequest request, HttpServletResponse response)
119             throws ServletException, IOException {
120         DispatcherServlet delegate = this.delegate; //Shadowing intentional
121         if (delegate == null) {
122             JohnsonConfig config = Johnson.getConfig(getServletContext());
123 
124             String nextUrl = request.getRequestURI();
125 
126             if (request.getQueryString() != null && !request.getQueryString().isEmpty()) {
127                 nextUrl += "?" + request.getQueryString();
128             }
129 
130             String redirectUrl = request.getContextPath() + config.getErrorPath()
131                     + "?next=" + URLEncoder.encode(nextUrl, "UTF-8");
132 
133             response.sendRedirect(redirectUrl);
134         } else {
135             delegate.service(request, response);
136         }
137     }
138 }