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.lang.annotation.Annotation;
22  import java.util.List;
23  import java.util.Map;
24  import javax.enterprise.context.ContextNotActiveException;
25  import javax.enterprise.context.spi.Context;
26  import javax.enterprise.context.spi.Contextual;
27  import javax.enterprise.context.spi.CreationalContext;
28  import javax.enterprise.inject.Typed;
29  import javax.enterprise.inject.spi.Bean;
30  import javax.enterprise.inject.spi.BeanManager;
31  import javax.enterprise.inject.spi.PassivationCapable;
32  import javax.faces.context.FacesContext;
33  import javax.faces.flow.Flow;
34  import javax.faces.flow.FlowHandler;
35  import javax.faces.flow.FlowScoped;
36  import org.apache.myfaces.cdi.util.BeanProvider;
37  import org.apache.myfaces.cdi.util.ContextualInstanceInfo;
38  import org.apache.myfaces.cdi.util.ContextualStorage;
39  import org.apache.myfaces.flow.FlowReference;
40  import org.apache.myfaces.flow.util.FlowUtils;
41  
42  /**
43   * Minimal implementation of FlowScope.
44   * 
45   * @TODO: We need something better for this part. The problem is the beans
46   * are just put under the session map using the old know SubKeyMap hack used
47   * in Flash object, but CDI implementations like OWB solves the passivation
48   * problem better. The ideal is provide a myfaces specific SPI interface, to
49   * allow provide custom implementation of this detail.
50   * 
51   * @TODO: FlowHandler.transition() method should call this object when the
52   * user enter or exit a flow.
53   *
54   * @author Leonardo Uribe
55   */
56  @Typed()
57  public class FlowScopedContextImpl implements Context
58  {
59      /**
60       * Whether the Context is for a passivating scope.
61       */
62      private final boolean passivatingScope;
63  
64      /**
65       * needed for serialisation and passivationId
66       */
67      private BeanManager beanManager;
68      
69      private Map<Class, FlowReference> flowBeanReferences;
70  
71      //private FlowScopeBeanHolder flowScopeBeanHolder;
72      
73      public FlowScopedContextImpl(BeanManager beanManager, 
74          Map<Class, FlowReference> flowBeanReferences)
75      {
76          this.beanManager = beanManager;
77          this.flowBeanReferences = flowBeanReferences;
78          passivatingScope = beanManager.isPassivatingScope(getScope());
79      }
80      
81      /*
82      public void initFlowContext(FlowScopeBeanHolder flowScopeBeanHolder)
83      {
84          this.flowScopeBeanHolder = flowScopeBeanHolder;
85      }*/
86      
87      protected FlowScopeBeanHolder getFlowScopeBeanHolder()
88      {
89          return getFlowScopeBeanHolder(FacesContext.getCurrentInstance());
90      }
91      
92      protected FlowScopeBeanHolder getFlowScopeBeanHolder(FacesContext facesContext)
93      {
94          FlowScopeBeanHolder flowScopeBeanHolder = (FlowScopeBeanHolder) 
95              facesContext.getExternalContext().getApplicationMap().get(
96                  "oam.flow.FlowScopeBeanHolder");
97          if (flowScopeBeanHolder == null)
98          {
99              flowScopeBeanHolder = BeanProvider.getContextualReference(
100                 beanManager, FlowScopeBeanHolder.class, false);
101             facesContext.getExternalContext().getApplicationMap().put(
102                 "oam.flow.FlowScopeBeanHolder", flowScopeBeanHolder);
103         }
104         return flowScopeBeanHolder;
105     }
106     
107     public String getCurrentClientWindowFlowId(FacesContext facesContext)
108     {
109         String flowMapKey = null;
110         FlowHandler flowHandler = facesContext.getApplication().getFlowHandler();
111         Flow flow = flowHandler.getCurrentFlow(facesContext);
112         if (flow != null)
113         {
114             flowMapKey = FlowUtils.getFlowMapKey(facesContext, flow);
115         }
116         return flowMapKey;
117     }
118 
119     /**
120      * An implementation has to return the underlying storage which
121      * contains the items held in the Context.
122      * @param createIfNotExist whether a ContextualStorage shall get created if it doesn't yet exist.
123      * @return the underlying storage
124      */
125     protected ContextualStorage getContextualStorage(boolean createIfNotExist, String clientWindowFlowId)
126     {
127         //FacesContext facesContext = FacesContext.getCurrentInstance();
128         //String clientWindowFlowId = getCurrentClientWindowFlowId(facesContext);
129         if (clientWindowFlowId == null)
130         {
131             throw new ContextNotActiveException("FlowScopedContextImpl: no current active flow");
132         }
133 
134         if (createIfNotExist)
135         {
136             return getFlowScopeBeanHolder().getContextualStorage(beanManager, clientWindowFlowId);
137         }
138         else
139         {
140             return getFlowScopeBeanHolder().getContextualStorageNoCreate(beanManager, clientWindowFlowId);
141         }
142     }
143 
144     public Class<? extends Annotation> getScope()
145     {
146         return FlowScoped.class;
147     }
148 
149     public boolean isActive()
150     {
151         return isActive(FacesContext.getCurrentInstance());
152     }
153 
154     public boolean isActive(FacesContext facesContext)
155     {
156         if (facesContext == null)
157         {
158             return false;
159         }
160         Flow flow = facesContext.getApplication().
161             getFlowHandler().getCurrentFlow(facesContext);
162         
163         return flow != null;
164     }
165 
166     /**
167      * @return whether the served scope is a passivating scope
168      */
169     public boolean isPassivatingScope()
170     {
171         return passivatingScope;
172     }
173 
174     @Override
175     public <T> T get(Contextual<T> bean)
176     {
177         FacesContext facesContext = FacesContext.getCurrentInstance();
178 
179         checkActive(facesContext);
180 
181         
182         FlowReference reference = flowBeanReferences.get(((Bean)bean).getBeanClass());
183         if (reference != null)
184         {
185             String flowMapKey = FlowUtils.getFlowMapKey(facesContext, reference);
186             if (flowMapKey != null)
187             {
188                 ContextualStorage storage = getContextualStorage(false, flowMapKey);
189                 if (storage != null)
190                 {
191                     Map<Object, ContextualInstanceInfo<?>> contextMap = storage.getStorage();
192                     ContextualInstanceInfo<?> contextualInstanceInfo = contextMap.get(storage.getBeanKey(bean));
193 
194                     if (contextualInstanceInfo != null)
195                     {
196                         return (T) contextualInstanceInfo.getContextualInstance();
197                     }
198                 }
199             }
200             else
201             {
202                 throw new IllegalStateException("Flow "+ reference.getId()+
203                     " cannot be found when resolving bean " +bean.toString());
204             }
205         }
206         
207         List<String> activeFlowMapKeys = getFlowScopeBeanHolder().getActiveFlowMapKeys(facesContext);
208         for (String flowMapKey : activeFlowMapKeys)
209         {
210             ContextualStorage storage = getContextualStorage(false, flowMapKey);
211             if (storage == null)
212             {
213                 //return null;
214                 continue;
215             }
216 
217             Map<Object, ContextualInstanceInfo<?>> contextMap = storage.getStorage();
218             ContextualInstanceInfo<?> contextualInstanceInfo = contextMap.get(storage.getBeanKey(bean));
219             if (contextualInstanceInfo == null)
220             {
221                 //return null;
222                 continue;
223             }
224 
225             return (T) contextualInstanceInfo.getContextualInstance();
226         }
227         return null;
228     }
229 
230     @Override
231     public <T> T get(Contextual<T> bean, CreationalContext<T> creationalContext)
232     {
233         FacesContext facesContext = FacesContext.getCurrentInstance();
234         
235         checkActive(facesContext);
236 
237         if (passivatingScope && !(bean instanceof PassivationCapable))
238         {
239             throw new IllegalStateException(bean.toString() +
240                     " doesn't implement " + PassivationCapable.class.getName());
241         }
242         
243         FlowReference reference = flowBeanReferences.get(((Bean)bean).getBeanClass());
244         if (reference != null)
245         {
246             String flowMapKey = FlowUtils.getFlowMapKey(facesContext, reference);
247             if (flowMapKey != null)
248             {
249                 ContextualStorage storage = getContextualStorage(false, flowMapKey);
250                 if (storage != null)
251                 {
252                     Map<Object, ContextualInstanceInfo<?>> contextMap = storage.getStorage();
253                     ContextualInstanceInfo<?> contextualInstanceInfo = contextMap.get(storage.getBeanKey(bean));
254 
255                     if (contextualInstanceInfo != null)
256                     {
257                         return (T) contextualInstanceInfo.getContextualInstance();
258                     }
259                 }
260             }
261             else
262             {
263                 throw new IllegalStateException("Flow "+ reference.getId()+
264                     " cannot be found when resolving bean " + bean.toString());
265             }
266             
267             FlowHandler flowHandler = facesContext.getApplication().getFlowHandler();
268             // Since it is possible to have only the flow id without documentId, the best
269             // is first get the flow using flowHandler.getFlow and then check if the flow is
270             // active or not, but using the documentId and id of the retrieved flow.
271             Flow flow = flowHandler.getFlow(facesContext, 
272                 reference.getDocumentId() == null ? "" : reference.getDocumentId(), reference.getId());
273             if (flow == null)
274             {
275                 throw new IllegalStateException(bean.toString() + "cannot be created because flow "+ reference.getId()+
276                     " is not registered");
277             }
278             if (!flowHandler.isActive(facesContext, flow.getDefiningDocumentId(), flow.getId())) 
279             {
280                 throw new IllegalStateException(bean.toString() + "cannot be created if flow "+ reference.getId()+
281                     " is not active");
282             }
283             
284             ContextualStorage storage = getContextualStorage(true, flowMapKey);
285             Map<Object, ContextualInstanceInfo<?>> contextMap = storage.getStorage();
286             ContextualInstanceInfo<?> contextualInstanceInfo = contextMap.get(storage.getBeanKey(bean));
287 
288             if (contextualInstanceInfo != null)
289             {
290                 @SuppressWarnings("unchecked")
291                 final T instance =  (T) contextualInstanceInfo.getContextualInstance();
292 
293                 if (instance != null)
294                 {
295                     return instance;
296                 }
297             }
298 
299             return storage.createContextualInstance(bean, creationalContext);
300         }
301 
302         List<String> activeFlowMapKeys = getFlowScopeBeanHolder().getActiveFlowMapKeys(facesContext);
303         for (String flowMapKey : activeFlowMapKeys)
304         {
305             ContextualStorage storage = getContextualStorage(false, flowMapKey);
306 
307             Map<Object, ContextualInstanceInfo<?>> contextMap = storage.getStorage();
308             ContextualInstanceInfo<?> contextualInstanceInfo = contextMap.get(storage.getBeanKey(bean));
309 
310             if (contextualInstanceInfo != null)
311             {
312                 @SuppressWarnings("unchecked")
313                 final T instance =  (T) contextualInstanceInfo.getContextualInstance();
314 
315                 if (instance != null)
316                 {
317                     return instance;
318                 }
319             }
320 
321         }
322         
323         ContextualStorage storage = getContextualStorage(true, getCurrentClientWindowFlowId(facesContext));
324         Map<Object, ContextualInstanceInfo<?>> contextMap = storage.getStorage();
325         ContextualInstanceInfo<?> contextualInstanceInfo = contextMap.get(storage.getBeanKey(bean));
326 
327         if (contextualInstanceInfo != null)
328         {
329             @SuppressWarnings("unchecked")
330             final T instance =  (T) contextualInstanceInfo.getContextualInstance();
331 
332             if (instance != null)
333             {
334                 return instance;
335             }
336         }
337             
338         return storage.createContextualInstance(bean, creationalContext);
339     }
340 
341     /**
342      * Destroy the Contextual Instance of the given Bean.
343      * @param bean dictates which bean shall get cleaned up
344      * @return <code>true</code> if the bean was destroyed, <code>false</code> if there was no such bean.
345      */
346     public boolean destroy(Contextual bean)
347     {
348         FacesContext facesContext = FacesContext.getCurrentInstance();
349         List<String> activeFlowMapKeys = getFlowScopeBeanHolder().getActiveFlowMapKeys(facesContext);
350         for (String flowMapKey : activeFlowMapKeys)
351         {
352             ContextualStorage storage = getContextualStorage(false, flowMapKey);
353             if (storage == null)
354             {
355                 //return false;
356                 continue;
357             }
358             ContextualInstanceInfo<?> contextualInstanceInfo = storage.getStorage().get(storage.getBeanKey(bean));
359 
360             if (contextualInstanceInfo == null)
361             {
362                 //return false;
363                 continue;
364             }
365 
366             bean.destroy(contextualInstanceInfo.getContextualInstance(), contextualInstanceInfo.getCreationalContext());
367             return true;
368         }
369         return false;
370     }
371 
372     /**
373      * destroys all the Contextual Instances in the Storage returned by
374      * {@link #getContextualStorage(boolean)}.
375      */
376     /*
377     public void destroyAllActive()
378     {
379         ContextualStorage storage = getContextualStorage(false);
380         if (storage == null)
381         {
382             return;
383         }
384 
385         destroyAllActive(storage);
386     }*/
387 
388     /**
389      * Destroys all the Contextual Instances in the specified ContextualStorage.
390      * This is a static method to allow various holder objects to cleanup
391      * properly in &#064;PreDestroy.
392      */
393     public static void destroyAllActive(ContextualStorage storage)
394     {
395         Map<Object, ContextualInstanceInfo<?>> contextMap = storage.getStorage();
396         for (Map.Entry<Object, ContextualInstanceInfo<?>> entry : contextMap.entrySet())
397         {
398             if (!FlowScopeBeanHolder.CURRENT_FLOW_SCOPE_MAP.equals(entry.getKey()))
399             {
400                 Contextual bean = storage.getBean(entry.getKey());
401 
402                 ContextualInstanceInfo<?> contextualInstanceInfo = entry.getValue();
403                 bean.destroy(contextualInstanceInfo.getContextualInstance(), 
404                     contextualInstanceInfo.getCreationalContext());
405             }
406         }
407     }
408 
409     /**
410      * Make sure that the Context is really active.
411      * @throws ContextNotActiveException if there is no active
412      *         Context for the current Thread.
413      */
414     protected void checkActive(FacesContext facesContext)
415     {
416         if (!isActive(facesContext))
417         {
418             throw new ContextNotActiveException("CDI context with scope annotation @"
419                 + getScope().getName() + " is not active with respect to the current thread");
420         }
421     }
422 
423 }