View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.webapp;
20  
21  import java.util.Enumeration;
22  import javax.faces.context.ExceptionHandler;
23  import javax.faces.context.ExternalContext;
24  import javax.faces.context.FacesContext;
25  
26  import javax.servlet.ServletContext;
27  import javax.servlet.ServletContextAttributeEvent;
28  import javax.servlet.ServletContextAttributeListener;
29  import javax.servlet.ServletContextEvent;
30  import javax.servlet.ServletContextListener;
31  import javax.servlet.ServletRequest;
32  import javax.servlet.ServletRequestAttributeEvent;
33  import javax.servlet.ServletRequestAttributeListener;
34  import javax.servlet.ServletRequestEvent;
35  import javax.servlet.ServletRequestListener;
36  import javax.servlet.http.HttpSessionAttributeListener;
37  import javax.servlet.http.HttpSessionBindingEvent;
38  import javax.servlet.http.HttpSessionEvent;
39  import javax.servlet.http.HttpSessionListener;
40  
41  import org.apache.myfaces.config.ManagedBeanDestroyer;
42  import org.apache.myfaces.context.ReleaseableExternalContext;
43  import org.apache.myfaces.context.servlet.StartupFacesContextImpl;
44  import org.apache.myfaces.context.servlet.StartupServletExternalContextImpl;
45  import org.apache.myfaces.shared.context.ExceptionHandlerImpl;
46  import org.apache.myfaces.spi.ViewScopeProvider;
47  
48  /**
49   * Listens to
50   *   - removing, replacing of attributes in context, session and request
51   *   - destroying of context, session and request
52   * for the ManagedBeanDestroyer to assure right destruction of managed beans in those scopes.
53   * 
54   * This listener is not registered in a tld or web.xml, but will be called by StartupServletContextListener.
55   * 
56   * @author Jakob Korherr (latest modification by $Author$)
57   * @version $Revision$ $Date$
58   * @since 2.0
59   */
60  public class ManagedBeanDestroyerListener implements 
61          HttpSessionAttributeListener, HttpSessionListener,
62          ServletContextListener, ServletContextAttributeListener,
63          ServletRequestListener, ServletRequestAttributeListener
64  {
65  
66      /**
67       * The instance of the ManagedBeanDestroyerListener created by
68       * StartupServletContextListener is stored under this key in the
69       * ApplicationMap.
70       */
71      public static final String APPLICATION_MAP_KEY = "org.apache.myfaces.ManagedBeanDestroyerListener";
72  
73      private ManagedBeanDestroyer _destroyer = null;
74      
75      private ViewScopeProvider _viewScopeHandler = null;
76  
77      /**
78       * Sets the ManagedBeanDestroyer instance to use.
79       *  
80       * @param destroyer
81       */
82      public void setManagedBeanDestroyer(ManagedBeanDestroyer destroyer)
83      {
84          _destroyer = destroyer;
85      }
86      
87      public void setViewScopeHandler(ViewScopeProvider listener)
88      {
89          _viewScopeHandler = listener;
90      }
91  
92      /* Session related methods ***********************************************/
93      
94      public void attributeAdded(HttpSessionBindingEvent event)
95      {
96          // noop
97      }
98  
99      public void attributeRemoved(HttpSessionBindingEvent event)
100     {
101         if (_destroyer != null)
102         {
103             _destroyer.destroy(event.getName(), event.getValue());
104         }
105     }
106 
107     public void attributeReplaced(HttpSessionBindingEvent event)
108     {
109         if (_destroyer != null)
110         {
111             _destroyer.destroy(event.getName(), event.getValue());
112         }
113     }
114 
115     public void sessionCreated(HttpSessionEvent event)
116     {
117         // noop
118     }
119 
120     @SuppressWarnings("unchecked")
121     public void sessionDestroyed(HttpSessionEvent event)
122     {
123         // MYFACES-3040 @PreDestroy Has Called 2 times
124         // attributeRemoved receives the event too, so it does not 
125         // have sense to handle it here. Unfortunately, it is not possible to 
126         // handle it first and then on attributeRemoved, so the best bet is
127         // let the code in just one place.
128         /*
129         if (_destroyer != null)
130         {
131             HttpSession session = event.getSession();
132             Enumeration<String> attributes = session.getAttributeNames();
133             if (!attributes.hasMoreElements())
134             {
135                 // nothing to do
136                 return;
137             }
138 
139             while (attributes.hasMoreElements())
140             {
141                 String name = attributes.nextElement();
142                 Object value = session.getAttribute(name);
143                 _destroyer.destroy(name, value);
144             }
145         }*/
146         
147         // If we don't propagate this event, CDI will do for us but outside JSF control
148         // so when @PreDestroy methods are called there will not be an active FacesContext.
149         // The trick here is ensure clean the affected scopes to avoid duplicates.
150         // Remember cdi session scope is different from jsf session scope, because in
151         // jsf case the beans are stored under a session attribute, so it has the problem
152         // with attributeRemoved, but on cdi a wrapper is used instead, avoiding the problem.
153         if (_viewScopeHandler != null)
154         {
155             FacesContext facesContext = FacesContext.getCurrentInstance();
156             if (facesContext != null)
157             {
158                 _viewScopeHandler.onSessionDestroyed();
159             }
160             else
161             {
162                 // In case no FacesContext is available, we are on session invalidation
163                 // through timeout. In that case, create a dummy FacesContext for this one
164                 // like the one used in startup or shutdown and invoke the destroy method.
165                 try
166                 {
167                     ServletContext servletContext = event.getSession().getServletContext();
168                     ExternalContext externalContext = new StartupServletExternalContextImpl(servletContext, false);
169                     ExceptionHandler exceptionHandler = new ExceptionHandlerImpl();
170                     facesContext = new StartupFacesContextImpl(externalContext, 
171                             (ReleaseableExternalContext) externalContext, exceptionHandler, false);
172                     _viewScopeHandler.onSessionDestroyed();
173                 }
174                 finally
175                 {
176                     facesContext.release();
177                 }
178             }
179         }
180     }
181     
182     /* Context related methods ***********************************************/
183     
184     public void attributeAdded(ServletContextAttributeEvent event)
185     {
186         // noop
187     }
188 
189     public void attributeRemoved(ServletContextAttributeEvent event)
190     {
191         if (_destroyer != null)
192         {
193             _destroyer.destroy(event.getName(), event.getValue());
194         }
195     }
196 
197     public void attributeReplaced(ServletContextAttributeEvent event)
198     {
199         if (_destroyer != null)
200         {
201             _destroyer.destroy(event.getName(), event.getValue());
202         }
203     }
204 
205     public void contextInitialized(ServletContextEvent event)
206     {
207         // noop
208     }
209     
210     @SuppressWarnings("unchecked")
211     public void contextDestroyed(ServletContextEvent event)
212     {
213         if (_destroyer != null)
214         {
215             ServletContext ctx = event.getServletContext();
216             Enumeration<String> attributes = ctx.getAttributeNames();
217             if (!attributes.hasMoreElements())
218             {
219                 // nothing to do
220                 return;
221             }
222 
223             while (attributes.hasMoreElements())
224             {
225                 String name = attributes.nextElement();
226                 Object value = ctx.getAttribute(name);
227                 _destroyer.destroy(name, value);
228             }
229         }
230     }
231     
232     /* Request related methods ***********************************************/
233     
234     public void attributeAdded(ServletRequestAttributeEvent event)
235     {
236         // noop
237     }
238 
239     public void attributeRemoved(ServletRequestAttributeEvent event)
240     {
241         if (_destroyer != null)
242         {
243             _destroyer.destroy(event.getName(), event.getValue());
244         }
245     }
246 
247     public void attributeReplaced(ServletRequestAttributeEvent event)
248     {
249         if (_destroyer != null)
250         {
251             _destroyer.destroy(event.getName(), event.getValue());
252         }
253     }
254 
255     public void requestInitialized(ServletRequestEvent event)
256     {
257         // noop
258     }
259     
260     @SuppressWarnings("unchecked")
261     public void requestDestroyed(ServletRequestEvent event)
262     {
263         if (_destroyer != null)
264         {
265             ServletRequest request = event.getServletRequest();
266             Enumeration<String> attributes = request.getAttributeNames();
267             if (!attributes.hasMoreElements())
268             {
269                 // nothing to do
270                 return;
271             }
272             
273             while (attributes.hasMoreElements())
274             {
275                 String name = attributes.nextElement();
276                 Object value = request.getAttribute(name);
277                 _destroyer.destroy(name, value);
278             }
279         }
280     }
281 
282 }