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.cdi.view;
20  
21  import org.apache.myfaces.cdi.JsfApplicationArtifactHolder;
22  import java.io.Serializable;
23  import java.util.Map;
24  import java.util.Random;
25  import java.util.concurrent.ConcurrentHashMap;
26  import javax.annotation.PostConstruct;
27  import javax.annotation.PreDestroy;
28  import javax.enterprise.context.SessionScoped;
29  import javax.enterprise.inject.spi.BeanManager;
30  import javax.faces.context.ExceptionHandler;
31  import javax.faces.context.ExternalContext;
32  import javax.faces.context.FacesContext;
33  import javax.inject.Inject;
34  import javax.servlet.ServletContext;
35  import org.apache.myfaces.context.ReleaseableExternalContext;
36  import org.apache.myfaces.context.servlet.StartupFacesContextImpl;
37  import org.apache.myfaces.context.servlet.StartupServletExternalContextImpl;
38  import org.apache.myfaces.shared.context.ExceptionHandlerImpl;
39  
40  /**
41   *
42   * @author Leonardo Uribe
43   */
44  @SessionScoped
45  public class ViewScopeBeanHolder implements Serializable
46  {
47      /**
48       * key: the windowId for the browser tab or window
49       * value: the {@link ViewScopeContextualStorage} which holds all the
50       * {@link javax.enterprise.inject.spi.Bean}s.
51       */
52      private Map<String, ViewScopeContextualStorage> storageMap;
53      
54      private static final Random RANDOM_GENERATOR = new Random();
55      
56      private static final String VIEW_SCOPE_PREFIX = "oam.view.SCOPE";
57      
58      public static final String VIEW_SCOPE_PREFIX_KEY = VIEW_SCOPE_PREFIX+".KEY";
59      
60      @Inject
61      JsfApplicationArtifactHolder applicationContextBean;
62      
63      public ViewScopeBeanHolder()
64      {
65      }
66      
67      @PostConstruct
68      public void init()
69      {
70          storageMap = new ConcurrentHashMap<String, ViewScopeContextualStorage>();
71          FacesContext facesContext = FacesContext.getCurrentInstance();
72          facesContext.getExternalContext().getSessionMap().put(VIEW_SCOPE_PREFIX_KEY,
73              1);
74      }
75      
76      /**
77       * This method will return the ViewScopeContextualStorage or create a new one
78       * if no one is yet assigned to the current windowId.
79       * 
80       * @param beanManager
81       * @param viewScopeId
82       * @return 
83       */
84      public ViewScopeContextualStorage getContextualStorage(
85          BeanManager beanManager, String viewScopeId)
86      {
87          ViewScopeContextualStorage contextualStorage = storageMap.get(viewScopeId);
88          if (contextualStorage == null)
89          {
90              synchronized (this)
91              {            
92                  contextualStorage = storageMap.get(viewScopeId);
93                  if (contextualStorage == null)
94                  {            
95                      contextualStorage = new ViewScopeContextualStorage(beanManager);
96                      storageMap.put(viewScopeId, contextualStorage);
97                  }
98              }
99          }
100         return contextualStorage;
101     }
102 
103     public Map<String, ViewScopeContextualStorage> getStorageMap()
104     {
105         return storageMap;
106     }
107 
108     /**
109      *
110      * This method will replace the storageMap and with
111      * a new empty one.
112      * This method can be used to properly destroy the WindowBeanHolder beans
113      * without having to sync heavily. Any
114      * {@link javax.enterprise.inject.spi.Bean#destroy(Object, javax.enterprise.context.spi.CreationalContext)}
115      * should be performed on the returned old storage map.
116      * @return the old storageMap.
117      */
118     public Map<String, ViewScopeContextualStorage> forceNewStorage()
119     {
120         Map<String, ViewScopeContextualStorage> oldStorageMap = storageMap;
121         storageMap = new ConcurrentHashMap<String, ViewScopeContextualStorage>();
122         return oldStorageMap;
123     }
124 
125     /**
126      * This method properly destroys all current &#064;WindowScoped beans
127      * of the active session and also prepares the storage for new beans.
128      * It will automatically get called when the session context closes
129      * but can also get invoked manually, e.g. if a user likes to get rid
130      * of all it's &#064;ViewScoped beans.
131      */
132     //@PreDestroy
133     public void destroyBeans()
134     {
135         // we replace the old windowBeanHolder beans with a new storage Map
136         // an afterwards destroy the old Beans without having to care about any syncs.
137         // This behavior also helps as a check to avoid destroy the same beans twice.
138         Map<String, ViewScopeContextualStorage> oldWindowContextStorages = forceNewStorage();
139 
140         for (ViewScopeContextualStorage contextualStorage : oldWindowContextStorages.values())
141         {
142             ViewScopeContextImpl.destroyAllActive(contextualStorage);
143         }
144     }
145     
146     public void destroyBeans(String viewScopeId)
147     {
148         ViewScopeContextualStorage contextualStorage = storageMap.get(viewScopeId);
149         if (contextualStorage != null)
150         {
151             try
152             {
153                 FacesContext facesContext = FacesContext.getCurrentInstance();
154                 if (facesContext == null &&
155                     applicationContextBean.getServletContext() != null)
156                 {
157                     try
158                     {
159                         ServletContext servletContext = applicationContextBean.getServletContext();
160                         ExternalContext externalContext = new StartupServletExternalContextImpl(servletContext, false);
161                         ExceptionHandler exceptionHandler = new ExceptionHandlerImpl();
162                         facesContext = new StartupFacesContextImpl(externalContext, 
163                                 (ReleaseableExternalContext) externalContext, exceptionHandler, false);
164                         ViewScopeContextImpl.destroyAllActive(contextualStorage, facesContext);
165                     }
166                     finally
167                     {
168                         facesContext.release();
169                     }
170                 }
171                 else
172                 {
173                     ViewScopeContextImpl.destroyAllActive(contextualStorage, facesContext);
174                 }
175             }
176             finally
177             {
178                 //remove the viewScopeId to prevent memory leak
179                 storageMap.remove(viewScopeId);
180             }
181         }
182     }
183     
184     @PreDestroy
185     public void destroyBeansOnPreDestroy()
186     {
187         // After some testing done two things are clear:
188         // 1. jetty +  weld call @PreDestroy at the end of the request
189         // 2. use a HttpServletListener in tomcat + owb does not work, because
190         //    CDI listener is executed first.
191         // So we need a mixed approach using both a listener and @PreDestroy annotations.
192         // When the first one in being called replace the storages with a new map 
193         // and call PreDestroy, when the second one is called, it founds an empty map
194         // and the process stops. A hack to get ServletContext from CDI is required to
195         // provide a valid FacesContext instance.
196         Map<String, ViewScopeContextualStorage> oldWindowContextStorages = forceNewStorage();
197         if (!oldWindowContextStorages.isEmpty())
198         {
199             FacesContext facesContext = FacesContext.getCurrentInstance();
200             if (facesContext == null &&
201                 applicationContextBean.getServletContext() != null)
202             {
203                 try
204                 {
205                     ServletContext servletContext = applicationContextBean.getServletContext();
206                     ExternalContext externalContext = new StartupServletExternalContextImpl(servletContext, false);
207                     ExceptionHandler exceptionHandler = new ExceptionHandlerImpl();
208                     facesContext = new StartupFacesContextImpl(externalContext, 
209                             (ReleaseableExternalContext) externalContext, exceptionHandler, false);
210                     for (ViewScopeContextualStorage contextualStorage : oldWindowContextStorages.values())
211                     {
212                         ViewScopeContextImpl.destroyAllActive(contextualStorage, facesContext);
213                     }
214                 }
215                 finally
216                 {
217                     facesContext.release();
218                 }
219             }
220             else
221             {
222                 for (ViewScopeContextualStorage contextualStorage : oldWindowContextStorages.values())
223                 {
224                     ViewScopeContextImpl.destroyAllActive(contextualStorage);
225                 }
226             }
227         }
228     }
229     
230     public String generateUniqueViewScopeId()
231     {
232         // To ensure uniqueness we just use a random generator and we check
233         // if the key is already used.
234         String key;
235         do 
236         {
237             key = Integer.toString(RANDOM_GENERATOR.nextInt());
238         } while (storageMap.containsKey(key));
239         return key;
240     }
241 
242 }