View Javadoc

1   /* LifecycleManager.java - Impl
2    *
3    * Copyright (c)2005 Roscopeco Open Technologies & Contributors
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   *
17   * File version: $Revision: 1.10 $ $Date: 2005/08/23 14:20:13 $
18   * Originated: 02-Jul-2005
19   * Author: Ross Bamford (rosco<at>roscopeco.co.uk)
20   */
21  
22  package org.roscopeco.janno.core;
23  
24  import javax.servlet.ServletContext;
25  import javax.servlet.ServletRequest;
26  import javax.servlet.http.HttpServletRequest;
27  import javax.servlet.http.HttpSession;
28  
29  import java.io.File;
30  
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.codehaus.classworlds.ClassRealm;
34  import org.codehaus.classworlds.ClassWorld;
35  import org.codehaus.classworlds.DuplicateRealmException;
36  import org.nanocontainer.integrationkit.ContainerBuilder;
37  import org.picocontainer.PicoContainer;
38  import org.picocontainer.defaults.DefaultPicoContainer;
39  
40  import org.roscopeco.moxy.builders.MoxyContainerBuilderFactory;
41  import org.roscopeco.moxy.core.MoxyContext;
42  import org.roscopeco.moxy.defaults.DefaultMoxyContext;
43  import org.roscopeco.moxy.util.BuilderUtils;
44  import org.roscopeco.moxy.util.ContextWalker;
45  import org.roscopeco.moxy.util.PicoContainerStack;
46  import org.roscopeco.moxy.util.PicoPathWalker;
47  
48  import static org.roscopeco.janno.core.CoreConstants.*;
49  import static org.roscopeco.janno.core.JannoLifecycleListener.warnOnce;
50  
51  /*** 
52   * Internal stateless static class with lifecycle management methods.
53   * This class has no instances and no non-final or dynamic fields.
54   * It does hold a shared <code>static final</code> commons logger,
55   * obtained during <code>&lt;clinit&gt;</code>. It uses the logger
56   * defined for the {@link JannoLifecycleListener} class.
57   *
58   * @author Ross Bamford (rosco&lt;at&gt;roscopeco.co.uk)
59   * @version $Revision: 1.10 $ $Date: 2005/08/23 14:20:13 $ 
60   */
61  // TODO Jetty inits the request and then the session on the first request! :(
62  final class LifecycleManager
63  {
64    static Log log = LogFactory.getLog(JannoLifecycleListener.class); /* hide */  
65    static final boolean logRequests = (System.getProperty(DEBUG_LOG_REQUESTS) != null) &&
66    (System.getProperty(DEBUG_LOG_REQUESTS).toLowerCase().equals("true"));
67  
68    /* **********************************
69     * PER-EVENT LIFECYCLE
70     * *********************************/
71    static void applicationSetup(ServletContext ctx) throws DuplicateRealmException {
72      if (log.isDebugEnabled())
73        log.debug("Starting application setup with ServletContext "+ctx.getClass().getName()+"@"+System.identityHashCode(ctx)+" '"+ctx.getServletContextName()+"'");
74      
75      PicoContainer p = buildAndStartApplicationContext(ctx,buildAndStartServerTree(ctx));
76      ctx.setAttribute(CONTEXT_KEY,p);
77      
78      if (log.isDebugEnabled()) {
79        log.debug("Application container is "+p.getClass().getName()+"@"+System.identityHashCode(p));
80        log.debug("Application setup complete (ServletContext "+ctx.getClass().getName()+"@"+System.identityHashCode(ctx)+" '"+ctx.getServletContextName()+"')");
81      }
82    }
83    
84    static void applicationCleanup(ServletContext ctx) {
85      // checking first to avoid the string manipulation...
86      if (log.isDebugEnabled())
87        log.debug("Starting application cleanup with ServletContext "+ctx.getClass().getName()+"@"+System.identityHashCode(ctx)+" '"+ctx.getServletContextName()+"'");
88      PicoContainer p = (PicoContainer)ctx.getAttribute(CONTEXT_KEY);
89      if (p != null) {
90        p.stop();
91        p.dispose();
92        ctx.removeAttribute(CONTEXT_KEY);          
93      } else {
94        log.warn("Application context has no container?");
95      }    
96      if (log.isDebugEnabled())
97        log.debug("Application cleanup complete (ServletContext "+ctx.getClass().getName()+"@"+System.identityHashCode(ctx)+" '"+ctx.getServletContextName()+"')");
98    }
99    
100   static void sessionSetup(HttpSession sess) {
101     if (log.isDebugEnabled())
102       log.debug("Starting session setup with HttpSession "+sess.getClass().getName()+"@"+System.identityHashCode(sess)+" '"+sess.getId()+"'");
103     // con with app con as parent.
104     PicoContainer parent = 
105       (PicoContainer)sess.getServletContext().getAttribute(CONTEXT_KEY);
106     if (parent == null) {
107       Exception e = new MissingContextException(CONTEXT_KEY);
108       log.error("Missing application context",e);
109       throw(new RuntimeException(e));
110     }
111     
112     // Use SESSION_BUILDER if is one, or have a default pico container if not.
113     ContainerBuilder b = (ContainerBuilder)parent.getComponentInstance(SESSION_BUILDER);
114     if (b == null) warnOnce("No Session-scoped builder; using default container");
115 
116     if (log.isDebugEnabled())
117       log.debug("Will use parent "+parent.getClass().getName()+"@"+System.identityHashCode(parent));
118     PicoContainer p = null;
119     if (b != null) { 
120       if (log.isDebugEnabled())
121         log.debug("Building session container "+b.getClass().getName()+"@"+System.identityHashCode(b));
122       p = BuilderUtils.buildContainer(b,parent,sess);
123     } else {
124       log.debug("Building DEFAULT session container");
125       p = new DefaultPicoContainer(parent);      
126     }
127 
128     log.debug("STARTING SESSION CONTAINER");
129     p.start();
130     sess.setAttribute(SESSION_KEY,p);
131     if (log.isDebugEnabled()) {
132       log.debug("Session container is "+p.getClass().getName()+"@"+System.identityHashCode(p));
133       log.debug("Session setup complete (HttpSession "+sess.getClass().getName()+"@"+System.identityHashCode(sess)+" '"+sess.getId()+"')");
134     }
135   }
136   
137   static void sessionCleanup(HttpSession sess) {
138     if (log.isDebugEnabled())
139       log.debug("Starting session cleanup with HttpSession "+sess.getClass().getName()+"@"+System.identityHashCode(sess)+" '"+sess.getId()+"'");
140 
141     PicoContainer p = (PicoContainer)sess.getAttribute(SESSION_KEY);
142     if (p != null) {
143       if (log.isDebugEnabled())
144         log.debug("Found session container "+p.getClass().getName()+"@"+System.identityHashCode(p));
145       log.debug("STOPPING SESSION CONTAINER");
146       p.stop();
147       p.dispose();
148       sess.removeAttribute(SESSION_KEY);          
149       log.debug("Container removed from session");
150     } else {
151       log.warn("Session (ID "+sess.getId()+") has no container?");
152     }    
153 
154     if (log.isDebugEnabled()) {
155       log.debug("Session cleanup complete (HttpSession "+sess.getClass().getName()+"@"+System.identityHashCode(sess)+" '"+sess.getId()+"')");
156     }
157   }  
158 
159   static final void requestSetup(ServletRequest req, ServletContext app) {
160     if (log.isDebugEnabled())
161       log.debug("Starting request setup with ServletRequest "+req.getClass().getName()+"@"+System.identityHashCode(req)+" '"+req.getRemoteAddr()+"'");
162 
163     // App 'tainer, or Session 'tainer if one exists...
164     PicoContainer parent = (PicoContainer)app.getAttribute(CONTEXT_KEY);
165     if (parent == null) {
166       Exception e = new MissingContextException(CONTEXT_KEY);
167       log.error("Missing application context",e);
168       throw(new RuntimeException(e));
169     }
170 
171     if (req instanceof HttpServletRequest) {
172       HttpSession sess = ((HttpServletRequest)req).getSession(false);
173       if (sess != null) {
174         PicoContainer t = (PicoContainer)sess.getAttribute(SESSION_KEY);
175         if (t != null) {
176           parent = t;
177           if (log.isDebugEnabled())
178             log.debug("+ Found session container "+parent.getClass().getName()+"@"+System.identityHashCode(parent));
179         }
180       }      
181     }
182     
183     if (log.isDebugEnabled()) {
184       log.debug("Will use parent "+parent.getClass().getName()+"@"+System.identityHashCode(parent));
185       log.debug("Making new request stack...");
186     }
187     
188     // Make stack 
189     PicoContainerStack rs = new PicoContainerStack(parent);
190     
191     // Use REQUEST_BUILDER if is one, or have a default pico container if not.
192     ContainerBuilder b = (ContainerBuilder)parent.getComponentInstance(REQUEST_BUILDER);
193     if (b == null) warnOnce("No Request-scoped builder; using default container");
194     
195     // Push default if no builder, or new from builder
196     if (b == null) {
197       log.debug("Pushing new empty container");
198       rs.pushNew();
199     } else {
200       log.debug("Building request container "+b.getClass().getName()+"@"+System.identityHashCode(b));
201       rs.pushNew(b,req);
202     }
203 
204     if (log.isDebugEnabled())
205       log.debug("Stack initialized ("+rs.getClass().getName()+"@"+System.identityHashCode(rs)+"; with "+rs.size()+" container(s)");
206     // start stack (thus req container) and set it in request
207     log.debug("STARTING REQUEST STACK");
208     rs.start();
209     req.setAttribute(REQUEST_KEY,rs);
210 
211     if (log.isDebugEnabled()) {
212       log.debug("Started and registered");
213       log.debug("Request setup complete (ServletRequest "+req.getClass().getName()+"@"+System.identityHashCode(req)+" '"+req.getRemoteAddr()+"')");
214     }
215   }
216   
217   static final void requestCleanup(ServletRequest req) {
218     if (log.isDebugEnabled())
219       log.debug("Starting request cleanup (ServletRequest "+req.getClass().getName()+"@"+System.identityHashCode(req)+" '"+req.getRemoteAddr()+"')");
220     
221     PicoContainerStack p = (PicoContainerStack)req.getAttribute(REQUEST_KEY);
222     if (p != null) {
223       if (log.isDebugEnabled())
224         log.debug("Found request container "+p.getClass().getName()+"@"+System.identityHashCode(p));
225       log.debug("STOPPING REQUEST STACK");
226       p.stop();
227       p.dispose();
228       req.removeAttribute(REQUEST_KEY);          
229     } else {
230       log.warn("Request (ID "+req.toString()+") has no container?");
231     }        
232     
233     if (log.isDebugEnabled()) {
234       log.debug("Request cleanup complete (ServletRequest "+req.getClass().getName()+"@"+System.identityHashCode(req)+" '"+req.getRemoteAddr()+"')");
235     }    
236   }
237   
238   /* **********************************
239    * BUILDERS AND UTIL
240    * *********************************/
241   /* Top-level, builds the server, modules, and all that bumpf. */
242   private static final MoxyContext buildAndStartServerTree(ServletContext ctx) {
243     ClassRealm sysRealm;
244     MoxyContext server;
245     
246     log.info("Initialising Server context");
247     
248     // TODO this needs cleaning up, exception wise
249     try {
250       // We want the 'server' realm to be parented by our webapp classloader. Thus, the
251       // Moxy 'system' exists entirely within the webapp bubble, which is good.
252       sysRealm = new ClassWorld().newRealm("server",LifecycleManager.class.getClassLoader());
253       server = new DefaultMoxyContext(ROOT_NAME,sysRealm);
254       
255       // Register up implicit comps
256       server.registerComponentInstance(ContextWalker.class,new PicoPathWalker(server)); // hmm...
257       server.registerComponentInstance(CoreConfig.class, 
258           new CoreConfigImpl(new File(ctx.getRealPath("/")),(File)null,(File)null,ctx,server,
259               LogFactory.getLog("Janno: Runtime core")));      
260       
261       MoxyContext mod = doModuleContextBuildNoStart(ctx,server);  // auto registers both ways
262       if (mod == null) {} // stops warnings
263       
264       log.debug("Starting context tree...");
265       server.start(); /* will start modules too */      
266       log.debug("Started");
267       return server;
268       
269     } catch (DuplicateRealmException e) {
270       throw(new InternalError(e+" with new world"));      
271     }
272   }
273   
274   /* Top level, called after buildAndStartServerTree to make a context for the 
275    * Application (servlet) Context.
276    */
277   private static final MoxyContext buildAndStartApplicationContext(ServletContext ctx, MoxyContext parent) 
278   throws DuplicateRealmException, MissingParameterException {
279     log.info("Building Application context");
280     String script = ctx.getInitParameter(APP_SCRIPT_PARAM);
281     if (script == null) throw(new MissingParameterException(APP_SCRIPT_PARAM));
282     log.debug("Using script: "+script);
283     MoxyContext ctxt = doCompositionFromScript(ctx,parent,script);
284     ctxt.start();
285     return ctxt;
286   }  
287   
288   /*
289    * TODO This should be a single method (with ^--) that builds / starts any context 
290    *      accepting maybe the name of the init param.
291    */ 
292   private static final MoxyContext doModuleContextBuildNoStart(
293       ServletContext ctx, MoxyContext parent) 
294   throws DuplicateRealmException, MissingParameterException {
295     log.info("Building Module context");
296     String script = ctx.getInitParameter(MODULE_SCRIPT_PARAM);
297     if (script == null) throw(new MissingParameterException(MODULE_SCRIPT_PARAM));
298     log.debug("Using script: "+script);
299     return doCompositionFromScript(ctx,parent,script);
300   }
301   
302   /* called by the above to do the actual composition */
303   private static final MoxyContext doCompositionFromScript(
304       ServletContext ctx, MoxyContext parent, String script) {
305     MoxyContext c;
306     try {
307       c = (MoxyContext)MoxyContainerBuilderFactory.
308           buildContainer(ctx.getResource(script),parent,ctx);
309     } catch (ClassCastException e) {
310       throw(new FatalStartupException("Composition \""+script+"\" returns non-MoxyContext"));      
311     } catch (Exception e) {
312       throw(new FatalStartupException("Exception in composition \""+script+"\"",e));
313     }
314     return c;    
315   }
316 }