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 }