| /* |
| * 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); |
| } |
| |
| } |