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    
032    import org.apache.commons.lang.StringUtils;
033    import org.kuali.rice.core.api.CoreConstants;
034    import org.kuali.rice.core.api.mo.AbstractDataTransferObject;
035    import org.kuali.rice.core.api.mo.ModelBuilder;
036    import org.kuali.rice.krms.api.KrmsConstants;
037    import org.kuali.rice.krms.api.repository.LogicalOperator;
038    import 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    })
074    public 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    }