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