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