View Javadoc

1   /**
2    * Copyright 2005-2011 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krms.impl.ui;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.core.api.util.tree.Node;
20  import org.kuali.rice.krad.document.MaintenanceDocument;
21  import org.kuali.rice.krad.service.KRADServiceLocator;
22  import org.kuali.rice.krad.service.SequenceAccessorService;
23  import org.kuali.rice.krad.uif.UifParameters;
24  import org.kuali.rice.krad.util.ObjectUtils;
25  import org.kuali.rice.krad.web.controller.MaintenanceDocumentController;
26  import org.kuali.rice.krad.web.form.MaintenanceForm;
27  import org.kuali.rice.krad.web.form.UifFormBase;
28  import org.kuali.rice.krms.api.repository.type.KrmsAttributeDefinition;
29  import org.kuali.rice.krms.impl.repository.ActionAttributeBo;
30  import org.kuali.rice.krms.impl.repository.ActionBo;
31  import org.kuali.rice.krms.api.repository.LogicalOperator;
32  import org.kuali.rice.krms.api.repository.proposition.PropositionType;
33  import org.kuali.rice.krms.impl.repository.AgendaBo;
34  import org.kuali.rice.krms.impl.repository.AgendaItemBo;
35  import org.kuali.rice.krms.impl.repository.ContextBoService;
36  import org.kuali.rice.krms.impl.repository.KrmsAttributeDefinitionBo;
37  import org.kuali.rice.krms.impl.repository.KrmsAttributeDefinitionService;
38  import org.kuali.rice.krms.impl.repository.KrmsRepositoryServiceLocator;
39  import org.kuali.rice.krms.impl.repository.PropositionBo;
40  import org.kuali.rice.krms.impl.repository.RuleBo;
41  import org.kuali.rice.krms.impl.rule.AgendaEditorBusRule;
42  import org.springframework.stereotype.Controller;
43  import org.springframework.validation.BindingResult;
44  import org.springframework.web.bind.annotation.ModelAttribute;
45  import org.springframework.web.bind.annotation.RequestMapping;
46  import org.springframework.web.servlet.ModelAndView;
47  
48  import javax.servlet.http.HttpServletRequest;
49  import javax.servlet.http.HttpServletResponse;
50  import java.util.ArrayList;
51  import java.util.HashMap;
52  import java.util.HashSet;
53  import java.util.List;
54  import java.util.Map;
55  import java.util.Set;
56  
57  /**
58   * Controller for the Test UI Page
59   * @author Kuali Rice Team (rice.collab@kuali.org)
60   */
61  @Controller
62  @RequestMapping(value = org.kuali.rice.krms.impl.util.KrmsImplConstants.WebPaths.AGENDA_EDITOR_PATH)
63  public class AgendaEditorController extends MaintenanceDocumentController {
64  
65      private SequenceAccessorService sequenceAccessorService;
66  
67      /**
68       * This overridden method does extra work on refresh to update the namespace when the context has been changed.
69       *
70       * @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)
71       */
72      @RequestMapping(params = "methodToCall=" + "refresh")
73      @Override
74      public ModelAndView refresh(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
75              HttpServletRequest request, HttpServletResponse response) throws Exception {
76          ModelAndView modelAndView = super.refresh(form, result, request, response);
77  
78          // handle return from context lookup and update the namespace on all agenda related objects if the contest has been changed
79          MaintenanceForm maintenanceForm = (MaintenanceForm) form;
80          AgendaEditor agendaEditor = ((AgendaEditor) maintenanceForm.getDocument().getNewMaintainableObject().getDataObject());
81          if (!StringUtils.equals(agendaEditor.getOldContextId(), agendaEditor.getAgenda().getContextId())) {
82              agendaEditor.setOldContextId(agendaEditor.getAgenda().getContextId());
83  
84              String namespace = "";
85              if (!StringUtils.isBlank(agendaEditor.getAgenda().getContextId())) {
86                  namespace = getContextBoService().getContextByContextId(agendaEditor.getAgenda().getContextId()).getNamespace();
87              }
88  
89              for (AgendaItemBo agendaItem : agendaEditor.getAgenda().getItems()) {
90                  agendaItem.getRule().setNamespace(namespace);
91                  for (ActionBo action : agendaItem.getRule().getActions()) {
92                      action.setNamespace(namespace);
93                  }
94              }
95          }
96          return modelAndView;
97      }
98  
99      /**
100      * This method updates the existing rule in the agenda.
101      */
102     @RequestMapping(params = "methodToCall=" + "goToAddRule")
103     public ModelAndView goToAddRule(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
104             HttpServletRequest request, HttpServletResponse response) throws Exception {
105         setAgendaItemLine(form, null);
106 
107         form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-AddRule-Page");
108         return super.navigate(form, result, request, response);
109     }
110 
111     /**
112      * This method sets the agendaItemLine for adding/editing AgendaItems.
113      * The agendaItemLine is a copy of the agendaItem so that changes are not applied when
114      * they are abandoned.  If the agendaItem is null a new empty agendaItemLine is created.
115      *
116      * @param form
117      * @param agendaItem
118      */
119     private void setAgendaItemLine(UifFormBase form, AgendaItemBo agendaItem) {
120         AgendaEditor agendaEditor = getAgendaEditor(form);
121         if (agendaItem == null) {
122             RuleBo rule = new RuleBo();
123             rule.setId(getSequenceAccessorService().getNextAvailableSequenceNumber("KRMS_RULE_S")
124                     .toString());
125             if (StringUtils.isBlank(agendaEditor.getAgenda().getContextId())) {
126                 rule.setNamespace("");
127             } else {
128                 rule.setNamespace(getContextBoService().getContextByContextId(agendaEditor.getAgenda().getContextId()).getNamespace());
129             }
130             agendaItem = new AgendaItemBo();
131             agendaItem.setRule(rule);
132             agendaEditor.setAgendaItemLine(agendaItem);
133         } else {
134             // TODO: Add a copy not the reference
135             agendaEditor.setAgendaItemLine((AgendaItemBo) ObjectUtils.deepCopy(agendaItem));
136         }
137 
138 
139         if (agendaItem.getRule().getActions().isEmpty()) {
140             ActionBo actionBo = new ActionBo();
141             actionBo.setTypeId("");
142             actionBo.setNamespace(agendaItem.getRule().getNamespace());
143             actionBo.setRuleId(agendaItem.getRule().getId());
144             actionBo.setSequenceNumber(1);
145             agendaEditor.setAgendaItemLineRuleAction(actionBo);
146         } else {
147             agendaEditor.setAgendaItemLineRuleAction(agendaItem.getRule().getActions().get(0));
148         }
149 
150         agendaEditor.setCustomRuleActionAttributesMap(agendaEditor.getAgendaItemLineRuleAction().getAttributes());
151     }
152 
153     /**
154      * This method returns the agendaItemLine from adding/editing AgendaItems.
155      *
156      * @param form
157      * @return agendaItem
158      */
159     private AgendaItemBo getAgendaItemLine(UifFormBase form) {
160         AgendaEditor agendaEditor = getAgendaEditor(form);
161         return agendaEditor.getAgendaItemLine();
162     }
163 
164     /**
165      * This method returns the id of the selected agendaItem.
166      *
167      * @param form
168      * @return selectedAgendaItemId
169      */
170     private String getSelectedAgendaItemId(UifFormBase form) {
171         AgendaEditor agendaEditor = getAgendaEditor(form);
172         return agendaEditor.getSelectedAgendaItemId();
173     }
174 
175     /**
176      * This method sets the id of the cut agendaItem.
177      *
178      * @param form
179      * @param cutAgendaItemId
180      */
181     private void setCutAgendaItemId(UifFormBase form, String cutAgendaItemId) {
182         AgendaEditor agendaEditor = getAgendaEditor(form);
183         agendaEditor.setCutAgendaItemId(cutAgendaItemId);
184     }
185 
186     /**
187      * This method returns the id of the cut agendaItem.
188      *
189      * @param form
190      * @return cutAgendaItemId
191      */
192     private String getCutAgendaItemId(UifFormBase form) {
193         AgendaEditor agendaEditor = getAgendaEditor(form);
194         return agendaEditor.getCutAgendaItemId();
195     }
196 
197     /**
198      * This method updates the existing rule in the agenda.
199      */
200     @RequestMapping(params = "methodToCall=" + "goToEditRule")
201     public ModelAndView goToEditRule(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
202             HttpServletRequest request, HttpServletResponse response) throws Exception {
203 
204         AgendaEditor agendaEditor = getAgendaEditor(form);
205         // this is the root of the tree:
206         AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda());
207         String selectedItemId = agendaEditor.getSelectedAgendaItemId();
208         AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId);
209 
210         setAgendaItemLine(form, node);
211 
212         form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-EditRule-Page");
213         return super.navigate(form, result, request, response);
214     }
215 
216     /**
217      *  This method adds the newly create rule to the agenda.
218      */
219     @RequestMapping(params = "methodToCall=" + "addRule")
220     public ModelAndView addRule(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
221             HttpServletRequest request, HttpServletResponse response) throws Exception {
222 
223         AgendaEditor agendaEditor = getAgendaEditor(form);
224         AgendaBo agenda = agendaEditor.getAgenda();
225         AgendaItemBo newAgendaItem = agendaEditor.getAgendaItemLine();
226 
227         updateRuleAction(agendaEditor);
228 
229         if (agenda.getItems() == null) {
230             agenda.setItems(new ArrayList<AgendaItemBo>());
231         }
232 
233         AgendaEditorBusRule rule = new AgendaEditorBusRule();
234         MaintenanceForm maintenanceForm = (MaintenanceForm) form;
235         MaintenanceDocument document = maintenanceForm.getDocument();
236         if (rule.processAgendaItemBusinessRules(document)) {
237             newAgendaItem.setId(getSequenceAccessorService().getNextAvailableSequenceNumber("KRMS_AGENDA_ITM_S")
238                     .toString());
239             newAgendaItem.setAgendaId(getCreateAgendaId(agenda));
240             if (agenda.getFirstItemId() == null) {
241                 agenda.setFirstItemId(newAgendaItem.getId());
242             } else {
243                 // insert agenda in tree
244                 String selectedAgendaItemId = getSelectedAgendaItemId(form);
245                 if (StringUtils.isBlank(selectedAgendaItemId)) {
246                     // add after the last root node
247                     AgendaItemBo node = getFirstAgendaItem(agenda);
248                     while (node.getAlways() != null) {
249                         node = node.getAlways();
250                     }
251                     node.setAlwaysId(newAgendaItem.getId());
252                     node.setAlways(newAgendaItem);
253                 } else {
254                     // add after selected node
255                     AgendaItemBo firstItem = getFirstAgendaItem(agenda);
256                     AgendaItemBo node = getAgendaItemById(firstItem, selectedAgendaItemId);
257                     newAgendaItem.setAlwaysId(node.getAlwaysId());
258                     newAgendaItem.setAlways(node.getAlways());
259                     node.setAlwaysId(newAgendaItem.getId());
260                     node.setAlways(newAgendaItem);
261                 }
262             }
263             // add it to the collection on the agenda too
264             agenda.getItems().add(newAgendaItem);
265 
266             form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-Agenda-Page");
267         } else {
268             form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-AddRule-Page");
269         }
270         return super.navigate(form, result, request, response);
271     }
272 
273     /**
274      * This method returns the agendaId of the given agenda.  If the agendaId is null a new id will be created.
275      */
276     private String getCreateAgendaId(AgendaBo agenda) {
277         if (agenda.getId() == null) {
278             agenda.setId(getSequenceAccessorService().getNextAvailableSequenceNumber("KRMS_AGENDA_S").toString());
279         }
280         return agenda.getId();
281     }
282 
283     private void updateRuleAction(AgendaEditor agendaEditor) {
284         agendaEditor.getAgendaItemLine().getRule().setActions(new ArrayList<ActionBo>());
285         if (StringUtils.isNotBlank(agendaEditor.getAgendaItemLineRuleAction().getTypeId())) {
286             updateRuleActionAttributes(agendaEditor.getCustomRuleActionAttributesMap(), agendaEditor.getAgendaItemLineRuleAction());
287             agendaEditor.getAgendaItemLine().getRule().getActions().add(agendaEditor.getAgendaItemLineRuleAction());
288         }
289     }
290 
291     private void updateRuleActionAttributes(Map<String, String> customRuleActionAttributeMap, ActionBo action) {
292         Set<ActionAttributeBo> attributes = new HashSet<ActionAttributeBo>();
293 
294         Map<String, KrmsAttributeDefinition> attributeDefinitionMap = buildAttributeDefinitionMap(action.getTypeId());
295 
296         for (Map.Entry<String, String> entry : customRuleActionAttributeMap.entrySet()) {
297             KrmsAttributeDefinition attrDef = attributeDefinitionMap.get(entry.getKey()); // get the definition from our map
298 
299             if (attrDef != null) {
300                 ActionAttributeBo attributeBo = new ActionAttributeBo();
301                 attributeBo.setActionId(action.getId());
302                 attributeBo.setAttributeDefinitionId(attrDef.getId());
303                 attributeBo.setValue(entry.getValue());
304                 attributeBo.setAttributeDefinition(KrmsAttributeDefinitionBo.from(attrDef));
305                 attributes.add(attributeBo);
306             }
307         }
308         action.setAttributeBos(attributes);
309     }
310 
311     /**
312      * Build a map from attribute name to attribute definition from all the defined attribute definitions for the
313      * specified rule action type
314      * @param actionTypeId
315      * @return
316      */
317     private Map<String, KrmsAttributeDefinition> buildAttributeDefinitionMap(String actionTypeId) {
318         KrmsAttributeDefinitionService attributeDefinitionService =
319             KrmsRepositoryServiceLocator.getKrmsAttributeDefinitionService();
320 
321         // build a map from attribute name to definition
322         Map<String, KrmsAttributeDefinition> attributeDefinitionMap = new HashMap<String, KrmsAttributeDefinition>();
323 
324         List<KrmsAttributeDefinition> attributeDefinitions =
325                 attributeDefinitionService.findAttributeDefinitionsByType(actionTypeId);
326 
327         for (KrmsAttributeDefinition attributeDefinition : attributeDefinitions) {
328             attributeDefinitionMap.put(attributeDefinition.getName(), attributeDefinition);
329         }
330         return attributeDefinitionMap;
331     }
332 
333     /**
334      * This method updates the existing rule in the agenda.
335      */
336     @RequestMapping(params = "methodToCall=" + "editRule")
337     public ModelAndView editRule(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
338             HttpServletRequest request, HttpServletResponse response) throws Exception {
339         AgendaEditor agendaEditor = getAgendaEditor(form);
340         // this is the root of the tree:
341         AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda());
342         AgendaItemBo node = getAgendaItemById(firstItem, getSelectedAgendaItemId(form));
343         AgendaItemBo agendaItemLine = getAgendaItemLine(form);
344 
345         updateRuleAction(agendaEditor);
346 
347         AgendaEditorBusRule rule = new AgendaEditorBusRule();
348         MaintenanceForm maintenanceForm = (MaintenanceForm) form;
349         MaintenanceDocument document = maintenanceForm.getDocument();
350         if (rule.processAgendaItemBusinessRules(document)) {
351             node.setRule(agendaItemLine.getRule());
352             form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-Agenda-Page");
353         } else {
354             form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-EditRule-Page");
355         }
356         return super.navigate(form, result, request, response);
357     }
358 
359     /**
360      * @return the ALWAYS {@link AgendaItemInstanceChildAccessor} for the last ALWAYS child of the instance accessed by the parameter.
361      * It will by definition refer to null.  If the instanceAccessor parameter refers to null, then it will be returned.  This is useful
362      * for adding a youngest child to a sibling group.
363      */
364     private AgendaItemInstanceChildAccessor getLastChildsAlwaysAccessor(AgendaItemInstanceChildAccessor instanceAccessor) {
365         AgendaItemBo next = instanceAccessor.getChild();
366         if (next == null) return instanceAccessor;
367         while (next.getAlways() != null) { next = next.getAlways(); };
368         return new AgendaItemInstanceChildAccessor(AgendaItemChildAccessor.always, next);
369     }
370 
371     /**
372      * @return the accessor to the child with the given agendaItemId under the given parent.  This method will search both When TRUE and 
373      * When FALSE sibling groups.  If the instance with the given id is not found, null is returned.
374      * @see AgendaItemChildAccessor for nomenclature explanation
375      */
376     private AgendaItemInstanceChildAccessor getInstanceAccessorToChild(AgendaItemBo parent, String agendaItemId) {
377 
378         // first try When TRUE, then When FALSE via AgendaItemChildAccessor.levelOrderChildren
379         for (AgendaItemChildAccessor levelOrderChildAccessor : AgendaItemChildAccessor.children) {
380 
381             AgendaItemBo next = levelOrderChildAccessor.getChild(parent);
382             
383             // if the first item matches, return the accessor from the parent
384             if (next != null && agendaItemId.equals(next.getId())) return new AgendaItemInstanceChildAccessor(levelOrderChildAccessor, parent);
385 
386             // otherwise walk the children
387             while (next != null && next.getAlwaysId() != null) {
388                 if (next.getAlwaysId().equals(agendaItemId)) return new AgendaItemInstanceChildAccessor(AgendaItemChildAccessor.always, next);
389                 // move down
390                 next = next.getAlways();
391             }
392         }
393         
394         return null;
395     }
396 
397     @RequestMapping(params = "methodToCall=" + "ajaxRefresh")
398     public ModelAndView ajaxRefresh(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
399             HttpServletRequest request, HttpServletResponse response)
400             throws Exception {
401         // call the super method to avoid the agenda tree being reloaded from the db
402         return super.updateComponent(form, result, request, response);
403     }
404 
405     @RequestMapping(params = "methodToCall=" + "moveUp")
406     public ModelAndView moveUp(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
407             HttpServletRequest request, HttpServletResponse response)
408             throws Exception {
409         moveSelectedSubtreeUp(form);
410 
411         return super.refresh(form, result, request, response);
412     }
413 
414     @RequestMapping(params = "methodToCall=" + "ajaxMoveUp")
415     public ModelAndView ajaxMoveUp(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
416             HttpServletRequest request, HttpServletResponse response)
417             throws Exception {
418         moveSelectedSubtreeUp(form);
419 
420         // call the super method to avoid the agenda tree being reloaded from the db
421         return super.updateComponent(form, result, request, response);
422     }
423 
424     /**
425      *
426      * @param form
427      * @see AgendaItemChildAccessor for nomenclature explanation
428      */
429     private void moveSelectedSubtreeUp(UifFormBase form) {
430 
431         /* Rough algorithm for moving a node up.  This is a "level order" move.  Note that in this tree,
432          * level order means something a bit funky.  We are defining a level as it would be displayed in the browser,
433          * so only the traversal of When FALSE or When TRUE links increments the level, since ALWAYS linked nodes are
434          * considered siblings.
435          *
436          * find the following:
437          *   node := the selected node
438          *   parent := the selected node's parent, its containing node (via when true or when false relationship)
439          *   parentsOlderCousin := the parent's level-order predecessor (sibling or cousin)
440          *
441          * if (node is first child in sibling group)
442          *     if (node is in When FALSE group)
443          *         move node to last position in When TRUE group
444          *     else
445          *         find youngest child of parentsOlderCousin and put node after it
446          * else
447          *     move node up within its sibling group
448          */
449 
450         AgendaEditor agendaEditor = getAgendaEditor(form);
451         // this is the root of the tree:
452         AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda());
453 
454         String selectedItemId = agendaEditor.getSelectedAgendaItemId();
455         AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId);
456         AgendaItemBo parent = getParent(firstItem, selectedItemId);
457         AgendaItemBo parentsOlderCousin = (parent == null) ? null : getNextOldestOfSameGeneration(firstItem, parent);
458 
459         AgendaItemChildAccessor childAccessor = getOldestChildAccessor(node, parent);
460         if (childAccessor != null) { // node is first child in sibling group
461             if (childAccessor == AgendaItemChildAccessor.whenFalse) {
462                 // move node to last position in When TRUE group
463                 AgendaItemInstanceChildAccessor youngestWhenTrueSiblingInsertionPoint =
464                         getLastChildsAlwaysAccessor(new AgendaItemInstanceChildAccessor(AgendaItemChildAccessor.whenTrue, parent));
465                 youngestWhenTrueSiblingInsertionPoint.setChild(node);
466                 AgendaItemChildAccessor.whenFalse.setChild(parent, node.getAlways());
467                 AgendaItemChildAccessor.always.setChild(node, null);
468 
469             } else if (parentsOlderCousin != null) {
470                 // find youngest child of parentsOlderCousin and put node after it
471                 AgendaItemInstanceChildAccessor youngestWhenFalseSiblingInsertionPoint =
472                         getLastChildsAlwaysAccessor(new AgendaItemInstanceChildAccessor(AgendaItemChildAccessor.whenFalse, parentsOlderCousin));
473                 youngestWhenFalseSiblingInsertionPoint.setChild(node);
474                 AgendaItemChildAccessor.whenTrue.setChild(parent, node.getAlways());
475                 AgendaItemChildAccessor.always.setChild(node, null);
476             }
477         } else if (!selectedItemId.equals(firstItem.getId())) { // conditional to miss special case of first node
478 
479             AgendaItemBo bogusRootNode = null;
480             if (parent == null) {
481                 // special case, this is a top level sibling. rig up special parent node
482                 bogusRootNode = new AgendaItemBo();
483                 AgendaItemChildAccessor.whenTrue.setChild(bogusRootNode, firstItem);
484                 parent = bogusRootNode;
485             }
486 
487             // move node up within its sibling group
488             AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId());
489             AgendaItemBo olderSibling = accessorToSelectedNode.getInstance();
490             AgendaItemInstanceChildAccessor accessorToOlderSibling = getInstanceAccessorToChild(parent, olderSibling.getId());
491 
492             accessorToOlderSibling.setChild(node);
493             accessorToSelectedNode.setChild(node.getAlways());
494             AgendaItemChildAccessor.always.setChild(node, olderSibling);
495 
496             if (bogusRootNode != null) {
497                 // clean up special case with bogus root node
498                 agendaEditor.getAgenda().setFirstItemId(bogusRootNode.getWhenTrueId());
499             }
500         }
501     }
502 
503     @RequestMapping(params = "methodToCall=" + "moveDown")
504     public ModelAndView moveDown(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
505             HttpServletRequest request, HttpServletResponse response)
506             throws Exception {
507         moveSelectedSubtreeDown(form);
508         
509         return super.refresh(form, result, request, response);
510     }
511 
512     @RequestMapping(params = "methodToCall=" + "ajaxMoveDown")
513     public ModelAndView ajaxMoveDown(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
514             HttpServletRequest request, HttpServletResponse response)
515             throws Exception {
516         moveSelectedSubtreeDown(form);
517 
518         // call the super method to avoid the agenda tree being reloaded from the db
519         return super.updateComponent(form, result, request, response);
520     }
521 
522     /**
523      *
524      * @param form
525      * @see AgendaItemChildAccessor for nomenclature explanation
526      */
527     private void moveSelectedSubtreeDown(UifFormBase form) {
528 
529         /* Rough algorithm for moving a node down.  This is a "level order" move.  Note that in this tree,
530          * level order means something a bit funky.  We are defining a level as it would be displayed in the browser,
531          * so only the traversal of When FALSE or When TRUE links increments the level, since ALWAYS linked nodes are
532          * considered siblings.
533          *
534          * find the following:
535          *   node := the selected node
536          *   parent := the selected node's parent, its containing node (via when true or when false relationship)
537          *   parentsYoungerCousin := the parent's level-order successor (sibling or cousin)
538          *
539          * if (node is last child in sibling group)
540          *     if (node is in When TRUE group)
541          *         move node to first position in When FALSE group
542          *     else
543          *         move to first child of parentsYoungerCousin
544          * else
545          *     move node down within its sibling group
546          */
547 
548         AgendaEditor agendaEditor = getAgendaEditor(form);
549         // this is the root of the tree:
550         AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda());
551 
552         String selectedItemId = agendaEditor.getSelectedAgendaItemId();
553         AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId);
554         AgendaItemBo parent = getParent(firstItem, selectedItemId);
555         AgendaItemBo parentsYoungerCousin = (parent == null) ? null : getNextYoungestOfSameGeneration(firstItem, parent);
556 
557         if (node.getAlways() == null && parent != null) { // node is last child in sibling group
558             // set link to selected node to null
559             if (parent.getWhenTrue() != null && isSiblings(parent.getWhenTrue(), node)) { // node is in When TRUE group
560                 // move node to first child under When FALSE
561 
562                 AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId());
563                 accessorToSelectedNode.setChild(null);
564 
565                 AgendaItemBo parentsFirstChild = parent.getWhenFalse();
566                 AgendaItemChildAccessor.whenFalse.setChild(parent, node);
567                 AgendaItemChildAccessor.always.setChild(node, parentsFirstChild);
568             } else if (parentsYoungerCousin != null) { // node is in the When FALSE group
569                 // move to first child of parentsYoungerCousin under When TRUE
570 
571                 AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId());
572                 accessorToSelectedNode.setChild(null);
573 
574                 AgendaItemBo parentsYoungerCousinsFirstChild = parentsYoungerCousin.getWhenTrue();
575                 AgendaItemChildAccessor.whenTrue.setChild(parentsYoungerCousin, node);
576                 AgendaItemChildAccessor.always.setChild(node, parentsYoungerCousinsFirstChild);
577             }
578         } else if (node.getAlways() != null) { // move node down within its sibling group
579 
580             AgendaItemBo bogusRootNode = null;
581             if (parent == null) {
582                 // special case, this is a top level sibling. rig up special parent node
583                 bogusRootNode = new AgendaItemBo();
584                 AgendaItemChildAccessor.whenFalse.setChild(bogusRootNode, firstItem);
585                 parent = bogusRootNode;
586             }
587 
588             // move node down within its sibling group
589             AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId());
590             AgendaItemBo youngerSibling = node.getAlways();
591             accessorToSelectedNode.setChild(youngerSibling);
592             AgendaItemChildAccessor.always.setChild(node, youngerSibling.getAlways());
593             AgendaItemChildAccessor.always.setChild(youngerSibling, node);
594 
595             if (bogusRootNode != null) {
596                 // clean up special case with bogus root node
597                 agendaEditor.getAgenda().setFirstItemId(bogusRootNode.getWhenFalseId());
598             }
599         }
600     }
601 
602     @RequestMapping(params = "methodToCall=" + "moveLeft")
603     public ModelAndView moveLeft(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
604             HttpServletRequest request, HttpServletResponse response)
605             throws Exception {
606         moveSelectedSubtreeLeft(form);
607         
608         return super.refresh(form, result, request, response);
609     }
610 
611     @RequestMapping(params = "methodToCall=" + "ajaxMoveLeft")
612     public ModelAndView ajaxMoveLeft(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
613             HttpServletRequest request, HttpServletResponse response)
614             throws Exception {
615 
616         moveSelectedSubtreeLeft(form);
617 
618         // call the super method to avoid the agenda tree being reloaded from the db
619         return super.updateComponent(form, result, request, response);
620     }
621 
622     /**
623      *
624      * @param form
625      * @see AgendaItemChildAccessor for nomenclature explanation
626      */
627     private void moveSelectedSubtreeLeft(UifFormBase form) {
628 
629         /*
630          * Move left means make it a younger sibling of it's parent.
631          */
632 
633         AgendaEditor agendaEditor = getAgendaEditor(form);
634         // this is the root of the tree:
635         AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda());
636 
637         String selectedItemId = agendaEditor.getSelectedAgendaItemId();
638         AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId);
639         AgendaItemBo parent = getParent(firstItem, selectedItemId);
640 
641         if (parent != null) {
642             AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId());
643             accessorToSelectedNode.setChild(node.getAlways());
644 
645             AgendaItemChildAccessor.always.setChild(node, parent.getAlways());
646             AgendaItemChildAccessor.always.setChild(parent, node);
647         }
648     }
649 
650 
651     @RequestMapping(params = "methodToCall=" + "moveRight")
652     public ModelAndView moveRight(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
653             HttpServletRequest request, HttpServletResponse response)
654             throws Exception {
655 
656         moveSelectedSubtreeRight(form);
657 
658         return super.refresh(form, result, request, response);
659     }
660 
661     @RequestMapping(params = "methodToCall=" + "ajaxMoveRight")
662     public ModelAndView ajaxMoveRight(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
663             HttpServletRequest request, HttpServletResponse response)
664             throws Exception {
665 
666         moveSelectedSubtreeRight(form);
667 
668         // call the super method to avoid the agenda tree being reloaded from the db
669         return super.updateComponent(form, result, request, response);
670     }
671 
672     /**
673      *
674      * @param form
675      * @see AgendaItemChildAccessor for nomenclature explanation
676      */
677     private void moveSelectedSubtreeRight(UifFormBase form) {
678 
679         /*
680          * Move right prefers moving to bottom of upper sibling's When FALSE branch
681          * ... otherwise ..
682          * moves to top of lower sibling's When TRUE branch
683          */
684 
685         AgendaEditor agendaEditor = getAgendaEditor(form);
686         // this is the root of the tree:
687         AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda());
688 
689         String selectedItemId = agendaEditor.getSelectedAgendaItemId();
690         AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId);
691         AgendaItemBo parent = getParent(firstItem, selectedItemId);
692 
693         AgendaItemBo bogusRootNode = null;
694         if (parent == null) {
695             // special case, this is a top level sibling. rig up special parent node
696             bogusRootNode = new AgendaItemBo();
697             AgendaItemChildAccessor.whenFalse.setChild(bogusRootNode, firstItem);
698             parent = bogusRootNode;
699         }
700 
701         AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId());
702         AgendaItemBo olderSibling = (accessorToSelectedNode.getInstance() == parent) ? null : accessorToSelectedNode.getInstance();
703 
704         if (olderSibling != null) {
705             accessorToSelectedNode.setChild(node.getAlways());
706             AgendaItemInstanceChildAccessor yougestWhenFalseSiblingInsertionPoint =
707                     getLastChildsAlwaysAccessor(new AgendaItemInstanceChildAccessor(AgendaItemChildAccessor.whenFalse, olderSibling));
708             yougestWhenFalseSiblingInsertionPoint.setChild(node);
709             AgendaItemChildAccessor.always.setChild(node, null);
710         } else if (node.getAlways() != null) { // has younger sibling
711             accessorToSelectedNode.setChild(node.getAlways());
712             AgendaItemBo childsWhenTrue = node.getAlways().getWhenTrue();
713             AgendaItemChildAccessor.whenTrue.setChild(node.getAlways(), node);
714             AgendaItemChildAccessor.always.setChild(node, childsWhenTrue);
715         }
716 
717         if (bogusRootNode != null) {
718             // clean up special case with bogus root node
719             agendaEditor.getAgenda().setFirstItemId(bogusRootNode.getWhenFalseId());
720         }
721     }
722 
723     /**
724      *
725      * @param cousin1
726      * @param cousin2
727      * @return
728      * @see AgendaItemChildAccessor for nomenclature explanation
729      */
730     private boolean isSiblings(AgendaItemBo cousin1, AgendaItemBo cousin2) {
731         if (cousin1.equals(cousin2)) return true; // this is a bit abusive
732         
733         // can you walk to c1 from ALWAYS links of c2?
734         AgendaItemBo candidate = cousin2;
735         while (null != (candidate = candidate.getAlways())) {
736             if (candidate.equals(cousin1)) return true;
737         }
738         // can you walk to c2 from ALWAYS links of c1?
739         candidate = cousin1;
740         while (null != (candidate = candidate.getAlways())) {
741             if (candidate.equals(cousin2)) return true;
742         }
743         return false;
744     }
745 
746     /**
747      * This method returns the level order accessor (getWhenTrue or getWhenFalse) that relates the parent directly 
748      * to the child.  If the two nodes don't have such a relationship, null is returned. 
749      * Note that this only finds accessors for oldest children, not younger siblings.
750      * @see AgendaItemChildAccessor for nomenclature explanation
751      */
752     private AgendaItemChildAccessor getOldestChildAccessor(
753             AgendaItemBo child, AgendaItemBo parent) {
754         AgendaItemChildAccessor levelOrderChildAccessor = null;
755         
756         if (parent != null) {
757             for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.children) {
758                 if (child.equals(childAccessor.getChild(parent))) {
759                     levelOrderChildAccessor = childAccessor;
760                     break;
761                 }
762             }
763         }
764         return levelOrderChildAccessor;
765     }
766     
767     /**
768      * This method finds and returns the first agenda item in the agenda, or null if there are no items presently
769      * 
770      * @param agenda
771      * @return
772      */
773     private AgendaItemBo getFirstAgendaItem(AgendaBo agenda) {
774         AgendaItemBo firstItem = null;
775         if (agenda != null && agenda.getItems() != null) for (AgendaItemBo agendaItem : agenda.getItems()) {
776             if (agenda.getFirstItemId().equals(agendaItem.getId())) {
777                 firstItem = agendaItem;
778                 break;
779             }
780         }
781         return firstItem;
782     }
783     
784     /**
785      * @return the closest younger sibling of the agenda item with the given ID, and if there is no such sibling, the closest younger cousin.
786      * If there is no such cousin either, then null is returned.
787      * @see AgendaItemChildAccessor for nomenclature explanation
788      */
789     private AgendaItemBo getNextYoungestOfSameGeneration(AgendaItemBo root, AgendaItemBo agendaItem) {
790 
791         int genNumber = getAgendaItemGenerationNumber(0, root, agendaItem.getId());
792         List<AgendaItemBo> genList = new ArrayList<AgendaItemBo>();
793         buildAgendaItemGenerationList(genList, root, 0, genNumber);
794 
795         int itemIndex = genList.indexOf(agendaItem);
796         if (genList.size() > itemIndex + 1) return genList.get(itemIndex + 1);
797 
798         return null;
799     }
800 
801     /**
802      *
803      * @param currentLevel
804      * @param node
805      * @param agendaItemId
806      * @return
807      * @see AgendaItemChildAccessor for nomenclature explanation
808      */
809     private int getAgendaItemGenerationNumber(int currentLevel, AgendaItemBo node, String agendaItemId) {
810         int result = -1;
811         if (agendaItemId.equals(node.getId())) {
812             result = currentLevel;
813         } else {
814             for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) {
815                 AgendaItemBo child = childAccessor.getChild(node);
816                 if (child != null) {
817                     int nextLevel = currentLevel;
818                     // we don't change the level order parent when we traverse ALWAYS links
819                     if (childAccessor != AgendaItemChildAccessor.always) {
820                         nextLevel = currentLevel +1;
821                     }
822                     result = getAgendaItemGenerationNumber(nextLevel, child, agendaItemId);
823                     if (result != -1) break;
824                 }
825             }
826         }
827         return result;
828     }
829 
830     /**
831      *
832      * @param genList
833      * @param node
834      * @param currentLevel
835      * @param generation
836      * @see AgendaItemChildAccessor for nomenclature explanation
837      */
838     private void buildAgendaItemGenerationList(List<AgendaItemBo> genList, AgendaItemBo node, int currentLevel, int generation) {
839         if (currentLevel == generation) {
840             genList.add(node);
841         }
842 
843         if (currentLevel > generation) return;
844 
845         for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) {
846             AgendaItemBo child = childAccessor.getChild(node);
847             if (child != null) {
848                 int nextLevel = currentLevel;
849                 // we don't change the level order parent when we traverse ALWAYS links
850                 if (childAccessor != AgendaItemChildAccessor.always) {
851                     nextLevel = currentLevel +1;
852                 }
853                 buildAgendaItemGenerationList(genList, child, nextLevel, generation);
854             }
855         }
856     }
857 
858     /**
859      * @return the closest older sibling of the agenda item with the given ID, and if there is no such sibling, the closest older cousin.
860      * If there is no such cousin either, then null is returned.
861      * @see AgendaItemChildAccessor for nomenclature explanation
862      */
863     private AgendaItemBo getNextOldestOfSameGeneration(AgendaItemBo root, AgendaItemBo agendaItem) {
864 
865         int genNumber = getAgendaItemGenerationNumber(0, root, agendaItem.getId());
866         List<AgendaItemBo> genList = new ArrayList<AgendaItemBo>();
867         buildAgendaItemGenerationList(genList, root, 0, genNumber);
868 
869         int itemIndex = genList.indexOf(agendaItem);
870         if (itemIndex >= 1) return genList.get(itemIndex - 1);
871 
872         return null;
873     }
874     
875 
876     /**
877      * returns the parent of the item with the passed in id.  Note that {@link AgendaItemBo}s related by ALWAYS relationships are considered siblings.
878      * @see AgendaItemChildAccessor for nomenclature explanation
879      */
880     private AgendaItemBo getParent(AgendaItemBo root, String agendaItemId) {
881         return getParentHelper(root, null, agendaItemId);
882     }
883 
884     /**
885      *
886      * @param node
887      * @param levelOrderParent
888      * @param agendaItemId
889      * @return
890      * @see AgendaItemChildAccessor for nomenclature explanation
891      */
892     private AgendaItemBo getParentHelper(AgendaItemBo node, AgendaItemBo levelOrderParent, String agendaItemId) {
893         AgendaItemBo result = null;
894         if (agendaItemId.equals(node.getId())) {
895             result = levelOrderParent;
896         } else {
897             for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) {
898                 AgendaItemBo child = childAccessor.getChild(node);
899                 if (child != null) {
900                     // we don't change the level order parent when we traverse ALWAYS links 
901                     AgendaItemBo lop = (childAccessor == AgendaItemChildAccessor.always) ? levelOrderParent : node;
902                     result = getParentHelper(child, lop, agendaItemId);
903                     if (result != null) break;
904                 }
905             }
906         }
907         return result;
908     }
909 
910     /**
911      * Search the tree for the agenda item with the given id.
912      */
913     private AgendaItemBo getAgendaItemById(AgendaItemBo node, String agendaItemId) {
914         if (node == null) throw new IllegalArgumentException("node must be non-null");
915 
916         AgendaItemBo result = null;
917         
918         if (agendaItemId.equals(node.getId())) {
919             result = node;
920         } else {
921             for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) {
922                 AgendaItemBo child = childAccessor.getChild(node);
923                 if (child != null) {
924                     result = getAgendaItemById(child, agendaItemId);
925                     if (result != null) break;
926                 }
927             }
928         } 
929         return result;
930     }
931 
932     /**
933      * @param form
934      * @return the {@link AgendaEditor} from the form
935      */
936     private AgendaEditor getAgendaEditor(UifFormBase form) {
937         MaintenanceForm maintenanceForm = (MaintenanceForm) form;
938         return ((AgendaEditor)maintenanceForm.getDocument().getDocumentDataObject());
939     }
940 
941     private void treeToInOrderList(AgendaItemBo agendaItem, List<AgendaItemBo> listToBuild) {
942         listToBuild.add(agendaItem);
943         for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) {
944             AgendaItemBo child = childAccessor.getChild(agendaItem);
945             if (child != null) treeToInOrderList(child, listToBuild);
946         }
947     }
948 
949     
950     @RequestMapping(params = "methodToCall=" + "delete")
951     public ModelAndView delete(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
952             HttpServletRequest request, HttpServletResponse response)
953             throws Exception {
954 
955         deleteSelectedSubtree(form);
956 
957         return super.refresh(form, result, request, response);
958     }
959 
960     @RequestMapping(params = "methodToCall=" + "ajaxDelete")
961     public ModelAndView ajaxDelete(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
962             HttpServletRequest request, HttpServletResponse response)
963             throws Exception {
964 
965         deleteSelectedSubtree(form);
966 
967         // call the super method to avoid the agenda tree being reloaded from the db
968         return super.updateComponent(form, result, request, response);
969     }
970 
971     
972     private void deleteSelectedSubtree(UifFormBase form) {
973         AgendaEditor agendaEditor = getAgendaEditor(form);
974         AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda());
975         String agendaItemSelected = agendaEditor.getSelectedAgendaItemId();
976 
977         if (firstItem != null) {
978             // need to handle the first item here, our recursive method won't handle it.
979             if (agendaItemSelected.equals(firstItem.getId())) {
980                 agendaEditor.getAgenda().setFirstItemId(firstItem.getAlwaysId());
981             } else {
982                 deleteAgendaItem(firstItem, agendaItemSelected);
983             }
984         }
985     }
986 
987     private void deleteAgendaItem(AgendaItemBo root, String agendaItemIdToDelete) {
988         if (deleteAgendaItem(root, AgendaItemChildAccessor.whenTrue, agendaItemIdToDelete) || 
989                 deleteAgendaItem(root, AgendaItemChildAccessor.whenFalse, agendaItemIdToDelete) || 
990                 deleteAgendaItem(root, AgendaItemChildAccessor.always, agendaItemIdToDelete)); // TODO: this is confusing, refactor
991     }
992     
993     private boolean deleteAgendaItem(AgendaItemBo agendaItem, AgendaItemChildAccessor childAccessor, String agendaItemIdToDelete) {
994         if (agendaItem == null || childAccessor.getChild(agendaItem) == null) return false;
995         if (agendaItemIdToDelete.equals(childAccessor.getChild(agendaItem).getId())) {
996             // delete the child in such a way that any ALWAYS children don't get lost from the tree
997             AgendaItemBo grandchildToKeep = childAccessor.getChild(agendaItem).getAlways();
998             childAccessor.setChild(agendaItem, grandchildToKeep);
999             return true;
1000         } else {
1001             AgendaItemBo child = childAccessor.getChild(agendaItem);
1002             // recurse
1003             for (AgendaItemChildAccessor nextChildAccessor : AgendaItemChildAccessor.linkedNodes) {
1004                 if (deleteAgendaItem(child, nextChildAccessor, agendaItemIdToDelete)) return true;
1005             }
1006         }
1007         return false;
1008     }
1009 
1010     @RequestMapping(params = "methodToCall=" + "ajaxCut")
1011     public ModelAndView ajaxCut(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
1012             HttpServletRequest request, HttpServletResponse response) throws Exception {
1013 
1014         AgendaEditor agendaEditor = getAgendaEditor(form);
1015         // this is the root of the tree:
1016         AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda());
1017         String selectedItemId = agendaEditor.getSelectedAgendaItemId();
1018 
1019         setCutAgendaItemId(form, selectedItemId);
1020 
1021         // call the super method to avoid the agenda tree being reloaded from the db
1022         return super.updateComponent(form, result, request, response);
1023     }
1024 
1025     @RequestMapping(params = "methodToCall=" + "ajaxPaste")
1026     public ModelAndView ajaxPaste(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
1027             HttpServletRequest request, HttpServletResponse response) throws Exception {
1028 
1029         AgendaEditor agendaEditor = getAgendaEditor(form);
1030         // this is the root of the tree:
1031         AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda());
1032         String selectedItemId = agendaEditor.getSelectedAgendaItemId();
1033 
1034         String agendaItemId = getCutAgendaItemId(form);
1035 
1036         if (StringUtils.isNotBlank(selectedItemId) && StringUtils.isNotBlank(agendaItemId)) {
1037             AgendaItemBo node = getAgendaItemById(firstItem, agendaItemId);
1038             AgendaItemBo orgRefNode = getReferringNode(firstItem, agendaItemId);
1039             AgendaItemBo newRefNode = getAgendaItemById(firstItem, selectedItemId);
1040 
1041             if (isSameOrChildNode(node, newRefNode)) {
1042                 // do nothing; can't paste to itself
1043             } else {
1044                 // remove node
1045                 if (orgRefNode == null) {
1046                     agendaEditor.getAgenda().setFirstItemId(node.getAlwaysId());
1047                 } else {
1048                     // determine if true, false or always
1049                     // do appropriate operation
1050                     if (node.getId().equals(orgRefNode.getWhenTrueId())) {
1051                         orgRefNode.setWhenTrueId(node.getAlwaysId());
1052                         orgRefNode.setWhenTrue(node.getAlways());
1053                     } else if(node.getId().equals(orgRefNode.getWhenFalseId())) {
1054                         orgRefNode.setWhenFalseId(node.getAlwaysId());
1055                         orgRefNode.setWhenFalse(node.getAlways());
1056                     } else {
1057                         orgRefNode.setAlwaysId(node.getAlwaysId());
1058                         orgRefNode.setAlways(node.getAlways());
1059                     }
1060                 }
1061 
1062                 // insert node
1063                 node.setAlwaysId(newRefNode.getAlwaysId());
1064                 node.setAlways(newRefNode.getAlways());
1065                 newRefNode.setAlwaysId(node.getId());
1066                 newRefNode.setAlways(node);
1067             }
1068         }
1069 
1070         setCutAgendaItemId(form, null);
1071 
1072         // call the super method to avoid the agenda tree being reloaded from the db
1073         return super.updateComponent(form, result, request, response);
1074     }
1075 
1076     /**
1077      * This method checks if the node is the same as the new parent node or a when-true/when-fase
1078      * child of the new parent node.
1079      *
1080      * @param node - the node to be checked if it's the same or a child
1081      * @param newParent - the parent node to check against
1082      * @return true if same or child, false otherwise
1083      * @see AgendaItemChildAccessor for nomenclature explanation
1084      */
1085     private boolean isSameOrChildNode(AgendaItemBo node, AgendaItemBo newParent) {
1086         return isSameOrChildNodeHelper(node, newParent, AgendaItemChildAccessor.children);
1087     }
1088 
1089     private boolean isSameOrChildNodeHelper(AgendaItemBo node, AgendaItemBo newParent, AgendaItemChildAccessor[] childAccessors) {
1090         boolean result = false;
1091         if (StringUtils.equals(node.getId(), newParent.getId())) {
1092             result = true;
1093         } else {
1094             for (AgendaItemChildAccessor childAccessor : childAccessors) {
1095                 AgendaItemBo child = childAccessor.getChild(node);
1096                 if (child != null) {
1097                     result = isSameOrChildNodeHelper(child, newParent, AgendaItemChildAccessor.linkedNodes);
1098                     if (result == true) break;
1099                 }
1100             }
1101         }
1102         return result;
1103     }
1104 
1105     /**
1106      * This method returns the node that points to the specified agendaItemId.
1107      * (returns the next older sibling or the parent if no older sibling exists)
1108      *
1109      * @param root - the first agenda item of the agenda
1110      * @param agendaItemId - agenda item id of the agenda item whose referring node is to be returned
1111      * @return AgendaItemBo that points to the specified agenda item
1112      * @see AgendaItemChildAccessor for nomenclature explanation
1113      */
1114     private AgendaItemBo getReferringNode(AgendaItemBo root, String agendaItemId) {
1115         return getReferringNodeHelper(root, null, agendaItemId);
1116     }
1117 
1118     private AgendaItemBo getReferringNodeHelper(AgendaItemBo node, AgendaItemBo referringNode, String agendaItemId) {
1119         AgendaItemBo result = null;
1120         if (agendaItemId.equals(node.getId())) {
1121             result = referringNode;
1122         } else {
1123             for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) {
1124                 AgendaItemBo child = childAccessor.getChild(node);
1125                 if (child != null) {
1126                     result = getReferringNodeHelper(child, node, agendaItemId);
1127                     if (result != null) break;
1128                 }
1129             }
1130         }
1131         return result;
1132     }
1133 
1134     /**
1135      *  return the sequenceAssessorService
1136      */
1137     private SequenceAccessorService getSequenceAccessorService() {
1138         if ( sequenceAccessorService == null ) {
1139             sequenceAccessorService = KRADServiceLocator.getSequenceAccessorService();
1140         }
1141         return sequenceAccessorService;
1142     }
1143 
1144     /**
1145      * return the contextBoService
1146      */
1147     private ContextBoService getContextBoService() {
1148         return KrmsRepositoryServiceLocator.getContextBoService();
1149     }
1150 
1151     /**
1152      * binds a child accessor to an AgendaItemBo instance.  An {@link AgendaItemInstanceChildAccessor} allows you to
1153      * get and set the referent
1154      */
1155     private static class AgendaItemInstanceChildAccessor {
1156         
1157         private final AgendaItemChildAccessor accessor;
1158         private final AgendaItemBo instance;
1159 
1160         public AgendaItemInstanceChildAccessor(AgendaItemChildAccessor accessor, AgendaItemBo instance) {
1161             this.accessor = accessor;
1162             this.instance = instance;
1163         }
1164         
1165         public void setChild(AgendaItemBo child) {
1166             accessor.setChild(instance, child);
1167         }
1168         
1169         public AgendaItemBo getChild() {
1170             return accessor.getChild(instance);
1171         }
1172         
1173         public AgendaItemBo getInstance() { return instance; }
1174     }
1175     
1176     /**
1177      * <p>This class abstracts getting and setting a child of an AgendaItemBo, making some recursive operations
1178      * require less boiler plate.</p>
1179      *
1180      * <p>The word 'child' in AgendaItemChildAccessor means child in the strict data structures sense, in that the
1181      * instance passed in holds a reference to some other node (or null).  However, when discussing the agenda tree
1182      * and algorithms for manipulating it, the meaning of 'child' is somewhat different, and there are notions of
1183      * 'sibling' and 'cousin' that are tossed about too. It's probably worth explaining that somewhat here:</p>
1184      *
1185      * <p>General principals of relationships when talking about the agenda tree:
1186      * <ul>
1187      * <li>Generation boundaries (parent to child) are across 'When TRUE' and 'When FALSE' references.</li>
1188      * <li>"Age" among siblings & cousins goes from top (oldest) to bottom (youngest).</li>
1189      * <li>siblings are related by 'Always' references.</li>
1190      * </ul>
1191      * </p>
1192      * <p>This diagram of an agenda tree and the following examples seek to illustrate these principals:</p>
1193      * <img src="doc-files/AgendaEditorController-1.png" alt="Example Agenda Items"/>
1194      * <p>Examples:
1195      * <ul>
1196      * <li>A is the parent of B, C, & D</li>
1197      * <li>E is the younger sibling of A</li>
1198      * <li>B is the older cousin of C</li>
1199      * <li>C is the older sibling of D</li>
1200      * <li>F is the younger cousin of D</li>
1201      * </ul>
1202      * </p>
1203      */
1204     protected static class AgendaItemChildAccessor {
1205         
1206         private enum Child { WHEN_TRUE, WHEN_FALSE, ALWAYS };
1207         
1208         private static final AgendaItemChildAccessor whenTrue = new AgendaItemChildAccessor(Child.WHEN_TRUE); 
1209         private static final AgendaItemChildAccessor whenFalse = new AgendaItemChildAccessor(Child.WHEN_FALSE); 
1210         private static final AgendaItemChildAccessor always = new AgendaItemChildAccessor(Child.ALWAYS); 
1211 
1212         /**
1213          * Accessors for all linked items
1214          */
1215         private static final AgendaItemChildAccessor [] linkedNodes = { whenTrue, whenFalse, always };
1216         
1217         /**
1218          * Accessors for children (so ALWAYS is omitted);
1219          */
1220         private static final AgendaItemChildAccessor [] children = { whenTrue, whenFalse };
1221         
1222         private final Child whichChild;
1223         
1224         private AgendaItemChildAccessor(Child whichChild) {
1225             if (whichChild == null) throw new IllegalArgumentException("whichChild must be non-null");
1226             this.whichChild = whichChild;
1227         }
1228         
1229         /**
1230          * @return the referenced child
1231          */
1232         public AgendaItemBo getChild(AgendaItemBo parent) {
1233             switch (whichChild) {
1234             case WHEN_TRUE: return parent.getWhenTrue();
1235             case WHEN_FALSE: return parent.getWhenFalse();
1236             case ALWAYS: return parent.getAlways();
1237             default: throw new IllegalStateException();
1238             }
1239         }
1240         
1241         /**
1242          * Sets the child reference and the child id 
1243          */
1244         public void setChild(AgendaItemBo parent, AgendaItemBo child) {
1245             switch (whichChild) {
1246             case WHEN_TRUE: 
1247                 parent.setWhenTrue(child);
1248                 parent.setWhenTrueId(child == null ? null : child.getId());
1249                 break;
1250             case WHEN_FALSE:
1251                 parent.setWhenFalse(child);
1252                 parent.setWhenFalseId(child == null ? null : child.getId());
1253                 break;
1254             case ALWAYS:
1255                 parent.setAlways(child);
1256                 parent.setAlwaysId(child == null ? null : child.getId());
1257                 break;
1258             default: throw new IllegalStateException();
1259             }
1260         }
1261     }
1262     //
1263     // Rule Editor Controller methods
1264     //
1265     /**
1266      * This method starts an edit proposition.
1267      */
1268     @RequestMapping(params = "methodToCall=" + "goToEditProposition")
1269     public ModelAndView goToEditProposition(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
1270             HttpServletRequest request, HttpServletResponse response) throws Exception {
1271 
1272         // open the selected node for editing
1273         AgendaEditor agendaEditor = getAgendaEditor(form);
1274         RuleBo rule = agendaEditor.getAgendaItemLine().getRule();
1275         String selectedPropId = agendaEditor.getSelectedPropositionId();
1276 
1277         // find parent
1278         Node<RuleTreeNode,String> root = rule.getPropositionTree().getRootElement();
1279         Node<RuleTreeNode,String> parent = findParentPropositionNode( root, selectedPropId);
1280         if (parent != null){
1281             List<Node<RuleTreeNode,String>> children = parent.getChildren();
1282             for( int index=0; index< children.size(); index++){
1283                 Node<RuleTreeNode,String> child = children.get(index);
1284                 if (propIdMatches(child, selectedPropId)){
1285                     PropositionBo prop = child.getData().getProposition();
1286                     boolean editMode =  !prop.getEditMode();
1287                     prop.setEditMode(editMode);
1288                     // if compound node, set all children into same edit mode
1289                     if (PropositionType.COMPOUND.getCode().equalsIgnoreCase(prop.getPropositionTypeCode())){
1290                        for ( PropositionBo compoundComponent : prop.getCompoundComponents() ){
1291                            compoundComponent.setEditMode(editMode);
1292                        }
1293                     }
1294                     //refresh the tree
1295                     rule.refreshPropositionTree(null);
1296                     break;
1297                 }
1298             }
1299         }
1300         return super.updateComponent(form, result, request, response);
1301     }
1302 
1303     @RequestMapping(params = "methodToCall=" + "addProposition")
1304     public ModelAndView addProposition(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
1305             HttpServletRequest request, HttpServletResponse response) throws Exception {
1306 
1307         AgendaEditor agendaEditor = getAgendaEditor(form);
1308         RuleBo rule = agendaEditor.getAgendaItemLine().getRule();
1309         String selectedPropId = agendaEditor.getSelectedPropositionId();
1310 
1311         // find parent
1312         Node<RuleTreeNode,String> root = agendaEditor.getAgendaItemLine().getRule().getPropositionTree().getRootElement();
1313         Node<RuleTreeNode,String> parent = findParentPropositionNode( root, selectedPropId);
1314 
1315         // add new child at appropriate spot
1316         if (parent != null){
1317             List<Node<RuleTreeNode,String>> children = parent.getChildren();
1318             for( int index=0; index< children.size(); index++){
1319                 Node<RuleTreeNode,String> child = children.get(index);
1320 
1321                 // if our selected node is a simple proposition, add a new one after
1322                 if (propIdMatches(child, selectedPropId)){
1323                     // handle special case of adding to a lone simple proposition.
1324                     // in this case, we need to change the root level proposition to a compound proposition
1325                     // move the existing simple proposition as the first compound component,
1326                     // then add a new blank simple prop as the second compound component.
1327                     if (parent == root &&
1328                         (SimplePropositionNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType()) ||
1329                         SimplePropositionEditNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType()))){
1330 
1331                         // create a new compound proposition
1332                         PropositionBo compound = PropositionBo.createCompoundPropositionBoStub(child.getData().getProposition());
1333                         compound.setEditMode(true);
1334                         rule.setProposition(compound);
1335                         rule.refreshPropositionTree(null);
1336                     }
1337                     // handle regular case of adding a simple prop to an existing compound prop
1338                     else if(SimplePropositionNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType()) ||
1339                        SimplePropositionEditNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType())){
1340 
1341                         // build new Blank Proposition
1342                         PropositionBo blank = PropositionBo.createSimplePropositionBoStub(child.getData().getProposition(),PropositionType.SIMPLE.getCode());
1343                         //add it to the parent
1344                         PropositionBo parentProp = parent.getData().getProposition();
1345                         parentProp.getCompoundComponents().add(((index/2)+1), blank);
1346 
1347                         rule.refreshPropositionTree(true);
1348                     }
1349 
1350                     break;
1351                 }
1352             }
1353         } else {
1354             // special case, if root has no children, add a new simple proposition
1355             // todo: how to add compound proposition. - just add another to the firs simple
1356             if (root.getChildren().isEmpty()){
1357                 PropositionBo blank = PropositionBo.createSimplePropositionBoStub(null,PropositionType.SIMPLE.getCode());
1358                 blank.setRuleId(rule.getId());
1359                 blank.setTypeId(rule.getTypeId());  // ?? bug
1360                 rule.setPropId(blank.getId());
1361                 rule.setProposition(blank);
1362                 rule.refreshPropositionTree(true);
1363             }
1364         }
1365         return super.updateComponent(form, result, request, response);
1366     }
1367 
1368     /**
1369      *
1370      * This method adds an opCode Node to separate components in a compound proposition.
1371      *
1372      * @param currentNode
1373      * @param prop
1374      * @return
1375      */
1376     private void addOpCodeNode(Node currentNode, PropositionBo prop, int index){
1377         String opCodeLabel = "";
1378 
1379         if (LogicalOperator.AND.getCode().equalsIgnoreCase(prop.getCompoundOpCode())){
1380             opCodeLabel = "AND";
1381         } else if (LogicalOperator.OR.getCode().equalsIgnoreCase(prop.getCompoundOpCode())){
1382             opCodeLabel = "OR";
1383         }
1384         Node<RuleTreeNode, String> aNode = new Node<RuleTreeNode, String>();
1385         aNode.setNodeLabel("");
1386         aNode.setNodeType("ruleTreeNode compoundOpCodeNode");
1387         aNode.setData(new CompoundOpCodeNode(prop));
1388         currentNode.insertChildAt(index, aNode);
1389     }
1390 
1391 
1392     private boolean propIdMatches(Node<RuleTreeNode, String> node, String propId){
1393         if (propId!=null && node != null && node.getData() != null && propId.equalsIgnoreCase(node.getData().getProposition().getId())) {
1394             return true;
1395         }
1396         return false;
1397     }
1398 
1399     private Node<RuleTreeNode, String> findPropositionTreeNode(Node<RuleTreeNode, String> currentNode, String selectedPropId){
1400         Node<RuleTreeNode,String> bingo = null;
1401         if (currentNode.getData() != null){
1402             RuleTreeNode dataNode = currentNode.getData();
1403             if (selectedPropId.equalsIgnoreCase(dataNode.getProposition().getId())){
1404                 return currentNode;
1405             }
1406         }
1407         List<Node<RuleTreeNode,String>> children = currentNode.getChildren();
1408         for( Node<RuleTreeNode,String> child : children){
1409               bingo = findPropositionTreeNode(child, selectedPropId);
1410               if (bingo != null) break;
1411         }
1412         return bingo;
1413     }
1414 
1415     private Node<RuleTreeNode, String> findParentPropositionNode(Node<RuleTreeNode, String> currentNode, String selectedPropId){
1416         Node<RuleTreeNode,String> bingo = null;
1417         if (selectedPropId != null) {
1418             // if it's in children, we have the parent
1419             List<Node<RuleTreeNode,String>> children = currentNode.getChildren();
1420             for( Node<RuleTreeNode,String> child : children){
1421                 RuleTreeNode dataNode = child.getData();
1422                 if (selectedPropId.equalsIgnoreCase(dataNode.getProposition().getId()))
1423                     return currentNode;
1424             }
1425 
1426             // if not found check grandchildren
1427             for( Node<RuleTreeNode,String> kid : children){
1428                   bingo = findParentPropositionNode(kid, selectedPropId);
1429                   if (bingo != null) break;
1430             }
1431         }
1432         return bingo;
1433     }
1434 
1435     /**
1436      * This method return the index of the position of the child that matches the id
1437      * @param parent
1438      * @param propId
1439      * @return index if found, -1 if not found
1440      */
1441     private int findChildIndex(Node<RuleTreeNode,String> parent, String propId){
1442         int index;
1443         List<Node<RuleTreeNode,String>> children = parent.getChildren();
1444         for(index=0; index< children.size(); index++){
1445             Node<RuleTreeNode,String> child = children.get(index);
1446             // if our selected node is a simple proposition, add a new one after
1447             if (propIdMatches(child, propId)){
1448                 return index;
1449             }
1450         }
1451         return -1;
1452     }
1453 
1454     @RequestMapping(params = "methodToCall=" + "movePropositionUp")
1455     public ModelAndView movePropositionUp(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
1456             HttpServletRequest request, HttpServletResponse response)
1457             throws Exception {
1458         moveSelectedProposition(form, true);
1459 
1460         return super.updateComponent(form, result, request, response);
1461     }
1462 
1463     @RequestMapping(params = "methodToCall=" + "movePropositionDown")
1464     public ModelAndView movePropositionDown(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
1465             HttpServletRequest request, HttpServletResponse response)
1466             throws Exception {
1467         moveSelectedProposition(form, false);
1468 
1469         return super.updateComponent(form, result, request, response);
1470     }
1471 
1472     private void moveSelectedProposition(UifFormBase form, boolean up) {
1473 
1474         /* Rough algorithm for moving a node up.
1475          *
1476          * find the following:
1477          *   node := the selected node
1478          *   parent := the selected node's parent, its containing node (via when true or when false relationship)
1479          *   parentsOlderCousin := the parent's level-order predecessor (sibling or cousin)
1480          *
1481          */
1482         AgendaEditor agendaEditor = getAgendaEditor(form);
1483         RuleBo rule = agendaEditor.getAgendaItemLine().getRule();
1484         String selectedPropId = agendaEditor.getSelectedPropositionId();
1485 
1486         // find parent
1487         Node<RuleTreeNode,String> parent = findParentPropositionNode(rule.getPropositionTree().getRootElement(), selectedPropId);
1488 
1489         // add new child at appropriate spot
1490         if (parent != null){
1491             List<Node<RuleTreeNode,String>> children = parent.getChildren();
1492             for( int index=0; index< children.size(); index++){
1493                 Node<RuleTreeNode,String> child = children.get(index);
1494                 // if our selected node is a simple proposition, add a new one after
1495                 if (propIdMatches(child, selectedPropId)){
1496                     if(SimplePropositionNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType()) ||
1497                        SimplePropositionEditNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType()) ||
1498                        RuleTreeNode.COMPOUND_NODE_TYPE.equalsIgnoreCase(child.getNodeType()) ){
1499 
1500                         if (((index > 0) && up) || ((index <(children.size() - 1)&& !up))){
1501                             //remove it from its current spot
1502                             PropositionBo parentProp = parent.getData().getProposition();
1503                             PropositionBo workingProp = parentProp.getCompoundComponents().remove(index/2);
1504                             if (up){
1505                                 parentProp.getCompoundComponents().add((index/2)-1, workingProp);
1506                             }else{
1507                                 parentProp.getCompoundComponents().add((index/2)+1, workingProp);
1508                             }
1509 
1510                             // insert it in the new spot
1511                             // redisplay the tree (editMode = true)
1512                             boolean editMode = (SimplePropositionEditNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType()));
1513                             rule.refreshPropositionTree(editMode);
1514                         }
1515                     }
1516 
1517                     break;
1518                 }
1519             }
1520         }
1521     }
1522     @RequestMapping(params = "methodToCall=" + "movePropositionLeft")
1523     public ModelAndView movePropositionLeft(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
1524             HttpServletRequest request, HttpServletResponse response)
1525             throws Exception {
1526 
1527         /* Rough algorithm for moving a node up.
1528          *
1529          * find the following:
1530          *   node := the selected node
1531          *   parent := the selected node's parent, its containing node (via when true or when false relationship)
1532          *   parentsOlderCousin := the parent's level-order predecessor (sibling or cousin)
1533          *
1534          */
1535         AgendaEditor agendaEditor = getAgendaEditor(form);
1536         RuleBo rule = agendaEditor.getAgendaItemLine().getRule();
1537         String selectedPropId = agendaEditor.getSelectedPropositionId();
1538 
1539         // find agendaEditor.getAgendaItemLine().getRule().getPropositionTree().getRootElement()parent
1540         Node<RuleTreeNode,String> root = rule.getPropositionTree().getRootElement();
1541         Node<RuleTreeNode,String> parent = findParentPropositionNode(root, selectedPropId);
1542         if ((parent != null) && (RuleTreeNode.COMPOUND_NODE_TYPE.equalsIgnoreCase(parent.getNodeType()))){
1543             Node<RuleTreeNode,String> granny = findParentPropositionNode(root,parent.getData().getProposition().getId());
1544             if (granny != root){
1545                 int oldIndex = findChildIndex(parent, selectedPropId);
1546                 int newIndex = findChildIndex(granny, parent.getData().getProposition().getId());
1547                 if (oldIndex >= 0 && newIndex >= 0){
1548                     PropositionBo prop = parent.getData().getProposition().getCompoundComponents().remove(oldIndex/2);
1549                     granny.getData().getProposition().getCompoundComponents().add((newIndex/2)+1, prop);
1550                     rule.refreshPropositionTree(false);
1551                 }
1552             } else {
1553                 // TODO: do we allow moving up to the root?
1554                 // we could add a new top level compound node, with current root as 1st child,
1555                 // and move the node to the second child.
1556             }
1557         }
1558         return super.updateComponent(form, result, request, response);
1559     }
1560 
1561     @RequestMapping(params = "methodToCall=" + "movePropositionRight")
1562     public ModelAndView movePropositionRight(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
1563             HttpServletRequest request, HttpServletResponse response)
1564             throws Exception {
1565         /* Rough algorithm for moving a node Right
1566          * if the selected node is above a compound proposition, move it into the compound proposition as the first child
1567          * if the node is above a simple proposition, do nothing.
1568          * find the following:
1569          *   node := the selected node
1570          *   parent := the selected node's parent, its containing node
1571          *   nextSibling := the node after the selected node
1572          *
1573          */
1574         AgendaEditor agendaEditor = getAgendaEditor(form);
1575         RuleBo rule = agendaEditor.getAgendaItemLine().getRule();
1576         String selectedPropId = agendaEditor.getSelectedPropositionId();
1577 
1578         // find parent
1579         Node<RuleTreeNode,String> parent = findParentPropositionNode(
1580                 rule.getPropositionTree().getRootElement(), selectedPropId);
1581         if (parent != null){
1582             int index = findChildIndex(parent, selectedPropId);
1583             // if we are the last child, do nothing, otherwise
1584             if (index >= 0 && index+1 < parent.getChildren().size()){
1585                 Node<RuleTreeNode,String> child = parent.getChildren().get(index);
1586                 Node<RuleTreeNode,String> nextSibling = parent.getChildren().get(index+2);
1587                 // if selected node above a compound node, move it into it as first child
1588                 if(RuleTreeNode.COMPOUND_NODE_TYPE.equalsIgnoreCase(nextSibling.getNodeType()) ){
1589                     // remove selected node from it's current spot
1590                     PropositionBo prop = parent.getData().getProposition().getCompoundComponents().remove(index/2);
1591                     // add it to it's siblings children
1592                     nextSibling.getData().getProposition().getCompoundComponents().add(0, prop);
1593                     rule.refreshPropositionTree(false);
1594                 }
1595             }
1596         }
1597         return super.updateComponent(form, result, request, response);
1598     }
1599 
1600 
1601     @RequestMapping(params = "methodToCall=" + "cutProposition")
1602     public ModelAndView cutProposition(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
1603             HttpServletRequest request, HttpServletResponse response)
1604             throws Exception {
1605 
1606         AgendaEditor agendaEditor = getAgendaEditor(form);
1607         String selectedPropId = agendaEditor.getSelectedPropositionId();
1608         agendaEditor.setCutPropositionId(selectedPropId);
1609 
1610         return super.updateComponent(form, result, request, response);
1611     }
1612 
1613     @RequestMapping(params = "methodToCall=" + "pasteProposition")
1614     public ModelAndView pasteProposition(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
1615             HttpServletRequest request, HttpServletResponse response)
1616             throws Exception {
1617 
1618         AgendaEditor agendaEditor = getAgendaEditor(form);
1619         RuleBo rule = agendaEditor.getAgendaItemLine().getRule();
1620 
1621         // get selected id
1622         String cutPropId = agendaEditor.getCutPropositionId();
1623         String selectedPropId = agendaEditor.getSelectedPropositionId();
1624 
1625         if (selectedPropId == cutPropId) {
1626                 // do nothing; can't paste to itself
1627         } else {
1628 
1629             // proposition tree root
1630             Node<RuleTreeNode, String> root = rule.getPropositionTree().getRootElement();
1631 
1632             if (StringUtils.isNotBlank(selectedPropId) && StringUtils.isNotBlank(cutPropId)) {
1633                 Node<RuleTreeNode,String> parentNode = findParentPropositionNode(root, selectedPropId);
1634                 PropositionBo newParent;
1635                 if (parentNode == root){
1636                     // special case
1637                     // build new top level compound proposition,
1638                     // add existing as first child
1639                     // then paste cut node as 2nd child
1640                     newParent = PropositionBo.createCompoundPropositionBoStub2(
1641                             root.getChildren().get(0).getData().getProposition());
1642                     newParent.setEditMode(true);
1643                     rule.setProposition(newParent);
1644                 } else {
1645                     newParent = parentNode.getData().getProposition();
1646                 }
1647                 PropositionBo oldParent = findParentPropositionNode(root, cutPropId).getData().getProposition();
1648 
1649                 PropositionBo workingProp = null;
1650                 // cut from old
1651                 if (oldParent != null){
1652                     List <PropositionBo> children = oldParent.getCompoundComponents();
1653                     for( int index=0; index< children.size(); index++){
1654                         if (cutPropId.equalsIgnoreCase(children.get(index).getId())){
1655                             workingProp = oldParent.getCompoundComponents().remove(index);
1656                             break;
1657                         }
1658                     }
1659                 }
1660 
1661                 // add to new
1662                 if (newParent != null && workingProp != null){
1663                     List <PropositionBo> children = newParent.getCompoundComponents();
1664                     for( int index=0; index< children.size(); index++){
1665                         if (selectedPropId.equalsIgnoreCase(children.get(index).getId())){
1666                             children.add(index+1, workingProp);
1667                             break;
1668                         }
1669                     }
1670                 }
1671                 // TODO: determine edit mode.
1672 //                boolean editMode = (SimplePropositionEditNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType()));
1673                 rule.refreshPropositionTree(false);
1674             }
1675         }
1676         agendaEditor.setCutPropositionId(null);
1677         // call the super method to avoid the agenda tree being reloaded from the db
1678         return super.updateComponent(form, result, request, response);
1679     }
1680 
1681     @RequestMapping(params = "methodToCall=" + "deleteProposition")
1682     public ModelAndView deleteProposition(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
1683             HttpServletRequest request, HttpServletResponse response)
1684             throws Exception {
1685         AgendaEditor agendaEditor = getAgendaEditor(form);
1686         String selectedPropId = agendaEditor.getSelectedPropositionId();
1687         Node<RuleTreeNode, String> root = agendaEditor.getAgendaItemLine().getRule().getPropositionTree().getRootElement();
1688 
1689         PropositionBo parent = findParentPropositionNode(root, selectedPropId).getData().getProposition();
1690         if (parent != null){
1691             List <PropositionBo> children = parent.getCompoundComponents();
1692             for( int index=0; index< children.size(); index++){
1693                 if (selectedPropId.equalsIgnoreCase(children.get(index).getId())){
1694                     parent.getCompoundComponents().remove(index);
1695                     break;
1696                 }
1697             }
1698         }
1699         //TODO: handle edit mode
1700         agendaEditor.getAgendaItemLine().getRule().refreshPropositionTree(false);
1701         return super.updateComponent(form, result, request, response);
1702     }
1703 
1704     @RequestMapping(params = "methodToCall=" + "updateCompoundOperator")
1705     public ModelAndView updateCompoundOperator(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
1706             HttpServletRequest request, HttpServletResponse response)
1707             throws Exception {
1708 
1709         AgendaEditor agendaEditor = getAgendaEditor(form);
1710         RuleBo rule = agendaEditor.getAgendaItemLine().getRule();
1711         rule.refreshPropositionTree(false);
1712 
1713         return super.updateComponent(form, result, request, response);
1714     }
1715 }