blob: 94f97080c6dd5fb58dc7a5a41da0a7eaee4610e4 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.myfaces.flow;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.faces.FacesWrapper;
import javax.faces.application.ConfigurableNavigationHandler;
import javax.faces.application.NavigationCase;
import javax.faces.application.NavigationHandler;
import javax.faces.application.NavigationHandlerWrapper;
import javax.faces.context.FacesContext;
import javax.faces.event.SystemEvent;
import javax.faces.event.SystemEventListener;
import javax.faces.flow.Flow;
import javax.faces.flow.FlowCallNode;
import javax.faces.flow.FlowHandler;
import javax.faces.flow.FlowNode;
import javax.faces.flow.Parameter;
import javax.faces.flow.ReturnNode;
import javax.faces.lifecycle.ClientWindow;
import org.apache.myfaces.event.PostClientWindowAndViewInitializedEvent;
import org.apache.myfaces.spi.FacesFlowProvider;
import org.apache.myfaces.spi.FacesFlowProviderFactory;
import org.apache.myfaces.util.lang.Assert;
import org.apache.myfaces.util.lang.StringUtils;
/**
*
* @since 2.2
* @author Leonardo Uribe
*/
public class FlowHandlerImpl extends FlowHandler implements SystemEventListener
{
private final static String CURRENT_FLOW_STACK = "oam.flow.STACK.";
private final static String ROOT_LAST_VIEW_ID = "oam.flow.ROOT_LAST_VIEW_ID.";
private final static String RETURN_MODE = "oam.flow.RETURN_MODE";
private final static String FLOW_RETURN_STACK = "oam.flow.RETURN_STACK.";
private final static String CURRENT_FLOW_REQUEST_STACK = "oam.flow.REQUEST_STACK.";
private Map<String, Map<String, Flow>> _flowMapByDocumentId;
private Map<String, Flow> _flowMapById;
private FacesFlowProvider _facesFlowProvider;
public FlowHandlerImpl()
{
_flowMapByDocumentId = new ConcurrentHashMap<String, Map<String, Flow>>();
_flowMapById = new ConcurrentHashMap<String, Flow>();
}
@Override
public Flow getFlow(FacesContext context, String definingDocumentId, String id)
{
Assert.notNull(context, "context");
Assert.notNull(definingDocumentId, "definingDocumentId");
Assert.notNull(id, "id");
// First try the combination.
Map<String, Flow> flowMap = _flowMapByDocumentId.get(definingDocumentId);
if (flowMap != null)
{
Flow flow = flowMap.get(id);
if (flow != null)
{
return flow;
}
}
//if definingDocumentId is an empty string,
if (StringUtils.isEmpty(definingDocumentId))
{
return _flowMapById.get(id);
}
return null;
}
@Override
public void addFlow(FacesContext context, Flow toAdd)
{
Assert.notNull(context, "context");
Assert.notNull(toAdd, "toAdd");
String id = toAdd.getId();
String definingDocumentId = toAdd.getDefiningDocumentId();
if (id == null)
{
throw new IllegalArgumentException("Flow must have a non null id");
}
else if (id.length() == 0)
{
throw new IllegalArgumentException("Flow must have a non empty id");
}
if (definingDocumentId == null)
{
throw new IllegalArgumentException("Flow must have a non null definingDocumentId");
}
Map<String, Flow> flowMap = _flowMapByDocumentId.computeIfAbsent(definingDocumentId,
k -> new ConcurrentHashMap<>());
flowMap.put(id, toAdd);
Flow duplicateFlow = _flowMapById.get(id);
if (duplicateFlow != null)
{
// There are two flows with the same flowId.
// Raise an exception if flows share a duplicate ID and definingDocumentId
if (toAdd.getDefiningDocumentId().equals(duplicateFlow.getDefiningDocumentId()))
{
throw new IllegalArgumentException("There cannot be multiple flows with both the"
+ " same ID and the same definingDocumentId");
}
// Give priority to the flow with no defining document id
else if ("".equals(toAdd.getDefiningDocumentId()))
{
_flowMapById.put(id, toAdd);
}
else if ("".equals(duplicateFlow.getDefiningDocumentId()))
{
// Already added, skip
}
else
{
// Put the last one
_flowMapById.put(id, toAdd);
}
}
else
{
_flowMapById.put(id, toAdd);
}
// Once the flow is added to the map, it is still necessary to
// pass the flow to the ConfigurableNavigationHandler, so it can be
// inspected for navigation rules. This is the best place to do that because
// the spec says "... Called by the flow system to cause the flow to
// be inspected for navigation rules... " (note it says "flow system" not
// "configuration system" where the calls to addFlow() are done).
invokeInspectFlow(context, context.getApplication().getNavigationHandler(), toAdd);
}
@Override
public Flow getCurrentFlow(FacesContext context)
{
Object session = context.getExternalContext().getSession(false);
if (session == null)
{
return null;
}
ClientWindow clientWindow = context.getExternalContext().getClientWindow();
if (clientWindow == null)
{
return null;
}
_FlowContextualInfo info = getCurrentFlowReference(context, clientWindow);
if (info == null)
{
return null;
}
FlowReference flowReference = info.getFlowReference();
return getFlow(context, flowReference.getDocumentId(), flowReference.getId());
}
@Override
public void transition(FacesContext context, Flow sourceFlow, Flow targetFlow,
FlowCallNode outboundCallNode, String toViewId)
{
Assert.notNull(context, "context");
Assert.notNull(toViewId, "toViewId");
ClientWindow clientWindow = context.getExternalContext().getClientWindow();
boolean outboundCallNodeProcessed = false;
if (clientWindow == null)
{
return;
}
if (sourceFlow == null && targetFlow == null)
{
return;
}
// Calculate the parentFlowReference, since it will be used later.
FlowReference parentFlowReference = (outboundCallNode != null && sourceFlow != null) ?
new FlowReference(sourceFlow.getDefiningDocumentId(), sourceFlow.getId()) : null;
if (sourceFlow == null)
{
// Entering a flow
Map<String, Object> outboundParameters = doBeforeEnterFlow(context,
targetFlow, !outboundCallNodeProcessed ? outboundCallNode : null);
outboundCallNodeProcessed = true;
pushFlowReference(context, clientWindow,
new FlowReference(targetFlow.getDefiningDocumentId(), targetFlow.getId()),
toViewId, parentFlowReference);
doAfterEnterFlow(context, targetFlow, outboundParameters);
}
else if (targetFlow == null)
{
// Getting out of the flow, since targetFlow is null,
// we need to take sourceFlow and take it out and all the chain
List<_FlowContextualInfo> currentFlowStack = getCurrentFlowStack(context, clientWindow);
if (currentFlowStack != null)
{
removeFlowFromStack(context, currentFlowStack, sourceFlow);
}
}
else
{
// Both sourceFlow and targetFlow are not null, if there is no call node set (force enter flow)
// we need to check the direction
// If targetFlow is on the stack, remove elements until get there.
// If targetFlow is not there, add it to the stack.
List<_FlowContextualInfo> currentFlowStack = getCurrentFlowStack(context, clientWindow);
if (currentFlowStack != null && outboundCallNode == null)
{
FlowReference targetFlowReference = new FlowReference(
targetFlow.getDefiningDocumentId(), targetFlow.getId());
int targetFlowIndex = -1;
for (int j = currentFlowStack.size()-1; j >= 0; j--)
{
if (targetFlowReference.equals(currentFlowStack.get(j).getFlowReference()))
{
targetFlowIndex = j;
break;
}
}
if (targetFlowIndex >= 0)
{
// targetFlow is on the stack, so it is a return.
removeFlowFromStack(context, currentFlowStack, sourceFlow);
}
else
{
// targetFlow is not on the stack, so it is flow call.
Map<String, Object> outboundParameters = doBeforeEnterFlow(context,
targetFlow, !outboundCallNodeProcessed ? outboundCallNode : null);
outboundCallNodeProcessed = true;
pushFlowReference(context, clientWindow,
new FlowReference(targetFlow.getDefiningDocumentId(), targetFlow.getId()), toViewId,
parentFlowReference);
doAfterEnterFlow(context, targetFlow, outboundParameters);
}
}
else
{
// sourceFlow and targetFlow are not null, but there is no currentFlowStack. It that
// case just enter into targetFlow
Map<String, Object> outboundParameters = doBeforeEnterFlow(context,
targetFlow, !outboundCallNodeProcessed ? outboundCallNode : null);
outboundCallNodeProcessed = true;
pushFlowReference(context, clientWindow,
new FlowReference(targetFlow.getDefiningDocumentId(), targetFlow.getId()), toViewId,
parentFlowReference);
doAfterEnterFlow(context, targetFlow, outboundParameters);
}
}
}
private void removeFlowFromStack(FacesContext context, List<_FlowContextualInfo> currentFlowStack, Flow sourceFlow)
{
// Steps to remove a flow:
// 1. locate where is the flow in the chain
int sourceFlowIndex = -1;
FlowReference sourceFlowReference = new FlowReference(sourceFlow.getDefiningDocumentId(),
sourceFlow.getId());
List<_FlowContextualInfo> flowsToRemove = new ArrayList<_FlowContextualInfo>();
for (int i = currentFlowStack.size()-1; i >= 0; i--)
{
_FlowContextualInfo fci = currentFlowStack.get(i);
if (fci.getFlowReference().equals(sourceFlowReference))
{
sourceFlowIndex = i;
flowsToRemove.add(fci);
break;
}
}
if (sourceFlowIndex != -1)
{
// From sourceFlowIndex, remove(add to flowsToRemove list) all flows
traverseDependantFlows(sourceFlowReference, sourceFlowIndex+1, currentFlowStack, flowsToRemove);
// Remove all marked elements
if (!flowsToRemove.isEmpty())
{
for (int i = flowsToRemove.size()-1; i >= 0; i--)
{
_FlowContextualInfo fci = flowsToRemove.get(i);
FlowReference fr = fci.getFlowReference();
doBeforeExitFlow(context, getFlow(context, fr.getDocumentId(), fr.getId()));
//popFlowReference(context, clientWindow, currentFlowStack, i);
//Remove flows from the last to the first to keep the right sequence.
for (int j = currentFlowStack.size()-1; j >= 0; j--)
{
if (currentFlowStack.get(j) == fci)
{
currentFlowStack.remove(j);
break;
}
}
}
}
if (currentFlowStack.isEmpty())
{
// Remove it from session but keep it in request scope.
context.getAttributes().put(ROOT_LAST_VIEW_ID,
context.getExternalContext().getSessionMap().remove(ROOT_LAST_VIEW_ID +
context.getExternalContext().getClientWindow().getId()));
}
}
}
private void traverseDependantFlows(FlowReference sourceFlowReference,
int index, List<_FlowContextualInfo> currentFlowStack, List<_FlowContextualInfo> flowsToRemove)
{
if (index < currentFlowStack.size())
{
for (int i = index; i < currentFlowStack.size(); i++)
{
_FlowContextualInfo info = currentFlowStack.get(i);
if (sourceFlowReference.equals(info.getSourceFlowReference()) &&
!flowsToRemove.contains(info))
{
flowsToRemove.add(info);
traverseDependantFlows(info.getFlowReference(), i+1, currentFlowStack, flowsToRemove);
}
}
}
}
private Map<String, Object> doBeforeEnterFlow(FacesContext context, Flow flow, FlowCallNode outboundCallNode)
{
Map<String, Object> outboundParameters = null;
if (outboundCallNode != null && !outboundCallNode.getOutboundParameters().isEmpty())
{
outboundParameters = new HashMap<String, Object>();
for (Map.Entry<String, Parameter> entry : outboundCallNode.getOutboundParameters().entrySet())
{
Parameter parameter = entry.getValue();
if (parameter.getValue() != null)
{
outboundParameters.put(entry.getKey(), parameter.getValue().getValue(context.getELContext()));
}
}
}
return outboundParameters;
}
private void doAfterEnterFlow(FacesContext context, Flow flow, Map<String, Object> outboundParameters)
{
getFacesFlowProvider(context).doAfterEnterFlow(context, flow);
if (outboundParameters != null)
{
for (Map.Entry<String, Parameter> entry : flow.getInboundParameters().entrySet())
{
Parameter parameter = entry.getValue();
if (parameter.getValue() != null && outboundParameters.containsKey(entry.getKey()))
{
parameter.getValue().setValue(context.getELContext(), outboundParameters.get(entry.getKey()));
}
}
}
if (flow.getInitializer() != null)
{
flow.getInitializer().invoke(context.getELContext(), null);
}
}
public FacesFlowProvider getFacesFlowProvider(FacesContext facesContext)
{
if (_facesFlowProvider == null)
{
FacesFlowProviderFactory factory =
FacesFlowProviderFactory.getFacesFlowProviderFactory(
facesContext.getExternalContext());
_facesFlowProvider = factory.getFacesFlowProvider(
facesContext.getExternalContext());
facesContext.getApplication().unsubscribeFromEvent(PostClientWindowAndViewInitializedEvent.class, this);
facesContext.getApplication().subscribeToEvent(PostClientWindowAndViewInitializedEvent.class, this);
}
return _facesFlowProvider;
}
private void doBeforeExitFlow(FacesContext context, Flow flow)
{
if (flow.getFinalizer() != null)
{
flow.getFinalizer().invoke(context.getELContext(), null);
}
getFacesFlowProvider(context).doBeforeExitFlow(context, flow);
}
@Override
public boolean isActive(FacesContext context, String definingDocumentId, String id)
{
Assert.notNull(context, "context");
Assert.notNull(definingDocumentId, "definingDocumentId");
Assert.notNull(id, "id");
Object session = context.getExternalContext().getSession(false);
if (session == null)
{
return false;
}
ClientWindow clientWindow = context.getExternalContext().getClientWindow();
if (clientWindow == null)
{
return false;
}
Map<String, Object> sessionMap = context.getExternalContext().getSessionMap();
String currentFlowMapKey = CURRENT_FLOW_STACK + clientWindow.getId();
List<_FlowContextualInfo> currentFlowStack = (List<_FlowContextualInfo>) sessionMap.get(currentFlowMapKey);
if (currentFlowStack == null)
{
return false;
}
FlowReference reference = new FlowReference(definingDocumentId, id);
for (_FlowContextualInfo info : currentFlowStack)
{
if (reference.equals(info.getFlowReference()))
{
return true;
}
}
return false;
}
@Override
public Map<Object, Object> getCurrentFlowScope()
{
FacesContext facesContext = FacesContext.getCurrentInstance();
return getFacesFlowProvider(facesContext).getCurrentFlowScope(facesContext);
}
/**
* The interpretation done for this issue is this:
*
* There are two basic cases: Enter into a flow and return from a flow.
*
* - FlowHandler.TO_FLOW_DOCUMENT_ID_REQUEST_PARAM_NAME : value of the toFlowDocumentId property
* of the navigation case when enter into a flow OR FlowHandler.NULL_FLOW when return from a flow.
*
* - FlowHandler.FLOW_ID_REQUEST_PARAM_NAME : value of the fromOutcome property of the navigation case.
* According to the intention it has multiple options:
*
* 1. It can be a flowId, which means enter into a flow.
* 2. It can be a flow call id, which means enter into a flow.
* 3. It can be a flow return id, which means return from a flow.
* - The javadoc of NavigationCase.getToFlowDocumentId() says this:
* "... If this navigation case represents a flow invocation, this property is the documentId in
* which the flow whose id is given by the return from getFromOutcome() is defined. Implementations
* must override this method to return the value defined in the corresponding application
* configuration resources element. The base implementation returns the empty string. ..."
*
* This is consistent with the previous interpretation, but we need to include the case where
* toFlowDocumentId is FlowHandler.NULL_FLOW too, which is derived implicitly. The key of the trick
* is override fromOutcome / toFlowDocumentId in the navigation algorithm to indicate when the
* navigation case is entering into a flow or return from a flow. In that way, it is possible
* to use ConfigurableNavigationHandler.getNavigationCase(...) to know the "route" using the
* initial fromOutcome given in FLOW_ID_REQUEST_PARAM_NAME.
*
* @param context
*/
@Override
public void clientWindowTransition(FacesContext context)
{
String flowDocumentIdRequestParam = (String) context.getExternalContext().
getRequestParameterMap().get(FlowHandler.TO_FLOW_DOCUMENT_ID_REQUEST_PARAM_NAME);
if (flowDocumentIdRequestParam != null)
{
String flowIdRequestParam = (String) context.getExternalContext().
getRequestParameterMap().get(FlowHandler.FLOW_ID_REQUEST_PARAM_NAME);
if (flowIdRequestParam == null)
{
// If we don't have an fromOutcome, it is not possible to calculate the transitions
// involved.
return;
}
FlowHandler flowHandler = context.getApplication().getFlowHandler();
ConfigurableNavigationHandler nh =
(ConfigurableNavigationHandler) context.getApplication().getNavigationHandler();
if (FlowHandler.NULL_FLOW.equals(flowDocumentIdRequestParam))
{
// It is a return node. The trick here is we need to calculate
// where the flow should return, because that information was not passed
// in the parameters of the link.
String toFlowDocumentId = FlowHandler.NULL_FLOW;
String fromOutcome = flowIdRequestParam;
//Flow sourceFlow = null;
List<Flow> sourceFlows = null;
List<Flow> targetFlows = null;
boolean failed = false;
int i = 0;
while (FlowHandler.NULL_FLOW.equals(toFlowDocumentId) && !failed)
{
Flow currentFlow = flowHandler.getCurrentFlow(context);
if (currentFlow == null)
{
failed = true;
break;
}
String currentLastDisplayedViewId = flowHandler.getLastDisplayedViewId(context);
FlowNode node = currentFlow.getNode(fromOutcome);
if (node instanceof ReturnNode)
{
if (targetFlows == null)
{
sourceFlows = new ArrayList<Flow>(4);
targetFlows = new ArrayList<Flow>(4);
}
// Get the navigation case using the outcome
Flow sourceFlow = currentFlow;
flowHandler.pushReturnMode(context);
currentFlow = flowHandler.getCurrentFlow(context);
i++;
NavigationCase navCase = nh.getNavigationCase(context, null,
((ReturnNode) node).getFromOutcome(context), FlowHandler.NULL_FLOW);
if (navCase == null)
{
if (currentLastDisplayedViewId != null)
{
sourceFlows.add(sourceFlow);
if (currentFlow != null)
{
toFlowDocumentId = currentFlow.getDefiningDocumentId();
targetFlows.add(currentFlow);
}
else
{
// No active flow
toFlowDocumentId = null;
targetFlows.add(null);
}
}
else
{
// Invalid state because no navCase and
// no saved lastDisplayedViewId into session
failed = true;
}
}
else
{
if (FlowHandler.NULL_FLOW.equals(navCase.getToFlowDocumentId()))
{
fromOutcome = navCase.getFromOutcome();
}
else
{
sourceFlows.add(sourceFlow);
// The absence of FlowHandler.NULL_FLOW means the return went somewhere else.
if (currentFlow != null)
{
toFlowDocumentId = currentFlow.getDefiningDocumentId();
targetFlows.add(currentFlow);
}
else
{
// No active flow
toFlowDocumentId = null;
targetFlows.add(null);
}
}
}
}
else
{
// No return node found in current flow, push it and check
// the next flow
flowHandler.pushReturnMode(context);
currentFlow = flowHandler.getCurrentFlow(context);
i++;
if (currentFlow == null)
{
failed = true;
}
}
}
for (int j = 0; j<i; j++)
{
flowHandler.popReturnMode(context);
}
if (!failed)
{
//Call transitions.
for (int j = 0; j < targetFlows.size(); j++)
{
Flow sourceFlow = sourceFlows.get(j);
Flow targetFlow = targetFlows.get(j);
flowHandler.transition(context,
sourceFlow,
targetFlow, null, context.getViewRoot().getViewId());
}
}
}
else
{
// This transition is for start a new flow. In this case
// FlowHandler.FLOW_ID_REQUEST_PARAM_NAME could be the flow name to enter
// or the flow call node to activate.
// 1. check if is a flow
Flow targetFlow = flowHandler.getFlow(context, flowDocumentIdRequestParam, flowIdRequestParam);
Flow currentFlow = null;
FlowCallNode outboundCallNode = null;
FlowNode node = null;
if (targetFlow == null)
{
//Check if is a call flow node
List<Flow> activeFlows = FlowHandlerImpl.getActiveFlows(context, flowHandler);
for (Flow activeFlow : activeFlows)
{
node = activeFlow != null ? activeFlow.getNode(flowIdRequestParam) : null;
if (node != null && node instanceof FlowCallNode)
{
outboundCallNode = (FlowCallNode) node;
String calledFlowDocumentId = outboundCallNode.getCalledFlowDocumentId(context);
if (calledFlowDocumentId == null)
{
calledFlowDocumentId = activeFlow.getDefiningDocumentId();
}
targetFlow = flowHandler.getFlow(context,
calledFlowDocumentId,
outboundCallNode.getCalledFlowId(context));
if (targetFlow == null && !"".equals(calledFlowDocumentId))
{
targetFlow = flowHandler.getFlow(context, "",
outboundCallNode.getCalledFlowId(context));
}
if (targetFlow != null)
{
currentFlow = activeFlow;
break;
}
}
}
}
if (targetFlow != null)
{
if (flowHandler.isActive(context, targetFlow.getDefiningDocumentId(), targetFlow.getId()))
{
Flow baseReturnFlow = flowHandler.getCurrentFlow();
if (!(baseReturnFlow.getDefiningDocumentId().equals(targetFlow.getDefiningDocumentId()) &&
baseReturnFlow.getId().equals(targetFlow.getId())))
{
flowHandler.transition(context,
baseReturnFlow, targetFlow, outboundCallNode, context.getViewRoot().getViewId());
}
flowHandler.pushReturnMode(context);
Flow previousFlow = flowHandler.getCurrentFlow(context);
flowHandler.popReturnMode(context);
flowHandler.transition(context,
targetFlow, previousFlow, outboundCallNode, context.getViewRoot().getViewId());
}
// Invoke transition
flowHandler.transition(context,
currentFlow, targetFlow, outboundCallNode, context.getViewRoot().getViewId());
// Handle 2 or more flow consecutive start.
boolean failed = false;
String startNodeId = targetFlow.getStartNodeId();
while (startNodeId != null && !failed)
{
NavigationCase navCase = nh.getNavigationCase(context, null,
startNodeId, targetFlow.getDefiningDocumentId());
if (navCase != null && navCase.getToFlowDocumentId() != null)
{
currentFlow = flowHandler.getCurrentFlow(context);
node = currentFlow.getNode(navCase.getFromOutcome());
if (node != null && node instanceof FlowCallNode)
{
outboundCallNode = (FlowCallNode) node;
String calledFlowDocumentId = outboundCallNode.getCalledFlowDocumentId(context);
if (calledFlowDocumentId == null)
{
calledFlowDocumentId = currentFlow.getDefiningDocumentId();
}
targetFlow = flowHandler.getFlow(context,
calledFlowDocumentId,
outboundCallNode.getCalledFlowId(context));
if (targetFlow == null && !"".equals(calledFlowDocumentId))
{
targetFlow = flowHandler.getFlow(context, "",
outboundCallNode.getCalledFlowId(context));
}
}
else
{
String calledFlowDocumentId = navCase.getToFlowDocumentId();
if (calledFlowDocumentId == null)
{
calledFlowDocumentId = currentFlow.getDefiningDocumentId();
}
targetFlow = flowHandler.getFlow(context,
calledFlowDocumentId,
navCase.getFromOutcome());
if (targetFlow == null && !"".equals(calledFlowDocumentId))
{
targetFlow = flowHandler.getFlow(context, "",
navCase.getFromOutcome());
}
}
if (targetFlow != null)
{
flowHandler.transition(context,
currentFlow, targetFlow, outboundCallNode, context.getViewRoot().getViewId());
startNodeId = targetFlow.getStartNodeId();
}
else
{
startNodeId = null;
}
}
else
{
startNodeId = null;
}
}
}
}
}
}
private void invokeInspectFlow(FacesContext context, NavigationHandler navHandler, Flow toAdd)
{
if (navHandler instanceof ConfigurableNavigationHandler)
{
((ConfigurableNavigationHandler)navHandler).inspectFlow(context, toAdd);
}
else if (navHandler instanceof NavigationHandlerWrapper)
{
invokeInspectFlow(context, ((NavigationHandlerWrapper)navHandler).getWrapped(), toAdd);
}
}
private _FlowContextualInfo getCurrentFlowReference(FacesContext context, ClientWindow clientWindow)
{
if ( Boolean.TRUE.equals(context.getAttributes().get(RETURN_MODE)) )
{
List<_FlowContextualInfo> returnFlowList = getCurrentReturnModeFlowStack(
context, clientWindow, CURRENT_FLOW_REQUEST_STACK);
if (returnFlowList != null && !returnFlowList.isEmpty())
{
_FlowContextualInfo info = returnFlowList.get(returnFlowList.size()-1);
return info;
}
return null;
}
else
{
Map<String, Object> sessionMap = context.getExternalContext().getSessionMap();
String currentFlowMapKey = CURRENT_FLOW_STACK + clientWindow.getId();
List<_FlowContextualInfo> currentFlowStack =
(List<_FlowContextualInfo>) sessionMap.get(currentFlowMapKey);
if (currentFlowStack == null)
{
return null;
}
return currentFlowStack.size() > 0 ?
currentFlowStack.get(currentFlowStack.size()-1) : null;
}
}
private void pushFlowReference(FacesContext context, ClientWindow clientWindow, FlowReference flowReference,
String toViewId, FlowReference sourceFlowReference)
{
Map<String, Object> sessionMap = context.getExternalContext().getSessionMap();
String currentFlowMapKey = CURRENT_FLOW_STACK + clientWindow.getId();
List<_FlowContextualInfo> currentFlowStack = (List<_FlowContextualInfo>)
sessionMap.computeIfAbsent(currentFlowMapKey, k -> new ArrayList<>(4));
if (!currentFlowStack.isEmpty())
{
currentFlowStack.get(currentFlowStack.size()-1).setLastDisplayedViewId(context.getViewRoot().getViewId());
}
else
{
//Save root lastDisplayedViewId
context.getExternalContext().getSessionMap().put(ROOT_LAST_VIEW_ID + clientWindow.getId(),
context.getViewRoot().getViewId());
}
currentFlowStack.add(new _FlowContextualInfo(flowReference, toViewId, sourceFlowReference));
}
private List<_FlowContextualInfo> getCurrentFlowStack(FacesContext context, ClientWindow clientWindow)
{
Map<String, Object> sessionMap = context.getExternalContext().getSessionMap();
String currentFlowMapKey = CURRENT_FLOW_STACK + clientWindow.getId();
List<_FlowContextualInfo> currentFlowStack = (List<_FlowContextualInfo>) sessionMap.get(currentFlowMapKey);
return currentFlowStack;
}
@Override
public String getLastDisplayedViewId(FacesContext context)
{
Object session = context.getExternalContext().getSession(false);
if (session == null)
{
return null;
}
ClientWindow clientWindow = context.getExternalContext().getClientWindow();
if (clientWindow == null)
{
return null;
}
_FlowContextualInfo info = getCurrentFlowReference(context, clientWindow);
if (info == null)
{
String lastDisplayedViewId = (String) context.getAttributes().get(ROOT_LAST_VIEW_ID);
if (lastDisplayedViewId == null)
{
lastDisplayedViewId = (String) context.getExternalContext().getSessionMap().
get(ROOT_LAST_VIEW_ID + clientWindow.getId());
}
return lastDisplayedViewId;
}
return info.getLastDisplayedViewId();
}
@Override
public void pushReturnMode(FacesContext context)
{
// The return mode is a way to allow NavigationHandler to know the context
// without expose it. The idea is call pushReturnMode()/popReturnMode() and
// then check for getCurrentFlow().
//
// Remember the navigation algorithm is split in two parts:
// - Calculates the navigation
// - Perform the navigation
//
// Generated links requires only to perform the first one, but the operations
// are only perfomed when the transition between pages occur or in a get request
// when there is a pending navigation.
ClientWindow clientWindow = context.getExternalContext().getClientWindow();
if (clientWindow == null)
{
return;
}
if ( !Boolean.TRUE.equals(context.getAttributes().get(RETURN_MODE)) )
{
// Return mode not active, activate it, copy the current flow stack.
List<_FlowContextualInfo> currentFlowStack = getCurrentFlowStack(context, clientWindow);
Map<Object, Object> attributesMap = context.getAttributes();
String returnFlowMapKey = CURRENT_FLOW_REQUEST_STACK + clientWindow.getId();
List<_FlowContextualInfo> returnFlowStack = new ArrayList<_FlowContextualInfo>(currentFlowStack);
attributesMap.put(returnFlowMapKey, returnFlowStack);
context.getAttributes().put(RETURN_MODE, Boolean.TRUE);
}
_FlowContextualInfo flowReference = popFlowReferenceReturnMode(context,
clientWindow, CURRENT_FLOW_REQUEST_STACK);
pushFlowReferenceReturnMode(context, clientWindow, FLOW_RETURN_STACK, flowReference);
}
@Override
public void popReturnMode(FacesContext context)
{
ClientWindow clientWindow = context.getExternalContext().getClientWindow();
if (clientWindow == null)
{
return;
}
_FlowContextualInfo flowReference = popFlowReferenceReturnMode(context, clientWindow, FLOW_RETURN_STACK);
pushFlowReferenceReturnMode(context, clientWindow, CURRENT_FLOW_REQUEST_STACK, flowReference);
Map<Object, Object> attributesMap = context.getAttributes();
String returnFlowMapKey = FLOW_RETURN_STACK + clientWindow.getId();
List<_FlowContextualInfo> returnFlowStack = (List<_FlowContextualInfo>) attributesMap.get(returnFlowMapKey);
if (returnFlowStack != null && returnFlowStack.isEmpty())
{
context.getAttributes().put(RETURN_MODE, Boolean.FALSE);
}
}
public List<Flow> getActiveFlows(FacesContext context)
{
Object session = context.getExternalContext().getSession(false);
if (session == null)
{
return Collections.emptyList();
}
ClientWindow clientWindow = context.getExternalContext().getClientWindow();
if (clientWindow == null)
{
return Collections.emptyList();
}
if ( Boolean.TRUE.equals(context.getAttributes().get(RETURN_MODE)) )
{
// Use the standard form
FlowHandler fh = context.getApplication().getFlowHandler();
Flow curFlow = fh.getCurrentFlow(context);
if (curFlow != null)
{
List<Flow> activeFlows = new ArrayList<Flow>();
while (curFlow != null)
{
activeFlows.add(curFlow);
fh.pushReturnMode(context);
curFlow = fh.getCurrentFlow(context);
}
for (int i = 0; i < activeFlows.size(); i++)
{
fh.popReturnMode(context);
}
return activeFlows;
}
else
{
return Collections.emptyList();
}
}
else
{
Map<String, Object> sessionMap = context.getExternalContext().getSessionMap();
String currentFlowMapKey = CURRENT_FLOW_STACK + clientWindow.getId();
List<_FlowContextualInfo> currentFlowStack = (List<_FlowContextualInfo>) sessionMap.get(currentFlowMapKey);
if (currentFlowStack == null)
{
return Collections.emptyList();
}
if (!currentFlowStack.isEmpty())
{
List<Flow> activeFlows = new ArrayList<Flow>();
for(_FlowContextualInfo info : currentFlowStack)
{
activeFlows.add(0, getFlow(context,
info.getFlowReference().getDocumentId(),
info.getFlowReference().getId()));
}
return activeFlows;
}
return Collections.emptyList();
}
}
private void pushFlowReferenceReturnMode(FacesContext context, ClientWindow clientWindow,
String stackKey, _FlowContextualInfo flowReference)
{
Map<Object, Object> attributesMap = context.getAttributes();
String currentFlowMapKey = stackKey + clientWindow.getId();
List<_FlowContextualInfo> currentFlowStack = (List<_FlowContextualInfo>)
attributesMap.computeIfAbsent(currentFlowMapKey, k -> new ArrayList<_FlowContextualInfo>(4));
currentFlowStack.add(flowReference);
}
private _FlowContextualInfo popFlowReferenceReturnMode(FacesContext context, ClientWindow clientWindow,
String stackKey)
{
Map<Object, Object> attributesMap = context.getAttributes();
String currentFlowMapKey = stackKey + clientWindow.getId();
List<_FlowContextualInfo> currentFlowStack = (List<_FlowContextualInfo>) attributesMap.get(currentFlowMapKey);
if (currentFlowStack == null)
{
return null;
}
return currentFlowStack.size() > 0 ? currentFlowStack.remove(currentFlowStack.size()-1) : null;
}
private List<_FlowContextualInfo> getCurrentReturnModeFlowStack(FacesContext context, ClientWindow clientWindow,
String stackKey)
{
Map<Object, Object> attributesMap = context.getAttributes();
String currentFlowMapKey = stackKey + clientWindow.getId();
List<_FlowContextualInfo> currentFlowStack = (List<_FlowContextualInfo>) attributesMap.get(currentFlowMapKey);
return currentFlowStack;
}
public static List<Flow> getActiveFlows(FacesContext facesContext, FlowHandler fh)
{
FlowHandler flowHandler = fh;
while (flowHandler != null)
{
if (flowHandler instanceof FlowHandlerImpl)
{
break;
}
else if (flowHandler instanceof FacesWrapper)
{
flowHandler = ((FacesWrapper<FlowHandler>)flowHandler).getWrapped();
}
else
{
flowHandler = null;
}
}
if (flowHandler == null)
{
// Use the standard form
Flow curFlow = fh.getCurrentFlow(facesContext);
if (curFlow != null)
{
List<Flow> activeFlows = new ArrayList<Flow>();
while (curFlow != null)
{
activeFlows.add(curFlow);
fh.pushReturnMode(facesContext);
curFlow = fh.getCurrentFlow(facesContext);
}
for (int i = 0; i < activeFlows.size(); i++)
{
fh.popReturnMode(facesContext);
}
return activeFlows;
}
else
{
return Collections.emptyList();
}
}
else
{
FlowHandlerImpl flowHandlerImpl = (FlowHandlerImpl) flowHandler;
return flowHandlerImpl.getActiveFlows(facesContext);
}
}
@Override
public boolean isListenerForSource(Object source)
{
return source instanceof ClientWindow;
}
@Override
public void processEvent(SystemEvent event)
{
// refresh client window to faces flow provider
FacesContext facesContext = FacesContext.getCurrentInstance();
FacesFlowProvider provider = getFacesFlowProvider(facesContext);
provider.refreshClientWindow(facesContext);
}
}