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 }