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.flow.cdi;
20  
21  import java.io.Serializable;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.concurrent.ConcurrentHashMap;
28  import java.util.logging.Level;
29  import java.util.logging.Logger;
30  import javax.annotation.PostConstruct;
31  import javax.annotation.PreDestroy;
32  import javax.enterprise.context.SessionScoped;
33  import javax.enterprise.inject.spi.BeanManager;
34  import javax.faces.context.ExceptionHandler;
35  import javax.faces.context.ExternalContext;
36  import javax.faces.context.FacesContext;
37  import javax.faces.flow.Flow;
38  import javax.faces.flow.FlowHandler;
39  import javax.faces.lifecycle.ClientWindow;
40  import javax.inject.Inject;
41  import javax.servlet.ServletContext;
42  import org.apache.myfaces.cdi.util.ContextualInstanceInfo;
43  import org.apache.myfaces.cdi.util.ContextualStorage;
44  import org.apache.myfaces.cdi.JsfApplicationArtifactHolder;
45  import org.apache.myfaces.context.ReleaseableExternalContext;
46  import org.apache.myfaces.context.servlet.StartupFacesContextImpl;
47  import org.apache.myfaces.context.servlet.StartupServletExternalContextImpl;
48  import org.apache.myfaces.flow.util.FlowUtils;
49  import org.apache.myfaces.shared.config.MyfacesConfig;
50  import org.apache.myfaces.shared.context.ExceptionHandlerImpl;
51  
52  
53  /**
54   *
55   * This holder will store the flow scope active ids and it's beans for the current
56   * HTTP Session. We use standard SessionScoped bean to not need
57   * to treat async-supported and similar headache.
58   * 
59   * @author lu4242
60   */
61  @SessionScoped
62  public class FlowScopeBeanHolder implements Serializable
63  {
64      /**
65       * key: client window id + flow id
66       * value: the {@link ContextualStorage} which holds all the
67       * {@link javax.enterprise.inject.spi.Bean}s.
68       */
69      private Map<String, ContextualStorage> storageMap;
70      
71      private Map<String, List<String>> activeFlowMapKeys;
72      
73      private FacesFlowClientWindowCollection windowCollection;
74      
75      public static final String CURRENT_FLOW_SCOPE_MAP = "oam.CURRENT_FLOW_SCOPE_MAP";
76      
77      private static final String FLOW_SCOPE_PREFIX = "oam.flow.SCOPE";
78      
79      public static final String FLOW_SCOPE_PREFIX_KEY = FLOW_SCOPE_PREFIX+".KEY";
80      
81      @Inject
82      JsfApplicationArtifactHolder applicationContextBean;
83  
84      public FlowScopeBeanHolder()
85      {
86      }
87      
88      @PostConstruct
89      public void init()
90      {
91          storageMap = new ConcurrentHashMap<String, ContextualStorage>();
92          activeFlowMapKeys = new ConcurrentHashMap<String, List<String>>();
93          windowCollection = null;
94          
95          FacesContext facesContext = FacesContext.getCurrentInstance();
96          this.refreshClientWindow(facesContext);
97          facesContext.getExternalContext().getSessionMap().put(FLOW_SCOPE_PREFIX_KEY,
98              1);
99      }
100     
101     /**
102      * This method will return the ContextualStorage or create a new one
103      * if no one is yet assigned to the current flowClientWindowId.
104      * @param beanManager we need the CDI {@link BeanManager} for serialisation.
105      * @param flowClientWindowId the flowClientWindowId for the current flow.
106      */
107     public ContextualStorage getContextualStorage(BeanManager beanManager, String flowClientWindowId)
108     {
109         ContextualStorage contextualStorage = storageMap.get(flowClientWindowId);
110         if (contextualStorage == null)
111         {
112             synchronized (this)
113             {
114                 contextualStorage = storageMap.get(flowClientWindowId);
115                 if (contextualStorage == null)
116                 {
117                     contextualStorage = new ContextualStorage(beanManager, true, true);
118                     storageMap.put(flowClientWindowId, contextualStorage);
119                 }
120             }
121         }
122 
123         return contextualStorage;
124     }
125     
126     public ContextualStorage getContextualStorageNoCreate(BeanManager beanManager, String flowClientWindowId)
127     {
128         return storageMap.get(flowClientWindowId);
129     }
130 
131     public Map<String, ContextualStorage> getStorageMap()
132     {
133         return storageMap;
134     }
135     
136     public Map<Object, Object> getFlowScopeMap(
137         BeanManager beanManager, String flowClientWindowId, boolean create)
138     {
139         Map<Object, Object> map = null;
140         if (create)
141         {
142             ContextualStorage contextualStorage = getContextualStorage(
143                 beanManager, flowClientWindowId);
144             ContextualInstanceInfo info = contextualStorage.getStorage().get(CURRENT_FLOW_SCOPE_MAP);
145             if (info == null)
146             {
147                 info = new ContextualInstanceInfo<Object>();
148                 contextualStorage.getStorage().put(CURRENT_FLOW_SCOPE_MAP, info);
149             }
150             map = (Map<Object, Object>) info.getContextualInstance();
151             if (map == null)
152             {
153                 map = new HashMap<Object,Object>();
154                 info.setContextualInstance(map);
155             }
156         }
157         else
158         {
159             ContextualStorage contextualStorage = getContextualStorageNoCreate(
160                 beanManager, flowClientWindowId);
161             if (contextualStorage != null)
162             {
163                 ContextualInstanceInfo info = contextualStorage.getStorage().get(CURRENT_FLOW_SCOPE_MAP);
164                 if (info != null)
165                 {
166                     map = (Map<Object, Object>) info.getContextualInstance();
167                 }
168             }
169         }
170         return map;
171     }
172 
173     /**
174      *
175      * This method will replace the storageMap and with
176      * a new empty one.
177      * This method can be used to properly destroy the WindowBeanHolder beans
178      * without having to sync heavily. Any
179      * {@link javax.enterprise.inject.spi.Bean#destroy(Object, javax.enterprise.context.spi.CreationalContext)}
180      * should be performed on the returned old storage map.
181      * @return the old storageMap.
182      */
183     public Map<String, ContextualStorage> forceNewStorage()
184     {
185         Map<String, ContextualStorage> oldStorageMap = storageMap;
186         storageMap = new ConcurrentHashMap<String, ContextualStorage>();
187         return oldStorageMap;
188     }
189 
190     /**
191      * This method properly destroys all current &#064;WindowScoped beans
192      * of the active session and also prepares the storage for new beans.
193      * It will automatically get called when the session context closes
194      * but can also get invoked manually, e.g. if a user likes to get rid
195      * of all it's &#064;WindowScoped beans.
196      */
197     //@PreDestroy
198     public void destroyBeans()
199     {
200         // we replace the old windowBeanHolder beans with a new storage Map
201         // an afterwards destroy the old Beans without having to care about any syncs.
202         Map<String, ContextualStorage> oldWindowContextStorages = forceNewStorage();
203 
204         for (ContextualStorage contextualStorage : oldWindowContextStorages.values())
205         {
206             FlowScopedContextImpl.destroyAllActive(contextualStorage);
207         }
208     }
209     
210     /**
211      * See description on ViewScopeBeanHolder for details about how this works
212      */
213     @PreDestroy
214     public void destroyBeansOnPreDestroy()
215     {
216         Map<String, ContextualStorage> oldWindowContextStorages = forceNewStorage();
217         if (!oldWindowContextStorages.isEmpty())
218         {
219             FacesContext facesContext = FacesContext.getCurrentInstance();
220             ServletContext servletContext = null;
221             if (facesContext == null)
222             {
223                 try
224                 {
225                     servletContext = applicationContextBean.getServletContext();
226                 }
227                 catch (Throwable e)
228                 {
229                     Logger.getLogger(FlowScopeBeanHolder.class.getName()).log(Level.WARNING,
230                         "Cannot locate servletContext to create FacesContext on @PreDestroy flow scope beans. "
231                                 + "The beans will be destroyed without active FacesContext instance.");
232                     servletContext = null;
233                 }
234             }
235             if (facesContext == null &&
236                 servletContext != null)
237             {
238                 try
239                 {
240                     ExternalContext externalContext = new StartupServletExternalContextImpl(servletContext, false);
241                     ExceptionHandler exceptionHandler = new ExceptionHandlerImpl();
242                     facesContext = new StartupFacesContextImpl(externalContext, 
243                             (ReleaseableExternalContext) externalContext, exceptionHandler, false);
244                     for (ContextualStorage contextualStorage : oldWindowContextStorages.values())
245                     {
246                         FlowScopedContextImpl.destroyAllActive(contextualStorage);
247                     }
248                 }
249                 finally
250                 {
251                     facesContext.release();
252                 }
253             }
254             else
255             {
256                 for (ContextualStorage contextualStorage : oldWindowContextStorages.values())
257                 {
258                     FlowScopedContextImpl.destroyAllActive(contextualStorage);
259                 }
260             }
261         }
262     }
263     
264     public void refreshClientWindow(FacesContext facesContext)
265     {
266         if (windowCollection == null)
267         {
268             Integer ft = MyfacesConfig.getCurrentInstance(facesContext.getExternalContext()).
269                     getNumberOfFacesFlowClientWindowIdsInSession();
270             windowCollection = new FacesFlowClientWindowCollection(new ClientWindowFacesFlowLRUMap(ft));
271         }
272         ClientWindow cw = facesContext.getExternalContext().getClientWindow();
273         if (cw != null && cw.getId() != null)
274         {
275             windowCollection.setFlowScopeBeanHolder(this);
276             windowCollection.put(cw.getId(), "");
277         }
278     }
279     
280     public void clearFlowMap(String clientWindowId)
281     {
282         List<String> activeFlowKeys = activeFlowMapKeys.remove(clientWindowId);
283         if (activeFlowKeys != null && !activeFlowKeys.isEmpty())
284         {
285             for (String flowMapKey : activeFlowKeys)
286             {
287                 ContextualStorage contextualStorage = storageMap.remove(flowMapKey);
288                 if (contextualStorage != null)
289                 {
290                     FlowScopedContextImpl.destroyAllActive(contextualStorage);
291                 }
292             }
293         }
294     }
295     
296     public List<String> getActiveFlowMapKeys(FacesContext facesContext)
297     {
298         ClientWindow cw = facesContext.getExternalContext().getClientWindow();
299         String baseKey = cw.getId();
300         List<String> activeFlowKeys = activeFlowMapKeys.get(baseKey);
301         if (activeFlowKeys == null)
302         {
303             return Collections.emptyList();
304         }
305         else
306         {
307             return activeFlowKeys;
308         }
309     }
310     
311     public void createCurrentFlowScope(FacesContext facesContext)
312     {
313         ClientWindow cw = facesContext.getExternalContext().getClientWindow();
314         String baseKey = cw.getId();
315         
316         FlowHandler flowHandler = facesContext.getApplication().getFlowHandler();
317         Flow flow = flowHandler.getCurrentFlow(facesContext);
318         String flowMapKey = FlowUtils.getFlowMapKey(facesContext, flow);
319 
320         List<String> activeFlowKeys = activeFlowMapKeys.get(baseKey);
321         if (activeFlowKeys == null)
322         {
323             activeFlowKeys = new ArrayList<String>();
324             
325         }
326         activeFlowKeys.add(0, flowMapKey);
327         activeFlowMapKeys.put(baseKey, activeFlowKeys);
328         refreshClientWindow(facesContext);
329     }
330     
331     public void destroyCurrentFlowScope(FacesContext facesContext)
332     {
333         ClientWindow cw = facesContext.getExternalContext().getClientWindow();
334         String baseKey = cw.getId();
335         
336         FlowHandler flowHandler = facesContext.getApplication().getFlowHandler();
337         Flow flow = flowHandler.getCurrentFlow(facesContext);
338         String flowMapKey = FlowUtils.getFlowMapKey(facesContext, flow);
339 
340         ContextualStorage contextualStorage = storageMap.remove(flowMapKey);
341         if (contextualStorage != null)
342         {
343             FlowScopedContextImpl.destroyAllActive(contextualStorage);
344         }
345         
346         List<String> activeFlowKeys = activeFlowMapKeys.get(baseKey);
347         if (activeFlowKeys != null && !activeFlowKeys.isEmpty())
348         {
349             activeFlowKeys.remove(flowMapKey);
350         }
351     }
352 }