001    /*******************************************************************************
002     * Copyright (C) PicoContainer Organization. All rights reserved.
003     * --------------------------------------------------------------------------
004     * The software in this package is published under the terms of the BSD style
005     * license a copy of which has been included with this distribution in the
006     * LICENSE.txt file.
007     ******************************************************************************/
008    package org.picocontainer.web;
009    
010    import java.io.Serializable;
011    
012    import javax.servlet.ServletContext;
013    import javax.servlet.ServletContextEvent;
014    import javax.servlet.ServletContextListener;
015    import javax.servlet.http.HttpSession;
016    import javax.servlet.http.HttpSessionEvent;
017    import javax.servlet.http.HttpSessionListener;
018    
019    import org.picocontainer.DefaultPicoContainer;
020    import org.picocontainer.PicoCompositionException;
021    import org.picocontainer.PicoContainer;
022    import org.picocontainer.BehaviorFactory;
023    import org.picocontainer.LifecycleStrategy;
024    import org.picocontainer.ComponentMonitor;
025    import org.picocontainer.MutablePicoContainer;
026    import org.picocontainer.monitors.NullComponentMonitor;
027    import org.picocontainer.lifecycle.StartableLifecycleStrategy;
028    import org.picocontainer.containers.EmptyPicoContainer;
029    import org.picocontainer.behaviors.Storing;
030    import org.picocontainer.behaviors.Guarding;
031    import org.picocontainer.behaviors.Caching;
032    
033    /**
034     * Servlet listener class that hooks into the underlying servlet container and
035     * instantiates, assembles, starts, stores and disposes the appropriate pico
036     * containers when applications/sessions start/stop.
037     * <p>
038     * To use, simply add as a listener to the web.xml the listener-class
039     * 
040     * <pre>
041     * &lt;listener&gt;
042     *  &lt;listener-class&gt;org.picocontainer.web.PicoServletContainerListener&lt;/listener-class&gt;
043     * &lt;/listener&gt; 
044     * </pre>
045     * 
046     * </p>
047     * <p>
048     * The listener also requires a the class name of the
049     * {@link org.picocontainer.web.WebappComposer} as a context-param in web.xml:
050     * 
051     * <pre>
052     *  &lt;context-param&gt;
053     *   &lt;param-name&gt;webapp-composer-class&lt;/param-name&gt;
054     *   &lt;param-value&gt;com.company.MyWebappComposer&lt;/param-value&gt;
055     *  &lt;/context-param&gt;
056     * </pre>
057     * 
058     * The composer will be used to compose the components for the different webapp
059     * scopes after the context has been initialised.
060     * </p>
061     * 
062     * @author Joe Walnes
063     * @author Aslak Helles&oslash;y
064     * @author Philipp Meier
065     * @author Paul Hammant
066     * @author Mauro Talevi
067     * @author Konstantin Pribluda
068     */
069    @SuppressWarnings("serial")
070    public class PicoServletContainerListener implements ServletContextListener, HttpSessionListener, Serializable {
071    
072        public static final String WEBAPP_COMPOSER_CLASS = "webapp-composer-class";
073    
074        public static final String STATELESS_WEBAPP = "stateless-webapp";
075    
076        public static final String PRINT_SESSION_SIZE = "print-session-size";
077    
078        private boolean isStateless;
079        /**
080         * Default constructor used in webapp containers
081         */
082        public PicoServletContainerListener() {
083        }
084    
085        public void contextInitialized(final ServletContextEvent event) {
086            ServletContext context = event.getServletContext();
087            isStateless = Boolean.parseBoolean(context.getInitParameter(STATELESS_WEBAPP));        
088            ScopedContainers scopedContainers = makeScopedContainers(isStateless);
089            scopedContainers.getApplicationContainer().setName("application");
090            if (!isStateless) {
091                scopedContainers.getSessionContainer().setName("session");
092            }
093            scopedContainers.getRequestContainer().setName("request");
094            compose(loadComposer(context), context, scopedContainers);
095            start(scopedContainers.getApplicationContainer());
096            context.setAttribute(ScopedContainers.class.getName(), scopedContainers);
097        }
098    
099        @Deprecated
100        protected ScopedContainers makeScopedContainers() {
101            return makeScopedContainers(false);
102        }
103    
104        /**
105         * Overide this method if you need a more specialized container tree.
106         * Here is the default block of code for this -
107         *
108         *   DefaultPicoContainer appCtnr = new DefaultPicoContainer(new Guarding().wrap(new Caching()), makeLifecycleStrategy(), makeParentContainer(), makeAppComponentMonitor());
109         *   DefaultPicoContainer sessCtnr;
110         *   PicoContainer parentOfRequestContainer;
111         *   ThreadLocalLifecycleState sessionState;
112         *   Storing sessStoring;
113         *   if (stateless) {
114         *       sessionState = null;
115         *       sessStoring = null;
116         *       sessCtnr = null;
117         *       parentOfRequestContainer = appCtnr;
118         *   } else {
119         *       sessionState = new ThreadLocalLifecycleState();
120         *       sessStoring = new Storing();
121         *       sessCtnr = new DefaultPicoContainer(new Guarding().wrap(sessStoring), makeLifecycleStrategy(), appCtnr, makeSessionComponentMonitor());
122         *       sessCtnr.setLifecycleState(sessionState);
123         *       parentOfRequestContainer = sessCtnr;
124         *   }
125         *   Storing reqStoring = new Storing();
126         *   DefaultPicoContainer reqCtnr = new DefaultPicoContainer(new Guarding().wrap(addRequestBehaviors(reqStoring)), makeLifecycleStrategy(), sessCtnr, makeRequestComponentMonitor());
127         *   ThreadLocalLifecycleState requestState = new ThreadLocalLifecycleState();
128         *   reqCtnr.setLifecycleState(requestState);
129         *   return new ScopedContainers(appCtnr, sessCtnr, reqCtnr, sessStoring, reqStoring, sessionState, requestState);
130         *
131         * @param stateless
132         * @return
133         */
134        protected ScopedContainers makeScopedContainers(boolean stateless) {
135            DefaultPicoContainer appCtnr = new DefaultPicoContainer(new Guarding().wrap(new Caching()), makeLifecycleStrategy(), makeParentContainer(), makeAppComponentMonitor());
136            DefaultPicoContainer sessCtnr;
137            PicoContainer parentOfRequestContainer;
138            ThreadLocalLifecycleState sessionState;
139            Storing sessStoring;
140            if (stateless) {
141                sessionState = null;
142                sessStoring = null;
143                sessCtnr = null;
144                parentOfRequestContainer = appCtnr;
145            } else {
146                sessionState = new ThreadLocalLifecycleState();
147                sessStoring = new Storing();
148                sessCtnr = new DefaultPicoContainer(new Guarding().wrap(sessStoring), makeLifecycleStrategy(), appCtnr, makeSessionComponentMonitor());
149                sessCtnr.setLifecycleState(sessionState);
150                parentOfRequestContainer = sessCtnr;
151            }
152            Storing reqStoring = new Storing();
153            DefaultPicoContainer reqCtnr = new DefaultPicoContainer(new Guarding().wrap(addRequestBehaviors(reqStoring)), makeLifecycleStrategy(), parentOfRequestContainer, makeRequestComponentMonitor());
154            ThreadLocalLifecycleState requestState = new ThreadLocalLifecycleState();
155            reqCtnr.setLifecycleState(requestState);
156            return new ScopedContainers(appCtnr, sessCtnr, reqCtnr, sessStoring, reqStoring, sessionState, requestState);
157        }
158    
159        protected PicoContainer makeParentContainer() {
160            return new EmptyPicoContainer();
161        }
162    
163        protected LifecycleStrategy makeLifecycleStrategy() {
164            return new StartableLifecycleStrategy(makeRequestComponentMonitor());
165        }
166    
167        protected ComponentMonitor makeAppComponentMonitor() {
168            return new NullComponentMonitor();
169        }
170    
171        protected ComponentMonitor makeSessionComponentMonitor() {
172            return new NullComponentMonitor();
173        }
174    
175        protected ComponentMonitor makeRequestComponentMonitor() {
176            return new NullComponentMonitor();
177        }
178    
179        protected BehaviorFactory addRequestBehaviors(BehaviorFactory beforeThisBehaviorFactory) {
180            return beforeThisBehaviorFactory;
181        }
182    
183        /**
184         * Get the class to do compostition with - from a "webapp-composer-class" config param
185         * from web.xml :
186         *
187         *   <context-param>
188         *       <param-name>webapp-composer-class</param-name>
189         *       <param-value>com.yourcompany.YourWebappComposer</param-value>
190         *   </context-param>
191         *
192         * @param context
193         * @return
194         */
195        protected WebappComposer loadComposer(ServletContext context) {
196            String composerClassName = context.getInitParameter(WEBAPP_COMPOSER_CLASS);
197            try {
198                return (WebappComposer) Thread.currentThread().getContextClassLoader().loadClass(composerClassName)
199                        .newInstance();
200            } catch (Exception e) {
201                throw new PicoCompositionException("Failed to load webapp composer class " + composerClassName
202                        + ": ensure the context-param '" + WEBAPP_COMPOSER_CLASS + "' is configured in the web.xml.", e);
203            }
204        }
205    
206        protected void compose(WebappComposer composer, ServletContext context, ScopedContainers scopedContainers) {
207            composer.composeApplication(scopedContainers.getApplicationContainer(), context);
208            if (!isStateless) {
209                composer.composeSession(scopedContainers.getSessionContainer());
210            }
211            composer.composeRequest(scopedContainers.getRequestContainer());
212        }
213    
214        public void contextDestroyed(ServletContextEvent event) {
215            ScopedContainers scopedContainers = getScopedContainers(event.getServletContext());
216            stop(scopedContainers.getApplicationContainer());
217            dispose(scopedContainers.getApplicationContainer());
218        }
219    
220        private void start(MutablePicoContainer container) {
221            container.start();
222        }
223    
224        private void dispose(MutablePicoContainer container) {
225            container.dispose();
226        }
227    
228        private void stop(MutablePicoContainer container) {
229            container.stop();
230        }
231    
232        private ScopedContainers getScopedContainers(ServletContext context) {
233            return (ScopedContainers) context.getAttribute(ScopedContainers.class.getName());
234        }
235    
236        public void sessionCreated(HttpSessionEvent event) {
237            if (!isStateless) {
238                HttpSession session = event.getSession();
239                ScopedContainers scopedContainers = getScopedContainers(session.getServletContext());
240                SessionStoreHolder ssh = new SessionStoreHolder(scopedContainers.getSessionStoring().resetCacheForThread(), scopedContainers.getSessionState().resetStateModelForThread());
241                start(scopedContainers.getSessionContainer());
242                session.setAttribute(SessionStoreHolder.class.getName(), ssh);
243            }
244        }
245    
246        public void sessionDestroyed(HttpSessionEvent event) {
247            if (!isStateless) {
248                HttpSession session = event.getSession();
249                ScopedContainers scopedContainers = getScopedContainers(session.getServletContext());
250                MutablePicoContainer sessionCtr = scopedContainers.getSessionContainer();
251                SessionStoreHolder ssh = (SessionStoreHolder) session.getAttribute(SessionStoreHolder.class.getName());
252                scopedContainers.getSessionStoring().putCacheForThread(ssh.getStoreWrapper());
253                scopedContainers.getSessionState().putLifecycleStateModelForThread(ssh.getLifecycleState());
254                stop(sessionCtr);
255                dispose(sessionCtr);
256                scopedContainers.getSessionStoring().invalidateCacheForThread();
257                scopedContainers.getSessionState().invalidateStateModelForThread();
258                session.setAttribute(SessionStoreHolder.class.getName(), null);
259            }
260        }
261    
262    }