View Javadoc

1   /* ===================================================================
2    * Copyright 2002-04 SRI International.
3    * Released under the MOZILLA PUBLIC LICENSE Version 1.1
4    * which can be obtained at http://www.mozilla.org/MPL/MPL-1.1.html
5    * This software is distributed on an "AS IS"
6    * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.
7    * See the License for the specific language governing rights and
8    * limitations under the License.
9    * =================================================================== */
10  package com.sri.emo.controller;
11  
12  import com.jcorporate.expresso.core.controller.*;
13  import com.jcorporate.expresso.core.db.DBException;
14  import com.jcorporate.expresso.core.db.exception.DBRecordNotFoundException;
15  import com.jcorporate.expresso.core.dbobj.MultiDBObject;
16  import com.jcorporate.expresso.core.misc.RecordPaginator;
17  import com.jcorporate.expresso.core.misc.StringUtil;
18  import com.jcorporate.expresso.core.registry.RequestRegistry;
19  import com.jcorporate.expresso.core.security.SuperUser;
20  import com.jcorporate.expresso.core.security.User;
21  import com.jcorporate.expresso.kernel.util.FastStringBuffer;
22  import com.jcorporate.expresso.services.dbobj.*;
23  import com.sri.common.controller.StateHandler;
24  import com.sri.common.taglib.InputTag;
25  import com.sri.emo.EmoSchema;
26  import com.sri.emo.controller.nodefilter.DoFilterNodes;
27  import com.sri.emo.controller.nodefilter.NodeFilter;
28  import com.sri.emo.dbobj.*;
29  import com.sri.emo.dbobj.selectiontree.TreeDeletionSelector;
30  
31  import java.util.*;
32  
33  /***
34   * Handle Node manipulation.
35   *
36   * @author larry hamel
37   */
38  public class NodeAction extends NodeController {
39      /***
40       *
41       */
42      private static final long serialVersionUID = 1L;
43  
44      // states
45      /***
46       * Constant for access to state "List Nodes"
47       */
48      public static final String LIST_NODE = "listNodes";
49  
50      /***
51       * Constant for access to state "Prompt Edit Picklist For Nodes"
52       */
53      public static final String PROMPT_PICKLIST_NODE = "promptPickListNodes";
54  
55      /***
56       * Constant for access to state "Save Picklist for nodes"
57       */
58      public static final String DO_PICKLIST_NODE = "doPickListNodes";
59  
60      /***
61       * Constant for access to state "Prompt Edit Attribute"
62       */
63      public static final String PROMPT_EDIT_ATTRIB = "promptEditAttrib";
64  
65      /***
66       * Constant for access to state "Save Node Attributes"
67       */
68      public static final String DO_EDIT_ATTRIB = "doEditAttrib";
69  
70      /***
71       * Constant for access to state "Delete Node"
72       */
73      public static final String DO_DELETE_NODE = "doDeleteNode";
74  
75      /***
76       * Constant for access to state "Prompt Delete Node"
77       */
78      public static final String PROMPT_DELETE_NODE = "promptDeleteNode";
79  
80      /***
81       * Constant for access to state "Prompt Delete A Node Tree"
82       */
83      public static final String PROMPT_DELETE_NODE_TREE = "promptDeleteNodeTree";
84  
85      public static final String DO_EDIT_MORE_ATTRIB = "doEditMoreAttrib";
86  
87      public static final String DO_ATTRIB_PICK = "doAttribPick";
88  
89      public static final String DO_DELETE_ONE_ATTRIB = "doDeleteAttrib";
90  
91      /***
92       * Constant for access to state "Prompt Clone Node"
93       */
94      public static final String PROMPT_CLONE_NODE = "promptCloneNode";
95  
96      /***
97       * Constant for access to state "Prompt Clone Node and its Parts"
98       */
99      public static final String PROMPT_CLONE_TREE = "promptCloneTree";
100 
101     /***
102      * Constant for access to state "Clone a Tree Of Nodes"
103      */
104     public static final String DO_CLONE_TREE = "doCloneTree";
105 
106     /***
107      * Constant for access to state "Clone a Single Node"
108      */
109     public static final String DO_CLONE_NODE = "doCloneNode";
110 
111     public static final String DO_ORPHAN_CLONE_NODE = "doOrphanCloneNode";
112 
113     public static final String ADD_GROUP = "addGroup";
114 
115     public static final String INDEX = "index";
116 
117     public static final String PROMPT_EDIT_PERMS = "promptEditPerms";
118 
119     public static final String REMOVE_GROUP = "removeGroup";
120 
121     public static final String PROMPT_ADD_ATTRIB = "promptAddAttrib";
122 
123     public static final String PROMPT_ATTRIB_PICK = "promptAttribPick";
124 
125     public static final String PROMPT_EDIT_ONE_ATTRIB = "promptEditOneAttrib";
126 
127     public static final String DO_EDIT_ONE_ATTRIB = "doEditOneAttrib";
128 
129     public static final String LIST_ALL_TYPES = "listAllTypes";
130 
131     /***
132      * Constant for access to state "View Node as Tree"
133      */
134     public static final String VIEW_TREE = "viewTree";
135 
136     /***
137      * Constant for access to state "View Single Attribute"
138      */
139     public static final String VIEW_SINGLE_ATTRIBUTE = "viewSingleAttribute";
140 
141     /***
142      * checkbox name
143      */
144     public static final String PICK_LIST_ITEM = "PICK_LIST_ITEM";
145 
146     public static final String ARE_MULTIPLE_ITEMS = "ARE_MULTIPLE_ITEMS";
147 
148     public static final String TYPE_TRANS = "allTypes";
149 
150     public static final String PERMS_BLOCK = "PERMS_BLOCK";
151 
152     /***
153      * setup code for list of node types to omit from a tree listing; use "|" to
154      * delimit items in one string.
155      */
156     public static final String OMIT_TYPES = "OMIT_TYPES";
157 
158     /***
159      * sorting columns for lists
160      */
161     public static final String TITLE_SORT = "TITLE_SORT";
162 
163     public static final String DESCENDING_FLAG = "_DESC";
164 
165     public static final String ASCENDING_FLAG = "_ASC";
166 
167     public static final String TITLE_SORT_DESC = TITLE_SORT + DESCENDING_FLAG;
168 
169     public static final String GROUP_SORT = "GROUP_SORT";
170 
171     public static final String GROUP_SORT_DESC = GROUP_SORT + DESCENDING_FLAG;
172 
173     public static final String OWNER_SORT = "OWNER_SORT";
174 
175     public static final String OWNER_SORT_DESC = OWNER_SORT + DESCENDING_FLAG;
176 
177     public static final String KEYWORD_SORT = "KEYWORD_SORT";
178 
179     public static final String KEYWORD_SORT_DESC = KEYWORD_SORT
180             + DESCENDING_FLAG;
181 
182     public static final String[][] ALL_SORTS = {
183             // order is important; we use fact that even items are ascending,
184             // odd are decending
185             {TITLE_SORT, Node.NODE_TITLE},
186             {TITLE_SORT_DESC, Node.NODE_TITLE + " DESC"},
187             {GROUP_SORT, Node.GROUP_OF_OWNER},
188             {GROUP_SORT_DESC, Node.GROUP_OF_OWNER + " DESC"},
189             {OWNER_SORT, Node.NODE_OWNER},
190             {OWNER_SORT_DESC, Node.NODE_OWNER + " DESC"},
191             {KEYWORD_SORT, Node.NODE_COMMENT},
192             {KEYWORD_SORT_DESC, Node.NODE_COMMENT + " DESC"},};
193 
194     /***
195      * flavors for cloning
196      */
197     public static final String SIBLING_MARKER = "sibling";
198 
199     public static final String RELATION_TARGET = "relationTarget"; // /***
200     public static final String FILTER_SESSION_KEY = "filtered";
201     public static final String SORT_PARAM = "sort";
202 
203     // * names used to indicate that ascending arrows are in order
204     // */
205     // public static final String TITLE_SORT_ASC = "TITLE_SORT_ASC";
206     //
207     // public static final String OWNER_DESCENDING_TRANS =
208     // "OWNER_DESCENDING_TRANS";
209     // public static final String OWNER_ASCENDING_TRANS =
210     // "OWNER_ASCENDING_TRANS";
211     //
212     // public static final String KEYWORD_DESCENDING_TRANS =
213     // "KEYWORD_DESCENDING_TRANS";
214     // public static final String KEYWORD_ASCENDING_TRANS =
215     // "KEYWORD_ASCENDING_TRANS";
216     public NodeAction() {
217         super(EmoSchema.class);
218         addState(new State(LIST_NODE, "List Nodes"));
219         addState(new State(PROMPT_PICKLIST_NODE, "List nodes for selection"));
220         addState(new State(DO_PICKLIST_NODE, "Submit nodes to be related"));
221 
222         State s = new State(PROMPT_EDIT_ATTRIB, "Prompt to edit attribute");
223         s.addRequiredParameter(Node.NODE_ID);
224         s.addRequiredParameter(Attribute.ATTRIBUTE_TYPE);
225         addState(s);
226 
227         s = new State(PROMPT_ATTRIB_PICK,
228                 "Prompt to edit attribute that has picklist");
229         s.addRequiredParameter(Node.NODE_ID);
230         s.addRequiredParameter(Attribute.ATTRIBUTE_TYPE);
231         addState(s);
232 
233         s = new State(DO_ATTRIB_PICK, "Submit attribute that has picklist");
234         s.addRequiredParameter(Node.NODE_ID);
235         s.addRequiredParameter(Attribute.ATTRIBUTE_TYPE);
236         addState(s);
237 
238         s = new State(DO_DELETE_ONE_ATTRIB, "Delete attribute");
239         s.addRequiredParameter(Attribute.ATTRIBUTE_ID);
240         addState(s);
241 
242         s = new State(PROMPT_EDIT_ONE_ATTRIB, "Prompt to edit ONE attribute");
243         s.addRequiredParameter(Attribute.ATTRIBUTE_ID);
244         addState(s);
245 
246         s = new State(DO_EDIT_ONE_ATTRIB, "Submit edit ONE attribute");
247         s.addRequiredParameter(Attribute.ATTRIBUTE_ID);
248         addState(s);
249 
250         s = new State(PROMPT_ADD_ATTRIB, "Prompt to add ONE attribute");
251         s.addRequiredParameter(Node.NODE_ID);
252         s.addRequiredParameter(Attribute.ATTRIBUTE_TYPE);
253         addState(s);
254 
255         s = new State(PROMPT_EDIT_ONE_ATTRIB, "Prompt to edit ONE attribute");
256         s.addRequiredParameter(Attribute.ATTRIBUTE_ID);
257         addState(s);
258 
259         addState(new State(DO_EDIT_ATTRIB, "Submit attribute edits"));
260         addState(new State(DO_DELETE_NODE, "Delete Node"));
261         addState(new State(PROMPT_DELETE_NODE, "Prompt to delete node"));
262         addState(new State(PROMPT_DELETE_NODE_TREE,
263                 "Prompt to delete node tree"));
264         addState(new State(DO_EDIT_MORE_ATTRIB,
265                 "Submit attribute edits and return to edit more attributes"));
266 
267         addState(new State(PROMPT_CLONE_NODE, "Prompt to clone node"));
268         addState(new State(DO_CLONE_NODE, "Submit to clone node"));
269         addState(new State(DO_ORPHAN_CLONE_NODE, "Submit to orphan clone node"));
270         addState(new State(PROMPT_CLONE_TREE, "Prompt clone tree"));
271         addState(new State(DO_CLONE_TREE, "Do clone tree"));
272 
273         addState(new State(INDEX, "Index of all node types (auto generated)"));
274         addState(new State(PROMPT_EDIT_PERMS,
275                 "Prompt editting of permission groups for this node"));
276         addState(new State(REMOVE_GROUP,
277                 "Remove permission groups for this node"));
278         addState(new State(ADD_GROUP, "Add permission groups for this node"));
279         addState(new State(LIST_ALL_TYPES, "List of lists; all node types"));
280 
281         s = new State(VIEW_TREE, "Tree view");
282         s.addRequiredParameter(Node.NODE_ID);
283         addState(s);
284 
285         s = new State(AddNodeAction.VIEW_NODE, "View");
286         s.addRequiredParameter(Node.NODE_ID);
287         addState(s);
288 
289         s = new State(VIEW_SINGLE_ATTRIBUTE, "View Single Attribute");
290         s.addRequiredParameter(Attribute.ATTRIBUTE_ID);
291         addState(s);
292 
293         //Filtering State
294         s = this.addStateHandler(DoFilterNodes.STATE_NAME, DoFilterNodes.STATE_DESCRIPTION, DoFilterNodes.class);
295 
296         setInitialState(LIST_ALL_TYPES);
297     }
298 
299     /***
300      * Returns the title of this controller
301      *
302      * @return java.lang.String
303      */
304     public String getTitle() {
305         return "NodeAction Controller";
306     }
307 
308     /***
309      * Lists the nodes for a particular type.
310      *
311      * @param request  ExpressoRequest
312      * @param response ExpressoResponse
313      * @throws ControllerException
314      */
315     protected void runListNodesState(final ExpressoRequest request,
316                                      final ExpressoResponse response) throws ControllerException {
317         try {
318             // get any filtering elements
319             Node querynode = new Node(request);
320 
321             // get any params from request
322             isValidAndPopulated(querynode, new String[0], null, request, response);
323 
324             if (querynode.getNodeType().length() == 0) {
325                 // not set; just use first type of node found in list of types
326                 NodeType atype = new NodeType(RequestRegistry.getUser());
327                 List list = atype.searchAndRetrieveList();
328 
329                 if (list.size() == 0) {
330                     throw new ControllerException(
331                             "Cannot find any node types for listing");
332                 } else {
333                     NodeType chosen = (NodeType) list.get(0);
334                     getLogger()
335                             .warn(
336                                     "in runListNodesState, no node type is set for fetching list of nodes; using first item in list, which is: "
337                                             + chosen.getEntityName());
338                     querynode.setNodeType(chosen.getEntityName());
339                 }
340             }
341 
342             NodeType type = querynode.getEntity(); // will throw if not found
343 
344             if ((type != null) && type.hasCustomHandler()) {
345                 type.getCustomHandler().list(querynode, this,
346                         (ControllerRequest) request,
347                         (ControllerResponse) response);
348             } else {
349                 list(querynode, request, response);
350             }
351         } catch (DBException dbe) {
352             throw new ControllerException(dbe);
353         }
354     }
355 
356     /***
357      * List all nodes of type indicated by samplenode.
358      *
359      * @param samplenode has given type, but may NOT have an ID--it is just a sample
360      * @param request    The ExpressoRequest object.
361      * @param response   The ExpressoResponse object.
362      * @throws ControllerException upon error.
363      * @throws DBException         upon database related error.
364      */
365     public void list(Node samplenode, ExpressoRequest request,
366                      ExpressoResponse response) throws ControllerException, DBException {
367         String categoryName = request.getParameter(NodeType.DISPLAY_TITLE);
368 
369         if (categoryName == null) {
370             categoryName = NodeType.getDisplayName(samplenode.getNodeType());
371         }
372 
373         response.add(new Output(NodeType.DISPLAY_TITLE, categoryName));
374 
375         String sortparam = request.getParameter(SORT_PARAM);
376 
377         if (sortparam == null) {
378             sortparam = TITLE_SORT;
379         }
380 
381         Block blockList = new Block("Nodes");
382         response.addBlock(blockList);
383 
384         List list = null;
385         RecordPaginator paginator = new RecordPaginator();
386         paginator.setCountRecords(true);
387         paginator.setPageNumber((ControllerRequest) request); // any previous number request
388 
389         // attribute filter, if any
390         NodeFilter filter = getFilter(request);
391         filter.addInputs(response);
392         if (!filter.isFiltered()) {
393             list = paginator.searchAndRetrieve(samplenode, getSort(sortparam));
394         } else {
395             MultiDBObject joinObject = filter.getMulti(samplenode);
396 
397             //Set page limits for the MultiDBObject.
398             setPageLimit(joinObject);
399 
400             // use paginator
401             List multiList = paginator.searchAndRetrieve(joinObject, getSort(sortparam));
402 
403             // we're just interested in the resulting nodes, not others in join
404             list = new ArrayList(multiList.size());
405             for (Iterator iterator = multiList.iterator(); iterator.hasNext();) {
406                 MultiDBObject multiDBObject = (MultiDBObject) iterator.next();
407                 Node anode = (Node) multiDBObject.getDBObject(Node.NODE_TABLE);
408                 list.add(anode);
409             }
410         }
411 
412         int numItems = list.size();
413 
414         for (int k = 0; k < numItems; k++) {
415             Node node = (Node) list.get(k);
416 
417             // Create a Block for each row tuple
418             Block rowBlock = new Block("NodeItem");
419             blockList.add(rowBlock);
420 
421             // Create an Output for the DBObject row tuple and add to
422             // the row Block
423             Output output = new Output(Node.NODE_TITLE,
424                     str(node.getNodeTitle()));
425             rowBlock.add(output);
426             output = new Output(Node.NODE_ID, str(node.getNodeId()));
427             rowBlock.add(output);
428 
429             String annotation = node.getNodeAnnotation();
430 
431             if (!StringUtil.isBlankOrNull(annotation)) {
432                 output = new Output(Node.NODE_ANNOTATION, str(annotation));
433                 rowBlock.add(output);
434             }
435 
436             output = new Output(Node.NODE_COMMENT, strTrunc(node
437                     .getNodeComment(), MAX_CHARS_OUTPUT));
438             rowBlock.add(output);
439 
440             // get edit group
441             List groups = node.getWriteGroups();
442             FastStringBuffer buf = FastStringBuffer.getInstance();
443 
444             try {
445                 boolean firsttime = true;
446 
447                 for (Iterator iter = groups.iterator(); iter.hasNext();) {
448                     if (!firsttime) {
449                         buf.append(", ");
450                     }
451 
452                     String grp = (String) iter.next();
453                     UserGroup group = new UserGroup();
454                     group.setDBName(request.getDataContext());
455                     group.setField(UserGroup.GROUP_NAME_FIELD, grp);
456 
457                     if (!group.find()) {
458                         throw new DBException("cannot find group: " + grp);
459                     }
460 
461                     buf.append(group.getGroupDescription());
462                     firsttime = false;
463                 }
464 
465                 output = new Output(EDIT_GROUP_DISPLAY, str(buf.toString()));
466             } finally {
467                 buf.release();
468                 buf = null;
469             }
470 
471             rowBlock.add(output);
472 
473             String owner = node.getNodeOwner();
474             output = new Output(Node.NODE_OWNER, str(owner));
475             rowBlock.add(output);
476 
477             // Add an "edit" transition
478             Transition editTransition = new Transition("", AddNodeAction.class,
479                     AddNodeAction.VIEW_NODE);
480             editTransition.addParam(Node.NODE_ID, node.getNodeId());
481             checkEmbedded(request, editTransition);
482             rowBlock.add(editTransition);
483 
484             if (node.canRequesterWrite()) {
485                 // Add a "delete" transition
486                 Transition deleteTransition = new Transition(
487                         PROMPT_DELETE_NODE, this);
488                 deleteTransition.addParam(Node.NODE_ID, node.getNodeId());
489                 checkEmbedded(request, deleteTransition);
490 
491                 rowBlock.add(deleteTransition);
492 
493                 // add edit for perms
494                 // if (node.canRequesterAdministrate()) {
495                 Transition editPerms = new Transition(PROMPT_EDIT_PERMS, this);
496                 editPerms.addParam(Node.NODE_ID, node.getNodeId());
497                 checkEmbedded(request, editPerms);
498 
499                 rowBlock.add(editPerms);
500                 // }
501             }
502         } // all nodes
503 
504         if (samplenode.canRequesterAdd()) {
505             // Add an "add" transition which uses the PROMPT_EDIT_NODE state,
506             // with a blank nodeId
507             Transition addTransition = new Transition("Add "
508                     + NodeType.getDisplayName(samplenode.getNodeType()),
509                     AddNodeAction.class, AddNodeAction.PROMPT_EDIT_NODE);
510             addTransition.addParam(Node.NODE_TYPE, samplenode.getNodeType());
511             addTransition.addParam(NodeType.DISPLAY_TITLE, NodeType
512                     .getDisplayName(samplenode.getNodeType()));
513             checkEmbedded(request, addTransition);
514 
515             response.add(addTransition);
516 
517             Transition importTrans = new Transition("Import",
518                     AddNodeAction.class, AddNodeAction.PROMPT_IMPORT_NODE);
519             checkEmbedded(request, importTrans);
520 
521             response.add(importTrans);
522         }
523 
524         addSortTransitions(sortparam, request, response);
525 
526         //Add pagination.
527         Block paginationBlock = generatePageTransitions(request, paginator, 10);
528         if (paginationBlock != null) {
529             //We have to embedd if within a frame.
530             if (isEmbeddedMode(request)) {
531                 for (Enumeration transitionIterator = paginationBlock.getTransitions().elements(); transitionIterator.hasMoreElements();)
532                 {
533                     Transition eachPageTransition = (Transition) transitionIterator.nextElement();
534                     addEmbeddedParameter(eachPageTransition);
535                 }
536             }
537             response.add(paginationBlock);
538         }
539 
540         response.add(new Output("pageCount", Integer.toString(paginator.getPageCount())));
541 
542     }
543 
544     /***
545      * add a parameter to any transition if we are embedded
546      */
547     public static void checkEmbedded(ExpressoRequest request, Transition addTransition) {
548         if (isEmbeddedMode(request)) {
549             addEmbeddedParameter(addTransition);
550         }
551     }
552 
553     /***
554      * @return filter constructed from session + request info or null if no filtering
555      */
556     private NodeFilter getFilter(ExpressoRequest request) {
557         return new NodeFilter(request);
558     }
559 
560     private void setPageLimit(MultiDBObject joinObject) throws DBException {
561         DBObjLimit dl = new DBObjLimit(SuperUser.INSTANCE);
562         dl.setDataContext(joinObject.getDataContext());
563         dl.setField("DBObjectName", Node.class.getName());
564         Integer pageLim = null;
565         if (dl.find()) {
566             pageLim = new Integer(dl.getField("PageLimit"));
567         } else {
568             pageLim = new Integer(50);
569         }
570         joinObject.setAttribute("pageLimit", pageLim);
571     }
572 
573     /***
574      * Generates the page transitions.
575      *
576      * @param request   expresso request.
577      * @param rp        the record paginator.
578      * @param pageLimit the maximum number of page links to display at a time.  (10, for
579      *                  example, is a good number).
580      * @return a block with the appropriate page transitions
581      * @throws ControllerException
582      */
583     protected Block generatePageTransitions(final ExpressoRequest request,
584                                             final RecordPaginator rp, int pageLimit) throws ControllerException {
585 
586         Block returnBlock = null;
587 
588         if (!rp.isCountRecords()) {
589             throw new java.lang.IllegalArgumentException(
590                     "You must have countRecords set "
591                             + "to true for pagination to work");
592         }
593 
594         int endPage = rp.getPageCount();
595         if (endPage > 1) {
596 
597             returnBlock = new Block();
598             returnBlock.setName("pageJumpBlock");
599 
600             int curPage = rp.getPageNumber();
601 
602             int startPage;
603             int index = 1;
604             if (curPage >= 10) {
605                 startPage = curPage - (curPage % 10);
606             } else {
607                 startPage = curPage - (curPage % 10) + 1;
608             }
609             if (endPage >= (startPage + 10)) {
610                 endPage = curPage + (10 - (curPage % 10));
611             }
612 
613             Class controllerClass = this.getClass();
614             Map allParameters = request.getAllParameters();
615             allParameters.remove(Controller.CONTROLLER_PARAM_KEY);
616             String state = (String) allParameters.get(Controller.STATE_PARAM_KEY);
617             allParameters.remove(Controller.STATE_PARAM_KEY);
618 
619             if (curPage != 1) {
620                 Transition t = new Transition();
621                 t.setParams(allParameters);
622                 t.setState(state);
623                 t.setControllerObject(controllerClass);
624                 t.addParam("page", Integer.toString(curPage - 1));
625                 t.setLabel("< Prev");
626                 t.setName(Integer.toString(index));
627                 index++;
628                 returnBlock.add(t);
629             }
630 
631             for (int i = startPage; i <= endPage; i++) {
632                 Transition t = new Transition();
633                 t.setParams(allParameters);
634                 t.setControllerObject(controllerClass);
635                 t.addParam("page", Integer.toString(i));
636                 t.setLabel(Integer.toString(i));
637                 t.setState(state);
638                 if (curPage == i) {
639                     t.setAttribute("currentPage", "true");
640                     Output o = new Output();
641                     o.setName("CurrentPage");
642                     o.setContent(Integer.toString(curPage));
643                     returnBlock.add(o);
644                 }
645                 t.setName(Integer.toString(index));
646                 index++;
647 
648                 returnBlock.add(t);
649             }
650 
651             if (curPage < endPage) {
652                 Transition t = new Transition();
653                 t.setParams(allParameters);
654                 t.setControllerObject(controllerClass);
655                 t.addParam("page", Integer.toString(curPage + 1));
656                 t.setState(state);
657                 t.setLabel("Next >");
658                 t.setName(Integer.toString(index));
659                 index++;
660                 returnBlock.add(t);
661             }
662 
663         }
664 
665         return returnBlock;
666     }
667 
668     /***
669      * @param request  The ExpressoRequest object.
670      * @param response The ExpressoResponse object.
671      * @throws ControllerException upon error.
672      * @throws DBException         upon database related error.
673      */
674 
675     private void addSortTransitions(String sortparam, ExpressoRequest request,
676                                     ExpressoResponse response) throws ControllerException, DBException {
677         String reverseSort = getReverseSortParam(sortparam);
678 
679         String nodeType = request.getParameter(Node.NODE_TYPE);
680 
681         if (!NodeType.isValidType(nodeType)) {
682             nodeType = getFirstNodeTypeInDB();
683         }
684 
685         String specialDisplayNodeType = request
686                 .getParameter(NodeType.NODE_TYPE_NAME);
687 
688         Transition trans = null;
689 
690         for (int i = 0; i < ALL_SORTS.length; i++) {
691             String oneSortKey = ALL_SORTS[i][0];
692 
693             // is this the current sort?
694             if (oneSortKey.equals(sortparam)) {
695                 // do NOT include sortparam trans, but rather add signal for an
696                 // ascend/decend arrow
697                 if (sortparam.endsWith(DESCENDING_FLAG)) {
698                     // signal to show (unlinked) plain text and arrow
699                     trans = new Transition(reverseSort + ASCENDING_FLAG, "",
700                             getClass(), LIST_NODE);
701                     trans.addParam(SORT_PARAM, reverseSort);
702                     trans.addParam(Node.NODE_TYPE, nodeType);
703                     checkEmbedded(request, trans);
704 
705                     response.add(trans);
706 
707                     // add flag for background color of this column label
708                     response.add(new Output(reverseSort, "true"));
709                 } else {
710                     // reverse will have _DESC on it, and this will mean an
711                     // arrow
712                     trans = new Transition(reverseSort, "", getClass(),
713                             LIST_NODE);
714                     trans.addParam(SORT_PARAM, reverseSort);
715                     trans.addParam(Node.NODE_TYPE, nodeType);
716                     response.add(trans);
717                     checkEmbedded(request, trans);
718 
719                     // add flag for background color of this column label
720                     response.add(new Output(sortparam, "true"));
721                 }
722 
723             } else if (!reverseSort.equals(oneSortKey)) {
724                 // add only even, ascending sorts
725                 if ((i % 2) == 0) {
726                     trans = new Transition(oneSortKey, "", getClass(),
727                             LIST_NODE);
728                     trans.addParam(SORT_PARAM, oneSortKey);
729                     trans.addParam(Node.NODE_TYPE, nodeType);
730                     checkEmbedded(request, trans);
731                     response.add(trans);
732                 }
733             }
734 
735             // add special display in any case
736             if ((specialDisplayNodeType != null) && (trans != null)) {
737                 trans.addParam(NodeType.NODE_TYPE_NAME, specialDisplayNodeType);
738             }
739         }
740     }
741 
742     /***
743      * Return node type (in string form) of first node type in database.
744      *
745      * @return java.lang.String
746      * @throws DBException upon database related error.
747      */
748     private String getFirstNodeTypeInDB() throws DBException {
749         NodeType atype = new NodeType(RequestRegistry.getUser());
750         List list = atype.searchAndRetrieveList();
751         String result = "";
752 
753         if (!list.isEmpty()) {
754             atype = (NodeType) list.get(0);
755             result = atype.getEntityName();
756         }
757 
758         return result;
759     }
760 
761     /***
762      * @param sort java.lang.String
763      * @return key which indicates reverse sort on same column
764      */
765     private String getReverseSortParam(String sort) {
766         String result = ALL_SORTS[1][0];
767 
768         if (sort == null) {
769             if (getLogger().isDebugEnabled()) {
770                 getLogger().debug("Sort == null");
771             }
772         } else {
773             for (int i = 0; i < ALL_SORTS.length; i++) {
774                 String[] oneSort = ALL_SORTS[i];
775 
776                 if (oneSort[0].equals(sort)) {
777                     // need reverse, use fact that even items are ascending, odd
778                     // are decending
779                     if ((i % 2) == 0) {
780                         result = ALL_SORTS[i + 1][0];
781                     } else {
782                         result = ALL_SORTS[i - 1][0];
783                     }
784 
785                     break;
786                 }
787             }
788         }
789 
790         return result;
791     }
792 
793     /***
794      * @param sort java.lang.String
795      * @return actual db sort string for use in list query
796      */
797     private String getSort(String sort) {
798         String result = ALL_SORTS[0][1];
799 
800         if (sort == null) {
801             if (getLogger().isDebugEnabled()) {
802                 getLogger().debug("Sort == null");
803             }
804         } else {
805             for (int i = 0; i < ALL_SORTS.length; i++) {
806                 String[] oneSort = ALL_SORTS[i];
807 
808                 if (oneSort[0].equals(sort)) {
809                     result = oneSort[1];
810 
811                     if (!result.startsWith(Node.NODE_TITLE)) {
812                         // add secondary sort
813                         result += ("|" + Node.NODE_TITLE);
814                     }
815 
816                     break;
817                 }
818             }
819         }
820 
821         return result;
822     }
823 
824     /***
825      * Display list with checkboxes and submit, in order to add relation to some
826      * other node.
827      *
828      * @param request  The ExpressoRequest object.
829      * @param response The ExpressoResponse object.
830      * @throws ControllerException upon error.
831      */
832     protected void runPromptPickListNodesState(final ExpressoRequest request,
833                                                final ExpressoResponse response) throws ControllerException {
834         String type = request.getParameter(Node.NODE_TYPE);
835         String targetId = request.getParameter(Node.NODE_ID);
836         String relation = request.getParameter(Relation.RELATION_TYPE);
837 
838         if ((type == null) || (targetId == null) || (relation == null)
839                 || (type.length() == 0) || (targetId.length() == 0)
840                 || (relation.length() == 0)) {
841             throw new ControllerException(
842                     "type, targetId and relation are required parameters");
843         }
844 
845         try {
846             Node target = new Node(request, targetId);
847             target.retrieve();
848 
849             // node type is not editable
850             response.add(new Output(NodeType.DISPLAY_TITLE, NodeType
851                     .getDisplayName(type)));
852             response.add(new Output(RelationType.RELATION_TYPE_DISPLAY_NAME,
853                     RelationType.getRelTypeDisplayName(relation)));
854 
855             response.add(new Output(Node.NODE_TITLE, target.getNodeTitle()));
856 
857             Transition nodetrans = new Transition("", AddNodeAction.class,
858                     AddNodeAction.VIEW_NODE);
859             nodetrans.addParam(Node.NODE_ID, targetId);
860             checkEmbedded(request, nodetrans);
861             response.add(nodetrans);
862 
863             Input targetIdHidden = new Input(Node.NODE_ID);
864             targetIdHidden.setType(InputTag.TYPE_HIDDEN);
865             targetIdHidden.setDefaultValue(targetId);
866             response.add(targetIdHidden);
867 
868             Input relationHidden = new Input(Relation.RELATION_TYPE);
869             relationHidden.setType(InputTag.TYPE_HIDDEN);
870             relationHidden.setDefaultValue(relation);
871             response.add(relationHidden);
872 
873             Input typeHidden = new Input(Node.NODE_TYPE);
874             typeHidden.setType(InputTag.TYPE_HIDDEN);
875             typeHidden.setDefaultValue(type);
876             response.add(typeHidden);
877 
878             Block selectedBlock = new Block(ROW_BLOCK);
879             response.addBlock(selectedBlock);
880 
881             Block unselBlock = new Block(SIBLING_MARKER);
882             response.addBlock(unselBlock);
883 
884             Hashtable reflexNodes = getReflexiveNodeHash(relation, target, type);
885             Node querynode = new Node(RequestRegistry.getUser());
886             querynode.setDBName(request.getDataContext());
887 
888             // test not necessary if we can relate to any node; e.g.,
889             // miscellaneous associations block
890             if (!type.equals(NodeType.ANY_OBJECT)) {
891                 querynode.setField(Node.NODE_TYPE, type);
892                 querynode.setCustomWhereClause(Node.NODE_TYPE + " = '" + type
893                         + "'");
894             }
895 
896             querynode.setFieldsToRetrieve(Node.NODE_ID + "|" + Node.NODE_TITLE
897                     + "|" + Node.NODE_ANNOTATION);
898 
899             ArrayList allPossibleNodeList = querynode
900                     .searchAndRetrieveList(Node.NODE_TITLE);
901 
902             // also get a hash of any selections already made
903             Node srcnode = new Node(request, targetId);
904             List multilist = srcnode.getRawRelated(relation, type);
905             Hashtable alreadySelectedHash = new Hashtable(multilist.size());
906 
907             // Hashtable alreadySelRelHash = new Hashtable(multilist.size());
908             for (int i = 0; i < multilist.size(); i++) {
909                 MultiDBObject aMultiObject = (MultiDBObject) multilist.get(i);
910                 String destId = aMultiObject.getField(Node.NODE_JOIN,
911                         Node.NODE_ID);
912                 alreadySelectedHash.put(destId, aMultiObject);
913 
914                 Relation rel = (Relation) aMultiObject
915                         .getDBObject(Node.RELATION_JOIN);
916 
917                 // alreadySelRelHash.put(destId, rel);
918                 Node aNode = (Node) aMultiObject.getDBObject(Node.NODE_JOIN);
919 
920                 Block rowBlock = new Block(ROW);
921                 selectedBlock.add(rowBlock);
922                 rowBlock.add(getCheckbox(PICK_LIST_ITEM, "", rel.getDestId(),
923                         true));
924 
925                 Transition trans = AddNodeAction.getViewTrans(rel.getDestId());
926                 trans.setLabel(aNode.getNodeTitle());
927                 checkEmbedded(request, trans);
928                 rowBlock.add(trans);
929 
930                 // separate blocks for +/- because the transition names must be
931                 // unique
932                 Block decblock = new Block(AddNodeAction.DEC_REL_ORDER);
933                 rowBlock.add(decblock);
934 
935                 Block incblock = new Block(AddNodeAction.INC_REL_ORDER);
936                 rowBlock.add(incblock);
937 
938                 if (multilist.size() > 1) {
939                     if (rel.getOrderInt() > 1) {
940                         trans = new Transition("", AddNodeAction.class,
941                                 AddNodeAction.DEC_REL_ORDER);
942                         trans.setName(AddNodeAction.DEC_REL_ORDER
943                                 + rel.getKey()); // unique name
944                         trans.addParam(Relation.RELATION_SRC, rel.getSrcId());
945                         trans.addParam(Relation.RELATION_TYPE, rel
946                                 .getRelationTypeName());
947                         trans.addParam(Relation.RELATION_DEST, rel.getDestId());
948                         trans.setAttribute(Relation.RELATION_ORDER, rel
949                                 .getOrder());
950                         checkEmbedded(request, trans);
951 
952                         decblock.add(trans);
953                     }
954 
955                     if (rel.getOrderInt() < multilist.size()) {
956                         trans = new Transition("", AddNodeAction.class,
957                                 AddNodeAction.INC_REL_ORDER);
958                         trans.setName(AddNodeAction.INC_REL_ORDER
959                                 + rel.getKey()); // unique name
960                         trans.addParam(Relation.RELATION_SRC, rel.getSrcId());
961                         trans.addParam(Relation.RELATION_TYPE, rel
962                                 .getRelationTypeName());
963                         trans.addParam(Relation.RELATION_DEST, rel.getDestId());
964                         trans.setAttribute(Relation.RELATION_ORDER, rel
965                                 .getOrder());
966                         checkEmbedded(request, trans);
967                         incblock.add(trans);
968                     }
969                 }
970             }
971 
972             for (Iterator iterator = allPossibleNodeList.iterator(); iterator
973                     .hasNext();) {
974                 Node node = (Node) iterator.next();
975                 String aNodeId = node.getNodeId();
976 
977                 if (alreadySelectedHash.get(aNodeId) != null) {
978                     continue;
979                 }
980 
981                 // don't list target item as a possible relation
982                 if (aNodeId.equals(targetId)) {
983                     continue;
984                 }
985 
986                 if (reflexNodes.get(aNodeId) != null) {
987                     continue; // we cannot permit BOTH sides of a reflexive
988                     // relationship
989                 }
990 
991                 // Create a Block for each row tuple
992                 Block rowBlock = new Block(ROW);
993                 unselBlock.add(rowBlock);
994 
995                 // checkbox name will show as title of node, but
996                 // underlying tag name will contain the node ID, so that a check
997                 // can be translated into a choice for the node
998                 rowBlock.add(getCheckbox(PICK_LIST_ITEM, "", aNodeId, false));
999 
1000                 // addRelationComment(response, aNodeId, rowBlock);
1001                 Transition trans = AddNodeAction.getViewTrans(aNodeId);
1002                 trans.setLabel(node.getNodeTitle());
1003                 checkEmbedded(request, trans);
1004 
1005                 rowBlock.add(trans);
1006 
1007                 // rowBlock.add(new Output(Node.NODE_TITLE,
1008                 // node.getNodeTitle()));
1009                 rowBlock.add(new Output(Node.NODE_ANNOTATION, str(node
1010                         .getNodeAnnotation())));
1011             }
1012 
1013             // Create an transition to submit the form
1014             Transition submit = new Transition(DO_PICKLIST_NODE, this);
1015             submit.setLabel("Save");
1016             checkEmbedded(request, submit);
1017             response.add(submit);
1018 
1019             if (querynode.canRequesterAdd()) {
1020                 addPrivilegedLinks(type, targetId, relation, request, response);
1021             }
1022         } catch (DBException dbe) {
1023             throw new ControllerException(dbe);
1024         }
1025     }
1026 
1027     private void addPrivilegedLinks(String type, String targetId,
1028                                     String relation, ExpressoRequest request, ExpressoResponse response)
1029             throws DBException, ControllerException {
1030         // Add an "add" transition which uses the PROMPT_EDIT_NODE state, with a
1031         // blank nodeId
1032         Transition addTransition = new Transition("Add New "
1033                 + NodeType.getDisplayName(type), AddNodeAction.class,
1034                 AddNodeAction.PROMPT_EDIT_NODE);
1035         addTransition.addParam(Node.NODE_TYPE, type);
1036 
1037         // add special return-to-sender ID
1038         addTransition.addParam(RELATION_TARGET, targetId);
1039         addTransition.addParam(Relation.RELATION_TYPE, relation);
1040         checkEmbedded(request, addTransition);
1041 
1042         response.add(addTransition);
1043     }
1044 
1045     // private void addRelationComment(ExpressoResponse response, String
1046     // aNodeId, Block rowBlock) throws ControllerException {
1047     // Input relComment = new Input(Relation.RELATION_ANNOTATION);
1048     // relComment.setType(InputTag.TYPE_TEXTAREA);
1049     // relComment.setDisplayLength(35);
1050     //
1051     // //relComment.setLines();
1052     // relComment.setDefaultValue(response.getFormCache(Relation.RELATION_ANNOTATION
1053     // + aNodeId));
1054     // relComment.setAttribute(Node.NODE_ID, aNodeId);
1055     // rowBlock.add(relComment);
1056     // }
1057     //
1058     private Hashtable getReflexiveNodeHash(String relation, Node target,
1059                                            String type) throws DBException {
1060         RelationType relType = new RelationType();
1061         relType.setRelType(relation);
1062         relType.retrieve();
1063 
1064         Hashtable reflexNodes = null;
1065 
1066         if (relType.isReflexive()) {
1067             reflexNodes = new Hashtable(target.getRelatedNodesHash(relType
1068                     .getInverseRelType().getRelTypeName(), type, null));
1069             // we must do extra tests to prevent overlapping assertions
1070             // reflexNodes = getRelatedNodesHash(targetId,
1071             // relType.getInverseRelType().getRelTypeName(),
1072             // type, null);
1073         } else {
1074             reflexNodes = new Hashtable(); // no entries
1075         }
1076 
1077         return reflexNodes;
1078     }
1079 
1080     protected void runDoPickListNodesState(
1081             final ServletControllerRequest request,
1082             final ExpressoResponse response) throws ControllerException,
1083             DBException {
1084 
1085         String targetId = request.getParameter(Node.NODE_ID);
1086         String relationType = request.getParameter(Relation.RELATION_TYPE);
1087         String nodeType = request.getParameter(Node.NODE_TYPE);
1088 
1089         if ((targetId == null) || (relationType == null)
1090                 || (targetId.length() == 0) || (relationType.length() == 0)
1091                 || (nodeType == null) || (nodeType.length() == 0)) {
1092             throw new ControllerException(
1093                     "targetId, nodeType and relationType are required parameters");
1094         }
1095 
1096         Node node = new Node(request, request.getParameter(Node.NODE_ID));
1097         node.retrieve(); // will throw
1098 
1099         // get the "before" look
1100         Hashtable reflexNodes = getReflexiveNodeHash(relationType, node,
1101                 nodeType);
1102         String[] checkValues = getParamValues(
1103                 (ServletControllerRequest) request, PICK_LIST_ITEM);
1104 
1105         // some relations must be singular
1106         if ((checkValues != null) && (checkValues.length > 1)) {
1107 
1108             String parentType = node.getNodeType();
1109             Part part = PartsFactory
1110                     .getPart(parentType, nodeType, relationType);
1111 
1112             if (part == null) {
1113                 getLogger().error(
1114                         "cannot find part for parent/nodetype/relation: "
1115                                 + parentType + "/" + nodeType + "/"
1116                                 + relationType);
1117             } else if (!part.areMultipleAttributesAllowed()) {
1118                 response
1119                         .addError("Only 1 item is allowed. Please choose just one.");
1120                 response.setFormCache();
1121 
1122                 Transition trans = new Transition("x", "", NodeAction.class,
1123                         NodeAction.PROMPT_PICKLIST_NODE);
1124                 Map parameters = request.getAllParameters();
1125                 parameters.remove(CONTROLLER_PARAM_KEY);
1126                 parameters.remove(STATE_PARAM_KEY);
1127                 trans.setParams(parameters);
1128                 checkEmbedded(request, trans);
1129                 trans.executeTransition(request, response);
1130 
1131                 return;
1132             }
1133         }
1134 
1135         //
1136         // First validate
1137         //
1138         for (int k = 0; (checkValues != null) && (k < checkValues.length); k++) {
1139             String nodeId = checkValues[k];
1140 
1141             // is this a forbidden inverse relation?
1142             if (reflexNodes.get(nodeId) != null) {
1143                 response
1144                         .addError("Cannot add a relation that already has a reciprocal relation for: "
1145                                 + ((Node) reflexNodes.get(nodeId))
1146                                 .getNodeTitle());
1147                 response.setFormCache();
1148 
1149                 Transition trans = new Transition("", NodeAction.class,
1150                         NodeAction.PROMPT_PICKLIST_NODE);
1151                 Map parameters = request.getAllParameters();
1152                 parameters.remove(CONTROLLER_PARAM_KEY);
1153                 parameters.remove(STATE_PARAM_KEY);
1154                 trans.setParams(parameters);
1155                 checkEmbedded(request, trans);
1156                 trans.executeTransition(request, response);
1157 
1158                 return;
1159             }
1160 
1161         }
1162 
1163         // add and remove noted for removal relations
1164         node.updateNodeRelations(nodeType, relationType, checkValues);
1165 
1166         Transition trans = new Transition("", AddNodeAction.class,
1167                 AddNodeAction.VIEW_NODE);
1168         trans.addParam(Node.NODE_ID, targetId);
1169 
1170         //
1171         boolean sentRedirect = redirectToSender(request, "Update Saved");
1172 
1173         if (sentRedirect) {
1174             return;
1175         }
1176 
1177         trans.redirectTransition(request, response);
1178     }
1179 
1180     /***
1181      * Optional submit button for adding attributes AND getting chance to add
1182      * more.
1183      *
1184      * @param request  The ExpressoRequest object.
1185      * @param response The ExpressoResponse object.
1186      * @throws ControllerException upon error.
1187      */
1188     protected void runDoEditMoreAttribState(ExpressoRequest request,
1189                                             ExpressoResponse response) throws ControllerException {
1190         saveAllAttribs(request);
1191 
1192         try {
1193             // go back to the node after deletion
1194             Transition trans = new Transition(PROMPT_EDIT_ATTRIB, this);
1195             String nodeId = request.getParameter(Node.NODE_ID);
1196             trans.addParam(Node.NODE_ID, nodeId);
1197 
1198             String attribType = request.getParameter(Attribute.ATTRIBUTE_TYPE);
1199             trans.addParam(Attribute.ATTRIBUTE_TYPE, attribType);
1200             checkEmbedded(request, trans);
1201             trans.redirectTransition(request, response);
1202         } catch (Exception dbe) {
1203             throw new ControllerException(dbe);
1204         }
1205     }
1206 
1207     /***
1208      * Attribute info comes in as sets. A set includes the attribute value,
1209      * comment, and either <p/> 1) id (if existing) or
1210      * </p>
1211      * <p/>
1212      * 2) type (if new)
1213      * </p>
1214      * <p/>
1215      * We can pick any one of the set, like the value item, then save all the
1216      * items in the set with that set number (the number is the suffix of the
1217      * param name)
1218      * </p>
1219      *
1220      * @param request  The ExpressoRequest object.
1221      * @param response The ExpressoResponse object.
1222      * @throws ControllerException    upon error.
1223      * @throws NonHandleableException upon transition error.
1224      */
1225     protected void runDoEditAttribState(final ServletControllerRequest request,
1226                                         final ExpressoResponse response) throws ControllerException,
1227             NonHandleableException {
1228         try {
1229             saveAttribs(request);
1230             Transition trans = AddNodeAction.getViewTrans(request
1231                     .getParameter(Node.NODE_ID));
1232 
1233             if (redirectToSender(request, "Update Saved")) {
1234                 return;
1235             }
1236             trans.redirectTransition(request, response);
1237         } catch (DBException dbe) {
1238             ErrorCollection errors = new ErrorCollection();
1239             errors.addError(dbe.getMessage());
1240             response.setFormCache();
1241             response.saveErrors(errors);
1242 
1243             try {
1244                 transition(LIST_NODE, request, response);
1245             } catch (NonHandleableException nhe) {
1246                 throw new ControllerException(nhe);
1247             }
1248         }
1249     }
1250 
1251     private void saveAttribs(ExpressoRequest request) throws DBException,
1252             ControllerException {
1253         // see if we just have a single attrib
1254         String attribId = request.getParameter(Attribute.ATTRIBUTE_ID);
1255 
1256         if ((attribId != null) && (attribId.length() > 0)) {
1257             // we have a single attrib to save
1258             Attribute attribute = new Attribute(attribId);
1259             attribute.retrieve();
1260 
1261             String value = request.getParameter(Attribute.ATTRIBUTE_VALUE);
1262             String comment = request.getParameter(Attribute.ATTRIBUTE_COMMENT);
1263             if (comment != null) {
1264                 comment = comment.trim();
1265             }
1266             attribute.setAttributeValue(value);
1267             attribute.setAttributeComment(comment);
1268             attribute.update(true);
1269 
1270             // touch mod date and person for parent node
1271             Node node = attribute.getParentNode();
1272             node.touch();
1273 
1274             request.setParameter(Node.NODE_ID, node.getNodeId());
1275             request.setParameter(Attribute.ATTRIBUTE_TYPE, attribute
1276                     .getAttribType());
1277         } else {
1278             // handle add's
1279             saveAllAttribs(request);
1280         }
1281     }
1282 
1283     /***
1284      * Saves the picklist attributes.
1285      *
1286      * @param request  ServletControllerRequest the servlet controller request
1287      *                 (servlet environment only)
1288      * @param response ExpressoResponse the controller response
1289      * @throws ControllerException    upon error.
1290      * @throws NonHandleableException upon fatal error.
1291      */
1292     protected void runDoAttribPickState(final ServletControllerRequest request,
1293                                         final ExpressoResponse response) throws ControllerException,
1294             NonHandleableException {
1295         try {
1296             String attribId = request.getParameter(Attribute.ATTRIBUTE_ID);
1297             String value = request.getParameter(Attribute.ATTRIBUTE_VALUE);
1298             String comment = request.getParameter(Attribute.ATTRIBUTE_COMMENT);
1299 
1300             if ((attribId != null) && (attribId.length() > 0)) {
1301                 // we have a single attrib to save
1302                 Attribute attribute = new Attribute(attribId);
1303                 attribute.retrieve();
1304 
1305                 attribute.setAttributeValue(value);
1306                 attribute.setAttributeComment(comment);
1307                 attribute.update(true);
1308 
1309                 // touch mod date and person for parent node
1310                 attribute.getParentNode().touch();
1311             } else {
1312                 // handle add's
1313                 // for case when attrib is new, these are required
1314                 String nodeId = request.getParameter(Node.NODE_ID);
1315                 String attribType = request
1316                         .getParameter(Attribute.ATTRIBUTE_TYPE);
1317 
1318                 // for new attribute, attribute type & nodeId are required
1319                 if ((nodeId == null) || (nodeId.length() == 0)
1320                         || (attribType == null) || (attribType.length() == 0)) {
1321                     throw new ControllerException(
1322                             "nodeId and attrib_type are required parameters");
1323                 }
1324 
1325                 // at this point, we know we have a new attribute
1326                 // but do we really have something to save?
1327                 if ((value.length() == 0) && (comment.length() == 0)) {
1328                     throw new ControllerException(
1329                             "found no value or comment to save");
1330                 }
1331 
1332                 // this node info should be in cache since we were just editing
1333                 // it
1334                 Node node = new Node(request, nodeId);
1335 
1336                 if (!node.find()) {
1337                     throw new ControllerException("cannot find node with id: "
1338                             + nodeId);
1339                 }
1340 
1341                 Attribute attribute = new Attribute();
1342                 attribute.setField(Attribute.ATTRIBUTE_TYPE, attribType);
1343                 attribute.setField(Attribute.NODE_ID, nodeId);
1344                 attribute.setField(Attribute.NODE_TYPE, node.getNodeType());
1345                 attribute.setField(Attribute.ATTRIBUTE_VALUE, value);
1346                 attribute.setField(Attribute.ATTRIBUTE_COMMENT, comment);
1347                 attribute.add();
1348 
1349                 node.touch();
1350             }
1351 
1352             // go back to the node after deletion
1353             Transition trans = AddNodeAction.getViewTrans(request
1354                     .getParameter(Node.NODE_ID));
1355 
1356             if (redirectToSender(request, "Update Saved")) {
1357                 return;
1358             }
1359 
1360             trans.redirectTransition(request, response);
1361         } catch (DBException dbe) {
1362             ErrorCollection errors = new ErrorCollection();
1363             errors.addError(dbe.getMessage());
1364             response.setFormCache();
1365             response.saveErrors(errors);
1366 
1367             try {
1368                 transition(LIST_NODE, request, response);
1369             } catch (NonHandleableException nhe) {
1370                 throw new ControllerException(nhe);
1371             }
1372         }
1373     }
1374 
1375     protected void runDoDeleteAttribState(ExpressoRequest request,
1376                                           ExpressoResponse response) throws ControllerException {
1377         try {
1378 
1379             // since some comments might have been changed on this page,
1380             // save them first; but delete form sets a PARTICULAR attrib ID,
1381             // which will fool doEdit into thinking it should update only
1382             // that one item; therefore, remove the ID temporarily
1383             String attribId = request.getParameter(Attribute.ATTRIBUTE_ID);
1384             request.removeParameter(Attribute.ATTRIBUTE_ID);
1385             saveAttribs(request);
1386 
1387             // now do the deleting
1388             Attribute attrib = new Attribute(attribId);
1389             attrib.retrieve();
1390             Node node = attrib.getParentNode();
1391             attrib.delete();
1392 
1393             // go back to the edit attrib page after deletion IFF there are
1394             // remaining attribs
1395             if (node.getAttributes(attrib.getAttribType()).length > 0) {
1396                 Transition trans = new Transition(PROMPT_EDIT_ATTRIB, this);
1397                 trans
1398                         .addParam(Attribute.ATTRIBUTE_TYPE, attrib
1399                                 .getAttribType());
1400                 trans.addParam(Node.NODE_ID, node.getNodeId());
1401                 checkEmbedded(request, trans);
1402                 trans.redirectTransition(request, response);
1403             } else {
1404                 // deleted last one; go back to viewing node
1405                 Transition trans = AddNodeAction.getViewTrans(node.getNodeId());
1406                 checkEmbedded(request, trans);
1407                 trans.redirectTransition(request, response);
1408             }
1409         } catch (DBException dbe) {
1410             ErrorCollection errors = new ErrorCollection();
1411             errors.addError(dbe.getMessage());
1412             response.setFormCache();
1413             response.saveErrors(errors);
1414 
1415             try {
1416                 transition(LIST_NODE, request, response);
1417             } catch (NonHandleableException nhe) {
1418                 throw new ControllerException(nhe);
1419             }
1420         }
1421     }
1422 
1423     private void saveAllAttribs(ExpressoRequest request)
1424             throws ControllerException {
1425         Set keys = new TreeSet(request.getAllParameters().keySet()); // alphabetize
1426         for (Iterator iterator = keys.iterator(); iterator.hasNext();) {
1427             String paramName = (String) iterator.next();
1428 
1429             // boolean haveUpdatedOwningNodeModDate = false;
1430 
1431             if (paramName.startsWith(Attribute.ATTRIBUTE_VALUE_PREFIX)) {
1432                 int startChar = Attribute.ATTRIBUTE_VALUE_PREFIX.length();
1433                 int setNum = Integer.parseInt(paramName.substring(startChar));
1434                 saveAttribute(request, setNum);
1435 
1436                 // This code should now be handled internally inside Node.
1437                 // touch mod date and person for parent node
1438                 // if ((attrib != null) && !haveUpdatedOwningNodeModDate) {
1439                 // try {
1440                 // Node node = attrib.getParentNode();
1441                 // node.setRecentEditor(RequestRegistry.getUser().getLoginName());
1442                 // node.update(); // will touch mod date
1443                 // } catch (DBException e) {
1444                 // throw new ControllerException(e);
1445                 // }
1446                 //
1447                 // haveUpdatedOwningNodeModDate = true;
1448                 // }
1449             }
1450         }
1451     }
1452 
1453     /***
1454      * Prompts for editing a node attribute.
1455      *
1456      * @param request  ExpressoRequest
1457      * @param response ExpressoResponse
1458      * @throws ControllerException
1459      */
1460     protected void runPromptEditAttribState(final ExpressoRequest request,
1461                                             final ExpressoResponse response) throws ControllerException {
1462         String nodeId = request.getParameter(Node.NODE_ID);
1463         String attribName = request.getParameter(Attribute.ATTRIBUTE_TYPE);
1464 
1465         try {
1466             String attribDisplayName = null;
1467             Part part = null;
1468 
1469             Node node = new Node(request, nodeId);
1470 
1471             if (!node.find()) {
1472                 response
1473                         .addError("Cannot find an item with ID="
1474                                 + nodeId
1475                                 + ". You may have used an obsolete link, "
1476                                 + "where the item has been deleted.  Please navigate to and use a fresh list of items.");
1477 
1478                 Transition trans = new Transition("", NodeAction.class,
1479                         NodeAction.INDEX);
1480                 checkEmbedded(request, trans);
1481                 trans.executeTransition(request, response);
1482 
1483                 return;
1484             }
1485 
1486             part = PartsFactory.getAttribPart(node.getNodeType(), attribName);
1487             attribDisplayName = part.getPartLabel();
1488             response.add(new Output(Node.NODE_TITLE, node.getNodeTitle()));
1489             response.add(new Output(Attribute.ATTRIBUTE_DISPLAY_NAME,
1490                     attribDisplayName));
1491 
1492             Attribute[] attribs = node.getAttributes(part.getPartType());
1493             Attribute representativeAttribute = getRepAttribute(attribs, node,
1494                     attribName);
1495 
1496             if (representativeAttribute.hasCustomHandler()) {
1497                 IPartHandler handler = representativeAttribute
1498                         .getCustomHandler();
1499 
1500                 try {
1501                     Transition trans = handler.getEditTransition(request
1502                             .getAllParameters());
1503                     if (trans != null) {
1504                         if (isEmbeddedMode(request)) {
1505                             addEmbeddedParameter(trans);
1506                             addReturnToSenderParameter(request, response, trans);
1507                         }
1508                         trans.executeTransition(request, response, false);
1509                         return;
1510                     }
1511                 } catch (Exception e) {
1512                     throw new ControllerException(e);
1513                 }
1514             }
1515 
1516             // short circuit to picklist display if appropriate
1517             if (part.hasPicklist()) {
1518                 Transition trans = new Transition(PROMPT_ATTRIB_PICK, this);
1519                 trans.addParam(Node.NODE_ID, nodeId);
1520                 trans.addParam(Attribute.ATTRIBUTE_TYPE, attribName);
1521                 if (isEmbeddedMode(request)) {
1522                     addEmbeddedParameter(trans);
1523                     addReturnToSenderParameter(request, response, trans);
1524                 }
1525                 trans.executeTransition(request, response);
1526 
1527                 return;
1528             }
1529 
1530             if (!part.areMultipleAttributesAllowed()) {
1531                 response.add(new Output(Part.CARDINALITY,
1532                         " (only single attribute allowed) "));
1533             }
1534 
1535             // transition for adding
1536             Transition addTrans = new Transition(PROMPT_ADD_ATTRIB, this);
1537             Map parameters = request.getAllParameters();
1538             parameters.remove(CONTROLLER_PARAM_KEY);
1539             parameters.remove(STATE_PARAM_KEY);
1540             addTrans.setParams(parameters);
1541             if (isEmbeddedMode(request)) {
1542                 addReturnToSenderParameter(request, response, addTrans);
1543                 addEmbeddedParameter(addTrans);
1544             }
1545 
1546             if (attribs.length == 0) {
1547                 // short-circuit to adding new items if none exist
1548                 addTrans.executeTransition(request, response);
1549                 return;
1550             } else if (part.areMultipleAttributesAllowed()) {
1551                 // add additional
1552 
1553                 response.add(addTrans);
1554                 if (attribs.length > 1) {
1555                     response.add(new Output(Part.PART_NUM, "Y"));
1556                 }
1557             }
1558 
1559             response.add(getAttributeInputBlock(attribs, part, request, false));
1560 
1561             response.add(AddNodeAction.getViewTrans(nodeId));
1562 
1563             // 'Save' button
1564             Transition trans = new Transition("", getClass(), DO_EDIT_ATTRIB);
1565             trans.addParam(Node.NODE_ID, nodeId);
1566             trans.addParam(Attribute.ATTRIBUTE_TYPE, attribName);
1567             if (isEmbeddedMode(request)) {
1568                 addEmbeddedParameter(trans);
1569                 addReturnToSenderParameter(request, response, trans);
1570             }
1571             response.add(trans);
1572         } catch (Exception e) {
1573             throw new ControllerException(e);
1574         }
1575     }
1576 
1577     public static Attribute getRepAttribute(final List attribs,
1578                                             final Node node, final String attribName) throws DBException {
1579         Attribute representativeAttribute = null;
1580         if (attribs.size() > 0) {
1581             representativeAttribute = (Attribute) attribs.get(0);
1582         } else {
1583             representativeAttribute = new Attribute(node);
1584             representativeAttribute.setAttributeType(attribName);
1585         }
1586         return representativeAttribute;
1587     }
1588 
1589     public Attribute getRepAttribute(final Attribute[] attribs,
1590                                      final Node node, final String attribName) throws DBException {
1591         Attribute representativeAttribute = null;
1592         if (attribs.length > 0) {
1593             representativeAttribute = attribs[0];
1594         } else {
1595             representativeAttribute = new Attribute(node);
1596             representativeAttribute.setAttributeType(attribName);
1597         }
1598         return representativeAttribute;
1599     }
1600 
1601     /***
1602      * Prompts for editing a picklist attribute.
1603      *
1604      * @param request  ExpressoRequest the ExpressoRequest object.
1605      * @param response ExpressoResponse the ExpressoResponse object.
1606      * @throws ControllerException upon error.
1607      */
1608     protected void runPromptAttribPickState(final ExpressoRequest request,
1609                                             final ExpressoResponse response) throws ControllerException {
1610         String nodeId = request.getParameter(Node.NODE_ID);
1611         String attribName = request.getParameter(Attribute.ATTRIBUTE_TYPE);
1612 
1613         try {
1614 
1615             Node node = new Node(request, nodeId);
1616 
1617             if (!node.find()) {
1618                 response
1619                         .addError("Cannot find an item with ID="
1620                                 + nodeId
1621                                 + ". You may have used an obsolete link, "
1622                                 + "where the item has been deleted.  Please navigate to and use a fresh list of items.");
1623 
1624                 Transition trans = new Transition("", NodeAction.class,
1625                         NodeAction.INDEX);
1626                 checkEmbedded(request, trans);
1627                 trans.executeTransition(request, response);
1628 
1629                 return;
1630             }
1631 
1632             Part part = PartsFactory.getAttribPart(node.getNodeType(),
1633                     attribName);
1634             // todo handle part not found
1635             String attribDisplayName = part.getPartLabel();
1636             response.add(new Output(Node.NODE_TITLE, node.getNodeTitle()));
1637             response.add(new Output(Attribute.ATTRIBUTE_DISPLAY_NAME,
1638                     attribDisplayName));
1639 
1640             Attribute[] attribs = node.getAttributes(part.getPartType());
1641             Attribute representativeAttribute = getRepAttribute(attribs, node,
1642                     attribName);
1643 
1644             // shortcut to custom transition if found
1645             if (representativeAttribute.hasCustomHandler()) {
1646                 try {
1647                     Transition trans = representativeAttribute
1648                             .getEditTrans(request.getAllParameters());
1649                     if (trans != null) {
1650                         checkEmbedded(request, trans);
1651                         trans.executeTransition(request, response, false);
1652                         return;
1653                     }
1654                 } catch (Exception e) {
1655                     throw new ControllerException(e);
1656                 }
1657             }
1658 
1659             if (!part.hasPicklist()) {
1660                 throw new ControllerException(
1661                         "unexpected that this attribute does not have picklist: "
1662                                 + part.getPartType() + " for nodeId: " + nodeId);
1663             }
1664 
1665             Block blk = addPicklist(part, request, response, nodeId);
1666             response.add(blk);
1667 
1668             Transition addTrans = new Transition("Save", NodeAction.class,
1669                     DO_ATTRIB_PICK);
1670             addTrans.addParam(Node.NODE_ID, nodeId);
1671             addTrans.addParam(Attribute.ATTRIBUTE_TYPE, attribName);
1672             if (isEmbeddedMode(request)) {
1673                 addEmbeddedParameter(addTrans);
1674                 addReturnToSenderParameter(request, response, addTrans);
1675             }
1676 
1677             String attribId = blk.getAttribute(Attribute.ATTRIBUTE_ID);
1678             if ((attribId != null) && (attribId.length() > 0)) {
1679                 addTrans.addParam(Attribute.ATTRIBUTE_ID, attribId);
1680             }
1681 
1682             response.add(addTrans);
1683         } catch (Exception e) {
1684             throw new ControllerException(e);
1685         }
1686     }
1687 
1688     /***
1689      * Prompts for adding an attribute.
1690      *
1691      * @param request  ExpressoRequest The <tt>ExpressoRequest</tt> object.
1692      * @param response ExpressoResponse The <tt>ExpressoResponse</tt> object.
1693      * @throws ControllerException upon error.
1694      */
1695     protected void runPromptAddAttribState(
1696             final ServletControllerRequest request,
1697             final ExpressoResponse response) throws ControllerException {
1698         String nodeId = request.getParameter(Node.NODE_ID);
1699         String attribName = request.getParameter(Attribute.ATTRIBUTE_TYPE);
1700 
1701         try {
1702             String attribDisplayName = null;
1703             Part part = null;
1704 
1705             Node node = new Node(request, nodeId);
1706 
1707             if (!node.find()) {
1708                 response
1709                         .addError("Cannot find an item with ID="
1710                                 + nodeId
1711                                 + ". You may have used an obsolete link, "
1712                                 + "where the item has been deleted.  Please navigate to and "
1713                                 + "use a fresh list of items.");
1714 
1715                 if (this.redirectToSender(request, null)) {
1716                     return;
1717                 }
1718 
1719                 Transition trans = new Transition("", NodeAction.class,
1720                         NodeAction.INDEX);
1721                 trans.executeTransition(request, response);
1722 
1723                 return;
1724             }
1725 
1726             part = PartsFactory.getAttribPart(node.getNodeType(), attribName);
1727             attribDisplayName = part.getPartLabel();
1728             response.add(new Output(Node.NODE_TITLE, node.getNodeTitle()));
1729             response.add(new Output(Attribute.ATTRIBUTE_DISPLAY_NAME,
1730                     attribDisplayName));
1731 
1732             Attribute[] attribs = node.getAttributes(part.getPartType());
1733             Attribute representative = getRepAttribute(attribs, node,
1734                     attribName);
1735 
1736             if (representative.hasCustomHandler()) {
1737                 try {
1738                     Transition trans = representative.getEditTrans(request
1739                             .getAllParameters());
1740                     if (trans != null) {
1741                         if (isEmbeddedMode(request)) {
1742                             addEmbeddedParameter(trans);
1743                             propagateReturnToSenderParameter(request, response,
1744                                     trans);
1745                         }
1746                         trans.executeTransition(request, response, false);
1747                         return;
1748                     }
1749                 } catch (Exception e) {
1750                     throw new ControllerException(e);
1751                 }
1752             }
1753 
1754             // add empty box(es)
1755             int num_empty_attribs = 5;
1756 
1757             if (!part.areMultipleAttributesAllowed()) {
1758                 num_empty_attribs = 1;
1759                 response.add(new Output(Part.CARDINALITY,
1760                         " (only single attribute allowed) "));
1761             }
1762 
1763             Block rowBlock = new Block(ROW_BLOCK);
1764             response.add(rowBlock);
1765 
1766             for (int i = 0; i < num_empty_attribs; i++) {
1767                 // Create a Block for each row tuple
1768                 Block row = new Block(ROW);
1769                 rowBlock.add(row);
1770 
1771                 Input valueInput = getTextArea(getValueParamName(i), null, part
1772                         .numDisplayLines(), TEXTAREA_NUM_COLS);
1773                 row.add(valueInput);
1774 
1775                 Input commentInput = getTextArea(getCommentParamName(i), null,
1776                         part.numDisplayLines(), TEXTAREA_NUM_COLS);
1777                 row.add(commentInput);
1778             } // for
1779 
1780             Transition addTrans = new Transition("Add", NodeAction.class,
1781                     DO_EDIT_ATTRIB);
1782             addTrans.addParam(Node.NODE_ID, nodeId);
1783             addTrans.addParam(Attribute.ATTRIBUTE_TYPE, attribName);
1784             if (isEmbeddedMode(request)) {
1785                 propagateReturnToSenderParameter(request, response, addTrans);
1786                 addEmbeddedParameter(addTrans);
1787             }
1788             response.add(addTrans);
1789 
1790             // for link in header
1791             response.add(AddNodeAction.getViewTrans(node.getNodeId()));
1792         } catch (Exception e) {
1793             throw new ControllerException(e);
1794         }
1795     }
1796 
1797     /***
1798      * Edits a single attribute.
1799      *
1800      * @param request  ExpressoRequest
1801      * @param response ExpressoResponse
1802      * @throws ControllerException
1803      */
1804     protected void runPromptEditOneAttribState(final ExpressoRequest request,
1805                                                final ExpressoResponse response) throws ControllerException {
1806         String attribId = request.getParameter(Attribute.ATTRIBUTE_ID);
1807 
1808         try {
1809             Attribute attrib = new Attribute(attribId);
1810             attrib.retrieve();
1811 
1812             Node node = attrib.getParentNode();
1813 
1814             // Node node = new Node(request, nodeId);
1815             // if (!node.find()) {
1816             // response.addError("Cannot find an item with ID=" + nodeId + ".
1817             // You may have used an obsolete link, " +
1818             // "where the item has been deleted. Please navigate to and use a
1819             // fresh list of items.");
1820             // Transition trans = new Transition("", NodeAction.class,
1821             // NodeAction.INDEX);
1822             // trans.transition(request, response);
1823             // return;
1824             // }
1825             Part part = PartsFactory.getAttribPart(node.getNodeType(), attrib
1826                     .getAttribType());
1827 
1828             if (attrib.hasCustomHandler()) {
1829                 try {
1830                     Transition trans = attrib.getEditTrans(request
1831                             .getAllParameters());
1832                     checkEmbedded(request, trans);
1833                     if (trans != null) {
1834                         trans.executeTransition(request, response, false);
1835                         return;
1836                     }
1837                 } catch (Exception e) {
1838                     throw new ControllerException(e);
1839                 }
1840             }
1841 
1842             String attribDisplayName = part.getPartLabel();
1843             response.add(new Output(Node.NODE_TITLE, node.getNodeTitle()));
1844             response.add(new Output(Attribute.ATTRIBUTE_DISPLAY_NAME,
1845                     attribDisplayName));
1846 
1847             Input in = new Input(Attribute.ATTRIBUTE_VALUE);
1848             in.setDefaultValue(attrib.getAttribValueRaw());
1849             response.add(in);
1850             in = new Input(Attribute.ATTRIBUTE_COMMENT);
1851             in.setDefaultValue(attrib.getAttribCommentRaw().trim());
1852             response.add(in);
1853 
1854             Transition trans = new Transition("Save", NodeAction.class,
1855                     DO_EDIT_ATTRIB);
1856             trans.addParam(Attribute.ATTRIBUTE_ID, attribId);
1857             checkEmbedded(request, trans);
1858             response.add(trans);
1859 
1860             // for link in header
1861             response.add(AddNodeAction.getViewTrans(node.getNodeId()));
1862 
1863             // for deletion
1864             trans = new Transition(DO_DELETE_ONE_ATTRIB, this);
1865             trans.addParam(Attribute.ATTRIBUTE_ID, attribId);
1866             checkEmbedded(request, trans);
1867             response.add(trans);
1868         } catch (Exception e) {
1869             throw new ControllerException(e);
1870         }
1871     }
1872 
1873     public static Node getNode(ExpressoRequest request, String nodeId)
1874             throws DBException {
1875         Node node = new Node(request, nodeId);
1876         node.retrieve();
1877 
1878         return node;
1879     }
1880 
1881     /***
1882      * Write changes in attribute to DB. An erased attribute means that we
1883      * should *delete* the item from the DB.
1884      *
1885      * @param request The ExpressoRequest Object
1886      * @param setNum  integer
1887      * @return Attribute
1888      * @throws ControllerException upon error.
1889      */
1890     protected Attribute saveAttribute(ExpressoRequest request, int setNum)
1891             throws ControllerException {
1892         try {
1893             String id = request.getParameter(getIdParamName(setNum)); // can
1894             // be
1895             // null/empty
1896             String value = request.getParameter(getValueParamName(setNum));
1897             String comment = request.getParameter(getCommentParamName(setNum));
1898 
1899             // for case when attrib is new, these are required
1900             String nodeId = request.getParameter(Node.NODE_ID);
1901             String attribType = request.getParameter(Attribute.ATTRIBUTE_TYPE);
1902 
1903             boolean isNew = (id == null) || (id.length() == 0)
1904                     || id.equals(Attribute.ATTRIBUTE_ID_UNKNOWN);
1905 
1906             // at this point, we know we have a new attribute
1907             // but do we really have something to save?
1908             if ((value.length() == 0) && (comment.length() == 0)) {
1909                 return null; // nothing to save
1910             }
1911 
1912             // for new attribute, attribute type & nodeId are required
1913             if ((nodeId == null) || (nodeId.length() == 0)
1914                     || (attribType == null) || (attribType.length() == 0)) {
1915                 throw new ControllerException(
1916                         "nodeId and attrib_type are required parameters");
1917             }
1918 
1919             // this node info should be in cache since we were just editing it
1920             Node node = new Node(request, nodeId);
1921 
1922             if (!node.find()) {
1923                 throw new ControllerException("cannot find node with id: "
1924                         + nodeId);
1925             }
1926 
1927             if (isNew) {
1928                 return node.addAttribute(attribType, value, comment);
1929             } else {
1930                 try {
1931                     return node.updateAttribute(id, value, comment);
1932                 } catch (DBRecordNotFoundException ex) {
1933                     getLogger().warn(ex.getMessage());
1934                     return node.addAttribute(attribType, value, comment);
1935                 }
1936             }
1937 
1938         } catch (DBException dbe) {
1939             throw new ControllerException(dbe);
1940         }
1941     }
1942 
1943     /***
1944      * Utility to create an input block for a given attribute part. <p/> this
1945      * method is complicated by fact that we must associate sets of items
1946      * together as inputs; we'll get back just a pile of parameters from the
1947      * HTML page POST, so we need a way to prefix param names such that they can
1948      * be grouped together later.
1949      * </p>
1950      *
1951      * @param attribs The attributes for the part.
1952      * @param part    The part itself. later.
1953      * @param request The ExpressoRequest object
1954      * @return populated Block
1955      * @throws ControllerException upon error.
1956      */
1957     protected Block getAttributeInputBlock(final Attribute[] attribs,
1958                                            final Part part, final ExpressoRequest request, boolean readOnly)
1959             throws ControllerException {
1960         Block typeBlock = new Block(ROW_BLOCK);
1961 
1962         try {
1963             // no picklist, use text fields for entry
1964             //
1965             // loop through all existing attributes with this name/parentNode,
1966             // creating block for each
1967             // /
1968             int itemNum = 0;
1969 
1970             for (int i = 0; i < attribs.length; i++) {
1971                 Attribute attribute = attribs[i];
1972                 String attribId = attribute.getAttribId();
1973 
1974                 // Create a Block for each row tuple
1975                 Block rowBlock = new Block(ROW);
1976                 typeBlock.add(rowBlock);
1977 
1978                 Block aDeleteBlock = new Block(DO_DELETE_ONE_ATTRIB);
1979                 rowBlock.add(aDeleteBlock);
1980                 if (!readOnly) {
1981                     Transition trans = new Transition(DO_DELETE_ONE_ATTRIB,
1982                             this);
1983                     trans.setName(DO_DELETE_ONE_ATTRIB + i); // each delete
1984                     // button must
1985                     // have unique
1986                     // name
1987                     trans.addParam(Attribute.ATTRIBUTE_ID, attribId);
1988                     checkEmbedded(request, trans);
1989                     aDeleteBlock.add(trans);
1990                 }
1991                 Input valueInput = new Input(getValueParamName(itemNum));
1992                 if (readOnly) {
1993                     valueInput.setAttribute(Input.ATTRIBUTE_READONLY,
1994                             "readonly");
1995                 }
1996                 rowBlock.add(valueInput);
1997 
1998                 if (!part.hasPicklist()) {
1999                     valueInput.setMaxLength(4000);
2000                     valueInput.setType(InputTag.TYPE_TEXTAREA);
2001                     valueInput.setLines(part.numDisplayLines());
2002                     valueInput.setDisplayLength(TEXTAREA_NUM_COLS);
2003 
2004                     // we want raw value
2005                     valueInput.setDefaultValue(attribute.getAttribValueRaw());
2006                 }
2007 
2008                 // Get commetn input, using raw field for default text value,
2009                 // since it
2010                 // does not need translation to HTML
2011                 Input commentInput = getTextArea(getCommentParamName(itemNum),
2012                         attribute.getAttribCommentRaw(),
2013                         part.numDisplayLines(), TEXTAREA_NUM_COLS);
2014 
2015                 if (readOnly) {
2016                     commentInput.setAttribute(Input.ATTRIBUTE_READONLY,
2017                             "readonly");
2018                 }
2019 
2020                 rowBlock.add(commentInput);
2021 
2022                 // add id for this existing attribute
2023                 Input hiddenId = new Input(getIdParamName(itemNum));
2024                 rowBlock.add(hiddenId);
2025                 hiddenId.setType(InputTag.TYPE_HIDDEN);
2026                 hiddenId.setDefaultValue(attribId);
2027 
2028                 itemNum++;
2029 
2030                 if (!readOnly) {
2031                     // separate blocks for +/- because the transition names must
2032                     // be unique
2033                     Block decblock = new Block(AddNodeAction.DEC_ATTRIB_ORDER);
2034                     rowBlock.add(decblock);
2035 
2036                     Block incblock = new Block(AddNodeAction.INC_ATTRIB_ORDER);
2037                     rowBlock.add(incblock);
2038 
2039                     if (attribs.length > 1) {
2040                         if (attribute.getOrderInt() > 1) {
2041                             final Transition trans = new Transition("",
2042                                     AddNodeAction.class,
2043                                     AddNodeAction.DEC_ATTRIB_ORDER);
2044                             trans.setName(AddNodeAction.DEC_ATTRIB_ORDER
2045                                     + attribute.getAttribId());
2046                             trans.addParam(Attribute.ATTRIBUTE_ID, attribute
2047                                     .getAttribId());
2048                             checkEmbedded(request, trans);
2049                             decblock.add(trans);
2050                         }
2051 
2052                         if (attribute.getOrderInt() < attribs.length) {
2053                             final Transition trans = new Transition("",
2054                                     AddNodeAction.class,
2055                                     AddNodeAction.INC_ATTRIB_ORDER);
2056                             trans.setName(AddNodeAction.INC_ATTRIB_ORDER
2057                                     + attribute.getAttribId());
2058                             trans.addParam(Attribute.ATTRIBUTE_ID, attribute
2059                                     .getAttribId());
2060                             checkEmbedded(request, trans);
2061                             incblock.add(trans);
2062                         }
2063                     }
2064                 }
2065             } // for
2066         } catch (Exception dbe) {
2067             throw new ControllerException(dbe);
2068         }
2069 
2070         return typeBlock;
2071     }
2072 
2073     public static Block addPicklist(Part part, ExpressoRequest request,
2074                                     ExpressoResponse response, String nodeId) throws Exception {
2075         Block rowBlock = new Block(ROW_BLOCK);
2076 
2077         // construct dropdown/picklist menu
2078         Input valueInput = new Input(Attribute.ATTRIBUTE_VALUE);
2079         rowBlock.add(valueInput);
2080         valueInput.setType(InputTag.TYPE_DROPDOWN);
2081 
2082         // existing info
2083         String selected = "";
2084         String comment = "";
2085 
2086         // existing attrib?
2087         Node node = new Node(request, nodeId);
2088         Attribute[] attribs = node.getAttributes(part.getPartType());
2089         boolean hasAttrib = attribs.length > 0;
2090 
2091         if (hasAttrib) {
2092             Attribute attribute = attribs[0];
2093             selected = attribute.getAttribValue();
2094 
2095             if (selected == null) {
2096                 selected = "";
2097             }
2098 
2099             comment = attribute.getAttribComment();
2100 
2101             if (comment == null) {
2102                 comment = "";
2103             }
2104 
2105             rowBlock.setAttribute(Attribute.ATTRIBUTE_ID, attribute
2106                     .getAttribId());
2107         }
2108 
2109         String[][] picklistArray = null;
2110 
2111         try {
2112             picklistArray = part.getPicklistArray(request);
2113         } catch (Exception e) {
2114             throw new DBException(e);
2115         }
2116 
2117         boolean foundSelected = false;
2118         String defaultSelected = "";
2119         if (picklistArray.length > 0 && picklistArray[0].length > 0) {
2120             defaultSelected = picklistArray[0][0]; // if selected item not
2121             // found, and if better
2122             // default not found
2123         } else {
2124             response.addError("Picklist is empty!");
2125         }
2126 
2127         for (int i = 0; i < picklistArray.length; i++) {
2128             valueInput.addValidValue(picklistArray[i][0], picklistArray[i][1]);
2129 
2130             if (selected.equals(picklistArray[i][0])) {
2131                 foundSelected = true;
2132                 valueInput.setDefaultValue(picklistArray[i][0]);
2133             }
2134 
2135             // make sure a better default is available on list (we expect this)
2136             if (PickList.NOT_SPECIFIED_DISPLAY.equals(picklistArray[i][0])) {
2137                 defaultSelected = PickList.NOT_SPECIFIED_DISPLAY;
2138             }
2139         }
2140 
2141         // default if there is no previous selection
2142         if (!foundSelected) {
2143             valueInput.setDefaultValue(defaultSelected);
2144         }
2145 
2146         // add edit link if user has rights
2147         List picklist = null;
2148         picklist = part.getPicklist(request);
2149 
2150         // need an initial entry in picklist to indicate who can
2151         // edit this list.
2152         if ((picklist.size() > 0)
2153                 && ((PickList) picklist.get(0)).canRequesterWrite()) {
2154             Transition promptEditPicklist = new Transition("",
2155                     PicklistAction.class, PicklistAction.PROMPT_LIST);
2156             rowBlock.add(promptEditPicklist);
2157             promptEditPicklist.addParam(PickList.NODE_TYPE, part
2158                     .getParentType());
2159             promptEditPicklist.addParam(PickList.ATTRIBUTE_TYPE, part
2160                     .getPartType());
2161             checkEmbedded(request, promptEditPicklist);
2162             promptEditPicklist.addParam(Node.NODE_ID, nodeId);
2163         }
2164         Input commentInput = getTextArea(Attribute.ATTRIBUTE_COMMENT, comment);
2165         rowBlock.add(commentInput);
2166 
2167         return rowBlock;
2168     }
2169 
2170     /***
2171      * Utility to create an output block for a given shared node part.
2172      *
2173      * @param srcNodeId The source node.
2174      * @param srcType   The source node type.
2175      * @param request   The ExpressoRequest object
2176      * @param part      the Part we're rendering to the block.
2177      * @param nodeTitle Unused
2178      * @param canEdit   true if the node can be edited.
2179      * @return populated block.
2180      * @throws ControllerException upon error.
2181      * @throws DBException         upon database error.
2182      */
2183     protected Block getSharedNodeBlock(String srcNodeId, String srcType,
2184                                        ExpressoRequest request, Part part, String nodeTitle,
2185                                        boolean canEdit) throws ControllerException, DBException {
2186         if (part.isOwnedAttribute()) {
2187             throw new ControllerException("cannot handle attribute node here");
2188         }
2189 
2190         Block typeBlock = new Block("part");
2191         typeBlock.add(new Output(Part.PART_DISPLAY_NAME, part.getPartLabel()));
2192 
2193         // Relation.NODE_PART_RELATION_TYPE is signal to JSP that this is shared
2194         // block
2195         typeBlock
2196                 .add(new Output(Relation.RELATION_TYPE, part.getNodeRelation()));
2197 
2198         // output for help string identifier (anchor name)
2199         // if type is same, output relation; if type is different, output part
2200         // type
2201         if (srcType.equals(part.getPartType())) {
2202             typeBlock.add(new Output(Part.PART_HELP_STRING, part
2203                     .getNodeRelation()));
2204         } else {
2205             typeBlock
2206                     .add(new Output(Part.PART_HELP_STRING, part.getPartType()));
2207         }
2208 
2209         try {
2210             Node srcnode = new Node(request, srcNodeId);
2211             Node[] related = srcnode.getRelatedNodes(part.getNodeRelation(),
2212                     part.getPartType());
2213 
2214             //
2215             // loop through all nodes found, creating block
2216             // /
2217             for (int i = 0; i < related.length; i++) {
2218                 Node node = related[i];
2219 
2220                 // Create a Block for each row tuple
2221                 Block rowBlock = new Block(ROW_BLOCK);
2222                 typeBlock.add(rowBlock);
2223 
2224                 String blockNodeId = node.getNodeId();
2225 
2226                 // rowBlock.add( new Output(Node.NODE_ID, blockNodeId) );
2227                 rowBlock.add(new Output(Node.NODE_TITLE, node.getNodeTitle()));
2228                 rowBlock.add(new Output(Node.NODE_ANNOTATION, strTrunc(node
2229                         .getNodeAnnotation(), MAX_CHARS_OUTPUT)));
2230 
2231                 // maybe somedays we'll offer a means to annotate relations
2232                 // node.getRelationAnnotation(),
2233                 rowBlock.add(new Output(Relation.RELATION_ANNOTATION, str("")));
2234                 rowBlock.add(new Output(Node.NODE_COMMENT, strTrunc(node
2235                         .getNodeComment(), MAX_CHARS_OUTPUT)));
2236 
2237                 // Add a "view" transition
2238                 Transition editTransition = new Transition(
2239                         AddNodeAction.VIEW_NODE, "View", AddNodeAction.class,
2240                         AddNodeAction.VIEW_NODE);
2241                 checkEmbedded(request, editTransition);
2242                 editTransition.addParam(Node.NODE_ID, blockNodeId);
2243                 rowBlock.add(editTransition);
2244             }
2245         } catch (DBException e) {
2246             throw new ControllerException(e);
2247         }
2248 
2249         if (canEdit) {
2250             Transition associateTrans = new Transition(PROMPT_PICKLIST_NODE,
2251                     this);
2252             associateTrans.setLabel("Edit");
2253             associateTrans.addParam(Node.NODE_TYPE, part.getPartType());
2254 
2255             // src of association
2256             associateTrans.addParam(Node.NODE_ID, srcNodeId);
2257             associateTrans.addParam(Relation.RELATION_TYPE, part
2258                     .getNodeRelation());
2259             checkEmbedded(request, associateTrans);
2260             typeBlock.add(associateTrans);
2261         }
2262 
2263         return typeBlock;
2264     }
2265 
2266     /***
2267      * Add number suffix from to Attribute.ATTRIBUTE_COMMENT_PREFIX.
2268      *
2269      * @param setNum The number to add.
2270      * @return String.
2271      */
2272     private static String getCommentParamName(int setNum) {
2273         return Attribute.ATTRIBUTE_COMMENT_PREFIX + setNum;
2274     }
2275 
2276     /***
2277      * Add number suffix from to Attribute.ATTRIBUTE_ID_PREFIX.
2278      *
2279      * @param setNum The number to add.
2280      * @return java.lang.String.
2281      */
2282     private static String getIdParamName(int setNum) {
2283         return Attribute.ATTRIBUTE_ID_PREFIX + setNum;
2284     }
2285 
2286     /***
2287      * Ddd number suffix from to Attribute.ATTRIBUTE_VALUE_PREFIX.
2288      *
2289      * @param setNum The number suffix
2290      * @return java.lang.String.
2291      */
2292     private static String getValueParamName(int setNum) {
2293         return Attribute.ATTRIBUTE_VALUE_PREFIX + setNum;
2294     }
2295 
2296     /***
2297      * Find all properly-typed nodes related to this source node, with relation
2298      * of given type.
2299      *
2300      * @param srcNodeId
2301      *            source node id
2302      * @param relation
2303      *            the relationship type
2304      * @param nodeType
2305      *            nodes of this type (only) will be returned
2306      * @param orderList
2307      *            (returned param) list to receive ordered list of
2308      *            MultiDBObjects; pass in null if you don't want this list added
2309      * @return hash with key = node Id, value = node
2310      * @throws DBException
2311      *             upon error.
2312      */
2313     // private Hashtable getRelatedNodesHash(String srcNodeId, String relation,
2314     // String nodeType, List orderList ) throws DBException {
2315     //
2316     // Node srcnode = new Node(srcNodeId);
2317     // List list = srcnode.getRawRelated(relation, nodeType);
2318     //
2319     // if (orderList != null) {
2320     // orderList.addAll(list);
2321     // }
2322     //
2323     // Hashtable alreadySelected = new Hashtable(list.size());
2324     //
2325     // for (int i = 0; i < list.size(); i++) {
2326     // MultiDBObject aMultiObject = (MultiDBObject) list.get(i);
2327     // String nodeId = aMultiObject.getField(Node.NODE_JOIN,
2328     // Node.NODE_ID);
2329     // alreadySelected.put(nodeId, aMultiObject);
2330     // }
2331     //
2332     // return alreadySelected;
2333     // }
2334 
2335     /***
2336      * Prompt to confirm delete.
2337      *
2338      * @param request  The ExpressoRequest object.
2339      * @param response The ExpressoResponse object.
2340      * @throws ControllerException upon error.
2341      */
2342     protected void runPromptDeleteNodeState(final ExpressoRequest request,
2343                                             final ExpressoResponse response) throws ControllerException {
2344         try {
2345             Node existing = getNode(request);
2346             String titleStr = existing.getNodeTitle();
2347 
2348             response.add(new Output(Node.NODE_TITLE, titleStr));
2349 
2350             // Create an transition to submit the form
2351             Transition submitTransition = new Transition(DO_DELETE_NODE, this);
2352             submitTransition.setLabel("Delete");
2353             submitTransition.addParam(Node.NODE_ID, existing.getNodeId());
2354             response.add(submitTransition);
2355 
2356             Transition treeTransition = new Transition(PROMPT_DELETE_NODE_TREE,
2357                     this);
2358             treeTransition.setLabel("Delete tree...");
2359             treeTransition.addParam(Node.NODE_ID, existing.getNodeId());
2360             checkEmbedded(request, treeTransition);
2361             response.add(treeTransition);
2362         } catch (DBException dbe) {
2363             ErrorCollection errors = new ErrorCollection();
2364             errors.addError(dbe.getMessage());
2365             response.setFormCache();
2366             response.saveErrors(errors);
2367 
2368             try {
2369                 transition(LIST_NODE, request, response);
2370             } catch (NonHandleableException nhe) {
2371                 throw new ControllerException(nhe);
2372             }
2373         }
2374     }
2375 
2376     public static Node getNode(final ExpressoRequest request)
2377             throws ControllerException, DBException {
2378         String nodeId = request.getParameter(Node.NODE_ID);
2379 
2380         if ((nodeId == null) || (nodeId.length() == 0)) {
2381             throw new ControllerException("nodeId is a required parameter");
2382         }
2383 
2384         return getNode(request, nodeId);
2385     }
2386 
2387     /***
2388      * show lists of contained nodes that will be deleted (with checkmarks for
2389      * opt-out) and list of contained nodes that will not because they are
2390      * contained 'externally' to this tree. <p/> algorithm is complex: we can
2391      * determine internal/external links easily enough when given a hash of all
2392      * nodes in tree: any links to tree-external node makes a node undeletable.
2393      * But for all the nodes considered 'internal' on the first pass, some of
2394      * their containers might be declared undeletable. Then we need additional
2395      * passes to make sure the nodes established as undeletable in pass N-1 also
2396      * 'preserves' any nodes it contains. <p/> In other words, a branch is
2397      * deleted only if its root is not external, and vice-versa.
2398      */
2399     protected void runPromptDeleteNodeTreeState(final ExpressoRequest request,
2400                                                 final ExpressoResponse response) throws ControllerException {
2401         try {
2402 
2403             // if (1==1) throw new ControllerException("out of order; come back
2404             // later.");
2405 
2406             Node existing = getNode(request);
2407 
2408             response.add(new Output(Node.NODE_TITLE, existing.getNodeTitle()));
2409 
2410             // Create an transition to submit the form
2411             Transition submitTransition = new Transition(DO_DELETE_NODE, this);
2412             submitTransition.setLabel("Delete");
2413             submitTransition.addParam(Node.NODE_ID, existing.getNodeId());
2414             checkEmbedded(request, submitTransition);
2415             response.add(submitTransition);
2416 
2417             // handle tree
2418             HashMap allNodeHash = new HashMap();
2419             existing.setAttribute(Node.INDENT, new Integer(1)); // root node
2420             // starts at
2421             // level 1; used
2422             // for
2423             // formatting
2424             // output
2425             existing.getNodesInStronglyRelatedTree(request, allNodeHash);
2426 
2427             // We're more likely to not delete things than delete things
2428             // So preallocate to save some time.
2429             Set saveSet = new HashSet(allNodeHash.values().size());
2430             Set toDeleteSet = new HashSet();
2431             TreeDeletionSelector selector = new TreeDeletionSelector(
2432                     allNodeHash);
2433             selector.determineDeletionTree(existing, saveSet, toDeleteSet);
2434 
2435             // Copy and transform the sets to sortable lists.
2436             List toDelete = new ArrayList(toDeleteSet);
2437             List notdel = new ArrayList(saveSet);
2438 
2439             // add to response a list of ones we will not delete
2440             Block noDelBlock = new Block(PERMS_BLOCK);
2441             response.add(noDelBlock);
2442             Collections.sort(notdel, new IndentComparator());
2443 
2444             for (Iterator iterator = notdel.iterator(); iterator.hasNext();) {
2445                 Node anode = (Node) iterator.next();
2446                 String blkname = "";
2447                 Integer indent = (Integer) anode.getAttribute(Node.INDENT);
2448 
2449                 if (indent != null) {
2450                     blkname = StringUtil.repeatString(
2451                             "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;", indent
2452                             .intValue());
2453                 }
2454 
2455                 Block rowblk = new Block(blkname);
2456                 noDelBlock.add(rowblk);
2457 
2458                 // this related item is in the main set to be deleted--it 'wraps
2459                 // around'--ok to delete
2460                 Transition trans = AddNodeAction
2461                         .getViewTrans(anode.getNodeId());
2462                 trans.setLabel(anode.getNodeTitle());
2463                 checkEmbedded(request, trans);
2464                 rowblk.add(trans);
2465 
2466                 rowblk.add(new Output(Node.NODE_TYPE, anode.getEntity()
2467                         .getDisplayName()));
2468             }
2469 
2470             // and list of those that will be deleted
2471             Collections.sort(toDelete, new IndentComparator());
2472 
2473             Block deleteBlock = new Block(ROW_BLOCK);
2474             response.add(deleteBlock);
2475 
2476             for (Iterator iterator = toDelete.iterator(); iterator.hasNext();) {
2477                 Node anode = (Node) iterator.next();
2478                 String blkname = "";
2479                 Integer indent = (Integer) anode.getAttribute(Node.INDENT);
2480 
2481                 if (indent != null) {
2482                     blkname = StringUtil.repeatString(
2483                             "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;", indent
2484                             .intValue());
2485                 }
2486 
2487                 Block rowblk = new Block(blkname);
2488                 deleteBlock.add(rowblk);
2489 
2490                 // this related item is in the main set to be deleted--it 'wraps
2491                 // around'--ok to delete
2492                 Transition trans = AddNodeAction
2493                         .getViewTrans(anode.getNodeId());
2494                 trans.setLabel(anode.getNodeTitle());
2495                 checkEmbedded(request, trans);
2496                 rowblk.add(trans);
2497 
2498                 rowblk.add(new Output(Node.NODE_TYPE, anode.getEntity()
2499                         .getDisplayName()));
2500 
2501                 Input in = new Input(SIBLING_MARKER);
2502                 in.setType(InputTag.TYPE_CHECKBOX);
2503                 in.addValidValue(anode.getNodeId(), "");
2504                 in.setDefaultValue(anode.getNodeId());
2505                 rowblk.add(in);
2506                 in.setAttribute(Input.SELECTED, "true");
2507 
2508                 // display indentation
2509                 // deleteBlock.add(new Output(ROW, aRel.getRelationTypeName()));
2510 
2511                 //
2512                 // To eliminate large pauses while the garbage collector
2513                 // runs, it is best to nullify the biggest memory hogs
2514                 // in this function and then run a garbage collection
2515                 //
2516                 allNodeHash = null;
2517                 selector = null;
2518                 saveSet = null;
2519                 toDeleteSet = null;
2520                 toDelete = null;
2521                 notdel = null;
2522                 System.gc();
2523             }
2524         } catch (Exception dbe) {
2525             throw new ControllerException(dbe);
2526         }
2527     }
2528 
2529     /***
2530      * Performs the deletion of the node after prompt state.
2531      *
2532      * @param request  ServletControllerRequest the servlet controller request.
2533      * @param response ExpressoResponse
2534      * @throws ControllerException
2535      */
2536     protected void runDoDeleteNodeState(final ServletControllerRequest request,
2537                                         final ExpressoResponse response) throws ControllerException {
2538         try {
2539             Node existing = getNode(request);
2540 
2541             // tree delete will take another phase of prompting
2542             final boolean isTreeDelete = request
2543                     .getParameter(PROMPT_DELETE_NODE_TREE) != null; // checkbox
2544             // param
2545             // only
2546             // shows up
2547             // if
2548             // checked
2549 
2550             if (isTreeDelete) {
2551                 Transition trans = new Transition(PROMPT_DELETE_NODE_TREE, this);
2552                 trans.addParam(Node.NODE_ID, existing.getNodeId());
2553                 checkEmbedded(request, trans);
2554                 trans.redirectTransition(request, response);
2555 
2556                 return;
2557             }
2558 
2559             // go back to the list after deletion
2560             Transition trans = null;
2561 
2562             // special handler?
2563             NodeType entity = existing.getEntity();
2564 
2565             if (entity.hasCustomHandler()) {
2566                 INodeHandler handler = entity.getCustomHandler();
2567                 trans = handler.getListTransition(request);
2568             } else {
2569                 trans = getListTransition(existing.getNodeType());
2570             }
2571 
2572             assert trans != null : "Transition should not be null by this point";
2573             checkEmbedded(request, trans);
2574 
2575             existing.delete(true); // true for cascading deletes of all
2576             // attributes & relations
2577 
2578             // delete tree if specified
2579             String[] delIds = getParamValues(
2580                     (ServletControllerRequest) request, SIBLING_MARKER);
2581 
2582             for (int i = 0; (delIds != null) && (i < delIds.length); i++) {
2583                 String id = delIds[i];
2584                 getNode(request, id).delete(); // delete 'em all
2585             }
2586 
2587             trans.redirectTransition(request, response);
2588         } catch (Exception dbe) {
2589             throw new ControllerException(dbe);
2590         }
2591     }
2592 
2593     /***
2594      * @param type node type of desired list; null is ok--will default to first
2595      *             list (design patterns)
2596      * @return Transition.
2597      */
2598     public static Transition getListTransition(String type) {
2599         Transition trans = new Transition("", NodeAction.class, LIST_NODE);
2600         trans.addParam(Node.NODE_TYPE, type);
2601         return trans;
2602     }
2603 
2604     /***
2605      * Prompts for cloning the node.
2606      *
2607      * @param request  ExpressoRequest
2608      * @param response ExpressoResponse
2609      * @throws ControllerException
2610      */
2611     protected void runPromptCloneNodeState(ExpressoRequest request,
2612                                            ExpressoResponse response) throws ControllerException {
2613         String nodeId = request.getParameter(Node.NODE_ID);
2614 
2615         if ((nodeId == null) || (nodeId.length() == 0)) {
2616             throw new ControllerException("nodeId is a required parameter");
2617         }
2618 
2619         try {
2620             Node existing = new Node(request, nodeId);
2621             existing.retrieve();
2622 
2623             addAttribIO(nodeId, response, existing);
2624 
2625             // Create an transition to submit the form
2626             Transition submitTransition = new Transition(DO_CLONE_NODE, this);
2627             submitTransition.setLabel("Duplicate an Independent");
2628             checkEmbedded(request, submitTransition);
2629             response.add(submitTransition);
2630 
2631             Transition siblingTrans = new Transition(SIBLING_MARKER, "",
2632                     getClass(), DO_CLONE_NODE);
2633             siblingTrans.addParam(SIBLING_MARKER, "true");
2634             siblingTrans.setLabel("Duplicate Within Container");
2635             checkEmbedded(request, siblingTrans);
2636             response.add(siblingTrans);
2637 
2638             Transition treeTrans = new Transition(PROMPT_CLONE_TREE, "",
2639                     getClass(), PROMPT_CLONE_TREE);
2640             treeTrans.setLabel("Duplicate Entire Tree Beneath");
2641             checkEmbedded(request, treeTrans);
2642             response.add(treeTrans);
2643         } catch (DBException dbe) {
2644             ErrorCollection errors = new ErrorCollection();
2645             errors.addError(dbe.getMessage());
2646             response.setFormCache();
2647             response.saveErrors(errors);
2648 
2649             try {
2650                 transition(LIST_NODE, request, response);
2651             } catch (NonHandleableException nhe) {
2652                 throw new ControllerException(nhe);
2653             }
2654         }
2655     }
2656 
2657     private void addAttribIO(String nodeId, ExpressoResponse response,
2658                              Node existing) throws ControllerException, DBException {
2659         Input hiddenId = new Input(Node.NODE_ID);
2660         hiddenId.setType(InputTag.TYPE_HIDDEN);
2661         hiddenId.setDefaultValue(nodeId);
2662         response.add(hiddenId);
2663 
2664         Input titleInput = new Input(Node.NODE_TITLE);
2665         titleInput.setType(InputTag.TYPE_TEXT);
2666         titleInput.setDefaultValue("Copy of " + existing.getNodeTitle());
2667         response.add(titleInput);
2668 
2669         // input for tree clone--title suffix
2670         Input input = new Input(TITLE_SORT);
2671         input.setType(InputTag.TYPE_TEXT);
2672         input.setDefaultValue("copy");
2673         response.add(input);
2674 
2675         Output output = new Output(Node.NODE_TITLE, existing.getNodeTitle());
2676         response.add(output);
2677     }
2678 
2679     /***
2680      * Prompts for cloning an entire tree of nodes.
2681      *
2682      * @param request  ExpressoRequest
2683      * @param response ExpressoResponse
2684      * @throws ControllerException
2685      */
2686     protected void runPromptCloneTreeState(final ExpressoRequest request,
2687                                            final ExpressoResponse response) throws ControllerException {
2688         String nodeId = request.getParameter(Node.NODE_ID);
2689 
2690         if ((nodeId == null) || (nodeId.length() == 0)) {
2691             throw new ControllerException("nodeId is a required parameter");
2692         }
2693 
2694         try {
2695             Node existing = new Node(request, nodeId);
2696             existing.retrieve();
2697 
2698             Input hiddenId = new Input(Node.NODE_ID);
2699             hiddenId.setType(InputTag.TYPE_HIDDEN);
2700             hiddenId.setDefaultValue(nodeId);
2701             response.add(hiddenId);
2702 
2703             Input titleInput = new Input(Node.NODE_TITLE);
2704             titleInput.setType(InputTag.TYPE_TEXT);
2705             titleInput.setDefaultValue("Copy of " + existing.getNodeTitle());
2706             response.add(titleInput);
2707 
2708             Output output = new Output(Node.NODE_TITLE, existing.getNodeTitle());
2709             response.add(output);
2710 
2711             // todo prompt entire tree
2712             // need level of hierarchy, id, proposed title, type
2713             // Create an transition to submit the form
2714             Transition treeTrans = new Transition(DO_CLONE_TREE, "",
2715                     getClass(), DO_CLONE_TREE);
2716             treeTrans.addParam(Node.NODE_ID, nodeId);
2717             treeTrans.setLabel("Duplicate Entire Tree Beneath");
2718             checkEmbedded(request, treeTrans);
2719             response.add(treeTrans);
2720         } catch (DBException dbe) {
2721             ErrorCollection errors = new ErrorCollection();
2722             errors.addError(dbe.getMessage());
2723             response.setFormCache();
2724             response.saveErrors(errors);
2725 
2726             try {
2727                 transition(LIST_NODE, request, response);
2728             } catch (NonHandleableException nhe) {
2729                 throw new ControllerException(nhe);
2730             }
2731         }
2732     }
2733 
2734     /***
2735      * Clone either orphan or sibling. If this is the ORPHAN clone, where
2736      * &quot;owning&quot; relations are not copied, creating an orphan object
2737      * has links to the shared relations &quot;below&quot; it in the &quot;part
2738      * of&quot; relation hierarchy, but not links to similar relations
2739      * &quot;above&quot; this object. in contrast, sibling clones also
2740      * duplicated the 'container' relations
2741      *
2742      * @param request  The ExpressoRequest object.
2743      * @param response The ExpressoResponse object.
2744      * @throws ControllerException upon error.
2745      */
2746     protected void runDoCloneNodeState(ExpressoRequest request,
2747                                        ExpressoResponse response) throws ControllerException, DBException {
2748         String nodeId = request.getParameter(Node.NODE_ID);
2749 
2750         if (nodeId == null) {
2751             throw new ControllerException("nodeId is a required parameter");
2752         }
2753 
2754         String title = request.getParameter(Node.NODE_TITLE);
2755 
2756         if (title == null) {
2757             throw new ControllerException("title is a required parameter");
2758         }
2759 
2760         ErrorCollection ec = new ErrorCollection();
2761 
2762         Node origNode = new Node(nodeId);
2763         origNode.retrieve();
2764 
2765         NodeCloner nodeCloner = new NodeCloner(origNode, title);
2766         Node clone = null;
2767 
2768         // clone according to button param
2769         String sibling = request.getParameter(SIBLING_MARKER);
2770         if (sibling == null) {
2771             clone = nodeCloner.cloneAsOrphan(ec);
2772         } else {
2773             clone = nodeCloner.cloneAsSibling(ec);
2774         }
2775 
2776         if (ec.size() > 0) {
2777             response.saveErrors(ec);
2778             Transition trans = new Transition(PROMPT_CLONE_NODE, this);
2779             trans.addParam(Node.NODE_ID, nodeId);
2780             checkEmbedded(request, trans);
2781             trans.executeTransition(request, response);
2782 
2783             return;
2784         }
2785 
2786         assert clone != null : "Cloned node should have been either clone or there should have been errors";
2787 
2788         // go back to the list after clone
2789         Transition trans = new Transition("", "", AddNodeAction.class,
2790                 AddNodeAction.VIEW_NODE);
2791         trans.addParam(Node.NODE_ID, clone.getNodeId());
2792         checkEmbedded(request, trans);
2793         trans.redirectTransition(request, response);
2794     }
2795 
2796     /***
2797      * Clone either orphan or sibling. if this is the ORPHAN clone, where
2798      * &quot;owning&quot; relations are not copied, creating an orphan object
2799      * has links to the shared relations &quot;below&quot; it in the &quot;part
2800      * of&quot; relation hierarchy, but not links to similar relations
2801      * &quot;above&quot; this object. in contrast, sibling clones also
2802      * duplicated the 'container' relations
2803      *
2804      * @param request  The ExpressoRequest object.
2805      * @param response The ExpressoResponse object.
2806      * @throws ControllerException upon error.
2807      */
2808     protected void runDoCloneTreeState(ExpressoRequest request,
2809                                        ExpressoResponse response) throws ControllerException {
2810         String nodeId = request.getParameter(Node.NODE_ID);
2811 
2812         if (nodeId == null) {
2813             throw new ControllerException("nodeId is a required parameter");
2814         }
2815 
2816         String title = request.getParameter(Node.NODE_TITLE);
2817 
2818         if (title == null) {
2819             throw new ControllerException("title is a required parameter");
2820         }
2821 
2822         try {
2823             Node origNode = new Node(request, nodeId);
2824             origNode.retrieve();
2825 
2826             // is title ok?
2827             Node titleSearch = new Node(request);
2828             titleSearch.setNodeTitle(title);
2829 
2830             if (titleSearch.find()) {
2831                 response.addError("Duplicate title not allowed. Please choose"
2832                         + " something different.");
2833 
2834                 Transition trans = new Transition(PROMPT_CLONE_NODE, this);
2835                 trans.addParam(Node.NODE_ID, nodeId);
2836                 checkEmbedded(request, trans);
2837                 trans.executeTransition(request, response);
2838 
2839                 return;
2840             }
2841 
2842             // map of all nodes to be cloned: key = id, includes title and any
2843             // special instructions
2844             HashMap map = new HashMap();
2845 
2846             Node clone = origNode.cloneTree(title, map);
2847 
2848             // go back to the list after deletion
2849             Transition trans = new Transition("", "", AddNodeAction.class,
2850                     AddNodeAction.VIEW_NODE);
2851             trans.addParam(Node.NODE_ID, clone.getNodeId());
2852             checkEmbedded(request, trans);
2853             trans.redirectTransition(request, response);
2854         } catch (Exception dbe) {
2855             throw new ControllerException(dbe);
2856         }
2857     }
2858 
2859     /***
2860      * Generate an index of all nodes--just use shortcuts from (auto) footer.
2861      *
2862      * @param request  The ExpressoRequest object.
2863      * @param response The ExpressoResponse object.
2864      * @throws ControllerException upon error.
2865      */
2866     protected void runIndexState(final ExpressoRequest request,
2867                                  final ExpressoResponse response) throws ControllerException {
2868     }
2869 
2870     /***
2871      * Edit permission groups for this node.
2872      *
2873      * @param request  The ExpressoRequest object.
2874      * @param response The ExpressoResponse object.
2875      * @throws ControllerException upon error.
2876      */
2877     protected void runPromptEditPermsState(final ExpressoRequest request,
2878                                            final ExpressoResponse response) throws ControllerException {
2879         String nodeId = request.getParameter(Node.NODE_ID);
2880 
2881         if (nodeId == null) {
2882             throw new ControllerException("nodeId is a required parameter");
2883         }
2884 
2885         try {
2886             Node origNode = getNode(request);
2887             response.add(new Output(Node.NODE_TITLE, origNode.getNodeTitle()));
2888 
2889             Transition trans = new Transition(AddNodeAction.VIEW_NODE, "",
2890                     AddNodeAction.class, AddNodeAction.VIEW_NODE);
2891             trans.addParam(Node.NODE_ID, origNode.getNodeId());
2892             checkEmbedded(request, trans);
2893 
2894             response.add(trans);
2895 
2896             List groupsOfUsersMembership = RequestRegistry.getUser()
2897                     .getGroupsList();
2898 
2899             List groupsForListing;
2900             if (request.getUserInfo().isAdmin()) {
2901                 // admin gets ALL groups, less unused ones
2902                 List allgrps = UserGroup.getAllGroups();
2903                 groupsForListing = new ArrayList(allgrps.size());
2904                 for (Iterator iterator = allgrps.iterator(); iterator.hasNext();) {
2905                     UserGroup group = (UserGroup) iterator.next();
2906                     groupsForListing.add(group.getGroupName());
2907                 }
2908             } else {
2909                 groupsForListing = new ArrayList(groupsOfUsersMembership);
2910             }
2911 
2912             groupsForListing.remove(UserGroup.ALL_USERS_GROUP); // we put this
2913             // in
2914             // 'associated'
2915             groupsForListing.remove(UserGroup.UNKNOWN_USERS_GROUP);
2916             groupsForListing.remove(UserGroup.NOT_REG_USERS_GROUP);
2917 
2918             // the groups that have ANY rights to manipulate this node
2919             List nodeGroups = origNode.getGroups();
2920 
2921             if (nodeGroups.size() > 0) {
2922                 Block permblock = new Block(NodeAction.PERMS_BLOCK);
2923                 response.add(permblock);
2924 
2925                 int i = 0;
2926 
2927                 for (Iterator iterator = nodeGroups.iterator(); iterator
2928                         .hasNext();) {
2929                     RowGroupPerms groupperms = (RowGroupPerms) iterator.next();
2930 
2931                     if (groupperms.canGroupWrite()) {
2932                         Block rowBlock = new Block(ROW_BLOCK);
2933                         permblock.add(rowBlock);
2934                         // we will add a remove button for this group,
2935                         // so do not permit an "add" button for this group
2936                         groupsForListing.remove(groupperms.group());
2937 
2938                         // need group description
2939                         UserGroup usergroup = new UserGroup();
2940                         usergroup.setRequestingUser(SuperUser.INSTANCE); // just
2941                         // for
2942                         // usergroup
2943                         // display
2944                         // name
2945                         // retrieval
2946                         usergroup.setGroupName(groupperms.group());
2947                         usergroup.retrieve();
2948 
2949                         rowBlock.add(new Output(UserGroup.GROUP_NAME_FIELD,
2950                                 usergroup.getGroupDescription()));
2951 
2952                         trans = new Transition(REMOVE_GROUP + i++, "Remove",
2953                                 getClass(), REMOVE_GROUP);
2954                         trans.addParam(Node.NODE_ID, origNode.getNodeId());
2955                         checkEmbedded(request, trans);
2956                         trans.addParam(UserGroup.GROUP_NAME_FIELD, groupperms
2957                                 .group());
2958                         rowBlock.add(trans);
2959                     }
2960                 } // for
2961             }
2962 
2963             if (groupsForListing.size() > 0) {
2964                 // filter out some groups
2965                 Block permblock = new Block(NodeAction.ADD_GROUP);
2966                 response.add(permblock);
2967 
2968                 int i = 0;
2969 
2970                 for (Iterator iterator = groupsForListing.iterator(); iterator
2971                         .hasNext();) {
2972                     String groupname = (String) iterator.next();
2973 
2974                     Block rowBlock = new Block(ROW_BLOCK);
2975                     permblock.add(rowBlock);
2976 
2977                     // need group description
2978                     UserGroup usergroup = new UserGroup();
2979                     usergroup.setRequestingUser(User.getAdmin(request
2980                             .getDataContext())); // just for usergroup
2981                     // display name retrieval
2982                     usergroup.setGroupName(groupname);
2983                     usergroup.retrieve();
2984 
2985                     rowBlock.add(new Output(UserGroup.GROUP_NAME_FIELD,
2986                             usergroup.getGroupDescription()));
2987 
2988                     trans = new Transition(ADD_GROUP + i++, "Add", getClass(),
2989                             ADD_GROUP);
2990                     trans.addParam(Node.NODE_ID, origNode.getNodeId());
2991                     trans.addParam(UserGroup.GROUP_NAME_FIELD, groupname);
2992                     checkEmbedded(request, trans);
2993                     rowBlock.add(trans);
2994                 }
2995             }
2996         } catch (Exception dbe) {
2997             throw new ControllerException(dbe);
2998         }
2999     }
3000 
3001     /***
3002      * Edit permission groups for this node.
3003      *
3004      * @param request  The ExpressoRequest object.
3005      * @param response The ExpressoResponse object.
3006      * @throws ControllerException upon error.
3007      */
3008     protected void runRemoveGroupState(final ExpressoRequest request,
3009                                        final ExpressoResponse response) throws ControllerException {
3010         String nodeId = request.getParameter(Node.NODE_ID);
3011 
3012         if (nodeId == null) {
3013             throw new ControllerException("nodeId is a required parameter");
3014         }
3015 
3016         String group = request.getParameter(UserGroup.GROUP_NAME_FIELD);
3017 
3018         if (group == null) {
3019             throw new ControllerException("group name is a required parameter");
3020         }
3021 
3022         try {
3023             Node origNode = new Node(request, nodeId);
3024             origNode.retrieve();
3025 
3026             Transition trans = NodeAction.getListTransition(origNode
3027                     .getNodeType());
3028             checkEmbedded(request, trans);
3029 
3030             List list = origNode.getGroups();
3031 
3032             if (list.size() == 1) {
3033                 response.addError("Cannot remove last group");
3034                 trans.executeTransition(request, response);
3035 
3036                 return;
3037             }
3038 
3039             origNode.removeGroup(group);
3040 
3041             NodeType type = origNode.getEntity();
3042 
3043             if (type.hasCustomHandler()) {
3044                 INodeHandler handler = type.getCustomHandler();
3045                 trans = handler.getListTransition((ControllerRequest) request);
3046                 checkEmbedded(request, trans);
3047             }
3048 
3049             trans.redirectTransition(request, response);
3050         } catch (Exception dbe) {
3051             throw new ControllerException(dbe);
3052         }
3053     }
3054 
3055     /***
3056      * Edit permission groups for this node.
3057      *
3058      * @param request  The ExpressoRequest object.
3059      * @param response The ExpressoResponse object.
3060      * @throws ControllerException upon error.
3061      */
3062     protected void runAddGroupState(final ExpressoRequest request,
3063                                     final ExpressoResponse response) throws ControllerException {
3064         String nodeId = request.getParameter(Node.NODE_ID);
3065 
3066         if (nodeId == null) {
3067             throw new ControllerException("nodeId is a required parameter");
3068         }
3069 
3070         String group = request.getParameter(UserGroup.GROUP_NAME_FIELD);
3071 
3072         if (group == null) {
3073             throw new ControllerException("group name is a required parameter");
3074         }
3075 
3076         try {
3077             Node origNode = new Node(request, nodeId);
3078             origNode.retrieve();
3079 
3080             origNode.addGroupPerm(group, RowPermissions.DEFAULT_PERMISSIONS);
3081 
3082             Transition trans = NodeAction.getListTransition(origNode
3083                     .getNodeType());
3084 
3085             NodeType type = origNode.getEntity();
3086 
3087             if (type.hasCustomHandler()) {
3088                 INodeHandler handler = type.getCustomHandler();
3089                 trans = handler.getListTransition((ControllerRequest) request);
3090             }
3091 
3092             assert trans != null;
3093 
3094             checkEmbedded(request, trans);
3095 
3096             trans.redirectTransition(request, response);
3097         } catch (Exception dbe) {
3098             throw new ControllerException(dbe);
3099         }
3100     }
3101 
3102     /***
3103      * List of lists.
3104      *
3105      * @param request  The ExpressoRequest object.
3106      * @param response The ExpressoResponse object.
3107      * @throws ControllerException upon error.
3108      */
3109     protected void runListAllTypesState(final ExpressoRequest request,
3110                                         final ExpressoResponse response) throws ControllerException {
3111         try {
3112             Block allblock = new Block(ROW_BLOCK);
3113             response.add(allblock);
3114 
3115             User requestor = request.getUserInfo();
3116             boolean canEditModel = requestor.isAdmin()
3117                     || requestor
3118                     .isMember(Setup
3119                             .getValueRequired(PartAction.MODEL_EDIT_GROUP_DEFAULT_SETUP_CODE));
3120 
3121             TreeSet types = PartsFactory.getEntities();
3122 
3123             for (Iterator iterator = types.iterator(); iterator.hasNext();) {
3124                 NodeType type = (NodeType) iterator.next();
3125 
3126                 Block row = new Block(ROW);
3127                 allblock.add(row);
3128 
3129                 Output output = new Output(NodeType.NODE_TYPE_ID, type.getId());
3130                 row.add(output);
3131 
3132                 output = new Output(NodeType.DISPLAY_TITLE, type
3133                         .getDisplayName());
3134                 row.add(output);
3135 
3136                 output = new Output(NodeType.NODE_TYPE_DESCRIP, type
3137                         .getEntityDescription());
3138                 row.add(output);
3139 
3140                 Transition trans = NodeAction.getListTransition(type
3141                         .getEntityName());
3142                 checkEmbedded(request, trans);
3143 
3144                 row.add(trans);
3145 
3146                 if (canEditModel) {
3147                     trans = new Transition("edit", PartAction.class,
3148                             PartAction.PROMPT_EDIT_ENTITY);
3149                     trans.addParam(NodeType.NODE_TYPE_ID, type.getId());
3150                     checkEmbedded(request, trans);
3151                     row.add(trans);
3152 
3153                     trans = new Transition("delete", PartAction.class,
3154                             PartAction.PROMPT_DELETE_ENTITY);
3155                     trans.addParam(NodeType.NODE_TYPE_ID, type.getId());
3156                     row.add(trans);
3157                     checkEmbedded(request, trans);
3158                     row.add(getPermsTrans(type));
3159                 }
3160             }
3161 
3162             if (canEditModel) {
3163                 Transition trans = new Transition("add", PartAction.class,
3164                         PartAction.PROMPT_ADD_ENTITY);
3165                 checkEmbedded(request, trans);
3166                 response.add(trans);
3167 
3168                 trans = new Transition("import from XML", PartAction.class,
3169                         PartAction.PROMPT_IMPORT_ENTITY);
3170                 checkEmbedded(request, trans);
3171                 response.add(trans);
3172             }
3173         } catch (Exception dbe) {
3174             throw new ControllerException(dbe);
3175         }
3176     }
3177 
3178     public static Transition getDeleteTrans(final String nodeId) {
3179         Transition deleteTransition = new Transition("Delete",
3180                 NodeAction.class, NodeAction.PROMPT_DELETE_NODE);
3181         deleteTransition.addParam(Node.NODE_ID, nodeId);
3182 
3183         return deleteTransition;
3184     }
3185 
3186     /***
3187      * Views a single attribute in its entirety. Useful for when somebody does
3188      * not have permission to update an attribute, but the attribute label is
3189      * truncates (such as in treeview)
3190      *
3191      * @param request  ExpressoRequest the ExpressoRequest object.
3192      * @param response ExpressoResponse the ExpressoResponse object.
3193      * @throws ControllerException upon controller framework related error.
3194      * @throws DBException         upon database access related error.
3195      */
3196     protected void runViewSingleAttributeState(final ExpressoRequest request,
3197                                                final ExpressoResponse response) throws ControllerException,
3198             DBException {
3199         String id = request.getParameter(Attribute.ATTRIBUTE_ID);
3200         Attribute attrib = new Attribute();
3201         attrib.setAttributeId(id);
3202         attrib.retrieve();
3203 
3204         Node node = attrib.getParentNode();
3205 
3206         // String nodeId = request.getParameter(Node.NODE_ID);
3207         // String attribName = request.getParameter(Attribute.ATTRIBUTE_TYPE);
3208 
3209         request.getSession().setAttribute("ReadOnlyRequest", Boolean.TRUE);
3210 
3211         try {
3212             Part part = attrib.getPart();
3213             response.add(new Output(Attribute.ATTRIBUTE_DISPLAY_NAME, part
3214                     .getPartLabel()));
3215 
3216             Transition viewTrans = AddNodeAction.getViewTrans(node.getNodeId());
3217             viewTrans.setLabel(node.getNodeTitle());
3218             response.add(viewTrans);
3219 
3220             String value = attrib.getAttribValue();
3221             if (part.hasPicklist()) {
3222                 value = attrib.getField(Attribute.ATTRIBUTE_PICKLIST_DISPLAY);
3223             }
3224             response.add(new Output(Attribute.ATTRIBUTE_VALUE, value));
3225 
3226             response.add(new Output(Attribute.ATTRIBUTE_COMMENT, attrib
3227                     .getAttribComment()));
3228         } catch (Exception e) {
3229             throw new ControllerException(e);
3230         }
3231 
3232     }
3233 
3234     /***
3235      * synonym for method in AddNodeAction
3236      *
3237      * @param request  ExpressoRequest
3238      * @param response ExpressoResponse
3239      * @throws ControllerException
3240      * @throws DBException
3241      */
3242     protected void runViewNodeState(final ExpressoRequest request,
3243                                     final ExpressoResponse response) throws ControllerException,
3244             DBException, NonHandleableException {
3245         AddNodeAction.getViewTrans(request.getParameter(Node.NODE_ID))
3246                 .executeTransition(request, response);
3247     }
3248 
3249     /***
3250      * Executes the view tree command.
3251      *
3252      * @param request  ExpressoRequest
3253      * @param response ExpressoResponse
3254      * @throws ControllerException
3255      * @throws DBException
3256      */
3257     protected void runViewTreeState(final ExpressoRequest request,
3258                                     final ExpressoResponse response) throws ControllerException,
3259             DBException, NonHandleableException {
3260 
3261         Node existing = getNode(request);
3262         StateHandler handler = new ViewNodeAsTree(existing);
3263         handler.handleRequest(request, response);
3264 
3265         Transition reflexiveTransition = new Transition("", this.getClass(),
3266                 NodeAction.VIEW_TREE);
3267         reflexiveTransition.addParam(Node.NODE_ID, request
3268                 .getParameter(Node.NODE_ID));
3269         checkEmbedded(request, reflexiveTransition);
3270         // reflexiveTransition.setParams(request.getParameters());
3271 
3272         try {
3273             Transition expandAll = (Transition) reflexiveTransition.clone();
3274             expandAll.addParam(ViewNodeAsTree.PARAM_EXPAND_ALL, "Y");
3275             expandAll.setLabel("Expand All");
3276             expandAll.setName("ExpandAll");
3277             checkEmbedded(request, expandAll);
3278             response.add(expandAll);
3279 
3280             Transition collapseAll = (Transition) reflexiveTransition.clone();
3281             collapseAll.addParam(ViewNodeAsTree.PARAM_COLLAPSE_ALL, "Y");
3282             collapseAll.setLabel("Collapse All");
3283             collapseAll.setName("CollapseAll");
3284             checkEmbedded(request, collapseAll);
3285             response.add(collapseAll);
3286 
3287             // add flat view
3288             Transition flat = AddNodeAction.getViewTrans(request
3289                     .getParameter(Node.NODE_ID));
3290             checkEmbedded(request, flat);
3291             response.add(flat);
3292         } catch (CloneNotSupportedException ex) {
3293             throw new ControllerException("Error cloning Transitions.", ex);
3294         }
3295     }
3296 
3297 }