001/** 002 * Copyright 2005-2015 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.apache.commons.lang.builder.ToStringBuilder; 019import org.kuali.rice.kew.api.document.node.RouteNodeInstanceState; 020import org.kuali.rice.kew.doctype.bo.DocumentType; 021import org.kuali.rice.kew.engine.node.dao.impl.RouteNodeDAOJpa; 022import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue; 023import org.kuali.rice.kew.service.KEWServiceLocator; 024import org.kuali.rice.krad.data.jpa.PortableSequenceGenerator; 025 026import javax.persistence.CascadeType; 027import javax.persistence.Column; 028import javax.persistence.Entity; 029import javax.persistence.GeneratedValue; 030import javax.persistence.Id; 031import javax.persistence.JoinColumn; 032import javax.persistence.JoinTable; 033import javax.persistence.ManyToMany; 034import javax.persistence.ManyToOne; 035import javax.persistence.NamedQueries; 036import javax.persistence.NamedQuery; 037import javax.persistence.OneToMany; 038import javax.persistence.Table; 039import javax.persistence.Version; 040import java.io.Serializable; 041import java.util.ArrayList; 042import java.util.Iterator; 043import java.util.List; 044import java.util.Map; 045 046/** 047 * Represents a materialized instance of a {@link RouteNode} definition on a {@link DocumentRouteHeaderValue}. Node instances 048 * are generated by the engine using the {@link RouteNode} as a prototype and connected as a 049 * Directed Acyclic Graph. 050 * 051 * @author Kuali Rice Team (rice.collab@kuali.org) 052 */ 053@Entity 054@Table(name="KREW_RTE_NODE_INSTN_T") 055@NamedQueries({ 056 @NamedQuery(name= RouteNodeDAOJpa.FIND_INITIAL_NODE_INSTANCES_NAME, 057 query=RouteNodeDAOJpa.FIND_INITIAL_NODE_INSTANCES_QUERY) 058}) 059public class RouteNodeInstance implements Serializable { 060 061 private static final long serialVersionUID = 7183670062805580420L; 062 063 @Id 064 @GeneratedValue(generator="KREW_RTE_NODE_S") 065 @PortableSequenceGenerator(name="KREW_RTE_NODE_S") 066 @Column(name="RTE_NODE_INSTN_ID") 067 private String routeNodeInstanceId; 068 069 @Column(name="DOC_HDR_ID") 070 private String documentId; 071 072 @ManyToOne(cascade = CascadeType.ALL) 073 @JoinColumn(name="BRCH_ID") 074 private Branch branch; 075 076 @ManyToOne 077 @JoinColumn(name="RTE_NODE_ID", nullable = false) 078 private RouteNode routeNode; 079 080 @Column(name="ACTV_IND") 081 private boolean active = false; 082 083 @Column(name="CMPLT_IND") 084 private boolean complete = false; 085 086 @Column(name="INIT_IND") 087 private boolean initial = true; 088 089 @ManyToOne(cascade = CascadeType.ALL) 090 @JoinColumn(name="PROC_RTE_NODE_INSTN_ID") 091 private RouteNodeInstance process; 092 093 @ManyToMany(cascade = CascadeType.ALL) 094 @JoinTable(name = "KREW_RTE_NODE_INSTN_LNK_T", 095 joinColumns = @JoinColumn(name = "FROM_RTE_NODE_INSTN_ID"), 096 inverseJoinColumns = @JoinColumn(name = "TO_RTE_NODE_INSTN_ID")) 097 private List<RouteNodeInstance> nextNodeInstances = new ArrayList<RouteNodeInstance>(); 098 099 @ManyToMany(mappedBy = "nextNodeInstances") 100 @JoinTable(name = "KREW_RTE_NODE_INSTN_LNK_T", 101 joinColumns = @JoinColumn(name = "TO_RTE_NODE_INSTN_ID", updatable = false, insertable = false), 102 inverseJoinColumns = @JoinColumn(name = "FROM_RTE_NODE_INSTN_ID", updatable = false, insertable = false)) 103 private List<RouteNodeInstance> previousNodeInstances = new ArrayList<RouteNodeInstance>(); 104 105 @OneToMany(cascade = CascadeType.ALL, mappedBy = "nodeInstance", orphanRemoval = true) 106 private List<NodeState> state = new ArrayList<NodeState>(); 107 108 @Version 109 @Column(name="VER_NBR") 110 private Integer lockVerNbr; 111 112 public boolean isActive() { 113 return active; 114 } 115 public void setActive(boolean active) { 116 this.active = active; 117 } 118 119 public boolean isComplete() { 120 return complete; 121 } 122 public void setComplete(boolean complete) { 123 this.complete = complete; 124 } 125 public Branch getBranch() { 126 return branch; 127 } 128 public void setBranch(Branch branch) { 129 this.branch = branch; 130 } 131 public RouteNode getRouteNode() { 132 return routeNode; 133 } 134 public void setRouteNode(RouteNode node) { 135 this.routeNode = node; 136 } 137 public String getRouteNodeInstanceId() { 138 return routeNodeInstanceId; 139 } 140 public void setRouteNodeInstanceId(String routeNodeInstanceId) { 141 this.routeNodeInstanceId = routeNodeInstanceId; 142 } 143 public String getDocumentId() { 144 return documentId; 145 } 146 public void setDocumentId(String documentId) { 147 this.documentId = documentId; 148 } 149 public List<RouteNodeInstance> getNextNodeInstances() { 150 return nextNodeInstances; 151 } 152 public RouteNodeInstance getNextNodeInstance(int index) { 153 while (getNextNodeInstances().size() <= index) { 154 nextNodeInstances.add(new RouteNodeInstance()); 155 } 156 return getNextNodeInstances().get(index); 157 } 158 public void setNextNodeInstances(List<RouteNodeInstance> nextNodeInstances) { 159 this.nextNodeInstances = nextNodeInstances; 160 } 161 public List<RouteNodeInstance> getPreviousNodeInstances() { 162 return previousNodeInstances; 163 } 164 public RouteNodeInstance getPreviousNodeInstance(int index) { 165 while (previousNodeInstances.size() <= index) { 166 previousNodeInstances.add(new RouteNodeInstance()); 167 } 168 return (RouteNodeInstance) getPreviousNodeInstances().get(index); 169 } 170 public void setPreviousNodeInstances(List<RouteNodeInstance> previousNodeInstances) { 171 this.previousNodeInstances = previousNodeInstances; 172 } 173 public boolean isInitial() { 174 return initial; 175 } 176 public void setInitial(boolean initial) { 177 this.initial = initial; 178 } 179 public List<NodeState> getState() { 180 return state; 181 } 182 public void setState(List<NodeState> state) { 183 this.state.clear(); 184 this.state.addAll(state); 185 //this.state = state; 186 } 187 public RouteNodeInstance getProcess() { 188 return process; 189 } 190 public void setProcess(RouteNodeInstance process) { 191 this.process = process; 192 } 193 public Integer getLockVerNbr() { 194 return lockVerNbr; 195 } 196 public void setLockVerNbr(Integer lockVerNbr) { 197 this.lockVerNbr = lockVerNbr; 198 } 199 200 public String getRouteNodeId() { 201 if (getRouteNode() == null) { 202 return null; 203 } 204 return getRouteNode().getRouteNodeId(); 205 } 206 207 public NodeState getNodeState(String key) { 208 for (Iterator iter = getState().iterator(); iter.hasNext();) { 209 NodeState nodeState = (NodeState) iter.next(); 210 if (nodeState.getKey().equals(key)) { 211 return nodeState; 212 } 213 } 214 return null; 215 } 216 217 public void addNodeState(NodeState state) { 218 this.state.add(state); 219 state.setNodeInstance(this); 220 } 221 222 public void removeNodeState(String key) { 223 for (Iterator iter = getState().iterator(); iter.hasNext();) { 224 NodeState nodeState = (NodeState) iter.next(); 225 if (nodeState.getKey().equals(key)) { 226 iter.remove(); 227 break; 228 } 229 } 230 } 231 232 public void addNextNodeInstance(RouteNodeInstance nextNodeInstance) { 233 nextNodeInstances.add(nextNodeInstance); 234 nextNodeInstance.getPreviousNodeInstances().add(this); 235 } 236 237 public void removeNextNodeInstance(RouteNodeInstance nextNodeInstance) { 238 nextNodeInstances.remove(nextNodeInstance); 239 nextNodeInstance.getPreviousNodeInstances().remove(this); 240 } 241 242 public void clearNextNodeInstances() { 243 for (Iterator iterator = nextNodeInstances.iterator(); iterator.hasNext();) { 244 RouteNodeInstance nextNodeInstance = (RouteNodeInstance) iterator.next(); 245 iterator.remove(); 246 nextNodeInstance.getPreviousNodeInstances().remove(this); 247 } 248 } 249 250 public String getName() { 251 return (getRouteNode() == null ? null : getRouteNode().getRouteNodeName()); 252 } 253 254 public boolean isInProcess() { 255 return getProcess() != null; 256 } 257 258 public DocumentType getDocumentType() { 259 return KEWServiceLocator.getDocumentTypeService().findByDocumentId(getDocumentId()); 260 } 261 262 /* 263 * methods used to display route node instances' data on documentoperation.jsp 264 */ 265 266 public NodeState getNodeStateByIndex(int index){ 267 while (state.size() <= index) { 268 state.add(new NodeState()); 269 } 270 return getState().get(index); 271 } 272 273 public void populateState(List<NodeState> state) { 274 this.state.addAll(state); 275 } 276 277 public RouteNodeInstance deepCopy(Map<Object, Object> visited) { 278 if (visited.containsKey(this)) { 279 return (RouteNodeInstance)visited.get(this); 280 } 281 RouteNodeInstance copy = new RouteNodeInstance(); 282 visited.put(this, copy); 283 copy.routeNodeInstanceId = routeNodeInstanceId; 284 copy.documentId = documentId; 285 copy.active = active; 286 copy.complete = complete; 287 copy.initial = initial; 288 copy.lockVerNbr = lockVerNbr; 289 // no need to deep copy route node because it's static configuration 290 copy.routeNode = routeNode; 291 if (branch != null) { 292 copy.branch = branch.deepCopy(visited); 293 } 294 if (process != null) { 295 copy.process = process.deepCopy(visited); 296 } 297 if (nextNodeInstances != null) { 298 List<RouteNodeInstance> copies = new ArrayList<RouteNodeInstance>(); 299 for (RouteNodeInstance nextNodeInstance : nextNodeInstances) { 300 copies.add(nextNodeInstance.deepCopy(visited)); 301 } 302 copy.nextNodeInstances = copies; 303 } 304 if (previousNodeInstances != null) { 305 List<RouteNodeInstance> copies = new ArrayList<RouteNodeInstance>(); 306 for (RouteNodeInstance previousNodeInstance : previousNodeInstances) { 307 copies.add(previousNodeInstance.deepCopy(visited)); 308 } 309 copy.previousNodeInstances = copies; 310 } 311 if (state != null) { 312 List<NodeState> copies = new ArrayList<NodeState>(); 313 for (NodeState aState : state) { 314 copies.add(aState.deepCopy(visited)); 315 } 316 copy.state = copies; 317 } 318 return copy; 319 } 320 321 public String toString() { 322 return new ToStringBuilder(this) 323 .append("routeNodeInstanceId", routeNodeInstanceId) 324 .append("documentId", documentId) 325 .append("branch", branch == null ? null : branch.getBranchId()) 326 .append("routeNode", routeNode == null ? null : routeNode.getRouteNodeName() + " " + routeNode.getRouteNodeId()) 327 .append("active", active) 328 .append("complete", complete) 329 .append("initial", initial) 330 .append("process", process) 331 .append("state", state == null ? null : state.size()) 332 .toString(); 333 } 334 335 public static org.kuali.rice.kew.api.document.node.RouteNodeInstance to(RouteNodeInstance routeNodeInstance) { 336 if (routeNodeInstance == null) { 337 return null; 338 } 339 org.kuali.rice.kew.api.document.node.RouteNodeInstance.Builder builder = org.kuali.rice.kew.api.document.node 340 .RouteNodeInstance.Builder.create(); 341 builder.setActive(routeNodeInstance.isActive()); 342 builder.setBranchId(routeNodeInstance.getBranch().getBranchId()); 343 builder.setComplete(routeNodeInstance.isComplete()); 344 builder.setDocumentId(routeNodeInstance.getDocumentId()); 345 builder.setId(routeNodeInstance.getRouteNodeInstanceId()); 346 builder.setInitial(routeNodeInstance.isInitial()); 347 builder.setName(routeNodeInstance.getName()); 348 if (routeNodeInstance.getProcess() != null) { 349 builder.setProcessId(routeNodeInstance.getProcess().getRouteNodeInstanceId()); 350 } 351 builder.setRouteNodeId(routeNodeInstance.getRouteNode().getRouteNodeId()); 352 List<RouteNodeInstanceState.Builder> states = new ArrayList<RouteNodeInstanceState.Builder>(); 353 for (NodeState stateBo : routeNodeInstance.getState()) { 354 RouteNodeInstanceState.Builder stateBuilder = RouteNodeInstanceState.Builder.create(); 355 stateBuilder.setId(stateBo.getStateId()); 356 stateBuilder.setKey(stateBo.getKey()); 357 stateBuilder.setValue(stateBo.getValue()); 358 states.add(stateBuilder); 359 } 360 builder.setState(states); 361 362 List<org.kuali.rice.kew.api.document.node.RouteNodeInstance.Builder> nextNodes = new ArrayList<org.kuali.rice.kew.api.document.node.RouteNodeInstance.Builder>(); 363 if (routeNodeInstance.getNextNodeInstances() != null) { 364 for (RouteNodeInstance next : routeNodeInstance.getNextNodeInstances()) { 365 // KULRICE-8152 - During load testing, sometimes routeNodeInstance.getNextNodeInstances() returns an 366 // arraylist with size = 1 but all elements are null, which causes a "contract was null" error when the 367 // create is called. This check to see if next is not null prevents the error from occurring. 368 if (next != null) { 369 // will this make things blow up? 370 nextNodes.add(org.kuali.rice.kew.api.document.node.RouteNodeInstance.Builder.create(RouteNodeInstance.to(next))); 371 } 372 } 373 } 374 builder.setNextNodeInstances(nextNodes); 375 376 return builder.build(); 377 378 379 380 } 381 382} 383