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