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 = true;
072    
073        /**
074         * Default constructor used in webapp containers
075         */
076        public PicoServletContainerListener() {
077        }
078    
079        /**
080         * Creates a PicoServletContainerListener with dependencies injected
081         * 
082         * @param applicationContainer the application-scoped container
083         * @param sessionContainer the session-scoped container
084         * @param requestContainer the request-scoped container
085         * @param sessionStoring the session storing behaviour
086         * @param requestStoring the request storing behaviour
087         */
088        public PicoServletContainerListener(DefaultPicoContainer applicationContainer,
089                DefaultPicoContainer sessionContainer, DefaultPicoContainer requestContainer, Storing sessionStoring,
090                Storing requestStoring) {
091            this.applicationContainer = applicationContainer;
092            this.sessionContainer = sessionContainer;
093            this.requestContainer = requestContainer;
094            this.sessionStoring = sessionStoring;
095            this.requestStoring = requestStoring;
096            useCompositionClass = false;
097        }
098    
099        protected PicoContainer makeParentContainer() {
100            return new EmptyPicoContainer();
101        }
102    
103        public void contextInitialized(final ServletContextEvent event) {
104    
105            ScopedContainers scopedContainers = makeScopedContainers();
106            applicationContainer = scopedContainers.applicationContainer;
107            sessionContainer = scopedContainers.sessionContainer;
108            requestContainer = scopedContainers.requestContainer;
109            sessionStoring = scopedContainers.sessionStoring;
110            requestStoring = scopedContainers.requestStoring;
111    
112            ServletContext context = event.getServletContext();
113            applicationContainer.setName("application");
114    
115            context.setAttribute(ApplicationContainerHolder.class.getName(), new ApplicationContainerHolder(
116                    applicationContainer));
117    
118            sessionContainer.setName("session");
119            ThreadLocalLifecycleState sessionStateModel = new ThreadLocalLifecycleState();
120            sessionContainer.setLifecycleState(sessionStateModel);
121    
122            context.setAttribute(SessionContainerHolder.class.getName(), new SessionContainerHolder(sessionContainer,
123                    sessionStoring, sessionStateModel));
124    
125            requestContainer.setName("request");
126            ThreadLocalLifecycleState requestStateModel = new ThreadLocalLifecycleState();
127            requestContainer.setLifecycleState(requestStateModel);
128    
129            context.setAttribute(RequestContainerHolder.class.getName(), new RequestContainerHolder(requestContainer,
130                    requestStoring, requestStateModel));
131    
132            if (useCompositionClass) {
133                compose(loadComposer(context));
134            }
135            applicationContainer.start();
136        }
137    
138        /**
139         * Overide this method if you need a more specialized container tree.
140         * Here is the default block of code for this -
141         *
142         *     DefaultPicoContainer appCtnr = new DefaultPicoContainer(new Caching(), makeParentContainer());
143         *     Storing sessStoring = new Storing();
144         *     DefaultPicoContainer sessCtnr = new DefaultPicoContainer(sessStoring, appCtnr);
145         *     Storing reqStoring = new Storing();
146         *     DefaultPicoContainer reqCtnr = new DefaultPicoContainer(reqStoring, sessCtnr);
147         *     return new ScopedContainers(appCtnr,sessCtnr,reqCtnr,sessStoring,reqStoring);
148         *
149         * @return an instance of ScopedContainers
150         */
151        protected ScopedContainers makeScopedContainers() {
152            DefaultPicoContainer appCtnr = new DefaultPicoContainer(new Caching(), makeParentContainer());
153            Storing sessStoring = new Storing();
154            DefaultPicoContainer sessCtnr = new DefaultPicoContainer(sessStoring, appCtnr);
155            Storing reqStoring = new Storing();
156            DefaultPicoContainer reqCtnr = new DefaultPicoContainer(reqStoring, sessCtnr);
157            return new ScopedContainers(appCtnr,sessCtnr,reqCtnr,sessStoring,reqStoring);
158        }
159    
160        /**
161         * Get the class to do compostition with - from a "webapp-composer-class" config param
162         * from web.xml :
163         *
164         *   <context-param>
165         *       <param-name>webapp-composer-class</param-name>
166         *       <param-value>com.yourcompany.YourWebappComposer</param-value>
167         *   </context-param>
168         *
169         * @param context
170         * @return
171         */
172        protected WebappComposer loadComposer(ServletContext context) {
173            String composerClassName = context.getInitParameter(WEBAPP_COMPOSER_CLASS);
174            try {
175                return (WebappComposer) Thread.currentThread().getContextClassLoader().loadClass(composerClassName)
176                        .newInstance();
177            } catch (Exception e) {
178                throw new PicoCompositionException("Failed to load webapp composer class " + composerClassName
179                        + ": ensure the context-param '" + WEBAPP_COMPOSER_CLASS + "' is configured in the web.xml.", e);
180            }
181        }
182    
183        protected void compose(WebappComposer composer) {
184            composer.composeApplication(applicationContainer);
185            composer.composeSession(sessionContainer);
186            composer.composeRequest(requestContainer);
187        }
188    
189        public void contextDestroyed(ServletContextEvent event) {
190            applicationContainer.stop();
191            applicationContainer.dispose();
192        }
193    
194        public void sessionCreated(HttpSessionEvent event) {
195            HttpSession session = event.getSession();
196            ServletContext context = session.getServletContext();
197    
198            SessionContainerHolder sch = (SessionContainerHolder) context.getAttribute(SessionContainerHolder.class
199                    .getName());
200            ThreadLocalLifecycleState tlLifecycleState = sch.getLifecycleStateModel();
201            session.setAttribute(SessionStoreHolder.class.getName(), new SessionStoreHolder(sessionStoring
202                    .resetCacheForThread(), tlLifecycleState.resetStateModelForThread()));
203    
204            sessionContainer.start();
205        }
206    
207        public void sessionDestroyed(HttpSessionEvent event) {
208            HttpSession session = event.getSession();
209            ServletContext context = session.getServletContext();
210    
211            SessionStoreHolder ssh = (SessionStoreHolder) session.getAttribute(SessionStoreHolder.class.getName());
212    
213            SessionContainerHolder sch = (SessionContainerHolder) context.getAttribute(SessionContainerHolder.class
214                    .getName());
215            ThreadLocalLifecycleState tlLifecycleState = sch.getLifecycleStateModel();
216    
217            sessionStoring.putCacheForThread(ssh.getStoreWrapper());
218            tlLifecycleState.putLifecycleStateModelForThread(ssh.getDefaultLifecycleState());
219    
220            sessionContainer.stop();
221            sessionContainer.dispose();
222            sessionStoring.invalidateCacheForThread();
223        }
224    
225        public static class ScopedContainers {
226    
227            private DefaultPicoContainer applicationContainer;
228            private DefaultPicoContainer sessionContainer;
229            private DefaultPicoContainer requestContainer;
230            private Storing sessionStoring;
231            private Storing requestStoring;
232    
233            public ScopedContainers(DefaultPicoContainer applicationContainer, DefaultPicoContainer sessionContainer, DefaultPicoContainer requestContainer, Storing sessionStoring, Storing requestStoring) {
234                this.applicationContainer = applicationContainer;
235                this.sessionContainer = sessionContainer;
236                this.requestContainer = requestContainer;
237                this.sessionStoring = sessionStoring;
238                this.requestStoring = requestStoring;
239            }
240        }
241    
242    
243    }