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.impl.ui; 017 018 import org.apache.commons.collections.CollectionUtils; 019 import org.apache.commons.lang.StringUtils; 020 import org.kuali.rice.core.api.util.tree.Node; 021 import org.kuali.rice.krad.maintenance.MaintenanceDocument; 022 import org.kuali.rice.krad.service.KRADServiceLocator; 023 import org.kuali.rice.krad.service.SequenceAccessorService; 024 import org.kuali.rice.krad.uif.UifParameters; 025 import org.kuali.rice.krad.util.GlobalVariables; 026 import org.kuali.rice.krad.util.ObjectUtils; 027 import org.kuali.rice.krad.web.controller.MaintenanceDocumentController; 028 import org.kuali.rice.krad.web.form.MaintenanceForm; 029 import org.kuali.rice.krad.web.form.UifFormBase; 030 import org.kuali.rice.krms.api.KrmsApiServiceLocator; 031 import org.kuali.rice.krms.api.engine.expression.ComparisonOperatorService; 032 import org.kuali.rice.krms.api.repository.rule.RuleDefinition; 033 import org.kuali.rice.krms.api.repository.term.TermDefinition; 034 import org.kuali.rice.krms.api.repository.term.TermResolverDefinition; 035 import org.kuali.rice.krms.api.repository.term.TermSpecificationDefinition; 036 import org.kuali.rice.krms.api.repository.type.KrmsAttributeDefinition; 037 import org.kuali.rice.krms.impl.repository.ActionBo; 038 import org.kuali.rice.krms.api.repository.LogicalOperator; 039 import org.kuali.rice.krms.api.repository.proposition.PropositionType; 040 import org.kuali.rice.krms.impl.repository.AgendaBo; 041 import org.kuali.rice.krms.impl.repository.AgendaItemBo; 042 import org.kuali.rice.krms.impl.repository.ContextBoService; 043 import org.kuali.rice.krms.impl.repository.KrmsAttributeDefinitionService; 044 import org.kuali.rice.krms.impl.repository.KrmsRepositoryServiceLocator; 045 import org.kuali.rice.krms.impl.repository.PropositionBo; 046 import org.kuali.rice.krms.impl.repository.RuleBo; 047 import org.kuali.rice.krms.impl.repository.RuleBoService; 048 import org.kuali.rice.krms.impl.repository.TermBo; 049 import org.kuali.rice.krms.impl.rule.AgendaEditorBusRule; 050 import org.kuali.rice.krms.impl.util.KRMSPropertyConstants; 051 import org.kuali.rice.krms.impl.util.KrmsImplConstants; 052 import org.springframework.stereotype.Controller; 053 import org.springframework.validation.BindingResult; 054 import org.springframework.web.bind.annotation.ModelAttribute; 055 import org.springframework.web.bind.annotation.RequestMapping; 056 import org.springframework.web.bind.annotation.RequestMethod; 057 import org.springframework.web.bind.annotation.RequestParam; 058 import org.springframework.web.bind.annotation.ResponseBody; 059 import org.springframework.web.servlet.ModelAndView; 060 061 import javax.jws.WebParam; 062 import javax.servlet.http.HttpServletRequest; 063 import javax.servlet.http.HttpServletResponse; 064 import java.util.ArrayList; 065 import java.util.Collection; 066 import java.util.Collections; 067 import java.util.Enumeration; 068 import java.util.HashMap; 069 import java.util.List; 070 import java.util.Map; 071 import java.util.UUID; 072 073 /** 074 * Controller for the Test UI Page 075 * @author Kuali Rice Team (rice.collab@kuali.org) 076 */ 077 @Controller 078 @RequestMapping(value = org.kuali.rice.krms.impl.util.KrmsImplConstants.WebPaths.AGENDA_EDITOR_PATH) 079 public class AgendaEditorController extends MaintenanceDocumentController { 080 081 private SequenceAccessorService sequenceAccessorService; 082 083 /** 084 * This overridden method does extra work on refresh to update the namespace when the context has been changed. 085 * 086 * @see org.kuali.rice.krad.web.controller.UifControllerBase#refresh(org.kuali.rice.krad.web.form.UifFormBase, org.springframework.validation.BindingResult, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) 087 */ 088 @RequestMapping(params = "methodToCall=" + "refresh") 089 @Override 090 public ModelAndView refresh(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 091 HttpServletRequest request, HttpServletResponse response) throws Exception { 092 ModelAndView modelAndView = super.refresh(form, result, request, response); 093 094 // handle return from context lookup 095 MaintenanceForm maintenanceForm = (MaintenanceForm) form; 096 AgendaEditor agendaEditor = ((AgendaEditor) maintenanceForm.getDocument().getNewMaintainableObject().getDataObject()); 097 AgendaEditorBusRule rule = new AgendaEditorBusRule(); 098 if (rule.validContext(agendaEditor) && rule.validAgendaName(agendaEditor)) { 099 // update the namespace on all agenda related objects if the contest has been changed 100 if (!StringUtils.equals(agendaEditor.getOldContextId(), agendaEditor.getAgenda().getContextId())) { 101 agendaEditor.setOldContextId(agendaEditor.getAgenda().getContextId()); 102 103 String namespace = ""; 104 if (!StringUtils.isBlank(agendaEditor.getAgenda().getContextId())) { 105 namespace = getContextBoService().getContextByContextId(agendaEditor.getAgenda().getContextId()).getNamespace(); 106 } 107 108 for (AgendaItemBo agendaItem : agendaEditor.getAgenda().getItems()) { 109 agendaItem.getRule().setNamespace(namespace); 110 for (ActionBo action : agendaItem.getRule().getActions()) { 111 action.setNamespace(namespace); 112 } 113 } 114 } 115 } 116 return modelAndView; 117 } 118 119 /** 120 * This method updates the existing rule in the agenda. 121 */ 122 @RequestMapping(params = "methodToCall=" + "goToAddRule") 123 public ModelAndView goToAddRule(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 124 HttpServletRequest request, HttpServletResponse response) throws Exception { 125 setAgendaItemLine(form, null); 126 AgendaEditor agendaEditor = getAgendaEditor(form); 127 agendaEditor.setAddRuleInProgress(true); 128 form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-AddRule-Page"); 129 return super.navigate(form, result, request, response); 130 } 131 132 /** 133 * This method sets the agendaItemLine for adding/editing AgendaItems. 134 * The agendaItemLine is a copy of the agendaItem so that changes are not applied when 135 * they are abandoned. If the agendaItem is null a new empty agendaItemLine is created. 136 * 137 * @param form 138 * @param agendaItem 139 */ 140 private void setAgendaItemLine(UifFormBase form, AgendaItemBo agendaItem) { 141 AgendaEditor agendaEditor = getAgendaEditor(form); 142 if (agendaItem == null) { 143 RuleBo rule = new RuleBo(); 144 rule.setId(getSequenceAccessorService().getNextAvailableSequenceNumber("KRMS_RULE_S", RuleBo.class) 145 .toString()); 146 if (StringUtils.isBlank(agendaEditor.getAgenda().getContextId())) { 147 rule.setNamespace(""); 148 } else { 149 rule.setNamespace(getContextBoService().getContextByContextId(agendaEditor.getAgenda().getContextId()).getNamespace()); 150 } 151 agendaItem = new AgendaItemBo(); 152 agendaItem.setRule(rule); 153 agendaEditor.setAgendaItemLine(agendaItem); 154 } else { 155 // TODO: Add a copy not the reference 156 agendaEditor.setAgendaItemLine((AgendaItemBo) ObjectUtils.deepCopy(agendaItem)); 157 } 158 159 160 if (agendaItem.getRule().getActions().isEmpty()) { 161 ActionBo actionBo = new ActionBo(); 162 actionBo.setTypeId(""); 163 actionBo.setNamespace(agendaItem.getRule().getNamespace()); 164 actionBo.setRuleId(agendaItem.getRule().getId()); 165 actionBo.setSequenceNumber(1); 166 agendaEditor.setAgendaItemLineRuleAction(actionBo); 167 } else { 168 agendaEditor.setAgendaItemLineRuleAction(agendaItem.getRule().getActions().get(0)); 169 } 170 171 agendaEditor.setCustomRuleActionAttributesMap(agendaEditor.getAgendaItemLineRuleAction().getAttributes()); 172 agendaEditor.setCustomRuleAttributesMap(agendaEditor.getAgendaItemLine().getRule().getAttributes()); 173 } 174 175 /** 176 * This method returns the id of the selected agendaItem. 177 * 178 * @param form 179 * @return selectedAgendaItemId 180 */ 181 private String getSelectedAgendaItemId(UifFormBase form) { 182 AgendaEditor agendaEditor = getAgendaEditor(form); 183 return agendaEditor.getSelectedAgendaItemId(); 184 } 185 186 /** 187 * This method sets the id of the cut agendaItem. 188 * 189 * @param form 190 * @param cutAgendaItemId 191 */ 192 private void setCutAgendaItemId(UifFormBase form, String cutAgendaItemId) { 193 AgendaEditor agendaEditor = getAgendaEditor(form); 194 agendaEditor.setCutAgendaItemId(cutAgendaItemId); 195 } 196 197 /** 198 * This method returns the id of the cut agendaItem. 199 * 200 * @param form 201 * @return cutAgendaItemId 202 */ 203 private String getCutAgendaItemId(UifFormBase form) { 204 AgendaEditor agendaEditor = getAgendaEditor(form); 205 return agendaEditor.getCutAgendaItemId(); 206 } 207 208 /** 209 * This method updates the existing rule in the agenda. 210 */ 211 @RequestMapping(params = "methodToCall=" + "goToEditRule") 212 public ModelAndView goToEditRule(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 213 HttpServletRequest request, HttpServletResponse response) throws Exception { 214 215 AgendaEditor agendaEditor = getAgendaEditor(form); 216 agendaEditor.setAddRuleInProgress(false); 217 // this is the root of the tree: 218 AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda()); 219 String selectedItemId = agendaEditor.getSelectedAgendaItemId(); 220 AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId); 221 222 setAgendaItemLine(form, node); 223 224 form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-EditRule-Page"); 225 return super.navigate(form, result, request, response); 226 } 227 228 /** 229 * This method adds the newly create rule to the agenda. 230 */ 231 @RequestMapping(params = "methodToCall=" + "addRule") 232 public ModelAndView addRule(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 233 HttpServletRequest request, HttpServletResponse response) throws Exception { 234 235 AgendaEditor agendaEditor = getAgendaEditor(form); 236 AgendaBo agenda = agendaEditor.getAgenda(); 237 AgendaItemBo newAgendaItem = agendaEditor.getAgendaItemLine(); 238 239 if (!validateProposition(newAgendaItem.getRule().getProposition(), newAgendaItem.getRule().getNamespace())) { 240 form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-AddRule-Page"); 241 // NOTICE short circuit method on invalid proposition 242 return super.navigate(form, result, request, response); 243 } 244 245 newAgendaItem.getRule().setAttributes(agendaEditor.getCustomRuleAttributesMap()); 246 updateRuleAction(agendaEditor); 247 248 if (agenda.getItems() == null) { 249 agenda.setItems(new ArrayList<AgendaItemBo>()); 250 } 251 252 AgendaEditorBusRule rule = new AgendaEditorBusRule(); 253 MaintenanceForm maintenanceForm = (MaintenanceForm) form; 254 MaintenanceDocument document = maintenanceForm.getDocument(); 255 if (rule.processAgendaItemBusinessRules(document)) { 256 newAgendaItem.setId(getSequenceAccessorService().getNextAvailableSequenceNumber("KRMS_AGENDA_ITM_S", AgendaItemBo.class) 257 .toString()); 258 newAgendaItem.setAgendaId(getCreateAgendaId(agenda)); 259 if (agenda.getFirstItemId() == null) { 260 agenda.setFirstItemId(newAgendaItem.getId()); 261 } else { 262 // insert agenda in tree 263 String selectedAgendaItemId = getSelectedAgendaItemId(form); 264 if (StringUtils.isBlank(selectedAgendaItemId)) { 265 // add after the last root node 266 AgendaItemBo node = getFirstAgendaItem(agenda); 267 while (node.getAlways() != null) { 268 node = node.getAlways(); 269 } 270 node.setAlwaysId(newAgendaItem.getId()); 271 node.setAlways(newAgendaItem); 272 } else { 273 // add after selected node 274 AgendaItemBo firstItem = getFirstAgendaItem(agenda); 275 AgendaItemBo node = getAgendaItemById(firstItem, selectedAgendaItemId); 276 newAgendaItem.setAlwaysId(node.getAlwaysId()); 277 newAgendaItem.setAlways(node.getAlways()); 278 node.setAlwaysId(newAgendaItem.getId()); 279 node.setAlways(newAgendaItem); 280 } 281 } 282 // add it to the collection on the agenda too 283 agenda.getItems().add(newAgendaItem); 284 agendaEditor.setAddRuleInProgress(false); 285 form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-Agenda-Page"); 286 } else { 287 form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-AddRule-Page"); 288 } 289 return super.navigate(form, result, request, response); 290 } 291 292 /** 293 * Validate the given proposition and its children. Note that this method is side-effecting, 294 * when errors are detected with the proposition, errors are added to the error map. 295 * @param proposition the proposition to validate 296 * @param namespace the namespace of the parent rule 297 * @return true if the proposition and its children (if any) are considered valid 298 */ 299 // TODO also wire up to proposition for faster feedback to the user 300 private boolean validateProposition(PropositionBo proposition, String namespace) { 301 boolean result = true; 302 303 if (proposition != null) { // Null props are allowed. 304 305 if (StringUtils.isBlank(proposition.getDescription())) { 306 GlobalVariables.getMessageMap().putError(KRMSPropertyConstants.Rule.PROPOSITION_TREE_GROUP_ID, 307 "error.rule.proposition.missingDescription"); 308 result &= false; 309 } 310 311 if (StringUtils.isBlank(proposition.getCompoundOpCode())) { 312 // then this is a simple proposition, validate accordingly 313 314 result &= validateSimpleProposition(proposition, namespace); 315 316 } else { 317 // this is a compound proposition (or it should be) 318 List<PropositionBo> compoundComponents = proposition.getCompoundComponents(); 319 320 if (!CollectionUtils.isEmpty(proposition.getParameters())) { 321 GlobalVariables.getMessageMap().putError(KRMSPropertyConstants.Rule.PROPOSITION_TREE_GROUP_ID, 322 "error.rule.proposition.compound.invalidParameter", proposition.getDescription()); 323 result &= false; 324 } 325 326 // recurse 327 if (!CollectionUtils.isEmpty(compoundComponents)) for (PropositionBo childProp : compoundComponents) { 328 result &= validateProposition(childProp, namespace); 329 } 330 } 331 } 332 333 return result; 334 } 335 336 /** 337 * Validate the given simple proposition. Note that this method is side-effecting, 338 * when errors are detected with the proposition, errors are added to the error map. 339 * @param proposition the proposition to validate 340 * @param namespace the namespace of the parent rule 341 * @return true if the proposition is considered valid 342 */ 343 private boolean validateSimpleProposition(PropositionBo proposition, String namespace) { 344 boolean result = true; 345 346 String propConstant = null; 347 if (proposition.getParameters().get(1) != null) { 348 propConstant = proposition.getParameters().get(1).getValue(); 349 } 350 String operator = null; 351 if (proposition.getParameters().get(2) != null) { 352 operator = proposition.getParameters().get(2).getValue(); 353 } 354 355 String termId = null; 356 if (proposition.getParameters().get(0) != null) { 357 termId = proposition.getParameters().get(0).getValue(); 358 } 359 // Simple proposition requires all of propConstant, termId and operator to be specified 360 if (StringUtils.isBlank(termId)) { 361 GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(KRMSPropertyConstants.Rule.PROPOSITION_TREE_GROUP_ID, 362 "error.rule.proposition.simple.blankField", proposition.getDescription(), "Term"); 363 result &= false; 364 } else { 365 result = validateTerm(proposition, namespace); 366 } 367 if (StringUtils.isBlank(operator)) { 368 GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(KRMSPropertyConstants.Rule.PROPOSITION_TREE_GROUP_ID, 369 "error.rule.proposition.simple.blankField", proposition.getDescription(), "Operator"); 370 result &= false; 371 } 372 if (StringUtils.isBlank(propConstant) && !operator.endsWith("null")) { // ==null and !=null operators have blank values. 373 GlobalVariables.getMessageMap().putErrorForSectionId(KRMSPropertyConstants.Rule.PROPOSITION_TREE_GROUP_ID, 374 "error.rule.proposition.simple.blankField", proposition.getDescription(), "Value"); 375 result &= false; 376 } else if (operator.endsWith("null")) { // ==null and !=null operators have blank values. 377 if (propConstant != null) { 378 proposition.getParameters().get(1).setValue(null); 379 } 380 } else if (!StringUtils.isBlank(termId)) { 381 // validate that the constant value is comparable against the term 382 String termType = lookupTermType(termId); 383 ComparisonOperatorService comparisonOperatorService = KrmsApiServiceLocator.getComparisonOperatorService(); 384 if (comparisonOperatorService.canCoerce(termType, propConstant)) { 385 if (comparisonOperatorService.coerce(termType, propConstant) == null) { // HMM, what if we wanted a rule that 386 // checked a null value? 387 GlobalVariables.getMessageMap().putError(KRMSPropertyConstants.Rule.PROPOSITION_TREE_GROUP_ID, 388 "error.rule.proposition.simple.invalidValue", proposition.getDescription(), propConstant); 389 result &= false; 390 } 391 } 392 } 393 394 if (!CollectionUtils.isEmpty(proposition.getCompoundComponents())) { 395 GlobalVariables.getMessageMap().putError(KRMSPropertyConstants.Rule.PROPOSITION_TREE_GROUP_ID, 396 "error.rule.proposition.simple.hasChildren", proposition.getDescription()); 397 result &= false; // simple prop should not have compound components 398 } 399 return result; 400 } 401 402 /** 403 * Validate the term in the given simple proposition. Note that this method is side-effecting, 404 * when errors are detected with the proposition, errors are added to the error map. 405 * @param proposition the proposition with the term to validate 406 * @param namespace the namespace of the parent rule 407 * @return true if the proposition's term is considered valid 408 */ 409 private boolean validateTerm(PropositionBo proposition, String namespace) { 410 boolean result = true; 411 412 String termId = proposition.getParameters().get(0).getValue(); 413 if (termId.startsWith(KrmsImplConstants.PARAMETERIZED_TERM_PREFIX)) { 414 // validate parameterized term 415 416 // is the term name non-blank 417 if (StringUtils.isBlank(proposition.getNewTermDescription())) { 418 GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(KRMSPropertyConstants.Rule.PROPOSITION_TREE_GROUP_ID, 419 "error.rule.proposition.simple.emptyTermName", proposition.getDescription()); 420 result &= false; 421 } else { // check if the term name is unique 422 423 Map<String, String> criteria = new HashMap<String, String>(); 424 425 criteria.put("description", proposition.getNewTermDescription()); 426 criteria.put("specification.namespace", namespace); 427 428 Collection<TermBo> matchingTerms = 429 KRADServiceLocator.getBusinessObjectService().findMatching(TermBo.class, criteria); 430 431 if (!CollectionUtils.isEmpty(matchingTerms)) { 432 // this is a Warning -- maybe it should be an error? 433 GlobalVariables.getMessageMap().putWarningWithoutFullErrorPath(KRMSPropertyConstants.Rule.PROPOSITION_TREE_GROUP_ID, 434 "warning.rule.proposition.simple.duplicateTermName", proposition.getDescription()); 435 } 436 } 437 438 String termSpecificationId = termId.substring(KrmsImplConstants.PARAMETERIZED_TERM_PREFIX.length()); 439 440 TermResolverDefinition termResolverDefinition = 441 AgendaEditorMaintainable.getSimplestTermResolver(termSpecificationId, namespace); 442 443 if (termResolverDefinition == null) { 444 GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(KRMSPropertyConstants.Rule.PROPOSITION_TREE_GROUP_ID, 445 "error.rule.proposition.simple.invalidTerm", proposition.getDescription()); 446 result &= false; 447 } else { 448 List<String> parameterNames = new ArrayList<String>(termResolverDefinition.getParameterNames()); 449 Collections.sort(parameterNames); 450 for (String parameterName : parameterNames) { 451 if (!proposition.getTermParameters().containsKey(parameterName) || 452 StringUtils.isBlank(proposition.getTermParameters().get(parameterName))) { 453 GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(KRMSPropertyConstants.Rule.PROPOSITION_TREE_GROUP_ID, 454 "error.rule.proposition.simple.missingTermParameter", proposition.getDescription()); 455 result &= false; 456 break; 457 } 458 } 459 } 460 461 } else { 462 //validate normal term 463 TermDefinition termDefinition = KrmsRepositoryServiceLocator.getTermBoService().getTerm(termId); 464 if (termDefinition == null) { 465 GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(KRMSPropertyConstants.Rule.PROPOSITION_TREE_GROUP_ID, 466 "error.rule.proposition.simple.invalidTerm", proposition.getDescription()); 467 } else if (!namespace.equals(termDefinition.getSpecification().getNamespace())) { 468 GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(KRMSPropertyConstants.Rule.PROPOSITION_TREE_GROUP_ID, 469 "error.rule.proposition.simple.invalidTerm", proposition.getDescription()); 470 } 471 } 472 return result; 473 } 474 475 /** 476 * Lookup the {@link org.kuali.rice.krms.api.repository.term.TermSpecificationDefinitionContract} type. 477 * @param key krms_term_t key 478 * @return String the krms_term_spec_t TYP for the given krms_term_t key given 479 */ 480 private String lookupTermType(String key) { 481 TermSpecificationDefinition termSpec = null; 482 if (key.startsWith(KrmsImplConstants.PARAMETERIZED_TERM_PREFIX)) { 483 String termSpecificationId = key.substring(KrmsImplConstants.PARAMETERIZED_TERM_PREFIX.length()); 484 termSpec = KrmsRepositoryServiceLocator.getTermBoService().getTermSpecificationById(termSpecificationId); 485 } else { 486 TermDefinition term = KrmsRepositoryServiceLocator.getTermBoService().getTerm(key); 487 if (term != null) { 488 termSpec = term.getSpecification(); 489 } 490 } 491 if (termSpec != null) { 492 return termSpec.getType(); 493 } else { 494 return null; 495 } 496 } 497 498 499 /** 500 * This method returns the agendaId of the given agenda. If the agendaId is null a new id will be created. 501 */ 502 private String getCreateAgendaId(AgendaBo agenda) { 503 if (agenda.getId() == null) { 504 agenda.setId(getSequenceAccessorService().getNextAvailableSequenceNumber("KRMS_AGENDA_S", AgendaItemBo.class).toString()); 505 } 506 return agenda.getId(); 507 } 508 509 private void updateRuleAction(AgendaEditor agendaEditor) { 510 agendaEditor.getAgendaItemLine().getRule().setActions(new ArrayList<ActionBo>()); 511 if (StringUtils.isNotBlank(agendaEditor.getAgendaItemLineRuleAction().getTypeId())) { 512 agendaEditor.getAgendaItemLineRuleAction().setAttributes(agendaEditor.getCustomRuleActionAttributesMap()); 513 agendaEditor.getAgendaItemLine().getRule().getActions().add(agendaEditor.getAgendaItemLineRuleAction()); 514 } 515 } 516 517 /** 518 * Build a map from attribute name to attribute definition from all the defined attribute definitions for the 519 * specified rule action type 520 * @param actionTypeId 521 * @return 522 */ 523 private Map<String, KrmsAttributeDefinition> buildAttributeDefinitionMap(String actionTypeId) { 524 KrmsAttributeDefinitionService attributeDefinitionService = 525 KrmsRepositoryServiceLocator.getKrmsAttributeDefinitionService(); 526 527 // build a map from attribute name to definition 528 Map<String, KrmsAttributeDefinition> attributeDefinitionMap = new HashMap<String, KrmsAttributeDefinition>(); 529 530 List<KrmsAttributeDefinition> attributeDefinitions = 531 attributeDefinitionService.findAttributeDefinitionsByType(actionTypeId); 532 533 for (KrmsAttributeDefinition attributeDefinition : attributeDefinitions) { 534 attributeDefinitionMap.put(attributeDefinition.getName(), attributeDefinition); 535 } 536 return attributeDefinitionMap; 537 } 538 539 /** 540 * This method updates the existing rule in the agenda. 541 */ 542 @RequestMapping(params = "methodToCall=" + "editRule") 543 public ModelAndView editRule(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 544 HttpServletRequest request, HttpServletResponse response) throws Exception { 545 AgendaEditor agendaEditor = getAgendaEditor(form); 546 // this is the root of the tree: 547 AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda()); 548 AgendaItemBo node = getAgendaItemById(firstItem, getSelectedAgendaItemId(form)); 549 AgendaItemBo agendaItemLine = agendaEditor.getAgendaItemLine(); 550 551 if (!validateProposition(agendaItemLine.getRule().getProposition(), agendaItemLine.getRule().getNamespace())) { 552 form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-EditRule-Page"); 553 // NOTICE short circuit method on invalid proposition 554 return super.navigate(form, result, request, response); 555 } 556 557 agendaItemLine.getRule().setAttributes(agendaEditor.getCustomRuleAttributesMap()); 558 updateRuleAction(agendaEditor); 559 560 AgendaEditorBusRule rule = new AgendaEditorBusRule(); 561 MaintenanceForm maintenanceForm = (MaintenanceForm) form; 562 MaintenanceDocument document = maintenanceForm.getDocument(); 563 if (rule.processAgendaItemBusinessRules(document)) { 564 node.setRule(agendaItemLine.getRule()); 565 form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-Agenda-Page"); 566 } else { 567 form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-EditRule-Page"); 568 } 569 return super.navigate(form, result, request, response); 570 } 571 572 /** 573 * @return the ALWAYS {@link AgendaItemInstanceChildAccessor} for the last ALWAYS child of the instance accessed by the parameter. 574 * It will by definition refer to null. If the instanceAccessor parameter refers to null, then it will be returned. This is useful 575 * for adding a youngest child to a sibling group. 576 */ 577 private AgendaItemInstanceChildAccessor getLastChildsAlwaysAccessor(AgendaItemInstanceChildAccessor instanceAccessor) { 578 AgendaItemBo next = instanceAccessor.getChild(); 579 if (next == null) return instanceAccessor; 580 while (next.getAlways() != null) { next = next.getAlways(); }; 581 return new AgendaItemInstanceChildAccessor(AgendaItemChildAccessor.always, next); 582 } 583 584 /** 585 * @return the accessor to the child with the given agendaItemId under the given parent. This method will search both When TRUE and 586 * When FALSE sibling groups. If the instance with the given id is not found, null is returned. 587 * @see AgendaItemChildAccessor for nomenclature explanation 588 */ 589 private AgendaItemInstanceChildAccessor getInstanceAccessorToChild(AgendaItemBo parent, String agendaItemId) { 590 591 // first try When TRUE, then When FALSE via AgendaItemChildAccessor.levelOrderChildren 592 for (AgendaItemChildAccessor levelOrderChildAccessor : AgendaItemChildAccessor.children) { 593 594 AgendaItemBo next = levelOrderChildAccessor.getChild(parent); 595 596 // if the first item matches, return the accessor from the parent 597 if (next != null && agendaItemId.equals(next.getId())) return new AgendaItemInstanceChildAccessor(levelOrderChildAccessor, parent); 598 599 // otherwise walk the children 600 while (next != null && next.getAlwaysId() != null) { 601 if (next.getAlwaysId().equals(agendaItemId)) return new AgendaItemInstanceChildAccessor(AgendaItemChildAccessor.always, next); 602 // move down 603 next = next.getAlways(); 604 } 605 } 606 607 return null; 608 } 609 610 @RequestMapping(params = "methodToCall=" + "ajaxRefresh") 611 public ModelAndView ajaxRefresh(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 612 HttpServletRequest request, HttpServletResponse response) 613 throws Exception { 614 // call the super method to avoid the agenda tree being reloaded from the db 615 return super.updateComponent(form, result, request, response); 616 } 617 618 @RequestMapping(params = "methodToCall=" + "moveUp") 619 public ModelAndView moveUp(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 620 HttpServletRequest request, HttpServletResponse response) 621 throws Exception { 622 moveSelectedSubtreeUp(form); 623 624 return super.refresh(form, result, request, response); 625 } 626 627 @RequestMapping(params = "methodToCall=" + "ajaxMoveUp") 628 public ModelAndView ajaxMoveUp(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 629 HttpServletRequest request, HttpServletResponse response) 630 throws Exception { 631 moveSelectedSubtreeUp(form); 632 633 // call the super method to avoid the agenda tree being reloaded from the db 634 return super.updateComponent(form, result, request, response); 635 } 636 637 /** 638 * Exposes Ajax callback to UI to validate entered rule name to copy 639 * @param name the copyRuleName 640 * @param namespace the rule namespace 641 * @return true or false 642 */ 643 @RequestMapping(params = "methodToCall=" + "ajaxValidRuleName", method=RequestMethod.GET) 644 public @ResponseBody boolean ajaxValidRuleName(@RequestParam String name, @RequestParam String namespace) { 645 return (getRuleBoService().getRuleByNameAndNamespace(name, namespace) != null); 646 } 647 648 /** 649 * 650 * @param form 651 * @see AgendaItemChildAccessor for nomenclature explanation 652 */ 653 private void moveSelectedSubtreeUp(UifFormBase form) { 654 655 /* Rough algorithm for moving a node up. This is a "level order" move. Note that in this tree, 656 * level order means something a bit funky. We are defining a level as it would be displayed in the browser, 657 * so only the traversal of When FALSE or When TRUE links increments the level, since ALWAYS linked nodes are 658 * considered siblings. 659 * 660 * find the following: 661 * node := the selected node 662 * parent := the selected node's parent, its containing node (via when true or when false relationship) 663 * parentsOlderCousin := the parent's level-order predecessor (sibling or cousin) 664 * 665 * if (node is first child in sibling group) 666 * if (node is in When FALSE group) 667 * move node to last position in When TRUE group 668 * else 669 * find youngest child of parentsOlderCousin and put node after it 670 * else 671 * move node up within its sibling group 672 */ 673 674 AgendaEditor agendaEditor = getAgendaEditor(form); 675 // this is the root of the tree: 676 AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda()); 677 678 String selectedItemId = agendaEditor.getSelectedAgendaItemId(); 679 AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId); 680 AgendaItemBo parent = getParent(firstItem, selectedItemId); 681 AgendaItemBo parentsOlderCousin = (parent == null) ? null : getNextOldestOfSameGeneration(firstItem, parent); 682 683 StringBuilder ruleEditorMessage = new StringBuilder(); 684 AgendaItemChildAccessor childAccessor = getOldestChildAccessor(node, parent); 685 if (childAccessor != null) { // node is first child in sibling group 686 if (childAccessor == AgendaItemChildAccessor.whenFalse) { 687 // move node to last position in When TRUE group 688 AgendaItemInstanceChildAccessor youngestWhenTrueSiblingInsertionPoint = 689 getLastChildsAlwaysAccessor(new AgendaItemInstanceChildAccessor(AgendaItemChildAccessor.whenTrue, parent)); 690 youngestWhenTrueSiblingInsertionPoint.setChild(node); 691 AgendaItemChildAccessor.whenFalse.setChild(parent, node.getAlways()); 692 AgendaItemChildAccessor.always.setChild(node, null); 693 694 ruleEditorMessage.append("Moved ").append(node.getRule().getName()).append(" up "); 695 ruleEditorMessage.append("to last position in When TRUE group of ").append(parent.getRule().getName()); 696 } else if (parentsOlderCousin != null) { 697 // find youngest child of parentsOlderCousin and put node after it 698 AgendaItemInstanceChildAccessor youngestWhenFalseSiblingInsertionPoint = 699 getLastChildsAlwaysAccessor(new AgendaItemInstanceChildAccessor(AgendaItemChildAccessor.whenFalse, parentsOlderCousin)); 700 youngestWhenFalseSiblingInsertionPoint.setChild(node); 701 AgendaItemChildAccessor.whenTrue.setChild(parent, node.getAlways()); 702 AgendaItemChildAccessor.always.setChild(node, null); 703 ruleEditorMessage.append("Moved ").append(node.getRule().getName()).append(" up "); 704 ruleEditorMessage.append("to When FALSE group of ").append(parentsOlderCousin.getRule().getName()); 705 } 706 } else if (!selectedItemId.equals(firstItem.getId())) { // conditional to miss special case of first node 707 708 AgendaItemBo bogusRootNode = null; 709 if (parent == null) { 710 // special case, this is a top level sibling. rig up special parent node 711 bogusRootNode = new AgendaItemBo(); 712 AgendaItemChildAccessor.whenTrue.setChild(bogusRootNode, firstItem); 713 parent = bogusRootNode; 714 } 715 716 // move node up within its sibling group 717 AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId()); 718 AgendaItemBo olderSibling = accessorToSelectedNode.getInstance(); 719 AgendaItemInstanceChildAccessor accessorToOlderSibling = getInstanceAccessorToChild(parent, olderSibling.getId()); 720 721 accessorToOlderSibling.setChild(node); 722 accessorToSelectedNode.setChild(node.getAlways()); 723 AgendaItemChildAccessor.always.setChild(node, olderSibling); 724 725 ruleEditorMessage.append("Moved ").append(node.getRule().getName()).append(" up "); 726 727 if (bogusRootNode != null) { 728 // clean up special case with bogus root node 729 agendaEditor.getAgenda().setFirstItemId(bogusRootNode.getWhenTrueId()); 730 ruleEditorMessage.append(" to ").append(getFirstAgendaItem(agendaEditor.getAgenda()).getRule().getName()).append(" When TRUE group"); 731 } else { 732 ruleEditorMessage.append(" within its sibling group, above " + olderSibling.getRule().getName()); 733 } 734 } 735 agendaEditor.setRuleEditorMessage(ruleEditorMessage.toString()); 736 } 737 738 @RequestMapping(params = "methodToCall=" + "moveDown") 739 public ModelAndView moveDown(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 740 HttpServletRequest request, HttpServletResponse response) 741 throws Exception { 742 moveSelectedSubtreeDown(form); 743 744 return super.refresh(form, result, request, response); 745 } 746 747 @RequestMapping(params = "methodToCall=" + "ajaxMoveDown") 748 public ModelAndView ajaxMoveDown(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 749 HttpServletRequest request, HttpServletResponse response) 750 throws Exception { 751 moveSelectedSubtreeDown(form); 752 753 // call the super method to avoid the agenda tree being reloaded from the db 754 return super.updateComponent(form, result, request, response); 755 } 756 757 /** 758 * 759 * @param form 760 * @see AgendaItemChildAccessor for nomenclature explanation 761 */ 762 private void moveSelectedSubtreeDown(UifFormBase form) { 763 764 /* Rough algorithm for moving a node down. This is a "level order" move. Note that in this tree, 765 * level order means something a bit funky. We are defining a level as it would be displayed in the browser, 766 * so only the traversal of When FALSE or When TRUE links increments the level, since ALWAYS linked nodes are 767 * considered siblings. 768 * 769 * find the following: 770 * node := the selected node 771 * parent := the selected node's parent, its containing node (via when true or when false relationship) 772 * parentsYoungerCousin := the parent's level-order successor (sibling or cousin) 773 * 774 * if (node is last child in sibling group) 775 * if (node is in When TRUE group) 776 * move node to first position in When FALSE group 777 * else 778 * move to first child of parentsYoungerCousin 779 * else 780 * move node down within its sibling group 781 */ 782 783 AgendaEditor agendaEditor = getAgendaEditor(form); 784 // this is the root of the tree: 785 AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda()); 786 787 String selectedItemId = agendaEditor.getSelectedAgendaItemId(); 788 AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId); 789 AgendaItemBo parent = getParent(firstItem, selectedItemId); 790 AgendaItemBo parentsYoungerCousin = (parent == null) ? null : getNextYoungestOfSameGeneration(firstItem, parent); 791 792 StringBuilder ruleEditorMessage = new StringBuilder(); 793 if (node.getAlways() == null && parent != null) { // node is last child in sibling group 794 // set link to selected node to null 795 if (parent.getWhenTrue() != null && isSiblings(parent.getWhenTrue(), node)) { // node is in When TRUE group 796 // move node to first child under When FALSE 797 AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId()); 798 accessorToSelectedNode.setChild(null); 799 800 AgendaItemBo parentsFirstChild = parent.getWhenFalse(); 801 AgendaItemChildAccessor.whenFalse.setChild(parent, node); 802 AgendaItemChildAccessor.always.setChild(node, parentsFirstChild); 803 804 ruleEditorMessage.append("Moved ").append(node.getRule().getName()).append(" down "); 805 ruleEditorMessage.append("to first child under When FALSE group of ").append(parent.getRule().getName()); 806 } else if (parentsYoungerCousin != null) { // node is in the When FALSE group 807 // move to first child of parentsYoungerCousin under When TRUE 808 AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId()); 809 accessorToSelectedNode.setChild(null); 810 811 AgendaItemBo parentsYoungerCousinsFirstChild = parentsYoungerCousin.getWhenTrue(); 812 AgendaItemChildAccessor.whenTrue.setChild(parentsYoungerCousin, node); 813 AgendaItemChildAccessor.always.setChild(node, parentsYoungerCousinsFirstChild); 814 815 ruleEditorMessage.append("Moved ").append(node.getRule().getName()).append(" down "); 816 ruleEditorMessage.append("to first child under When TRUE group of ").append(parentsYoungerCousin.getRule().getName()); 817 } 818 } else if (node.getAlways() != null) { // move node down within its sibling group 819 820 AgendaItemBo bogusRootNode = null; 821 if (parent == null) { 822 // special case, this is a top level sibling. rig up special parent node 823 824 bogusRootNode = new AgendaItemBo(); 825 AgendaItemChildAccessor.whenFalse.setChild(bogusRootNode, firstItem); 826 parent = bogusRootNode; 827 } 828 829 // move node down within its sibling group 830 AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId()); 831 AgendaItemBo youngerSibling = node.getAlways(); 832 accessorToSelectedNode.setChild(youngerSibling); 833 AgendaItemChildAccessor.always.setChild(node, youngerSibling.getAlways()); 834 AgendaItemChildAccessor.always.setChild(youngerSibling, node); 835 836 if (bogusRootNode != null) { 837 // clean up special case with bogus root node 838 agendaEditor.getAgenda().setFirstItemId(bogusRootNode.getWhenFalseId()); 839 } 840 ruleEditorMessage.append("Moved ").append(node.getRule().getName()).append(" down "); 841 ruleEditorMessage.append(" within its sibling group, below ").append(youngerSibling.getRule().getName()); 842 } // falls through if already bottom-most 843 agendaEditor.setRuleEditorMessage(ruleEditorMessage.toString()); 844 } 845 846 @RequestMapping(params = "methodToCall=" + "moveLeft") 847 public ModelAndView moveLeft(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 848 HttpServletRequest request, HttpServletResponse response) 849 throws Exception { 850 moveSelectedSubtreeLeft(form); 851 852 return super.refresh(form, result, request, response); 853 } 854 855 @RequestMapping(params = "methodToCall=" + "ajaxMoveLeft") 856 public ModelAndView ajaxMoveLeft(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 857 HttpServletRequest request, HttpServletResponse response) 858 throws Exception { 859 860 moveSelectedSubtreeLeft(form); 861 862 // call the super method to avoid the agenda tree being reloaded from the db 863 return super.updateComponent(form, result, request, response); 864 } 865 866 /** 867 * 868 * @param form 869 * @see AgendaItemChildAccessor for nomenclature explanation 870 */ 871 private void moveSelectedSubtreeLeft(UifFormBase form) { 872 873 /* 874 * Move left means make it a younger sibling of it's parent. 875 */ 876 877 AgendaEditor agendaEditor = getAgendaEditor(form); 878 // this is the root of the tree: 879 AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda()); 880 881 String selectedItemId = agendaEditor.getSelectedAgendaItemId(); 882 AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId); 883 AgendaItemBo parent = getParent(firstItem, selectedItemId); 884 885 if (parent != null) { 886 AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId()); 887 accessorToSelectedNode.setChild(node.getAlways()); 888 AgendaItemChildAccessor.always.setChild(node, parent.getAlways()); 889 AgendaItemChildAccessor.always.setChild(parent, node); 890 891 StringBuilder ruleEditorMessage = new StringBuilder(); 892 ruleEditorMessage.append("Moved ").append(node.getRule().getName()).append(" left to be a sibling of its parent ").append(parent.getRule().getName()); 893 agendaEditor.setRuleEditorMessage(ruleEditorMessage.toString()); 894 } 895 } 896 897 898 @RequestMapping(params = "methodToCall=" + "moveRight") 899 public ModelAndView moveRight(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 900 HttpServletRequest request, HttpServletResponse response) 901 throws Exception { 902 903 moveSelectedSubtreeRight(form); 904 905 return super.refresh(form, result, request, response); 906 } 907 908 @RequestMapping(params = "methodToCall=" + "ajaxMoveRight") 909 public ModelAndView ajaxMoveRight(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 910 HttpServletRequest request, HttpServletResponse response) 911 throws Exception { 912 913 moveSelectedSubtreeRight(form); 914 915 // call the super method to avoid the agenda tree being reloaded from the db 916 return super.updateComponent(form, result, request, response); 917 } 918 919 /** 920 * 921 * @param form 922 * @see AgendaItemChildAccessor for nomenclature explanation 923 */ 924 private void moveSelectedSubtreeRight(UifFormBase form) { 925 926 /* 927 * Move right prefers moving to bottom of upper sibling's When FALSE branch 928 * ... otherwise .. 929 * moves to top of lower sibling's When TRUE branch 930 */ 931 932 AgendaEditor agendaEditor = getAgendaEditor(form); 933 // this is the root of the tree: 934 AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda()); 935 936 String selectedItemId = agendaEditor.getSelectedAgendaItemId(); 937 AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId); 938 AgendaItemBo parent = getParent(firstItem, selectedItemId); 939 940 AgendaItemBo bogusRootNode = null; 941 if (parent == null) { 942 // special case, this is a top level sibling. rig up special parent node 943 bogusRootNode = new AgendaItemBo(); 944 AgendaItemChildAccessor.whenFalse.setChild(bogusRootNode, firstItem); 945 parent = bogusRootNode; 946 } 947 948 AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId()); 949 AgendaItemBo olderSibling = (accessorToSelectedNode.getInstance() == parent) ? null : accessorToSelectedNode.getInstance(); 950 951 StringBuilder ruleEditorMessage = new StringBuilder(); 952 if (olderSibling != null) { 953 accessorToSelectedNode.setChild(node.getAlways()); 954 AgendaItemInstanceChildAccessor yougestWhenFalseSiblingInsertionPoint = 955 getLastChildsAlwaysAccessor(new AgendaItemInstanceChildAccessor(AgendaItemChildAccessor.whenFalse, olderSibling)); 956 yougestWhenFalseSiblingInsertionPoint.setChild(node); 957 AgendaItemChildAccessor.always.setChild(node, null); 958 959 ruleEditorMessage.append("Moved ").append(node.getRule().getName()).append(" right to "); 960 ruleEditorMessage.append(olderSibling.getRule().getName()).append(" When FALSE group."); 961 } else if (node.getAlways() != null) { // has younger sibling 962 accessorToSelectedNode.setChild(node.getAlways()); 963 AgendaItemBo childsWhenTrue = node.getAlways().getWhenTrue(); 964 AgendaItemChildAccessor.whenTrue.setChild(node.getAlways(), node); 965 AgendaItemChildAccessor.always.setChild(node, childsWhenTrue); 966 967 ruleEditorMessage.append("Moved ").append(node.getRule().getName()).append(" right to "); 968 if (childsWhenTrue != null) { // childsWhenTrue is null if the topmost rule is moved right see bogusRootNode below 969 ruleEditorMessage.append(childsWhenTrue.getRule().getName()).append(" When TRUE group"); 970 } 971 } // falls through if node is already the rightmost. 972 973 if (bogusRootNode != null) { 974 // clean up special case with bogus root node 975 agendaEditor.getAgenda().setFirstItemId(bogusRootNode.getWhenFalseId()); 976 ruleEditorMessage.append(getFirstAgendaItem(agendaEditor.getAgenda()).getRule().getName()).append(" When TRUE group"); 977 } 978 agendaEditor.setRuleEditorMessage(ruleEditorMessage.toString()); 979 } 980 981 /** 982 * 983 * @param cousin1 984 * @param cousin2 985 * @return 986 * @see AgendaItemChildAccessor for nomenclature explanation 987 */ 988 private boolean isSiblings(AgendaItemBo cousin1, AgendaItemBo cousin2) { 989 if (cousin1.equals(cousin2)) return true; // this is a bit abusive 990 991 // can you walk to c1 from ALWAYS links of c2? 992 AgendaItemBo candidate = cousin2; 993 while (null != (candidate = candidate.getAlways())) { 994 if (candidate.equals(cousin1)) return true; 995 } 996 // can you walk to c2 from ALWAYS links of c1? 997 candidate = cousin1; 998 while (null != (candidate = candidate.getAlways())) { 999 if (candidate.equals(cousin2)) return true; 1000 } 1001 return false; 1002 } 1003 1004 /** 1005 * This method returns the level order accessor (getWhenTrue or getWhenFalse) that relates the parent directly 1006 * to the child. If the two nodes don't have such a relationship, null is returned. 1007 * Note that this only finds accessors for oldest children, not younger siblings. 1008 * @see AgendaItemChildAccessor for nomenclature explanation 1009 */ 1010 private AgendaItemChildAccessor getOldestChildAccessor( 1011 AgendaItemBo child, AgendaItemBo parent) { 1012 AgendaItemChildAccessor levelOrderChildAccessor = null; 1013 1014 if (parent != null) { 1015 for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.children) { 1016 if (child.equals(childAccessor.getChild(parent))) { 1017 levelOrderChildAccessor = childAccessor; 1018 break; 1019 } 1020 } 1021 } 1022 return levelOrderChildAccessor; 1023 } 1024 1025 /** 1026 * This method finds and returns the first agenda item in the agenda, or null if there are no items presently 1027 * 1028 * @param agenda 1029 * @return 1030 */ 1031 private AgendaItemBo getFirstAgendaItem(AgendaBo agenda) { 1032 AgendaItemBo firstItem = null; 1033 if (agenda != null && agenda.getItems() != null) for (AgendaItemBo agendaItem : agenda.getItems()) { 1034 if (agenda.getFirstItemId().equals(agendaItem.getId())) { 1035 firstItem = agendaItem; 1036 break; 1037 } 1038 } 1039 return firstItem; 1040 } 1041 1042 /** 1043 * @return the closest younger sibling of the agenda item with the given ID, and if there is no such sibling, the closest younger cousin. 1044 * If there is no such cousin either, then null is returned. 1045 * @see AgendaItemChildAccessor for nomenclature explanation 1046 */ 1047 private AgendaItemBo getNextYoungestOfSameGeneration(AgendaItemBo root, AgendaItemBo agendaItem) { 1048 1049 int genNumber = getAgendaItemGenerationNumber(0, root, agendaItem.getId()); 1050 List<AgendaItemBo> genList = new ArrayList<AgendaItemBo>(); 1051 buildAgendaItemGenerationList(genList, root, 0, genNumber); 1052 1053 int itemIndex = genList.indexOf(agendaItem); 1054 if (genList.size() > itemIndex + 1) return genList.get(itemIndex + 1); 1055 1056 return null; 1057 } 1058 1059 /** 1060 * 1061 * @param currentLevel 1062 * @param node 1063 * @param agendaItemId 1064 * @return 1065 * @see AgendaItemChildAccessor for nomenclature explanation 1066 */ 1067 private int getAgendaItemGenerationNumber(int currentLevel, AgendaItemBo node, String agendaItemId) { 1068 int result = -1; 1069 if (agendaItemId.equals(node.getId())) { 1070 result = currentLevel; 1071 } else { 1072 for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) { 1073 AgendaItemBo child = childAccessor.getChild(node); 1074 if (child != null) { 1075 int nextLevel = currentLevel; 1076 // we don't change the level order parent when we traverse ALWAYS links 1077 if (childAccessor != AgendaItemChildAccessor.always) { 1078 nextLevel = currentLevel +1; 1079 } 1080 result = getAgendaItemGenerationNumber(nextLevel, child, agendaItemId); 1081 if (result != -1) break; 1082 } 1083 } 1084 } 1085 return result; 1086 } 1087 1088 /** 1089 * 1090 * @param genList 1091 * @param node 1092 * @param currentLevel 1093 * @param generation 1094 * @see AgendaItemChildAccessor for nomenclature explanation 1095 */ 1096 private void buildAgendaItemGenerationList(List<AgendaItemBo> genList, AgendaItemBo node, int currentLevel, int generation) { 1097 if (currentLevel == generation) { 1098 genList.add(node); 1099 } 1100 1101 if (currentLevel > generation) return; 1102 1103 for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) { 1104 AgendaItemBo child = childAccessor.getChild(node); 1105 if (child != null) { 1106 int nextLevel = currentLevel; 1107 // we don't change the level order parent when we traverse ALWAYS links 1108 if (childAccessor != AgendaItemChildAccessor.always) { 1109 nextLevel = currentLevel +1; 1110 } 1111 buildAgendaItemGenerationList(genList, child, nextLevel, generation); 1112 } 1113 } 1114 } 1115 1116 /** 1117 * @return the closest older sibling of the agenda item with the given ID, and if there is no such sibling, the closest older cousin. 1118 * If there is no such cousin either, then null is returned. 1119 * @see AgendaItemChildAccessor for nomenclature explanation 1120 */ 1121 private AgendaItemBo getNextOldestOfSameGeneration(AgendaItemBo root, AgendaItemBo agendaItem) { 1122 1123 int genNumber = getAgendaItemGenerationNumber(0, root, agendaItem.getId()); 1124 List<AgendaItemBo> genList = new ArrayList<AgendaItemBo>(); 1125 buildAgendaItemGenerationList(genList, root, 0, genNumber); 1126 1127 int itemIndex = genList.indexOf(agendaItem); 1128 if (itemIndex >= 1) return genList.get(itemIndex - 1); 1129 1130 return null; 1131 } 1132 1133 1134 /** 1135 * returns the parent of the item with the passed in id. Note that {@link AgendaItemBo}s related by ALWAYS relationships are considered siblings. 1136 * @see AgendaItemChildAccessor for nomenclature explanation 1137 */ 1138 private AgendaItemBo getParent(AgendaItemBo root, String agendaItemId) { 1139 return getParentHelper(root, null, agendaItemId); 1140 } 1141 1142 /** 1143 * 1144 * @param node 1145 * @param levelOrderParent 1146 * @param agendaItemId 1147 * @return 1148 * @see AgendaItemChildAccessor for nomenclature explanation 1149 */ 1150 private AgendaItemBo getParentHelper(AgendaItemBo node, AgendaItemBo levelOrderParent, String agendaItemId) { 1151 AgendaItemBo result = null; 1152 if (agendaItemId.equals(node.getId())) { 1153 result = levelOrderParent; 1154 } else { 1155 for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) { 1156 AgendaItemBo child = childAccessor.getChild(node); 1157 if (child != null) { 1158 // we don't change the level order parent when we traverse ALWAYS links 1159 AgendaItemBo lop = (childAccessor == AgendaItemChildAccessor.always) ? levelOrderParent : node; 1160 result = getParentHelper(child, lop, agendaItemId); 1161 if (result != null) break; 1162 } 1163 } 1164 } 1165 return result; 1166 } 1167 1168 /** 1169 * Search the tree for the agenda item with the given id. 1170 */ 1171 private AgendaItemBo getAgendaItemById(AgendaItemBo node, String agendaItemId) { 1172 if (node == null) throw new IllegalArgumentException("node must be non-null"); 1173 1174 AgendaItemBo result = null; 1175 1176 if (agendaItemId.equals(node.getId())) { 1177 result = node; 1178 } else { 1179 for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) { 1180 AgendaItemBo child = childAccessor.getChild(node); 1181 if (child != null) { 1182 result = getAgendaItemById(child, agendaItemId); 1183 if (result != null) break; 1184 } 1185 } 1186 } 1187 return result; 1188 } 1189 1190 /** 1191 * @param form 1192 * @return the {@link AgendaEditor} from the form 1193 */ 1194 private AgendaEditor getAgendaEditor(UifFormBase form) { 1195 MaintenanceForm maintenanceForm = (MaintenanceForm) form; 1196 return ((AgendaEditor)maintenanceForm.getDocument().getDocumentDataObject()); 1197 } 1198 1199 private void treeToInOrderList(AgendaItemBo agendaItem, List<AgendaItemBo> listToBuild) { 1200 listToBuild.add(agendaItem); 1201 for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) { 1202 AgendaItemBo child = childAccessor.getChild(agendaItem); 1203 if (child != null) treeToInOrderList(child, listToBuild); 1204 } 1205 } 1206 1207 1208 @RequestMapping(params = "methodToCall=" + "delete") 1209 public ModelAndView delete(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 1210 HttpServletRequest request, HttpServletResponse response) 1211 throws Exception { 1212 1213 deleteSelectedSubtree(form); 1214 1215 return super.refresh(form, result, request, response); 1216 } 1217 1218 @RequestMapping(params = "methodToCall=" + "ajaxDelete") 1219 public ModelAndView ajaxDelete(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 1220 HttpServletRequest request, HttpServletResponse response) 1221 throws Exception { 1222 1223 deleteSelectedSubtree(form); 1224 1225 // call the super method to avoid the agenda tree being reloaded from the db 1226 return super.updateComponent(form, result, request, response); 1227 } 1228 1229 1230 private void deleteSelectedSubtree(UifFormBase form) { 1231 AgendaEditor agendaEditor = getAgendaEditor(form); 1232 AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda()); 1233 1234 if (firstItem != null) { 1235 String agendaItemSelected = agendaEditor.getSelectedAgendaItemId(); 1236 AgendaItemBo selectedItem = getAgendaItemById(firstItem, agendaItemSelected); 1237 1238 // need to handle the first item here, our recursive method won't handle it. 1239 if (agendaItemSelected.equals(firstItem.getId())) { 1240 agendaEditor.getAgenda().setFirstItemId(firstItem.getAlwaysId()); 1241 } else { 1242 deleteAgendaItem(firstItem, agendaItemSelected); 1243 } 1244 1245 StringBuilder ruleEditorMessage = new StringBuilder(); 1246 ruleEditorMessage.append("Deleted ").append(selectedItem.getRule().getName()); 1247 // remove agenda item and its whenTrue & whenFalse children from the list of agendaItems of the agenda 1248 if (selectedItem.getWhenTrue() != null) { 1249 removeAgendaItem(agendaEditor.getAgenda().getItems(), selectedItem.getWhenTrue()); 1250 ruleEditorMessage.append(" and its When TRUE ").append(selectedItem.getWhenTrue().getRule().getName()); 1251 } 1252 if (selectedItem.getWhenFalse() != null) { 1253 removeAgendaItem(agendaEditor.getAgenda().getItems(), selectedItem.getWhenFalse()); 1254 ruleEditorMessage.append(" and its When FALSE ").append(selectedItem.getWhenFalse().getRule().getName()); 1255 } 1256 agendaEditor.getAgenda().getItems().remove(selectedItem); 1257 agendaEditor.setRuleEditorMessage(ruleEditorMessage.toString()); 1258 } 1259 } 1260 1261 private void deleteAgendaItem(AgendaItemBo root, String agendaItemIdToDelete) { 1262 if (deleteAgendaItem(root, AgendaItemChildAccessor.whenTrue, agendaItemIdToDelete) || 1263 deleteAgendaItem(root, AgendaItemChildAccessor.whenFalse, agendaItemIdToDelete) || 1264 deleteAgendaItem(root, AgendaItemChildAccessor.always, agendaItemIdToDelete)); // TODO: this is confusing, refactor 1265 } 1266 1267 private boolean deleteAgendaItem(AgendaItemBo agendaItem, AgendaItemChildAccessor childAccessor, String agendaItemIdToDelete) { 1268 if (agendaItem == null || childAccessor.getChild(agendaItem) == null) return false; 1269 if (agendaItemIdToDelete.equals(childAccessor.getChild(agendaItem).getId())) { 1270 // delete the child in such a way that any ALWAYS children don't get lost from the tree 1271 AgendaItemBo grandchildToKeep = childAccessor.getChild(agendaItem).getAlways(); 1272 childAccessor.setChild(agendaItem, grandchildToKeep); 1273 return true; 1274 } else { 1275 AgendaItemBo child = childAccessor.getChild(agendaItem); 1276 // recurse 1277 for (AgendaItemChildAccessor nextChildAccessor : AgendaItemChildAccessor.linkedNodes) { 1278 if (deleteAgendaItem(child, nextChildAccessor, agendaItemIdToDelete)) return true; 1279 } 1280 } 1281 return false; 1282 } 1283 1284 /** 1285 * Recursively delete the agendaItem and its children from the agendaItemBo list. 1286 * @param items, the list of agendaItemBo that the agenda holds 1287 * @param removeAgendaItem, the agendaItemBo to be removed 1288 */ 1289 private void removeAgendaItem(List<AgendaItemBo> items, AgendaItemBo removeAgendaItem) { 1290 if (removeAgendaItem.getWhenTrue() != null) { 1291 removeAgendaItem(items, removeAgendaItem.getWhenTrue()); 1292 } 1293 if (removeAgendaItem.getWhenFalse() != null) { 1294 removeAgendaItem(items, removeAgendaItem.getWhenFalse()); 1295 } 1296 if (removeAgendaItem.getAlways() != null) { 1297 removeAgendaItem(items, removeAgendaItem.getAlways()); 1298 } 1299 items.remove(removeAgendaItem); 1300 } 1301 1302 @RequestMapping(params = "methodToCall=" + "ajaxCut") 1303 public ModelAndView ajaxCut(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 1304 HttpServletRequest request, HttpServletResponse response) throws Exception { 1305 1306 AgendaEditor agendaEditor = getAgendaEditor(form); 1307 // this is the root of the tree: 1308 AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda()); 1309 String selectedItemId = agendaEditor.getSelectedAgendaItemId(); 1310 1311 AgendaItemBo selectedAgendaItem = getAgendaItemById(firstItem, selectedItemId); 1312 setCutAgendaItemId(form, selectedItemId); 1313 1314 StringBuilder ruleEditorMessage = new StringBuilder(); 1315 ruleEditorMessage.append("Marked ").append(selectedAgendaItem.getRule().getName()).append(" for cutting."); 1316 agendaEditor.setRuleEditorMessage(ruleEditorMessage.toString()); 1317 // call the super method to avoid the agenda tree being reloaded from the db 1318 return super.updateComponent(form, result, request, response); 1319 } 1320 1321 @RequestMapping(params = "methodToCall=" + "ajaxPaste") 1322 public ModelAndView ajaxPaste(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 1323 HttpServletRequest request, HttpServletResponse response) throws Exception { 1324 1325 AgendaEditor agendaEditor = getAgendaEditor(form); 1326 // this is the root of the tree: 1327 AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda()); 1328 String selectedItemId = agendaEditor.getSelectedAgendaItemId(); 1329 1330 String agendaItemId = getCutAgendaItemId(form); 1331 if (StringUtils.isNotBlank(selectedItemId) && StringUtils.isNotBlank(agendaItemId)) { 1332 StringBuilder ruleEditorMessage = new StringBuilder(); 1333 AgendaItemBo node = getAgendaItemById(firstItem, agendaItemId); 1334 AgendaItemBo orgRefNode = getReferringNode(firstItem, agendaItemId); 1335 AgendaItemBo newRefNode = getAgendaItemById(firstItem, selectedItemId); 1336 1337 if (isSameOrChildNode(node, newRefNode)) { 1338 // note if the cut agenda item is not cleared, then the javascript on the AgendaEditorView will need to be 1339 // updated to deal with a paste that doesn't paste. As the ui disables the paste button after it is clicked 1340 ruleEditorMessage.append("Cannot paste ").append(node.getRule().getName()).append(" to itself."); 1341 } else { 1342 // remove node 1343 if (orgRefNode == null) { 1344 agendaEditor.getAgenda().setFirstItemId(node.getAlwaysId()); 1345 } else { 1346 // determine if true, false or always 1347 // do appropriate operation 1348 if (node.getId().equals(orgRefNode.getWhenTrueId())) { 1349 orgRefNode.setWhenTrueId(node.getAlwaysId()); 1350 orgRefNode.setWhenTrue(node.getAlways()); 1351 } else if(node.getId().equals(orgRefNode.getWhenFalseId())) { 1352 orgRefNode.setWhenFalseId(node.getAlwaysId()); 1353 orgRefNode.setWhenFalse(node.getAlways()); 1354 } else { 1355 orgRefNode.setAlwaysId(node.getAlwaysId()); 1356 orgRefNode.setAlways(node.getAlways()); 1357 } 1358 } 1359 1360 // insert node 1361 node.setAlwaysId(newRefNode.getAlwaysId()); 1362 node.setAlways(newRefNode.getAlways()); 1363 newRefNode.setAlwaysId(node.getId()); 1364 newRefNode.setAlways(node); 1365 1366 ruleEditorMessage.append(" Pasted ").append(node.getRule().getName()); 1367 ruleEditorMessage.append(" to ").append(newRefNode.getRule().getName()); 1368 agendaEditor.setRuleEditorMessage(ruleEditorMessage.toString()); 1369 1370 } 1371 setCutAgendaItemId(form, null); 1372 } 1373 1374 1375 // call the super method to avoid the agenda tree being reloaded from the db 1376 return super.updateComponent(form, result, request, response); 1377 } 1378 1379 /** 1380 * Updates to the category call back to this method to set the categoryId appropriately 1381 * TODO: shouldn't this happen automatically? We're taking it out of the form by hand here 1382 */ 1383 @RequestMapping(params = "methodToCall=" + "ajaxCategoryChangeRefresh") 1384 public ModelAndView ajaxCategoryChangeRefresh(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 1385 HttpServletRequest request, HttpServletResponse response) 1386 throws Exception { 1387 1388 String categoryParamName = null; 1389 Enumeration paramNames = request.getParameterNames(); 1390 while (paramNames.hasMoreElements()) { 1391 String paramName = paramNames.nextElement().toString(); 1392 if (paramName.endsWith("categoryId")) { 1393 categoryParamName = paramName; 1394 break; 1395 } 1396 } 1397 1398 if (categoryParamName != null) { 1399 String categoryId = request.getParameter(categoryParamName); 1400 1401 if (StringUtils.isBlank(categoryId)) { categoryId = null; } 1402 1403 AgendaEditor agendaEditor = getAgendaEditor(form); 1404 RuleBo rule = agendaEditor.getAgendaItemLine().getRule(); 1405 String selectedPropId = agendaEditor.getSelectedPropositionId(); 1406 1407 // TODO: This should work even if the prop isn't selected!!! Find the node in edit mode? 1408 if (!StringUtils.isBlank(selectedPropId)) { 1409 Node<RuleTreeNode, String> selectedPropositionNode = 1410 findPropositionTreeNode(rule.getPropositionTree().getRootElement(), selectedPropId); 1411 selectedPropositionNode.getData().getProposition().setCategoryId(categoryId); 1412 } 1413 } 1414 1415 return ajaxRefresh(form, result, request, response); 1416 } 1417 1418 /** 1419 * This method checks if the node is the same as the new parent node or a when-true/when-fase 1420 * child of the new parent node. 1421 * 1422 * @param node - the node to be checked if it's the same or a child 1423 * @param newParent - the parent node to check against 1424 * @return true if same or child, false otherwise 1425 * @see AgendaItemChildAccessor for nomenclature explanation 1426 */ 1427 private boolean isSameOrChildNode(AgendaItemBo node, AgendaItemBo newParent) { 1428 return isSameOrChildNodeHelper(node, newParent, AgendaItemChildAccessor.children); 1429 } 1430 1431 private boolean isSameOrChildNodeHelper(AgendaItemBo node, AgendaItemBo newParent, AgendaItemChildAccessor[] childAccessors) { 1432 boolean result = false; 1433 if (newParent == null || node == null) { 1434 return false; 1435 } 1436 if (StringUtils.equals(node.getId(), newParent.getId())) { 1437 result = true; 1438 } else { 1439 for (AgendaItemChildAccessor childAccessor : childAccessors) { 1440 AgendaItemBo child = childAccessor.getChild(node); 1441 if (child != null) { 1442 result = isSameOrChildNodeHelper(child, newParent, AgendaItemChildAccessor.linkedNodes); 1443 if (result == true) break; 1444 } 1445 } 1446 } 1447 return result; 1448 } 1449 1450 /** 1451 * This method returns the node that points to the specified agendaItemId. 1452 * (returns the next older sibling or the parent if no older sibling exists) 1453 * 1454 * @param root - the first agenda item of the agenda 1455 * @param agendaItemId - agenda item id of the agenda item whose referring node is to be returned 1456 * @return AgendaItemBo that points to the specified agenda item 1457 * @see AgendaItemChildAccessor for nomenclature explanation 1458 */ 1459 private AgendaItemBo getReferringNode(AgendaItemBo root, String agendaItemId) { 1460 return getReferringNodeHelper(root, null, agendaItemId); 1461 } 1462 1463 private AgendaItemBo getReferringNodeHelper(AgendaItemBo node, AgendaItemBo referringNode, String agendaItemId) { 1464 AgendaItemBo result = null; 1465 if (agendaItemId.equals(node.getId())) { 1466 result = referringNode; 1467 } else { 1468 for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) { 1469 AgendaItemBo child = childAccessor.getChild(node); 1470 if (child != null) { 1471 result = getReferringNodeHelper(child, node, agendaItemId); 1472 if (result != null) break; 1473 } 1474 } 1475 } 1476 return result; 1477 } 1478 1479 /** 1480 * return the sequenceAssessorService 1481 */ 1482 private SequenceAccessorService getSequenceAccessorService() { 1483 if ( sequenceAccessorService == null ) { 1484 sequenceAccessorService = KRADServiceLocator.getSequenceAccessorService(); 1485 } 1486 return sequenceAccessorService; 1487 } 1488 1489 /** 1490 * return the contextBoService 1491 */ 1492 private ContextBoService getContextBoService() { 1493 return KrmsRepositoryServiceLocator.getContextBoService(); 1494 } 1495 1496 /** 1497 * return the contextBoService 1498 */ 1499 private RuleBoService getRuleBoService() { 1500 return KrmsRepositoryServiceLocator.getRuleBoService(); 1501 } 1502 1503 /** 1504 * binds a child accessor to an AgendaItemBo instance. An {@link AgendaItemInstanceChildAccessor} allows you to 1505 * get and set the referent 1506 */ 1507 private static class AgendaItemInstanceChildAccessor { 1508 1509 private final AgendaItemChildAccessor accessor; 1510 private final AgendaItemBo instance; 1511 1512 public AgendaItemInstanceChildAccessor(AgendaItemChildAccessor accessor, AgendaItemBo instance) { 1513 this.accessor = accessor; 1514 this.instance = instance; 1515 } 1516 1517 public void setChild(AgendaItemBo child) { 1518 accessor.setChild(instance, child); 1519 } 1520 1521 public AgendaItemBo getChild() { 1522 return accessor.getChild(instance); 1523 } 1524 1525 public AgendaItemBo getInstance() { return instance; } 1526 } 1527 1528 /** 1529 * <p>This class abstracts getting and setting a child of an AgendaItemBo, making some recursive operations 1530 * require less boiler plate.</p> 1531 * 1532 * <p>The word 'child' in AgendaItemChildAccessor means child in the strict data structures sense, in that the 1533 * instance passed in holds a reference to some other node (or null). However, when discussing the agenda tree 1534 * and algorithms for manipulating it, the meaning of 'child' is somewhat different, and there are notions of 1535 * 'sibling' and 'cousin' that are tossed about too. It's probably worth explaining that somewhat here:</p> 1536 * 1537 * <p>General principals of relationships when talking about the agenda tree: 1538 * <ul> 1539 * <li>Generation boundaries (parent to child) are across 'When TRUE' and 'When FALSE' references.</li> 1540 * <li>"Age" among siblings & cousins goes from top (oldest) to bottom (youngest).</li> 1541 * <li>siblings are related by 'Always' references.</li> 1542 * </ul> 1543 * </p> 1544 * <p>This diagram of an agenda tree and the following examples seek to illustrate these principals:</p> 1545 * <img src="doc-files/AgendaEditorController-1.png" alt="Example Agenda Items"/> 1546 * <p>Examples: 1547 * <ul> 1548 * <li>A is the parent of B, C, & D</li> 1549 * <li>E is the younger sibling of A</li> 1550 * <li>B is the older cousin of C</li> 1551 * <li>C is the older sibling of D</li> 1552 * <li>F is the younger cousin of D</li> 1553 * </ul> 1554 * </p> 1555 */ 1556 protected static class AgendaItemChildAccessor { 1557 1558 private enum Child { WHEN_TRUE, WHEN_FALSE, ALWAYS }; 1559 1560 private static final AgendaItemChildAccessor whenTrue = new AgendaItemChildAccessor(Child.WHEN_TRUE); 1561 private static final AgendaItemChildAccessor whenFalse = new AgendaItemChildAccessor(Child.WHEN_FALSE); 1562 private static final AgendaItemChildAccessor always = new AgendaItemChildAccessor(Child.ALWAYS); 1563 1564 /** 1565 * Accessors for all linked items 1566 */ 1567 private static final AgendaItemChildAccessor [] linkedNodes = { whenTrue, whenFalse, always }; 1568 1569 /** 1570 * Accessors for children (so ALWAYS is omitted); 1571 */ 1572 private static final AgendaItemChildAccessor [] children = { whenTrue, whenFalse }; 1573 1574 private final Child whichChild; 1575 1576 private AgendaItemChildAccessor(Child whichChild) { 1577 if (whichChild == null) throw new IllegalArgumentException("whichChild must be non-null"); 1578 this.whichChild = whichChild; 1579 } 1580 1581 /** 1582 * @return the referenced child 1583 */ 1584 public AgendaItemBo getChild(AgendaItemBo parent) { 1585 switch (whichChild) { 1586 case WHEN_TRUE: return parent.getWhenTrue(); 1587 case WHEN_FALSE: return parent.getWhenFalse(); 1588 case ALWAYS: return parent.getAlways(); 1589 default: throw new IllegalStateException(); 1590 } 1591 } 1592 1593 /** 1594 * Sets the child reference and the child id 1595 */ 1596 public void setChild(AgendaItemBo parent, AgendaItemBo child) { 1597 switch (whichChild) { 1598 case WHEN_TRUE: 1599 parent.setWhenTrue(child); 1600 parent.setWhenTrueId(child == null ? null : child.getId()); 1601 break; 1602 case WHEN_FALSE: 1603 parent.setWhenFalse(child); 1604 parent.setWhenFalseId(child == null ? null : child.getId()); 1605 break; 1606 case ALWAYS: 1607 parent.setAlways(child); 1608 parent.setAlwaysId(child == null ? null : child.getId()); 1609 break; 1610 default: throw new IllegalStateException(); 1611 } 1612 } 1613 } 1614 // 1615 // Rule Editor Controller methods 1616 // 1617 @RequestMapping(params = "methodToCall=" + "copyRule") 1618 public ModelAndView copyRule(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 1619 HttpServletRequest request, HttpServletResponse response) throws Exception { 1620 1621 AgendaEditor agendaEditor = getAgendaEditor(form); 1622 String name = agendaEditor.getCopyRuleName(); 1623 String namespace = agendaEditor.getNamespace(); 1624 // fetch existing rule and copy fields to new rule 1625 1626 final String copyRuleNameErrorPropertyName = "AgendaEditorView-AddRule-Page"; //"copyRuleName", 1627 if (StringUtils.isBlank(name)) { 1628 GlobalVariables.getMessageMap().putError(copyRuleNameErrorPropertyName, "error.rule.missingCopyRuleName"); 1629 return super.refresh(form, result, request, response); 1630 } 1631 1632 RuleDefinition oldRuleDefinition = getRuleBoService().getRuleByNameAndNamespace(name, namespace); 1633 1634 if (oldRuleDefinition == null) { 1635 GlobalVariables.getMessageMap().putError(copyRuleNameErrorPropertyName, "error.rule.invalidCopyRuleName", namespace + ":" + name); 1636 return super.refresh(form, result, request, response); 1637 } 1638 1639 RuleBo oldRule = RuleBo.from(oldRuleDefinition); 1640 RuleBo newRule = RuleBo.copyRule(oldRule); 1641 agendaEditor.getAgendaItemLine().setRule( newRule ); 1642 // hack to set ui action object to first action in the list 1643 if (!newRule.getActions().isEmpty()) { 1644 agendaEditor.setAgendaItemLineRuleAction( newRule.getActions().get(0)); 1645 } 1646 return super.refresh(form, result, request, response); 1647 } 1648 1649 1650 /** 1651 * This method starts an edit proposition. 1652 */ 1653 @RequestMapping(params = "methodToCall=" + "goToEditProposition") 1654 public ModelAndView goToEditProposition(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 1655 HttpServletRequest request, HttpServletResponse response) throws Exception { 1656 1657 // open the selected node for editing 1658 AgendaEditor agendaEditor = getAgendaEditor(form); 1659 RuleBo rule = agendaEditor.getAgendaItemLine().getRule(); 1660 String selectedPropId = agendaEditor.getSelectedPropositionId(); 1661 1662 Node<RuleTreeNode,String> root = rule.getPropositionTree().getRootElement(); 1663 PropositionBo propositionToToggleEdit = null; 1664 boolean newEditMode = true; 1665 1666 // find parent 1667 Node<RuleTreeNode,String> parent = findParentPropositionNode( root, selectedPropId); 1668 if (parent != null){ 1669 List<Node<RuleTreeNode,String>> children = parent.getChildren(); 1670 for( int index=0; index< children.size(); index++){ 1671 Node<RuleTreeNode,String> child = children.get(index); 1672 if (propIdMatches(child, selectedPropId)){ 1673 PropositionBo prop = child.getData().getProposition(); 1674 propositionToToggleEdit = prop; 1675 newEditMode = !prop.getEditMode(); 1676 break; 1677 } else { 1678 child.getData().getProposition().setEditMode(false); 1679 } 1680 } 1681 } 1682 1683 resetEditModeOnPropositionTree(root); 1684 if (propositionToToggleEdit != null) { 1685 propositionToToggleEdit.setEditMode(newEditMode); 1686 //refresh the tree 1687 rule.refreshPropositionTree(null); 1688 } 1689 1690 return super.updateComponent(form, result, request, response); 1691 } 1692 1693 @RequestMapping(params = "methodToCall=" + "addProposition") 1694 public ModelAndView addProposition(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 1695 HttpServletRequest request, HttpServletResponse response) throws Exception { 1696 1697 AgendaEditor agendaEditor = getAgendaEditor(form); 1698 RuleBo rule = agendaEditor.getAgendaItemLine().getRule(); 1699 String selectedPropId = agendaEditor.getSelectedPropositionId(); 1700 1701 1702 // find parent 1703 Node<RuleTreeNode,String> root = agendaEditor.getAgendaItemLine().getRule().getPropositionTree().getRootElement(); 1704 Node<RuleTreeNode,String> parent = findParentPropositionNode( root, selectedPropId); 1705 1706 resetEditModeOnPropositionTree(root); 1707 1708 // add new child at appropriate spot 1709 if (parent != null){ 1710 List<Node<RuleTreeNode,String>> children = parent.getChildren(); 1711 for( int index=0; index< children.size(); index++){ 1712 Node<RuleTreeNode,String> child = children.get(index); 1713 1714 // if our selected node is a simple proposition, add a new one after 1715 if (propIdMatches(child, selectedPropId)){ 1716 // handle special case of adding to a lone simple proposition. 1717 // in this case, we need to change the root level proposition to a compound proposition 1718 // move the existing simple proposition as the first compound component, 1719 // then add a new blank simple prop as the second compound component. 1720 if (parent == root && 1721 (SimplePropositionNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType()) || 1722 SimplePropositionEditNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType()))){ 1723 1724 // create a new compound proposition 1725 PropositionBo compound = PropositionBo.createCompoundPropositionBoStub(child.getData().getProposition(), true); 1726 // don't set compound.setEditMode(true) as the Simple Prop in the compound prop is the only prop in edit mode 1727 rule.setProposition(compound); 1728 rule.refreshPropositionTree(null); 1729 } 1730 // handle regular case of adding a simple prop to an existing compound prop 1731 else if(SimplePropositionNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType()) || 1732 SimplePropositionEditNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType())){ 1733 1734 // build new Blank Proposition 1735 PropositionBo blank = PropositionBo.createSimplePropositionBoStub(child.getData().getProposition(),PropositionType.SIMPLE.getCode()); 1736 //add it to the parent 1737 PropositionBo parentProp = parent.getData().getProposition(); 1738 parentProp.getCompoundComponents().add(((index/2)+1), blank); 1739 1740 rule.refreshPropositionTree(true); 1741 } 1742 1743 break; 1744 } 1745 } 1746 } else { 1747 // special case, if root has no children, add a new simple proposition 1748 // todo: how to add compound proposition. - just add another to the firs simple 1749 if (root.getChildren().isEmpty()){ 1750 PropositionBo blank = PropositionBo.createSimplePropositionBoStub(null,PropositionType.SIMPLE.getCode()); 1751 blank.setRuleId(rule.getId()); 1752 rule.setPropId(blank.getId()); 1753 rule.setProposition(blank); 1754 rule.refreshPropositionTree(true); 1755 } 1756 } 1757 return super.updateComponent(form, result, request, response); 1758 } 1759 1760 /** 1761 * 1762 * This method adds an opCode Node to separate components in a compound proposition. 1763 * 1764 * @param currentNode 1765 * @param prop 1766 * @return 1767 */ 1768 private void addOpCodeNode(Node currentNode, PropositionBo prop, int index){ 1769 String opCodeLabel = ""; 1770 1771 if (LogicalOperator.AND.getCode().equalsIgnoreCase(prop.getCompoundOpCode())){ 1772 opCodeLabel = "AND"; 1773 } else if (LogicalOperator.OR.getCode().equalsIgnoreCase(prop.getCompoundOpCode())){ 1774 opCodeLabel = "OR"; 1775 } 1776 Node<RuleTreeNode, String> aNode = new Node<RuleTreeNode, String>(); 1777 aNode.setNodeLabel(""); 1778 aNode.setNodeType("ruleTreeNode compoundOpCodeNode"); 1779 aNode.setData(new CompoundOpCodeNode(prop)); 1780 currentNode.insertChildAt(index, aNode); 1781 } 1782 1783 1784 private boolean propIdMatches(Node<RuleTreeNode, String> node, String propId){ 1785 if (propId!=null && node != null && node.getData() != null && propId.equalsIgnoreCase(node.getData().getProposition().getId())) { 1786 return true; 1787 } 1788 return false; 1789 } 1790 1791 /** 1792 * disable edit mode for all Nodes beneath and including the passed in Node 1793 * @param currentNode 1794 */ 1795 private void resetEditModeOnPropositionTree(Node<RuleTreeNode, String> currentNode){ 1796 if (currentNode.getData() != null){ 1797 RuleTreeNode dataNode = currentNode.getData(); 1798 dataNode.getProposition().setEditMode(false); 1799 } 1800 List<Node<RuleTreeNode,String>> children = currentNode.getChildren(); 1801 for( Node<RuleTreeNode,String> child : children){ 1802 resetEditModeOnPropositionTree(child); 1803 } 1804 } 1805 1806 private Node<RuleTreeNode, String> findPropositionTreeNode(Node<RuleTreeNode, String> currentNode, String selectedPropId){ 1807 Node<RuleTreeNode,String> bingo = null; 1808 if (currentNode.getData() != null){ 1809 RuleTreeNode dataNode = currentNode.getData(); 1810 if (selectedPropId.equalsIgnoreCase(dataNode.getProposition().getId())){ 1811 return currentNode; 1812 } 1813 } 1814 List<Node<RuleTreeNode,String>> children = currentNode.getChildren(); 1815 for( Node<RuleTreeNode,String> child : children){ 1816 bingo = findPropositionTreeNode(child, selectedPropId); 1817 if (bingo != null) break; 1818 } 1819 return bingo; 1820 } 1821 1822 private Node<RuleTreeNode, String> findParentPropositionNode(Node<RuleTreeNode, String> currentNode, String selectedPropId){ 1823 Node<RuleTreeNode,String> bingo = null; 1824 if (selectedPropId != null) { 1825 // if it's in children, we have the parent 1826 List<Node<RuleTreeNode,String>> children = currentNode.getChildren(); 1827 for( Node<RuleTreeNode,String> child : children){ 1828 RuleTreeNode dataNode = child.getData(); 1829 if (selectedPropId.equalsIgnoreCase(dataNode.getProposition().getId())) 1830 return currentNode; 1831 } 1832 1833 // if not found check grandchildren 1834 for( Node<RuleTreeNode,String> kid : children){ 1835 bingo = findParentPropositionNode(kid, selectedPropId); 1836 if (bingo != null) break; 1837 } 1838 } 1839 return bingo; 1840 } 1841 1842 /** 1843 * This method return the index of the position of the child that matches the id 1844 * @param parent 1845 * @param propId 1846 * @return index if found, -1 if not found 1847 */ 1848 private int findChildIndex(Node<RuleTreeNode,String> parent, String propId){ 1849 int index; 1850 List<Node<RuleTreeNode,String>> children = parent.getChildren(); 1851 for(index=0; index< children.size(); index++){ 1852 Node<RuleTreeNode,String> child = children.get(index); 1853 // if our selected node is a simple proposition, add a new one after 1854 if (propIdMatches(child, propId)){ 1855 return index; 1856 } 1857 } 1858 return -1; 1859 } 1860 1861 @RequestMapping(params = "methodToCall=" + "movePropositionUp") 1862 public ModelAndView movePropositionUp(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 1863 HttpServletRequest request, HttpServletResponse response) 1864 throws Exception { 1865 moveSelectedProposition(form, true); 1866 1867 return super.updateComponent(form, result, request, response); 1868 } 1869 1870 @RequestMapping(params = "methodToCall=" + "movePropositionDown") 1871 public ModelAndView movePropositionDown(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 1872 HttpServletRequest request, HttpServletResponse response) 1873 throws Exception { 1874 moveSelectedProposition(form, false); 1875 1876 return super.updateComponent(form, result, request, response); 1877 } 1878 1879 private void moveSelectedProposition(UifFormBase form, boolean up) { 1880 1881 /* Rough algorithm for moving a node up. 1882 * 1883 * find the following: 1884 * node := the selected node 1885 * parent := the selected node's parent, its containing node (via when true or when false relationship) 1886 * parentsOlderCousin := the parent's level-order predecessor (sibling or cousin) 1887 * 1888 */ 1889 AgendaEditor agendaEditor = getAgendaEditor(form); 1890 RuleBo rule = agendaEditor.getAgendaItemLine().getRule(); 1891 String selectedPropId = agendaEditor.getSelectedPropositionId(); 1892 1893 // find parent 1894 Node<RuleTreeNode,String> parent = findParentPropositionNode(rule.getPropositionTree().getRootElement(), selectedPropId); 1895 1896 // add new child at appropriate spot 1897 if (parent != null){ 1898 List<Node<RuleTreeNode,String>> children = parent.getChildren(); 1899 for( int index=0; index< children.size(); index++){ 1900 Node<RuleTreeNode,String> child = children.get(index); 1901 // if our selected node is a simple proposition, add a new one after 1902 if (propIdMatches(child, selectedPropId)){ 1903 if(SimplePropositionNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType()) || 1904 SimplePropositionEditNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType()) || 1905 RuleTreeNode.COMPOUND_NODE_TYPE.equalsIgnoreCase(child.getNodeType()) ){ 1906 1907 if (((index > 0) && up) || ((index <(children.size() - 1)&& !up))){ 1908 //remove it from its current spot 1909 PropositionBo parentProp = parent.getData().getProposition(); 1910 PropositionBo workingProp = parentProp.getCompoundComponents().remove(index/2); 1911 if (up){ 1912 parentProp.getCompoundComponents().add((index/2)-1, workingProp); 1913 }else{ 1914 parentProp.getCompoundComponents().add((index/2)+1, workingProp); 1915 } 1916 1917 // insert it in the new spot 1918 // redisplay the tree (editMode = true) 1919 boolean editMode = (SimplePropositionEditNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType())); 1920 rule.refreshPropositionTree(editMode); 1921 } 1922 } 1923 1924 break; 1925 } 1926 } 1927 } 1928 } 1929 @RequestMapping(params = "methodToCall=" + "movePropositionLeft") 1930 public ModelAndView movePropositionLeft(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 1931 HttpServletRequest request, HttpServletResponse response) 1932 throws Exception { 1933 1934 /* Rough algorithm for moving a node up. 1935 * 1936 * find the following: 1937 * node := the selected node 1938 * parent := the selected node's parent, its containing node (via when true or when false relationship) 1939 * parentsOlderCousin := the parent's level-order predecessor (sibling or cousin) 1940 * 1941 */ 1942 AgendaEditor agendaEditor = getAgendaEditor(form); 1943 RuleBo rule = agendaEditor.getAgendaItemLine().getRule(); 1944 String selectedPropId = agendaEditor.getSelectedPropositionId(); 1945 1946 // find agendaEditor.getAgendaItemLine().getRule().getPropositionTree().getRootElement()parent 1947 Node<RuleTreeNode,String> root = rule.getPropositionTree().getRootElement(); 1948 Node<RuleTreeNode,String> parent = findParentPropositionNode(root, selectedPropId); 1949 if ((parent != null) && (RuleTreeNode.COMPOUND_NODE_TYPE.equalsIgnoreCase(parent.getNodeType()))){ 1950 Node<RuleTreeNode,String> granny = findParentPropositionNode(root,parent.getData().getProposition().getId()); 1951 if (granny != root){ 1952 int oldIndex = findChildIndex(parent, selectedPropId); 1953 int newIndex = findChildIndex(granny, parent.getData().getProposition().getId()); 1954 if (oldIndex >= 0 && newIndex >= 0){ 1955 PropositionBo prop = parent.getData().getProposition().getCompoundComponents().remove(oldIndex/2); 1956 granny.getData().getProposition().getCompoundComponents().add((newIndex/2)+1, prop); 1957 rule.refreshPropositionTree(false); 1958 } 1959 } else { 1960 // TODO: do we allow moving up to the root? 1961 // we could add a new top level compound node, with current root as 1st child, 1962 // and move the node to the second child. 1963 } 1964 } 1965 return super.updateComponent(form, result, request, response); 1966 } 1967 1968 @RequestMapping(params = "methodToCall=" + "movePropositionRight") 1969 public ModelAndView movePropositionRight(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 1970 HttpServletRequest request, HttpServletResponse response) 1971 throws Exception { 1972 /* Rough algorithm for moving a node Right 1973 * if the selected node is above a compound proposition, move it into the compound proposition as the first child 1974 * if the node is above a simple proposition, do nothing. 1975 * find the following: 1976 * node := the selected node 1977 * parent := the selected node's parent, its containing node 1978 * nextSibling := the node after the selected node 1979 * 1980 */ 1981 AgendaEditor agendaEditor = getAgendaEditor(form); 1982 RuleBo rule = agendaEditor.getAgendaItemLine().getRule(); 1983 String selectedPropId = agendaEditor.getSelectedPropositionId(); 1984 1985 // find parent 1986 Node<RuleTreeNode,String> parent = findParentPropositionNode( 1987 rule.getPropositionTree().getRootElement(), selectedPropId); 1988 if (parent != null){ 1989 int index = findChildIndex(parent, selectedPropId); 1990 // if we are the last child, do nothing, otherwise 1991 if (index >= 0 && index+1 < parent.getChildren().size()){ 1992 Node<RuleTreeNode,String> child = parent.getChildren().get(index); 1993 Node<RuleTreeNode,String> nextSibling = parent.getChildren().get(index+2); 1994 // if selected node above a compound node, move it into it as first child 1995 if(RuleTreeNode.COMPOUND_NODE_TYPE.equalsIgnoreCase(nextSibling.getNodeType()) ){ 1996 // remove selected node from it's current spot 1997 PropositionBo prop = parent.getData().getProposition().getCompoundComponents().remove(index/2); 1998 // add it to it's siblings children 1999 nextSibling.getData().getProposition().getCompoundComponents().add(0, prop); 2000 rule.refreshPropositionTree(false); 2001 } 2002 } 2003 } 2004 return super.updateComponent(form, result, request, response); 2005 } 2006 2007 /** 2008 * introduces a new compound proposition between the selected proposition and its parent. 2009 * Additionally, it puts a new blank simple proposition underneath the compound proposition 2010 * as a sibling to the selected proposition. 2011 */ 2012 @RequestMapping(params = "methodToCall=" + "togglePropositionSimpleCompound") 2013 public ModelAndView togglePropositionSimpleCompound(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 2014 HttpServletRequest request, HttpServletResponse response) 2015 throws Exception { 2016 2017 AgendaEditor agendaEditor = getAgendaEditor(form); 2018 RuleBo rule = agendaEditor.getAgendaItemLine().getRule(); 2019 String selectedPropId = agendaEditor.getSelectedPropositionId(); 2020 2021 resetEditModeOnPropositionTree(rule.getPropositionTree().getRootElement()); 2022 2023 if (!StringUtils.isBlank(selectedPropId)) { 2024 // find parent 2025 Node<RuleTreeNode,String> parent = findParentPropositionNode( 2026 rule.getPropositionTree().getRootElement(), selectedPropId); 2027 if (parent != null){ 2028 2029 int index = findChildIndex(parent, selectedPropId); 2030 2031 PropositionBo propBo = parent.getChildren().get(index).getData().getProposition(); 2032 2033 // create a new compound proposition 2034 PropositionBo compound = PropositionBo.createCompoundPropositionBoStub(propBo, true); 2035 compound.setDescription("New Compound Proposition"); 2036 compound.setEditMode(false); 2037 2038 if (parent.getData() == null) { // SPECIAL CASE: this is the only proposition in the tree 2039 rule.setProposition(compound); 2040 } else { 2041 PropositionBo parentBo = parent.getData().getProposition(); 2042 List<PropositionBo> siblings = parentBo.getCompoundComponents(); 2043 2044 int propIndex = -1; 2045 for (int i=0; i<siblings.size(); i++) { 2046 if (propBo.getId().equals(siblings.get(i).getId())) { 2047 propIndex = i; 2048 break; 2049 } 2050 } 2051 2052 parentBo.getCompoundComponents().set(propIndex, compound); 2053 } 2054 } 2055 } 2056 2057 agendaEditor.getAgendaItemLine().getRule().refreshPropositionTree(true); 2058 return super.updateComponent(form, result, request, response); 2059 } 2060 2061 2062 @RequestMapping(params = "methodToCall=" + "cutProposition") 2063 public ModelAndView cutProposition(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 2064 HttpServletRequest request, HttpServletResponse response) 2065 throws Exception { 2066 2067 AgendaEditor agendaEditor = getAgendaEditor(form); 2068 String selectedPropId = agendaEditor.getSelectedPropositionId(); 2069 agendaEditor.setCutPropositionId(selectedPropId); 2070 2071 return super.updateComponent(form, result, request, response); 2072 } 2073 2074 @RequestMapping(params = "methodToCall=" + "pasteProposition") 2075 public ModelAndView pasteProposition(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 2076 HttpServletRequest request, HttpServletResponse response) 2077 throws Exception { 2078 2079 AgendaEditor agendaEditor = getAgendaEditor(form); 2080 RuleBo rule = agendaEditor.getAgendaItemLine().getRule(); 2081 2082 // get selected id 2083 String cutPropId = agendaEditor.getCutPropositionId(); 2084 String selectedPropId = agendaEditor.getSelectedPropositionId(); 2085 2086 if (StringUtils.isNotBlank(selectedPropId) && selectedPropId.equals(cutPropId)) { 2087 // do nothing; can't paste to itself 2088 } else { 2089 2090 // proposition tree root 2091 Node<RuleTreeNode, String> root = rule.getPropositionTree().getRootElement(); 2092 2093 if (StringUtils.isNotBlank(selectedPropId) && StringUtils.isNotBlank(cutPropId)) { 2094 Node<RuleTreeNode,String> parentNode = findParentPropositionNode(root, selectedPropId); 2095 PropositionBo newParent; 2096 if (parentNode == root){ 2097 // special case 2098 // build new top level compound proposition, 2099 // add existing as first child 2100 // then paste cut node as 2nd child 2101 newParent = PropositionBo.createCompoundPropositionBoStub2( 2102 root.getChildren().get(0).getData().getProposition()); 2103 newParent.setEditMode(true); 2104 rule.setProposition(newParent); 2105 } else { 2106 newParent = parentNode.getData().getProposition(); 2107 } 2108 PropositionBo oldParent = findParentPropositionNode(root, cutPropId).getData().getProposition(); 2109 2110 PropositionBo workingProp = null; 2111 // cut from old 2112 if (oldParent != null){ 2113 List <PropositionBo> children = oldParent.getCompoundComponents(); 2114 for( int index=0; index< children.size(); index++){ 2115 if (cutPropId.equalsIgnoreCase(children.get(index).getId())){ 2116 workingProp = oldParent.getCompoundComponents().remove(index); 2117 break; 2118 } 2119 } 2120 } 2121 2122 // add to new 2123 if (newParent != null && workingProp != null){ 2124 List <PropositionBo> children = newParent.getCompoundComponents(); 2125 for( int index=0; index< children.size(); index++){ 2126 if (selectedPropId.equalsIgnoreCase(children.get(index).getId())){ 2127 children.add(index+1, workingProp); 2128 break; 2129 } 2130 } 2131 } 2132 // TODO: determine edit mode. 2133 // boolean editMode = (SimplePropositionEditNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType())); 2134 rule.refreshPropositionTree(false); 2135 } 2136 } 2137 agendaEditor.setCutPropositionId(null); 2138 // call the super method to avoid the agenda tree being reloaded from the db 2139 return super.updateComponent(form, result, request, response); 2140 } 2141 2142 @RequestMapping(params = "methodToCall=" + "deleteProposition") 2143 public ModelAndView deleteProposition(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 2144 HttpServletRequest request, HttpServletResponse response) 2145 throws Exception { 2146 AgendaEditor agendaEditor = getAgendaEditor(form); 2147 String selectedPropId = agendaEditor.getSelectedPropositionId(); 2148 Node<RuleTreeNode, String> root = agendaEditor.getAgendaItemLine().getRule().getPropositionTree().getRootElement(); 2149 2150 Node<RuleTreeNode, String> parentNode = findParentPropositionNode(root, selectedPropId); 2151 2152 // what if it is the root? 2153 if (parentNode != null && parentNode.getData() != null) { // it is not the root as there is a parent w/ a prop 2154 PropositionBo parent = parentNode.getData().getProposition(); 2155 if (parent != null){ 2156 List <PropositionBo> children = parent.getCompoundComponents(); 2157 for( int index=0; index< children.size(); index++){ 2158 if (selectedPropId.equalsIgnoreCase(children.get(index).getId())){ 2159 parent.getCompoundComponents().remove(index); 2160 break; 2161 } 2162 } 2163 } 2164 } else { // no parent, it is the root 2165 if (ObjectUtils.isNotNull(parentNode)) { 2166 parentNode.getChildren().clear(); 2167 agendaEditor.getAgendaItemLine().getRule().getPropositionTree().setRootElement(null); 2168 agendaEditor.getAgendaItemLine().getRule().setPropId(null); 2169 agendaEditor.getAgendaItemLine().getRule().setProposition(null); 2170 } else { 2171 GlobalVariables.getMessageMap().putError(KRMSPropertyConstants.Rule.PROPOSITION_TREE_GROUP_ID, 2172 "error.rule.proposition.noneHighlighted"); 2173 } 2174 } 2175 2176 agendaEditor.getAgendaItemLine().getRule().refreshPropositionTree(false); 2177 return super.updateComponent(form, result, request, response); 2178 } 2179 2180 @RequestMapping(params = "methodToCall=" + "updateCompoundOperator") 2181 public ModelAndView updateCompoundOperator(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 2182 HttpServletRequest request, HttpServletResponse response) 2183 throws Exception { 2184 2185 AgendaEditor agendaEditor = getAgendaEditor(form); 2186 RuleBo rule = agendaEditor.getAgendaItemLine().getRule(); 2187 rule.refreshPropositionTree(false); 2188 2189 return super.updateComponent(form, result, request, response); 2190 } 2191 2192 }