001    /**
002     * Copyright 2005-2013 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     */
016    package org.kuali.rice.krms.api.repository.proposition;
017    
018    import java.io.Serializable;
019    import java.util.ArrayList;
020    import java.util.Collection;
021    import java.util.Collections;
022    import java.util.List;
023    
024    import javax.xml.bind.annotation.XmlAccessType;
025    import javax.xml.bind.annotation.XmlAccessorType;
026    import javax.xml.bind.annotation.XmlAnyElement;
027    import javax.xml.bind.annotation.XmlElement;
028    import javax.xml.bind.annotation.XmlElementWrapper;
029    import javax.xml.bind.annotation.XmlRootElement;
030    import javax.xml.bind.annotation.XmlType;
031    import org.kuali.rice.krms.api.repository.term.TermParameterDefinition;
032    
033    import org.apache.commons.lang.StringUtils;
034    import org.kuali.rice.core.api.CoreConstants;
035    import org.kuali.rice.core.api.mo.AbstractDataTransferObject;
036    import org.kuali.rice.core.api.mo.ModelBuilder;
037    import org.kuali.rice.krms.api.KrmsConstants;
038    import org.kuali.rice.krms.api.repository.LogicalOperator;
039    import org.kuali.rice.krms.api.repository.rule.RuleDefinition;
040    
041    /**
042     * Concrete model object implementation of KRMS Proposition. 
043     * Immutable. 
044     * Instances of Proposition can be (un)marshalled to and from XML.
045     *
046     * There are three main types of Propositions:
047     *    1. Compound Propositions - a proposition consisting of other propositions
048     *       and a boolean algebra operator (AND, OR) defining how to evaluate those propositions.
049     *    2. Parameterized Propositions - a proposition which is parameterized by some set of values, 
050     *       evaluation logic is implemented by hand and returns true or false
051     *    3. Simple Propositions - a proposition of the form lhs op rhs where 
052     *      lhs=left-hand side, rhs=right-hand side, and op=operator
053     * Propositions are reference by a rule or another proposition (in the case of compound propositions).
054     * Propositions are never re-used across multiple rules.
055     * Each proposition can have zero or more parameters. The proposition parameter is the primary 
056     * data element used to define the proposition.  (@see PropositionParameter)
057     * 
058     * @see PropositonContract
059     * @see PropositionParameterContract
060     */
061    @XmlRootElement(name = PropositionDefinition.Constants.ROOT_ELEMENT_NAME)
062    @XmlAccessorType(XmlAccessType.NONE)
063    @XmlType(name = PropositionDefinition.Constants.TYPE_NAME, propOrder = {
064                    PropositionDefinition.Elements.ID,
065                    PropositionDefinition.Elements.DESC,
066            PropositionDefinition.Elements.RULE_ID,
067            PropositionDefinition.Elements.TYPE_ID,
068                    PropositionDefinition.Elements.PROP_TYPE_CODE,
069                    PropositionDefinition.Elements.PARAMETERS,                                                                      // xml element name differs from class property name
070                    PropositionDefinition.Elements.CMPND_OP_CODE,
071                    PropositionDefinition.Elements.CMPND_COMPONENTS,
072            CoreConstants.CommonElements.VERSION_NUMBER,
073            PropositionDefinition.Elements.CMPND_SEQ_NO,
074                    CoreConstants.CommonElements.FUTURE_ELEMENTS
075    })
076    public final class PropositionDefinition extends AbstractDataTransferObject implements PropositionDefinitionContract {
077            private static final long serialVersionUID = 2783959459503209577L;
078    
079            // TODO: change this to field name to id
080            @XmlElement(name = Elements.ID, required=true)
081            private String id;
082    
083            @XmlElement(name = Elements.DESC, required=true)
084            private String description;
085    
086            @XmlElement(name = Elements.TYPE_ID, required=true)
087            private String typeId;
088    
089        @XmlElement(name = Elements.RULE_ID, required=true)
090        private String ruleId;
091    
092        @XmlElement(name = Elements.PROP_TYPE_CODE, required=true)
093            private String propositionTypeCode;
094    
095            @XmlElementWrapper(name = Elements.PARAMETERS)
096            @XmlElement(name = Elements.PARAMETER, required=false)
097            private List<PropositionParameter> parameters;
098    
099            @XmlElement(name = Elements.CMPND_OP_CODE, required=false)
100            private String compoundOpCode;
101            
102            @XmlElementWrapper(name = Elements.CMPND_COMPONENTS, required=false)
103            @XmlElement(name = Elements.CMPND_COMPONENT, required=false)
104            private List<PropositionDefinition> compoundComponents;
105    
106        @XmlElement(name = CoreConstants.CommonElements.VERSION_NUMBER, required = false)
107        private final Long versionNumber;
108    
109        @XmlElement(name = Elements.CMPND_SEQ_NO, required=false)
110        private Integer compoundSequenceNumber;
111    
112        @SuppressWarnings("unused")
113        @XmlAnyElement
114        private final Collection<org.w3c.dom.Element> _futureElements = null;
115    
116    
117             /**
118         * This constructor should never be called.  It is only present for use during JAXB unmarshalling.
119         */
120        private PropositionDefinition() {
121            this.id = null;
122            this.description = null;
123            this.typeId = null;
124            this.propositionTypeCode = null;
125            this.parameters = null;
126            this.compoundOpCode = null;
127            this.compoundSequenceNumber = null;
128            this.compoundComponents = null;
129            this.versionNumber = null;
130        }
131    
132        /**
133             * Constructs a KRMS Proposition from the given builder.
134             * This constructor is private and should only ever be invoked from the builder.
135             *
136             * @param builder the Builder from which to construct the KRMS Proposition
137             */
138        private PropositionDefinition(Builder builder) {
139            this.id = builder.getId();
140            this.description = builder.getDescription();
141            this.ruleId = builder.getRuleId();
142            this.typeId = builder.getTypeId();
143            this.propositionTypeCode = builder.getPropositionTypeCode();
144    
145            // Build parameter list
146            List<PropositionParameter> paramList = new ArrayList<PropositionParameter>();
147            for (PropositionParameter.Builder b : builder.parameters){
148                    b.setProposition(builder);
149                    paramList.add(b.build());
150            }
151            this.parameters = Collections.unmodifiableList(paramList);
152    
153            // Build Compound Proposition properties
154            this.compoundOpCode = builder.getCompoundOpCode();
155            this.compoundSequenceNumber = builder.getCompoundSequenceNumber();
156            List <PropositionDefinition> componentList = new ArrayList<PropositionDefinition>();
157            if (builder.compoundComponents != null){
158                    for (PropositionDefinition.Builder b : builder.compoundComponents){
159                            componentList.add(b.build());
160                    }
161                this.compoundComponents = Collections.unmodifiableList(componentList);
162            }
163            this.versionNumber = builder.getVersionNumber();
164        }
165    
166            @Override
167            public String getId() {
168                    return this.id;
169            }
170    
171            @Override
172            public String getDescription() {
173                    return this.description;
174            }
175    
176        /**
177         * @return the ruleId
178         */
179        @Override
180        public String getRuleId() {
181            return this.ruleId;
182        }
183    
184            @Override
185            public String getTypeId() {
186                    return this.typeId;
187            }
188    
189            @Override
190            public String getPropositionTypeCode() {
191                    return this.propositionTypeCode;
192            }
193    
194            @Override
195            public List<PropositionParameter> getParameters() {
196                    return this.parameters;
197            }
198    
199            @Override
200            public String getCompoundOpCode() {
201                    return this.compoundOpCode;
202            }
203            
204            @Override
205            public List<PropositionDefinition> getCompoundComponents() {
206                    return this.compoundComponents;
207            }
208    
209        @Override
210        public Long getVersionNumber() {
211            return versionNumber;
212        }
213    
214        @Override
215        public Integer getCompoundSequenceNumber() {
216            return this.compoundSequenceNumber;
217        }
218    
219        /**
220         * This builder is used to construct instances of KRMS Proposition.  It enforces the constraints of the {@link PropositionDefinitionContract}.
221         */
222        public static class Builder implements PropositionDefinitionContract, ModelBuilder, Serializable {
223            private static final long serialVersionUID = -6889320709850568900L;
224    
225            private String id;
226            private String description;
227            private String ruleId;
228            private String typeId;
229            private String propositionTypeCode;
230            private List<PropositionParameter.Builder> parameters;
231            private String compoundOpCode;
232            private Integer compoundSequenceNumber;
233            private List<PropositionDefinition.Builder> compoundComponents;
234            private RuleDefinition.Builder rule;
235            private Long versionNumber;
236    
237                    /**
238                     * Private constructor for creating a builder with all of it's required attributes.
239             * @param propId the propId value to set
240             * @param propTypeCode the propTypeCode value to set
241             * @param ruleId the ruleId value to set
242             * @param typeId the typeId value to set
243             * @param parameters the parameters value to set
244             */
245            private Builder(String propId, String propTypeCode, String ruleId, String typeId, List<PropositionParameter.Builder> parameters) {
246                    setId(propId);
247                            setPropositionTypeCode(propTypeCode);
248                            setRuleId(ruleId);
249                            setTypeId(typeId);
250                            setParameters(parameters);
251            }
252    
253            /**
254             * Set the value of the opCode to the given value.
255             * @param opCode the opCode value to set
256             * @return Builder an instance of the builder populated with given parameters
257             */
258            public Builder compoundOpCode(String opCode){
259                    setCompoundOpCode(opCode);
260                    return this;
261            }
262    
263            /**
264             * Set the value of the components to the given value.
265             * @param components the components value to set
266             * @return Builder
267             */
268            public Builder compoundComponents (List<PropositionDefinition.Builder> components){
269                    setCompoundComponents(components);
270                    return this;
271            }
272    
273            /**
274             * Create a Builder with the given values
275             * @param propId the propId value to set
276             * @param propTypeCode the propTypeCode value to set
277             * @param ruleId the ruleId value to set
278             * @param typeId the typeId value to set
279             * @param parameters the parameters value to set
280             * @return Builder an instance of the builder populated with given parameters
281             */
282            public static Builder create(String propId, String propTypeCode, String ruleId, String typeId, List<PropositionParameter.Builder> parameters){
283                    return new Builder(propId, propTypeCode, ruleId, typeId, parameters);
284            }
285    
286            /**
287             * Creates a builder by populating it with data from the given {@link PropositionDefinitionContract}.
288             *
289             * @param contract the contract from which to populate this builder
290             * @return an instance of the builder populated with data from the contract
291             */
292            public static Builder create(PropositionDefinitionContract contract) {
293                    if (contract == null) {
294                    throw new IllegalArgumentException("contract is null");
295                }
296                    List <PropositionParameter.Builder> paramBuilderList = new ArrayList<PropositionParameter.Builder>();
297                    if (contract.getParameters() != null){
298                            for (PropositionParameterContract paramContract : contract.getParameters()){
299                                    PropositionParameter.Builder myBuilder = PropositionParameter.Builder.create(paramContract);
300                                    paramBuilderList.add(myBuilder);
301                            }
302                    }
303                Builder builder =  new Builder(contract.getId(), contract.getPropositionTypeCode(), contract.getRuleId(), contract.getTypeId(), paramBuilderList);
304    
305                    List <PropositionDefinition.Builder> componentBuilderList = new ArrayList<PropositionDefinition.Builder>();
306                    if (contract.getCompoundComponents() != null) {
307                            for (PropositionDefinitionContract cContract : contract.getCompoundComponents()){
308                                    PropositionDefinition.Builder pBuilder = PropositionDefinition.Builder.create(cContract);
309                                    componentBuilderList.add(pBuilder);
310                            }
311                    builder.setCompoundComponents(componentBuilderList);
312                    }
313                    builder.setCompoundOpCode(contract.getCompoundOpCode());
314                    builder.setCompoundSequenceNumber(contract.getCompoundSequenceNumber());
315                builder.setDescription(contract.getDescription());
316                builder.setVersionNumber(contract.getVersionNumber());
317                return builder;
318            }
319    
320                    /**
321                     * Sets the value of the propId on this builder to the given value.
322                     *
323                     * @param propId the propId value to set
324             * @throws IllegalArgumentException if the propId is null or blank
325                     */
326            public void setId(String propId) {
327                if (propId != null && StringUtils.isBlank(propId)) {
328                    throw new IllegalArgumentException("proposition id must not be blank");
329                }
330                            this.id = propId;
331                    }
332    
333            /**
334             * Sets the value of the description on this builder to the given value.
335             *
336             * @param description the description value to set
337             */
338                    public void setDescription(String description) {
339                            this.description = description;
340                    }
341    
342            /**
343             * Sets the value of the typeId on this builder to the given value.
344             *
345             * @param typeId the typeId value to set
346             */
347            public void setTypeId(String typeId) {
348                this.typeId = typeId;
349            }
350    
351            /**
352             * Sets the value of the ruleId on this builder to the given value.
353             *
354             * @param ruleId the ruleId value to set
355             */
356            public void setRuleId(String ruleId) {
357                this.ruleId = ruleId;
358            }
359    
360            /**
361             * Sets the value of the rule on this builder to the given value.
362             *
363             * @param rule the rule value to set
364             */
365            public void setRule(RuleDefinition.Builder rule) {
366                if (rule != null && !StringUtils.isBlank(rule.getId())) {
367                    setRuleId(rule.getId());
368                }
369                this.rule = rule;
370            }
371    
372            /**
373             * Sets the value of the propTypeCode on this builder to the given value.
374             *
375             * @param propTypeCode the propTypeCode value to set
376             * @throws IllegalArgumentException if the propTypeCode is null, blank or invalid
377             */
378                    public void setPropositionTypeCode(String propTypeCode) {
379                            if (StringUtils.isBlank(propTypeCode)) {
380                    throw new IllegalArgumentException("proposition type code is blank");
381                            }
382                            if (!PropositionType.VALID_TYPE_CODES.contains(propTypeCode)) {
383                    throw new IllegalArgumentException("invalid proposition type code");
384                            }
385                            this.propositionTypeCode = propTypeCode;
386                    }
387    
388            /**
389             * Sets the value of the parameters on this builder to the given value.
390             *
391             * @param parameters the parameters value to set
392             */
393                    public void setParameters(List<PropositionParameter.Builder> parameters) {
394                            // compound propositions have empty parameter lists
395                            // Simple propositions must have a non-empty parameter list
396                            if (parameters == null || parameters.isEmpty()){
397                                    this.parameters = Collections.unmodifiableList(new ArrayList<PropositionParameter.Builder>());
398                            } else {
399                                this.parameters = Collections.unmodifiableList(parameters);
400                            }
401                    }
402    
403            /**
404             * Sets the value of the opCode on this builder to the given value.
405             *
406             * @param opCode the opCode value to set
407             * @throws IllegalArgumentException if the opCode invalid
408             */
409                    public void setCompoundOpCode(String opCode){
410                            if (StringUtils.isBlank(opCode)){ return; }
411                            if (!LogicalOperator.OP_CODES.contains(opCode)){
412                                    throw new IllegalArgumentException("invalid opCode value");
413                            }
414                            this.compoundOpCode = opCode;
415                    }
416                    
417            /**
418             * Sets the value of the compound sequence no on this builder to the
419             * given value.
420             *
421             * @param seqNo the sequence number for this compound prop
422             * @throws IllegalArgumentException if the seqNo invalid
423             */
424            public void setCompoundSequenceNumber(Integer seqNo) {
425                this.compoundSequenceNumber = seqNo;
426            }
427    
428            /**
429             * Sets the value of the components on this builder to the given value.
430             *
431             * @param components the components value to set
432             */
433                    public void setCompoundComponents(List<PropositionDefinition.Builder> components){
434                            if (components == null || components.isEmpty()){
435                                    this.compoundComponents = new ArrayList<PropositionDefinition.Builder>();
436                                    return;
437                            }
438                            this.compoundComponents = new ArrayList<PropositionDefinition.Builder>(components);
439                    }
440    
441            /**
442             * Sets the value of the versionNumber on this builder to the given value.
443             *
444             * @param versionNumber the versionNumber value to set
445             */
446            public void setVersionNumber(Long versionNumber){
447                this.versionNumber = versionNumber;
448            }
449    
450                    @Override
451                    public String getId() {
452                            return id;
453                    }
454    
455                    @Override
456                    public String getDescription() {
457                            return description;
458                    }
459    
460                    @Override
461                    public String getRuleId() {
462                        return ruleId;
463                    }
464    
465                    @Override
466                    public String getTypeId() {
467                            return typeId;
468                    }
469    
470                    @Override
471                    public String getPropositionTypeCode() {
472                            return propositionTypeCode;
473                    }
474    
475                    @Override
476                    public List<PropositionParameter.Builder> getParameters() {
477                            return parameters;
478                    }
479    
480                    @Override
481                    public String getCompoundOpCode() {
482                            return compoundOpCode;
483                    }
484    
485                    @Override
486                    public Integer getCompoundSequenceNumber() {
487                            return compoundSequenceNumber;
488                    }
489    
490                    @Override
491                    public List<PropositionDefinition.Builder> getCompoundComponents() {
492                            return compoundComponents;
493                    }
494    
495            @Override
496            public Long getVersionNumber() {
497                return versionNumber;
498            }
499    
500                    /**
501                     * Builds an instance of a Proposition based on the current state of the builder.
502                     *
503                     * @return the fully-constructed Proposition
504                     */
505            @Override
506            public PropositionDefinition build() {
507                return new PropositionDefinition(this);
508            }
509    
510        }
511    
512            /**
513             * Defines some internal constants used on this class.
514             */
515            static class Constants {
516                    final static String ROOT_ELEMENT_NAME = "proposition";
517                    final static String TYPE_NAME = "PropositionType";
518            }
519    
520            /**
521             * A private class which exposes constants which define the XML element names to use
522             * when this object is marshalled to XML.
523             */
524            public static class Elements {
525                    final static String ID = "id";
526                    final static String DESC = "description";
527            final static String RULE_ID = "ruleId";
528                    final static String TYPE_ID = "typeId";
529                    final static String PROP_TYPE_CODE = "propositionTypeCode";
530                    final static String PARAMETER = "parameter";
531                    final static String PARAMETERS = "parameters";
532                    final static String CMPND_OP_CODE = "compoundOpCode";
533                    final static String CMPND_SEQ_NO = "compoundSequenceNumber";
534                    final static String CMPND_COMPONENTS = "compoundComponents";
535                    final static String CMPND_COMPONENT = "proposition";
536            }
537    
538        public static class Cache {
539            public static final String NAME = KrmsConstants.Namespaces.KRMS_NAMESPACE_2_0 + "/" + PropositionDefinition.Constants.TYPE_NAME;
540        }
541            
542    }