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; 017 018import org.kuali.rice.kew.actionrequest.ActionRequestValue; 019import org.kuali.rice.kew.api.KewApiConstants; 020import org.kuali.rice.kew.api.WorkflowRuntimeException; 021import org.kuali.rice.kew.doctype.bo.DocumentType; 022import org.kuali.rice.kew.engine.node.ProcessDefinitionBo; 023import org.kuali.rice.kew.engine.node.RouteNode; 024import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue; 025import org.kuali.rice.kew.api.KewApiConstants; 026 027import java.util.ArrayList; 028import java.util.List; 029 030 031/** 032 * Provides utility methods for handling backwards compatibility between KEW releases. 033 * Currently, it's primary function is to handle backward compatability between the 034 * deprecated "route level" concept and the "node" concept which was introduced in 035 * KEW 2.1. 036 * 037 * @author Kuali Rice Team (rice.collab@kuali.org) 038 */ 039public final class CompatUtils { 040 041 private static RouteHelper helper = new RouteHelper(); 042 043 private CompatUtils() { 044 throw new UnsupportedOperationException("do not call"); 045 } 046 047 public static Integer getLevelForNode(DocumentType documentType, String nodeName) { 048 if (isRouteLevelCompatible(documentType)) { 049 return getLevelForNode(documentType.getPrimaryProcess().getInitialRouteNode(), nodeName, 0); 050 } 051 return new Integer(KewApiConstants.INVALID_ROUTE_LEVEL); 052 } 053 054 private static Integer getLevelForNode(RouteNode node, String nodeName, Integer currentLevel) { 055 if (node == null) { 056 throw new WorkflowRuntimeException("Could not locate node with name '"+nodeName+"'"); 057 } 058 // TODO potential for infinite recursion here if their document type has loops in it. Should this be a concern? 059 // If their routing version is really "route level" then there should be no cycles. 060 if (node.getRouteNodeName().equals(nodeName)) { 061 return currentLevel; 062 } 063 List<RouteNode> nextNodes = node.getNextNodes(); 064 if (nextNodes.isEmpty()) { 065 throw new WorkflowRuntimeException("Could not locate node with name '"+nodeName+"'"); 066 } 067 if (nextNodes.size() > 1) { 068 throw new WorkflowRuntimeException("Can only determine route level for document types with no splitting"); 069 } 070 RouteNode nextNode = (RouteNode)nextNodes.get(0); 071 return getLevelForNode(nextNode, nodeName, new Integer(currentLevel.intValue()+1)); 072 } 073 074 /** 075 * Returns the RouteNode at the given numerical route level for the given document type. 076 * This currently throws a WorkflowException if the document has parallel routing structures 077 * because the route level as a number becomes arbitrary in that case. 078 */ 079 public static RouteNode getNodeForLevel(DocumentType documentType, Integer routeLevel) { 080 RouteNode result = null; 081 082 RouteNode initialRouteNode = documentType.getPrimaryProcess().getInitialRouteNode(); 083 if (initialRouteNode != null) { 084 Object[] node = getNodeForLevel(initialRouteNode, routeLevel, new Integer(0)); 085 result = (RouteNode)node[0]; 086 } 087 return result; 088 } 089 090 private static Object[] getNodeForLevel(RouteNode node, Integer routeLevel, Integer currentLevel) { 091 if (helper.isSubProcessNode(node)) { 092 Object[] result = getNodeForLevel(node.getDocumentType().getNamedProcess(node.getRouteNodeName()).getInitialRouteNode(), routeLevel, currentLevel); 093 if (result[0] != null) { 094 node = (RouteNode)result[0]; 095 } 096 currentLevel = (Integer)result[1]; 097 } 098 if (currentLevel.equals(routeLevel)) { 099 return new Object[] { node, currentLevel }; 100 } 101 List<RouteNode> nextNodes = node.getNextNodes(); 102 if (nextNodes.isEmpty()) { 103 return new Object[] { null, currentLevel }; 104 } 105 if (nextNodes.size() > 1) { 106 throw new WorkflowRuntimeException("Cannot determine a route level number for documents with splitting."); 107 } 108 currentLevel = new Integer(currentLevel.intValue()+1); 109 return getNodeForLevel((RouteNode)nextNodes.get(0), routeLevel, currentLevel); 110 } 111 112 public static boolean isRouteLevelCompatible(DocumentType documentType) { 113 return KewApiConstants.ROUTING_VERSION_ROUTE_LEVEL.equals(documentType.getRoutingVersion()); 114 } 115 116 public static boolean isRouteLevelCompatible(DocumentRouteHeaderValue document) { 117 return isRouteLevelCompatible(document.getDocumentType()); 118 } 119 120 public static boolean isNodalDocument(DocumentRouteHeaderValue document) { 121 return KewApiConstants.DocumentContentVersions.NODAL == document.getDocVersion().intValue(); 122 } 123 124 public static boolean isNodalRequest(ActionRequestValue request) { 125 return KewApiConstants.DocumentContentVersions.NODAL == request.getDocVersion().intValue(); 126 } 127 128 public static boolean isRouteLevelDocument(DocumentRouteHeaderValue document) { 129 return KewApiConstants.DocumentContentVersions.ROUTE_LEVEL == document.getDocVersion().intValue(); 130 } 131 132 public static boolean isRouteLevelRequest(ActionRequestValue request) { 133 return KewApiConstants.DocumentContentVersions.ROUTE_LEVEL == request.getDocVersion().intValue(); 134 } 135 136 /** 137 * Returns a list of RouteNodes in a flat list which is equivalent to the route level concept of 138 * Workflow <= version 2.0. If the document type is not route level compatible, then this method will throw an error. 139 */ 140 public static List<RouteNode> getRouteLevelCompatibleNodeList(DocumentType documentType) { 141 if (!isRouteLevelCompatible(documentType)) { 142 throw new WorkflowRuntimeException("Attempting to invoke a 'route level' operation on a document which is not route level compatible."); 143 } 144 ProcessDefinitionBo primaryProcess = documentType.getPrimaryProcess(); 145 RouteNode routeNode = primaryProcess.getInitialRouteNode(); 146 List<RouteNode> nodes = new ArrayList<RouteNode>(); 147 int count = 0; 148 int maxCount = 100; 149 if (routeNode != null) { 150 while (true) { 151 nodes.add(routeNode); 152 List<RouteNode> nextNodes = routeNode.getNextNodes(); 153 if (nextNodes.size() == 0) { 154 break; 155 } 156 if (nextNodes.size() > 1) { 157 throw new RuntimeException("Node has more than one next node! It is not route level compatible!" + routeNode.getRouteNodeName()); 158 } 159 if (count >= maxCount) { 160 throw new RuntimeException("A runaway loop was detected when attempting to create route level compatible node graph. documentType=" + documentType.getDocumentTypeId()+","+documentType.getName()); 161 } 162 routeNode = nextNodes.iterator().next(); 163 } 164 } 165 return nodes; 166 } 167 168 public static int getMaxRouteLevel(DocumentType documentType) { 169 return getRouteLevelCompatibleNodeList(documentType).size(); 170 } 171}