View Javadoc

1   /* ===================================================================
2    * Copyright 2002-05 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 java.io.StringReader;
13  import java.sql.SQLException;
14  import java.text.DateFormat;
15  import java.util.ArrayList;
16  import java.util.Arrays;
17  import java.util.Collections;
18  import java.util.Iterator;
19  import java.util.List;
20  import java.util.Map;
21  import java.util.TreeSet;
22  
23  import org.apache.log4j.Logger;
24  import org.dom4j.Document;
25  import org.dom4j.Element;
26  
27  import com.jcorporate.expresso.core.controller.Block;
28  import com.jcorporate.expresso.core.controller.ControllerException;
29  import com.jcorporate.expresso.core.controller.ExpressoRequest;
30  import com.jcorporate.expresso.core.controller.ExpressoResponse;
31  import com.jcorporate.expresso.core.controller.Input;
32  import com.jcorporate.expresso.core.controller.NonHandleableException;
33  import com.jcorporate.expresso.core.controller.Output;
34  import com.jcorporate.expresso.core.controller.ServletControllerRequest;
35  import com.jcorporate.expresso.core.controller.State;
36  import com.jcorporate.expresso.core.controller.Transition;
37  import com.jcorporate.expresso.core.dataobjects.jdbc.JoinedDataObject;
38  import com.jcorporate.expresso.core.db.DBException;
39  import com.jcorporate.expresso.core.dbobj.MultiDBObject;
40  import com.jcorporate.expresso.core.misc.StringUtil;
41  import com.jcorporate.expresso.core.registry.RequestRegistry;
42  import com.jcorporate.expresso.core.security.ReadOnlyUser;
43  import com.jcorporate.expresso.core.security.SuperUser;
44  import com.jcorporate.expresso.core.security.filters.Filter;
45  import com.jcorporate.expresso.core.security.filters.RawFilter;
46  import com.jcorporate.expresso.services.dbobj.Setup;
47  import com.sri.common.taglib.InputTag;
48  import com.sri.emo.annotations.NodeTag;
49  import com.sri.emo.annotations.NodeTagController;
50  import com.sri.emo.annotations.PromptAddTag;
51  import com.sri.emo.annotations.PromptDeleteTag;
52  import com.sri.emo.dbobj.Attribute;
53  import com.sri.emo.dbobj.IndentComparator;
54  import com.sri.emo.dbobj.Node;
55  import com.sri.emo.dbobj.NodeType;
56  import com.sri.emo.dbobj.Part;
57  import com.sri.emo.dbobj.PartsFactory;
58  import com.sri.emo.dbobj.PickList;
59  import com.sri.emo.dbobj.Relation;
60  import com.sri.emo.dbobj.RelationType;
61  
62  /***
63   * Handle Node manipulation.
64   *
65   * @author larry hamel
66   */
67  public class AddNodeAction extends NodeController {
68      /***
69  	 * 
70  	 */
71  	private static final long serialVersionUID = 1L;
72  	// states
73      public static final String VIEW_NODE = "viewNode";
74      public static final String PROMPT_EXPORT_NODE = "promptExportNode";
75      public static final String DO_EXPORT_NODE = "doExportNode";
76      public static final String PROMPT_EDIT_NODE = "promptEditNode";
77      public static final String DO_EDIT_NODE = "doEditNode";
78      public static final String PROMPT_IMPORT_NODE = "promptImportNode";
79      public static final String DO_IMPORT_NODE = "doImportNode";
80      public static final String FINISH_IMPORT = "finishImportNode";
81      public static final String INC_ATTRIB_ORDER = "incAttribOrder";
82      public static final String DEC_ATTRIB_ORDER = "decAttribOrder";
83      public static final String INC_REL_ORDER = "incRelOrder";
84      public static final String DEC_REL_ORDER = "decRelOrder";
85      public static final String EDIT_GROUP_DISPLAY = "EDIT_GROUP_DISPLAY";
86      public static final String VIEW_PART = "viewPart";
87  
88      /***
89       * limit number of chars that appear in display for a given field
90       */
91      public static final int MAX_CHARS_OUTPUT = 100;
92  
93      /***
94       * checkbox name
95       */
96      public static final String PICK_LIST_ITEM = "PICK_LIST_ITEM";
97      public static final String RETURN_TO = "RETURN_TO";
98      public static final String PICKLISTS_TAGNAME = "PICKLISTS";
99      public static final String IS_CHANGE_TITLE = "IS_CHANGE_TITLE";
100     public static final String APPEND_TO_TITLE = "APPEND_TO_TITLE";
101 
102     private static Logger sLog = Logger.getLogger(AddNodeAction.class);
103     public static final String DELETE_STRAY_ATTRIBUTES = "DEL_STRAY_ATTRIBS";
104 
105     public AddNodeAction() {
106         addState(new State(VIEW_NODE, "View a node"));
107 
108         State promptState = new State(PROMPT_EDIT_NODE, "Edit Node");
109         addState(promptState);
110 
111         State handleState = new State(DO_EDIT_NODE, "Submit Node");
112         addState(handleState);
113 
114         addState(new State(PROMPT_IMPORT_NODE, "Prompt import node"));
115         addState(new State(DO_IMPORT_NODE, "Do import node"));
116         addState(new State(FINISH_IMPORT, "Finish import node"));
117         addState(new State(PROMPT_EXPORT_NODE, "promptExportNode"));
118         addState(new State(DO_EXPORT_NODE, "doExportNode"));
119 
120         addState(new State(INC_ATTRIB_ORDER, "incAttribOrder"));
121         addState(new State(DEC_ATTRIB_ORDER, "decAttribOrder"));
122 
123         addState(new State(INC_REL_ORDER, "incRelOrder"));
124         addState(new State(DEC_REL_ORDER, "decRelOrder"));
125 
126         State s = new State(VIEW_PART, "View Part");
127         s.addRequiredParameter(Node.NODE_ID);
128         s.addRequiredParameter(Part.PART_ID);
129         addState(s);
130 
131         setInitialState(VIEW_NODE);
132     }
133 
134     /***
135      * Returns the title of this controller.
136      *
137      * @return String.
138      */
139     public String getTitle() {
140         return "AddNodeAction Controller";
141     }
142 
143     /***
144      * Show view of node.  if user has rights, include edit buttons
145      *
146      * @param request  the <code>ExpressoRequest</code> object.
147      * @param response the <code>ExpressoResponse</code> object.
148      * @throws ControllerException upon error.
149      */
150     protected void runViewNodeState(final ExpressoRequest request,
151                                     final ExpressoResponse response) throws ControllerException {
152         String nodeId = request.getParameter(Node.NODE_ID);
153 
154         if (nodeId == null) {
155             throw new ControllerException("nodeId is a required parameter");
156         }
157 
158         try {
159             Node querynode = new Node(request, nodeId);
160 
161             if (!querynode.find()) {
162                 response.addError("Cannot find an item with ID=" + nodeId +
163                         ". You may have used an obsolete link, where the item"
164                         + " has been deleted.  Please navigate to and use a "
165                         + "fresh list of items.");
166 
167                 Transition trans = new Transition("", NodeAction.class,
168                         NodeAction.INDEX);
169                 if (isEmbeddedMode(request)) {
170                     addEmbeddedParameter(trans);
171                 }
172                 trans.executeTransition(request, response);
173 
174                 return;
175             }
176 
177             // for xml, short-circuit expresso path
178             if (request.getParameter("isXML") != null) {
179                 viewXML(querynode, request, response);
180 
181                 return;
182             }
183 
184             querynode.view(this, request, response);
185 
186             return;
187         } catch (Exception dbe) {
188             throw new ControllerException(dbe);
189         }
190     }
191 
192     private void viewXML(final Node querynode, final ExpressoRequest request,
193                          ExpressoResponse response) throws ControllerException {
194         response.setCustomResponse(true);
195 
196         try {
197             ModelXMLWriter writer = new ModelXMLWriter();
198             Document document = writer.renderNodeAsXml(querynode, request);
199             outputXML(document, request);
200         } catch (Exception e) {
201             throw new ControllerException(e);
202         }
203     }
204 
205     /***
206      * Prompt for preferred xml format.
207      *
208      * @param request  the <code>ExpressoRequest</code> object.
209      * @param response the <code>ExpressoResponse</code> object.
210      * @throws ControllerException upon error.
211      */
212     protected void runPromptExportNodeState(final ExpressoRequest request,
213                                             final ExpressoResponse response) throws ControllerException {
214         String nodeId = request.getParameter(Node.NODE_ID);
215 
216         if (nodeId == null) {
217             throw new ControllerException("nodeId is a required parameter");
218         }
219 
220         try {
221             Node querynode = new Node(request, nodeId);
222 
223             if (!querynode.find()) {
224                 response.addError("Cannot find an item with ID=" + nodeId +
225                         ". You may have used an obsolete link, where the item "
226                         + "has been deleted.  Please navigate to and use a " +
227                         "fresh list of items.");
228 
229                 Transition trans = new Transition("", NodeAction.class, NodeAction.INDEX);
230                 if (isEmbeddedMode(request)) {
231                     addEmbeddedParameter(trans);
232                 }
233                 trans.executeTransition(request, response);
234 
235                 return;
236             }
237 
238             Input in = new Input(IS_CHANGE_TITLE);
239             in.setType(Input.ATTRIBUTE_CHECKBOX);
240             in.setDefaultValue(IS_CHANGE_TITLE);
241             response.add(in);
242 
243             in = new Input(APPEND_TO_TITLE);
244             in.setType(Input.ATTRIBUTE_TEXTLINE);
245             in.setDefaultValue(" copy");
246             response.add(in);
247 
248             Block refsBlock = new Block(Node.REFS_ONLY);
249             response.add(refsBlock);
250 
251             TreeSet set = PartsFactory.getEntities();
252 
253             for (Iterator iterator = set.iterator(); iterator.hasNext();) {
254                 NodeType type = (NodeType) iterator.next();
255                 in = new Input(type.getEntityName());
256                 in.setType(Input.ATTRIBUTE_CHECKBOX);
257                 in.setDefaultValue(type.getDisplayName());
258 
259                 if (type.isReferenceIdPreferred()) {
260                     in.setAttribute(Input.SELECTED, "true");
261                 }
262 
263                 refsBlock.add(in);
264             }
265 
266             Transition trans = new Transition(DO_EXPORT_NODE, this);
267             trans.setLabel("Export");
268             trans.addParam(Node.NODE_ID, nodeId);
269             if (isEmbeddedMode(request)) {
270                 addEmbeddedParameter(trans);
271             }
272             response.add(trans);
273         } catch (Exception dbe) {
274             throw new ControllerException(dbe);
275         }
276     }
277 
278     /***
279      * Prompt for preferred xml format.
280      *
281      * @param request  the <code>ExpressoRequest</code> object.
282      * @param response the <code>ExpressoResponse</code> object.
283      * @throws ControllerException upon error.
284      */
285     protected void runDoExportNodeState(ExpressoRequest request,
286                                         ExpressoResponse response) throws ControllerException {
287         String nodeId = request.getParameter(Node.NODE_ID);
288 
289         if (nodeId == null) {
290             throw new ControllerException("nodeId is a required parameter");
291         }
292 
293         try {
294             Node querynode = new Node(request, nodeId);
295 
296             if (!querynode.find()) {
297                 response.addError("Cannot find an item with ID=" + nodeId
298                         + ". You may have used an obsolete link, where the "
299                         + "item has been deleted.  Please navigate to and use "
300                         + "a fresh list of items.");
301 
302                 Transition trans = new Transition("", NodeAction.class, NodeAction.INDEX);
303                 if (isEmbeddedMode(request)) {
304                     addEmbeddedParameter(trans);
305                 }
306                 trans.executeTransition(request, response);
307 
308                 return;
309             }
310 
311             viewXML(querynode, request, response);
312 
313             return;
314         } catch (Exception dbe) {
315             throw new ControllerException(dbe);
316         }
317     }
318 
319 
320     /***
321      * Views a given node.  In otherwords, creates the data and edit
322      * links in a table format.
323      *
324      * @param querynode Node the node we're querying to form the data.
325      * @param request   ExpressoRequest the ExpressoRequest object.
326      * @param response  ExpressoResponse The ExpressoResponse object.
327      * @throws DBException         upon error querying/operating on the node.
328      * @throws ControllerException upon error populating the ExpressoResponse
329      *                             object.
330      */
331     public void view(final Node querynode, final ExpressoRequest request,
332                      final ExpressoResponse response) throws DBException, ControllerException {
333         boolean canEdit = querynode.canRequesterWrite();
334 
335         String type = querynode.getNodeType();
336         String titleStr = querynode.getNodeTitle();
337 
338         Output title = new Output(Node.NODE_TITLE, StringUtil.truncate(titleStr, MAX_CHARS_OUTPUT));
339         response.add(title);
340 
341         response.add(new Output(Node.NODE_ID, querynode.getNodeId()));
342 
343         Output message = new Output(Node.NODE_ANNOTATION,
344                 str(querynode.getNodeAnnotation()));
345         response.add(message);
346 
347         Output comment = new Output(Node.NODE_COMMENT,
348                 str(querynode.getNodeComment()));
349         response.add(comment);
350 
351         // an edit link for the above node fields
352         if (canEdit) {
353             addEditLink(querynode.getNodeId(), response);
354         }
355 
356         String nodeTypeTitle = request.getParameter(NodeType.DISPLAY_TITLE);
357 
358         if (nodeTypeTitle == null) {
359             nodeTypeTitle = NodeType.getDisplayName(type);
360         }
361 
362         response.add(new Output(NodeType.DISPLAY_TITLE, nodeTypeTitle));
363 
364         // parts, which are either attributes or (shared) node references
365         //
366         addParts(querynode, request, canEdit, response);
367 
368         if (querynode.canRequesterAdd()) {
369             // add duplication link if user has ANY write ability
370             addCloneLink(querynode.getNodeId(), response);
371         }
372 
373         if (querynode.canRequesterWrite()) {
374             addDeleteLink(querynode.getNodeId(), response);
375         }
376 
377         // add list of items which contain me
378         addContainedByLinks(querynode.getNodeId(), response);
379         addViewXMLLink(querynode.getNodeId(), response);
380         addTreeViewLink(querynode.getNodeId(), response);
381 
382         addTags(querynode, request, response);
383     }
384 
385     /***
386      * Grabs all node tags and drops them into a request attribute called 'NodeTags'
387      * that the JSP can modify.
388      *
389      * @param queryNode Node the current node we're viewing.
390      * @param request   ExpressoRequest the request
391      * @param response  ExpressoResposne the response
392      * @throws DBException         upon database error.
393      * @throws ControllerException upon adding controller element error.
394      */
395     public static void addTags(final Node queryNode, final ExpressoRequest request, final ExpressoResponse response) throws DBException, ControllerException {
396 
397         Transition addTag = new Transition("addTag", "add tag", NodeTagController.class, PromptAddTag.NAME);
398         addTag.addParam("nodeId", queryNode.getNodeId());
399         response.add(addTag);
400 
401 
402         Block tags = new Block("NodeTags");
403 
404 
405         List allTags = queryNode.getTags();
406 
407         //List all nodes.
408         DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, request.getLocale());
409 
410         ReadOnlyUser requestingUser = request.getRequestingUser();
411 
412         for (Iterator i = allTags.iterator(); i.hasNext();) {
413             NodeTag eachTag = (NodeTag) i.next();
414             Block eachRow = new Block("Tag_" + eachTag.getTagId().toString());
415             eachRow.add(new Output("dateAdded", df.format(eachTag.getAddedOn())));
416             eachRow.add(new Output("tagValue", eachTag.getTagValue()));
417             eachRow.add(new Output("addedBy", eachTag.getAddedBy()));
418 
419             boolean canDelete = RequestRegistry.getUser().isAdmin() ||
420                     eachTag.getAddedBy().equals(requestingUser.getLoginName());
421             if (canDelete) {
422                 Transition edit = new Transition("[delete]",
423                         NodeTagController.class, PromptDeleteTag.NAME);
424                 edit.addParam(PromptDeleteTag.TAG_ID, eachTag.getTagId().toString());
425                 eachRow.add(edit);
426             }
427             tags.add(eachRow);
428         }
429 
430         response.add(tags);
431 
432     }
433 
434 
435     private void addTreeViewLink(final String nodeId, final ExpressoResponse response) throws ControllerException {
436         if (!isEmbeddedMode(response.getExpressoRequest())) {
437             Transition trans = new Transition("", NodeAction.class, NodeAction.VIEW_TREE);
438             trans.addParam(Node.NODE_ID, nodeId);
439             response.add(trans);
440         }
441     }
442 
443     private void addParts(final Node node,
444                           final ExpressoRequest request,
445                           final boolean canEdit,
446                           ExpressoResponse response) throws DBException, ControllerException {
447 
448         Part[] parts = PartsFactory.getParts(node.getNodeType());
449         List allAttribs = node.getAttributes();
450         List allRelations = node.getRawRelated();
451         Block partList = new Block("allparts");
452 
453         // ignore the reflexive relations formed when src node is part of
454         // another node
455         // todo use the iAmAPartOf for appending a fake Part to end of tree, just like Flat view does
456 //        List iAmAPartOf = filterIamPartOfMultiRelations(allRelations);
457 
458         for (int i = 0; (parts != null) && (i < parts.length); i++) {
459             Part part = parts[i];
460             ArrayList attribsForThisPart = new ArrayList();
461             ArrayList relatedMultiesForThisPart = new ArrayList();
462 
463             if (part.isOwnedAttribute()) {
464                 filterAttribs(allAttribs, part, attribsForThisPart);
465             } else {
466                 filterMultiRelations(allRelations, part, relatedMultiesForThisPart);
467             }
468 
469             partList.add(getBlock(node,
470                     request,
471                     part,
472                     canEdit,
473                     attribsForThisPart,
474                     relatedMultiesForThisPart));
475 
476             // remove the 'used' items from pool, to hasten next filtering, and provide 'cleanup' below
477             allAttribs.removeAll(attribsForThisPart);
478             allRelations.removeAll(relatedMultiesForThisPart);
479         }
480 
481         // all relations and attribs should have been removed by now; any remaining are 'strays'
482         deleteMultiRelations(allRelations, node);
483         deleteAttributes(allAttribs, node);
484 
485         response.add(partList);
486     }
487 
488     /***
489      * remove relations of type "I am a part of" because these
490      * are reflexive relations, not intended as true parts
491      */
492 //    private List filterIamPartOfMultiRelations(List allRelations) throws DBException {
493 //        ArrayList iAmPartOf = new ArrayList();
494 //        for (Iterator iterator = allRelations.iterator(); iterator.hasNext();) {
495 //            MultiDBObject multiDBObject = (MultiDBObject) iterator.next();
496 //            String relation = multiDBObject.getField(Node.RELATION_JOIN, Relation.RELATION_TYPE);
497 //            if (RelationType.DEST_CONTAINS_SRC.equals(relation)) {
498 //                iAmPartOf.add(multiDBObject);
499 //            }
500 //        }
501 //
502 //        allRelations.removeAll(iAmPartOf);
503 //
504 //        return iAmPartOf;
505 //    }
506 
507     /***
508      * remove relations of type "I am a part of" because these
509      * are reflexive relations, not intended as true parts
510      */
511     public static List filterIamPartOfJoinedRelations(List allRelations) throws DBException {
512         ArrayList iAmPartOf = new ArrayList();
513         for (Iterator iterator = allRelations.iterator(); iterator.hasNext();) {
514             JoinedDataObject oneJoinedRecord = (JoinedDataObject) iterator.next();
515             String relation = oneJoinedRecord.getDataField("Relation." + Relation.RELATION_TYPE).asString();
516             if (RelationType.DEST_CONTAINS_SRC.equals(relation)) {
517                 iAmPartOf.add(oneJoinedRecord);
518             }
519         }
520 
521         allRelations.removeAll(iAmPartOf);
522 
523         return iAmPartOf;
524     }
525 
526     private void filterMultiRelations(List allRelations, Part part, List relatedMultiesForThisPart) throws DBException {
527         for (Iterator iterator = allRelations.iterator(); iterator.hasNext();) {
528             MultiDBObject multiDBObject = (MultiDBObject) iterator.next();
529             String nodetype = multiDBObject.getField(Node.NODE_JOIN, Node.NODE_TYPE);
530             String relation = multiDBObject.getField(Node.RELATION_JOIN, Relation.RELATION_TYPE);
531             if (part.getNodeRelation().equals(relation)
532                     && part.getPartType().equals(nodetype)
533                     ) {
534                 relatedMultiesForThisPart.add(multiDBObject);
535             }
536         }
537     }
538 
539     public static void filterJoinedDataObjectRelations(List allRelations,
540                                                        Part part,
541                                                        List relatedDataObjectsForThisPart) throws DBException {
542         int relationsSize = allRelations.size();
543         for (int i = 0; i < relationsSize; i++) {
544             JoinedDataObject oneJoinedRecord = (JoinedDataObject) allRelations.get(i);
545             String nodetype = oneJoinedRecord.getDataField("Node." + Node.NODE_TYPE).asString();
546             String relation = oneJoinedRecord.getDataField("Relation." + Relation.RELATION_TYPE).asString();
547             if (part.getNodeRelation().equals(relation)
548                     && part.getPartType().equals(nodetype)
549                     ) {
550                 relatedDataObjectsForThisPart.add(oneJoinedRecord);
551             }
552         }
553 
554     }
555 
556 
557     /***
558      * iterate through list of all attributes and filter those that belong to the specified Part
559      * into the output list
560      *
561      * @param allAttribs         input list of Attribute
562      * @param part               membership criteria (the part type)
563      * @param attribsForThisPart output list
564      */
565     public static void filterAttribs(List allAttribs, Part part, ArrayList attribsForThisPart) throws DBException {
566         for (Iterator iterator = allAttribs.iterator(); iterator.hasNext();) {
567             Attribute anAttrib = (Attribute) iterator.next();
568             if (part.getPartType().equals(anAttrib.getAttribType())) {
569                 attribsForThisPart.add(anAttrib);
570             }
571         }
572     }
573 
574     /***
575      * @param attribsLeftOver list of Attribute which should be deleted
576      */
577     public static void deleteAttributes(List attribsLeftOver, Node srcNode) throws DBException {
578         for (Iterator iterator = attribsLeftOver.iterator(); iterator.hasNext();) {
579             Attribute attrib = (Attribute) iterator.next();
580             String msg = "Attribute type is not found in any part of parent node for attribute: "
581                     + attrib.forKey() + " which has parentId/parent-title/type: "
582                     + attrib.getParentNodeId() + "/" + srcNode.getNodeTitle() + "/" + attrib.getAttribType();
583             if (StringUtil.toBoolean(Setup.getValueUnrequired(DELETE_STRAY_ATTRIBUTES))) {
584                 sLog.warn(msg + " so it is being deleted.");
585                 attrib.setRequestingUser(SuperUser.INSTANCE);
586                 try {
587                     attrib.delete();
588                 } catch (DBException e) {
589                     sLog.error("Cannot delete: ", e);
590                 }
591             } else {
592                 sLog.warn(msg);
593             }
594         }
595 
596     }
597 
598     /***
599      * @param relLeftOver list of MultiDBObjects which should have relation elements deleted
600      */
601     private void deleteMultiRelations(List relLeftOver, Node srcNode) throws DBException {
602         // check on any leftover relations
603         for (Iterator iterator = relLeftOver.iterator(); iterator.hasNext();) {
604             MultiDBObject aMulti = (MultiDBObject) iterator.next();
605             Relation rel = (Relation) aMulti.getDBObject(Node.RELATION_JOIN);
606 
607             // exception for relations that are reflexive--like the 'I am a part of' relation
608             // that will write a reflexive relation from this object to its parent
609             if (rel.isReflexive()) {
610                 continue;
611             }
612 
613             String msg = "Relation type is not found in any part of src node for relation: "
614                     + rel.forKey()
615                     + " for src node: " + srcNode.getNodeTitle() + "|" + srcNode.getNodeId();
616             if (StringUtil.toBoolean(Setup.getValueUnrequired(DELETE_STRAY_ATTRIBUTES))) {
617                 sLog.warn(msg + " so it is being deleted.");
618                 rel.setRequestingUser(SuperUser.INSTANCE);
619                 try {
620                     rel.delete();
621                 } catch (DBException e) {
622                     sLog.error("Cannot delete: ", e);
623                 }
624             } else {
625                 sLog.warn(msg);
626             }
627 
628         }
629     }
630 
631     /***
632      * @param relLeftOver list of JoinedDataObject which should have relation elements deleted
633      */
634     public static void deleteJoinedRelations(List relLeftOver, Node srcNode) throws DBException {
635         int relationsSize = relLeftOver.size();
636         for (int i = 0; i < relationsSize; i++) {
637             JoinedDataObject aJoin = (JoinedDataObject) relLeftOver.get(i);
638             Relation rel = (Relation) aJoin.getNestedObject("Relation");
639 
640             String msg = "Relation type is not found in any part of src node for relation: "
641                     + rel.forKey()
642                     + " for src node: " + srcNode.getNodeTitle() + "|" + srcNode.getNodeId();
643             if (StringUtil.toBoolean(Setup.getValueUnrequired(DELETE_STRAY_ATTRIBUTES))) {
644                 sLog.warn(msg + " so it is being deleted.");
645                 rel.setRequestingUser(SuperUser.INSTANCE);
646                 try {
647                     rel.delete();
648                 } catch (DBException e) {
649                     sLog.error("Cannot delete: ", e);
650                 }
651             } else {
652                 sLog.warn(msg);
653             }
654         }
655     }
656 
657     private void addViewXMLLink(final String nodeId, final ExpressoResponse response) throws ControllerException {
658         Transition trans = new Transition(PROMPT_EXPORT_NODE, this);
659         trans.addParam(Node.NODE_ID, nodeId);
660         response.add(trans);
661     }
662 
663     private void addContainedByLinks(final String nodeId,
664                                      final ExpressoResponse response) throws DBException, ControllerException {
665         Relation relation = new Relation(RequestRegistry.getUser());
666         relation.setSrcId(nodeId);
667         relation.setRelationTypeName(RelationType.DEST_CONTAINS_SRC);
668 
669         List containerRelations = relation.searchAndRetrieveList();
670 
671         if (containerRelations.size() > 0) {
672             Block block = new Block(ROW_BLOCK);
673             response.add(block);
674 
675             // first get container nodes
676             ArrayList containers = new ArrayList(containerRelations.size());
677 
678             for (Iterator iterator = containerRelations.iterator();
679                  iterator.hasNext();) {
680                 Relation rel = (Relation) iterator.next(); 
681                 containers.add(rel.getDestNode());
682             }
683 
684             // sorts by title
685             Collections.sort(containers);
686 
687             for (Iterator iter = containers.iterator(); iter.hasNext();) {
688                 Node anode = (Node) iter.next();
689                 Block row = new Block(ROW);
690                 block.add(row);
691 
692                 Transition trans = new Transition(anode.getNodeTitle(),
693                         getClass(), VIEW_NODE);
694                 trans.addParam(Node.NODE_ID, anode.getNodeId());
695                 row.add(trans);
696                 row.setName(NodeType.getDisplayName(anode.getNodeType()));
697                 row.add(new Output(Node.NODE_ID, anode.getNodeId()));
698             }
699         }
700     }
701 
702     private void addDeleteLink(final String nodeId, final ExpressoResponse response) throws ControllerException {
703         Transition deleteTransition = new Transition("Delete",
704                 NodeAction.class, NodeAction.PROMPT_DELETE_NODE);
705         deleteTransition.addParam(Node.NODE_ID, nodeId);
706         response.add(deleteTransition);
707     }
708 
709     /***
710      * Adds the clone link.  Makes no sense in tree-view of a node.  Not
711      * available in embedded mode.
712      *
713      * @param nodeId   String
714      * @param response ExpressoResponse
715      * @throws ControllerException
716      */
717     private void addCloneLink(final String nodeId, final ExpressoResponse response) throws ControllerException {
718         if (!isEmbeddedMode(response.getExpressoRequest())) {
719             Transition trans = new Transition("Duplicate", NodeAction.class,
720                     NodeAction.PROMPT_CLONE_NODE);
721             trans.addParam(Node.NODE_ID, nodeId);
722             response.add(trans);
723         }
724     }
725 
726     /***
727      * Adds the edit link for a paritcular node.
728      *
729      * @param nodeId   String
730      * @param response ExpressoResponse
731      * @throws ControllerException
732      */
733     private void addEditLink(final String nodeId, final ExpressoResponse response) throws ControllerException {
734         Transition editTrans = new Transition("", getClass(),
735                 PROMPT_EDIT_NODE);
736         editTrans.addParam(Node.NODE_ID, nodeId);
737         if (isEmbeddedMode(response.getExpressoRequest())) {
738             addEmbeddedParameter(editTrans);
739         }
740         response.add(editTrans);
741     }
742     
743     /***
744      * prompt for NEW node or updates to node info--not attributes or shared relations,
745      * but the few fields that the node owns: title, annotation, comment.
746      * <p/>
747      * can be for a new node, or existing
748      *
749      * @param request  the <code>ExpressoRequest</code> object.
750      * @param response the <code>ExpressoResponse</code> object.
751      * @throws ControllerException upon error.
752      */
753     protected void runPromptEditNodeState(final ExpressoRequest request,
754                                           final ExpressoResponse response) throws ControllerException {
755         // type is required ONLY if this is request for new node
756         // id is required ONLY if existing;
757         // so one or the other is required
758         String type = request.getParameter(Node.NODE_TYPE);
759         String nodeId = request.getParameter(Node.NODE_ID);
760 
761         if ((type == null) && (nodeId == null)) {
762             throw new ControllerException("either node ID or node type is requried.");
763         }
764 
765         // target param added if this new item should be associated with existing object
766         String targetRelation = request.getParameter(NodeAction.RELATION_TARGET);
767         String relation = request.getParameter(Relation.RELATION_TYPE);
768 
769         if (targetRelation != null) {
770             Input target = new Input(NodeAction.RELATION_TARGET);
771             target.setType(InputTag.TYPE_HIDDEN);
772             target.setDefaultValue(targetRelation);
773             response.add(target);
774 
775             Input rel = new Input(Relation.RELATION_TYPE);
776             rel.setType(InputTag.TYPE_HIDDEN);
777             rel.setDefaultValue(relation);
778             response.add(rel);
779         }
780 
781         Transition errorTrans = NodeAction.getListTransition(null);
782         if (isEmbeddedMode(request)) {
783             addEmbeddedParameter(errorTrans);
784         }
785 
786         // use existing info for fields if available
787         String titleStr = "";
788         String summaryStr = "";
789         String commentStr = "";
790         Node existing = null;
791 
792         boolean isExisting = nodeId != null;
793 
794         try {
795             existing = new Node(request);
796 
797             if (isExisting) {
798                 /***
799                  * will throw if not found;
800                  * programming error if provided nodeId is not found...
801                  * hmm, what if someone deleted node in the meanwhile?
802                  * still an edge case--we don't want to create a new one if user
803                  * thinks that they are editing old one.
804                  */
805                 existing.setField(Node.NODE_ID, nodeId);
806                 existing.retrieve();
807                 type = existing.getNodeType();
808 
809                 Input hiddenId = new Input(Node.NODE_ID);
810                 hiddenId.setType(InputTag.TYPE_HIDDEN);
811                 hiddenId.setDefaultValue(nodeId);
812                 response.add(hiddenId);
813             } else {
814                 // must specify the kind of node a priori for a new node
815                 if (!NodeType.isValidType(type)) {
816                     throw new ControllerException("invalid node type: " + type);
817                 }
818 
819                 // get any params from request
820                 if (!isValidAndPopulated(existing, new String[0], null,
821                         request, response)) {
822                     try {
823                         errorTrans.addParam(Node.NODE_TYPE, type);
824                         errorTrans.executeTransition(request, response);
825                     } catch (Exception e) {
826                         //getLogger().error(e);
827                         throw new ControllerException(e);
828                     }
829                 }
830             }
831 
832             titleStr = existing.getNodeTitle();
833 
834             // use raw field for default text value, since
835             // it does not need translation to HTML
836             Filter old = existing.setFilterClass(new RawFilter());
837             summaryStr = existing.getNodeAnnotation();
838             commentStr = existing.getNodeComment();
839 
840             // restore
841             existing.setFilterClass(old);
842 
843             Input title = new Input(Node.NODE_TITLE);
844             response.add(title);
845             title.setMaxLength(255);
846             title.setType(Input.ATTRIBUTE_TEXTLINE);
847             title.setDefaultValue(titleStr);
848             title.setDisplayLength(40);
849 
850             Input hiddenType = new Input(Node.NODE_TYPE);
851             hiddenType.setType(InputTag.TYPE_HIDDEN);
852             hiddenType.setDefaultValue(type);
853             response.add(hiddenType);
854 
855             response.add(new Output(NodeType.DISPLAY_TITLE,
856                     NodeType.getDisplayName(type)));
857 
858             Input message = getTextArea(Node.NODE_ANNOTATION, summaryStr);
859             response.add(message);
860 
861             Input comment = getTextArea(Node.NODE_COMMENT, commentStr);
862             response.add(comment);
863 
864             // Create an transition to submit the form
865             Transition submitTransition = new Transition(DO_EDIT_NODE, this);
866             if (isEmbeddedMode(request)) {
867                 addEmbeddedParameter(submitTransition);
868                 addReturnToSenderParameter(request, response, submitTransition);
869             }
870             submitTransition.setLabel("Save");
871             response.add(submitTransition);
872         } catch (DBException dbe) {
873             response.addError("error: " + dbe.getMessage());
874             getLogger().error("error: ", dbe);
875             response.setFormCache();
876 
877             try {
878                 errorTrans.executeTransition(request, response);
879 
880                 return;
881             } catch (Exception e) {
882                 throw new ControllerException(e);
883             }
884         }
885     }
886 
887     /***
888      * Write changes in attribute to DB.
889      * note that an erased attribute means that we should *delete* the item from the DB
890      *
891      * @param request the <code>ExpressoRequest</code> object.
892      * @param setNum  the set number.
893      * @throws ControllerException upon error.
894      */
895     protected void saveAttribute(final ExpressoRequest request, final int setNum)
896             throws ControllerException {
897         try {
898             String id = request.getParameter(getIdParamName(setNum)); // can be null/empty
899             String value = request.getParameter(getValueParamName(setNum));
900             String comment = request.getParameter(getCommentParamName(setNum));
901 
902             // String order = request.getParameter(getCommentParamName(setNum));
903             // for case when attrib is new, these are required
904             String nodeId = request.getParameter(Node.NODE_ID);
905             String attribType = request.getParameter(Attribute.ATTRIBUTE_TYPE);
906 
907             boolean isNew = (id == null) || (id.length() == 0) ||
908                     id.equals(Attribute.ATTRIBUTE_ID_UNKNOWN);
909             Attribute attribute = new Attribute(RequestRegistry.getUser());
910 
911             if (!isNew) {
912                 attribute.setDBName(request.getDataContext());
913                 attribute.setField(Attribute.ATTRIBUTE_ID, id);
914 
915                 boolean found = attribute.find();
916                 boolean isSame = found &&
917                         value.equals(attribute.getAttribValue()) &&
918                         comment.equals(attribute.getAttribComment());
919                 boolean shouldDelete = found && !isSame &&
920                         (value.length() == 0) && (comment.length() == 0);
921 
922                 if (isSame) {
923                     // no saving necessary
924                     return;
925                 }
926 
927                 // edit is just to remove all text, signalling deletion
928                 if (shouldDelete) {
929                     attribute.delete();
930 
931                     return;
932                 }
933 
934                 if (found) {
935                     if (value != null) {
936                         value = value.trim();
937                     }
938                     if (comment != null) {
939                         comment = comment.trim();
940                     }
941                     attribute.setField(Attribute.ATTRIBUTE_VALUE, value);
942                     attribute.setField(Attribute.ATTRIBUTE_COMMENT, comment);
943                     attribute.update();
944 
945                     return;
946                 }
947             }
948 
949             // at this point, we know we have a new attribute
950             // but do we really have something to save?
951             if ((value.length() == 0) && (comment.length() == 0)) {
952                 return; // nothing to save
953             }
954 
955             // for new attribute, attribute type & nodeId are required
956             if ((nodeId == null) || (nodeId.length() == 0) ||
957                     (attribType == null) || (attribType.length() == 0)) {
958                 throw new ControllerException("nodeId and attrib_type are required parameters");
959             }
960 
961             // this node info should be in cache since we were just editing it
962             Node node = new Node(request, nodeId);
963 
964             if (!node.find()) {
965                 throw new ControllerException("cannot find node with id: " +
966                         nodeId);
967             }
968 
969             attribute.setAttributeType(attribType);
970             attribute.setParentNodeId(nodeId);
971             attribute.setParentNodeType(node.getNodeType());
972 
973             if (value != null) {
974                 value = value.trim();
975             }
976             if (comment != null) {
977                 comment = comment.trim();
978             }
979             attribute.setAttributeValue(value);
980             attribute.setAttributeComment(comment);
981             attribute.add();
982         } catch (DBException dbe) {
983             throw new ControllerException(dbe);
984         }
985     }
986 
987     protected Block getBlock(final Node node,
988                              final ExpressoRequest request, final Part part,
989                              boolean canEdit,
990                              final List attribs, final List relatedNodes) throws DBException, ControllerException {
991         Block block = null;
992 
993         if (part.isOwnedAttribute()) {
994             block = getAttributeOutputBlock(node, request, part,
995                     canEdit, attribs);
996         } else {
997             block = getSharedNodeBlock(node, request, part,
998                     canEdit, relatedNodes);
999         }
1000 
1001         // add help URL, common to both
1002         block.add(getHelpUrl(part.getPartType()));
1003 
1004         return block;
1005     }
1006 
1007     ;
1008 
1009     /***
1010      * Return an attributes of this node, of this type.
1011      *
1012      * @param parentNodeId the id of the parent in the tree.
1013      * @param request      the <code>ExpressoRequest</code> object.
1014      * @param attribType   the attribute type.
1015      * @return ArrayList of attributes
1016      * @throws ControllerException upon error.
1017      */
1018     protected ArrayList getAttributes(final String parentNodeId,
1019                                       final ExpressoRequest request,
1020                                       final String attribType)
1021             throws ControllerException {
1022         try {
1023             Attribute attribute = new Attribute(RequestRegistry.getUser());
1024             attribute.setDBName(request.getDataContext());
1025             attribute.setField(Attribute.NODE_ID, parentNodeId);
1026             attribute.setField(Attribute.ATTRIBUTE_TYPE, attribType);
1027 
1028             return attribute.searchAndRetrieveList(Attribute.ATTRIBUTE_ORDER);
1029         } catch (DBException e) {
1030             throw new ControllerException(e);
1031         }
1032     }
1033 
1034     /***
1035      * Utility to create an output block for a given attribute part.
1036      *
1037      * @param node    the parent node
1038      * @param request the <code>ExpressoRequest</code> object.
1039      * @param part    the <code>Part</code> to get the attributes from.
1040      * @param canEdit true if you want a transition link
1041      *                to the edit controller
1042      * @param attribs A list of attributes
1043      * @return Populated Block.
1044      * @throws DBException         upon database related error.
1045      * @throws ControllerException upon Expresso controller related error.
1046      */
1047     protected Block getAttributeOutputBlock(final Node node,
1048                                             final ExpressoRequest request, final Part part,
1049                                             boolean canEdit,
1050                                             final List attribs) throws DBException,
1051             ControllerException {
1052         if (!part.isOwnedAttribute()) {
1053             throw new DBException("cannot handle shared node here");
1054         }
1055 
1056         Block typeBlock = new Block("part" + part.getPartNum());
1057         typeBlock.add(new Output(Part.PART_DISPLAY_NAME, part.getPartLabel()));
1058 
1059         /***
1060          * add ATTRIBUTE_TYPE item to mark this block as an attribute
1061          */
1062         typeBlock.add(new Output(Attribute.ATTRIBUTE_TYPE, part.getPartType()));
1063         typeBlock.add(new Output(Part.PART_HELP_STRING, part.getPartType()));
1064 
1065 
1066         Attribute repAttrib = NodeAction.getRepAttribute(attribs, node, part.getPartType());
1067 
1068         if (repAttrib.hasCustomHandler()) {
1069             Transition trans = repAttrib.getViewTrans(request.getAllParameters());
1070             if (trans != null) {
1071                 Block rowBlock = new Block(ROW_BLOCK);
1072                 typeBlock.add(rowBlock);
1073                 rowBlock.add(trans);
1074                 rowBlock.add(repAttrib.getViewComment(request.getAllParameters()));
1075                 if (!repAttrib.getAttribCreated().equals(repAttrib.getAttribModified())) {
1076                     rowBlock.add(new Output(Attribute.ATTRIBUTE_MODIFIED, repAttrib.getAttribModified().substring(0, 10)));
1077                 }
1078             }
1079         } else {
1080             int numItems = 0;
1081             numItems = attribs.size();
1082 
1083             //
1084             // loop through all attributes found, creating block
1085             ///
1086             if (numItems > 0) {
1087                 int i = 0;
1088                 for (Iterator iterator = attribs.iterator(); iterator.hasNext();) {
1089                     Attribute attribute = (Attribute) iterator.next();
1090 
1091                     // only allow one item if singlevalued
1092                     if (part.isSingleValued() && i >= 1) {
1093                         break;
1094                     }
1095 
1096                     // Create a Block for each row tuple
1097                     Block rowBlock = new Block(ROW_BLOCK);
1098                     typeBlock.add(rowBlock);
1099 
1100                     try {
1101                         String value = attribute.getAttribValue();
1102 
1103                         if (part.hasPicklist()) {
1104                             // we must translate attribute value
1105                             //from id to display
1106                             value = attribute.getField(Attribute.ATTRIBUTE_PICKLIST_DISPLAY);
1107                         }
1108 
1109                         rowBlock.add(new Output(Attribute.ATTRIBUTE_VALUE,
1110                                 str(value)));
1111                         rowBlock.add(new Output(Attribute.ATTRIBUTE_COMMENT,
1112                                 str(attribute.getAttribComment())));
1113 
1114                         Transition trans = new Transition(NodeAction.
1115                                 PROMPT_EDIT_ATTRIB +
1116                                 attribute.getAttribId(), "", NodeAction.class,
1117                                 NodeAction.PROMPT_EDIT_ATTRIB);
1118                         trans.addParam(Attribute.ATTRIBUTE_ID,
1119                                 attribute.getAttribId());
1120                         rowBlock.add(trans);
1121                     } catch (DBException dbe) {
1122                         throw new ControllerException(dbe);
1123                     }
1124 
1125                     i++;
1126                 } // for
1127             }
1128         }
1129 
1130         if (canEdit) {
1131             Transition trans = part.getEditTrans(node.getNodeId(), request.getAllParameters());
1132             if (isEmbeddedMode(request)) {
1133                 addEmbeddedParameter(trans);
1134             }
1135 
1136             typeBlock.add(trans);
1137         }
1138 
1139         return typeBlock;
1140     }
1141 
1142     private Transition getHelpUrl(final String partType) {
1143         Transition trans = new Transition("", PartAction.class, PartAction.GENERATE_DOCS);
1144         trans.setAttribute("PART_HELP_STRING", partType);
1145         return trans;
1146     }
1147 
1148     /***
1149      * utility to create an input block for a given attribute part.
1150      * <p/>
1151      * this method is complicated by fact that we must associate sets of items
1152      * together as inputs;
1153      * we'll get back just a pile of parameters from the HTML page POST,
1154      * so we need a way to prefix param names such that
1155      * they can be grouped together later.
1156      *
1157      * @param nodeId      the id of the node.
1158      * @param srcNodeType source the node type.
1159      * @param request     The ExpressoRequest object
1160      * @param attribName  the name of the attribute to populate
1161      * @return a Block of Inputs for the attributes
1162      * @throws ControllerException upon any sort of error.
1163      */
1164     protected Block getAttributeInputBlock(final String nodeId,
1165                                            final String srcNodeType,
1166                                            final ExpressoRequest request,
1167                                            final String attribName) throws ControllerException {
1168         Block typeBlock = new Block("attributes");
1169         int itemNum = 0;
1170         ArrayList list = getAttributes(nodeId, request, attribName);
1171         Part part = null;
1172         boolean isMultiple = false;
1173 
1174         try {
1175             part = PartsFactory.getAttribPart(srcNodeType, attribName);
1176             isMultiple = part.areMultipleAttributesAllowed();
1177 
1178             if (part.hasPicklist()) {
1179                 Block rowBlock = new Block(ROW_BLOCK);
1180                 typeBlock.add(rowBlock);
1181 
1182                 String selected = "";
1183                 String comment = "";
1184 
1185                 if (list.size() > 0) {
1186                     // determine selected value, if any, as well as comment
1187                     Attribute attribute = (Attribute) list.get(0);
1188                     selected = attribute.getAttribValue();
1189 
1190                     if (selected == null) {
1191                         selected = "";
1192                     }
1193 
1194                     comment = attribute.getAttribComment();
1195 
1196                     if (comment == null) {
1197                         comment = "";
1198                     }
1199 
1200                     // add id for this existing attribute
1201                     Input hiddenId = new Input(getIdParamName(itemNum));
1202                     rowBlock.add(hiddenId);
1203                     hiddenId.setType(InputTag.TYPE_HIDDEN);
1204                     hiddenId.setDefaultValue(attribute.getAttribId());
1205                 }
1206 
1207                 // construct dropdown/picklist menu
1208                 addPicklistDropDown(nodeId, part, request, rowBlock, selected);
1209 
1210                 Input commentInput = getTextArea(getCommentParamName(itemNum),
1211                         comment,
1212                         part.numDisplayLines(),
1213                         TEXTAREA_NUM_COLS);
1214                 rowBlock.add(commentInput);
1215             } else {
1216                 // no picklist, use text fields for entry
1217                 //
1218                 // loop through all existing attributes with this
1219                 // name/parentNode,
1220                 // creating block for each
1221                 ///
1222                 for (Iterator iter = list.iterator(); iter.hasNext();) {
1223                     Attribute attribute = (Attribute) iter.next();
1224                     String attribId = attribute.getAttribId();
1225 
1226                     // Create a Block for each row tuple
1227                     Block rowBlock = new Block(ROW_BLOCK);
1228                     typeBlock.add(rowBlock);
1229 
1230                     Input valueInput = new Input(this
1231                             .getValueParamName(itemNum));
1232                     rowBlock.add(valueInput);
1233 
1234                     if (!part.hasPicklist()) {
1235                         valueInput.setMaxLength(4000);
1236                         valueInput.setType(InputTag.TYPE_TEXTAREA);
1237                         valueInput.setLines(part.numDisplayLines());
1238                         valueInput.setDisplayLength(TEXTAREA_NUM_COLS);
1239                         valueInput.setDefaultValue(attribute.getAttribValue());
1240                     }
1241 
1242                     Input commentInput = getTextArea(getCommentParamName(itemNum),
1243                             attribute.getAttribComment(),
1244                             part.numDisplayLines(),
1245                             TEXTAREA_NUM_COLS);
1246                     rowBlock.add(commentInput);
1247 
1248                     // add id for this existing attribute
1249                     Input hiddenId = new Input(getIdParamName(itemNum));
1250                     rowBlock.add(hiddenId);
1251                     hiddenId.setType(InputTag.TYPE_HIDDEN);
1252                     hiddenId.setDefaultValue(attribId);
1253 
1254                     itemNum++;
1255                 } // for
1256 
1257                 // Add spaces for new attributes.
1258                 // The new attributes are designated
1259                 // without an attribId, which distinguishes
1260                 // them from the existing IDs above
1261                 int num_empty_attribs = 5;
1262 
1263                 if (!isMultiple) {
1264                     if (itemNum == 0) {
1265                         num_empty_attribs = 1;
1266                     } else {
1267                         num_empty_attribs = 0;
1268                     }
1269                 }
1270 
1271                 for (int i = itemNum; i < (itemNum + num_empty_attribs); i++) {
1272                     // Create a Block for each row tuple
1273                     Block rowBlock = new Block(ROW_BLOCK);
1274                     typeBlock.add(rowBlock);
1275 
1276                     Input valueInput = getTextArea(getValueParamName(i),
1277                             null, part.numDisplayLines(),
1278                             TEXTAREA_NUM_COLS);
1279                     rowBlock.add(valueInput);
1280 
1281                     Input commentInput = getTextArea(getCommentParamName(i),
1282                             null, part.numDisplayLines(),
1283                             TEXTAREA_NUM_COLS);
1284                     rowBlock.add(commentInput);
1285                 } // for
1286             }
1287         } catch (Exception dbe) {
1288             throw new ControllerException(dbe);
1289         }
1290 
1291         return typeBlock;
1292     }
1293 
1294     /***
1295      * Utility to create an output block for a given shared node part.
1296      *
1297      * @param node         the source node
1298      * @param request      ExpressoRequest The <code>ExpressoRequest</code>
1299      *                     object
1300      * @param part         Part the part we're querying.
1301      * @param canEdit      boolean true if the user can edit the node
1302      * @param relatedNodes list of multidbobjects from node.getRawRelated() of related node info.
1303      * @return Block Bloc populated with the appropriate inputs/outputs, etc
1304      * @throws DBException         upon database access error
1305      * @throws ControllerException upon Controller related error.
1306      */
1307     protected Block getSharedNodeBlock(final Node node,
1308                                        final ExpressoRequest request, final Part part,
1309                                        final boolean canEdit,
1310                                        final List relatedNodes) throws DBException, ControllerException {
1311         if (part.isOwnedAttribute()) {
1312             throw new DBException("cannot handle attribute node here");
1313         }
1314         String relTarget = request.getParameter(NodeAction.RELATION_TARGET);
1315 
1316         Block typeBlock = new Block("part" + part.getPartNum());
1317         typeBlock.add(new Output(Part.PART_DISPLAY_NAME, part.getPartLabel()));
1318 
1319         // Relation.NODE_PART_RELATION_TYPE is signal to
1320         // JSP that this is shared block
1321         typeBlock.add(new Output(Relation.RELATION_TYPE, part.getNodeRelation()));
1322 
1323         // output for help string identifier (anchor name)
1324         // if type is same, output relation; if type is different, output part type
1325         if (node.getNodeType().equals(part.getPartType())) {
1326             typeBlock.add(new Output(Part.PART_HELP_STRING, part.getNodeRelation()));
1327         } else {
1328             typeBlock.add(new Output(Part.PART_HELP_STRING, part.getPartType()));
1329         }
1330 
1331         try {
1332 
1333             //
1334             // loop through all nodes found, creating block
1335             //
1336             for (Iterator iterator = relatedNodes.iterator(); iterator.hasNext();) {
1337                 MultiDBObject multiDBObject = (MultiDBObject) iterator.next();
1338                 Node relatedNode = (Node) multiDBObject.getDBObject(Node.NODE_JOIN);
1339 
1340                 // Create a Block for each row tuple
1341                 Block rowBlock = new Block(ROW_BLOCK);
1342                 typeBlock.add(rowBlock);
1343 
1344                 try {
1345                     //rowBlock.add(new Output(Node.NODE_TITLE, relatedNode.getNodeTitle()));
1346                     rowBlock.add(new Output(Node.NODE_ANNOTATION,
1347                             str(StringUtil.truncate(relatedNode.getNodeAnnotation(),
1348                                     MAX_CHARS_OUTPUT))));
1349                     rowBlock.add(new Output(Relation.RELATION_ANNOTATION,
1350                             str(StringUtil.truncate("", MAX_CHARS_OUTPUT))));
1351                     rowBlock.add(new Output(Node.NODE_COMMENT,
1352                             str(StringUtil.truncate(relatedNode.getNodeComment(),
1353                                     MAX_CHARS_OUTPUT))));
1354 
1355                     if (relatedNode.getNodeId().equals(relTarget)) {
1356                         rowBlock.add(new Output(NodeAction.RELATION_TARGET, "1"));
1357                     }
1358                     Transition trans = AddNodeAction.getViewTrans(relatedNode.
1359                             getNodeId());
1360                     trans.setLabel(relatedNode.getNodeTitle());
1361                     rowBlock.add(trans);
1362                 } catch (DBException dbe) {
1363                     throw new ControllerException(dbe);
1364                 }
1365             }
1366         } catch (DBException e) {
1367             throw new ControllerException(e);
1368         }
1369 
1370         if (canEdit) {
1371             Transition associateTrans = part.getEditTrans(node.getNodeId(), request.getAllParameters());
1372             associateTrans.setLabel("Edit");
1373             if (isEmbeddedMode(request)) {
1374                 addEmbeddedParameter(associateTrans);
1375             }
1376             typeBlock.add(associateTrans);
1377         }
1378 
1379         return typeBlock;
1380     }
1381 
1382     /***
1383      * Add number suffix from to Attribute.ATTRIBUTE_COMMENT_PREFIX.
1384      *
1385      * @param setNum The number suffix.
1386      * @return java.lang.String
1387      */
1388     private String getCommentParamName(final int setNum) {
1389         return Attribute.ATTRIBUTE_COMMENT_PREFIX + setNum;
1390     }
1391 
1392     /***
1393      * Add number suffix from to Attribute.ATTRIBUTE_ID_PREFIX.
1394      *
1395      * @param setNum the number suffix.
1396      * @return java.lang.String
1397      */
1398     private String getIdParamName(final int setNum) {
1399         return Attribute.ATTRIBUTE_ID_PREFIX + setNum;
1400     }
1401 
1402     /***
1403      * Add number suffix from to Attribute.ATTRIBUTE_VALUE_PREFIX.
1404      *
1405      * @param setNum the number suffix.
1406      * @return java.lang.String
1407      */
1408     private String getValueParamName(final int setNum) {
1409         return Attribute.ATTRIBUTE_VALUE_PREFIX + setNum;
1410     }
1411 
1412     private String getValueParamName(final String setNum) {
1413     	//Unused?
1414         return Attribute.ATTRIBUTE_VALUE_PREFIX + setNum;
1415     }
1416 
1417     /***
1418      * handle updates to node info--not attributes or shared relations,
1419      * but the few fields that the node owns: title, annotation, comment
1420      *
1421      * @param request  The ServletExpressoRequest object.
1422      * @param response The ExpressoResponse object.
1423      * @throws ControllerException upon error.
1424      */
1425     protected void runDoEditNodeState(final ExpressoRequest request,
1426                                       final ExpressoResponse response) throws ControllerException {
1427         try {
1428             /***
1429              * @todo auto-inc fields are not handled by isValidAndPopulated :-(
1430              */
1431             String nodeId = request.getParameter(Node.NODE_ID);
1432             boolean isExisting = nodeId != null;
1433             Node existing = null;
1434             String oldtitle = "";
1435 
1436             if (isExisting) {
1437                 existing = new Node(request, nodeId);
1438 
1439                 /***
1440                  * will throw if not found;
1441                  * programming error if provided nodeId is not found...
1442                  * hmm, what if someone deleted node in the meanwhile?
1443                  * still an edge case--we don't want to create a new one if user
1444                  * thinks that they are editing old one.
1445                  */
1446                 try {
1447                     existing.retrieve();
1448                     oldtitle = existing.getNodeTitleRaw();
1449                 } catch (DBException dbe) {
1450                     response.addError("error: " + dbe.getMessage());
1451                     getLogger().error("error: ", dbe);
1452                     response.setFormCache();
1453                     transition(PROMPT_EDIT_NODE, request, response);
1454 
1455                     return;
1456                 }
1457             }
1458 
1459             Node uploaded = new Node(RequestRegistry.getUser());
1460             String[] required = {Node.NODE_TYPE, Node.NODE_TITLE};
1461 
1462             if (!isValidAndPopulated(uploaded, required, PROMPT_EDIT_NODE,
1463                     request, response)) {
1464                 return; // method handles redirect back to failure state
1465                 //which will report errors
1466             }
1467 
1468             String title = uploaded.getNodeTitle();
1469 
1470             // handle case where NAME is already taken
1471             if (!(isExisting && title.equals(oldtitle))) {
1472                 // is new name already taken
1473                 Node checkName = new Node(RequestRegistry.getUser());
1474                 checkName.setNodeTitle(title);
1475 
1476                 if (checkName.find()) {
1477                     response.addError("Title is already in use; please choose another.");
1478                     response.setFormCache();
1479                     transition(PROMPT_EDIT_NODE, request, response);
1480 
1481                     return;
1482                 }
1483             }
1484 
1485             String targetId = request.getParameter(NodeAction.RELATION_TARGET);
1486             if (!isExisting) {
1487                 // save new node and we're finished
1488                 uploaded.setDBName(RequestRegistry.getDataContext());
1489                 uploaded.setNodeOwner(RequestRegistry.getUser().getLoginName());
1490 
1491                 try {
1492                     uploaded.add();
1493                     nodeId = uploaded.getNodeId();
1494 
1495                     // handle case where we are creating new node
1496                     // that should be related to some target automatically
1497                     if (targetId != null) {
1498                         Node target = new Node(targetId);
1499                         target.retrieve();
1500                         String relationType = request.getParameter(Relation.RELATION_TYPE);
1501 
1502                         Relation relation = new Relation(RequestRegistry.getUser());
1503                         relation.setField(Relation.RELATION_SRC, targetId);
1504                         relation.setField(Relation.RELATION_DEST, nodeId);
1505                         relation.setField(Relation.RELATION_TYPE, relationType);
1506                         relation.add();
1507                     }
1508                 } catch (DBException dbe) {
1509                     response.addError("error: " + dbe.getMessage());
1510                     getLogger().error("error: ", dbe);
1511                     response.setFormCache();
1512                     transition(PROMPT_EDIT_NODE, request, response);
1513 
1514                     return;
1515                 }
1516             } else {
1517                 // existing node
1518                 boolean isChanged = false; // we'll test this assumption below
1519 
1520                 String annotation = uploaded.getNodeAnnotation();
1521                 String comment = uploaded.getNodeComment();
1522 
1523                 // did we change the info in the node?
1524                 if (oldtitle.equals(title) &&
1525                         existing.getNodeAnnotation().equals(annotation) &&
1526                         existing.getNodeComment().equals(comment)) {
1527                     isChanged = false;
1528                 } else {
1529                     isChanged = true;
1530                     existing.setNodeTitle(title);
1531                     existing.setNodeAnnotation(annotation);
1532                     existing.setNodeComment(comment);
1533                     existing.setNodeOwner(RequestRegistry.getUser().getLoginName());
1534                 }
1535 
1536                 if (isChanged) { // only write to DB if there are changes
1537                     existing.update();
1538                 }
1539             } // existing node
1540 
1541             // if there is a return-to flag, go there
1542             if (targetId != null) {
1543 
1544                 // go back to target object if we were in 'creation dialog'
1545                 Transition trans = getViewTrans(targetId);
1546                 trans.addParam(NodeAction.RELATION_TARGET, nodeId); // add id of new item, for highlighting
1547                 if (this.redirectToSender((ServletControllerRequest) request, "Changes Saved")) {
1548                     return;
1549                 }
1550                 trans.redirectTransition(request, response);
1551             } else {
1552                 // now send off to display page
1553                 // do redirect to protect against refresh/resubmit
1554                 Transition trans = new Transition(VIEW_NODE, this);
1555                 trans.addParam(Node.NODE_ID, nodeId);
1556 
1557                 if (this.redirectToSender((ServletControllerRequest) request, "Changes Saved")) {
1558                     return;
1559                 }
1560 
1561                 trans.redirectTransition(request, response);
1562             }
1563         } catch (Exception dbe) {
1564             throw new ControllerException(dbe);
1565         }
1566     }
1567 
1568     private void addPicklistDropDown(final String parent_id, final Part part,
1569                                      final ExpressoRequest request, Block row, String selected) throws DBException {
1570         // construct dropdown/picklist menu
1571         Input valueInput = new Input(getValueParamName(parent_id));
1572         valueInput.setType(InputTag.TYPE_DROPDOWN);
1573         row.add(valueInput);
1574 
1575         String[][] picklistArray = null;
1576 
1577         try {
1578             picklistArray = part.getPicklistArray(request);
1579         } catch (Exception e) {
1580             throw new DBException(e);
1581         }
1582 
1583         boolean foundSelected = false;
1584 
1585         // if selected item not
1586         //found, and if better default not found
1587         String defaultSelected = picklistArray[0][0];
1588 
1589         for (int i = 0; i < picklistArray.length; i++) {
1590             valueInput.addValidValue(picklistArray[i][0], picklistArray[i][1]);
1591 
1592             if (selected.equals(picklistArray[i][0])) {
1593                 foundSelected = true;
1594                 valueInput.setDefaultValue(selected);
1595             }
1596 
1597             // if our standard default is in list, make it defaultSelected
1598             //(only used if selected not found)
1599             if (PickList.NOT_SPECIFIED_DISPLAY.equals(picklistArray[i][0])) {
1600                 defaultSelected = PickList.NOT_SPECIFIED_DISPLAY;
1601             }
1602         }
1603 
1604         // use default if there is no previous selection
1605         if (!foundSelected) {
1606             valueInput.setDefaultValue(defaultSelected);
1607         }
1608 
1609         // add edit link if user has rights
1610         List picklist = null;
1611 
1612         try {
1613             picklist = part.getPicklist(request);
1614         } catch (Exception e) {
1615             throw new DBException(e);
1616         }
1617 
1618         // need an initial entry in picklist to indicate who can
1619         // edit this list.
1620         if ((picklist.size() > 0) &&
1621                 ((PickList) picklist.get(0)).canRequesterWrite()) {
1622             Transition promptEditPicklist = new Transition(PicklistAction.PROMPT_LIST,
1623                     "", PicklistAction.class, PicklistAction.PROMPT_LIST);
1624             promptEditPicklist.addParam(PickList.NODE_TYPE, part.getParentType());
1625             promptEditPicklist.addParam(PickList.ATTRIBUTE_TYPE, part.getPartType());
1626             promptEditPicklist.addParam(Node.NODE_ID, parent_id);
1627             if (isEmbeddedMode(request)) {
1628                 addEmbeddedParameter(promptEditPicklist);
1629             }
1630             row.add(promptEditPicklist);
1631         }
1632     }
1633 
1634     /***
1635      * Prompt import of node from xml.
1636      *
1637      * @param request  The ExpressoRequest object.
1638      * @param response The ExpressoResponse object.
1639      * @throws ControllerException upon error.
1640      */
1641     protected void runPromptImportNodeState(final ExpressoRequest request,
1642                                             final ExpressoResponse response) throws ControllerException {
1643         try {
1644             Input in = getTextArea("xml", response.getFormCache("xml"), 20, 80);
1645             response.add(in);
1646 
1647             in = new Input(Node.RELATION_JOIN);
1648             in.setType(Input.ATTRIBUTE_CHECKBOX);
1649             in.addValidValue(Node.RELATION_JOIN,
1650                     "Ignore external relation IDs (relations not satisfied"
1651                             + " within XML itself are ignored)");
1652 
1653             String def = response.getFormCache(Node.RELATION_JOIN);
1654 
1655             if (def.length() == 0) {
1656                 def = Node.RELATION_JOIN;
1657             }
1658 
1659             in.setDefaultValue(def);
1660             in.setAttribute(Input.SELECTED, "true");
1661             response.add(in);
1662 
1663             Transition doImport = new Transition("Import", getClass(),
1664                     DO_IMPORT_NODE);
1665             response.add(doImport);
1666         } catch (Exception dbe) {
1667             throw new ControllerException(dbe);
1668         }
1669     }
1670 
1671     /***
1672      * Prompt import of node from xml.
1673      * assumes that all reference IDs ('ident') are for Nodes internal
1674      * to the given tree
1675      *
1676      * @param request  The ExpressoRequest object.
1677      * @param response The ExpressoResponse object.
1678      * @throws ControllerException upon error.
1679      */
1680     protected void runDoImportNodeState(final ExpressoRequest request, final ExpressoResponse response) throws
1681             ControllerException {
1682         try {
1683             String xml = request.getParameter("xml");
1684             Element root = PartAction.parseXML(new StringReader(xml));
1685             // ok, what to do with any external reference IDs?
1686             // optional parameter tells us what to do
1687             boolean isFindAllIds = StringUtil.toBoolean(request.getParameter(Node.RELATION_JOIN));
1688             boolean isExternalRefRequired =
1689                     request.getParameter(Node.RELATION_JOIN) != null;
1690             // whether we must look for IDs missing in xml tree externally
1691             // relations
1692             ModelXMLReader reader = new ModelXMLReader();
1693 
1694             Map allNodesByXML_ID = reader.parseImportXmlAndCreateNodes(isFindAllIds, isExternalRefRequired, root,
1695                     request, "//");
1696 
1697             // output list of imported items
1698             Block imports = new Block(ROW_BLOCK);
1699             Block delblock = new Block(ROW);
1700             response.add(imports);
1701             response.add(delblock);
1702 
1703             List allnodes = new ArrayList(allNodesByXML_ID.values());
1704             Collections.sort(allnodes, new IndentComparator());
1705 
1706             for (Iterator iterator = allnodes.iterator();
1707                  iterator.hasNext();) {
1708                 Node anode = (Node) iterator.next();
1709 
1710                 // put indent string as name of block
1711                 Block row = new Block(anode.getAttribute(Node.INDENT)
1712                         .toString());
1713                 imports.add(row);
1714 
1715                 Transition trans = getViewTrans(anode.getNodeId());
1716                 trans.setLabel(anode.getNodeTitle());
1717                 row.add(trans);
1718 
1719                 // row.add(NodeAction.getDeleteTrans(anode.getNodeId()));
1720                 row.add(new Output(Node.NODE_TYPE,
1721                         anode.getEntity().getDisplayName()));
1722 
1723                 // for feature to delete all todo: make this happen
1724                 delblock.add(new Output(NodeAction.DO_DELETE_NODE,
1725                         anode.getNodeId()));
1726             }
1727 
1728             // we would normally redirect, but we have long list of added nodes to show,
1729             // and re-import should hit node-title issues right away,
1730             //preventing mistaken double-imports
1731         } catch (SQLException e) {
1732             String msg = StringUtil.notNull(e.getMessage());
1733 
1734             if ((msg.indexOf("Violation of unique index") != -1) ||
1735                     (msg.indexOf("Duplicate entry") != -1)) {
1736                 msg = "Duplicate titles are not allowed: " + msg;
1737             }
1738 
1739             response.addError("error: " + msg);
1740             getLogger().error("error: ", e);
1741             response.setFormCache();
1742 
1743             Transition prompt = new Transition(PROMPT_IMPORT_NODE, this);
1744             if (isEmbeddedMode(request)) {
1745                 addEmbeddedParameter(prompt);
1746             }
1747 
1748             prompt.executeTransition(request, response);
1749         } catch (Exception e) {
1750             response.addError("error: " + e.getMessage());
1751             getLogger().error("error: ", e);
1752             response.setFormCache();
1753 
1754             Transition prompt = new Transition(PROMPT_IMPORT_NODE, this);
1755             if (isEmbeddedMode(request)) {
1756                 addEmbeddedParameter(prompt);
1757             }
1758 
1759             try {
1760                 prompt.executeTransition(request, response);
1761             } catch (NonHandleableException e1) {
1762                 throw new ControllerException(e1);
1763             }
1764         }
1765     }
1766 
1767 
1768     public static Transition getViewTrans(final String nodeId) {
1769         Transition trans = new Transition("", AddNodeAction.class,
1770                 AddNodeAction.VIEW_NODE);
1771         trans.addParam(Node.NODE_ID, nodeId);
1772 
1773         return trans;
1774     }
1775 
1776     /***
1777      * Increment order of this attribute
1778      *
1779      * @param request  The ExpressoRequest object.
1780      * @param response The ExpressoResponse object.
1781      * @throws ControllerException upon error.
1782      * @throws DBException         upon database related error.
1783      */
1784     protected void runIncAttribOrderState(final ExpressoRequest request,
1785                                           final ExpressoResponse response) throws DBException, ControllerException {
1786         Attribute attrib = getAttrib(request);
1787         renumAttribs(attrib, true);
1788         redirectToPromptEditAttrib(attrib, request, response);
1789     }
1790 
1791     private Attribute getAttrib(final ExpressoRequest request) throws DBException {
1792         Attribute attrib = new Attribute();
1793         attrib.setAttributeId(request.getParameter(Attribute.ATTRIBUTE_ID));
1794         attrib.retrieve();
1795 
1796         return attrib;
1797     }
1798 
1799     /***
1800      * increment order of this attribute
1801      *
1802      * @param request  The ExpressoRequest object.
1803      * @param response The ExpressoResponse object.
1804      * @throws ControllerException upon error.
1805      * @throws DBException         upon database related error.
1806      */
1807     protected void runDecAttribOrderState(final ExpressoRequest request, final ExpressoResponse response) throws
1808             DBException,
1809             ControllerException {
1810         Attribute attrib = getAttrib(request);
1811         renumAttribs(attrib, false);
1812         redirectToPromptEditAttrib(attrib, request, response);
1813     }
1814 
1815     private void redirectToPromptEditAttrib(final Attribute attrib,
1816                                             final ExpressoRequest request,
1817                                             final ExpressoResponse response) throws DBException,
1818             ControllerException {
1819         Node parent = attrib.getParentNode();
1820         Transition trans = new Transition("", NodeAction.class, NodeAction.PROMPT_EDIT_ATTRIB);
1821         if (isEmbeddedMode(request)) {
1822             addEmbeddedParameter(trans);
1823         }
1824         trans.addParam(Node.NODE_ID, parent.getNodeId());
1825         trans.addParam(Attribute.ATTRIBUTE_TYPE, attrib.getAttribType());
1826         trans.redirectTransition(request, response);
1827     }
1828 
1829     /***
1830      * The meat of the renumbering.
1831      *
1832      * @param attrib the attribute to reorder.
1833      * @param isInc  true if we are incrementing the attrib order.
1834      * @throws DBException upon database related error.
1835      */
1836     private void renumAttribs(final Attribute attrib, final boolean isInc) throws DBException {
1837         /***
1838          * @todo add test for going below 0 or above N, but for now,
1839          * depend on UI to not provide for those mistakes
1840          */
1841         Attribute[] attribs = attrib.getParentNode().getAttributes(attrib.
1842                 getAttribType());
1843         int oldnum = attrib.getOrderInt();
1844 
1845         if (isInc) {
1846             attrib.setOrder(oldnum + 1);
1847             attrib.updateOrder();
1848 
1849             // move upper neighbor down if nec.
1850             // index is 0-based, orderInt is 1-based
1851             int index = (oldnum + 1) - 1; // adjust to array index by -1
1852 
1853             if (attribs[index].getOrderInt() == (oldnum + 1)) {
1854                 attribs[index].setOrder(oldnum);
1855                 attribs[index].updateOrder();
1856             }
1857         } else {
1858             attrib.setOrder(oldnum - 1);
1859             attrib.updateOrder();
1860 
1861             // move lower neighbor up if nec.
1862             int index = oldnum - 1 - 1; // adjust to array index by -1
1863 
1864             if (attribs[index].getOrderInt() == (oldnum - 1)) {
1865                 attribs[index].setOrder(oldnum);
1866                 attribs[index].updateOrder();
1867             }
1868         }
1869     }
1870 
1871     /***
1872      * Increment order of this relation.
1873      *
1874      * @param request  The ExpressoRequest object.
1875      * @param response The ExpressoResponse object.
1876      * @throws ControllerException upon error.
1877      * @throws DBException         upon database related error.
1878      */
1879     protected void runIncRelOrderState(final ExpressoRequest request, final ExpressoResponse response) throws
1880             DBException,
1881             ControllerException {
1882         Relation rel = getRelation(request);
1883         renumRelations(rel, true);
1884         redirectToPicklist(rel, request, response);
1885     }
1886 
1887     private void redirectToPicklist(final Relation rel, final ExpressoRequest request,
1888                                     ExpressoResponse response) throws DBException, ControllerException {
1889         Node srcNode = rel.getSrcNode();
1890         Node destNode = rel.getDestNode();
1891         Transition trans = new Transition("", NodeAction.class, NodeAction.PROMPT_PICKLIST_NODE);
1892         if (isEmbeddedMode(request)) {
1893             addEmbeddedParameter(trans);
1894         }
1895         trans.addParam(Node.NODE_TYPE, destNode.getNodeType());
1896         trans.addParam(Node.NODE_ID, srcNode.getNodeId());
1897         trans.addParam(Relation.RELATION_TYPE, rel.getRelationTypeName());
1898         trans.redirectTransition(request, response);
1899     }
1900 
1901     private Relation getRelation(final ExpressoRequest request) throws DBException {
1902         Relation rel = new Relation(RequestRegistry.getUser());
1903         rel.setSrcId(request.getParameter(Relation.RELATION_SRC));
1904         rel.setDestId(request.getParameter(Relation.RELATION_DEST));
1905         rel.setRelationTypeName(request.getParameter(Relation.RELATION_TYPE));
1906         rel.retrieve();
1907 
1908         return rel;
1909     }
1910 
1911     /***
1912      * Dec order of this relation.
1913      *
1914      * @param request  The ExpressoRequest object.
1915      * @param response The ExpressoResponse object.
1916      * @throws ControllerException upon error.
1917      * @throws DBException         upon database related error.
1918      */
1919     protected void runDecRelOrderState(final ExpressoRequest request, final ExpressoResponse response) throws
1920             DBException,
1921             ControllerException {
1922         Relation rel = getRelation(request);
1923         renumRelations(rel, false);
1924         redirectToPicklist(rel, request, response);
1925     }
1926 
1927     /***
1928      * The mean of the renumbering.
1929      *
1930      * @param rel   The relation we're reordering
1931      * @param isInc true if we're incrementing the order.
1932      * @throws DBException upon database related error.
1933      */
1934     private void renumRelations(final Relation rel, final boolean isInc) throws DBException {
1935         // todo add test for going below 0 or above N, but for now,
1936         // depend on UI to not provide for those mistakes
1937         Node srcNode = rel.getSrcNode();
1938         Node destNode = rel.getDestNode();
1939         List multiList = srcNode.getRawRelated(rel.getRelationTypeName(),
1940                 destNode.getNodeType());
1941         ArrayList rellist = new ArrayList();
1942 
1943         for (Iterator iterator = multiList.iterator(); iterator.hasNext();) {
1944             MultiDBObject multi = (MultiDBObject) iterator.next();
1945             rellist.add(multi.getDBObject(Node.RELATION_JOIN));
1946         }
1947 
1948         Relation[] rels = (Relation[]) rellist.toArray(new Relation[rellist.size()]);
1949 
1950         int oldnum = rel.getOrderInt();
1951 
1952         if (isInc) {
1953             rel.setOrder(oldnum + 1);
1954             rel.updateOrder();
1955 
1956             // move upper neighbor down if nec.
1957             // index is 0-based, orderInt is 1-based
1958             int index = (oldnum + 1) - 1; // adjust to array index by -1
1959 
1960             if (rels[index].getOrderInt() == (oldnum + 1)) {
1961                 rels[index].setOrder(oldnum);
1962                 rels[index].updateOrder();
1963             }
1964         } else {
1965             rel.setOrder(oldnum - 1);
1966             rel.updateOrder();
1967 
1968             // move lower neighbor up if nec.
1969             int index = oldnum - 1 - 1; // adjust to array index by -1
1970 
1971             if (rels[index].getOrderInt() == (oldnum - 1)) {
1972                 rels[index].setOrder(oldnum);
1973                 rels[index].updateOrder();
1974             }
1975         }
1976     }
1977 
1978 
1979     /***
1980      * Views all attributes or relations of a given part.  Useful for when somebody
1981      * does not have permission to update an attribute, but the attribute
1982      * label is truncates (such as in treeview)
1983      *
1984      * @param request  ExpressoRequest the ExpressoRequest object.
1985      * @param response ExpressoResponse the ExpressoResponse object.
1986      * @throws ControllerException upon controller framework related error.
1987      * @throws DBException         upon database access related error.
1988      */
1989     protected void runViewPartState(final ExpressoRequest request,
1990                                     final ExpressoResponse response)
1991             throws ControllerException, DBException {
1992         Node node = NodeAction.getNode(request);
1993         Part part = new Part();
1994         part.setPartId(request.getParameter(Part.PART_ID));
1995         part.retrieve();
1996 
1997         request.getSession().setAttribute("ReadOnlyRequest", Boolean.TRUE);
1998 
1999         try {
2000             response.add(new Output(Attribute.ATTRIBUTE_DISPLAY_NAME, part.getPartLabel()));
2001 
2002             Transition viewTrans = AddNodeAction.getViewTrans(node.getNodeId());
2003             viewTrans.setLabel(node.getNodeTitle());
2004             response.add(viewTrans);
2005 
2006             // we reuse jsp code from entire 'flat' view of node, so we reuse the top
2007             // block of allparts, even though we will only be adding one part to it.
2008             Block allparts = new Block("allparts");
2009             response.add(allparts);
2010             if (part.isOwnedAttribute()) {
2011                 Attribute[] attribsArray = node.getAttributes(part.getPartType());
2012                 allparts.add(getAttributeOutputBlock(node, request, part, false, Arrays.asList(attribsArray)));
2013             } else {
2014                 List related = node.getRawRelated(part.getNodeRelation(), part.getPartType());
2015                 allparts.add(getSharedNodeBlock(node, request, part, false, related));
2016             }
2017 
2018         } catch (Exception e) {
2019             throw new ControllerException(e);
2020         }
2021 
2022     }
2023 
2024 }