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