001/**
002 * Copyright 2005-2014 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krms.impl.repository;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.rice.core.api.mo.common.Versioned;
020import org.kuali.rice.krad.data.DataObjectService;
021import org.kuali.rice.krad.data.jpa.PortableSequenceGenerator;
022import org.kuali.rice.krad.service.KRADServiceLocator;
023import org.kuali.rice.krms.api.repository.LogicalOperator;
024import org.kuali.rice.krms.api.repository.proposition.PropositionDefinition;
025import org.kuali.rice.krms.api.repository.proposition.PropositionDefinitionContract;
026import org.kuali.rice.krms.api.repository.proposition.PropositionParameter;
027import org.kuali.rice.krms.api.repository.proposition.PropositionParameterType;
028import org.kuali.rice.krms.api.repository.proposition.PropositionType;
029import org.kuali.rice.krms.impl.ui.CustomOperatorUiTranslator;
030import org.kuali.rice.krms.impl.ui.TermParameter;
031import org.kuali.rice.krms.impl.util.KrmsImplConstants;
032import org.kuali.rice.krms.impl.util.KrmsServiceLocatorInternal;
033
034import javax.persistence.CascadeType;
035import javax.persistence.Column;
036import javax.persistence.Entity;
037import javax.persistence.GeneratedValue;
038import javax.persistence.Id;
039import javax.persistence.JoinColumn;
040import javax.persistence.JoinTable;
041import javax.persistence.ManyToMany;
042import javax.persistence.OneToMany;
043import javax.persistence.OrderBy;
044import javax.persistence.Table;
045import javax.persistence.Transient;
046import javax.persistence.Version;
047import java.io.IOException;
048import java.io.ObjectOutputStream;
049import java.io.Serializable;
050import java.util.ArrayList;
051import java.util.Arrays;
052import java.util.HashMap;
053import java.util.List;
054import java.util.Map;
055import java.util.UUID;
056
057@Entity
058@Table(name = "KRMS_PROP_T")
059public class PropositionBo implements PropositionDefinitionContract, Versioned, Serializable {
060
061    private static final long serialVersionUID = 1l;
062
063    private static final String PROP_SEQ_NAME = "KRMS_PROP_S";
064    static final RepositoryBoIncrementer propositionIdIncrementer = new RepositoryBoIncrementer(PROP_SEQ_NAME);
065    static final RepositoryBoIncrementer propositionParameterIdIncrementer = new RepositoryBoIncrementer("KRMS_PROP_PARM_S");
066
067    @PortableSequenceGenerator(name = PROP_SEQ_NAME)
068    @GeneratedValue(generator = PROP_SEQ_NAME)
069    @Id
070    @Column(name = "PROP_ID")
071    private String id;
072
073    @Column(name = "DESC_TXT")
074    private String description;
075
076    @Column(name = "RULE_ID")
077    private String ruleId;
078
079    @Column(name = "TYP_ID")
080    private String typeId;
081
082    @Column(name = "DSCRM_TYP_CD")
083    private String propositionTypeCode;
084
085    @OneToMany(orphanRemoval = true, cascade = CascadeType.ALL, mappedBy = "proposition")
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    @ManyToMany(targetEntity = PropositionBo.class, cascade = { CascadeType.REFRESH, CascadeType.MERGE, CascadeType.REMOVE, CascadeType.PERSIST })
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                sb.append(getParamValue(parameters.get(0))).append(" ").append(getParamValue(parameters.get(2)));
141
142                if (valueDisplay != null) {
143                    // !=null and =null operators values will be null and should not be displayed  
144                    sb.append(" ").append(valueDisplay);
145                }
146
147                setParameterDisplayString(sb.toString());
148            } else {
149                // should not happen
150            }
151        }
152    }
153
154    /**
155     * returns the string summary value for the given proposition parameter.
156     *
157     * @param param the proposition parameter to get the summary value for
158     * @return the summary value
159     */
160    private String getParamValue(PropositionParameterBo param) {
161        CustomOperatorUiTranslator customOperatorUiTranslator =
162                KrmsServiceLocatorInternal.getCustomOperatorUiTranslator();
163
164        if (PropositionParameterType.TERM.getCode().equalsIgnoreCase(param.getParameterType())) {
165            String termName = "";
166            String termId = param.getValue();
167
168            if (termId != null && termId.length() > 0) {
169                if (termId.startsWith(KrmsImplConstants.PARAMETERIZED_TERM_PREFIX)) {
170                    if (!StringUtils.isBlank(newTermDescription)) {
171                        termName = newTermDescription;
172                    } else {
173                        TermSpecificationBo termSpec = getDataObjectService().find(TermSpecificationBo.class,
174                                termId.substring(1 + termId.indexOf(":")));
175                        termName = termSpec.getName() + "(...)";
176                    }
177                } else {
178                    TermBo term = getDataObjectService().find(TermBo.class, termId);
179                    termName = term.getSpecification().getName();
180                }
181            }
182
183            return termName;
184
185        } else if (PropositionParameterType.FUNCTION.getCode().equalsIgnoreCase(param.getParameterType()) ||
186                PropositionParameterType.OPERATOR.getCode().equalsIgnoreCase(param.getParameterType())) {
187            if (customOperatorUiTranslator.isCustomOperatorFormValue(param.getValue())) {
188                String functionName = customOperatorUiTranslator.getCustomOperatorName(param.getValue());
189                if (!StringUtils.isEmpty(functionName)) {
190                    return functionName;
191                }
192            }
193        }
194
195        return param.getValue();
196    }
197
198    /**
199     * @return the parameterDisplayString
200     */
201    public String getParameterDisplayString() {
202        setupParameterDisplayString();
203
204        return this.parameterDisplayString;
205    }
206
207    /**
208     * @param parameterDisplayString the parameterDisplayString to set
209     */
210    public void setParameterDisplayString(String parameterDisplayString) {
211        this.parameterDisplayString = parameterDisplayString;
212    }
213
214    public boolean getEditMode() {
215        return this.editMode;
216    }
217
218    public void setEditMode(boolean editMode) {
219        this.editMode = editMode;
220    }
221
222    public String getCategoryId() {
223        return this.categoryId;
224    }
225
226    public void setCategoryId(String categoryId) {
227        this.categoryId = categoryId;
228    }
229
230    /**
231     * set the typeId.  If the parameter is blank, then this PropositionBo's
232     * typeId will be set to null
233     *
234     * @param typeId
235     */
236    public void setTypeId(String typeId) {
237        if (StringUtils.isBlank(typeId)) {
238            this.typeId = null;
239        } else {
240            this.typeId = typeId;
241        }
242    }
243
244    public Long getVersionNumber() {
245        return versionNumber;
246    }
247
248    public void setVersionNumber(Long versionNumber) {
249        this.versionNumber = versionNumber;
250    }
251
252    public Map<String, String> getTermParameters() {
253        return termParameters;
254    }
255
256    public void setTermParameters(Map<String, String> termParameters) {
257        this.termParameters = termParameters;
258    }
259
260    public DataObjectService getDataObjectService() {
261        return KRADServiceLocator.getDataObjectService();
262    }
263
264    /**
265     * Converts a mutable bo to it's immutable counterpart
266     *
267     * @param bo the mutable business object
268     * @return the immutable object
269     */
270    public static PropositionDefinition to(PropositionBo bo) {
271        if (bo == null) {
272            return null;
273        }
274
275        return PropositionDefinition.Builder.create(bo).build();
276    }
277
278    /**
279     * Converts a immutable object to it's mutable bo counterpart
280     *
281     * @param im immutable object
282     * @return the mutable bo
283     */
284    public static PropositionBo from(PropositionDefinition im) {
285        if (im == null) {
286            return null;
287        }
288
289        PropositionBo bo = new PropositionBo();
290        bo.id = im.getId();
291        bo.description = im.getDescription();
292
293        // we don't set rule here, it is set in RuleBo.from
294
295        setRuleIdRecursive(im.getRuleId(), bo);
296
297        bo.typeId = im.getTypeId();
298        bo.propositionTypeCode = im.getPropositionTypeCode();
299        bo.parameters = new ArrayList<PropositionParameterBo>();
300
301        for (PropositionParameter parm : im.getParameters()) {
302            PropositionParameterBo parmBo = PropositionParameterBo.from(parm);
303            bo.parameters.add(parmBo);
304            parmBo.setProposition(bo);
305        }
306
307        bo.compoundOpCode = im.getCompoundOpCode();
308        bo.compoundSequenceNumber = im.getCompoundSequenceNumber();
309        bo.compoundComponents = new ArrayList<PropositionBo>();
310
311        if (im.getCompoundComponents() != null) for (PropositionDefinition prop : im.getCompoundComponents()) {
312            bo.compoundComponents.add(PropositionBo.from(prop));
313        }
314
315        bo.setVersionNumber(im.getVersionNumber());
316
317        return bo;
318    }
319
320    private static void setRuleIdRecursive(String ruleId, PropositionBo prop) {
321        prop.ruleId = ruleId;
322
323        if (prop.compoundComponents != null) {
324            for (PropositionBo child : prop.compoundComponents) {
325                if (child != null) {
326                    setRuleIdRecursive(ruleId, child);
327                }
328            }
329        }
330    }
331
332    /**
333     * This method creates a partially populated Simple PropositionBo with
334     * three parameters:  a term type paramter (value not assigned)
335     * a operation parameter
336     * a constant parameter (value set to empty string)
337     * The returned PropositionBo has an generatedId. The type code and ruleId properties are assigned the
338     * same value as the sibling param passed in.
339     * Each PropositionParameter has the id generated, and type, sequenceNumber,
340     * propId default values set. The value is set to "".
341     *
342     * @param sibling -
343     * @param pType
344     * @return a PropositionBo partially populated.
345     */
346    public static PropositionBo createSimplePropositionBoStub(PropositionBo sibling, String pType) {
347        // create a simple proposition Bo  
348        PropositionBo prop = null;
349        if (PropositionType.SIMPLE.getCode().equalsIgnoreCase(pType)) {
350            prop = new PropositionBo();
351            prop.setId(propositionIdIncrementer.getNewId());
352            prop.setPropositionTypeCode(pType);
353            prop.setEditMode(true);
354
355            if (sibling != null) {
356                prop.setRuleId(sibling.getRuleId());
357            }
358
359            // create blank proposition parameters  
360            PropositionParameterBo pTerm = new PropositionParameterBo();
361            pTerm.setId(propositionParameterIdIncrementer.getNewId());
362            pTerm.setParameterType("T");
363            pTerm.setProposition(prop);
364            pTerm.setSequenceNumber(new Integer("0"));
365            pTerm.setVersionNumber(new Long(1));
366            pTerm.setValue("");
367
368            // create blank proposition parameters  
369            PropositionParameterBo pOp = new PropositionParameterBo();
370            pOp.setId(propositionParameterIdIncrementer.getNewId());
371            pOp.setParameterType("O");
372            pOp.setProposition(prop);
373            pOp.setSequenceNumber(new Integer("2"));
374            pOp.setVersionNumber(new Long(1));
375
376            // create blank proposition parameters  
377            PropositionParameterBo pConst = new PropositionParameterBo();
378            pConst.setId(propositionParameterIdIncrementer.getNewId());
379            pConst.setParameterType("C");
380            pConst.setProposition(prop);
381            pConst.setSequenceNumber(new Integer("1"));
382            pConst.setVersionNumber(new Long(1));
383            pConst.setValue("");
384            List<PropositionParameterBo> paramList = Arrays.asList(pTerm, pConst, pOp);
385
386            prop.setParameters(paramList);
387        }
388
389        return prop;
390    }
391
392    public static PropositionBo createCompoundPropositionBoStub(PropositionBo existing, boolean addNewChild) {
393        // create a simple proposition Bo  
394        PropositionBo prop = new PropositionBo();
395        prop.setId(propositionIdIncrementer.getNewId());
396        prop.setPropositionTypeCode(PropositionType.COMPOUND.getCode());
397        prop.setCompoundOpCode(LogicalOperator.AND.getCode());
398
399        // default to and  
400        prop.setDescription("");
401        prop.setEditMode(true);
402
403        if (existing != null) {
404            prop.setRuleId(existing.getRuleId());
405        }
406
407        List<PropositionBo> components = new ArrayList<PropositionBo>(2);
408        components.add(existing);
409
410        if (addNewChild) {
411            PropositionBo newProp = createSimplePropositionBoStub(existing, PropositionType.SIMPLE.getCode());
412            components.add(newProp);
413            prop.setEditMode(false);
414        }
415
416        prop.setCompoundComponents(components);
417
418        return prop;
419    }
420
421    public static PropositionBo createCompoundPropositionBoStub2(PropositionBo existing) {
422        // create a simple proposition Bo  
423        PropositionBo prop = new PropositionBo();
424        prop.setId(propositionIdIncrementer.getNewId());
425        prop.setPropositionTypeCode(PropositionType.COMPOUND.getCode());
426        prop.setRuleId(existing.getRuleId());
427        prop.setCompoundOpCode(LogicalOperator.AND.getCode());
428        // default to and  
429        prop.setDescription("");
430        prop.setEditMode(true);
431        List<PropositionBo> components = new ArrayList<PropositionBo>();
432        ((ArrayList<PropositionBo>) components).add(existing);
433        prop.setCompoundComponents(components);
434
435        return prop;
436    }
437
438    public static PropositionBo copyProposition(PropositionBo existing) {
439        // Note: RuleId is not set  
440        PropositionBo newProp = new PropositionBo();
441        newProp.setId(propositionIdIncrementer.getNewId());
442        newProp.setDescription(existing.getDescription());
443        newProp.setPropositionTypeCode(existing.getPropositionTypeCode());
444        newProp.setTypeId(existing.getTypeId());
445        newProp.setCompoundOpCode(existing.getCompoundOpCode());
446        newProp.setCompoundSequenceNumber(existing.getCompoundSequenceNumber());
447
448        // parameters  
449        List<PropositionParameterBo> newParms = new ArrayList<PropositionParameterBo>();
450
451        for (PropositionParameterBo parm : existing.getParameters()) {
452            PropositionParameterBo p = new PropositionParameterBo();
453            p.setId(propositionParameterIdIncrementer.getNewId());
454            p.setParameterType(parm.getParameterType());
455            p.setProposition(newProp);
456            p.setSequenceNumber(parm.getSequenceNumber());
457            p.setValue(parm.getValue());
458            ((ArrayList<PropositionParameterBo>) newParms).add(p);
459        }
460
461        newProp.setParameters(newParms);
462
463        // compoundComponents
464        List<PropositionBo> newCompoundComponents = new ArrayList<PropositionBo>();
465        for (PropositionBo component : existing.getCompoundComponents()) {
466            PropositionBo newComponent = copyProposition(component);
467            ((ArrayList<PropositionBo>) newCompoundComponents).add(component);
468        }
469
470        newProp.setCompoundComponents(newCompoundComponents);
471
472        return newProp;
473    }
474
475    /*
476     * This is being done because there is a  major issue with lazy relationships, in ensuring that the relationship is
477     * still available after the object has been detached, or serialized. For most JPA providers, after serialization
478     * any lazy relationship that was not instantiated will be broken, and either throw an error when accessed,
479     * or return null.
480     */
481    private void writeObject(ObjectOutputStream stream) throws IOException, ClassNotFoundException {
482        parameters.size();
483        stream.defaultWriteObject();
484    }
485
486    public String getTermSpecId() {
487        return termSpecId;
488    }
489
490    public void setTermSpecId(String componentId) {
491        this.termSpecId = componentId;
492    }
493
494    public boolean isShowCustomValue() {
495        return showCustomValue;
496    }
497
498    public void setShowCustomValue(boolean showCustomValue) {
499        this.showCustomValue = showCustomValue;
500    }
501
502    public String getTermParameter() {
503        return termParameter;
504    }
505
506    public void setTermParameter(String termParameter) {
507        this.termParameter = termParameter;
508    }
509
510    public List<TermParameter> getTermParameterList() {
511        return termParameterList;
512    }
513
514    public void setTermParameterList(List<TermParameter> termParameterList) {
515        this.termParameterList = termParameterList;
516    }
517
518    public String getId() {
519        return id;
520    }
521
522    public void setId(String id) {
523        this.id = id;
524    }
525
526    public String getDescription() {
527        return description;
528    }
529
530    public void setDescription(String description) {
531        this.description = description;
532    }
533
534    public String getRuleId() {
535        return ruleId;
536    }
537
538    public void setRuleId(String ruleId) {
539        this.ruleId = ruleId;
540    }
541
542    public String getTypeId() {
543        return typeId;
544    }
545
546    public String getPropositionTypeCode() {
547        return propositionTypeCode;
548    }
549
550    public void setPropositionTypeCode(String propositionTypeCode) {
551        this.propositionTypeCode = propositionTypeCode;
552    }
553
554    public List<PropositionParameterBo> getParameters() {
555        return parameters;
556    }
557
558    public void setParameters(List<PropositionParameterBo> parameters) {
559        this.parameters = parameters;
560    }
561
562    public String getCompoundOpCode() {
563        return compoundOpCode;
564    }
565
566    public void setCompoundOpCode(String compoundOpCode) {
567        this.compoundOpCode = compoundOpCode;
568    }
569
570    public Integer getCompoundSequenceNumber() {
571        return compoundSequenceNumber;
572    }
573
574    public void setCompoundSequenceNumber(Integer compoundSequenceNumber) {
575        this.compoundSequenceNumber = compoundSequenceNumber;
576    }
577
578    public List<PropositionBo> getCompoundComponents() {
579        return compoundComponents;
580    }
581
582    public void setCompoundComponents(List<PropositionBo> compoundComponents) {
583        this.compoundComponents = compoundComponents;
584    }
585
586    public boolean getShowCustomValue() {
587        return showCustomValue;
588    }
589
590    public String getNewTermDescription() {
591        return newTermDescription;
592    }
593
594    public void setNewTermDescription(String newTermDescription) {
595        this.newTermDescription = newTermDescription;
596    }
597}