001    /**
002     * Copyright 2005-2014 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.rice.kew.engine.transition;
017    
018    import org.kuali.rice.kew.engine.RouteContext;
019    import org.kuali.rice.kew.engine.node.*;
020    import org.kuali.rice.kew.exception.RouteManagerException;
021    
022    import java.util.HashSet;
023    import java.util.Iterator;
024    import java.util.List;
025    import java.util.Set;
026    
027    
028    /**
029     * The DynamicTransitionEngine operates on a {@link DynamicNode} and takes the next node instances returned 
030     * by the node and runs returns them in a TransitionResult after doing some processing and validation on them.
031     * 
032     * @see DynamicNode
033     * 
034     * @author Kuali Rice Team (rice.collab@kuali.org)
035     */
036    public class DynamicTransitionEngine extends TransitionEngine {
037    
038        // TODO interate the all the nodes and attach the dynamic node as the 'process'
039        // don't include the dynamic node instance in the routing structure - require a correctly built graph
040        // change dynamic node signature to next node because of above
041        // reconcile branching if necessary
042        public RouteNodeInstance transitionTo(RouteNodeInstance dynamicNodeInstance, RouteContext context) throws Exception {
043            dynamicNodeInstance.setInitial(false);
044            dynamicNodeInstance.setActive(false);
045            DynamicNode dynamicNode = (DynamicNode) getNode(dynamicNodeInstance.getRouteNode(), DynamicNode.class);
046            DynamicResult result = dynamicNode.transitioningInto(context, dynamicNodeInstance, getRouteHelper());
047            RouteNodeInstance nextNodeInstance = result.getNextNodeInstance();
048            RouteNodeInstance finalNodeInstance = null;
049            if (result.isComplete()) {
050                dynamicNodeInstance.setComplete(true);
051                finalNodeInstance = getFinalNodeInstance(dynamicNodeInstance, context); 
052                if (nextNodeInstance == null) {
053                    nextNodeInstance = finalNodeInstance;
054                }
055            }
056          
057            if (nextNodeInstance !=null) {
058                initializeNodeGraph(context, dynamicNodeInstance, nextNodeInstance, new HashSet<RouteNodeInstance>(), finalNodeInstance);
059            }
060            return nextNodeInstance;   
061        }
062        
063        public ProcessResult isComplete(RouteContext context) throws Exception {
064            throw new UnsupportedOperationException("isComplete() should not be invoked on a Dynamic node!");
065        }
066        
067        public Transition transitionFrom(RouteContext context, ProcessResult processResult) throws Exception {
068            
069            Transition transition = new Transition();
070            RouteNodeInstance dynamicNodeInstance = context.getNodeInstance().getProcess();
071            DynamicNode dynamicNode = (DynamicNode) getNode(dynamicNodeInstance.getRouteNode(), DynamicNode.class);
072            DynamicResult result = dynamicNode.transitioningOutOf(context, getRouteHelper());
073            if (result.getNextNodeInstance() == null && result.getNextNodeInstances().isEmpty() && result.isComplete()) {
074                dynamicNodeInstance.setComplete(true);
075                RouteNodeInstance finalNodeInstance = getFinalNodeInstance(dynamicNodeInstance, context);
076                if (finalNodeInstance != null) {
077                    transition.getNextNodeInstances().add(finalNodeInstance);    
078                }
079            } else {
080                if (result.getNextNodeInstance() != null) {
081                    result.getNextNodeInstance().setProcess(dynamicNodeInstance);
082                    transition.getNextNodeInstances().add(result.getNextNodeInstance());    
083                }
084                for (Iterator iter = result.getNextNodeInstances().iterator(); iter.hasNext();) {
085                    RouteNodeInstance nextNodeInstance = (RouteNodeInstance) iter.next();
086                    nextNodeInstance.setProcess(dynamicNodeInstance);
087                }
088                transition.getNextNodeInstances().addAll(result.getNextNodeInstances());
089            }
090            return transition;
091        }
092    
093        /**
094         * This method checks the next node returned by the user and walks the resulting node graph, filling in required data where possible.
095         * Will throw errors if there is a problem with what the implementer has returned to us. This allows them to do things like return next
096         * nodes with no attached branches, and we will go ahead and generate the branches for them, etc.
097         */
098        private void initializeNodeGraph(RouteContext context, RouteNodeInstance dynamicNodeInstance, RouteNodeInstance nodeInstance, Set<RouteNodeInstance> nodeInstances, RouteNodeInstance finalNodeInstance) throws Exception {
099            if (nodeInstances.contains(nodeInstance)) {
100                throw new RouteManagerException("A cycle was detected in the node graph returned from the dynamic node.", context);
101            }
102            nodeInstances.add(nodeInstance);
103            nodeInstance.setProcess(dynamicNodeInstance);
104            List<RouteNodeInstance> nextNodeInstances = nodeInstance.getNextNodeInstances();
105            
106            if (nextNodeInstances.size() > 1) {
107                // TODO implement this feature
108    //            throw new UnsupportedOperationException("Need to implement support for branch generation!");
109            }
110            for (RouteNodeInstance nextNodeInstance : nextNodeInstances)
111            {
112                initializeNodeGraph(context, dynamicNodeInstance, nextNodeInstance, nodeInstances, finalNodeInstance);
113            }
114            if (nextNodeInstances.isEmpty() && finalNodeInstance != null) {
115                nodeInstance.addNextNodeInstance(finalNodeInstance);
116            }
117        }
118    
119        private RouteNodeInstance getFinalNodeInstance(RouteNodeInstance dynamicNodeInstance, RouteContext context) throws Exception {
120            List<RouteNode> nextNodes = dynamicNodeInstance.getRouteNode().getNextNodes();
121            if (nextNodes.size() > 1) {
122                throw new RouteManagerException("There should only be 1 next node following a dynamic node, there were " + nextNodes.size(), context);
123            }
124            RouteNodeInstance finalNodeInstance = null;
125            if (!nextNodes.isEmpty()) {
126                finalNodeInstance = getRouteHelper().getNodeFactory().createRouteNodeInstance(context.getDocument().getDocumentId(), (RouteNode) nextNodes.get(0));
127                finalNodeInstance.setBranch(dynamicNodeInstance.getBranch());
128                finalNodeInstance.setProcess(dynamicNodeInstance.getProcess());
129            }
130            return finalNodeInstance;
131        }
132    }