001/** 002 * Copyright 2005-2017 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.krms.impl.repository; 017 018import java.io.Serializable; 019import java.util.ArrayList; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023 024import javax.persistence.CascadeType; 025import javax.persistence.Column; 026import javax.persistence.Convert; 027import javax.persistence.Entity; 028import javax.persistence.FetchType; 029import javax.persistence.GeneratedValue; 030import javax.persistence.Id; 031import javax.persistence.JoinColumn; 032import javax.persistence.ManyToOne; 033import javax.persistence.OneToMany; 034import javax.persistence.Table; 035import javax.persistence.Transient; 036import javax.persistence.Version; 037 038import org.apache.commons.lang.StringUtils; 039import org.eclipse.persistence.annotations.OptimisticLocking; 040import org.kuali.rice.core.api.mo.common.Versioned; 041import org.kuali.rice.core.api.util.tree.Node; 042import org.kuali.rice.core.api.util.tree.Tree; 043import org.kuali.rice.krad.data.DataObjectService; 044import org.kuali.rice.krad.data.jpa.PortableSequenceGenerator; 045import org.kuali.rice.krad.data.jpa.converters.BooleanYNConverter; 046import org.kuali.rice.krad.service.KRADServiceLocator; 047import org.kuali.rice.krms.api.repository.LogicalOperator; 048import org.kuali.rice.krms.api.repository.action.ActionDefinition; 049import org.kuali.rice.krms.api.repository.proposition.PropositionType; 050import org.kuali.rice.krms.api.repository.rule.RuleDefinition; 051import org.kuali.rice.krms.api.repository.rule.RuleDefinitionContract; 052import org.kuali.rice.krms.api.repository.type.KrmsAttributeDefinition; 053import org.kuali.rice.krms.impl.ui.CompoundOpCodeNode; 054import org.kuali.rice.krms.impl.ui.CompoundPropositionEditNode; 055import org.kuali.rice.krms.impl.ui.RuleTreeNode; 056import org.kuali.rice.krms.impl.ui.SimplePropositionEditNode; 057import org.kuali.rice.krms.impl.ui.SimplePropositionNode; 058 059@Entity 060@Table(name = "KRMS_RULE_T") 061@OptimisticLocking(cascade = true) 062public class RuleBo implements RuleDefinitionContract, Versioned, Serializable { 063 064 private static final long serialVersionUID = 1L; 065 066 public static final String RULE_SEQ_NAME = "KRMS_RULE_S"; 067 static final RepositoryBoIncrementer ruleIdIncrementer = new RepositoryBoIncrementer(RULE_SEQ_NAME); 068 static final RepositoryBoIncrementer actionIdIncrementer = new RepositoryBoIncrementer("KRMS_ACTN_S"); 069 static final RepositoryBoIncrementer ruleAttributeIdIncrementer = new RepositoryBoIncrementer("KRMS_RULE_ATTR_S"); 070 static final RepositoryBoIncrementer actionAttributeIdIncrementer = new RepositoryBoIncrementer("KRMS_ACTN_ATTR_S"); 071 072 @PortableSequenceGenerator(name = RULE_SEQ_NAME) 073 @GeneratedValue(generator = RULE_SEQ_NAME) 074 @Id 075 @Column(name = "RULE_ID") 076 private String id; 077 078 @Column(name = "NMSPC_CD") 079 private String namespace; 080 081 @Column(name = "DESC_TXT") 082 private String description; 083 084 @Column(name = "NM") 085 private String name; 086 087 @Column(name = "TYP_ID", nullable = true) 088 private String typeId; 089 090 @Column(name = "ACTV") 091 @Convert(converter = BooleanYNConverter.class) 092 private boolean active = true; 093 094 @Column(name = "VER_NBR") 095 @Version 096 private Long versionNumber; 097 098 @ManyToOne(targetEntity = PropositionBo.class, fetch = FetchType.LAZY, 099 cascade = { CascadeType.REFRESH, CascadeType.MERGE, CascadeType.REMOVE, CascadeType.PERSIST }) 100 @JoinColumn(name = "PROP_ID", referencedColumnName = "PROP_ID") 101 private PropositionBo proposition; 102 103 @OneToMany(orphanRemoval = true, mappedBy = "rule", fetch = FetchType.LAZY, 104 cascade = { CascadeType.REFRESH, CascadeType.MERGE, CascadeType.REMOVE, CascadeType.PERSIST }) 105 @JoinColumn(name = "RULE_ID", referencedColumnName = "RULE_ID") 106 private List<ActionBo> actions; 107 108 @OneToMany(orphanRemoval = true, mappedBy = "rule", fetch = FetchType.LAZY, 109 cascade = { CascadeType.REFRESH, CascadeType.MERGE, CascadeType.REMOVE, CascadeType.PERSIST }) 110 @JoinColumn(name = "RULE_ID", referencedColumnName = "RULE_ID") 111 private List<RuleAttributeBo> attributeBos; 112 113 @Transient 114 private Tree<RuleTreeNode, String> propositionTree; 115 116 @Transient 117 private String propositionSummary; 118 119 @Transient 120 private StringBuffer propositionSummaryBuffer; 121 122 @Transient 123 private String selectedPropositionId; 124 125 public RuleBo() { 126 actions = new ArrayList<ActionBo>(); 127 attributeBos = new ArrayList<RuleAttributeBo>(); 128 } 129 130 @Override 131 public PropositionBo getProposition() { 132 return proposition; 133 } 134 135 public void setProposition(PropositionBo proposition) { 136 this.proposition = proposition; 137 } 138 139 /** 140 * set the typeId. If the parameter is blank, then this RuleBo's 141 * typeId will be set to null 142 * 143 * @param typeId 144 */ 145 public void setTypeId(String typeId) { 146 if (StringUtils.isBlank(typeId)) { 147 this.typeId = null; 148 } else { 149 this.typeId = typeId; 150 } 151 } 152 153 @Override 154 public Map<String, String> getAttributes() { 155 HashMap<String, String> attributes = new HashMap<String, String>(); 156 157 for (RuleAttributeBo attr : attributeBos) { 158 DataObjectService dataObjectService = KRADServiceLocator.getDataObjectService(); 159 dataObjectService.wrap(attr).fetchRelationship("attributeDefinition", false, true); 160 attributes.put(attr.getAttributeDefinition().getName(), attr.getValue()); 161 } 162 163 return attributes; 164 } 165 166 public void setAttributes(Map<String, String> attributes) { 167 this.attributeBos = new ArrayList<RuleAttributeBo>(); 168 169 if (!StringUtils.isBlank(this.typeId)) { 170 List<KrmsAttributeDefinition> attributeDefinitions = KrmsRepositoryServiceLocator.getKrmsAttributeDefinitionService().findAttributeDefinitionsByType(this.getTypeId()); 171 Map<String, KrmsAttributeDefinition> attributeDefinitionsByName = new HashMap<String, KrmsAttributeDefinition>(); 172 173 if (attributeDefinitions != null) { 174 for (KrmsAttributeDefinition attributeDefinition : attributeDefinitions) { 175 attributeDefinitionsByName.put(attributeDefinition.getName(), attributeDefinition); 176 } 177 } 178 179 for (Map.Entry<String, String> attr : attributes.entrySet()) { 180 KrmsAttributeDefinition attributeDefinition = attributeDefinitionsByName.get(attr.getKey()); 181 RuleAttributeBo attributeBo = new RuleAttributeBo(); 182 attributeBo.setRule(this); 183 attributeBo.setValue(attr.getValue()); 184 attributeBo.setAttributeDefinition(KrmsAttributeDefinitionBo.from(attributeDefinition)); 185 attributeBos.add(attributeBo); 186 } 187 } 188 } 189 190 public String getPropositionSummary() { 191 if (this.propositionTree == null) { 192 this.propositionTree = refreshPropositionTree(false); 193 } 194 195 return propositionSummaryBuffer.toString(); 196 } 197 198 /** 199 * This method is used by the RuleEditor to display the proposition in tree form. 200 * 201 * @return Tree representing a rule proposition. 202 */ 203 public Tree getPropositionTree() { 204 if (this.propositionTree == null) { 205 this.propositionTree = refreshPropositionTree(false); 206 } 207 208 return this.propositionTree; 209 } 210 211 public void setPropositionTree(Tree<RuleTreeNode, String> tree) { 212 this.propositionTree.equals(tree); 213 } 214 215 public Tree refreshPropositionTree(Boolean editMode) { 216 Tree myTree = new Tree<RuleTreeNode, String>(); 217 218 Node<RuleTreeNode, String> rootNode = new Node<RuleTreeNode, String>(); 219 myTree.setRootElement(rootNode); 220 221 propositionSummaryBuffer = new StringBuffer(); 222 PropositionBo prop = this.getProposition(); 223 if (prop!=null && StringUtils.isBlank(prop.getDescription())) { 224 prop.setDescription(""); 225 } 226 buildPropTree(rootNode, prop, editMode); 227 this.propositionTree = myTree; 228 return myTree; 229 } 230 231 /** 232 * This method builds a propositionTree recursively walking through the children of the proposition. 233 * 234 * @param sprout - parent tree node 235 * @param prop - PropositionBo for which to make the tree node 236 * @param editMode - Boolean determines the node type used to represent the proposition 237 * false: create a view only node text control 238 * true: create an editable node with multiple controls 239 * null: use the proposition.editMode property to determine the node type 240 */ 241 private void buildPropTree(Node sprout, PropositionBo prop, Boolean editMode) { 242 // Depending on the type of proposition (simple/compound), and the editMode, 243 // Create a treeNode of the appropriate type for the node and attach it to the 244 // sprout parameter passed in. 245 // If the prop is a compound proposition, calls itself for each of the compoundComponents 246 if (prop != null) { 247 // Blank labels make propositions very difficult to select in the UI, as the label is what 248 // you click on for selecting the proposition. 249 String nodeLabel = prop.getDescription(); 250 if (StringUtils.isBlank(nodeLabel)) { 251 nodeLabel = ": : blank proposition name : :"; 252 } 253 254 if (PropositionType.SIMPLE.getCode().equalsIgnoreCase(prop.getPropositionTypeCode())) { 255 // Simple Proposition 256 // add a node for the description display with a child proposition node 257 Node<RuleTreeNode, String> child = new Node<RuleTreeNode, String>(); 258 child.setNodeLabel(nodeLabel); 259 260 if (prop.getEditMode()) { 261 child.setNodeLabel(""); 262 child.setNodeType(SimplePropositionEditNode.NODE_TYPE); 263 SimplePropositionEditNode pNode = new SimplePropositionEditNode(prop); 264 child.setData(pNode); 265 } else { 266 child.setNodeType(SimplePropositionNode.NODE_TYPE); 267 SimplePropositionNode pNode = new SimplePropositionNode(prop); 268 child.setData(pNode); 269 } 270 271 sprout.getChildren().add(child); 272 propositionSummaryBuffer.append(prop.getParameterDisplayString()); 273 } else if (PropositionType.COMPOUND.getCode().equalsIgnoreCase(prop.getPropositionTypeCode())) { 274 // Compound Proposition 275 propositionSummaryBuffer.append(" ( "); 276 Node<RuleTreeNode, String> aNode = new Node<RuleTreeNode, String>(); 277 aNode.setNodeLabel(nodeLabel); 278 279 // editMode has description as an editable field 280 if (prop.getEditMode()) { 281 aNode.setNodeLabel(""); 282 aNode.setNodeType("ruleTreeNode compoundNode editNode"); 283 CompoundPropositionEditNode pNode = new CompoundPropositionEditNode(prop); 284 aNode.setData(pNode); 285 } else { 286 aNode.setNodeType("ruleTreeNode compoundNode"); 287 RuleTreeNode pNode = new RuleTreeNode(prop); 288 aNode.setData(pNode); 289 } 290 291 sprout.getChildren().add(aNode); 292 boolean first = true; 293 List<PropositionBo> allMyChildren = prop.getCompoundComponents(); 294 int compoundSequenceNumber = 0; 295 296 for (PropositionBo child : allMyChildren) { 297 child.setCompoundSequenceNumber((compoundSequenceNumber = ++compoundSequenceNumber)); 298 299 // start with 1 300 // add an opcode node in between each of the children. 301 if (!first) { 302 addOpCodeNode(aNode, prop); 303 } 304 305 first = false; 306 // call to build the childs node 307 buildPropTree(aNode, child, editMode); 308 } 309 310 propositionSummaryBuffer.append(" ) "); 311 } 312 } 313 } 314 315 /** 316 * This method adds an opCode Node to separate components in a compound proposition. 317 * 318 * @param currentNode 319 * @param prop 320 * @return 321 */ 322 private void addOpCodeNode(Node currentNode, PropositionBo prop) { 323 String opCodeLabel = ""; 324 325 if (LogicalOperator.AND.getCode().equalsIgnoreCase(prop.getCompoundOpCode())) { 326 opCodeLabel = "AND"; 327 } else if (LogicalOperator.OR.getCode().equalsIgnoreCase(prop.getCompoundOpCode())) { 328 opCodeLabel = "OR"; 329 } 330 331 propositionSummaryBuffer.append(" " + opCodeLabel + " "); 332 Node<RuleTreeNode, String> aNode = new Node<RuleTreeNode, String>(); 333 aNode.setNodeLabel(""); 334 aNode.setNodeType("ruleTreeNode compoundOpCodeNode"); 335 aNode.setData(new CompoundOpCodeNode(prop)); 336 337 currentNode.getChildren().add(aNode); 338 } 339 340 /** 341 * Converts a mutable bo to it's immutable counterpart 342 * 343 * @param bo the mutable business object 344 * @return the immutable object 345 */ 346 public static RuleDefinition to(RuleBo bo) { 347 if (bo == null) { 348 return null; 349 } 350 351 return RuleDefinition.Builder.create(bo).build(); 352 } 353 354 /** 355 * Converts a immutable object to it's mutable bo counterpart 356 * 357 * @param im immutable object 358 * @return the mutable bo 359 */ 360 public static RuleBo from(RuleDefinition im) { 361 if (im == null) { 362 return null; 363 } 364 365 RuleBo bo = new RuleBo(); 366 bo.id = im.getId(); 367 bo.namespace = im.getNamespace(); 368 bo.name = im.getName(); 369 bo.description = im.getDescription(); 370 bo.typeId = im.getTypeId(); 371 bo.active = im.isActive(); 372 373 if (im.getProposition() != null) { 374 PropositionBo propositionBo = PropositionBo.from(im.getProposition()); 375 bo.proposition = propositionBo; 376 propositionBo.setRuleId(im.getId()); 377 } 378 379 bo.setVersionNumber(im.getVersionNumber()); 380 bo.actions = new ArrayList<ActionBo>(); 381 382 for (ActionDefinition action : im.getActions()) { 383 ActionBo actionBo = ActionBo.from(action); 384 bo.actions.add(actionBo); 385 actionBo.setRule(bo); 386 } 387 388 // build the set of agenda attribute BOs 389 List<RuleAttributeBo> attrs = new ArrayList<RuleAttributeBo>(); 390 // for each converted pair, build an RuleAttributeBo and add it to the set 391 RuleAttributeBo attributeBo; 392 for (Map.Entry<String, String> entry : im.getAttributes().entrySet()) { 393 KrmsAttributeDefinitionBo attrDefBo = KrmsRepositoryServiceLocator.getKrmsAttributeDefinitionService().getKrmsAttributeBo(entry.getKey(), im.getNamespace()); 394 attributeBo = new RuleAttributeBo(); 395 attributeBo.setRule(bo); 396 attributeBo.setAttributeDefinition(attrDefBo); 397 attributeBo.setValue(entry.getValue()); 398 attributeBo.setAttributeDefinition(attrDefBo); 399 attrs.add(attributeBo); 400 } 401 402 bo.setAttributeBos(attrs); 403 404 return bo; 405 } 406 407 public static RuleBo copyRule(RuleBo existing) { 408 // create a rule Bo 409 RuleBo newRule = new RuleBo(); 410 // copy simple fields 411 newRule.setId(ruleIdIncrementer.getNewId()); 412 newRule.setNamespace(existing.getNamespace()); 413 newRule.setDescription(existing.getDescription()); 414 newRule.setTypeId(existing.getTypeId()); 415 newRule.setActive(true); 416 417 PropositionBo newProp = PropositionBo.copyProposition(existing.getProposition()); 418 newProp.setRuleId(newRule.getId()); 419 newRule.setProposition(newProp); 420 421 newRule.setAttributeBos(copyRuleAttributes(existing, newRule)); 422 newRule.setActions(copyRuleActions(existing, newRule)); 423 424 return newRule; 425 } 426 427 /** 428 * Returns a new copy of this rule with new ids. 429 * 430 * @param newRuleName name of the copied rule 431 * @return RuleBo a copy of the this rule, with new ids, and the given name 432 */ 433 public RuleBo copyRule(String newRuleName) { 434 RuleBo copiedRule = RuleBo.copyRule(this); 435 436 // Rule names cannot be the same, the error for being the same name is not displayed to the user, and the document is 437 // said to have been successfully submitted. 438 // copiedRule.setName(rule.getName()); 439 copiedRule.setName(newRuleName); 440 441 return copiedRule; 442 } 443 444 public static List<RuleAttributeBo> copyRuleAttributes(RuleBo existing, RuleBo newRule) { 445 List<RuleAttributeBo> newAttributes = new ArrayList<RuleAttributeBo>(); 446 447 for (RuleAttributeBo attr : existing.getAttributeBos()) { 448 RuleAttributeBo newAttr = new RuleAttributeBo(); 449 newAttr.setId(ruleAttributeIdIncrementer.getNewId()); 450 newAttr.setRule(newRule); 451 newAttr.setAttributeDefinition(attr.getAttributeDefinition()); 452 newAttr.setValue(attr.getValue()); 453 newAttributes.add(newAttr); 454 } 455 456 return newAttributes; 457 } 458 459 public static List<ActionAttributeBo> copyActionAttributes(ActionBo existing, ActionBo newAction) { 460 List<ActionAttributeBo> newAttributes = new ArrayList<ActionAttributeBo>(); 461 462 for (ActionAttributeBo attr : existing.getAttributeBos()) { 463 ActionAttributeBo newAttr = new ActionAttributeBo(); 464 newAttr.setId(actionAttributeIdIncrementer.getNewId()); 465 newAttr.setAction(newAction); 466 newAttr.setAttributeDefinition(attr.getAttributeDefinition()); 467 newAttr.setValue(attr.getValue()); 468 newAttributes.add(newAttr); 469 } 470 471 return newAttributes; 472 } 473 474 public static List<ActionBo> copyRuleActions(RuleBo existing, RuleBo newRule) { 475 List<ActionBo> newActionList = new ArrayList<ActionBo>(); 476 477 for (ActionBo action : existing.getActions()) { 478 ActionBo newAction = new ActionBo(); 479 newAction.setId(actionIdIncrementer.getNewId()); 480 newAction.setRule(newRule); 481 newAction.setDescription(action.getDescription()); 482 newAction.setName(action.getName()); 483 newAction.setNamespace(action.getNamespace()); 484 newAction.setTypeId(action.getTypeId()); 485 newAction.setSequenceNumber(action.getSequenceNumber()); 486 newAction.setAttributeBos(copyActionAttributes(action, newAction)); 487 newActionList.add(newAction); 488 } 489 490 return newActionList; 491 } 492 493 @Override 494 public String getId() { 495 return id; 496 } 497 498 public void setId(String id) { 499 this.id = id; 500 } 501 502 @Override 503 public String getNamespace() { 504 return namespace; 505 } 506 507 public void setNamespace(String namespace) { 508 this.namespace = namespace; 509 } 510 511 @Override 512 public String getDescription() { 513 return description; 514 } 515 516 public void setDescription(String description) { 517 this.description = description; 518 } 519 520 @Override 521 public String getName() { 522 return name; 523 } 524 525 public void setName(String name) { 526 this.name = name; 527 } 528 529 @Override 530 public String getTypeId() { 531 return typeId; 532 } 533 534 @Override 535 public String getPropId() { 536 if (proposition != null) { 537 return proposition.getId(); 538 } 539 540 return null; 541 } 542 543 public boolean getActive() { 544 return active; 545 } 546 547 @Override 548 public boolean isActive() { 549 return active; 550 } 551 552 public void setActive(boolean active) { 553 this.active = active; 554 } 555 556 @Override 557 public Long getVersionNumber() { 558 return versionNumber; 559 } 560 561 public void setVersionNumber(Long versionNumber) { 562 this.versionNumber = versionNumber; 563 } 564 565 @Override 566 public List<ActionBo> getActions() { 567 return actions; 568 } 569 570 public void setActions(List<ActionBo> actions) { 571 this.actions = actions; 572 } 573 574 public List<RuleAttributeBo> getAttributeBos() { 575 return attributeBos; 576 } 577 578 public void setAttributeBos(List<RuleAttributeBo> attributeBos) { 579 this.attributeBos = attributeBos; 580 } 581 582 public void setPropositionSummary(String propositionSummary) { 583 this.propositionSummary = propositionSummary; 584 } 585 586 public String getSelectedPropositionId() { 587 return selectedPropositionId; 588 } 589 590 public void setSelectedPropositionId(String selectedPropositionId) { 591 this.selectedPropositionId = selectedPropositionId; 592 } 593 594}