All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.faces.flow.FlowHandlerImpl Maven / Gradle / Ivy

Go to download

This is the master POM file for Oracle's Implementation of the JSF 2.2 Specification.

There is a newer version: 2.4.0
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright (c) 1997-2012 Oracle and/or its affiliates. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 * 
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 * 
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 * 
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.

 */
package com.sun.faces.flow;

import com.sun.faces.util.Util;
import java.io.Serializable;
import java.text.MessageFormat;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.el.ELContext;
import javax.el.MethodExpression;
import javax.el.ValueExpression;
import javax.faces.application.ConfigurableNavigationHandler;
import javax.faces.application.NavigationHandler;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.flow.FlowCallNode;
import javax.faces.flow.Flow;
import javax.faces.flow.FlowHandler;
import javax.faces.flow.Parameter;

public class FlowHandlerImpl extends FlowHandler {
    
    public static final String ABANDONED_FLOW = "javax.faces.flow.AbandonedFlow";

    public FlowHandlerImpl() {
        flowFeatureIsEnabled = false;
        flows = new ConcurrentHashMap>();
        flowsByFlowId = new ConcurrentHashMap>();
    }
    
    private boolean flowFeatureIsEnabled;

    // key: definingDocumentId, value: Map
    private final Map> flows;
    
    // key: flowId, List
    private final Map> flowsByFlowId;

    @Override
    public Map getCurrentFlowScope() {
        return FlowCDIContext.getCurrentFlowScopeAndUpdateSession();
    }
    
    @Override
    public Flow getFlow(FacesContext context, String definingDocumentId, String id) {
        Util.notNull("context", context);
        Util.notNull("definingDocumentId", definingDocumentId);
        Util.notNull("id", id);
        Flow result = null;
        Map mapsForDefiningDocument = flows.get(definingDocumentId);
        
        if (null != mapsForDefiningDocument) {
            result = mapsForDefiningDocument.get(id);
        }
        
        return result;
    }

    @Override
    public void addFlow(FacesContext context, Flow toAdd) {
        Util.notNull("context", context);
        Util.notNull("toAdd", toAdd);

        String id = toAdd.getId();
        if (null == id || 0 == id.length()) {
            throw new IllegalArgumentException("The id of the flow may not be null or zero-length.");
        }
        String definingDocumentId = toAdd.getDefiningDocumentId();
        if (null == definingDocumentId) {
            throw new IllegalArgumentException("The definingDocumentId of the flow may not be null.");
        }
        Map mapsForDefiningDocument = flows.get(definingDocumentId);
        if (null == mapsForDefiningDocument) {
            mapsForDefiningDocument = new ConcurrentHashMap();
            flows.put(toAdd.getDefiningDocumentId(), mapsForDefiningDocument);
        }
        
        Flow oldFlow = mapsForDefiningDocument.put(id, toAdd);
        if (null != oldFlow) {
            String message = MessageFormat.format("Flow with id \"{0}\" and definingDocumentId \"{1}\" already exists.", 
                    id, definingDocumentId);
            throw new IllegalStateException(message);
        }

        // Make it possible for the "transition" method to map from view nodes
        // to flow instances.
        List flowsWithId = flowsByFlowId.get(id);
        if (null == flowsWithId) {
            flowsWithId = new CopyOnWriteArrayList();
            flowsByFlowId.put(id, flowsWithId);
        }
        flowsWithId.add(toAdd);
        
        NavigationHandler navigationHandler = context.getApplication().getNavigationHandler();
        if (navigationHandler instanceof ConfigurableNavigationHandler) {
            ((ConfigurableNavigationHandler)navigationHandler).inspectFlow(context, toAdd);
        }
        flowFeatureIsEnabled = true;
    }

    @Override
    public boolean isActive(FacesContext context, String definingDocumentId, 
                            String id) {
        Util.notNull("context", context);
        Util.notNull("definingDocumentId", definingDocumentId);
        Util.notNull("id", id);
        boolean result = false;
        FlowDeque flowStack = getFlowStack(context);
        for (Flow cur : flowStack) {
            if (id.equals(cur.getId()) &&
                definingDocumentId.equals(cur.getDefiningDocumentId())) {
                result = true;
                break;
            }
        }
        
        return result;
    }
    
    
    
    
    @Override
    public Flow getCurrentFlow(FacesContext context) {
        Util.notNull("context", context);
        
        if (!flowFeatureIsEnabled) {
            return null;
        }
        Flow result = null;
        // If there is no session, there cannot possibly be a flow, so
        // don't create one just to check.
        if (null == context.getExternalContext().getSession(false)) {
            return null;
        }
        
        FlowDeque flowStack = getFlowStack(context);
        int returnDepth = flowStack.getReturnDepth();
        if (flowStack.size() <= returnDepth) {
            return null;
        }
        if (0 < returnDepth) {
            Iterator stackIter = flowStack.iterator();
            int i = 0;
            stackIter.next();
            if (stackIter.hasNext()) {
                do {
                    result = stackIter.next();
                    i++;
                } while (i < returnDepth);
            }
        } else {
            result = getFlowStack(context).peekFirst();
        }
        return result;
    }

    @Override
    public String getLastDisplayedViewId(FacesContext context) {
        Util.notNull("context", context);
        String result = null;
        FlowDeque flowStack = getFlowStack(context);
        result = flowStack.peekLastDisplayedViewId();
        return result;
    }
    
    public static final String FLOW_RETURN_DEPTH_PARAM_NAME = "jffrd";
    
    public int getAndClearReturnModeDepth(FacesContext context) {
        int result = 0;
        FlowDeque flowStack = getFlowStack(context);
        result = flowStack.getAndClearMaxReturnDepth(context);
        
        return result;
    }

    @Override
    public void pushReturnMode(FacesContext context) {
        Util.notNull("context", context);
        FlowDeque flowStack = getFlowStack(context);
        flowStack.pushReturnMode();
    }

    @Override
    public void popReturnMode(FacesContext context) {
        Util.notNull("context", context);
        FlowDeque flowStack = getFlowStack(context);
        flowStack.popReturnMode();
    }
    // We need a method that takes a view id of a view that is in a flow
    // and makes the system "enter" the flow.
    
    @Override
    @SuppressWarnings(value="")
    public void transition(FacesContext context, Flow sourceFlow, 
                           Flow targetFlow, FlowCallNode outboundCallNode,
                           String toViewId) {
        Util.notNull("context", context);
        Util.notNull("toViewId", toViewId);
        if (!flowFeatureIsEnabled) {
           return;
        }
        
        // there has to be a better way to structure this logic
        if (!flowsEqual(sourceFlow, targetFlow)) {
            // Do we have an outboundCallNode?
            Map evaluatedParams = null;
            if (null != outboundCallNode) {
                Map outboundParameters = outboundCallNode.getOutboundParameters();
                Map inboundParameters = targetFlow.getInboundParameters();
                // Are we passing parameters?
                if (null != outboundParameters && !outboundParameters.isEmpty() &&
                        null != inboundParameters && !inboundParameters.isEmpty()) {
                    
                    ELContext elContext = context.getELContext();
                    String curName;
                    // for each outbound parameter...
                    for (Map.Entry curOutbound : outboundParameters.entrySet()) {
                        curName = curOutbound.getKey();
                        if (inboundParameters.containsKey(curName)) {
                            if (null == evaluatedParams) {
                                evaluatedParams = new HashMap();
                            }
                            // Evaluate it and put it in the temporary map.
                            // It is necessary to do this before the flow
                            // transition because EL expressions may refer to
                            // things in the current flow scope.
                            evaluatedParams.put(curName, curOutbound.getValue().getValue().getValue(elContext));
                        }
                    }
                }
            }
            
            performPops(context, sourceFlow, targetFlow);
            if (null != targetFlow && !targetFlow.equals(FlowImpl.ABANDONED_FLOW)) {
                pushFlow(context, targetFlow, toViewId, evaluatedParams);
            } else {
                assignInboundParameters(context, targetFlow, evaluatedParams);
            }
        } 
    }

    private void assignInboundParameters(FacesContext context, Flow calledFlow,
                                                                Map evaluatedParams) {
      // Now the new flow is active, it's time to evaluate the inbound
      // parameters.
      if (null != evaluatedParams) {
          Map inboundParameters = calledFlow.getInboundParameters();
          ELContext elContext = context.getELContext();
          String curName;
          ValueExpression toSet;
          for (Map.Entry curOutbound : evaluatedParams.entrySet()) {
              curName = curOutbound.getKey();
              assert(inboundParameters.containsKey(curName));
              toSet = inboundParameters.get(curName).getValue();
              toSet.setValue(elContext, curOutbound.getValue());
          }
      }
    }

    @Override
    public void clientWindowTransition(FacesContext context) {
        Map requestParamMap = context.getExternalContext().getRequestParameterMap();
        String toFlowDocumentId = requestParamMap.get(TO_FLOW_DOCUMENT_ID_REQUEST_PARAM_NAME);
        String flowId = requestParamMap.get(FLOW_ID_REQUEST_PARAM_NAME);
        if (null != toFlowDocumentId) {
            // don't use *this*, due to decoration
            FlowHandler fh = context.getApplication().getFlowHandler();
            Flow sourceFlow = fh.getCurrentFlow(context);
            Flow targetFlow = null;            
            FlowCallNode flowCallNode = null;
            // if this is not a return...
            if (null != flowId && !FlowHandler.NULL_FLOW.equals(toFlowDocumentId)) {
                targetFlow = fh.getFlow(context, toFlowDocumentId, flowId);
                if (null != targetFlow && null != sourceFlow) {
                    flowCallNode = sourceFlow.getFlowCall(targetFlow);
                }
            } else {
                String maxReturnDepthStr = requestParamMap.get(FLOW_RETURN_DEPTH_PARAM_NAME);
                int maxReturnDepth = Integer.valueOf(maxReturnDepthStr).intValue();
                FlowDeque flowStack = getFlowStack(context);
                flowStack.setMaxReturnDepth(context, maxReturnDepth);
            }
            
            fh.transition(context, sourceFlow, targetFlow, flowCallNode, context.getViewRoot().getViewId());
            
        }
    }
    
    private void performPops(FacesContext context, Flow sourceFlow, Flow targetFlow) {
        // case 0: sourceFlow is null.  There must be nothing to pop.
        if (null == sourceFlow) {
            assert(null == peekFlow(context));
            return;
        }
                
        // case 1: target is null
        if (null == targetFlow) {
            FlowDeque flowStack = getFlowStack(context);
            int maxReturns = flowStack.getAndClearMaxReturnDepth(context);
            for (int i=0; i < maxReturns; i++) {
                popFlow(context);
            }
            return;
        } 
        
        if (FlowImpl.ABANDONED_FLOW.equals(targetFlow)) {
            FlowDeque flowStack = getFlowStack(context);
            int depth = flowStack.size();
            for (int i=0; i
    
    private void pushFlow(FacesContext context, Flow toPush, String lastDisplayedViewId, 
                                    Map evaluatedParams) {
        FlowDeque flowStack = getFlowStack(context);
        flowStack.addFirst(toPush, lastDisplayedViewId);
        FlowCDIContext.flowEntered();
        
        assignInboundParameters(context, toPush, evaluatedParams);
        
        MethodExpression me  = toPush.getInitializer();
        if (null != me) {
            me.invoke(context.getELContext(), null);
        }
        forceSessionUpdateForFlowStack(context, flowStack);
    }
    
    private Flow peekFlow(FacesContext context) {
        FlowDeque flowStack = getFlowStack(context);
        return flowStack.peekFirst();
    }
    
    private Flow popFlow(FacesContext context) {
        FlowDeque flowStack = getFlowStack(context);
        Flow currentFlow = peekFlow(context);
        if (null != currentFlow) {
            callFinalizer(context, currentFlow, flowStack.size());
        }
        Flow result = flowStack.pollFirst();
        forceSessionUpdateForFlowStack(context, flowStack);
        return result;
        
    }
    
    private void callFinalizer(FacesContext context, Flow currentFlow, int depth) {
        MethodExpression me  = currentFlow.getFinalizer();
        if (null != me) {
            me.invoke(context.getELContext(), null);
        }
        FlowCDIContext.flowExited(currentFlow, depth);
    }
    
    static FlowDeque getFlowStack(FacesContext context) {
        FlowDeque result = null;
        ExternalContext extContext = context.getExternalContext();
        String sessionKey = extContext.getClientWindow().getId() + "_flowStack";
        Map sessionMap = extContext.getSessionMap();
        result = (FlowDeque) sessionMap.get(sessionKey);
        if (null == result) {
            result = new FlowDeque(sessionKey);
            sessionMap.put(sessionKey, result);
        }
        
        return result;
    }
    
    private void forceSessionUpdateForFlowStack(FacesContext context, FlowDeque stack) {
        ExternalContext extContext = context.getExternalContext();
        Map sessionMap = extContext.getSessionMap();
        sessionMap.put(stack.getSessionKey(), stack);
    }
   
    static class FlowDeque implements Iterable, Serializable {
        
        private static final long serialVersionUID = 7915803727932706270L;
        
        private int returnDepth;
        private ArrayDeque data;
        private static class RideAlong implements Serializable {
            String lastDisplayedViewId;

            public RideAlong(String lastDisplayedViewId) {
                this.lastDisplayedViewId = lastDisplayedViewId;
            }
            
        }
        private ArrayDeque rideAlong;
        private final String sessionKey;

        public FlowDeque(final String sessionKey) {
            data = new ArrayDeque();
            rideAlong = new ArrayDeque();
            this.sessionKey = sessionKey;
        }
        
        public String getSessionKey() {
            return sessionKey;
        }
        
        public int size() {
            return data.size();
        }

        @Override
        public Iterator iterator() {
            return data.iterator();
        }
        
        public void addFirst(E e, String lastDisplayedViewId) {
            rideAlong.addFirst(new RideAlong(lastDisplayedViewId));
            data.addFirst(e);
        }
        
        public E pollFirst() {
            rideAlong.pollFirst();
            return data.pollFirst();
        }
        
        public int getCurrentFlowDepth( ) {
            int returnDepth = this.returnDepth;
            if (data.size() <= returnDepth) {
                return 0;
            }
            
            int result = data.size();
            if (0 < returnDepth) {
                Iterator stackIter = data.iterator();
                int i = 0;
                while ( stackIter.hasNext() && i < returnDepth ) {
                    stackIter.next();
                    result--;
                    i++;
                } 
            }
            
            return result;
        }
        
        public E peekFirst() {
            return data.peekFirst();
        }
        
        public String peekLastDisplayedViewId() {
            String result = null;
            RideAlong helper = null;
            int myReturnDepth = this.getReturnDepth();
            if (0 < myReturnDepth) {
                Iterator stackIter = rideAlong.iterator();
                stackIter.next();
                int i = 0;
                if (stackIter.hasNext()) {
                    do {
                        helper = stackIter.next();
                        i++;
                    } while (i < myReturnDepth);
                }
            } else {
                helper = rideAlong.peekFirst();
            }
            
            if (null != helper) {
                result = helper.lastDisplayedViewId;
            }
            
            return result;
        }
        
        public int getReturnDepth() {
            return returnDepth;
        }
        
        private void setMaxReturnDepth(FacesContext context, int value) {
            Map attrs = context.getAttributes();
            attrs.put(FLOW_RETURN_DEPTH_PARAM_NAME, value);
        }
        
        private int getAndClearMaxReturnDepth(FacesContext context) {
            Map attrs = context.getAttributes();
            int result = 0;
            if (attrs.containsKey(FLOW_RETURN_DEPTH_PARAM_NAME)) {
              result = ((Integer)attrs.remove(FLOW_RETURN_DEPTH_PARAM_NAME)).intValue();
            } 
            return result;
        }
        
        private void incrementMaxReturnDepth() {
            FacesContext context = FacesContext.getCurrentInstance();
            Map attrs = context.getAttributes();
            if (!attrs.containsKey(FLOW_RETURN_DEPTH_PARAM_NAME)) {
                attrs.put(FLOW_RETURN_DEPTH_PARAM_NAME, (Integer) 1);
            } else {
                Integer cur = (Integer) attrs.get(FLOW_RETURN_DEPTH_PARAM_NAME);
                attrs.put(FLOW_RETURN_DEPTH_PARAM_NAME, (Integer) cur + 1);
            }
            
        }
        
        public void pushReturnMode() {
            this.incrementMaxReturnDepth();
            this.returnDepth++;
        }
        
        public void popReturnMode() {
            this.returnDepth--;
        }


        
    }
        
    // 
    
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy