001/**
002 * Copyright 2005-2016 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 */
016package org.kuali.rice.kew.engine.transition;
017
018import org.kuali.rice.kew.engine.RouteContext;
019import org.kuali.rice.kew.engine.node.*;
020import org.kuali.rice.kew.exception.RouteManagerException;
021
022import java.util.HashSet;
023import java.util.Iterator;
024import java.util.List;
025import 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 */
036public 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 signiture 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 implementor 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}