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 org.apache.commons.lang.StringUtils;
019import org.eclipse.persistence.annotations.PrivateOwned;
020import org.kuali.rice.core.api.mo.common.Versioned;
021import org.kuali.rice.krad.data.DataObjectService;
022import org.kuali.rice.krad.data.jpa.PortableSequenceGenerator;
023import org.kuali.rice.krad.service.KRADServiceLocator;
024import org.kuali.rice.krad.uif.util.ScriptUtils;
025import org.kuali.rice.krms.api.repository.LogicalOperator;
026import org.kuali.rice.krms.api.repository.proposition.PropositionDefinition;
027import org.kuali.rice.krms.api.repository.proposition.PropositionDefinitionContract;
028import org.kuali.rice.krms.api.repository.proposition.PropositionParameter;
029import org.kuali.rice.krms.api.repository.proposition.PropositionParameterType;
030import org.kuali.rice.krms.api.repository.proposition.PropositionType;
031import org.kuali.rice.krms.impl.ui.CustomOperatorUiTranslator;
032import org.kuali.rice.krms.impl.ui.TermParameter;
033import org.kuali.rice.krms.impl.util.KrmsImplConstants;
034import org.kuali.rice.krms.impl.util.KrmsServiceLocatorInternal;
035
036import javax.persistence.CascadeType;
037import javax.persistence.Column;
038import javax.persistence.Entity;
039import javax.persistence.GeneratedValue;
040import javax.persistence.Id;
041import javax.persistence.JoinColumn;
042import javax.persistence.JoinTable;
043import javax.persistence.OneToMany;
044import javax.persistence.OrderBy;
045import javax.persistence.Table;
046import javax.persistence.Transient;
047import javax.persistence.Version;
048import java.io.Serializable;
049import java.util.ArrayList;
050import java.util.HashMap;
051import java.util.List;
052import java.util.Map;
053import java.util.UUID;
054
055@Entity
056@Table(name = "KRMS_PROP_T")
057public class PropositionBo implements PropositionDefinitionContract, Versioned, Serializable {
058
059    private static final long serialVersionUID = 1l;
060
061    private static final String PROP_SEQ_NAME = "KRMS_PROP_S";
062    static final RepositoryBoIncrementer propositionIdIncrementer = new RepositoryBoIncrementer(PROP_SEQ_NAME);
063    static final RepositoryBoIncrementer propositionParameterIdIncrementer = new RepositoryBoIncrementer("KRMS_PROP_PARM_S");
064
065    @PortableSequenceGenerator(name = PROP_SEQ_NAME)
066    @GeneratedValue(generator = PROP_SEQ_NAME)
067    @Id
068    @Column(name = "PROP_ID")
069    private String id;
070
071    @Column(name = "DESC_TXT")
072    private String description;
073
074    @Column(name = "RULE_ID")
075    private String ruleId;
076
077    @Column(name = "TYP_ID")
078    private String typeId;
079
080    @Column(name = "DSCRM_TYP_CD")
081    private String propositionTypeCode;
082
083    @PrivateOwned
084    @OneToMany(orphanRemoval = true, targetEntity = PropositionParameterBo.class, mappedBy = "proposition",
085            cascade = { CascadeType.REFRESH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE })
086    @OrderBy("sequenceNumber")
087    private List<PropositionParameterBo> parameters = new ArrayList<PropositionParameterBo>();
088
089    @Column(name = "CMPND_OP_CD")
090    private String compoundOpCode;
091
092    @Column(name = "CMPND_SEQ_NO")
093    private Integer compoundSequenceNumber;
094
095    @Column(name = "VER_NBR")
096    @Version
097    private Long versionNumber;
098
099    @OneToMany(targetEntity = PropositionBo.class, cascade = { CascadeType.REFRESH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE})
100    @JoinTable(name = "KRMS_CMPND_PROP_PROPS_T", joinColumns = { @JoinColumn(name = "CMPND_PROP_ID", referencedColumnName = "PROP_ID") }, inverseJoinColumns = { @JoinColumn(name = "PROP_ID", referencedColumnName = "PROP_ID") })
101    @OrderBy("compoundSequenceNumber")
102    private List<PropositionBo> compoundComponents;
103
104    @Transient
105    private String parameterDisplayString;
106
107    @Transient
108    private boolean editMode = false;
109
110    @Transient
111    private String categoryId;
112
113    @Transient
114    private String termSpecId;
115
116    @Transient
117    private boolean showCustomValue;
118
119    @Transient
120    private String termParameter;
121
122    @Transient
123    private List<TermParameter> termParameterList = new ArrayList<TermParameter>();
124
125    @Transient
126    private String newTermDescription = "new term " + UUID.randomUUID().toString();
127
128    @Transient
129    private Map<String, String> termParameters = new HashMap<String, String>();
130
131    private void setupParameterDisplayString() {
132        if (PropositionType.SIMPLE.getCode().equalsIgnoreCase(getPropositionTypeCode())) {
133            // Simple Propositions should have 3 parameters ordered in reverse polish notation.
134            // TODO: enhance to get term names for term type parameters.
135            List<PropositionParameterBo> parameters = getParameters();
136
137            if (parameters != null && parameters.size() == 3) {
138                StringBuilder sb = new StringBuilder();
139                String valueDisplay = getParamValue(parameters.get(1));
140
141                String lhs = getParamValue(parameters.get(0));
142                String opr = getParamValue(parameters.get(2));
143
144                // Since we don't want the operator to be escaped by KRAD. We need to escape the lhs and rhs here
145                // so that we can safely display the parameterString as is.
146                sb.append( ScriptUtils.escapeHtml(lhs) ).append(" ").append( opr );
147
148                if (opr != null && (opr.equals("!=null") || opr.equals("=null"))){
149                    valueDisplay = null;
150                }
151
152                if (valueDisplay != null) {
153                    // !=null and =null operators values will be null and should not be displayed
154                    sb.append(" ").append(ScriptUtils.escapeHtml( valueDisplay ));
155                }
156
157                setParameterDisplayString(sb.toString());
158            } else {
159                // should not happen
160            }
161        }
162    }
163
164    /**
165     * returns the string summary value for the given proposition parameter.
166     *
167     * @param param the proposition parameter to get the summary value for
168     * @return the summary value
169     */
170    private String getParamValue(PropositionParameterBo param) {
171        CustomOperatorUiTranslator customOperatorUiTranslator =
172                KrmsServiceLocatorInternal.getCustomOperatorUiTranslator();
173
174        if (PropositionParameterType.TERM.getCode().equalsIgnoreCase(param.getParameterType())) {
175            String termName = "";
176            String termId = param.getValue();
177
178            if (termId != null && termId.length() > 0) {
179                if (termId.startsWith(KrmsImplConstants.PARAMETERIZED_TERM_PREFIX)) {
180                    if (!StringUtils.isBlank(newTermDescription)) {
181                        termName = newTermDescription;
182                    } else {
183                        TermSpecificationBo termSpec = getDataObjectService().find(TermSpecificationBo.class,
184                                termId.substring(1 + termId.indexOf(":")));
185                        termName = termSpec.getName() + "(...)";
186                    }
187                } else {
188                    TermBo term = getDataObjectService().find(TermBo.class, termId);
189                    termName = term.getSpecification().getName();
190                }
191            }
192
193            return termName;
194
195        } else if (PropositionParameterType.FUNCTION.getCode().equalsIgnoreCase(param.getParameterType()) ||
196                PropositionParameterType.OPERATOR.getCode().equalsIgnoreCase(param.getParameterType())) {
197            if (customOperatorUiTranslator.isCustomOperatorFormValue(param.getValue())) {
198                String functionName = customOperatorUiTranslator.getCustomOperatorName(param.getValue());
199                if (!StringUtils.isEmpty(functionName)) {
200                    return functionName;
201                }
202            }
203        }
204
205        return param.getValue();
206    }
207
208    /**
209     * @return the parameterDisplayString
210     */
211    public String getParameterDisplayString() {
212        setupParameterDisplayString();
213
214        return this.parameterDisplayString;
215    }
216
217    /**
218     * @param parameterDisplayString the parameterDisplayString to set
219     */
220    public void setParameterDisplayString(String parameterDisplayString) {
221        this.parameterDisplayString = parameterDisplayString;
222    }
223
224    public boolean getEditMode() {
225        return this.editMode;
226    }
227
228    public void setEditMode(boolean editMode) {
229        this.editMode = editMode;
230    }
231
232    public String getCategoryId() {
233        return this.categoryId;
234    }
235
236    public void setCategoryId(String categoryId) {
237        this.categoryId = categoryId;
238    }
239
240    /**
241     * set the typeId.  If the parameter is blank, then this PropositionBo's
242     * typeId will be set to null
243     *
244     * @param typeId
245     */
246    public void setTypeId(String typeId) {
247        if (StringUtils.isBlank(typeId)) {
248            this.typeId = null;
249        } else {
250            this.typeId = typeId;
251        }
252    }
253
254    @Override
255    public Long getVersionNumber() {
256        return versionNumber;
257    }
258
259    public void setVersionNumber(Long versionNumber) {
260        this.versionNumber = versionNumber;
261    }
262
263    public Map<String, String> getTermParameters() {
264        return termParameters;
265    }
266
267    public void setTermParameters(Map<String, String> termParameters) {
268        this.termParameters = termParameters;
269    }
270
271    public DataObjectService getDataObjectService() {
272        return KRADServiceLocator.getDataObjectService();
273    }
274
275    /**
276     * Converts a mutable bo to it's immutable counterpart
277     *
278     * @param bo the mutable business object
279     * @return the immutable object
280     */
281    public static PropositionDefinition to(PropositionBo bo) {
282        if (bo == null) {
283            return null;
284        }
285
286        return PropositionDefinition.Builder.create(bo).build();
287    }
288
289    /**
290     * Converts a immutable object to it's mutable bo counterpart
291     *
292     * @param im immutable object
293     * @return the mutable bo
294     */
295    public static PropositionBo from(PropositionDefinition im) {
296        if (im == null) {
297            return null;
298        }
299
300        PropositionBo bo = new PropositionBo();
301        bo.id = im.getId();
302        bo.description = im.getDescription();
303
304        // we don't set rule here, it is set in RuleBo.from
305
306        bo.setRuleId(im.getRuleId());
307
308        bo.typeId = im.getTypeId();
309        bo.propositionTypeCode = im.getPropositionTypeCode();
310        bo.parameters = new ArrayList<PropositionParameterBo>();
311
312        for (PropositionParameter parm : im.getParameters()) {
313            PropositionParameterBo parmBo = PropositionParameterBo.from(parm);
314            bo.parameters.add(parmBo);
315            parmBo.setProposition(bo);
316        }
317
318        bo.compoundOpCode = im.getCompoundOpCode();
319        bo.compoundSequenceNumber = im.getCompoundSequenceNumber();
320        bo.compoundComponents = new ArrayList<PropositionBo>();
321
322        if (im.getCompoundComponents() != null) {
323            for (PropositionDefinition prop : im.getCompoundComponents()) {
324                bo.compoundComponents.add(PropositionBo.from(prop));
325            }
326        }
327
328        bo.versionNumber = im.getVersionNumber();
329
330        return bo;
331    }
332
333    /**
334     * This method creates a partially populated Simple PropositionBo with
335     * three parameters:  a term type paramter (value not assigned)
336     * a operation parameter
337     * a constant parameter (value set to empty string)
338     * The returned PropositionBo has an generatedId. The type code and ruleId properties are assigned the
339     * same value as the sibling param passed in.
340     * Each PropositionParameter has the id generated, and type, sequenceNumber,
341     * propId default values set. The value is set to "".
342     *
343     * @param sibling -
344     * @param pType
345     * @return a PropositionBo partially populated.
346     */
347    public static PropositionBo createSimplePropositionBoStub(PropositionBo sibling, String pType) {
348        // create a simple proposition Bo
349        PropositionBo prop = null;
350        if (PropositionType.SIMPLE.getCode().equalsIgnoreCase(pType)) {
351            prop = new PropositionBo();
352            prop.setId(propositionIdIncrementer.getNewId());
353            prop.setPropositionTypeCode(pType);
354            prop.setEditMode(true);
355
356            if (sibling != null) {
357                prop.setRuleId(sibling.getRuleId());
358            }
359
360            // create blank proposition parameters
361            PropositionParameterBo pTerm = new PropositionParameterBo();
362            pTerm.setId(propositionParameterIdIncrementer.getNewId());
363            pTerm.setParameterType("T");
364            pTerm.setProposition(prop);
365            pTerm.setSequenceNumber(new Integer("0"));
366            pTerm.setVersionNumber(new Long(1));
367            pTerm.setValue("");
368
369            // create blank proposition parameters
370            PropositionParameterBo pOp = new PropositionParameterBo();
371            pOp.setId(propositionParameterIdIncrementer.getNewId());
372            pOp.setParameterType("O");
373            pOp.setProposition(prop);
374            pOp.setSequenceNumber(new Integer("2"));
375            pOp.setVersionNumber(new Long(1));
376
377            // create blank proposition parameters
378            PropositionParameterBo pConst = new PropositionParameterBo();
379            pConst.setId(propositionParameterIdIncrementer.getNewId());
380            pConst.setParameterType("C");
381            pConst.setProposition(prop);
382            pConst.setSequenceNumber(new Integer("1"));
383            pConst.setVersionNumber(new Long(1));
384            pConst.setValue("");
385
386            List<PropositionParameterBo> paramList = new ArrayList<PropositionParameterBo>(3);
387            paramList.add(pTerm);
388            paramList.add(pConst);
389            paramList.add(pOp);
390
391            prop.setParameters(paramList);
392        }
393
394        return prop;
395    }
396
397    public static PropositionBo createCompoundPropositionBoStub(PropositionBo existing, boolean addNewChild) {
398        // create a simple proposition Bo
399        PropositionBo prop = new PropositionBo();
400        prop.setId(propositionIdIncrementer.getNewId());
401        prop.setPropositionTypeCode(PropositionType.COMPOUND.getCode());
402        prop.setCompoundOpCode(LogicalOperator.AND.getCode());
403
404        // default to and
405        prop.setDescription("");
406        prop.setEditMode(true);
407
408        if (existing != null) {
409            prop.setRuleId(existing.getRuleId());
410        }
411
412        List<PropositionBo> components = new ArrayList<PropositionBo>(2);
413        components.add(existing);
414
415        if (addNewChild) {
416            PropositionBo newProp = createSimplePropositionBoStub(existing, PropositionType.SIMPLE.getCode());
417            components.add(newProp);
418            prop.setEditMode(false);
419        }
420
421        prop.setCompoundComponents(components);
422
423        return prop;
424    }
425
426    public static PropositionBo createCompoundPropositionBoStub2(PropositionBo existing) {
427        // create a simple proposition Bo
428        PropositionBo prop = new PropositionBo();
429        prop.setId(propositionIdIncrementer.getNewId());
430        prop.setPropositionTypeCode(PropositionType.COMPOUND.getCode());
431        prop.setRuleId(existing.getRuleId());
432        prop.setCompoundOpCode(LogicalOperator.AND.getCode());
433        // default to and
434        prop.setDescription("");
435        prop.setEditMode(true);
436        List<PropositionBo> components = new ArrayList<PropositionBo>();
437        ((ArrayList<PropositionBo>) components).add(existing);
438        prop.setCompoundComponents(components);
439
440        return prop;
441    }
442
443    public static PropositionBo copyProposition(PropositionBo existing) {
444        // Note: RuleId is not set
445        PropositionBo newProp = new PropositionBo();
446        newProp.setId(propositionIdIncrementer.getNewId());
447        newProp.setDescription(existing.getDescription());
448        newProp.setPropositionTypeCode(existing.getPropositionTypeCode());
449        newProp.setTypeId(existing.getTypeId());
450        newProp.setCompoundOpCode(existing.getCompoundOpCode());
451        newProp.setCompoundSequenceNumber(existing.getCompoundSequenceNumber());
452
453        // parameters
454        List<PropositionParameterBo> newParms = new ArrayList<PropositionParameterBo>();
455
456        for (PropositionParameterBo parm : existing.getParameters()) {
457            PropositionParameterBo p = new PropositionParameterBo();
458            p.setId(propositionParameterIdIncrementer.getNewId());
459            p.setParameterType(parm.getParameterType());
460            p.setProposition(newProp);
461            p.setSequenceNumber(parm.getSequenceNumber());
462            p.setValue(parm.getValue());
463            ((ArrayList<PropositionParameterBo>) newParms).add(p);
464        }
465
466        newProp.setParameters(newParms);
467
468        // compoundComponents
469        List<PropositionBo> newCompoundComponents = new ArrayList<PropositionBo>();
470        for (PropositionBo component : existing.getCompoundComponents()) {
471            PropositionBo newComponent = copyProposition(component);
472            ((ArrayList<PropositionBo>) newCompoundComponents).add(newComponent);
473        }
474
475        newProp.setCompoundComponents(newCompoundComponents);
476
477        return newProp;
478    }
479
480    public String getTermSpecId() {
481        return termSpecId;
482    }
483
484    public void setTermSpecId(String componentId) {
485        this.termSpecId = componentId;
486    }
487
488    public boolean isShowCustomValue() {
489        return showCustomValue;
490    }
491
492    public void setShowCustomValue(boolean showCustomValue) {
493        this.showCustomValue = showCustomValue;
494    }
495
496    public String getTermParameter() {
497        return termParameter;
498    }
499
500    public void setTermParameter(String termParameter) {
501        this.termParameter = termParameter;
502    }
503
504    public List<TermParameter> getTermParameterList() {
505        return termParameterList;
506    }
507
508    public void setTermParameterList(List<TermParameter> termParameterList) {
509        this.termParameterList = termParameterList;
510    }
511
512    @Override
513    public String getId() {
514        return id;
515    }
516
517    public void setId(String id) {
518        this.id = id;
519    }
520
521    @Override
522    public String getDescription() {
523        return description;
524    }
525
526    public void setDescription(String description) {
527        this.description = description;
528    }
529
530    @Override
531    public String getRuleId() {
532        return ruleId;
533    }
534
535    /**
536     * Sets the ruleId on this proposition and all its compound components.
537     *
538     * @param ruleId the ruleId to set
539     */
540    public void setRuleId(String ruleId) {
541        this.ruleId = ruleId;
542
543        if (getCompoundComponents() != null) for (PropositionBo child : getCompoundComponents()) {
544            child.setRuleId(ruleId);
545        }
546    }
547
548    @Override
549    public String getTypeId() {
550        return typeId;
551    }
552
553    @Override
554    public String getPropositionTypeCode() {
555        return propositionTypeCode;
556    }
557
558    public void setPropositionTypeCode(String propositionTypeCode) {
559        this.propositionTypeCode = propositionTypeCode;
560    }
561
562    @Override
563    public List<PropositionParameterBo> getParameters() {
564        return parameters;
565    }
566
567    public void setParameters(List<PropositionParameterBo> parameters) {
568        this.parameters = parameters;
569    }
570
571    @Override
572    public String getCompoundOpCode() {
573        return compoundOpCode;
574    }
575
576    public void setCompoundOpCode(String compoundOpCode) {
577        this.compoundOpCode = compoundOpCode;
578    }
579
580    @Override
581    public Integer getCompoundSequenceNumber() {
582        return compoundSequenceNumber;
583    }
584
585    public void setCompoundSequenceNumber(Integer compoundSequenceNumber) {
586        this.compoundSequenceNumber = compoundSequenceNumber;
587    }
588
589    @Override
590    public List<PropositionBo> getCompoundComponents() {
591        return compoundComponents;
592    }
593
594    public void setCompoundComponents(List<PropositionBo> compoundComponents) {
595        this.compoundComponents = compoundComponents;
596    }
597
598    public boolean getShowCustomValue() {
599        return showCustomValue;
600    }
601
602    public String getNewTermDescription() {
603        return newTermDescription;
604    }
605
606    public void setNewTermDescription(String newTermDescription) {
607        this.newTermDescription = newTermDescription;
608    }
609}