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}