View Javadoc
1   /**
2    * Copyright 2005-2015 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krms.impl.repository;
17  
18  import java.io.Serializable;
19  import java.util.ArrayList;
20  import java.util.HashMap;
21  import java.util.List;
22  import java.util.Map;
23  
24  import javax.persistence.CascadeType;
25  import javax.persistence.Column;
26  import javax.persistence.Convert;
27  import javax.persistence.Entity;
28  import javax.persistence.FetchType;
29  import javax.persistence.GeneratedValue;
30  import javax.persistence.Id;
31  import javax.persistence.JoinColumn;
32  import javax.persistence.ManyToOne;
33  import javax.persistence.OneToMany;
34  import javax.persistence.Table;
35  import javax.persistence.Transient;
36  import javax.persistence.Version;
37  
38  import org.apache.commons.lang.StringUtils;
39  import org.eclipse.persistence.annotations.OptimisticLocking;
40  import org.kuali.rice.core.api.mo.common.Versioned;
41  import org.kuali.rice.core.api.util.tree.Node;
42  import org.kuali.rice.core.api.util.tree.Tree;
43  import org.kuali.rice.krad.data.DataObjectService;
44  import org.kuali.rice.krad.data.jpa.PortableSequenceGenerator;
45  import org.kuali.rice.krad.data.jpa.converters.BooleanYNConverter;
46  import org.kuali.rice.krad.service.KRADServiceLocator;
47  import org.kuali.rice.krms.api.repository.LogicalOperator;
48  import org.kuali.rice.krms.api.repository.action.ActionDefinition;
49  import org.kuali.rice.krms.api.repository.proposition.PropositionType;
50  import org.kuali.rice.krms.api.repository.rule.RuleDefinition;
51  import org.kuali.rice.krms.api.repository.rule.RuleDefinitionContract;
52  import org.kuali.rice.krms.api.repository.type.KrmsAttributeDefinition;
53  import org.kuali.rice.krms.impl.ui.CompoundOpCodeNode;
54  import org.kuali.rice.krms.impl.ui.CompoundPropositionEditNode;
55  import org.kuali.rice.krms.impl.ui.RuleTreeNode;
56  import org.kuali.rice.krms.impl.ui.SimplePropositionEditNode;
57  import org.kuali.rice.krms.impl.ui.SimplePropositionNode;
58  
59  @Entity
60  @Table(name = "KRMS_RULE_T")
61  @OptimisticLocking(cascade = true)
62  public class RuleBo implements RuleDefinitionContract, Versioned, Serializable {
63  
64      private static final long serialVersionUID = 1L;
65  
66      public static final String RULE_SEQ_NAME = "KRMS_RULE_S";
67      static final RepositoryBoIncrementer ruleIdIncrementer = new RepositoryBoIncrementer(RULE_SEQ_NAME);
68      static final RepositoryBoIncrementer actionIdIncrementer = new RepositoryBoIncrementer("KRMS_ACTN_S");
69      static final RepositoryBoIncrementer ruleAttributeIdIncrementer = new RepositoryBoIncrementer("KRMS_RULE_ATTR_S");
70      static final RepositoryBoIncrementer actionAttributeIdIncrementer = new RepositoryBoIncrementer("KRMS_ACTN_ATTR_S");
71  
72      @PortableSequenceGenerator(name = RULE_SEQ_NAME)
73      @GeneratedValue(generator = RULE_SEQ_NAME)
74      @Id
75      @Column(name = "RULE_ID")
76      private String id;
77  
78      @Column(name = "NMSPC_CD")
79      private String namespace;
80  
81      @Column(name = "DESC_TXT")
82      private String description;
83  
84      @Column(name = "NM")
85      private String name;
86  
87      @Column(name = "TYP_ID", nullable = true)
88      private String typeId;
89  
90      @Column(name = "ACTV")
91      @Convert(converter = BooleanYNConverter.class)
92      private boolean active = true;
93  
94      @Column(name = "VER_NBR")
95      @Version
96      private Long versionNumber;
97  
98      @ManyToOne(targetEntity = PropositionBo.class, fetch = FetchType.LAZY,
99              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(RuleBo.from(im));
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 }