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