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.monitors.NullComponentMonitor;
026 import org.picocontainer.lifecycle.StartableLifecycleStrategy;
027 import org.picocontainer.containers.EmptyPicoContainer;
028 import org.picocontainer.behaviors.Caching;
029 import org.picocontainer.behaviors.Storing;
030 import org.picocontainer.behaviors.Guarding;
031
032 /**
033 * Servlet listener class that hooks into the underlying servlet container and
034 * instantiates, assembles, starts, stores and disposes the appropriate pico
035 * containers when applications/sessions start/stop.
036 * <p>
037 * To use, simply add as a listener to the web.xml the listener-class
038 *
039 * <pre>
040 * <listener>
041 * <listener-class>org.picocontainer.web.PicoServletContainerListener</listener-class>
042 * </listener>
043 * </pre>
044 *
045 * </p>
046 * <p>
047 * The listener also requires a the class name of the
048 * {@link org.picocontainer.web.WebappComposer} as a context-param in web.xml:
049 *
050 * <pre>
051 * <context-param>
052 * <param-name>webapp-composer-class</param-name>
053 * <param-value>com.company.MyWebappComposer</param-value>
054 * </context-param>
055 * </pre>
056 *
057 * The composer will be used to compose the components for the different webapp
058 * scopes after the context has been initialised.
059 * </p>
060 *
061 * @author Joe Walnes
062 * @author Aslak Hellesøy
063 * @author Philipp Meier
064 * @author Paul Hammant
065 * @author Mauro Talevi
066 * @author Konstantin Pribluda
067 */
068 @SuppressWarnings("serial")
069 public class PicoServletContainerListener implements ServletContextListener, HttpSessionListener, Serializable {
070
071 public static final String WEBAPP_COMPOSER_CLASS = "webapp-composer-class";
072 private DefaultPicoContainer applicationContainer;
073 private DefaultPicoContainer sessionContainer;
074 private DefaultPicoContainer requestContainer;
075 private Storing sessionStoring;
076 private Storing requestStoring;
077 private boolean useCompositionClass = true;
078
079 /**
080 * Default constructor used in webapp containers
081 */
082 public PicoServletContainerListener() {
083 }
084
085 /**
086 * Creates a PicoServletContainerListener with dependencies injected
087 *
088 * @param applicationContainer the application-scoped container
089 * @param sessionContainer the session-scoped container
090 * @param requestContainer the request-scoped container
091 * @param sessionStoring the session storing behaviour
092 * @param requestStoring the request storing behaviour
093 */
094 public PicoServletContainerListener(DefaultPicoContainer applicationContainer,
095 DefaultPicoContainer sessionContainer, DefaultPicoContainer requestContainer, Storing sessionStoring,
096 Storing requestStoring) {
097 this.applicationContainer = applicationContainer;
098 this.sessionContainer = sessionContainer;
099 this.requestContainer = requestContainer;
100 this.sessionStoring = sessionStoring;
101 this.requestStoring = requestStoring;
102 useCompositionClass = false;
103 }
104
105 protected PicoContainer makeParentContainer() {
106 return new EmptyPicoContainer();
107 }
108
109 public void contextInitialized(final ServletContextEvent event) {
110
111 ScopedContainers scopedContainers = makeScopedContainers();
112 applicationContainer = scopedContainers.applicationContainer;
113 sessionContainer = scopedContainers.sessionContainer;
114 requestContainer = scopedContainers.requestContainer;
115 sessionStoring = scopedContainers.sessionStoring;
116 requestStoring = scopedContainers.requestStoring;
117
118 ServletContext context = event.getServletContext();
119 applicationContainer.setName("application");
120
121 context.setAttribute(ApplicationContainerHolder.class.getName(), new ApplicationContainerHolder(
122 applicationContainer));
123
124 sessionContainer.setName("session");
125 ThreadLocalLifecycleState sessionStateModel = new ThreadLocalLifecycleState();
126 sessionContainer.setLifecycleState(sessionStateModel);
127
128 SessionContainerHolder sch = new SessionContainerHolder(sessionContainer, sessionStoring, sessionStateModel) ;
129 context.setAttribute(SessionContainerHolder.class.getName(), sch);
130
131 requestContainer.setName("request");
132 ThreadLocalLifecycleState requestStateModel = new ThreadLocalLifecycleState();
133 requestContainer.setLifecycleState(requestStateModel);
134
135 context.setAttribute(RequestContainerHolder.class.getName(), new RequestContainerHolder(requestContainer,
136 requestStoring, requestStateModel));
137
138 if (useCompositionClass) {
139 compose(loadComposer(context), context);
140 }
141 applicationContainer.start();
142 }
143
144 /**
145 * Overide this method if you need a more specialized container tree.
146 * Here is the default block of code for this -
147 *
148 * DefaultPicoContainer appCtnr = new DefaultPicoContainer(new Caching(), makeParentContainer());
149 * Storing sessStoring = new Storing();
150 * DefaultPicoContainer sessCtnr = new DefaultPicoContainer(sessStoring, appCtnr);
151 * Storing reqStoring = new Storing();
152 * DefaultPicoContainer reqCtnr = new DefaultPicoContainer(reqStoring, sessCtnr);
153 * return new ScopedContainers(appCtnr,sessCtnr,reqCtnr,sessStoring,reqStoring);
154 *
155 * @return an instance of ScopedContainers
156 */
157 protected ScopedContainers makeScopedContainers() {
158 DefaultPicoContainer appCtnr = new DefaultPicoContainer(new Guarding().wrap(new Caching()), makeParentContainer());
159 Storing sessStoring = new Storing();
160 DefaultPicoContainer sessCtnr = new DefaultPicoContainer(new Guarding().wrap(sessStoring), appCtnr);
161 Storing reqStoring = new Storing();
162 DefaultPicoContainer reqCtnr = new DefaultPicoContainer(new Guarding().wrap(addRequestBehaviors(reqStoring)), sessCtnr);
163 return new ScopedContainers(appCtnr, sessCtnr, reqCtnr, sessStoring, reqStoring);
164 }
165
166 protected LifecycleStrategy makeLifecycleStrategy() {
167 return new StartableLifecycleStrategy(makeComponentMonitor());
168 }
169
170 protected ComponentMonitor makeComponentMonitor() {
171 return new NullComponentMonitor();
172 }
173
174
175 protected BehaviorFactory addRequestBehaviors(BehaviorFactory reqStoring) {
176 return reqStoring;
177 }
178
179 /**
180 * Get the class to do compostition with - from a "webapp-composer-class" config param
181 * from web.xml :
182 *
183 * <context-param>
184 * <param-name>webapp-composer-class</param-name>
185 * <param-value>com.yourcompany.YourWebappComposer</param-value>
186 * </context-param>
187 *
188 * @param context
189 * @return
190 */
191 protected WebappComposer loadComposer(ServletContext context) {
192 String composerClassName = context.getInitParameter(WEBAPP_COMPOSER_CLASS);
193 try {
194 return (WebappComposer) Thread.currentThread().getContextClassLoader().loadClass(composerClassName)
195 .newInstance();
196 } catch (Exception e) {
197 throw new PicoCompositionException("Failed to load webapp composer class " + composerClassName
198 + ": ensure the context-param '" + WEBAPP_COMPOSER_CLASS + "' is configured in the web.xml.", e);
199 }
200 }
201
202 protected void compose(WebappComposer composer, ServletContext context) {
203 composer.composeApplication(applicationContainer, context);
204 composer.composeSession(sessionContainer);
205 composer.composeRequest(requestContainer);
206 }
207
208 public void contextDestroyed(ServletContextEvent event) {
209 applicationContainer.stop();
210 applicationContainer.dispose();
211 }
212
213 public void sessionCreated(HttpSessionEvent event) {
214 HttpSession session = event.getSession();
215 ServletContext context = session.getServletContext();
216
217 SessionContainerHolder sch = (SessionContainerHolder) context.getAttribute(SessionContainerHolder.class
218 .getName());
219 ThreadLocalLifecycleState tlLifecycleState = sch.getLifecycleStateModel();
220 session.setAttribute(SessionStoreHolder.class.getName(), new SessionStoreHolder(sessionStoring
221 .resetCacheForThread(), tlLifecycleState.resetStateModelForThread()));
222
223 sessionContainer.start();
224 }
225
226 public void sessionDestroyed(HttpSessionEvent event) {
227 HttpSession session = event.getSession();
228 ServletContext context = session.getServletContext();
229
230 SessionStoreHolder ssh = (SessionStoreHolder) session.getAttribute(SessionStoreHolder.class.getName());
231
232 SessionContainerHolder sch = (SessionContainerHolder) context.getAttribute(SessionContainerHolder.class
233 .getName());
234 ThreadLocalLifecycleState tlLifecycleState = sch.getLifecycleStateModel();
235
236 sessionStoring.putCacheForThread(ssh.getStoreWrapper());
237 tlLifecycleState.putLifecycleStateModelForThread(ssh.getDefaultLifecycleState());
238
239 sessionContainer.stop();
240 sessionContainer.dispose();
241 sessionStoring.invalidateCacheForThread();
242 }
243
244 public static class ScopedContainers {
245
246 private DefaultPicoContainer applicationContainer;
247 private DefaultPicoContainer sessionContainer;
248 private DefaultPicoContainer requestContainer;
249 private Storing sessionStoring;
250 private Storing requestStoring;
251
252 public ScopedContainers(DefaultPicoContainer applicationContainer, DefaultPicoContainer sessionContainer, DefaultPicoContainer requestContainer, Storing sessionStoring, Storing requestStoring) {
253 this.applicationContainer = applicationContainer;
254 this.sessionContainer = sessionContainer;
255 this.requestContainer = requestContainer;
256 this.sessionStoring = sessionStoring;
257 this.requestStoring = requestStoring;
258 }
259 }
260
261
262 }