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.containers.EmptyPicoContainer;
023    import org.picocontainer.behaviors.Caching;
024    import org.picocontainer.behaviors.Storing;
025    
026    /**
027     * Servlet listener class that hooks into the underlying servlet container and
028     * instantiates, assembles, starts, stores and disposes the appropriate pico
029     * containers when applications/sessions start/stop.
030     * <p>
031     * To use, simply add as a listener to the web.xml the listener-class
032     * 
033     * <pre>
034     * &lt;listener&gt;
035     *  &lt;listener-class&gt;org.picocontainer.web.PicoServletContainerListener&lt;/listener-class&gt;
036     * &lt;/listener&gt; 
037     * </pre>
038     * 
039     * </p>
040     * <p>
041     * The listener also requires a the class name of the
042     * {@link org.picocontainer.web.WebappComposer} as a context-param in web.xml:
043     * 
044     * <pre>
045     *  &lt;context-param&gt;
046     *   &lt;param-name&gt;webapp-composer-class&lt;/param-name&gt;
047     *   &lt;param-value&gt;com.company.MyWebappComposer&lt;/param-value&gt;
048     *  &lt;/context-param&gt;
049     * </pre>
050     * 
051     * The composer will be used to compose the components for the different webapp
052     * scopes after the context has been initialised.
053     * </p>
054     * 
055     * @author Joe Walnes
056     * @author Aslak Helles&oslash;y
057     * @author Philipp Meier
058     * @author Paul Hammant
059     * @author Mauro Talevi
060     * @author Konstantin Pribluda
061     */
062    @SuppressWarnings("serial")
063    public class PicoServletContainerListener implements ServletContextListener, HttpSessionListener, Serializable {
064    
065        public static final String WEBAPP_COMPOSER_CLASS = "webapp-composer-class";
066        private DefaultPicoContainer applicationContainer;
067        private DefaultPicoContainer sessionContainer;
068        private DefaultPicoContainer requestContainer;
069        private Storing sessionStoring;
070        private Storing requestStoring;
071        private boolean useCompositionClass;
072    
073        /**
074         * Default constructor used in webapp containers
075         */
076        public PicoServletContainerListener() {
077            applicationContainer = new DefaultPicoContainer(new Caching(), makeParentContainer());
078            sessionStoring = new Storing();
079            sessionContainer = new DefaultPicoContainer(sessionStoring, applicationContainer);
080            requestStoring = new Storing();
081            requestContainer = new DefaultPicoContainer(requestStoring, sessionContainer);
082            useCompositionClass = true;
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            ServletContext context = event.getServletContext();
112            applicationContainer.setName("application");
113    
114            context.setAttribute(ApplicationContainerHolder.class.getName(), new ApplicationContainerHolder(
115                    applicationContainer));
116    
117            sessionContainer.setName("session");
118            ThreadLocalLifecycleState sessionStateModel = new ThreadLocalLifecycleState();
119            sessionContainer.setLifecycleState(sessionStateModel);
120    
121            context.setAttribute(SessionContainerHolder.class.getName(), new SessionContainerHolder(sessionContainer,
122                    sessionStoring, sessionStateModel));
123    
124            requestContainer.setName("request");
125            ThreadLocalLifecycleState requestStateModel = new ThreadLocalLifecycleState();
126            requestContainer.setLifecycleState(requestStateModel);
127    
128            context.setAttribute(RequestContainerHolder.class.getName(), new RequestContainerHolder(requestContainer,
129                    requestStoring, requestStateModel));
130    
131            if (useCompositionClass) {
132                compose(loadComposer(context));
133            }
134            applicationContainer.start();
135        }
136    
137        protected WebappComposer loadComposer(ServletContext context) {
138            String composerClassName = context.getInitParameter(WEBAPP_COMPOSER_CLASS);
139            try {
140                return (WebappComposer) Thread.currentThread().getContextClassLoader().loadClass(composerClassName)
141                        .newInstance();
142            } catch (Exception e) {
143                throw new PicoCompositionException("Failed to load webapp composer class " + composerClassName
144                        + ": ensure the context-param '" + WEBAPP_COMPOSER_CLASS + "' is configured in the web.xml.", e);
145            }
146        }
147    
148        protected void compose(WebappComposer composer) {
149            composer.composeApplication(applicationContainer);
150            composer.composeSession(sessionContainer);
151            composer.composeRequest(requestContainer);
152        }
153    
154        public void contextDestroyed(ServletContextEvent event) {
155            applicationContainer.stop();
156            applicationContainer.dispose();
157        }
158    
159        public void sessionCreated(HttpSessionEvent event) {
160            HttpSession session = event.getSession();
161            ServletContext context = session.getServletContext();
162    
163            SessionContainerHolder sch = (SessionContainerHolder) context.getAttribute(SessionContainerHolder.class
164                    .getName());
165            ThreadLocalLifecycleState tlLifecycleState = sch.getLifecycleStateModel();
166            session.setAttribute(SessionStoreHolder.class.getName(), new SessionStoreHolder(sessionStoring
167                    .resetCacheForThread(), tlLifecycleState.resetStateModelForThread()));
168    
169            sessionContainer.start();
170        }
171    
172        public void sessionDestroyed(HttpSessionEvent event) {
173            HttpSession session = event.getSession();
174            ServletContext context = session.getServletContext();
175    
176            SessionStoreHolder ssh = (SessionStoreHolder) session.getAttribute(SessionStoreHolder.class.getName());
177    
178            SessionContainerHolder sch = (SessionContainerHolder) context.getAttribute(SessionContainerHolder.class
179                    .getName());
180            ThreadLocalLifecycleState tlLifecycleState = sch.getLifecycleStateModel();
181    
182            sessionStoring.putCacheForThread(ssh.getStoreWrapper());
183            tlLifecycleState.putLifecycleStateModelForThread(ssh.getDefaultLifecycleState());
184    
185            sessionContainer.stop();
186            sessionContainer.dispose();
187            sessionStoring.invalidateCacheForThread();
188        }
189    
190    }