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