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 * <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 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 }