Coverage Report - org.kuali.rice.krms.impl.ui.AgendaEditorController
 
Classes in this File Line Coverage Branch Coverage Complexity
AgendaEditorController
0%
0/338
0%
0/174
3.275
AgendaEditorController$1
0%
0/1
N/A
3.275
AgendaEditorController$AgendaItemChildAccessor
0%
0/27
0%
0/16
3.275
AgendaEditorController$AgendaItemChildAccessor$Child
0%
0/1
N/A
3.275
AgendaEditorController$AgendaItemInstanceChildAccessor
0%
0/8
N/A
3.275
 
 1  
 /*
 2  
  * Copyright 2007 The Kuali Foundation
 3  
  *
 4  
  * Licensed under the Educational Community License, Version 1.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/ecl1.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 java.util.ArrayList;
 19  
 import java.util.Collections;
 20  
 import java.util.HashMap;
 21  
 import java.util.List;
 22  
 import java.util.Map;
 23  
 
 24  
 import javax.servlet.http.HttpServletRequest;
 25  
 import javax.servlet.http.HttpServletResponse;
 26  
 
 27  
 import org.apache.commons.lang.StringUtils;
 28  
 import org.apache.commons.collections.MapUtils;
 29  
 import org.kuali.rice.krad.service.KRADServiceLocator;
 30  
 import org.kuali.rice.krad.service.SequenceAccessorService;
 31  
 import org.kuali.rice.krad.uif.UifParameters;
 32  
 import org.kuali.rice.krad.util.ObjectUtils;
 33  
 import org.kuali.rice.krad.web.controller.MaintenanceDocumentController;
 34  
 import org.kuali.rice.krad.web.form.MaintenanceForm;
 35  
 import org.kuali.rice.krad.web.form.UifFormBase;
 36  
 import org.kuali.rice.krms.impl.repository.AgendaBo;
 37  
 import org.kuali.rice.krms.impl.repository.AgendaItemBo;
 38  
 import org.kuali.rice.krms.impl.repository.ContextBo;
 39  
 import org.springframework.stereotype.Controller;
 40  
 import org.springframework.validation.BindingResult;
 41  
 import org.springframework.web.bind.annotation.ModelAttribute;
 42  
 import org.springframework.web.bind.annotation.RequestMapping;
 43  
 import org.springframework.web.bind.annotation.RequestMethod;
 44  
 import org.springframework.web.servlet.ModelAndView;
 45  
 
 46  
 /**
 47  
  * Controller for the Test UI Page
 48  
  * 
 49  
  * @author Kuali Rice Team (rice.collab@kuali.org)
 50  
  */
 51  0
 @Controller
 52  
 @RequestMapping(value = "/krmsAgendaEditor")
 53  0
 public class AgendaEditorController extends MaintenanceDocumentController {
 54  
 
 55  
     private static final String AGENDA_ITEM_SELECTED = "agenda_item_selected";
 56  
 
 57  
     private SequenceAccessorService sequenceAccessorService;
 58  
 
 59  
     @Override
 60  
     public MaintenanceForm createInitialForm(HttpServletRequest request) {
 61  0
         return new MaintenanceForm();
 62  
     }
 63  
     
 64  
     /**
 65  
      * This overridden method does extra work on refresh to populate the context and agenda
 66  
      * 
 67  
      * @see org.kuali.rice.krad.web.spring.controller.UifControllerBase#refresh(org.kuali.rice.krad.web.spring.form.UifFormBase, org.springframework.validation.BindingResult, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
 68  
      */
 69  
     @RequestMapping(params = "methodToCall=" + "refresh")
 70  
     @Override
 71  
     public ModelAndView refresh(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 72  
             HttpServletRequest request, HttpServletResponse response)
 73  
             throws Exception {
 74  
         
 75  0
         MapUtils.verbosePrint(System.out, "actionParameters", form.getActionParameters());
 76  0
         MapUtils.verbosePrint(System.out, "requestParameters", request.getParameterMap());
 77  
         
 78  0
         String agendaId = null;
 79  
 
 80  0
         MaintenanceForm maintenanceForm = (MaintenanceForm) form;
 81  0
         String conversionFields = maintenanceForm.getActionParameters().get("conversionFields");
 82  0
         String refreshCaller = request.getParameter("refreshCaller");
 83  
 
 84  
         // handle return from agenda lookup
 85  
         // TODO: this condition is sloppy 
 86  0
         if (refreshCaller != null && refreshCaller.contains("Agenda") && refreshCaller.contains("LookupView") &&
 87  
                 conversionFields != null &&
 88  
                 // TODO: this is sloppy
 89  
                 conversionFields.contains("agenda.id")) {
 90  0
             AgendaEditor editorDocument =
 91  
                     ((AgendaEditor) maintenanceForm.getDocument().getNewMaintainableObject().getDataObject());
 92  0
             agendaId = editorDocument.getAgenda().getId();
 93  0
             AgendaBo agenda = getBusinessObjectService().findBySinglePrimaryKey(AgendaBo.class, agendaId);
 94  0
             editorDocument.setAgenda(agenda);
 95  
 
 96  0
             if (agenda.getContextId() != null) {
 97  0
                 ContextBo context = getBusinessObjectService().findBySinglePrimaryKey(ContextBo.class, agenda.getContextId());
 98  0
                 editorDocument.setContext(context);
 99  
             }
 100  
         }
 101  
         
 102  0
         return super.refresh(form, result, request, response);
 103  
     }
 104  
 
 105  
     /**
 106  
      * This override is used to populate the agenda from the agenda name and context selection of the user.
 107  
      * It is triggered by the refreshWhenChanged property of the MaintenanceView.
 108  
      */
 109  
     @RequestMapping(method = RequestMethod.POST, params = "methodToCall=updateComponent")
 110  
     @Override
 111  
     public ModelAndView updateComponent(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 112  
             HttpServletRequest request, HttpServletResponse response) {
 113  
 
 114  0
         MaintenanceForm maintenanceForm = (MaintenanceForm) form;
 115  0
         AgendaEditor editorDocument =
 116  
                 ((AgendaEditor) maintenanceForm.getDocument().getNewMaintainableObject().getDataObject());
 117  0
         final Map<String, Object> map = new HashMap<String, Object>();
 118  0
         map.put("name", editorDocument.getAgenda().getName());
 119  0
         map.put("contextId", editorDocument.getContext().getId());
 120  
 
 121  0
         AgendaBo agenda = getBusinessObjectService().findByPrimaryKey(AgendaBo.class, Collections.unmodifiableMap(map));
 122  0
         editorDocument.setAgenda(agenda);
 123  
 
 124  0
         return super.updateComponent(form, result, request, response);
 125  
     }
 126  
 
 127  
     /**
 128  
      * This method updates the existing rule in the agenda.
 129  
      */
 130  
     @RequestMapping(params = "methodToCall=" + "goToAddRule")
 131  
     public ModelAndView goToAddRule(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 132  
             HttpServletRequest request, HttpServletResponse response) throws Exception {
 133  0
         AgendaBo agenda = getAgenda(form, request);
 134  
         // this is the root of the tree:
 135  0
         AgendaItemBo firstItem = getFirstAgendaItem(agenda);
 136  0
         String selectedItemId = request.getParameter(AGENDA_ITEM_SELECTED);
 137  
 
 138  0
         if (selectedItemId == null) {
 139  0
             setSelectedAgendaItemId(form, null);
 140  
         } else {
 141  0
             setSelectedAgendaItemId(form, selectedItemId);
 142  
         }
 143  0
         setAgendaItemLine(form, null);
 144  
 
 145  0
         form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-AddRule-Page");
 146  0
         return super.navigate(form, result, request, response);
 147  
     }
 148  
 
 149  
     /**
 150  
      * This method sets the agendaItemLine for adding/editing AgendaItems.
 151  
      * The agendaItemLine is a copy of the agendaItem so that changes are not applied when
 152  
      * they are abandoned.  If the agendaItem is null a new empty agendaItemLine is created.
 153  
      *
 154  
      * @param form
 155  
      * @param agendaItem
 156  
      */
 157  
     private void setAgendaItemLine(UifFormBase form, AgendaItemBo agendaItem) {
 158  0
         MaintenanceForm maintenanceForm = (MaintenanceForm) form;
 159  0
         AgendaEditor editorDocument = ((AgendaEditor)maintenanceForm.getDocument().getDocumentDataObject());
 160  0
         if (agendaItem == null) {
 161  0
             editorDocument.setAgendaItemLine(new AgendaItemBo());
 162  
         } else {
 163  
             // TODO: Add a copy not the reference
 164  0
             editorDocument.setAgendaItemLine((AgendaItemBo) ObjectUtils.deepCopy(agendaItem));
 165  
         }
 166  0
     }
 167  
 
 168  
     /**
 169  
      * This method returns the agendaItemLine from adding/editing AgendaItems.
 170  
      *
 171  
      * @param form
 172  
      * @return agendaItem
 173  
      */
 174  
     private AgendaItemBo getAgendaItemLine(UifFormBase form) {
 175  0
         MaintenanceForm maintenanceForm = (MaintenanceForm) form;
 176  0
         AgendaEditor editorDocument = ((AgendaEditor)maintenanceForm.getDocument().getDocumentDataObject());
 177  0
         return editorDocument.getAgendaItemLine();
 178  
     }
 179  
 
 180  
     /**
 181  
      * This method sets the id of the selected agendaItem.
 182  
      *
 183  
      * @param form
 184  
      * @param selectedItemId
 185  
      */
 186  
     private void setSelectedAgendaItemId(UifFormBase form, String selectedAgendaItemId) {
 187  0
         MaintenanceForm maintenanceForm = (MaintenanceForm) form;
 188  0
         AgendaEditor editorDocument = ((AgendaEditor)maintenanceForm.getDocument().getDocumentDataObject());
 189  0
         editorDocument.setSelectedAgendaItemId(selectedAgendaItemId);
 190  0
     }
 191  
 
 192  
     /**
 193  
      * This method returns the id of the selected agendaItem.
 194  
      *
 195  
      * @param form
 196  
      * @return selectedAgendaItemId
 197  
      */
 198  
     private String getSelectedAgendaItemId(UifFormBase form) {
 199  0
         MaintenanceForm maintenanceForm = (MaintenanceForm) form;
 200  0
         AgendaEditor editorDocument = ((AgendaEditor)maintenanceForm.getDocument().getDocumentDataObject());
 201  0
         return editorDocument.getSelectedAgendaItemId();
 202  
     }
 203  
 
 204  
     /**
 205  
      * This method updates the existing rule in the agenda.
 206  
      */
 207  
     @RequestMapping(params = "methodToCall=" + "goToEditRule")
 208  
     public ModelAndView goToEditRule(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 209  
             HttpServletRequest request, HttpServletResponse response) throws Exception {
 210  
 
 211  0
         AgendaBo agenda = getAgenda(form, request);
 212  
         // this is the root of the tree:
 213  0
         AgendaItemBo firstItem = getFirstAgendaItem(agenda);
 214  0
         String selectedItemId = request.getParameter(AGENDA_ITEM_SELECTED);
 215  0
         AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId);
 216  
 
 217  0
         setSelectedAgendaItemId(form, selectedItemId);
 218  0
         setAgendaItemLine(form, node);
 219  
 
 220  0
         form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-EditRule-Page");
 221  0
         return super.navigate(form, result, request, response);
 222  
     }
 223  
 
 224  
     /**
 225  
      *  This method adds the newly create rule to the agenda.
 226  
      */
 227  
     @RequestMapping(params = "methodToCall=" + "addRule")
 228  
     public ModelAndView addRule(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 229  
             HttpServletRequest request, HttpServletResponse response) throws Exception {
 230  0
         MaintenanceForm maintenanceForm = (MaintenanceForm) form;
 231  0
         AgendaEditor editorDocument =
 232  
                 ((AgendaEditor) maintenanceForm.getDocument().getNewMaintainableObject().getDataObject());
 233  0
         AgendaBo agenda = editorDocument.getAgenda();
 234  0
         AgendaItemBo newAgendaItem = editorDocument.getAgendaItemLine();
 235  0
         newAgendaItem.setId(getSequenceAccessorService().getNextAvailableSequenceNumber("KRMS_AGENDA_ITM_S").toString());
 236  0
         newAgendaItem.setAgendaId(agenda.getId());
 237  0
         if (agenda.getItems() == null) {
 238  0
             agenda.setItems(new ArrayList<AgendaItemBo>());
 239  
         }
 240  0
         if (agenda.getFirstItemId() == null) {
 241  0
             agenda.setFirstItemId(newAgendaItem.getId());
 242  0
             agenda.getItems().add(newAgendaItem);
 243  
         } else {
 244  
             // insert agenda in tree
 245  0
             String selectedAgendaItemId = getSelectedAgendaItemId(form);
 246  0
             if (StringUtils.isBlank(selectedAgendaItemId)) {
 247  
                 // add after the last root node
 248  0
                 AgendaItemBo node = getFirstAgendaItem(agenda);
 249  0
                 while (node.getAlways() != null) {
 250  0
                     node = node.getAlways();
 251  
                 }
 252  0
                 node.setAlwaysId(newAgendaItem.getId());
 253  0
                 node.setAlways(newAgendaItem);
 254  0
             } else {
 255  
                 // add after selected node
 256  0
                 AgendaItemBo firstItem = getFirstAgendaItem(agenda);
 257  0
                 AgendaItemBo node = getAgendaItemById(firstItem, getSelectedAgendaItemId(form));
 258  0
                 newAgendaItem.setAlwaysId(node.getAlwaysId());
 259  0
                 newAgendaItem.setAlways(node.getAlways());
 260  0
                 node.setAlwaysId(newAgendaItem.getId());
 261  0
                 node.setAlways(newAgendaItem);
 262  
             }
 263  
         }
 264  
 
 265  0
         form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-Agenda-Page");
 266  0
         return super.navigate(form, result, request, response);
 267  
     }
 268  
 
 269  
     /**
 270  
      * This method updates the existing rule in the agenda.
 271  
      */
 272  
     @RequestMapping(params = "methodToCall=" + "editRule")
 273  
     public ModelAndView editRule(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 274  
             HttpServletRequest request, HttpServletResponse response) throws Exception {
 275  0
         AgendaBo agenda = getAgenda(form, request);
 276  
         // this is the root of the tree:
 277  0
         AgendaItemBo firstItem = getFirstAgendaItem(agenda);
 278  0
         AgendaItemBo node = getAgendaItemById(firstItem, getSelectedAgendaItemId(form));
 279  0
         AgendaItemBo agendaItemLine = getAgendaItemLine(form);
 280  0
         node.setRule(agendaItemLine.getRule());
 281  
 
 282  0
         form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-Agenda-Page");
 283  0
         return super.navigate(form, result, request, response);
 284  
     }
 285  
 
 286  
     /**
 287  
      * @return the ALWAYS {@link AgendaItemInstanceChildAccessor} for the last ALWAYS child of the instance accessed by the parameter.
 288  
      * It will by definition refer to null.  If the instanceAccessor parameter refers to null, then it will be returned.  This is useful
 289  
      * for adding a youngest child to a sibling group. 
 290  
      */
 291  
     private AgendaItemInstanceChildAccessor getLastChildsAlwaysAccessor(AgendaItemInstanceChildAccessor instanceAccessor) {
 292  0
         AgendaItemBo next = instanceAccessor.getChild();
 293  0
         if (next == null) return instanceAccessor;
 294  0
         while (next.getAlways() != null) { next = next.getAlways(); };
 295  0
         return new AgendaItemInstanceChildAccessor(AgendaItemChildAccessor.always, next);
 296  
     }
 297  
 
 298  
     /**
 299  
      * @return the accessor to the child with the given agendaItemId under the given parent.  This method will search both When TRUE and 
 300  
      * When FALSE sibling groups.  If the instance with the given id is not found, null is returned.
 301  
      */
 302  
     private AgendaItemInstanceChildAccessor getInstanceAccessorToChild(AgendaItemBo parent, String agendaItemId) {
 303  
 
 304  
         // first try When TRUE, then When FALSE via AgendaItemChildAccessor.levelOrderChildren
 305  0
         for (AgendaItemChildAccessor levelOrderChildAccessor : AgendaItemChildAccessor.children) {
 306  
 
 307  0
             AgendaItemBo next = levelOrderChildAccessor.getChild(parent);
 308  
             
 309  
             // if the first item matches, return the accessor from the parent
 310  0
             if (next != null && agendaItemId.equals(next.getId())) return new AgendaItemInstanceChildAccessor(levelOrderChildAccessor, parent);
 311  
 
 312  
             // otherwise walk the children
 313  0
             while (next != null && next.getAlwaysId() != null) {
 314  0
                 if (next.getAlwaysId().equals(agendaItemId)) return new AgendaItemInstanceChildAccessor(AgendaItemChildAccessor.always, next);
 315  
                 // move down
 316  0
                 next = next.getAlways();
 317  
             }
 318  
         }
 319  
         
 320  0
         return null;
 321  
     }
 322  
 
 323  
     @RequestMapping(params = "methodToCall=" + "ajaxRefresh")
 324  
     public ModelAndView ajaxRefresh(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 325  
             HttpServletRequest request, HttpServletResponse response)
 326  
             throws Exception {
 327  
         // call the super method to avoid the agenda tree being reloaded from the db
 328  0
         return super.updateComponent(form, result, request, response);
 329  
     }
 330  
 
 331  
     @RequestMapping(params = "methodToCall=" + "moveUp")
 332  
     public ModelAndView moveUp(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 333  
             HttpServletRequest request, HttpServletResponse response)
 334  
             throws Exception {
 335  0
         moveSelectedSubtreeUp(form, request);
 336  
 
 337  0
         return super.refresh(form, result, request, response);
 338  
     }
 339  
 
 340  
     @RequestMapping(params = "methodToCall=" + "ajaxMoveUp")
 341  
     public ModelAndView ajaxMoveUp(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 342  
             HttpServletRequest request, HttpServletResponse response)
 343  
             throws Exception {
 344  0
         moveSelectedSubtreeUp(form, request);
 345  
 
 346  
         // call the super method to avoid the agenda tree being reloaded from the db
 347  0
         return super.updateComponent(form, result, request, response);
 348  
     }
 349  
 
 350  
     private void moveSelectedSubtreeUp(UifFormBase form, HttpServletRequest request) {
 351  
 
 352  
         /* Rough algorithm for moving a node up.  This is a "level order" move.  Note that in this tree,
 353  
          * level order means something a bit funky.  We are defining a level as it would be displayed in the browser,
 354  
          * so only the traversal of When FALSE or When TRUE links increments the level, since ALWAYS linked nodes are
 355  
          * considered siblings.
 356  
          *
 357  
          * find the following:
 358  
          *   node := the selected node
 359  
          *   parent := the selected node's parent, its containing node (via when true or when false relationship)
 360  
          *   parentsOlderCousin := the parent's level-order predecessor (sibling or cousin)
 361  
          *
 362  
          * if (node is first child in sibling group)
 363  
          *     if (node is in When FALSE group)
 364  
          *         move node to last position in When TRUE group
 365  
          *     else
 366  
          *         find youngest child of parentsOlderCousin and put node after it
 367  
          * else
 368  
          *     move node up within its sibling group
 369  
          */
 370  
 
 371  0
         AgendaBo agenda = getAgenda(form, request);
 372  
         // this is the root of the tree:
 373  0
         AgendaItemBo firstItem = getFirstAgendaItem(agenda);
 374  
 
 375  0
         String selectedItemId = request.getParameter(AGENDA_ITEM_SELECTED);
 376  0
         AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId);
 377  0
         AgendaItemBo parent = getParent(firstItem, selectedItemId);
 378  0
         AgendaItemBo parentsOlderCousin = (parent == null) ? null : getNextOldestOfSameGeneration(firstItem, parent);
 379  
 
 380  0
         AgendaItemChildAccessor childAccessor = getOldestChildAccessor(node, parent);
 381  0
         if (childAccessor != null) { // node is first child in sibling group
 382  0
             if (childAccessor == AgendaItemChildAccessor.whenFalse) {
 383  
                 // move node to last position in When TRUE group
 384  0
                 AgendaItemInstanceChildAccessor youngestWhenTrueSiblingInsertionPoint =
 385  
                         getLastChildsAlwaysAccessor(new AgendaItemInstanceChildAccessor(AgendaItemChildAccessor.whenTrue, parent));
 386  0
                 youngestWhenTrueSiblingInsertionPoint.setChild(node);
 387  0
                 AgendaItemChildAccessor.whenFalse.setChild(parent, node.getAlways());
 388  0
                 AgendaItemChildAccessor.always.setChild(node, null);
 389  
 
 390  0
             } else if (parentsOlderCousin != null) {
 391  
                 // find youngest child of parentsOlderCousin and put node after it
 392  0
                 AgendaItemInstanceChildAccessor youngestWhenFalseSiblingInsertionPoint =
 393  
                         getLastChildsAlwaysAccessor(new AgendaItemInstanceChildAccessor(AgendaItemChildAccessor.whenFalse, parentsOlderCousin));
 394  0
                 youngestWhenFalseSiblingInsertionPoint.setChild(node);
 395  0
                 AgendaItemChildAccessor.whenTrue.setChild(parent, node.getAlways());
 396  0
                 AgendaItemChildAccessor.always.setChild(node, null);
 397  0
             }
 398  0
         } else if (!selectedItemId.equals(firstItem.getId())) { // conditional to miss special case of first node
 399  
 
 400  0
             AgendaItemBo bogusRootNode = null;
 401  0
             if (parent == null) {
 402  
                 // special case, this is a top level sibling. rig up special parent node
 403  0
                 bogusRootNode = new AgendaItemBo();
 404  0
                 AgendaItemChildAccessor.whenTrue.setChild(bogusRootNode, firstItem);
 405  0
                 parent = bogusRootNode;
 406  
             }
 407  
 
 408  
             // move node up within its sibling group
 409  0
             AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId());
 410  0
             AgendaItemBo olderSibling = accessorToSelectedNode.getInstance();
 411  0
             AgendaItemInstanceChildAccessor accessorToOlderSibling = getInstanceAccessorToChild(parent, olderSibling.getId());
 412  
 
 413  0
             accessorToOlderSibling.setChild(node);
 414  0
             accessorToSelectedNode.setChild(node.getAlways());
 415  0
             AgendaItemChildAccessor.always.setChild(node, olderSibling);
 416  
 
 417  0
             if (bogusRootNode != null) {
 418  
                 // clean up special case with bogus root node
 419  0
                 agenda.setFirstItemId(bogusRootNode.getWhenTrueId());
 420  
             }
 421  
         }
 422  0
     }
 423  
 
 424  
     @RequestMapping(params = "methodToCall=" + "moveDown")
 425  
     public ModelAndView moveDown(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 426  
             HttpServletRequest request, HttpServletResponse response)
 427  
             throws Exception {
 428  0
         moveSelectedSubtreeDown(form, request);
 429  
         
 430  0
         return super.refresh(form, result, request, response);
 431  
     }
 432  
 
 433  
     @RequestMapping(params = "methodToCall=" + "ajaxMoveDown")
 434  
     public ModelAndView ajaxMoveDown(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 435  
             HttpServletRequest request, HttpServletResponse response)
 436  
             throws Exception {
 437  0
         moveSelectedSubtreeDown(form, request);
 438  
 
 439  
         // call the super method to avoid the agenda tree being reloaded from the db
 440  0
         return super.updateComponent(form, result, request, response);
 441  
     }
 442  
 
 443  
     private void moveSelectedSubtreeDown(UifFormBase form, HttpServletRequest request) {
 444  
 
 445  
         /* Rough algorithm for moving a node down.  This is a "level order" move.  Note that in this tree,
 446  
          * level order means something a bit funky.  We are defining a level as it would be displayed in the browser,
 447  
          * so only the traversal of When FALSE or When TRUE links increments the level, since ALWAYS linked nodes are
 448  
          * considered siblings.
 449  
          *
 450  
          * find the following:
 451  
          *   node := the selected node
 452  
          *   parent := the selected node's parent, its containing node (via when true or when false relationship)
 453  
          *   parentsYoungerCousin := the parent's level-order successor (sibling or cousin)
 454  
          *
 455  
          * if (node is last child in sibling group)
 456  
          *     if (node is in When TRUE group)
 457  
          *         move node to first position in When FALSE group
 458  
          *     else
 459  
          *         move to first child of parentsYoungerCousin
 460  
          * else
 461  
          *     move node down within its sibling group
 462  
          */
 463  
 
 464  0
         AgendaBo agenda = getAgenda(form, request);
 465  
         // this is the root of the tree:
 466  0
         AgendaItemBo firstItem = getFirstAgendaItem(agenda);
 467  
 
 468  0
         String selectedItemId = request.getParameter(AGENDA_ITEM_SELECTED);
 469  0
         AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId);
 470  0
         AgendaItemBo parent = getParent(firstItem, selectedItemId);
 471  0
         AgendaItemBo parentsYoungerCousin = (parent == null) ? null : getNextYoungestOfSameGeneration(firstItem, parent);
 472  
 
 473  0
         if (node.getAlways() == null && parent != null) { // node is last child in sibling group
 474  
             // set link to selected node to null
 475  0
             if (parent.getWhenTrue() != null && isSiblings(parent.getWhenTrue(), node)) { // node is in When TRUE group
 476  
                 // move node to first child under When FALSE
 477  
 
 478  0
                 AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId());
 479  0
                 accessorToSelectedNode.setChild(null);
 480  
 
 481  0
                 AgendaItemBo parentsFirstChild = parent.getWhenFalse();
 482  0
                 AgendaItemChildAccessor.whenFalse.setChild(parent, node);
 483  0
                 AgendaItemChildAccessor.always.setChild(node, parentsFirstChild);
 484  0
             } else if (parentsYoungerCousin != null) { // node is in the When FALSE group
 485  
                 // move to first child of parentsYoungerCousin under When TRUE
 486  
 
 487  0
                 AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId());
 488  0
                 accessorToSelectedNode.setChild(null);
 489  
 
 490  0
                 AgendaItemBo parentsYoungerCousinsFirstChild = parentsYoungerCousin.getWhenTrue();
 491  0
                 AgendaItemChildAccessor.whenTrue.setChild(parentsYoungerCousin, node);
 492  0
                 AgendaItemChildAccessor.always.setChild(node, parentsYoungerCousinsFirstChild);
 493  0
             }
 494  0
         } else if (node.getAlways() != null) { // move node down within its sibling group
 495  
 
 496  0
             AgendaItemBo bogusRootNode = null;
 497  0
             if (parent == null) {
 498  
                 // special case, this is a top level sibling. rig up special parent node
 499  0
                 bogusRootNode = new AgendaItemBo();
 500  0
                 AgendaItemChildAccessor.whenFalse.setChild(bogusRootNode, firstItem);
 501  0
                 parent = bogusRootNode;
 502  
             }
 503  
 
 504  
             // move node down within its sibling group
 505  0
             AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId());
 506  0
             AgendaItemBo youngerSibling = node.getAlways();
 507  0
             accessorToSelectedNode.setChild(youngerSibling);
 508  0
             AgendaItemChildAccessor.always.setChild(node, youngerSibling.getAlways());
 509  0
             AgendaItemChildAccessor.always.setChild(youngerSibling, node);
 510  
 
 511  0
             if (bogusRootNode != null) {
 512  
                 // clean up special case with bogus root node
 513  0
                 agenda.setFirstItemId(bogusRootNode.getWhenFalseId());
 514  
             }
 515  
         }
 516  0
     }
 517  
 
 518  
     @RequestMapping(params = "methodToCall=" + "moveLeft")
 519  
     public ModelAndView moveLeft(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 520  
             HttpServletRequest request, HttpServletResponse response)
 521  
             throws Exception {
 522  0
         moveSelectedSubtreeLeft(form, request);
 523  
         
 524  0
         return super.refresh(form, result, request, response);
 525  
     }
 526  
 
 527  
     @RequestMapping(params = "methodToCall=" + "ajaxMoveLeft")
 528  
     public ModelAndView ajaxMoveLeft(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 529  
             HttpServletRequest request, HttpServletResponse response)
 530  
             throws Exception {
 531  
 
 532  0
         moveSelectedSubtreeLeft(form, request);
 533  
 
 534  
         // call the super method to avoid the agenda tree being reloaded from the db
 535  0
         return super.updateComponent(form, result, request, response);
 536  
     }
 537  
 
 538  
     private void moveSelectedSubtreeLeft(UifFormBase form, HttpServletRequest request) {
 539  
 
 540  
         /*
 541  
          * Move left means make it a younger sibling of it's parent.
 542  
          */
 543  
 
 544  0
         AgendaBo agenda = getAgenda(form, request);
 545  
         // this is the root of the tree:
 546  0
         AgendaItemBo firstItem = getFirstAgendaItem(agenda);
 547  
 
 548  0
         String selectedItemId = request.getParameter(AGENDA_ITEM_SELECTED);
 549  0
         AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId);
 550  0
         AgendaItemBo parent = getParent(firstItem, selectedItemId);
 551  
 
 552  0
         if (parent != null) {
 553  0
             AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId());
 554  0
             accessorToSelectedNode.setChild(node.getAlways());
 555  
 
 556  0
             AgendaItemChildAccessor.always.setChild(node, parent.getAlways());
 557  0
             AgendaItemChildAccessor.always.setChild(parent, node);
 558  
         }
 559  0
     }
 560  
 
 561  
 
 562  
     @RequestMapping(params = "methodToCall=" + "moveRight")
 563  
     public ModelAndView moveRight(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 564  
             HttpServletRequest request, HttpServletResponse response)
 565  
             throws Exception {
 566  
 
 567  0
         moveSelectedSubtreeRight(form, request);
 568  
 
 569  0
         return super.refresh(form, result, request, response);
 570  
     }
 571  
 
 572  
     @RequestMapping(params = "methodToCall=" + "ajaxMoveRight")
 573  
     public ModelAndView ajaxMoveRight(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 574  
             HttpServletRequest request, HttpServletResponse response)
 575  
             throws Exception {
 576  
 
 577  0
         moveSelectedSubtreeRight(form, request);
 578  
 
 579  
         // call the super method to avoid the agenda tree being reloaded from the db
 580  0
         return super.updateComponent(form, result, request, response);
 581  
     }
 582  
 
 583  
     private void moveSelectedSubtreeRight(UifFormBase form, HttpServletRequest request) {
 584  
 
 585  
         /*
 586  
          * Move right prefers moving to bottom of upper sibling's When FALSE branch
 587  
          * ... otherwise ..
 588  
          * moves to top of lower sibling's When TRUE branch
 589  
          */
 590  
 
 591  0
         AgendaBo agenda = getAgenda(form, request);
 592  
         // this is the root of the tree:
 593  0
         AgendaItemBo firstItem = getFirstAgendaItem(agenda);
 594  
 
 595  0
         String selectedItemId = request.getParameter(AGENDA_ITEM_SELECTED);
 596  0
         AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId);
 597  0
         AgendaItemBo parent = getParent(firstItem, selectedItemId);
 598  
 
 599  0
         AgendaItemBo bogusRootNode = null;
 600  0
         if (parent == null) {
 601  
             // special case, this is a top level sibling. rig up special parent node
 602  0
             bogusRootNode = new AgendaItemBo();
 603  0
             AgendaItemChildAccessor.whenFalse.setChild(bogusRootNode, firstItem);
 604  0
             parent = bogusRootNode;
 605  
         }
 606  
 
 607  0
         AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId());
 608  0
         AgendaItemBo olderSibling = (accessorToSelectedNode.getInstance() == parent) ? null : accessorToSelectedNode.getInstance();
 609  
 
 610  0
         if (olderSibling != null) {
 611  0
             accessorToSelectedNode.setChild(node.getAlways());
 612  0
             AgendaItemInstanceChildAccessor yougestWhenFalseSiblingInsertionPoint =
 613  
                     getLastChildsAlwaysAccessor(new AgendaItemInstanceChildAccessor(AgendaItemChildAccessor.whenFalse, olderSibling));
 614  0
             yougestWhenFalseSiblingInsertionPoint.setChild(node);
 615  0
             AgendaItemChildAccessor.always.setChild(node, null);
 616  0
         } else if (node.getAlways() != null) { // has younger sibling
 617  0
             accessorToSelectedNode.setChild(node.getAlways());
 618  0
             AgendaItemBo childsWhenTrue = node.getAlways().getWhenTrue();
 619  0
             AgendaItemChildAccessor.whenTrue.setChild(node.getAlways(), node);
 620  0
             AgendaItemChildAccessor.always.setChild(node, childsWhenTrue);
 621  
         }
 622  
 
 623  0
         if (bogusRootNode != null) {
 624  
             // clean up special case with bogus root node
 625  0
             agenda.setFirstItemId(bogusRootNode.getWhenFalseId());
 626  
         }
 627  0
     }
 628  
 
 629  
     private boolean isSiblings(AgendaItemBo cousin1, AgendaItemBo cousin2) {
 630  0
         if (cousin1.equals(cousin2)) return true; // this is a bit abusive
 631  
         
 632  
         // can you walk to c1 from ALWAYS links of c2?
 633  0
         AgendaItemBo candidate = cousin2;
 634  0
         while (null != (candidate = candidate.getAlways())) {
 635  0
             if (candidate.equals(cousin1)) return true;
 636  
         }
 637  
         // can you walk to c2 from ALWAYS links of c1?
 638  0
         candidate = cousin1;
 639  0
         while (null != (candidate = candidate.getAlways())) {
 640  0
             if (candidate.equals(cousin2)) return true;
 641  
         }
 642  0
         return false;
 643  
     }
 644  
 
 645  
     /**
 646  
      * This method returns the level order accessor (getWhenTrue or getWhenFalse) that relates the parent directly 
 647  
      * to the child.  If the two nodes don't have such a relationship, null is returned. 
 648  
      * Note that this only finds accessors for oldest children, not younger siblings.
 649  
      */
 650  
     private AgendaItemChildAccessor getOldestChildAccessor(
 651  
             AgendaItemBo child, AgendaItemBo parent) {
 652  0
         AgendaItemChildAccessor levelOrderChildAccessor = null;
 653  
         
 654  0
         if (parent != null) {
 655  0
             for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.children) {
 656  0
                 if (child.equals(childAccessor.getChild(parent))) {
 657  0
                     levelOrderChildAccessor = childAccessor;
 658  0
                     break;
 659  
                 }
 660  
             }
 661  
         }
 662  0
         return levelOrderChildAccessor;
 663  
     }
 664  
     
 665  
     /**
 666  
      * This method finds and returns the first agenda item in the agenda, or null if there are no items presently
 667  
      * 
 668  
      * @param agenda
 669  
      * @return
 670  
      */
 671  
     private AgendaItemBo getFirstAgendaItem(AgendaBo agenda) {
 672  0
         AgendaItemBo firstItem = null;
 673  0
         for (AgendaItemBo agendaItem : agenda.getItems()) {
 674  0
             if (agenda.getFirstItemId().equals(agendaItem.getId())) {
 675  0
                 firstItem = agendaItem;
 676  0
                 break;
 677  
             }
 678  
         }
 679  0
         return firstItem;
 680  
     }
 681  
     
 682  
     /**
 683  
      * @return the closest younger sibling of the agenda item with the given ID, and if there is no such sibling, the closest younger cousin.
 684  
      * If there is no such cousin either, then null is returned.
 685  
      */
 686  
     private AgendaItemBo getNextYoungestOfSameGeneration(AgendaItemBo root, AgendaItemBo agendaItem) {
 687  
 
 688  0
         int genNumber = getAgendaItemGenerationNumber(0, root, agendaItem.getId());
 689  0
         List<AgendaItemBo> genList = new ArrayList<AgendaItemBo>();
 690  0
         buildAgendaItemGenerationList(genList, root, 0, genNumber);
 691  
 
 692  0
         int itemIndex = genList.indexOf(agendaItem);
 693  0
         if (genList.size() > itemIndex + 1) return genList.get(itemIndex + 1);
 694  
 
 695  0
         return null;
 696  
     }
 697  
 
 698  
     private int getAgendaItemGenerationNumber(int currentLevel, AgendaItemBo node, String agendaItemId) {
 699  0
         int result = -1;
 700  0
         if (agendaItemId.equals(node.getId())) {
 701  0
             result = currentLevel;
 702  
         } else {
 703  0
             for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) {
 704  0
                 AgendaItemBo child = childAccessor.getChild(node);
 705  0
                 if (child != null) {
 706  0
                     int nextLevel = currentLevel;
 707  
                     // we don't change the level order parent when we traverse ALWAYS links
 708  0
                     if (childAccessor != AgendaItemChildAccessor.always) {
 709  0
                         nextLevel = currentLevel +1;
 710  
                     }
 711  0
                     result = getAgendaItemGenerationNumber(nextLevel, child, agendaItemId);
 712  0
                     if (result != -1) break;
 713  
                 }
 714  
             }
 715  
         }
 716  0
         return result;
 717  
     }
 718  
 
 719  
     private void buildAgendaItemGenerationList(List<AgendaItemBo> genList, AgendaItemBo node, int currentLevel, int generation) {
 720  0
         if (currentLevel == generation) {
 721  0
             genList.add(node);
 722  
         }
 723  
 
 724  0
         if (currentLevel > generation) return;
 725  
 
 726  0
         for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) {
 727  0
             AgendaItemBo child = childAccessor.getChild(node);
 728  0
             if (child != null) {
 729  0
                 int nextLevel = currentLevel;
 730  
                 // we don't change the level order parent when we traverse ALWAYS links
 731  0
                 if (childAccessor != AgendaItemChildAccessor.always) {
 732  0
                     nextLevel = currentLevel +1;
 733  
                 }
 734  0
                 buildAgendaItemGenerationList(genList, child, nextLevel, generation);
 735  
             }
 736  
         }
 737  0
     }
 738  
     
 739  
 
 740  
     /**
 741  
      * @return the closest older sibling of the agenda item with the given ID, and if there is no such sibling, the closest older cousin.
 742  
      * If there is no such cousin either, then null is returned.
 743  
      */
 744  
     private AgendaItemBo getNextOldestOfSameGeneration(AgendaItemBo root, AgendaItemBo agendaItem) {
 745  
 
 746  0
         int genNumber = getAgendaItemGenerationNumber(0, root, agendaItem.getId());
 747  0
         List<AgendaItemBo> genList = new ArrayList<AgendaItemBo>();
 748  0
         buildAgendaItemGenerationList(genList, root, 0, genNumber);
 749  
 
 750  0
         int itemIndex = genList.indexOf(agendaItem);
 751  0
         if (itemIndex >= 1) return genList.get(itemIndex - 1);
 752  
 
 753  0
         return null;
 754  
     }
 755  
     
 756  
 
 757  
     /**
 758  
      * returns the parent of the item with the passed in id.  Note that {@link AgendaItemBo}s related by ALWAYS relationships are considered siblings.
 759  
      */ 
 760  
     private AgendaItemBo getParent(AgendaItemBo root, String agendaItemId) {
 761  0
         return getParentHelper(root, null, agendaItemId);
 762  
     }
 763  
     
 764  
     private AgendaItemBo getParentHelper(AgendaItemBo node, AgendaItemBo levelOrderParent, String agendaItemId) {
 765  0
         AgendaItemBo result = null;
 766  0
         if (agendaItemId.equals(node.getId())) {
 767  0
             result = levelOrderParent;
 768  
         } else {
 769  0
             for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) {
 770  0
                 AgendaItemBo child = childAccessor.getChild(node);
 771  0
                 if (child != null) {
 772  
                     // we don't change the level order parent when we traverse ALWAYS links 
 773  0
                     AgendaItemBo lop = (childAccessor == AgendaItemChildAccessor.always) ? levelOrderParent : node;
 774  0
                     result = getParentHelper(child, lop, agendaItemId);
 775  0
                     if (result != null) break;
 776  
                 }
 777  
             }
 778  
         }
 779  0
         return result;
 780  
     }
 781  
 
 782  
     /**
 783  
      * Search the tree for the agenda item with the given id.
 784  
      */
 785  
     private AgendaItemBo getAgendaItemById(AgendaItemBo node, String agendaItemId) {
 786  0
         if (node == null) throw new IllegalArgumentException("node must be non-null");
 787  
 
 788  0
         AgendaItemBo result = null;
 789  
         
 790  0
         if (agendaItemId.equals(node.getId())) {
 791  0
             result = node;
 792  
         } else {
 793  0
             for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) {
 794  0
                 AgendaItemBo child = childAccessor.getChild(node);
 795  0
                 if (child != null) {
 796  0
                     result = getAgendaItemById(child, agendaItemId);
 797  0
                     if (result != null) break;
 798  
                 }
 799  
             }
 800  
         } 
 801  0
         return result;
 802  
     }
 803  
 
 804  
     /**
 805  
      * This method gets the agenda from the form
 806  
      * 
 807  
      * @param form
 808  
      * @param request
 809  
      * @return
 810  
      */
 811  
     private AgendaBo getAgenda(UifFormBase form, HttpServletRequest request) {
 812  0
         MaintenanceForm maintenanceForm = (MaintenanceForm) form;
 813  0
         AgendaEditor editorDocument = ((AgendaEditor)maintenanceForm.getDocument().getDocumentDataObject());
 814  0
         AgendaBo agenda = editorDocument.getAgenda();
 815  0
         return agenda;
 816  
     }
 817  
 
 818  
     private void treeToInOrderList(AgendaItemBo agendaItem, List<AgendaItemBo> listToBuild) {
 819  0
         listToBuild.add(agendaItem);
 820  0
         for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) {
 821  0
             AgendaItemBo child = childAccessor.getChild(agendaItem);
 822  0
             if (child != null) treeToInOrderList(child, listToBuild);
 823  
         }
 824  0
     }
 825  
 
 826  
     
 827  
     @RequestMapping(params = "methodToCall=" + "delete")
 828  
     public ModelAndView delete(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 829  
             HttpServletRequest request, HttpServletResponse response)
 830  
             throws Exception {
 831  
 
 832  0
         deleteSelectedSubtree(form, request);
 833  
 
 834  0
         return super.refresh(form, result, request, response);
 835  
     }
 836  
 
 837  
     @RequestMapping(params = "methodToCall=" + "ajaxDelete")
 838  
     public ModelAndView ajaxDelete(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 839  
             HttpServletRequest request, HttpServletResponse response)
 840  
             throws Exception {
 841  
 
 842  0
         deleteSelectedSubtree(form, request);
 843  
 
 844  
         // call the super method to avoid the agenda tree being reloaded from the db
 845  0
         return super.updateComponent(form, result, request, response);
 846  
     }
 847  
 
 848  
     
 849  0
     private void deleteSelectedSubtree(UifFormBase form, HttpServletRequest request) {AgendaBo agenda = getAgenda(form, request);
 850  0
         AgendaItemBo firstItem = getFirstAgendaItem(agenda);
 851  
 
 852  0
         String agendaItemSelected = request.getParameter(AGENDA_ITEM_SELECTED);
 853  
 
 854  0
         if (firstItem != null) {
 855  
             // need to handle the first item here, our recursive method won't handle it.
 856  0
             if (agendaItemSelected.equals(firstItem.getAgendaId())) {
 857  0
                 agenda.setFirstItemId(firstItem.getAlwaysId());
 858  
             } else {
 859  0
                 deleteAgendaItem(firstItem, agendaItemSelected);
 860  
             }
 861  
         }
 862  0
     }
 863  
 
 864  
     private void deleteAgendaItem(AgendaItemBo root, String agendaItemIdToDelete) {
 865  0
         if (deleteAgendaItem(root, AgendaItemChildAccessor.whenTrue, agendaItemIdToDelete) || 
 866  
                 deleteAgendaItem(root, AgendaItemChildAccessor.whenFalse, agendaItemIdToDelete) || 
 867  
                 deleteAgendaItem(root, AgendaItemChildAccessor.always, agendaItemIdToDelete)); // TODO: this is confusing, refactor
 868  0
     }
 869  
     
 870  
     private boolean deleteAgendaItem(AgendaItemBo agendaItem, AgendaItemChildAccessor childAccessor, String agendaItemIdToDelete) {
 871  0
         if (agendaItem == null || childAccessor.getChild(agendaItem) == null) return false;
 872  0
         if (agendaItemIdToDelete.equals(childAccessor.getChild(agendaItem).getId())) {
 873  
             // delete the child in such a way that any ALWAYS children don't get lost from the tree
 874  0
             AgendaItemBo grandchildToKeep = childAccessor.getChild(agendaItem).getAlways();
 875  0
             childAccessor.setChild(agendaItem, grandchildToKeep);
 876  0
             return true;
 877  
         } else {
 878  0
             AgendaItemBo child = childAccessor.getChild(agendaItem);
 879  
             // recurse
 880  0
             for (AgendaItemChildAccessor nextChildAccessor : AgendaItemChildAccessor.linkedNodes) {
 881  0
                 if (deleteAgendaItem(child, nextChildAccessor, agendaItemIdToDelete)) return true;
 882  
             }
 883  
         }
 884  0
         return false;
 885  
     }
 886  
 
 887  
     /**
 888  
      * binds a child accessor to an AgendaItemBo instance
 889  
      */
 890  
     private static class AgendaItemInstanceChildAccessor {
 891  
         
 892  
         private final AgendaItemChildAccessor accessor;
 893  
         private final AgendaItemBo instance;
 894  
 
 895  0
         public AgendaItemInstanceChildAccessor(AgendaItemChildAccessor accessor, AgendaItemBo instance) {
 896  0
             this.accessor = accessor;
 897  0
             this.instance = instance;
 898  0
         }
 899  
         
 900  
         public void setChild(AgendaItemBo child) {
 901  0
             accessor.setChild(instance, child);
 902  0
         }
 903  
         
 904  
         public AgendaItemBo getChild() {
 905  0
             return accessor.getChild(instance);
 906  
         }
 907  
         
 908  0
         public AgendaItemBo getInstance() { return instance; }
 909  
     }
 910  
     
 911  
     /**
 912  
      * This class abstracts getting and setting a child of an AgendaItemBo, making some recursive operations 
 913  
      * require less boiler plate 
 914  
      */
 915  0
     private static class AgendaItemChildAccessor {
 916  
         
 917  0
         private enum Child { WHEN_TRUE, WHEN_FALSE, ALWAYS };
 918  
         
 919  0
         private static final AgendaItemChildAccessor whenTrue = new AgendaItemChildAccessor(Child.WHEN_TRUE); 
 920  0
         private static final AgendaItemChildAccessor whenFalse = new AgendaItemChildAccessor(Child.WHEN_FALSE); 
 921  0
         private static final AgendaItemChildAccessor always = new AgendaItemChildAccessor(Child.ALWAYS); 
 922  
 
 923  
         /**
 924  
          * Accessors for all linked items
 925  
          */
 926  0
         private static final AgendaItemChildAccessor [] linkedNodes = { whenTrue, whenFalse, always };
 927  
         
 928  
         /**
 929  
          * Accessors for children (so ALWAYS is omitted);
 930  
          */
 931  0
         private static final AgendaItemChildAccessor [] children = { whenTrue, whenFalse };
 932  
         
 933  
         private final Child whichChild;
 934  
         
 935  0
         private AgendaItemChildAccessor(Child whichChild) {
 936  0
             if (whichChild == null) throw new IllegalArgumentException("whichChild must be non-null");
 937  0
             this.whichChild = whichChild;
 938  0
         }
 939  
         
 940  
         /**
 941  
          * @return the referenced child
 942  
          */
 943  
         public AgendaItemBo getChild(AgendaItemBo parent) {
 944  0
             switch (whichChild) {
 945  0
             case WHEN_TRUE: return parent.getWhenTrue();
 946  0
             case WHEN_FALSE: return parent.getWhenFalse();
 947  0
             case ALWAYS: return parent.getAlways();
 948  0
             default: throw new IllegalStateException();
 949  
             }
 950  
         }
 951  
         
 952  
         /**
 953  
          * Sets the child reference and the child id 
 954  
          */
 955  
         public void setChild(AgendaItemBo parent, AgendaItemBo child) {
 956  0
             switch (whichChild) {
 957  
             case WHEN_TRUE: 
 958  0
                 parent.setWhenTrue(child);
 959  0
                 parent.setWhenTrueId(child == null ? null : child.getId());
 960  0
                 break;
 961  
             case WHEN_FALSE:
 962  0
                 parent.setWhenFalse(child);
 963  0
                 parent.setWhenFalseId(child == null ? null : child.getId());
 964  0
                 break;
 965  
             case ALWAYS:
 966  0
                 parent.setAlways(child);
 967  0
                 parent.setAlwaysId(child == null ? null : child.getId());
 968  0
                 break;
 969  0
             default: throw new IllegalStateException();
 970  
             }
 971  0
         }
 972  
     }
 973  
 
 974  
     protected SequenceAccessorService getSequenceAccessorService() {
 975  0
         if ( sequenceAccessorService == null ) {
 976  0
             sequenceAccessorService = KRADServiceLocator.getSequenceAccessorService();
 977  
         }
 978  0
         return sequenceAccessorService;
 979  
     }
 980  
     
 981  
 }