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;
20  
21  import java.util.ArrayList;
22  import java.util.Collections;
23  import java.util.HashMap;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.concurrent.ConcurrentHashMap;
27  import javax.faces.FacesWrapper;
28  import javax.faces.application.ConfigurableNavigationHandler;
29  import javax.faces.application.NavigationCase;
30  import javax.faces.application.NavigationHandler;
31  import javax.faces.application.NavigationHandlerWrapper;
32  import javax.faces.context.FacesContext;
33  import javax.faces.event.SystemEvent;
34  import javax.faces.event.SystemEventListener;
35  import javax.faces.flow.Flow;
36  import javax.faces.flow.FlowCallNode;
37  import javax.faces.flow.FlowHandler;
38  import javax.faces.flow.FlowNode;
39  import javax.faces.flow.Parameter;
40  import javax.faces.flow.ReturnNode;
41  import javax.faces.lifecycle.ClientWindow;
42  import org.apache.myfaces.event.PostClientWindowAndViewInitializedEvent;
43  import org.apache.myfaces.spi.FacesFlowProvider;
44  import org.apache.myfaces.spi.FacesFlowProviderFactory;
45  
46  /**
47   *
48   * @since 2.2
49   * @author Leonardo Uribe
50   */
51  public class FlowHandlerImpl extends FlowHandler implements SystemEventListener
52  {
53      private final static String CURRENT_FLOW_STACK = "oam.flow.STACK.";
54      private final static String ROOT_LAST_VIEW_ID = "oam.flow.ROOT_LAST_VIEW_ID.";
55      
56      private final static String RETURN_MODE = "oam.flow.RETURN_MODE";
57      private final static String FLOW_RETURN_STACK = "oam.flow.RETURN_STACK.";
58      private final static String CURRENT_FLOW_REQUEST_STACK = "oam.flow.REQUEST_STACK.";
59      
60      private Map<String, Map<String, Flow>> _flowMapByDocumentId;
61      private Map<String, Flow> _flowMapById;
62      
63      private FacesFlowProvider _facesFlowProvider;
64      
65      public FlowHandlerImpl()
66      {
67          _flowMapByDocumentId = new ConcurrentHashMap<String, Map<String, Flow>>();
68          _flowMapById = new ConcurrentHashMap<String, Flow>();
69      }
70  
71      @Override
72      public Flow getFlow(FacesContext context, String definingDocumentId, String id)
73      {
74          checkNull(context, "context");
75          checkNull(definingDocumentId, "definingDocumentId");
76          checkNull(id, "id");
77          
78          // First try the combination.
79          Map<String, Flow> flowMap = _flowMapByDocumentId.get(definingDocumentId);
80          if (flowMap != null)
81          {
82              Flow flow = flowMap.get(id);
83              if (flow != null)
84              {
85                  return flow;
86              }
87          }
88          
89          //if definingDocumentId is an empty string, 
90          if ("".equals(definingDocumentId))
91          {
92              return _flowMapById.get(id);
93          }
94          return null;
95      }
96  
97      @Override
98      public void addFlow(FacesContext context, Flow toAdd)
99      {
100         checkNull(context, "context");
101         checkNull(toAdd, "toAdd");
102         
103         String id = toAdd.getId();
104         String definingDocumentId = toAdd.getDefiningDocumentId();
105         
106         if (id == null)
107         {
108             throw new IllegalArgumentException("Flow must have a non null id");
109         }
110         else if (id.length() == 0)
111         {
112             throw new IllegalArgumentException("Flow must have a non empty id");
113         }
114         if (definingDocumentId == null)
115         {
116             throw new IllegalArgumentException("Flow must have a non null definingDocumentId");
117         }
118         
119         Map<String, Flow> flowMap = _flowMapByDocumentId.get(definingDocumentId);
120         if (flowMap == null)
121         {
122             flowMap = new ConcurrentHashMap<String, Flow>();
123             _flowMapByDocumentId.put(definingDocumentId, flowMap);
124         }
125         flowMap.put(id, toAdd);
126         
127         Flow duplicateFlow = _flowMapById.get(id);
128         if (duplicateFlow != null)
129         {
130             // There are two flows with the same flowId.
131             // Raise an exception if flows share a duplicate ID and definingDocumentId
132             if (toAdd.getDefiningDocumentId().equals(duplicateFlow.getDefiningDocumentId()))
133             {
134                 throw new IllegalArgumentException("There cannot be multiple flows with both the" 
135                                                    + " same ID and the same definingDocumentId");
136             }
137             // Give priority to the flow with no defining document id
138             else if ("".equals(toAdd.getDefiningDocumentId()))
139             {
140                 _flowMapById.put(id, toAdd);
141             }
142             else if ("".equals(duplicateFlow.getDefiningDocumentId()))
143             {
144                 // Already added, skip
145             }
146             else
147             {
148                 // Put the last one
149                 _flowMapById.put(id, toAdd);
150             }
151         }
152         else
153         {
154             _flowMapById.put(id, toAdd);
155         }
156 
157         // Once the flow is added to the map, it is still necessary to 
158         // pass the flow to the ConfigurableNavigationHandler, so it can be
159         // inspected for navigation rules. This is the best place to do that because
160         // the spec says "... Called by the flow system to cause the flow to 
161         // be inspected for navigation rules... " (note it says "flow system" not
162         // "configuration system" where the calls to addFlow() are done).
163         invokeInspectFlow(context, context.getApplication().getNavigationHandler(), toAdd);
164     }
165 
166     @Override
167     public Flow getCurrentFlow(FacesContext context)
168     {
169         Object session = context.getExternalContext().getSession(false);
170         if (session == null)
171         {
172             return null;
173         }
174         ClientWindow clientWindow = context.getExternalContext().getClientWindow();
175         if (clientWindow == null)
176         {
177             return null;
178         }
179         
180         
181         _FlowContextualInfo info = getCurrentFlowReference(context, clientWindow);
182         if (info == null)
183         {
184             return null;
185         }
186         FlowReference flowReference = info.getFlowReference();
187         return getFlow(context, flowReference.getDocumentId(), flowReference.getId());
188     }
189     
190     @Override
191     public void transition(FacesContext context, Flow sourceFlow, Flow targetFlow, 
192         FlowCallNode outboundCallNode, String toViewId)
193     {
194         checkNull(context, "context");
195         checkNull(toViewId, "toViewId");
196         ClientWindow clientWindow = context.getExternalContext().getClientWindow();
197         boolean outboundCallNodeProcessed = false;
198         if (clientWindow == null)
199         {
200             return;
201         }
202         
203         if (sourceFlow == null && targetFlow == null)
204         {
205             return;
206         }
207 
208         // Calculate the parentFlowReference, since it will be used later.
209         FlowReference parentFlowReference = (outboundCallNode != null && sourceFlow != null) ?
210             new FlowReference(sourceFlow.getDefiningDocumentId(), sourceFlow.getId()) : null;
211         
212         if (sourceFlow == null)
213         {
214             // Entering a flow
215             Map<String, Object> outboundParameters = doBeforeEnterFlow(context, 
216                 targetFlow, !outboundCallNodeProcessed ? outboundCallNode : null);
217             outboundCallNodeProcessed = true;
218             pushFlowReference(context, clientWindow, 
219                     new FlowReference(targetFlow.getDefiningDocumentId(), targetFlow.getId()), 
220                     toViewId, parentFlowReference);
221             doAfterEnterFlow(context, targetFlow, outboundParameters);
222         }
223         else if (targetFlow == null)
224         {
225             // Getting out of the flow, since targetFlow is null,
226             // we need to take sourceFlow and take it out and all the chain
227             List<_FlowContextualInfo> currentFlowStack = getCurrentFlowStack(context, clientWindow);
228             if (currentFlowStack != null)
229             {
230                 removeFlowFromStack(context, currentFlowStack, sourceFlow);
231             }
232         }
233         else
234         {
235             // Both sourceFlow and targetFlow are not null, if there is no call node set (force enter flow)
236             // we need to check the direction
237             // If targetFlow is on the stack, remove elements until get there.
238             // If targetFlow is not there, add it to the stack.
239             List<_FlowContextualInfo> currentFlowStack = getCurrentFlowStack(context, clientWindow);
240             if (currentFlowStack != null && outboundCallNode == null)
241             {
242                 FlowReference targetFlowReference = new FlowReference(
243                         targetFlow.getDefiningDocumentId(), targetFlow.getId());
244                 int targetFlowIndex = -1;
245                 for (int j = currentFlowStack.size()-1; j >= 0; j--)
246                 {
247                     if (targetFlowReference.equals(currentFlowStack.get(j).getFlowReference()))
248                     {
249                         targetFlowIndex = j;
250                         break;
251                     }
252                 }
253                 if (targetFlowIndex >= 0)
254                 {
255                     // targetFlow is on the stack, so it is a return.
256                     removeFlowFromStack(context, currentFlowStack, sourceFlow);
257                 }
258                 else
259                 {
260                     // targetFlow is not on the stack, so it is flow call.
261                     Map<String, Object> outboundParameters = doBeforeEnterFlow(context,
262                         targetFlow, !outboundCallNodeProcessed ? outboundCallNode : null);
263                     outboundCallNodeProcessed = true;
264                     pushFlowReference(context, clientWindow, 
265                             new FlowReference(targetFlow.getDefiningDocumentId(), targetFlow.getId()), toViewId,
266                             parentFlowReference);
267                     doAfterEnterFlow(context, targetFlow, outboundParameters);
268                 }
269             }
270             else
271             {
272                 // sourceFlow and targetFlow are not null, but there is no currentFlowStack. It that
273                 // case just enter into targetFlow
274                 Map<String, Object> outboundParameters = doBeforeEnterFlow(context, 
275                     targetFlow, !outboundCallNodeProcessed ? outboundCallNode : null);
276                 outboundCallNodeProcessed = true;
277                 pushFlowReference(context, clientWindow, 
278                         new FlowReference(targetFlow.getDefiningDocumentId(), targetFlow.getId()), toViewId,
279                         parentFlowReference);
280                 doAfterEnterFlow(context, targetFlow, outboundParameters);
281             }
282         }
283     }
284     
285     private void removeFlowFromStack(FacesContext context, List<_FlowContextualInfo> currentFlowStack, Flow sourceFlow)
286     {
287         // Steps to remove a flow:
288         // 1. locate where is the flow in the chain
289         int sourceFlowIndex = -1;
290         FlowReference sourceFlowReference = new FlowReference(sourceFlow.getDefiningDocumentId(),
291             sourceFlow.getId());
292         List<_FlowContextualInfo> flowsToRemove = new ArrayList<_FlowContextualInfo>();
293         for (int i = currentFlowStack.size()-1; i >= 0; i--)
294         {
295             _FlowContextualInfo fci = currentFlowStack.get(i);
296             if (fci.getFlowReference().equals(sourceFlowReference))
297             {
298                 sourceFlowIndex = i;
299                 flowsToRemove.add(fci);
300                 break;
301             }
302         }
303 
304         if (sourceFlowIndex != -1)
305         {
306             // From sourceFlowIndex, remove(add to flowsToRemove list) all flows 
307             traverseDependantFlows(sourceFlowReference, sourceFlowIndex+1, currentFlowStack, flowsToRemove);
308 
309             // Remove all marked elements
310             if (!flowsToRemove.isEmpty())
311             {
312                 for (int i = flowsToRemove.size()-1; i >= 0; i--)
313                 {
314                     _FlowContextualInfo fci = flowsToRemove.get(i);
315                     FlowReference fr = fci.getFlowReference();
316                     doBeforeExitFlow(context, getFlow(context, fr.getDocumentId(), fr.getId()));
317                     //popFlowReference(context, clientWindow, currentFlowStack, i);
318 
319                     //Remove flows from the last to the first to keep the right sequence.
320                     for (int j = currentFlowStack.size()-1; j >= 0; j--)
321                     {
322                         if (currentFlowStack.get(j) == fci)
323                         {
324                             currentFlowStack.remove(j);
325                             break;
326                         }
327                     }
328                 }
329             }
330 
331             if (currentFlowStack.isEmpty())
332             {
333                 // Remove it from session but keep it in request scope.
334                 context.getAttributes().put(ROOT_LAST_VIEW_ID, 
335                     context.getExternalContext().getSessionMap().remove(ROOT_LAST_VIEW_ID + 
336                     context.getExternalContext().getClientWindow().getId()));
337             }
338         }
339     }
340     
341     private void traverseDependantFlows(FlowReference sourceFlowReference, 
342         int index, List<_FlowContextualInfo> currentFlowStack, List<_FlowContextualInfo> flowsToRemove)
343     {
344         if (index < currentFlowStack.size())
345         {
346             for (int i = index; i < currentFlowStack.size(); i++)
347             {
348                 _FlowContextualInfo info = currentFlowStack.get(i);
349                 if (sourceFlowReference.equals(info.getSourceFlowReference()) &&
350                     !flowsToRemove.contains(info))
351                 {
352                     flowsToRemove.add(info);
353                     traverseDependantFlows(info.getFlowReference(), i+1, currentFlowStack, flowsToRemove);
354                 }
355             }
356         }
357     }
358     
359     private Map<String, Object> doBeforeEnterFlow(FacesContext context, Flow flow, FlowCallNode outboundCallNode)
360     {
361         Map<String, Object> outboundParameters = null;
362         if (outboundCallNode != null && !outboundCallNode.getOutboundParameters().isEmpty())
363         {
364             outboundParameters = new HashMap<String, Object>();
365             for (Map.Entry<String, Parameter> entry : outboundCallNode.getOutboundParameters().entrySet())
366             {
367                 Parameter parameter = entry.getValue();
368                 if (parameter.getValue() != null)
369                 {
370                     outboundParameters.put(entry.getKey(), parameter.getValue().getValue(context.getELContext()));
371                 }
372             }
373         }
374         return outboundParameters;
375     }
376     
377     private void doAfterEnterFlow(FacesContext context, Flow flow, Map<String, Object> outboundParameters)
378     {
379         getFacesFlowProvider(context).doAfterEnterFlow(context, flow);
380         
381         if (outboundParameters != null)
382         {
383             for (Map.Entry<String, Parameter> entry : flow.getInboundParameters().entrySet())
384             {
385                 Parameter parameter = entry.getValue();
386                 if (parameter.getValue() != null && outboundParameters.containsKey(entry.getKey()))
387                 {
388                     parameter.getValue().setValue(context.getELContext(), outboundParameters.get(entry.getKey()));
389                 }
390             }
391         }
392 
393         if (flow.getInitializer() != null)
394         {
395             flow.getInitializer().invoke(context.getELContext(), null);
396         }
397     }
398     
399     public FacesFlowProvider getFacesFlowProvider(FacesContext facesContext)
400     {
401         if (_facesFlowProvider == null)
402         {
403             FacesFlowProviderFactory factory = 
404                 FacesFlowProviderFactory.getFacesFlowProviderFactory(
405                     facesContext.getExternalContext());
406             _facesFlowProvider = factory.getFacesFlowProvider(
407                     facesContext.getExternalContext());
408             
409             facesContext.getApplication().unsubscribeFromEvent(PostClientWindowAndViewInitializedEvent.class, this);
410             facesContext.getApplication().subscribeToEvent(PostClientWindowAndViewInitializedEvent.class, this);
411         }
412         return _facesFlowProvider;
413     }
414     
415     private void doBeforeExitFlow(FacesContext context, Flow flow)
416     {
417         if (flow.getFinalizer() != null)
418         {
419             flow.getFinalizer().invoke(context.getELContext(), null);
420         }
421         
422         getFacesFlowProvider(context).doBeforeExitFlow(context, flow);
423     }
424 
425     @Override
426     public boolean isActive(FacesContext context, String definingDocumentId, String id)
427     {
428         checkNull(context, "context");
429         checkNull(definingDocumentId, "definingDocumentId");
430         checkNull(id, "id");
431         
432         Object session = context.getExternalContext().getSession(false);
433         if (session == null)
434         {
435             return false;
436         }
437         ClientWindow clientWindow = context.getExternalContext().getClientWindow();
438         if (clientWindow == null)
439         {
440             return false;
441         }
442         Map<String, Object> sessionMap = context.getExternalContext().getSessionMap();
443         String currentFlowMapKey = CURRENT_FLOW_STACK + clientWindow.getId();
444 
445         List<_FlowContextualInfo> currentFlowStack = (List<_FlowContextualInfo>) sessionMap.get(currentFlowMapKey);
446         if (currentFlowStack == null)
447         {
448             return false;
449         }
450         FlowReference reference = new FlowReference(definingDocumentId, id);
451         
452         for (_FlowContextualInfo info : currentFlowStack)
453         {
454             if (reference.equals(info.getFlowReference()))
455             {
456                 return true;
457             }
458         }
459         return false;
460     }
461 
462     @Override
463     public Map<Object, Object> getCurrentFlowScope()
464     {
465         FacesContext facesContext = FacesContext.getCurrentInstance();
466         return getFacesFlowProvider(facesContext).getCurrentFlowScope(facesContext);
467     }
468 
469     /**
470      * The interpretation done for this issue is this:
471      * 
472      * There are two basic cases: Enter into a flow and return from a flow.
473      * 
474      * - FlowHandler.TO_FLOW_DOCUMENT_ID_REQUEST_PARAM_NAME : value of the toFlowDocumentId property 
475      *   of the navigation case when enter into a flow OR FlowHandler.NULL_FLOW when return from a flow.
476      * 
477      * - FlowHandler.FLOW_ID_REQUEST_PARAM_NAME : value of the fromOutcome property of the navigation case.
478      * According to the intention it has multiple options:
479      * 
480      *  1. It can be a flowId, which means enter into a flow.
481      *  2. It can be a flow call id, which means enter into a flow.
482      *  3. It can be a flow return id, which means return from a flow.
483 
484      * - The javadoc of NavigationCase.getToFlowDocumentId() says this:
485      * "... If this navigation case represents a flow invocation, this property is the documentId in 
486      * which the flow whose id is given by the return from getFromOutcome() is defined. Implementations 
487      * must override this method to return the value defined in the corresponding application 
488      * configuration resources element. The base implementation returns the empty string. ..."
489      * 
490      * This is consistent with the previous interpretation, but we need to include the case where 
491      * toFlowDocumentId is FlowHandler.NULL_FLOW too, which is derived implicitly. The key of the trick 
492      * is override fromOutcome / toFlowDocumentId in the navigation algorithm to indicate when the 
493      * navigation case is entering into a flow or return from a flow. In that way, it is possible 
494      * to use ConfigurableNavigationHandler.getNavigationCase(...) to know the "route" using the 
495      * initial fromOutcome given in FLOW_ID_REQUEST_PARAM_NAME.
496      * 
497      * @param context 
498      */
499     @Override
500     public void clientWindowTransition(FacesContext context)
501     {
502         String flowDocumentIdRequestParam = (String) context.getExternalContext().
503             getRequestParameterMap().get(FlowHandler.TO_FLOW_DOCUMENT_ID_REQUEST_PARAM_NAME);
504         
505         if (flowDocumentIdRequestParam != null)
506         {
507             String flowIdRequestParam = (String) context.getExternalContext().
508                 getRequestParameterMap().get(FlowHandler.FLOW_ID_REQUEST_PARAM_NAME);
509             
510             if (flowIdRequestParam == null)
511             {
512                 // If we don't have an fromOutcome, it is not possible to calculate the transitions
513                 // involved.
514                 return;
515             }
516             
517             FlowHandler flowHandler = context.getApplication().getFlowHandler();
518             ConfigurableNavigationHandler nh = 
519                 (ConfigurableNavigationHandler) context.getApplication().getNavigationHandler();
520             
521             if (FlowHandler.NULL_FLOW.equals(flowDocumentIdRequestParam))
522             {
523                 // It is a return node. The trick here is we need to calculate
524                 // where the flow should return, because that information was not passed
525                 // in the parameters of the link. 
526                 String toFlowDocumentId = FlowHandler.NULL_FLOW;
527                 String fromOutcome = flowIdRequestParam;
528                 //Flow sourceFlow = null;
529                 List<Flow> sourceFlows = null;
530                 List<Flow> targetFlows = null;
531                 
532                 boolean failed = false;
533                 int i = 0;
534                 while (FlowHandler.NULL_FLOW.equals(toFlowDocumentId) && !failed)
535                 {
536                     Flow currentFlow = flowHandler.getCurrentFlow(context);
537                     if (currentFlow == null)
538                     {
539                         failed = true;
540                         break;
541                     }
542                     String currentLastDisplayedViewId = flowHandler.getLastDisplayedViewId(context);
543                     FlowNode node = currentFlow.getNode(fromOutcome);
544                     if (node instanceof ReturnNode)
545                     {
546                         if (targetFlows == null)
547                         {
548                             sourceFlows = new ArrayList<Flow>(4);
549                             targetFlows = new ArrayList<Flow>(4);
550                         }
551                         // Get the navigation case using the outcome
552                         Flow sourceFlow = currentFlow;
553                         flowHandler.pushReturnMode(context);
554                         currentFlow = flowHandler.getCurrentFlow(context);
555                         i++;
556                         
557                         NavigationCase navCase = nh.getNavigationCase(context, null, 
558                             ((ReturnNode) node).getFromOutcome(context), FlowHandler.NULL_FLOW);
559 
560                         if (navCase == null)
561                         {
562                             if (currentLastDisplayedViewId != null)
563                             {
564                                 sourceFlows.add(sourceFlow);
565                                 if (currentFlow != null)
566                                 {
567                                     toFlowDocumentId = currentFlow.getDefiningDocumentId();
568                                     targetFlows.add(currentFlow);
569                                 }
570                                 else
571                                 {
572                                     // No active flow
573                                     toFlowDocumentId = null;
574                                     targetFlows.add(null);
575                                 }
576                             }
577                             else
578                             {
579                                 // Invalid state because no navCase and 
580                                 // no saved lastDisplayedViewId into session
581                                 failed = true;
582                             }
583                         }
584                         else
585                         {
586                             if (FlowHandler.NULL_FLOW.equals(navCase.getToFlowDocumentId()))
587                             {
588                                 fromOutcome = navCase.getFromOutcome();
589                             }
590                             else
591                             {
592                                 sourceFlows.add(sourceFlow);
593                                 // The absence of FlowHandler.NULL_FLOW means the return went somewhere else.
594                                 if (currentFlow != null)
595                                 {
596                                     toFlowDocumentId = currentFlow.getDefiningDocumentId();
597                                     targetFlows.add(currentFlow);
598                                 }
599                                 else
600                                 {
601                                     // No active flow
602                                     toFlowDocumentId = null;
603                                     targetFlows.add(null);
604                                 }
605                             }
606                         }
607                     }
608                     else
609                     {
610                         // No return node found in current flow, push it and check 
611                         // the next flow
612                         flowHandler.pushReturnMode(context);
613                         currentFlow = flowHandler.getCurrentFlow(context);
614                         i++;
615                         if (currentFlow == null)
616                         {
617                             failed = true;
618                         }
619                     }
620                 }
621                 for (int j = 0; j<i; j++)
622                 {
623                     flowHandler.popReturnMode(context);
624                 }
625                 if (!failed)
626                 {
627                     //Call transitions.
628                     for (int j = 0; j < targetFlows.size(); j++)
629                     {
630                         Flow sourceFlow = sourceFlows.get(j);
631                         Flow targetFlow = targetFlows.get(j);
632                         flowHandler.transition(context, 
633                             sourceFlow,
634                             targetFlow, null, context.getViewRoot().getViewId());
635                         
636                     }
637                 }
638             }
639             else
640             {
641                 // This transition is for start a new flow. In this case 
642                 // FlowHandler.FLOW_ID_REQUEST_PARAM_NAME could be the flow name to enter
643                 // or the flow call node to activate.
644                 // 1. check if is a flow
645                 Flow targetFlow = flowHandler.getFlow(context, flowDocumentIdRequestParam, flowIdRequestParam);
646                 Flow currentFlow = null;
647                 FlowCallNode outboundCallNode = null;
648                 FlowNode node = null;
649                 if (targetFlow == null)
650                 {
651                     //Check if is a call flow node
652                     List<Flow> activeFlows = FlowHandlerImpl.getActiveFlows(context, flowHandler);
653                     for (Flow activeFlow : activeFlows)
654                     {
655                         node = activeFlow != null ? activeFlow.getNode(flowIdRequestParam) : null;
656                         if (node != null && node instanceof FlowCallNode)
657                         {
658                             outboundCallNode = (FlowCallNode) node;
659 
660                             String calledFlowDocumentId = outboundCallNode.getCalledFlowDocumentId(context);
661                             if (calledFlowDocumentId == null)
662                             {
663                                 calledFlowDocumentId = activeFlow.getDefiningDocumentId();
664                             }
665                             targetFlow = flowHandler.getFlow(context, 
666                                 calledFlowDocumentId, 
667                                 outboundCallNode.getCalledFlowId(context));
668                             if (targetFlow == null && !"".equals(calledFlowDocumentId))
669                             {
670                                 targetFlow = flowHandler.getFlow(context, "", 
671                                     outboundCallNode.getCalledFlowId(context));
672                             }
673                             if (targetFlow != null)
674                             {
675                                 currentFlow = activeFlow;
676                                 break;
677                             }
678                         }
679                     }
680                 }
681                 
682                 if (targetFlow != null)
683                 {
684                     if (flowHandler.isActive(context, targetFlow.getDefiningDocumentId(), targetFlow.getId()))
685                     {
686                         Flow baseReturnFlow = flowHandler.getCurrentFlow();
687                         if (!(baseReturnFlow.getDefiningDocumentId().equals(targetFlow.getDefiningDocumentId()) &&
688                              baseReturnFlow.getId().equals(targetFlow.getId())))
689                         {
690                             flowHandler.transition(context, 
691                                 baseReturnFlow, targetFlow, outboundCallNode, context.getViewRoot().getViewId());
692                         }
693                         flowHandler.pushReturnMode(context);
694                         Flow previousFlow = flowHandler.getCurrentFlow(context);
695                         flowHandler.popReturnMode(context);
696                         flowHandler.transition(context, 
697                                 targetFlow, previousFlow, outboundCallNode, context.getViewRoot().getViewId());
698                     }
699                     // Invoke transition
700                     flowHandler.transition(context, 
701                         currentFlow, targetFlow, outboundCallNode, context.getViewRoot().getViewId());
702 
703                     // Handle 2 or more flow consecutive start.
704                     boolean failed = false;
705                     
706                     String startNodeId = targetFlow.getStartNodeId();
707                     while (startNodeId != null && !failed)
708                     {
709                         NavigationCase navCase = nh.getNavigationCase(context, null, 
710                                     startNodeId, targetFlow.getDefiningDocumentId());
711                         
712                         if (navCase != null && navCase.getToFlowDocumentId() != null)
713                         {
714                             currentFlow = flowHandler.getCurrentFlow(context);
715                             node = currentFlow.getNode(navCase.getFromOutcome());
716                             if (node != null && node instanceof FlowCallNode)
717                             {
718                                 outboundCallNode = (FlowCallNode) node;
719                                 
720                                 String calledFlowDocumentId = outboundCallNode.getCalledFlowDocumentId(context);
721                                 if (calledFlowDocumentId == null)
722                                 {
723                                     calledFlowDocumentId = currentFlow.getDefiningDocumentId();
724                                 }
725                                 targetFlow = flowHandler.getFlow(context, 
726                                     calledFlowDocumentId, 
727                                     outboundCallNode.getCalledFlowId(context));
728                                 if (targetFlow == null && !"".equals(calledFlowDocumentId))
729                                 {
730                                     targetFlow = flowHandler.getFlow(context, "", 
731                                         outboundCallNode.getCalledFlowId(context));
732                                 }
733                             }
734                             else
735                             {
736                                 String calledFlowDocumentId = navCase.getToFlowDocumentId();
737                                 if (calledFlowDocumentId == null)
738                                 {
739                                     calledFlowDocumentId = currentFlow.getDefiningDocumentId();
740                                 }
741                                 targetFlow = flowHandler.getFlow(context, 
742                                     calledFlowDocumentId, 
743                                     navCase.getFromOutcome());
744                                 if (targetFlow == null && !"".equals(calledFlowDocumentId))
745                                 {
746                                     targetFlow = flowHandler.getFlow(context, "", 
747                                         navCase.getFromOutcome());
748                                 }
749                             }
750                             if (targetFlow != null)
751                             {
752                                 flowHandler.transition(context, 
753                                     currentFlow, targetFlow, outboundCallNode, context.getViewRoot().getViewId());
754                                 startNodeId = targetFlow.getStartNodeId();
755                             }
756                             else
757                             {
758                                 startNodeId = null;
759                             }
760                         }
761                         else
762                         {
763                             startNodeId = null;
764                         }
765                     }
766                 }
767                 
768             }
769         }
770     }
771     
772     private void checkNull(final Object o, final String param)
773     {
774         if (o == null)
775         {
776             throw new NullPointerException(param + " can not be null.");
777         }
778     }
779     
780     private void invokeInspectFlow(FacesContext context, NavigationHandler navHandler, Flow toAdd)
781     {
782         if (navHandler instanceof ConfigurableNavigationHandler)
783         {
784             ((ConfigurableNavigationHandler)navHandler).inspectFlow(context, toAdd);
785         }
786         else if (navHandler instanceof NavigationHandlerWrapper)
787         {
788             invokeInspectFlow(context, ((NavigationHandlerWrapper)navHandler).getWrapped(), toAdd);
789         }
790     }
791     
792     private _FlowContextualInfo getCurrentFlowReference(FacesContext context, ClientWindow clientWindow)
793     {
794         if ( Boolean.TRUE.equals(context.getAttributes().get(RETURN_MODE)) )
795         {
796             List<_FlowContextualInfo> returnFlowList = getCurrentReturnModeFlowStack(
797                     context, clientWindow, CURRENT_FLOW_REQUEST_STACK);
798             if (returnFlowList != null && !returnFlowList.isEmpty())
799             {
800                 _FlowContextualInfo info = returnFlowList.get(returnFlowList.size()-1);
801                 return info;
802             }
803             return null;
804         }
805         else
806         {
807             Map<String, Object> sessionMap = context.getExternalContext().getSessionMap();
808             String currentFlowMapKey = CURRENT_FLOW_STACK + clientWindow.getId();
809             List<_FlowContextualInfo> currentFlowStack = 
810                 (List<_FlowContextualInfo>) sessionMap.get(currentFlowMapKey);
811             if (currentFlowStack == null)
812             {
813                 return null;
814             }
815             return currentFlowStack.size() > 0 ? 
816                 currentFlowStack.get(currentFlowStack.size()-1) : null;
817         }
818     }
819     
820     private void pushFlowReference(FacesContext context, ClientWindow clientWindow, FlowReference flowReference,
821         String toViewId, FlowReference sourceFlowReference)
822     {
823         Map<String, Object> sessionMap = context.getExternalContext().getSessionMap();
824         String currentFlowMapKey = CURRENT_FLOW_STACK + clientWindow.getId();
825         List<_FlowContextualInfo> currentFlowStack = (List<_FlowContextualInfo>) sessionMap.get(currentFlowMapKey);
826         if (currentFlowStack == null)
827         {
828             currentFlowStack = new ArrayList<_FlowContextualInfo>(4);
829             sessionMap.put(currentFlowMapKey, currentFlowStack);
830         }
831         if (!currentFlowStack.isEmpty())
832         {
833             currentFlowStack.get(currentFlowStack.size()-1).setLastDisplayedViewId(context.getViewRoot().getViewId());
834         }
835         else
836         {
837             //Save root lastDisplayedViewId
838             context.getExternalContext().getSessionMap().put(ROOT_LAST_VIEW_ID + clientWindow.getId(), 
839                 context.getViewRoot().getViewId());
840         }
841         currentFlowStack.add(new _FlowContextualInfo(flowReference, toViewId, sourceFlowReference));
842     }
843     
844     private List<_FlowContextualInfo> getCurrentFlowStack(FacesContext context, ClientWindow clientWindow)
845     {
846         Map<String, Object> sessionMap = context.getExternalContext().getSessionMap();
847         String currentFlowMapKey = CURRENT_FLOW_STACK + clientWindow.getId();
848         List<_FlowContextualInfo> currentFlowStack = (List<_FlowContextualInfo>) sessionMap.get(currentFlowMapKey);
849         return currentFlowStack;
850     }
851 
852     @Override
853     public String getLastDisplayedViewId(FacesContext context)
854     {
855         Object session = context.getExternalContext().getSession(false);
856         if (session == null)
857         {
858             return null;
859         }
860         ClientWindow clientWindow = context.getExternalContext().getClientWindow();
861         if (clientWindow == null)
862         {
863             return null;
864         }
865         
866         _FlowContextualInfo info = getCurrentFlowReference(context, clientWindow);
867         if (info == null)
868         {
869             String lastDisplayedViewId = (String) context.getAttributes().get(ROOT_LAST_VIEW_ID);
870             if (lastDisplayedViewId == null)
871             {
872                 lastDisplayedViewId = (String) context.getExternalContext().getSessionMap().
873                     get(ROOT_LAST_VIEW_ID + clientWindow.getId());
874             }
875             return lastDisplayedViewId;
876         }
877         return info.getLastDisplayedViewId();
878     }
879 
880     @Override
881     public void pushReturnMode(FacesContext context)
882     {
883         // The return mode is a way to allow NavigationHandler to know the context
884         // without expose it. The idea is call pushReturnMode()/popReturnMode() and
885         // then check for getCurrentFlow(). 
886         //
887         // Remember the navigation algorithm is split in two parts:
888         // - Calculates the navigation
889         // - Perform the navigation
890         //
891         // Generated links requires only to perform the first one, but the operations
892         // are only perfomed when the transition between pages occur or in a get request
893         // when there is a pending navigation. 
894         ClientWindow clientWindow = context.getExternalContext().getClientWindow();
895         
896         if (clientWindow == null)
897         {
898             return;
899         }
900         
901         if ( !Boolean.TRUE.equals(context.getAttributes().get(RETURN_MODE)) )
902         {
903             // Return mode not active, activate it, copy the current flow stack.
904             List<_FlowContextualInfo> currentFlowStack = getCurrentFlowStack(context, clientWindow);
905             
906             Map<Object, Object> attributesMap = context.getAttributes();
907             String returnFlowMapKey = CURRENT_FLOW_REQUEST_STACK + clientWindow.getId();
908             List<_FlowContextualInfo> returnFlowStack = new ArrayList<_FlowContextualInfo>(currentFlowStack);
909             attributesMap.put(returnFlowMapKey, returnFlowStack);
910             context.getAttributes().put(RETURN_MODE, Boolean.TRUE);
911         }
912         
913         _FlowContextualInfo flowReference = popFlowReferenceReturnMode(context, 
914             clientWindow, CURRENT_FLOW_REQUEST_STACK);
915         pushFlowReferenceReturnMode(context, clientWindow, FLOW_RETURN_STACK, flowReference);
916     }
917 
918     @Override
919     public void popReturnMode(FacesContext context)
920     {
921         ClientWindow clientWindow = context.getExternalContext().getClientWindow();
922         
923         if (clientWindow == null)
924         {
925             return;
926         }
927         
928         _FlowContextualInfo flowReference = popFlowReferenceReturnMode(context, clientWindow, FLOW_RETURN_STACK);
929         pushFlowReferenceReturnMode(context, clientWindow, CURRENT_FLOW_REQUEST_STACK, flowReference);
930         
931         Map<Object, Object> attributesMap = context.getAttributes();
932         String returnFlowMapKey = FLOW_RETURN_STACK + clientWindow.getId();
933         List<_FlowContextualInfo> returnFlowStack = (List<_FlowContextualInfo>) attributesMap.get(returnFlowMapKey);
934         if (returnFlowStack != null && returnFlowStack.isEmpty())
935         {
936             context.getAttributes().put(RETURN_MODE, Boolean.FALSE);
937         }
938     }
939     
940     public List<Flow> getActiveFlows(FacesContext context)
941     {
942         Object session = context.getExternalContext().getSession(false);
943         if (session == null)
944         {
945             return Collections.emptyList();
946         }
947         ClientWindow clientWindow = context.getExternalContext().getClientWindow();
948         if (clientWindow == null)
949         {
950             return Collections.emptyList();
951         }
952         if ( Boolean.TRUE.equals(context.getAttributes().get(RETURN_MODE)) )
953         {
954             // Use the standard form
955             FlowHandler fh = context.getApplication().getFlowHandler();
956             Flow curFlow = fh.getCurrentFlow(context);
957             if (curFlow != null)
958             {
959                 List<Flow> activeFlows = new ArrayList<Flow>();
960                 while (curFlow != null)
961                 {
962                     activeFlows.add(curFlow);
963                     fh.pushReturnMode(context);
964                     curFlow = fh.getCurrentFlow(context);
965                 }
966 
967                 for (int i = 0; i < activeFlows.size(); i++)
968                 {
969                     fh.popReturnMode(context);
970                 }
971                 return activeFlows;
972             }
973             else
974             {
975                 return Collections.emptyList();
976             }
977         }
978         else
979         {
980             Map<String, Object> sessionMap = context.getExternalContext().getSessionMap();
981             String currentFlowMapKey = CURRENT_FLOW_STACK + clientWindow.getId();
982 
983             List<_FlowContextualInfo> currentFlowStack = (List<_FlowContextualInfo>) sessionMap.get(currentFlowMapKey);
984             if (currentFlowStack == null)
985             {
986                 return Collections.emptyList();
987             }
988 
989             if (!currentFlowStack.isEmpty())
990             {
991                 List<Flow> activeFlows = new ArrayList<Flow>();
992                 for(_FlowContextualInfo info : currentFlowStack)
993                 {
994                     activeFlows.add(0, getFlow(context, 
995                         info.getFlowReference().getDocumentId(), 
996                         info.getFlowReference().getId()));
997                 }
998                 return activeFlows;
999             }
1000 
1001             return Collections.emptyList();
1002         }
1003     }
1004 
1005     private void pushFlowReferenceReturnMode(FacesContext context, ClientWindow clientWindow,
1006             String stackKey, _FlowContextualInfo flowReference)
1007     {
1008         Map<Object, Object> attributesMap = context.getAttributes();
1009         String currentFlowMapKey = stackKey + clientWindow.getId();
1010         List<_FlowContextualInfo> currentFlowStack = (List<_FlowContextualInfo>) attributesMap.get(currentFlowMapKey);
1011         if (currentFlowStack == null)
1012         {
1013             currentFlowStack = new ArrayList<_FlowContextualInfo>(4);
1014             attributesMap.put(currentFlowMapKey, currentFlowStack);
1015         }
1016         currentFlowStack.add(flowReference);
1017     }
1018 
1019     private _FlowContextualInfo popFlowReferenceReturnMode(FacesContext context, ClientWindow clientWindow,
1020             String stackKey)
1021     {
1022         Map<Object, Object> attributesMap = context.getAttributes();
1023         String currentFlowMapKey = stackKey + clientWindow.getId();
1024         List<_FlowContextualInfo> currentFlowStack = (List<_FlowContextualInfo>) attributesMap.get(currentFlowMapKey);
1025         if (currentFlowStack == null)
1026         {
1027             return null;
1028         }
1029         return currentFlowStack.size() > 0 ? currentFlowStack.remove(currentFlowStack.size()-1) : null;
1030     }
1031     
1032     private List<_FlowContextualInfo> getCurrentReturnModeFlowStack(FacesContext context, ClientWindow clientWindow,
1033             String stackKey)
1034     {
1035         Map<Object, Object> attributesMap = context.getAttributes();
1036         String currentFlowMapKey = stackKey + clientWindow.getId();
1037         List<_FlowContextualInfo> currentFlowStack = (List<_FlowContextualInfo>) attributesMap.get(currentFlowMapKey);
1038         return currentFlowStack;
1039     }
1040     
1041     public static List<Flow> getActiveFlows(FacesContext facesContext, FlowHandler fh)
1042     {
1043         FlowHandler flowHandler = fh;
1044         while (flowHandler != null)
1045         {
1046             if (flowHandler instanceof FlowHandlerImpl)
1047             {
1048                 break;
1049             }
1050             else if (flowHandler instanceof FacesWrapper)
1051             {
1052                 flowHandler = ((FacesWrapper<FlowHandler>)flowHandler).getWrapped();
1053             }
1054             else
1055             {
1056                 flowHandler = null;
1057             }
1058         }
1059         if (flowHandler == null)
1060         {
1061             // Use the standard form
1062             Flow curFlow = fh.getCurrentFlow(facesContext);
1063             if (curFlow != null)
1064             {
1065                 List<Flow> activeFlows = new ArrayList<Flow>();
1066                 while (curFlow != null)
1067                 {
1068                     activeFlows.add(curFlow);
1069                     fh.pushReturnMode(facesContext);
1070                     curFlow = fh.getCurrentFlow(facesContext);
1071                 }
1072 
1073                 for (int i = 0; i < activeFlows.size(); i++)
1074                 {
1075                     fh.popReturnMode(facesContext);
1076                 }
1077                 return activeFlows;
1078             }
1079             else
1080             {
1081                 return Collections.emptyList();
1082             }
1083         }
1084         else
1085         {
1086             FlowHandlerImpl flowHandlerImpl = (FlowHandlerImpl) flowHandler;
1087             return flowHandlerImpl.getActiveFlows(facesContext);
1088         }
1089     }
1090 
1091     @Override
1092     public boolean isListenerForSource(Object source)
1093     {
1094         return source instanceof ClientWindow;
1095     }
1096 
1097     @Override
1098     public void processEvent(SystemEvent event)
1099     {
1100         // refresh client window to faces flow provider
1101         FacesContext facesContext = FacesContext.getCurrentInstance();
1102         FacesFlowProvider provider = getFacesFlowProvider(facesContext);
1103         provider.refreshClientWindow(facesContext);
1104     }
1105 
1106 }