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.apache.commons.lang.StringUtils; 019import org.apache.log4j.Logger; 020import org.kuali.rice.kew.api.KewApiConstants; 021import org.kuali.rice.kew.api.doctype.RouteNodeConfigurationParameterContract; 022import org.kuali.rice.kew.api.doctype.RouteNodeContract; 023import org.kuali.rice.kew.api.exception.ResourceUnavailableException; 024import org.kuali.rice.kew.doctype.bo.DocumentType; 025import org.kuali.rice.kew.rule.bo.RuleTemplateBo; 026import org.kuali.rice.kew.rule.service.RuleTemplateService; 027import org.kuali.rice.kew.service.KEWServiceLocator; 028import org.kuali.rice.kew.util.Utilities; 029import org.kuali.rice.kim.api.group.Group; 030import org.kuali.rice.kim.api.services.KimApiServiceLocator; 031import org.kuali.rice.krad.data.jpa.converters.Boolean01BigDecimalConverter; 032import org.kuali.rice.krad.data.jpa.PortableSequenceGenerator; 033 034import javax.persistence.CascadeType; 035import javax.persistence.Column; 036import javax.persistence.Convert; 037import javax.persistence.Entity; 038import javax.persistence.FetchType; 039import javax.persistence.GeneratedValue; 040import javax.persistence.Id; 041import javax.persistence.JoinColumn; 042import javax.persistence.JoinTable; 043import javax.persistence.ManyToMany; 044import javax.persistence.ManyToOne; 045import javax.persistence.OneToMany; 046import javax.persistence.OneToOne; 047import javax.persistence.Table; 048import javax.persistence.Transient; 049import javax.persistence.Version; 050import java.io.Serializable; 051import java.util.ArrayList; 052import java.util.List; 053import java.util.Map; 054 055/** 056 * Represents the prototype definition of a node in the route path of {@link DocumentType}. 057 * 058 * @author Kuali Rice Team (rice.collab@kuali.org) 059 */ 060@Entity 061@Table(name="KREW_RTE_NODE_T") 062public class RouteNode implements Serializable, RouteNodeContract { 063 064 private static final long serialVersionUID = 4891233177051752726L; 065 066 public static final String CONTENT_FRAGMENT_CFG_KEY = "contentFragment"; 067 public static final String RULE_SELECTOR_CFG_KEY = "ruleSelector"; 068 069 @Id 070 @PortableSequenceGenerator(name = "KREW_RTE_NODE_S") 071 @GeneratedValue(generator = "KREW_RTE_NODE_S") 072 @Column(name = "RTE_NODE_ID", nullable = false) 073 private String routeNodeId; 074 075 @Column(name = "DOC_TYP_ID", insertable = false, updatable = false) 076 private String documentTypeId; 077 078 @ManyToOne 079 @JoinColumn(name="DOC_TYP_ID", nullable = false) 080 private DocumentType documentType; 081 082 @Column(name="NM", nullable = false) 083 private String routeNodeName; 084 085 @Column(name="RTE_MTHD_NM") 086 private String routeMethodName; 087 088 @Column(name="FNL_APRVR_IND") 089 @Convert(converter=Boolean01BigDecimalConverter.class) 090 private Boolean finalApprovalInd; 091 092 @Column(name="MNDTRY_RTE_IND") 093 @Convert(converter=Boolean01BigDecimalConverter.class) 094 private Boolean mandatoryRouteInd; 095 096 @Column(name="GRP_ID") 097 private String exceptionWorkgroupId; 098 099 @Column(name="RTE_MTHD_CD") 100 private String routeMethodCode; 101 102 @Column(name="ACTVN_TYP") 103 private String activationType = ActivationTypeEnum.PARALLEL.getCode(); 104 105 /** 106 * The nextDocStatus property represents the value of the ApplicationDocumentStatus to be set 107 * in the RouteHeader upon transitioning from this node. 108 */ 109 @Column(name="NEXT_DOC_STAT") 110 private String nextDocStatus; 111 112 @Version 113 @Column(name="VER_NBR") 114 private Integer lockVerNbr; 115 116 @Column(name="TYP") 117 private String nodeType = RequestsNode.class.getName(); 118 119 @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "nextNodes") 120 private List<RouteNode> previousNodes = new ArrayList<RouteNode>(); 121 122 @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) 123 @JoinTable(name = "KREW_RTE_NODE_LNK_T", joinColumns = @JoinColumn(name = "FROM_RTE_NODE_ID"), inverseJoinColumns = @JoinColumn(name = "TO_RTE_NODE_ID")) 124 private List<RouteNode> nextNodes = new ArrayList<RouteNode>(); 125 126 @OneToMany(fetch = FetchType.EAGER, mappedBy = "routeNode", cascade = CascadeType.ALL) 127 private List<RouteNodeConfigParam> configParams = new ArrayList<RouteNodeConfigParam>(0); 128 129 @OneToOne(cascade = CascadeType.ALL) 130 @JoinColumn(name = "BRCH_PROTO_ID") 131 private BranchPrototype branch; 132 133 @Transient private String exceptionWorkgroupName; 134 @Transient private RuleTemplateBo ruleTemplate; 135 136 /** 137 * Looks up a config parameter for this route node definition 138 * @param key the config param key 139 * @return the RouteNodeConfigParam if present 140 */ 141 protected RouteNodeConfigParam getConfigParam(String key) { 142 Map<String, RouteNodeConfigParam> configParamMap = Utilities.getKeyValueCollectionAsLookupTable(configParams); 143 return configParamMap.get(key); 144 } 145 146 /** 147 * Sets a config parameter for this route node definition. If the key already exists 148 * the existing RouteNodeConfigParam is modified, otherwise a new one is created 149 * @param key the key of the parameter to set 150 * @param value the value to set 151 */ 152 protected void setConfigParam(String key, String value) { 153 Map<String, RouteNodeConfigParam> configParamMap = Utilities.getKeyValueCollectionAsLookupTable(configParams); 154 RouteNodeConfigParam cfCfgParam = configParamMap.get(key); 155 if (cfCfgParam == null) { 156 cfCfgParam = new RouteNodeConfigParam(this, key, value); 157 configParams.add(cfCfgParam); 158 } else { 159 cfCfgParam.setValue(value); 160 } 161 } 162 163 public List<RouteNodeConfigParam> getConfigParams() { 164 return configParams; 165 } 166 167 public void setConfigParams(List<RouteNodeConfigParam> configParams) { 168 this.configParams = configParams; 169 } 170 171 /** 172 * @return the RouteNodeConfigParam value under the 'contentFragment' key 173 */ 174 public String getContentFragment() { 175 RouteNodeConfigParam cfCfgParam = getConfigParam(CONTENT_FRAGMENT_CFG_KEY); 176 if (cfCfgParam == null) return null; 177 return cfCfgParam.getValue(); 178 } 179 180 /** 181 * @param contentFragment the content fragment of the node, which will be set as a RouteNodeConfigParam under the 'contentFragment' key 182 */ 183 public void setContentFragment(String contentFragment) { 184 setConfigParam(CONTENT_FRAGMENT_CFG_KEY, contentFragment); 185 } 186 187 public String getActivationType() { 188 return activationType; 189 } 190 191 public void setActivationType(String activationType) { 192 /* Cleanse the input. 193 * This is surely not the best way to validate the activation types; 194 * it would probably be better to use typesafe enums accross the board 195 * but that would probably entail refactoring large swaths of code, not 196 * to mention reconfiguring OJB (can typesafe enums be used?) and dealing 197 * with serialization compatibility issues (if any). 198 * So instead, let's just be sure to fail-fast. 199 */ 200 ActivationTypeEnum at = ActivationTypeEnum.lookupCode(activationType); 201 this.activationType = at.getCode(); 202 } 203 204 public Group getExceptionWorkgroup() { 205 if (!StringUtils.isBlank(exceptionWorkgroupId)) { 206 return KimApiServiceLocator.getGroupService().getGroup(exceptionWorkgroupId); 207 } 208 return null; 209 } 210 211 public boolean isExceptionGroupDefined() { 212 return getExceptionWorkgroupId() != null; 213 } 214 215 public String getExceptionWorkgroupId() { 216 return exceptionWorkgroupId; 217 } 218 219 public void setExceptionWorkgroupId(String workgroupId) { 220 this.exceptionWorkgroupId = workgroupId; 221 } 222 223 public void setFinalApprovalInd(Boolean finalApprovalInd) { 224 this.finalApprovalInd = finalApprovalInd; 225 } 226 227 public void setMandatoryRouteInd(Boolean mandatoryRouteInd) { 228 this.mandatoryRouteInd = mandatoryRouteInd; 229 } 230 231 public String getRouteMethodName() { 232 return routeMethodName; 233 } 234 235 public void setRouteMethodName(String routeMethodName) { 236 this.routeMethodName = routeMethodName; 237 } 238 239 public String getDocumentTypeId() { 240 return getDocumentType() != null ? getDocumentType().getDocumentTypeId() : documentTypeId; 241 } 242 243 public void setDocumentTypeId(String documentTypeId) { 244 this.documentTypeId = documentTypeId; 245 } 246 247 public String getRouteNodeId() { 248 return routeNodeId; 249 } 250 251 public void setRouteNodeId(String routeNodeId) { 252 this.routeNodeId = routeNodeId; 253 } 254 255 public String getRouteNodeName() { 256 return routeNodeName; 257 } 258 259 public void setRouteNodeName(String routeLevelName) { 260 this.routeNodeName = routeLevelName; 261 } 262 263 public DocumentType getDocumentType() { 264 return documentType; 265 } 266 267 public void setDocumentType(DocumentType documentType) { 268 this.documentType = documentType; 269 } 270 271 public String getRouteMethodCode() { 272 return routeMethodCode; 273 } 274 275 public void setRouteMethodCode(String routeMethodCode) { 276 this.routeMethodCode = routeMethodCode; 277 } 278 279 /** 280 * @param nextDocStatus the nextDocStatus to set 281 */ 282 public void setNextDocStatus(String nextDocStatus) { 283 this.nextDocStatus = nextDocStatus; 284 } 285 286 /** 287 * @return the nextDocStatus 288 */ 289 public String getNextDocStatus() { 290 return nextDocStatus; 291 } 292 293 public String getExceptionWorkgroupName() { 294 Group exceptionGroup = getExceptionWorkgroup(); 295 if (exceptionWorkgroupName == null || exceptionWorkgroupName.equals("")) { 296 if (exceptionGroup != null) { 297 return exceptionGroup.getName(); 298 } 299 } 300 return exceptionWorkgroupName; 301 } 302 303 public void setExceptionWorkgroupName(String exceptionWorkgroupName) { 304 this.exceptionWorkgroupName = exceptionWorkgroupName; 305 } 306 307 public Integer getLockVerNbr() { 308 return lockVerNbr; 309 } 310 311 public void setLockVerNbr(Integer lockVerNbr) { 312 this.lockVerNbr = lockVerNbr; 313 } 314 315 public boolean isFlexRM() { 316 return routeMethodCode != null && routeMethodCode.equals(KewApiConstants.ROUTE_LEVEL_FLEX_RM); 317 } 318 319 public boolean isRulesEngineNode() { 320 return StringUtils.equals(routeMethodCode, KewApiConstants.ROUTE_LEVEL_RULES_ENGINE); 321 } 322 323 public boolean isPeopleFlowNode() { 324 return StringUtils.equals(routeMethodCode, KewApiConstants.ROUTE_LEVEL_PEOPLE_FLOW); 325 } 326 327 public boolean isRoleNode() { 328 try { 329 return nodeType != null && NodeType.fromNode(this).isTypeOf(NodeType.ROLE); 330 } catch( ResourceUnavailableException ex ) { 331 Logger.getLogger( RouteNode.class ).info( "isRoleNode(): Unable to determine node type: " + ex.getMessage() ); 332 return false; 333 } 334 } 335 336 public Boolean getFinalApprovalInd() { 337 return finalApprovalInd; 338 } 339 340 public Boolean getMandatoryRouteInd() { 341 return mandatoryRouteInd; 342 } 343 344 public void addNextNode(RouteNode nextNode) { 345 getNextNodes().add(nextNode); 346 nextNode.getPreviousNodes().add(this); 347 } 348 349 public List<RouteNode> getNextNodes() { 350 return nextNodes; 351 } 352 353 public void setNextNodes(List<RouteNode> nextNodes) { 354 this.nextNodes = nextNodes; 355 } 356 357 public List<RouteNode> getPreviousNodes() { 358 return previousNodes; 359 } 360 361 public void setPreviousNodes(List<RouteNode> parentNodes) { 362 this.previousNodes = parentNodes; 363 } 364 365 public RuleTemplateBo getRuleTemplate() { 366 if (ruleTemplate == null) { 367 RuleTemplateService ruleTemplateService = (RuleTemplateService) KEWServiceLocator.getService(KEWServiceLocator.RULE_TEMPLATE_SERVICE); 368 ruleTemplate = ruleTemplateService.findByRuleTemplateName(getRouteMethodName()); 369 } 370 return ruleTemplate; 371 } 372 373 public String getNodeType() { 374 return nodeType; 375 } 376 377 public void setNodeType(String nodeType) { 378 this.nodeType = nodeType; 379 } 380 381 public BranchPrototype getBranch() { 382 return branch; 383 } 384 385 public void setBranch(BranchPrototype branch) { 386 this.branch = branch; 387 } 388 389 /** 390 * This overridden method ... 391 * 392 * @see java.lang.Object#toString() 393 */ 394 @Override 395 public String toString() { 396 return "RouteNode[routeNodeName="+routeNodeName+", nodeType="+nodeType+", activationType="+activationType+"]"; 397 } 398 399 @Override 400 public Long getVersionNumber() { 401 if (lockVerNbr == null) { 402 return null; 403 } 404 return Long.valueOf(lockVerNbr.longValue()); 405 } 406 407 @Override 408 public String getId() { 409 if (routeNodeId == null) { 410 return null; 411 } 412 return routeNodeId.toString(); 413 } 414 415 @Override 416 public String getName() { 417 return getRouteNodeName(); 418 } 419 420 @Override 421 public boolean isFinalApproval() { 422 if (finalApprovalInd == null) { 423 return false; 424 } 425 return finalApprovalInd.booleanValue(); 426 } 427 428 @Override 429 public boolean isMandatory() { 430 if (mandatoryRouteInd == null) { 431 return false; 432 } 433 return mandatoryRouteInd.booleanValue(); 434 } 435 436 @Override 437 public String getExceptionGroupId() { 438 return exceptionWorkgroupId; 439 } 440 441 @Override 442 public String getType() { 443 return nodeType; 444 } 445 446 @Override 447 public String getBranchName() { 448 if (branch == null) { 449 return null; 450 } 451 return branch.getName(); 452 } 453 454 @Override 455 public String getNextDocumentStatus() { 456 return nextDocStatus; 457 } 458 459 @Override 460 public List<? extends RouteNodeConfigurationParameterContract> getConfigurationParameters() { 461 return configParams; 462 } 463 464 @Override 465 public List<String> getPreviousNodeIds() { 466 List<String> previousNodeIds = new ArrayList<String>(); 467 if (previousNodes != null) { 468 for (RouteNode previousNode : previousNodes) { 469 previousNodeIds.add(previousNode.getRouteNodeId().toString()); 470 } 471 } 472 return previousNodeIds; 473 } 474 475 @Override 476 public List<String> getNextNodeIds() { 477 List<String> nextNodeIds = new ArrayList<String>(); 478 if (nextNodeIds != null) { 479 for (RouteNode nextNode : nextNodes) { 480 nextNodeIds.add(nextNode.getRouteNodeId().toString()); 481 } 482 } 483 return nextNodeIds; 484 } 485 486 487}