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 */ 016package org.kuali.rice.krms.service.impl; 017 018import org.apache.commons.lang.StringUtils; 019import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader; 020import org.kuali.rice.core.api.util.tree.Tree; 021import org.kuali.rice.krad.uif.UifConstants; 022import org.kuali.rice.krad.uif.component.Component; 023import org.kuali.rice.krad.uif.container.Container; 024import org.kuali.rice.krad.uif.util.ComponentFactory; 025import org.kuali.rice.krad.uif.util.ComponentUtils; 026import org.kuali.rice.krad.uif.view.View; 027import org.kuali.rice.krad.util.BeanPropertyComparator; 028import org.kuali.rice.krad.util.ObjectUtils; 029import org.kuali.rice.krad.web.form.MaintenanceDocumentForm; 030import org.kuali.rice.krms.api.KrmsConstants; 031import org.kuali.rice.krms.api.repository.LogicalOperator; 032import org.kuali.rice.krms.api.repository.RuleManagementService; 033import org.kuali.rice.krms.api.repository.proposition.PropositionType; 034import org.kuali.rice.krms.api.repository.term.TermRepositoryService; 035import org.kuali.rice.krms.api.repository.type.KrmsTypeDefinition; 036import org.kuali.rice.krms.api.repository.type.KrmsTypeRepositoryService; 037import org.kuali.rice.krms.builder.ComponentBuilder; 038import org.kuali.rice.krms.dto.PropositionEditor; 039import org.kuali.rice.krms.dto.PropositionParameterEditor; 040import org.kuali.rice.krms.dto.RuleEditor; 041import org.kuali.rice.krms.dto.RuleManagementWrapper; 042import org.kuali.rice.krms.dto.TermEditor; 043import org.kuali.rice.krms.dto.TermParameterEditor; 044import org.kuali.rice.krms.impl.repository.KrmsRepositoryServiceLocator; 045import org.kuali.rice.krms.service.TemplateRegistry; 046import org.kuali.rice.krms.tree.RuleViewTreeBuilder; 047import org.kuali.rice.krms.tree.node.CompareTreeNode; 048import org.kuali.rice.krms.tree.RuleCompareTreeBuilder; 049import org.kuali.rice.krms.tree.RuleEditTreeBuilder; 050import org.kuali.rice.krms.tree.RulePreviewTreeBuilder; 051import org.kuali.rice.krms.util.KRMSConstants; 052import org.kuali.rice.krms.util.NaturalLanguageHelper; 053import org.kuali.rice.krms.util.PropositionTreeUtil; 054import org.kuali.rice.krms.dto.TemplateInfo; 055import org.kuali.rice.krms.service.RuleViewHelperService; 056import org.kuali.student.common.uif.service.impl.KSViewHelperServiceImpl; 057import org.kuali.student.r1.common.rice.StudentIdentityConstants; 058import org.kuali.student.r2.core.constants.KSKRMSServiceConstants; 059import org.springframework.beans.BeanUtils; 060 061import javax.xml.namespace.QName; 062import java.util.ArrayList; 063import java.util.Arrays; 064import java.util.List; 065import java.util.Map; 066 067/** 068 * Helpers Service for the Rule Pages. 069 * 070 * @author Kuali Student Team 071 */ 072public class RuleViewHelperServiceImpl extends KSViewHelperServiceImpl implements RuleViewHelperService { 073 074 private transient RuleManagementService ruleManagementService; 075 private transient KrmsTypeRepositoryService krmsTypeRepositoryService; 076 private transient TermRepositoryService termRepositoryService; 077 078 private RuleCompareTreeBuilder compareTreeBuilder; 079 private RuleEditTreeBuilder editTreeBuilder; 080 private RulePreviewTreeBuilder previewTreeBuilder; 081 private RuleViewTreeBuilder viewTreeBuilder; 082 083 private NaturalLanguageHelper naturalLanguageHelper; 084 085 private static TemplateRegistry templateRegistry; 086 087 protected RuleEditor getRuleEditor(Object model) { 088 if (model instanceof MaintenanceDocumentForm) { 089 MaintenanceDocumentForm maintenanceDocumentForm = (MaintenanceDocumentForm) model; 090 Object dataObject = maintenanceDocumentForm.getDocument().getNewMaintainableObject().getDataObject(); 091 092 if (dataObject instanceof RuleEditor) { 093 return (RuleEditor) dataObject; 094 } else if (dataObject instanceof RuleManagementWrapper) { 095 RuleManagementWrapper wrapper = (RuleManagementWrapper) dataObject; 096 return wrapper.getRuleEditor(); 097 } 098 } 099 return null; 100 } 101 102 @Override 103 public TemplateInfo getTemplateForType(String type) { 104 return this.getTemplateRegistry().getTemplateForType(type); 105 } 106 107 @Override 108 protected void addCustomContainerComponents(View view, Object model, Container container) { 109 if (KRMSConstants.KRMS_PROPOSITION_DETAILSECTION_ID.equals(container.getId())) { 110 customizePropositionEditSection(view, model, container); 111 } 112 } 113 114 private void customizePropositionEditSection(View view, Object model, Container container) { 115 //Retrieve the current editing proposition if exists. 116 MaintenanceDocumentForm maintenanceDocumentForm = (MaintenanceDocumentForm) model; 117 Object dataObject = maintenanceDocumentForm.getDocument().getNewMaintainableObject().getDataObject(); 118 119 RuleEditor ruleEditor = ((RuleManagementWrapper) dataObject).getRuleEditor(); 120 PropositionEditor propEditor = PropositionTreeUtil.getProposition(ruleEditor); 121 122 List<Component> components = new ArrayList<Component>(); 123 if (propEditor != null) { 124 //Retrieve the name of the xml component to display for the proposition type. 125 TemplateInfo template = this.getTemplateForType(propEditor.getType()); 126 127 if (template != null && template.getComponentId() != null) { 128 Component component = ComponentFactory.getNewComponentInstance(template.getComponentId()); 129 view.assignComponentIds(component); 130 if(container.getId().equals(maintenanceDocumentForm.getUpdateComponentId())){ 131 String nodePath = view.getDefaultBindingObjectPath() + "." + propEditor.getBindingPath(); 132 ComponentUtils.pushObjectToContext(component, UifConstants.ContextVariableNames.NODE_PATH, nodePath); 133 ComponentUtils.prefixBindingPathNested(component, propEditor.getBindingPath()); 134 } 135 136 //Add Proposition Type FieldGroup to Tree Node 137 components.add(component); 138 } 139 140 if (template != null && template.getConstantComponentId() != null) { 141 Component component = ComponentFactory.getNewComponentInstance(template.getConstantComponentId()); 142 view.assignComponentIds(component); 143 144 //Add Proposition Type FieldGroup to Tree Node 145 components.add(component); 146 } 147 } 148 149 //Do not display if there are no components. 150 if (components.size() == 0) { 151 container.getHeader().setRender(false); 152 } 153 154 container.setItems(components); 155 } 156 157 /** 158 * Validate the proposition. 159 * 160 * @param proposition 161 */ 162 @Override 163 public void validateProposition(PropositionEditor proposition) { 164 165 // Retrieve the builder for the current proposition type. 166 ComponentBuilder builder = this.getTemplateRegistry().getComponentBuilderForType(proposition.getType()); 167 if (builder != null) { 168 // Execute validation 169 builder.validate(proposition); 170 } 171 } 172 173 /** 174 * Clear the description and natural language on proposition editors. 175 * 176 * @param prop 177 * @return 178 */ 179 @Override 180 public void resetDescription(PropositionEditor prop) { 181 182 //If proposition type is null, set description and term null 183 if (prop.getType() == null) { 184 prop.setDescription(StringUtils.EMPTY); 185 prop.setTerm(null); 186 prop.getNaturalLanguage().clear(); 187 return; 188 } 189 190 //Build the new termParamters with the matching component builder. 191 if (PropositionType.SIMPLE.getCode().equalsIgnoreCase(prop.getPropositionTypeCode())) { 192 Map<String, String> termParameters = null; 193 ComponentBuilder builder = this.getTemplateRegistry().getComponentBuilderForType(prop.getType()); 194 if (builder != null) { 195 termParameters = builder.buildTermParameters(prop); 196 } 197 198 List<TermParameterEditor> parameters = new ArrayList<TermParameterEditor>(); 199 if (termParameters != null) { 200 for (Map.Entry<String, String> entry : termParameters.entrySet()) { 201 202 TermParameterEditor parameterEditor = null; 203 if (prop.getTerm().getParameters() != null) { 204 for (TermParameterEditor parameter : prop.getTerm().getEditorParameters()) { 205 206 if (entry.getKey().equals(parameter.getName())) { 207 parameterEditor = parameter; 208 parameterEditor.setValue(entry.getValue()); 209 break; 210 } 211 } 212 } 213 214 //Create a new parameter if not exist. 215 if (parameterEditor == null) { 216 parameterEditor = new TermParameterEditor(); 217 parameterEditor.setName(entry.getKey()); 218 parameterEditor.setValue(entry.getValue()); 219 } 220 parameters.add(parameterEditor); 221 } 222 } 223 224 prop.getTerm().setParameters(parameters); 225 226 //Set the term specification if it doesn't exist. 227 if (prop.getTerm().getSpecification() == null) { 228 String termSpecName = this.getTemplateRegistry().getTermSpecNameForType(prop.getType()); 229 prop.getTerm().setSpecification(getTermRepositoryService().getTermSpecificationByNameAndNamespace(termSpecName, KSKRMSServiceConstants.NAMESPACE_CODE)); 230 } 231 232 } else { 233 prop.setTerm(null); 234 } 235 236 //Refresh the natural language. 237 prop.getNaturalLanguage().clear(); 238 } 239 240 public void configurePropositionForType(PropositionEditor proposition) { 241 242 if (proposition != null) { 243 244 if (PropositionType.COMPOUND.getCode().equalsIgnoreCase(proposition.getPropositionTypeCode())) { 245 return; 246 } 247 248 String propositionTypeId = proposition.getTypeId(); 249 if ((propositionTypeId == null) || (propositionTypeId.isEmpty())) { 250 proposition.setType(null); 251 return; 252 } 253 254 KrmsTypeDefinition type = KrmsRepositoryServiceLocator.getKrmsTypeRepositoryService().getTypeById(propositionTypeId); 255 if (type != null) { 256 proposition.setType(type.getName()); 257 } 258 259 if (proposition.getTerm() == null) { 260 proposition.setTerm(new TermEditor()); 261 } 262 263 String termSpecName = this.getTemplateRegistry().getTermSpecNameForType(proposition.getType()); 264 proposition.getTerm().setSpecification(getTermRepositoryService().getTermSpecificationByNameAndNamespace(termSpecName, KSKRMSServiceConstants.NAMESPACE_CODE)); 265 266 } 267 } 268 269 @Override 270 public void refreshInitTrees(RuleEditor rule) { 271 272 if (rule == null) { 273 return; 274 } 275 276 //Rebuild the trees 277 rule.setEditTree(this.getEditTreeBuilder().buildTree(rule)); 278 rule.setPreviewTree(this.getPreviewTreeBuilder().buildTree(rule)); 279 280 //Also reset the logic expression. Should only be done after editTree is already built. 281 if (rule.getProposition() != null) { 282 rule.setLogicArea(PropositionTreeUtil.configureLogicExpression(rule.getPropositionEditor())); 283 } else { 284 rule.setLogicArea(StringUtils.EMPTY); 285 } 286 287 } 288 289 /** 290 * Rebuild the tree used for the view only trees. 291 * 292 * @param rule 293 */ 294 @Override 295 public void refreshViewTree(RuleEditor rule) { 296 297 if (rule == null) { 298 return; 299 } 300 301 //Rebuild the trees 302 rule.setViewTree(this.getViewTreeBuilder().buildTree(rule)); 303 304 } 305 306 @Override 307 public Tree<CompareTreeNode, String> buildCompareTree(RuleEditor original, RuleEditor compare) throws Exception { 308 309 //Build the Tree 310 return this.getCompareTreeBuilder().buildTree(original, compare); 311 312 } 313 314 @Override 315 public Tree<CompareTreeNode, String> buildMultiViewTree(RuleEditor coRuleEditor, RuleEditor cluRuleEditor) throws Exception { 316 317 //Build the Tree 318 return this.getCompareTreeBuilder().buildTree(coRuleEditor, cluRuleEditor); 319 320 } 321 322 /** 323 * Compare all the propositions in a rule tree with a parent rule tree. Returns false if any proposition's type 324 * or term parameters are not the same. 325 * <p/> 326 * Apart from the type and termparameters, all other detail is derived from the typeid and therefore not included in 327 * the comparison. * 328 * 329 * @param original 330 * @return boolean 331 * @throws Exception 332 */ 333 @Override 334 public Boolean compareRules(RuleEditor original) { 335 336 //Do null check on propositions. 337 RuleEditor compareEditor = original.getParent(); 338 if ((compareEditor == null) || (compareEditor.getProposition() == null)) { 339 if (original.getProposition() != null) { 340 return false; //if compare is null and original is not, they differ. 341 } else { 342 return true; //both of them are null. 343 } 344 } else if (original.getProposition() == null) { 345 return false; 346 } 347 348 //Compare Root Proposition Type and if the same test recursively 349 if (original.getProposition().getTypeId().equals(compareEditor.getProposition().getTypeId())) { 350 return compareProposition(original.getPropositionEditor(), compareEditor.getPropositionEditor()); 351 } else { 352 return false; 353 } 354 } 355 356 /** 357 * Method to handle the proposition comparison recursively. 358 * 359 * @param original 360 * @param compare 361 * @return true if proposition are the same. 362 */ 363 @Override 364 public Boolean compareProposition(PropositionEditor original, PropositionEditor compare) { 365 //Compare the proposition 366 BeanPropertyComparator propositionComparator = new BeanPropertyComparator(Arrays.asList("typeId")); 367 if (propositionComparator.compare(original, compare) != 0) { 368 return false; 369 } 370 371 //Compare the term values 372 if (PropositionType.SIMPLE.getCode().equalsIgnoreCase(original.getPropositionTypeCode())) { 373 TermEditor term = new TermEditor(PropositionTreeUtil.getTermParameter(compare.getParameters()).getTermValue()); 374 if (!compareTerm(original.getTerm().getEditorParameters(), term.getEditorParameters())) { 375 return false; 376 } 377 } 378 379 //Compare the compound propositions. 380 return compareCompoundProposition(original.getCompoundEditors(), compare.getCompoundEditors()); 381 } 382 383 /** 384 * Compare all the keys and values of the term parameter. Returns false if any of the keys (names) or 385 * values of the term paramters is not the same. 386 * 387 * @param original list of term parameters for current term 388 * @param compare list of term paramters to compare with. 389 * @return true if all names and values are the same. 390 */ 391 @Override 392 public Boolean compareTerm(List<TermParameterEditor> original, List<TermParameterEditor> compare) { 393 394 //If the sizes doesn't match, they are not same. 395 int originalSize = original == null ? 0 : original.size(); 396 if (originalSize != (compare == null ? 0 : compare.size())) { 397 return false; 398 } else if (originalSize > 0) { 399 400 //Compare the compound propositions. 401 BeanPropertyComparator termComparator = new BeanPropertyComparator(Arrays.asList("name")); 402 for (int index = 0; index < originalSize; index++) { 403 if (termComparator.compare(original.get(index), compare.get(index)) != 0) { 404 return false; 405 } 406 } 407 } 408 409 return true; 410 411 } 412 413 /** 414 * Recursively compare child propositions. 415 * 416 * @param original 417 * @param compare 418 * @return 419 */ 420 @Override 421 public Boolean compareCompoundProposition(List<PropositionEditor> original, List<PropositionEditor> compare) { 422 423 //If the sizes doesn't match, they are not same. 424 int originalSize = original == null ? 0 : original.size(); 425 if (originalSize != (compare == null ? 0 : compare.size())) { 426 return false; 427 } else if (originalSize > 0) { 428 429 //Compare the compound propositions. 430 for (int index = 0; index < originalSize; index++) { 431 if (!compareProposition(original.get(index), compare.get(index))) { 432 return false; 433 } 434 } 435 } 436 437 return true; 438 } 439 440 /** 441 * Make a new copy of the current proposition including the compounds. 442 * <p/> 443 * The deepcopy is done to make sure that create a full copy and does not only copy the references. 444 * 445 * @param oldProposition 446 * @return 447 */ 448 @Override 449 public PropositionEditor copyProposition(PropositionEditor oldProposition) { 450 try { 451 PropositionEditor newProposition = this.copyPropositionEditor(oldProposition); 452 453 //Use a deepcopy to create new references to inner objects such as string. 454 return (PropositionEditor) ObjectUtils.deepCopy(newProposition); 455 } catch (Exception e) { 456 return null; 457 } 458 } 459 460 /** 461 * Used when the user clicked the copy button. It creates a new copy of the proposition with all the related 462 * compound propositions. 463 * <p/> 464 * The compound propositions is handled recursively. 465 * 466 * @param oldProposition 467 * @return 468 */ 469 protected PropositionEditor copyPropositionEditor(PropositionEditor oldProposition) { 470 PropositionEditor newProposition; 471 try { 472 newProposition = this.getPropositionEditorClass().newInstance(); 473 } catch (Exception e) { 474 newProposition = new PropositionEditor(); 475 } 476 BeanUtils.copyProperties(oldProposition, newProposition, new String[]{"key", "id", "term", "parameters"}); 477 478 if (!oldProposition.getPropositionTypeCode().equals("C")) { 479 List<PropositionParameterEditor> propositionParameterEditors = new ArrayList<PropositionParameterEditor>(); 480 for (PropositionParameterEditor parm : oldProposition.getParameters()) { 481 PropositionParameterEditor newParm = new PropositionParameterEditor(); 482 BeanUtils.copyProperties(parm, newParm, new String[]{"termValue", "id", "versionNumber"}); 483 propositionParameterEditors.add(newParm); 484 } 485 486 newProposition.setParameters(propositionParameterEditors); 487 488 TermEditor termEditor = new TermEditor(); 489 List<TermParameterEditor> termParameterEditors = new ArrayList<TermParameterEditor>(); 490 if( oldProposition.getTerm() != null) { 491 BeanUtils.copyProperties(oldProposition.getTerm(), termEditor, new String[]{"id", "versionNumber", "parameters"}); 492 for (TermParameterEditor termParm : oldProposition.getTerm().getEditorParameters()) { 493 TermParameterEditor newTermParm = new TermParameterEditor(); 494 BeanUtils.copyProperties(termParm, newTermParm, new String[]{"id", "versionNumber"}); 495 termParameterEditors.add(newTermParm); 496 } 497 } 498 termEditor.setParameters(termParameterEditors); 499 500 newProposition.setTerm(termEditor); 501 this.resetDescription(newProposition); 502 } 503 504 if (newProposition.getCompoundEditors() != null) { 505 List<PropositionEditor> props = new ArrayList<PropositionEditor>(); 506 for (PropositionEditor prop : newProposition.getCompoundEditors()) { 507 props.add(this.copyPropositionEditor(prop)); 508 } 509 newProposition.setCompoundEditors(props); 510 } 511 512 513 return newProposition; 514 } 515 516 @Override 517 public PropositionEditor createCompoundPropositionBoStub(PropositionEditor existing, boolean addNewChild) { 518 try { 519 PropositionEditor compound = PropositionTreeUtil.createCompoundPropositionBoStub(existing, addNewChild, this.getPropositionEditorClass()); 520 this.setTypeForCompoundOpCode(compound, LogicalOperator.AND.getCode()); 521 return compound; 522 } catch (Exception e) { 523 return null; 524 } 525 } 526 527 @Override 528 public void setTypeForCompoundOpCode(PropositionEditor proposition, String compoundOpCode) { 529 //Return as quickly as possible for performance. 530 if (compoundOpCode.equals(proposition.getCompoundOpCode())) { 531 return; 532 } 533 534 //Clear the natural language so the the tree builder can rebuild it. 535 proposition.getNaturalLanguage().clear(); 536 proposition.setCompoundOpCode(compoundOpCode); 537 } 538 539 /** 540 * Creates a new instance of a simple proposition. 541 * 542 * @param sibling 543 * @return 544 */ 545 @Override 546 public PropositionEditor createSimplePropositionBoStub(PropositionEditor sibling) { 547 try { 548 return PropositionTreeUtil.createSimplePropositionBoStub(sibling, this.getPropositionEditorClass()); 549 } catch (Exception e) { 550 return null; 551 } 552 } 553 554 /** 555 * Override this method to return a different class type if you need to use a different propositoin editor class. 556 * 557 * @return 558 */ 559 public Class<? extends PropositionEditor> getPropositionEditorClass() { 560 return PropositionEditor.class; 561 } 562 563 protected RuleManagementService getRuleManagementService() { 564 if (ruleManagementService == null) { 565 ruleManagementService = (RuleManagementService) GlobalResourceLoader.getService(QName.valueOf("ruleManagementService")); 566 } 567 return ruleManagementService; 568 } 569 570 protected RuleCompareTreeBuilder getCompareTreeBuilder() { 571 if (compareTreeBuilder == null) { 572 compareTreeBuilder = new RuleCompareTreeBuilder(); 573 } 574 return compareTreeBuilder; 575 } 576 577 protected RuleEditTreeBuilder getEditTreeBuilder() { 578 if (editTreeBuilder == null) { 579 editTreeBuilder = new RuleEditTreeBuilder(); 580 } 581 return editTreeBuilder; 582 } 583 584 protected RulePreviewTreeBuilder getPreviewTreeBuilder() { 585 if (previewTreeBuilder == null) { 586 previewTreeBuilder = new RulePreviewTreeBuilder(); 587 } 588 return previewTreeBuilder; 589 } 590 591 protected RuleViewTreeBuilder getViewTreeBuilder() { 592 if (viewTreeBuilder == null) { 593 viewTreeBuilder = new RuleViewTreeBuilder(); 594 } 595 return viewTreeBuilder; 596 } 597 598 protected NaturalLanguageHelper getNaturalLanguageHelper() { 599 if (naturalLanguageHelper == null) { 600 naturalLanguageHelper = new NaturalLanguageHelper(); 601 naturalLanguageHelper.setRuleManagementService(this.getRuleManagementService()); 602 } 603 return naturalLanguageHelper; 604 } 605 606 protected TemplateRegistry getTemplateRegistry() { 607 if (templateRegistry == null) { 608 templateRegistry = (TemplateRegistry) GlobalResourceLoader.getService(QName.valueOf("templateResolverMockService")); 609 } 610 return templateRegistry; 611 } 612 613 protected KrmsTypeRepositoryService getKrmsTypeRepositoryService() { 614 if (krmsTypeRepositoryService == null) { 615 krmsTypeRepositoryService = (KrmsTypeRepositoryService) GlobalResourceLoader.getService(new QName(KrmsConstants.Namespaces.KRMS_NAMESPACE_2_0, "krmsTypeRepositoryService")); 616 } 617 return krmsTypeRepositoryService; 618 } 619 620 public TermRepositoryService getTermRepositoryService() { 621 if (termRepositoryService == null) { 622 termRepositoryService = (TermRepositoryService) GlobalResourceLoader.getService(new QName(KrmsConstants.Namespaces.KRMS_NAMESPACE_2_0, "termRepositoryService")); 623 } 624 return termRepositoryService; 625 } 626 627}