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.node; 017 018import org.kuali.rice.core.framework.persistence.jpa.OrmUtils; 019import org.kuali.rice.kew.api.WorkflowRuntimeException; 020import org.kuali.rice.kew.engine.RouteContext; 021import org.kuali.rice.kew.service.KEWServiceLocator; 022 023import java.util.HashSet; 024import java.util.Iterator; 025import java.util.Set; 026import java.util.StringTokenizer; 027 028 029/** 030 * A basic implementation of the JoinEngine which handles join setup and makes determinations 031 * as to when a join condition has been satisfied. 032 * 033 * @author Kuali Rice Team (rice.collab@kuali.org) 034 */ 035public class BasicJoinEngine implements JoinEngine { 036 037 public static final String EXPECTED_JOINERS = "ExpectedJoiners"; 038 public static final String ACTUAL_JOINERS = "ActualJoiners"; 039 040 public void createExpectedJoinState(RouteContext context, RouteNodeInstance joinInstance, RouteNodeInstance previousNodeInstance) { 041 RouteNodeInstance splitNode = previousNodeInstance.getBranch().getSplitNode(); 042 if (splitNode == null) { 043 throw new WorkflowRuntimeException("The split node retrieved from node with name '" + previousNodeInstance.getName() + "' and branch with name '" + previousNodeInstance.getBranch().getName() + "' was null"); 044 } 045 for (Iterator iter = splitNode.getNextNodeInstances().iterator(); iter.hasNext();) { 046 RouteNodeInstance splitNodeNextNode = (RouteNodeInstance) iter.next(); 047 // Dilemma: we are given an unsaved join node. For linking to work in absence of joinNode auto-update we must 048 // ensure the join node instance is saved before we save the branch, however this save is done afterwards in 049 // the caller (StandardWorkflowEngine). 050 // If we save here we should probably be sure to take into account simulation context 051 saveNode(context, joinInstance); 052 splitNodeNextNode.getBranch().setJoinNode(joinInstance); 053 // The saveBranch() call below is necessary for parallel routing to work properly with OJB, but it breaks parallel routing with JPA, 054 // so only perform it if KEW is not JPA-enabled. 055 if (!OrmUtils.isJpaEnabled("rice.kew")) { 056 saveBranch(context, splitNodeNextNode.getBranch()); 057 } 058 addExpectedJoiner(joinInstance, splitNodeNextNode.getBranch()); 059 } 060 joinInstance.setBranch(splitNode.getBranch()); 061 joinInstance.setProcess(splitNode.getProcess()); 062 } 063 064 public void addExpectedJoiner(RouteNodeInstance nodeInstance, Branch branch) { 065 addJoinState(nodeInstance, branch, EXPECTED_JOINERS); 066 } 067 068 public void addActualJoiner(RouteNodeInstance nodeInstance, Branch branch) { 069 addJoinState(nodeInstance, branch, ACTUAL_JOINERS); 070 } 071 072 private void addJoinState(RouteNodeInstance nodeInstance, Branch branch, String key) { 073 NodeState state = nodeInstance.getNodeState(key); 074 if (state == null) { 075 state = new NodeState(); 076 state.setKey(key); 077 state.setValue(""); 078 state.setNodeInstance(nodeInstance); 079 nodeInstance.addNodeState(state); 080 } 081 state.setValue(state.getValue()+branch.getBranchId()+","); 082 } 083 084 public boolean isJoined(RouteNodeInstance nodeInstance) { 085 NodeState expectedState = nodeInstance.getNodeState(EXPECTED_JOINERS); 086 if (expectedState == null || org.apache.commons.lang.StringUtils.isEmpty(expectedState.getValue())) { 087 return true; 088 } 089 NodeState actualState = nodeInstance.getNodeState(ACTUAL_JOINERS); 090 Set expectedSet = loadIntoSet(expectedState); 091 Set actualSet = loadIntoSet(actualState); 092 for (Iterator iterator = expectedSet.iterator(); iterator.hasNext();) { 093 String value = (String) iterator.next(); 094 if (actualSet.contains(value)) { 095 iterator.remove(); 096 } 097 } 098 return expectedSet.size() == 0; 099 } 100 101 private Set loadIntoSet(NodeState state) { 102 Set set = new HashSet(); 103 StringTokenizer tokenizer = new StringTokenizer(state.getValue(), ","); 104 while (tokenizer.hasMoreTokens()) { 105 set.add(tokenizer.nextToken()); 106 } 107 return set; 108 } 109 110 private void saveBranch(RouteContext context, Branch branch) { 111 if (!context.isSimulation()) { 112 KEWServiceLocator.getRouteNodeService().save(branch); 113 } 114 } 115 116 // see {@link StandardWorkflowEngine#saveNode} 117 private void saveNode(RouteContext context, RouteNodeInstance nodeInstance) { 118 if (!context.isSimulation()) { 119 KEWServiceLocator.getRouteNodeService().save(nodeInstance); 120 } else { 121 // if we are in simulation mode, lets go ahead and assign some id 122 // values to our beans 123 for (Iterator<RouteNodeInstance> iterator = nodeInstance.getNextNodeInstances().iterator(); iterator.hasNext();) { 124 RouteNodeInstance routeNodeInstance = (RouteNodeInstance) iterator.next(); 125 if (routeNodeInstance.getRouteNodeInstanceId() == null) { 126 routeNodeInstance.setRouteNodeInstanceId(context.getEngineState().getNextSimulationId()); 127 } 128 } 129 if (nodeInstance.getProcess() != null && nodeInstance.getProcess().getRouteNodeInstanceId() == null) { 130 nodeInstance.getProcess().setRouteNodeInstanceId(context.getEngineState().getNextSimulationId()); 131 } 132 if (nodeInstance.getBranch() != null && nodeInstance.getBranch().getBranchId() == null) { 133 nodeInstance.getBranch().setBranchId(context.getEngineState().getNextSimulationId()); 134 } 135 } 136 } 137 138}