001/**
002 * Copyright 2005-2015 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.api.repository.proposition;
017
018import java.io.Serializable;
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.Collections;
022import java.util.List;
023
024import javax.xml.bind.annotation.XmlAccessType;
025import javax.xml.bind.annotation.XmlAccessorType;
026import javax.xml.bind.annotation.XmlAnyElement;
027import javax.xml.bind.annotation.XmlElement;
028import javax.xml.bind.annotation.XmlElementWrapper;
029import javax.xml.bind.annotation.XmlRootElement;
030import javax.xml.bind.annotation.XmlType;
031import org.kuali.rice.krms.api.repository.term.TermParameterDefinition;
032
033import org.apache.commons.lang.StringUtils;
034import org.kuali.rice.core.api.CoreConstants;
035import org.kuali.rice.core.api.mo.AbstractDataTransferObject;
036import org.kuali.rice.core.api.mo.ModelBuilder;
037import org.kuali.rice.krms.api.KrmsConstants;
038import org.kuali.rice.krms.api.repository.LogicalOperator;
039import 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})
076public 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}