View Javadoc
1   /**
2    * Copyright 2005-2014 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 org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.core.api.mo.common.Versioned;
20  import org.kuali.rice.core.api.util.tree.Node;
21  import org.kuali.rice.core.api.util.tree.Tree;
22  import org.kuali.rice.krad.data.DataObjectService;
23  import org.kuali.rice.krad.data.jpa.PortableSequenceGenerator;
24  import org.kuali.rice.krad.data.jpa.converters.BooleanYNConverter;
25  import org.kuali.rice.krad.service.KRADServiceLocator;
26  import org.kuali.rice.krms.api.repository.LogicalOperator;
27  import org.kuali.rice.krms.api.repository.action.ActionDefinition;
28  import org.kuali.rice.krms.api.repository.proposition.PropositionType;
29  import org.kuali.rice.krms.api.repository.rule.RuleDefinition;
30  import org.kuali.rice.krms.api.repository.rule.RuleDefinitionContract;
31  import org.kuali.rice.krms.api.repository.type.KrmsAttributeDefinition;
32  import org.kuali.rice.krms.impl.ui.CompoundOpCodeNode;
33  import org.kuali.rice.krms.impl.ui.CompoundPropositionEditNode;
34  import org.kuali.rice.krms.impl.ui.RuleTreeNode;
35  import org.kuali.rice.krms.impl.ui.SimplePropositionEditNode;
36  import org.kuali.rice.krms.impl.ui.SimplePropositionNode;
37  
38  import javax.persistence.CascadeType;
39  import javax.persistence.Column;
40  import javax.persistence.Convert;
41  import javax.persistence.Entity;
42  import javax.persistence.FetchType;
43  import javax.persistence.GeneratedValue;
44  import javax.persistence.Id;
45  import javax.persistence.JoinColumn;
46  import javax.persistence.ManyToOne;
47  import javax.persistence.OneToMany;
48  import javax.persistence.Table;
49  import javax.persistence.Transient;
50  import javax.persistence.Version;
51  import java.io.IOException;
52  import java.io.ObjectOutputStream;
53  import java.io.Serializable;
54  import java.util.ArrayList;
55  import java.util.HashMap;
56  import java.util.List;
57  import java.util.Map;
58  
59  @Entity
60  @Table(name = "KRMS_RULE_T")
61  public class RuleBo implements RuleDefinitionContract, Versioned, Serializable {
62  
63      private static final long serialVersionUID = 1L;
64  
65      public static final String RULE_SEQ_NAME = "KRMS_RULE_S";
66      static final RepositoryBoIncrementer ruleIdIncrementer = new RepositoryBoIncrementer(RULE_SEQ_NAME);
67      static final RepositoryBoIncrementer actionIdIncrementer = new RepositoryBoIncrementer("KRMS_ACTN_S");
68      static final RepositoryBoIncrementer ruleAttributeIdIncrementer = new RepositoryBoIncrementer("KRMS_RULE_ATTR_S");
69      static final RepositoryBoIncrementer actionAttributeIdIncrementer = new RepositoryBoIncrementer("KRMS_ACTN_ATTR_S");
70  
71      @PortableSequenceGenerator(name = RULE_SEQ_NAME)
72      @GeneratedValue(generator = RULE_SEQ_NAME)
73      @Id
74      @Column(name = "RULE_ID")
75      private String id;
76  
77      @Column(name = "NMSPC_CD")
78      private String namespace;
79  
80      @Column(name = "DESC_TXT")
81      private String description;
82  
83      @Column(name = "NM")
84      private String name;
85  
86      @Column(name = "TYP_ID", nullable = true)
87      private String typeId;
88  
89      @Column(name = "ACTV")
90      @Convert(converter = BooleanYNConverter.class)
91      private boolean active = true;
92  
93      @Column(name = "VER_NBR")
94      @Version
95      private Long versionNumber;
96  
97      @ManyToOne(targetEntity = PropositionBo.class, fetch = FetchType.LAZY, cascade = { CascadeType.REFRESH, CascadeType.REMOVE, CascadeType.PERSIST })
98      @JoinColumn(name = "PROP_ID", referencedColumnName = "PROP_ID", insertable = true, updatable = true)
99      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 }