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