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