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 * <listener>
035 * <listener-class>org.picocontainer.web.PicoServletContainerListener</listener-class>
036 * </listener>
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 * <context-param>
046 * <param-name>webapp-composer-class</param-name>
047 * <param-value>com.company.MyWebappComposer</param-value>
048 * </context-param>
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ø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 }