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