in impl/src/main/java/org/apache/myfaces/application/NavigationHandlerImpl.java [518:812]
public NavigationCase getNavigationCommand(
FacesContext facesContext, NavigationContext navigationContext, String fromAction, String outcome,
String toFlowDocumentId)
{
String viewId = facesContext.getViewRoot() != null ? facesContext.getViewRoot().getViewId() : null;
NavigationCase navigationCase = getNavigationCommandFromGlobalNavigationCases(
facesContext, viewId, navigationContext, fromAction, outcome);
if (outcome != null && navigationCase == null)
{
FlowHandler flowHandler = facesContext.getApplication().getFlowHandler();
List<Flow> activeFlows = FlowHandlerImpl.getActiveFlows(facesContext, flowHandler);
// Faces 2.2 section 7.4.2: "... When outside of a flow, view identifier
// has the additional possibility of being a flow id.
Flow targetFlow = calculateTargetFlow(facesContext, outcome, flowHandler, activeFlows, toFlowDocumentId);
Flow currentFlow = navigationContext.getCurrentFlow(facesContext);
FlowCallNode targetFlowCallNode = null;
boolean startFlow = false;
String startFlowDocumentId = null;
String startFlowId = null;
boolean checkFlowNode = false;
String outcomeToGo = outcome;
String actionToGo = fromAction;
if (currentFlow != null)
{
// Faces 2.2 section 7.4.2: When inside a flow, a view identifier has
// the additional possibility of being the id of any node within the
// current flow or the id of another flow
if (targetFlow != null)
{
if (flowHandler.isActive(facesContext, targetFlow.getDefiningDocumentId(), targetFlow.getId()))
{
// If the flow is already active, there is a chance that a node id has the same name as
// the flow and if that so, give preference to that node instead reenter into the flow.
FlowNode flowNode = targetFlow.getNode(outcome);
if (flowNode != null)
{
checkFlowNode = true;
}
else
{
startFlow = true;
}
}
else
{
startFlow = true;
}
}
else
{
checkFlowNode = true;
}
}
else
{
if (targetFlow != null)
{
startFlow = true;
}
}
if (!startFlow)
{
for (Flow activeFlow : activeFlows)
{
FlowNode node = activeFlow.getNode(outcome);
if (node != null)
{
currentFlow = activeFlow;
break;
}
_FlowNavigationStructure flowNavigationStructure = _flowNavigationStructureMap.get(
activeFlow.getId());
navigationCase = getNavigationCaseFromFlowStructure(facesContext,
flowNavigationStructure, fromAction, outcome, viewId);
if (navigationCase != null)
{
currentFlow = activeFlow;
break;
}
}
}
// If is necessary to enter a flow or there is a current
// flow and it is necessary to check a flow node
if (startFlow || (checkFlowNode && currentFlow != null))
{
boolean complete = false;
boolean checkNavCase = true;
while (!complete && (startFlow || checkFlowNode))
{
if (startFlow)
{
if (flowHandler.isActive(facesContext, targetFlow.getDefiningDocumentId(), targetFlow.getId())
&& targetFlowCallNode == null)
{
// Add the transition to exit from the flow
Flow baseReturnFlow = navigationContext.getCurrentFlow(facesContext);
// This is the part when the pseudo "recursive call" is done.
while (baseReturnFlow != null && !(baseReturnFlow.getDefiningDocumentId().equals(
targetFlow.getDefiningDocumentId()) &&
baseReturnFlow.getId().equals(targetFlow.getId())) )
{
navigationContext.popFlow(facesContext);
baseReturnFlow = navigationContext.getCurrentFlow(facesContext);
}
navigationContext.popFlow(facesContext);
currentFlow = navigationContext.getCurrentFlow(facesContext);
navigationContext.addTargetFlow(baseReturnFlow, currentFlow, null);
}
if (startFlowId == null)
{
startFlowDocumentId = targetFlow.getDefiningDocumentId();
startFlowId = targetFlowCallNode == null ? targetFlow.getId() : targetFlowCallNode.getId();
}
navigationContext.addTargetFlow(currentFlow, targetFlow, targetFlowCallNode);
targetFlowCallNode = null;
// Since we start a new flow, the current flow is now the
// target flow.
navigationContext.pushFlow(facesContext, targetFlow);
currentFlow = targetFlow;
//No outboundCallNode.
//Resolve start node.
outcomeToGo = resolveStartNodeOutcome(targetFlow);
checkFlowNode = true;
startFlow = false;
}
if (checkFlowNode)
{
FlowNode flowNode = currentFlow.getNode(outcomeToGo);
if (flowNode != null)
{
checkNavCase = true;
// Note: Node Ordering changed in MYFACES-4553
if (!complete && flowNode instanceof ViewNode viewNode)
{
navigationCase = createNavigationCase(viewId, flowNode.getId(),
viewNode.getVdlDocumentId());
complete = true;
}
if (!complete && flowNode instanceof ReturnNode returnNode)
{
String fromOutcome = returnNode.getFromOutcome(facesContext);
actionToGo = currentFlow.getId();
Flow sourceFlow = currentFlow;
Flow baseReturnFlow = navigationContext.getCurrentFlow(facesContext);
// This is the part when the pseudo "recursive call" is done.
while (baseReturnFlow != null && !(baseReturnFlow.getDefiningDocumentId().equals(
currentFlow.getDefiningDocumentId()) &&
baseReturnFlow.getId().equals(currentFlow.getId())) )
{
navigationContext.popFlow(facesContext);
baseReturnFlow = navigationContext.getCurrentFlow(facesContext);
}
navigationContext.popFlow(facesContext);
currentFlow = navigationContext.getCurrentFlow(facesContext);
navigationContext.addTargetFlow(sourceFlow, currentFlow, null);
outcomeToGo = fromOutcome;
String lastDisplayedViewId = navigationContext.getLastDisplayedViewId(facesContext,
currentFlow);
// The part where FlowHandler.NULL_FLOW is passed as documentId causes the effect of
// do not take into account the documentId of the returned flow in the command. In
// theory there is no Flow with defining documentId as FlowHandler.NULL_FLOW. It has
// sense because the one who specify the return rules should be the current flow
// after it is returned.
navigationCase = getNavigationCommand(facesContext,
navigationContext, actionToGo, outcomeToGo, FlowHandler.NULL_FLOW);
if (navigationCase != null)
{
navigationCase = new FlowNavigationCase(navigationCase,
flowNode.getId(), FlowHandler.NULL_FLOW);
complete = true;
}
else
{
// No navigation case
if (lastDisplayedViewId != null)
{
navigationCase = createNavigationCase(
viewId, flowNode.getId(), lastDisplayedViewId, FlowHandler.NULL_FLOW);
complete = true;
}
}
if (currentFlow == null)
{
complete = true;
}
continue;
}
if(!complete && flowHandler.getCurrentFlow() == null) // See MYFACES-4553 for details
{
flowHandler.transition(facesContext, null, targetFlow, null, outcomeToGo);
facesContext.getAttributes().put(STARTED_FLOW_TRANSITION, true);
continue;
}
if (!complete && flowNode instanceof FlowCallNode flowCallNode)
{
targetFlow = calculateFlowCallTargetFlow(facesContext,
flowHandler, flowCallNode, currentFlow);
if (targetFlow != null)
{
targetFlowCallNode = flowCallNode;
startFlow = true;
continue;
}
else
{
// Ask the FlowHandler for a Flow for this flowId, flowDocumentId pair. Obtain a
// reference to the start node and execute this algorithm again, on that start node.
complete = true;
}
}
if (!complete && flowNode instanceof SwitchNode node)
{
outcomeToGo = calculateSwitchOutcome(facesContext, node);
// Start over again checking if the node exists.
actionToGo = currentFlow.getId();
flowNode = currentFlow.getNode(outcomeToGo);
continue;
}
if (!complete && flowNode instanceof MethodCallNode methodCallNode)
{
String vdlViewIdentifier = calculateVdlViewIdentifier(facesContext, methodCallNode);
// note a vdlViewIdentifier could be a flow node too
if (vdlViewIdentifier != null)
{
outcomeToGo = vdlViewIdentifier;
actionToGo = currentFlow.getId();
continue;
}
else
{
complete = true;
}
}
else
{
complete = true; //Should not happen
}
}
else if (checkNavCase)
{
// Not found in current flow.
_FlowNavigationStructure flowNavigationStructure = _flowNavigationStructureMap.get(
currentFlow.getId());
navigationCase = getNavigationCaseFromFlowStructure(facesContext,
flowNavigationStructure, actionToGo, outcomeToGo, viewId);
// Faces 2.2 section 7.4.2 "... any text that references a view identifier, such as
// <from-view-id> or <to-view-id>, can also refer to a flow node ..."
if (navigationCase != null)
{
outcomeToGo = navigationCase.getToViewId(facesContext);
checkNavCase = false;
}
else
{
// No matter if navigationCase is null or not, complete the look.
complete = true;
}
}
else
{
complete = true;
}
}
}
if (outcomeToGo != null && navigationCase == null) // Apply implicit navigation rules over outcomeToGo
{
navigationCase = getOutcomeNavigationCase (facesContext, actionToGo, outcomeToGo);
}
}
if (startFlowId != null)
{
navigationCase = new FlowNavigationCase(navigationCase, startFlowId, startFlowDocumentId);
}
}
if (outcome != null && navigationCase == null)
{
//if outcome is null, we don't check outcome based nav cases
//otherwise, if navgiationCase is still null, check outcome-based nav cases
navigationCase = getOutcomeNavigationCase (facesContext, fromAction, outcome);
}
if (outcome != null && navigationCase == null && !facesContext.isProjectStage(ProjectStage.Production))
{
final FacesMessage facesMessage = new FacesMessage("No navigation case match for viewId " + viewId +
", action " + fromAction + " and outcome " + outcome);
facesMessage.setSeverity(FacesMessage.Severity.WARN);
facesContext.addMessage(null, facesMessage);
}
if (navigationCase != null)
{
navigationContext.setNavigationCase(navigationCase);
}
return navigationContext.getNavigationCase(); // if navigationCase == null, will stay on current view
}