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 }