001/** 002 * Copyright 2005-2014 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.impl.repository; 017 018import org.apache.commons.lang.StringUtils; 019import org.kuali.rice.core.api.mo.common.Versioned; 020import org.kuali.rice.krad.data.DataObjectService; 021import org.kuali.rice.krad.data.jpa.PortableSequenceGenerator; 022import org.kuali.rice.krad.service.KRADServiceLocator; 023import org.kuali.rice.krms.api.repository.LogicalOperator; 024import org.kuali.rice.krms.api.repository.proposition.PropositionDefinition; 025import org.kuali.rice.krms.api.repository.proposition.PropositionDefinitionContract; 026import org.kuali.rice.krms.api.repository.proposition.PropositionParameter; 027import org.kuali.rice.krms.api.repository.proposition.PropositionParameterType; 028import org.kuali.rice.krms.api.repository.proposition.PropositionType; 029import org.kuali.rice.krms.impl.ui.CustomOperatorUiTranslator; 030import org.kuali.rice.krms.impl.ui.TermParameter; 031import org.kuali.rice.krms.impl.util.KrmsImplConstants; 032import org.kuali.rice.krms.impl.util.KrmsServiceLocatorInternal; 033 034import javax.persistence.CascadeType; 035import javax.persistence.Column; 036import javax.persistence.Entity; 037import javax.persistence.GeneratedValue; 038import javax.persistence.Id; 039import javax.persistence.JoinColumn; 040import javax.persistence.JoinTable; 041import javax.persistence.ManyToMany; 042import javax.persistence.OneToMany; 043import javax.persistence.OrderBy; 044import javax.persistence.Table; 045import javax.persistence.Transient; 046import javax.persistence.Version; 047import java.io.IOException; 048import java.io.ObjectOutputStream; 049import java.io.Serializable; 050import java.util.ArrayList; 051import java.util.Arrays; 052import java.util.HashMap; 053import java.util.List; 054import java.util.Map; 055import java.util.UUID; 056 057@Entity 058@Table(name = "KRMS_PROP_T") 059public class PropositionBo implements PropositionDefinitionContract, Versioned, Serializable { 060 061 private static final long serialVersionUID = 1l; 062 063 private static final String PROP_SEQ_NAME = "KRMS_PROP_S"; 064 static final RepositoryBoIncrementer propositionIdIncrementer = new RepositoryBoIncrementer(PROP_SEQ_NAME); 065 static final RepositoryBoIncrementer propositionParameterIdIncrementer = new RepositoryBoIncrementer("KRMS_PROP_PARM_S"); 066 067 @PortableSequenceGenerator(name = PROP_SEQ_NAME) 068 @GeneratedValue(generator = PROP_SEQ_NAME) 069 @Id 070 @Column(name = "PROP_ID") 071 private String id; 072 073 @Column(name = "DESC_TXT") 074 private String description; 075 076 @Column(name = "RULE_ID") 077 private String ruleId; 078 079 @Column(name = "TYP_ID") 080 private String typeId; 081 082 @Column(name = "DSCRM_TYP_CD") 083 private String propositionTypeCode; 084 085 @OneToMany(orphanRemoval = true, cascade = CascadeType.ALL, mappedBy = "proposition") 086 @OrderBy("sequenceNumber") 087 private List<PropositionParameterBo> parameters = new ArrayList<PropositionParameterBo>(); 088 089 @Column(name = "CMPND_OP_CD") 090 private String compoundOpCode; 091 092 @Column(name = "CMPND_SEQ_NO") 093 private Integer compoundSequenceNumber; 094 095 @Column(name = "VER_NBR") 096 @Version 097 private Long versionNumber; 098 099 @ManyToMany(targetEntity = PropositionBo.class, cascade = { CascadeType.REFRESH, CascadeType.MERGE, CascadeType.REMOVE, CascadeType.PERSIST }) 100 @JoinTable(name = "KRMS_CMPND_PROP_PROPS_T", joinColumns = { @JoinColumn(name = "CMPND_PROP_ID", referencedColumnName = "PROP_ID") }, inverseJoinColumns = { @JoinColumn(name = "PROP_ID", referencedColumnName = "PROP_ID") }) 101 @OrderBy("compoundSequenceNumber") 102 private List<PropositionBo> compoundComponents; 103 104 @Transient 105 private String parameterDisplayString; 106 107 @Transient 108 private boolean editMode = false; 109 110 @Transient 111 private String categoryId; 112 113 @Transient 114 private String termSpecId; 115 116 @Transient 117 private boolean showCustomValue; 118 119 @Transient 120 private String termParameter; 121 122 @Transient 123 private List<TermParameter> termParameterList = new ArrayList<TermParameter>(); 124 125 @Transient 126 private String newTermDescription = "new term " + UUID.randomUUID().toString(); 127 128 @Transient 129 private Map<String, String> termParameters = new HashMap<String, String>(); 130 131 private void setupParameterDisplayString() { 132 if (PropositionType.SIMPLE.getCode().equalsIgnoreCase(getPropositionTypeCode())) { 133 // Simple Propositions should have 3 parameters ordered in reverse polish notation. 134 // TODO: enhance to get term names for term type parameters. 135 List<PropositionParameterBo> parameters = getParameters(); 136 137 if (parameters != null && parameters.size() == 3) { 138 StringBuilder sb = new StringBuilder(); 139 String valueDisplay = getParamValue(parameters.get(1)); 140 sb.append(getParamValue(parameters.get(0))).append(" ").append(getParamValue(parameters.get(2))); 141 142 if (valueDisplay != null) { 143 // !=null and =null operators values will be null and should not be displayed 144 sb.append(" ").append(valueDisplay); 145 } 146 147 setParameterDisplayString(sb.toString()); 148 } else { 149 // should not happen 150 } 151 } 152 } 153 154 /** 155 * returns the string summary value for the given proposition parameter. 156 * 157 * @param param the proposition parameter to get the summary value for 158 * @return the summary value 159 */ 160 private String getParamValue(PropositionParameterBo param) { 161 CustomOperatorUiTranslator customOperatorUiTranslator = 162 KrmsServiceLocatorInternal.getCustomOperatorUiTranslator(); 163 164 if (PropositionParameterType.TERM.getCode().equalsIgnoreCase(param.getParameterType())) { 165 String termName = ""; 166 String termId = param.getValue(); 167 168 if (termId != null && termId.length() > 0) { 169 if (termId.startsWith(KrmsImplConstants.PARAMETERIZED_TERM_PREFIX)) { 170 if (!StringUtils.isBlank(newTermDescription)) { 171 termName = newTermDescription; 172 } else { 173 TermSpecificationBo termSpec = getDataObjectService().find(TermSpecificationBo.class, 174 termId.substring(1 + termId.indexOf(":"))); 175 termName = termSpec.getName() + "(...)"; 176 } 177 } else { 178 TermBo term = getDataObjectService().find(TermBo.class, termId); 179 termName = term.getSpecification().getName(); 180 } 181 } 182 183 return termName; 184 185 } else if (PropositionParameterType.FUNCTION.getCode().equalsIgnoreCase(param.getParameterType()) || 186 PropositionParameterType.OPERATOR.getCode().equalsIgnoreCase(param.getParameterType())) { 187 if (customOperatorUiTranslator.isCustomOperatorFormValue(param.getValue())) { 188 String functionName = customOperatorUiTranslator.getCustomOperatorName(param.getValue()); 189 if (!StringUtils.isEmpty(functionName)) { 190 return functionName; 191 } 192 } 193 } 194 195 return param.getValue(); 196 } 197 198 /** 199 * @return the parameterDisplayString 200 */ 201 public String getParameterDisplayString() { 202 setupParameterDisplayString(); 203 204 return this.parameterDisplayString; 205 } 206 207 /** 208 * @param parameterDisplayString the parameterDisplayString to set 209 */ 210 public void setParameterDisplayString(String parameterDisplayString) { 211 this.parameterDisplayString = parameterDisplayString; 212 } 213 214 public boolean getEditMode() { 215 return this.editMode; 216 } 217 218 public void setEditMode(boolean editMode) { 219 this.editMode = editMode; 220 } 221 222 public String getCategoryId() { 223 return this.categoryId; 224 } 225 226 public void setCategoryId(String categoryId) { 227 this.categoryId = categoryId; 228 } 229 230 /** 231 * set the typeId. If the parameter is blank, then this PropositionBo's 232 * typeId will be set to null 233 * 234 * @param typeId 235 */ 236 public void setTypeId(String typeId) { 237 if (StringUtils.isBlank(typeId)) { 238 this.typeId = null; 239 } else { 240 this.typeId = typeId; 241 } 242 } 243 244 public Long getVersionNumber() { 245 return versionNumber; 246 } 247 248 public void setVersionNumber(Long versionNumber) { 249 this.versionNumber = versionNumber; 250 } 251 252 public Map<String, String> getTermParameters() { 253 return termParameters; 254 } 255 256 public void setTermParameters(Map<String, String> termParameters) { 257 this.termParameters = termParameters; 258 } 259 260 public DataObjectService getDataObjectService() { 261 return KRADServiceLocator.getDataObjectService(); 262 } 263 264 /** 265 * Converts a mutable bo to it's immutable counterpart 266 * 267 * @param bo the mutable business object 268 * @return the immutable object 269 */ 270 public static PropositionDefinition to(PropositionBo bo) { 271 if (bo == null) { 272 return null; 273 } 274 275 return PropositionDefinition.Builder.create(bo).build(); 276 } 277 278 /** 279 * Converts a immutable object to it's mutable bo counterpart 280 * 281 * @param im immutable object 282 * @return the mutable bo 283 */ 284 public static PropositionBo from(PropositionDefinition im) { 285 if (im == null) { 286 return null; 287 } 288 289 PropositionBo bo = new PropositionBo(); 290 bo.id = im.getId(); 291 bo.description = im.getDescription(); 292 293 // we don't set rule here, it is set in RuleBo.from 294 295 setRuleIdRecursive(im.getRuleId(), bo); 296 297 bo.typeId = im.getTypeId(); 298 bo.propositionTypeCode = im.getPropositionTypeCode(); 299 bo.parameters = new ArrayList<PropositionParameterBo>(); 300 301 for (PropositionParameter parm : im.getParameters()) { 302 PropositionParameterBo parmBo = PropositionParameterBo.from(parm); 303 bo.parameters.add(parmBo); 304 parmBo.setProposition(bo); 305 } 306 307 bo.compoundOpCode = im.getCompoundOpCode(); 308 bo.compoundSequenceNumber = im.getCompoundSequenceNumber(); 309 bo.compoundComponents = new ArrayList<PropositionBo>(); 310 311 if (im.getCompoundComponents() != null) for (PropositionDefinition prop : im.getCompoundComponents()) { 312 bo.compoundComponents.add(PropositionBo.from(prop)); 313 } 314 315 bo.setVersionNumber(im.getVersionNumber()); 316 317 return bo; 318 } 319 320 private static void setRuleIdRecursive(String ruleId, PropositionBo prop) { 321 prop.ruleId = ruleId; 322 323 if (prop.compoundComponents != null) { 324 for (PropositionBo child : prop.compoundComponents) { 325 if (child != null) { 326 setRuleIdRecursive(ruleId, child); 327 } 328 } 329 } 330 } 331 332 /** 333 * This method creates a partially populated Simple PropositionBo with 334 * three parameters: a term type paramter (value not assigned) 335 * a operation parameter 336 * a constant parameter (value set to empty string) 337 * The returned PropositionBo has an generatedId. The type code and ruleId properties are assigned the 338 * same value as the sibling param passed in. 339 * Each PropositionParameter has the id generated, and type, sequenceNumber, 340 * propId default values set. The value is set to "". 341 * 342 * @param sibling - 343 * @param pType 344 * @return a PropositionBo partially populated. 345 */ 346 public static PropositionBo createSimplePropositionBoStub(PropositionBo sibling, String pType) { 347 // create a simple proposition Bo 348 PropositionBo prop = null; 349 if (PropositionType.SIMPLE.getCode().equalsIgnoreCase(pType)) { 350 prop = new PropositionBo(); 351 prop.setId(propositionIdIncrementer.getNewId()); 352 prop.setPropositionTypeCode(pType); 353 prop.setEditMode(true); 354 355 if (sibling != null) { 356 prop.setRuleId(sibling.getRuleId()); 357 } 358 359 // create blank proposition parameters 360 PropositionParameterBo pTerm = new PropositionParameterBo(); 361 pTerm.setId(propositionParameterIdIncrementer.getNewId()); 362 pTerm.setParameterType("T"); 363 pTerm.setProposition(prop); 364 pTerm.setSequenceNumber(new Integer("0")); 365 pTerm.setVersionNumber(new Long(1)); 366 pTerm.setValue(""); 367 368 // create blank proposition parameters 369 PropositionParameterBo pOp = new PropositionParameterBo(); 370 pOp.setId(propositionParameterIdIncrementer.getNewId()); 371 pOp.setParameterType("O"); 372 pOp.setProposition(prop); 373 pOp.setSequenceNumber(new Integer("2")); 374 pOp.setVersionNumber(new Long(1)); 375 376 // create blank proposition parameters 377 PropositionParameterBo pConst = new PropositionParameterBo(); 378 pConst.setId(propositionParameterIdIncrementer.getNewId()); 379 pConst.setParameterType("C"); 380 pConst.setProposition(prop); 381 pConst.setSequenceNumber(new Integer("1")); 382 pConst.setVersionNumber(new Long(1)); 383 pConst.setValue(""); 384 List<PropositionParameterBo> paramList = Arrays.asList(pTerm, pConst, pOp); 385 386 prop.setParameters(paramList); 387 } 388 389 return prop; 390 } 391 392 public static PropositionBo createCompoundPropositionBoStub(PropositionBo existing, boolean addNewChild) { 393 // create a simple proposition Bo 394 PropositionBo prop = new PropositionBo(); 395 prop.setId(propositionIdIncrementer.getNewId()); 396 prop.setPropositionTypeCode(PropositionType.COMPOUND.getCode()); 397 prop.setCompoundOpCode(LogicalOperator.AND.getCode()); 398 399 // default to and 400 prop.setDescription(""); 401 prop.setEditMode(true); 402 403 if (existing != null) { 404 prop.setRuleId(existing.getRuleId()); 405 } 406 407 List<PropositionBo> components = new ArrayList<PropositionBo>(2); 408 components.add(existing); 409 410 if (addNewChild) { 411 PropositionBo newProp = createSimplePropositionBoStub(existing, PropositionType.SIMPLE.getCode()); 412 components.add(newProp); 413 prop.setEditMode(false); 414 } 415 416 prop.setCompoundComponents(components); 417 418 return prop; 419 } 420 421 public static PropositionBo createCompoundPropositionBoStub2(PropositionBo existing) { 422 // create a simple proposition Bo 423 PropositionBo prop = new PropositionBo(); 424 prop.setId(propositionIdIncrementer.getNewId()); 425 prop.setPropositionTypeCode(PropositionType.COMPOUND.getCode()); 426 prop.setRuleId(existing.getRuleId()); 427 prop.setCompoundOpCode(LogicalOperator.AND.getCode()); 428 // default to and 429 prop.setDescription(""); 430 prop.setEditMode(true); 431 List<PropositionBo> components = new ArrayList<PropositionBo>(); 432 ((ArrayList<PropositionBo>) components).add(existing); 433 prop.setCompoundComponents(components); 434 435 return prop; 436 } 437 438 public static PropositionBo copyProposition(PropositionBo existing) { 439 // Note: RuleId is not set 440 PropositionBo newProp = new PropositionBo(); 441 newProp.setId(propositionIdIncrementer.getNewId()); 442 newProp.setDescription(existing.getDescription()); 443 newProp.setPropositionTypeCode(existing.getPropositionTypeCode()); 444 newProp.setTypeId(existing.getTypeId()); 445 newProp.setCompoundOpCode(existing.getCompoundOpCode()); 446 newProp.setCompoundSequenceNumber(existing.getCompoundSequenceNumber()); 447 448 // parameters 449 List<PropositionParameterBo> newParms = new ArrayList<PropositionParameterBo>(); 450 451 for (PropositionParameterBo parm : existing.getParameters()) { 452 PropositionParameterBo p = new PropositionParameterBo(); 453 p.setId(propositionParameterIdIncrementer.getNewId()); 454 p.setParameterType(parm.getParameterType()); 455 p.setProposition(newProp); 456 p.setSequenceNumber(parm.getSequenceNumber()); 457 p.setValue(parm.getValue()); 458 ((ArrayList<PropositionParameterBo>) newParms).add(p); 459 } 460 461 newProp.setParameters(newParms); 462 463 // compoundComponents 464 List<PropositionBo> newCompoundComponents = new ArrayList<PropositionBo>(); 465 for (PropositionBo component : existing.getCompoundComponents()) { 466 PropositionBo newComponent = copyProposition(component); 467 ((ArrayList<PropositionBo>) newCompoundComponents).add(component); 468 } 469 470 newProp.setCompoundComponents(newCompoundComponents); 471 472 return newProp; 473 } 474 475 /* 476 * This is being done because there is a major issue with lazy relationships, in ensuring that the relationship is 477 * still available after the object has been detached, or serialized. For most JPA providers, after serialization 478 * any lazy relationship that was not instantiated will be broken, and either throw an error when accessed, 479 * or return null. 480 */ 481 private void writeObject(ObjectOutputStream stream) throws IOException, ClassNotFoundException { 482 parameters.size(); 483 stream.defaultWriteObject(); 484 } 485 486 public String getTermSpecId() { 487 return termSpecId; 488 } 489 490 public void setTermSpecId(String componentId) { 491 this.termSpecId = componentId; 492 } 493 494 public boolean isShowCustomValue() { 495 return showCustomValue; 496 } 497 498 public void setShowCustomValue(boolean showCustomValue) { 499 this.showCustomValue = showCustomValue; 500 } 501 502 public String getTermParameter() { 503 return termParameter; 504 } 505 506 public void setTermParameter(String termParameter) { 507 this.termParameter = termParameter; 508 } 509 510 public List<TermParameter> getTermParameterList() { 511 return termParameterList; 512 } 513 514 public void setTermParameterList(List<TermParameter> termParameterList) { 515 this.termParameterList = termParameterList; 516 } 517 518 public String getId() { 519 return id; 520 } 521 522 public void setId(String id) { 523 this.id = id; 524 } 525 526 public String getDescription() { 527 return description; 528 } 529 530 public void setDescription(String description) { 531 this.description = description; 532 } 533 534 public String getRuleId() { 535 return ruleId; 536 } 537 538 public void setRuleId(String ruleId) { 539 this.ruleId = ruleId; 540 } 541 542 public String getTypeId() { 543 return typeId; 544 } 545 546 public String getPropositionTypeCode() { 547 return propositionTypeCode; 548 } 549 550 public void setPropositionTypeCode(String propositionTypeCode) { 551 this.propositionTypeCode = propositionTypeCode; 552 } 553 554 public List<PropositionParameterBo> getParameters() { 555 return parameters; 556 } 557 558 public void setParameters(List<PropositionParameterBo> parameters) { 559 this.parameters = parameters; 560 } 561 562 public String getCompoundOpCode() { 563 return compoundOpCode; 564 } 565 566 public void setCompoundOpCode(String compoundOpCode) { 567 this.compoundOpCode = compoundOpCode; 568 } 569 570 public Integer getCompoundSequenceNumber() { 571 return compoundSequenceNumber; 572 } 573 574 public void setCompoundSequenceNumber(Integer compoundSequenceNumber) { 575 this.compoundSequenceNumber = compoundSequenceNumber; 576 } 577 578 public List<PropositionBo> getCompoundComponents() { 579 return compoundComponents; 580 } 581 582 public void setCompoundComponents(List<PropositionBo> compoundComponents) { 583 this.compoundComponents = compoundComponents; 584 } 585 586 public boolean getShowCustomValue() { 587 return showCustomValue; 588 } 589 590 public String getNewTermDescription() { 591 return newTermDescription; 592 } 593 594 public void setNewTermDescription(String newTermDescription) { 595 this.newTermDescription = newTermDescription; 596 } 597}