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