1
2
3
4
5
6
7
8
9
10 package com.sri.emo.dbobj;
11
12 import com.jcorporate.expresso.core.controller.*;
13 import com.jcorporate.expresso.core.dataobjects.jdbc.JoinedDataObject;
14 import com.jcorporate.expresso.core.db.DBConnection;
15 import com.jcorporate.expresso.core.db.DBException;
16 import com.jcorporate.expresso.core.db.exception.DBRecordNotFoundException;
17 import com.jcorporate.expresso.core.dbobj.*;
18 import com.jcorporate.expresso.core.misc.DateTime;
19 import com.jcorporate.expresso.core.registry.RequestRegistry;
20 import com.jcorporate.expresso.core.security.ReadOnlyUser;
21 import com.jcorporate.expresso.core.security.SuperUser;
22 import com.jcorporate.expresso.core.security.User;
23 import com.jcorporate.expresso.core.security.filters.AllowedHtmlPlusURLFilter;
24 import com.jcorporate.expresso.core.security.filters.Filter;
25 import com.jcorporate.expresso.core.security.filters.RawFilter;
26 import com.jcorporate.expresso.core.security.filters.XmlFilter;
27 import com.jcorporate.expresso.services.dbobj.RowPermissions;
28 import com.sri.common.controller.AbstractDBController;
29 import com.sri.emo.annotations.NodeTag;
30 import com.sri.emo.controller.AddNodeAction;
31 import com.sri.emo.dbobj.model_tree.ModelVisitable;
32 import com.sri.emo.dbobj.model_tree.ModelVisitor;
33 import com.sri.emo.dbobj.selectiontree.TreeSelectionFactory;
34 import org.dom4j.DocumentFactory;
35 import org.dom4j.Element;
36
37 import java.util.*;
38
39
40 /***
41 * Encapsulate an element in a linked graph.
42 *
43 * @author larry hamel
44 * @todo Implement equals and hashcode for handling in collections.
45 */
46 public class Node extends RowSecuredDBObject implements Comparable, ModelVisitable, IViewable {
47 /***
48 *
49 */
50 private static final long serialVersionUID = 1L;
51 public static final String NODE_ID = "NODE_ID";
52 public static final String NODE_TYPE = "NODE_TYPE";
53 public static final String NODE_OWNER = "NODE_OWNER";
54 public static final String NODE_TITLE = "NODE_TITLE";
55 public static final String NODE_ANNOTATION = "NODE_ANNOTATION";
56 public static final String NODE_CREATED = "NODE_CREATED";
57 public static final String NODE_MODIFIED = "NODE_MODIFIED";
58 public static final String NODE_PATH = "NODE_PATH";
59 public static final String NODE_MIME_TYPE = "NODE_MIME_TYPE";
60 public static final String NODE_COMMENT = "NODE_COMMENT";
61
62
63 public static final String RELATION_JOIN = "relation";
64 public static final String NODE_TABLE = "node";
65 public static final String NODE_JOIN = NODE_TABLE;
66 public static final String ATTRIBUTE_JOIN = "attribute";
67
68 /***
69 * XML: used to seperate 2 halves of name for related node tags.
70 */
71 public static final String RELATED_DELIMITER = "__-__";
72 public static final String REF_ID = "REF_ID";
73 public static final String RELATED = "RELATED";
74 public static final String REFERENCE = "REFERENCE";
75
76 /***
77 * Used to separate machine name from internal ID,
78 * for creating universally unique IDs ("global" ids).
79 *
80 * @see #getGlobalId
81 */
82 public static final String ID_DELIMITER = "|||";
83 public static final String IDENT_TAG_NAME = "ident";
84
85 /***
86 * virtual field; convenience for sorting
87 */
88 public static final String GROUP_OF_OWNER = "GROUP_OF_OWNER";
89 public static final String REFS_ONLY = "refs_only";
90 public static final String IGNORE_RELATIONS = "IGNORE_RELATIONS";
91
92 /***
93 * optional attribute which will hold indentation level for tree access
94 */
95 public static final String INDENT = "indent";
96 public static final String FULL_XML = "FULL_XML";
97 public static final String RELATION_TYPE_JOIN = "Relation_type";
98
99 public Node(final ReadOnlyUser user) throws DBException {
100 super(user);
101 }
102
103 /***
104 * Constructor without parameters is required by framework,
105 * but if you use this constructor,
106 * be sure to setRequestingUid() after instantiation.
107 *
108 * @throws DBException upon error.
109 */
110 public Node() throws DBException {
111 super();
112
113
114 }
115
116 /***
117 * Constructor.
118 *
119 * @param theConnection DBConnection to be used to
120 * communicate with the database
121 * @param theUser User name attempting to access the
122 * object
123 * @throws DBException If the user cannot access this
124 * object or the object cannot be initialized
125 */
126 public Node(DBConnection theConnection, int theUser) throws DBException {
127 super(theConnection, theUser);
128 }
129
130 /***
131 * Constructs a Node with the given 'context' and the given Id.
132 *
133 * @param id String the node id. (String)
134 * @param request the <code>ExpressoRequest</code> object.
135 * @throws DBException If the user cannot access this
136 * object or the object cannot be initialized
137 */
138 public Node(ExpressoRequest request, String id) throws DBException {
139 super(request);
140 setNodeId(id);
141 }
142
143 /***
144 * Constructs a Node that has the given string id. Security Parameters
145 * are set by RowSecuredDBObject's default constructors using
146 * the <tt>RequestRegistry</tt>
147 *
148 * @param id String the node id. (String)
149 * @throws DBException upon error.
150 */
151 public Node(String id) throws DBException {
152 setNodeId(id);
153 }
154
155 /***
156 * @param request the <code>ExpressoRequest</code> object.
157 * @throws DBException If the user cannot access this
158 * object or the object cannot be initialized
159 */
160 public Node(ExpressoRequest request) throws DBException {
161 super(request);
162 }
163
164 /***
165 * Constructs a node using another object as the security credentials.
166 *
167 * @param node SecuredDBObject
168 * @throws DBException upon construction error.
169 */
170 public Node(SecuredDBObject node) throws DBException {
171 super(node.getRequestingUser());
172 setDataContext(node.getDataContext());
173 }
174
175 /***
176 * Constructs a Node from an XML Element.
177 *
178 * @param root Element The Element to parse.
179 * @throws DBException upon error.
180 */
181 public Node(Element root) throws DBException {
182 createFromXml(root);
183 }
184
185
186 /***
187 * Construct node from XML--just basic XML attributes,
188 * NOT related nodes, or model-app Attributes
189 * side-effect: also adds DBObject.setAttribute()
190 * items for root param, and one for ident.
191 *
192 * @param request the <code>ExpressoRequest</code> object.
193 * @param root The Root Element to load.
194 * @throws DBException upon database access error.
195 */
196 public Node(ExpressoRequest request, Element root) throws DBException {
197 super(request);
198 createFromXml(root);
199 }
200
201 private void createFromXml(Element root) throws DBException {
202 setNodeType(root.getName());
203 setAttribute("root", root);
204
205 Part[] parts = PartsFactory.getParts(getNodeType());
206
207 if (parts.length == 0) {
208 throw new DBException("Cannot find type: " + getNodeType());
209 }
210
211
212 setNodeTitle(root.attributeValue(NODE_TITLE));
213
214
215 String ident = root.attributeValue(IDENT_TAG_NAME);
216 setAttribute(IDENT_TAG_NAME, ident);
217
218 Element ann = (Element) root.selectSingleNode("./" + NODE_ANNOTATION);
219
220 if (ann != null) {
221 setNodeAnnotation(ann.getTextTrim());
222 }
223
224 Element comment = (Element) root.selectSingleNode("./" + NODE_COMMENT);
225
226 if (comment != null) {
227 setNodeComment(comment.getTextTrim());
228 }
229
230 setRecentEditor(RequestRegistry.getUser().getLoginName());
231 }
232
233 /***
234 * @return list of NodeTag instances for this node
235 */
236 public List getTags() throws DBException {
237 NodeTag tags = new NodeTag(SuperUser.INSTANCE);
238 tags.setNodeId(this.getNodeId());
239 return tags.searchAndRetrieveList(NodeTag.TAG_ADDED_ON);
240 }
241
242 /***
243 * Defines the database table name and fields for this DB object.
244 *
245 * @throws DBException if the operation cannot be performed
246 */
247 protected synchronized void setupFields() throws DBException {
248 setTargetTable(NODE_TABLE);
249 setDescription("Node, a sample record");
250 addField(NODE_ID, DBField.AUTOINC_TYPE, 0, false, "Autoincrement ID");
251 addField(NODE_TYPE, DBField.VARCHAR_TYPE, 100, false, "Type of node");
252
253
254 addField(NODE_OWNER, DBField.VARCHAR_TYPE, 245, false,
255 "Last editor");
256 addField(NODE_TITLE, DBField.VARCHAR_TYPE, 255, false, "Title");
257 addField(NODE_ANNOTATION, DBField.LONGVARCHAR_TYPE, 0, true, "Summary");
258
259
260
261 DBField fieldMeta = (DBField) getMetaData().getFieldMetadata(NODE_ANNOTATION);
262 fieldMeta.setFilterClass(AllowedHtmlPlusURLFilter.class);
263 addField(NODE_COMMENT, DBField.LONGVARCHAR_TYPE, 0, true, "Comment");
264 fieldMeta = (DBField) getMetaData().getFieldMetadata(NODE_COMMENT);
265 fieldMeta.setFilterClass(AllowedHtmlPlusURLFilter.class);
266
267 addField(NODE_CREATED, DBField.DATETIME_TYPE, 0, false, "Created");
268 addField(NODE_MODIFIED, DBField.DATETIME_TYPE, 0, false,
269 "Modified Timestamp");
270
271
272 addField(NODE_PATH, DBField.LONGVARCHAR_TYPE, 0, true, "Path to File");
273 addField(NODE_MIME_TYPE, DBField.VARCHAR_TYPE, 255, true,
274 "MIME type of File");
275
276 setLookupObject(NODE_TYPE, NodeType.class.getName());
277 addKey(NODE_ID);
278
279 addDetail(Attribute.class.getName(), NODE_ID, Attribute.NODE_ID);
280 addDetail(Relation.class.getName(), NODE_ID, Relation.RELATION_SRC);
281 addDetail(NodeTag.class.getName(), NODE_ID, NodeTag.TAG_PARENT_NODE);
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313 /***
314 * @todo make name of node unique via index; may revisit later
315 */
316 addIndex("node_name_idx", Node.NODE_TITLE, true);
317 addIndex("node_type_idx", Node.NODE_TYPE, false);
318
319
320
321 }
322
323
324
325 /***
326 * Add sample record.
327 *
328 * @throws DBException if the operation cannot be performed
329 */
330 public synchronized void populateDefaultValues() throws DBException {
331 }
332
333 public void setNodeId(String id) throws DBException {
334 setField(NODE_ID, id);
335 }
336
337 public String getNodeType() throws DBException {
338 return getField(Node.NODE_TYPE);
339 }
340
341 public void setNodeType(String type) throws DBException {
342 setField(Node.NODE_TYPE, type);
343 }
344
345 public String getNodeId() throws DBException {
346 return getField(Node.NODE_ID);
347 }
348
349
350 /***
351 * Adds a new attribute to be associated with this node.
352 *
353 * @param attribType String the attribute type.
354 * @param value String the attribute value.
355 * @param comment String the attribute comment.
356 * @return Attribute the resulting saved attribute.
357 * @throws DBException upon general database error.
358 */
359 public Attribute addAttribute(final String attribType, final String value, final String comment) throws
360 DBException {
361 Attribute attribute = new Attribute();
362 attribute.setField(Attribute.ATTRIBUTE_TYPE, attribType);
363 attribute.setField(Attribute.NODE_ID, this.getNodeId());
364 attribute.setField(Attribute.NODE_TYPE, this.getNodeType());
365
366
367 if (attribute.getPart().isSingleValued()) {
368
369 List existing = attribute.searchAndRetrieveList();
370 if (existing.size() > 0) {
371
372 Attribute existingAttrib = (Attribute) existing.get(0);
373 existingAttrib.setField(Attribute.ATTRIBUTE_VALUE, value);
374 existingAttrib.setField(Attribute.ATTRIBUTE_COMMENT, comment);
375 existingAttrib.update();
376 return existingAttrib;
377 }
378 }
379
380
381 attribute.setField(Attribute.ATTRIBUTE_VALUE, value);
382 attribute.setField(Attribute.ATTRIBUTE_COMMENT, comment);
383 attribute.add();
384 touch();
385 return attribute;
386 }
387
388
389 /***
390 * Saves an attributes for the node, touching the node as necessary, etc.
391 *
392 * @param attributeId the attribute to update.
393 * @return Attribute the resulting attribute.
394 * @throws DBException upon general DBObject error.
395 * @throws DBRecordNotFoundException if the attribute wasn't found.
396 */
397 public Attribute updateAttribute(String attributeId, String value, String comment) throws DBException,
398 DBRecordNotFoundException {
399 Attribute attribute = new Attribute();
400
401 attribute.setField(Attribute.ATTRIBUTE_ID, attributeId);
402
403 boolean found = attribute.find();
404 boolean isSame = found &&
405 value.equals(attribute.getAttribValue()) &&
406 comment.equals(attribute.getAttribComment());
407 boolean shouldDelete = found && !isSame &&
408 (value.length() == 0) && (comment.length() == 0);
409
410 if (isSame) {
411
412 return null;
413 }
414
415
416 if (shouldDelete) {
417 attribute.delete();
418 touch();
419
420 return attribute;
421 }
422
423 if (found) {
424 attribute.setField(Attribute.ATTRIBUTE_VALUE, value);
425 attribute.setField(Attribute.ATTRIBUTE_COMMENT, comment);
426 attribute.update();
427 touch();
428 return attribute;
429 } else {
430 throw new DBRecordNotFoundException("Cannot find attribute with id = " + attributeId);
431 }
432
433 }
434
435
436 /***
437 * Get the node's title.
438 *
439 * @return current title or "" -- never null
440 * @throws DBException upon database access error.
441 */
442 public String getNodeTitle() throws DBException {
443 return getField(Node.NODE_TITLE);
444 }
445
446 /***
447 * @param relation
448 * @param nodeType
449 * @return a list of MultiDBObject which are products of an SQL join, in order by relation order
450 * @throws DBException upon database access error.
451 */
452 public List getRawRelated(String relation, String nodeType) throws DBException {
453 MultiDBObject joinObject = new MultiDBObject();
454 joinObject.setDataContext(getDataContext());
455 joinObject.addDBObj(Relation.class.getName(), RELATION_JOIN);
456
457 Node sampleNode = new Node(getRequestingUser());
458 sampleNode.setNodeType(nodeType);
459 joinObject.addDBObj(sampleNode, NODE_JOIN);
460
461 joinObject.setForeignKey(RELATION_JOIN, Relation.RELATION_DEST, NODE_JOIN, Node.NODE_ID);
462 joinObject.setField(RELATION_JOIN, Relation.RELATION_SRC, getNodeId());
463 joinObject.setField(RELATION_JOIN, Relation.RELATION_TYPE, relation);
464
465
466 return joinObject.searchAndRetrieveList(Relation.RELATION_ORDER);
467 }
468
469 static int numCalls = 0;
470
471 /***
472 * return all nodes related to this node as source
473 *
474 * @return a list of MultiDBObject which are products of an SQL join, in order by relation order
475 * @throws DBException upon database access error.
476 */
477 public List getRawRelated() throws DBException {
478 MultiDBObject joinObject = new MultiDBObject();
479 joinObject.setDataContext(getDataContext());
480
481 Relation rel = new Relation();
482 rel.setSrcId(getNodeId());
483 joinObject.addDBObj(rel, RELATION_JOIN);
484
485 Node sampleNode = new Node();
486 joinObject.addDBObj(sampleNode, NODE_JOIN);
487
488 joinObject.setForeignKey(RELATION_JOIN, Relation.RELATION_DEST,
489 NODE_JOIN, Node.NODE_ID);
490 joinObject.setField(RELATION_JOIN, Relation.RELATION_SRC, getNodeId());
491
492
493
494 return joinObject.searchAndRetrieveList(Relation.RELATION_ORDER);
495 }
496
497
498 /***
499 * Grabs a list of joined data objects based on the matrix join.
500 *
501 * @return List of JoinedDataObjects
502 * @throws DBException upon join creation/execution error.
503 */
504 public List getRawRelatedUsingDataObjects() throws DBException {
505 JoinedDataObject joinObject = new JoinedDataObject("/com/sri/emo/dbobj/NodeRelationsJoin.xml");
506 joinObject.setRequestingUser(this.getRequestingUser());
507 joinObject.set("Relation." + Relation.RELATION_SRC, getNodeId());
508 return joinObject.searchAndRetrieveList("Relation." + Relation.RELATION_ORDER);
509 }
510
511 /***
512 * Get related nodes, without ID test (for speed).
513 *
514 * @param dbname the database object to load from.
515 * @param relation
516 * @param typeOfPart
517 * @return a list of MultiDBObject which are products of an SQL join, in alpha order by title
518 * @throws DBException upon database access error.
519 */
520 public List getRawRelatedAssumeSecure(String dbname, String relation,
521 String typeOfPart) throws DBException {
522 MultiDBObject joinObject = new MultiDBObject();
523 joinObject.setDataContext(dbname);
524 joinObject.addDBObj(Relation.class.getName(), RELATION_JOIN);
525
526 Node sampleNode = new Node(SuperUser.INSTANCE);
527 sampleNode.setDataContext(dbname);
528 sampleNode.setNodeType(typeOfPart);
529 joinObject.addDBObj(sampleNode, NODE_JOIN);
530
531 joinObject.setForeignKey(RELATION_JOIN, Relation.RELATION_DEST,
532 NODE_JOIN, Node.NODE_ID);
533 joinObject.setField(RELATION_JOIN, Relation.RELATION_SRC, getNodeId());
534 joinObject.setField(RELATION_JOIN, Relation.RELATION_TYPE, relation);
535
536
537 return joinObject.searchAndRetrieveList(Relation.RELATION_ORDER);
538 }
539
540 /***
541 * @return a list of Nodes which are products of an SQL join, in by relation order; can be empty list; never returns null
542 * @throws DBException upon database access error.
543 */
544 public Node[] getRelatedNodes(String relation, String typeOfPart) throws DBException {
545 List list = getRawRelated(relation, typeOfPart);
546 ArrayList resultList = new ArrayList();
547
548 for (Iterator iterator = list.iterator(); iterator.hasNext();) {
549 MultiDBObject aMultiObject = (MultiDBObject) iterator.next();
550 String id = aMultiObject.getField(Node.NODE_JOIN, Node.NODE_ID);
551 Node node = new Node(this);
552 node.setNodeId(id);
553 node.retrieve();
554 resultList.add(node);
555 }
556
557 return (Node[]) resultList.toArray(new Node[resultList.size()]);
558 }
559
560 /***
561 * Fetch any attributes of this node, of this type, in alpha order by value.
562 *
563 * @return an array of Attributes; NEVER null, but could be empty
564 * @throws DBException upon database access error.
565 */
566 public Attribute[] getAttributes(String attribType) throws DBException {
567 Attribute attribute = new Attribute(getRequestingUser());
568 attribute.setDataContext(getDataContext());
569 attribute.setField(Attribute.ATTRIBUTE_TYPE, attribType);
570 attribute.setField(Attribute.NODE_ID, getNodeId());
571
572 return (Attribute[]) attribute.searchAndRetrieveList(Attribute.
573 ATTRIBUTE_ORDER)
574 .toArray(new Attribute[0]);
575 }
576
577
578 public ValidValue[] getMultiValuedAttributeMenu() throws DBException {
579 Attribute attrib = new Attribute();
580 attrib.setField(NODE_ID, getNodeId());
581 List attribs = attrib.searchAndRetrieveList(Attribute.ATTRIBUTE_ORDER);
582 ValidValue[] returnValue = new ValidValue[attribs.size()];
583 for (int i = 0; i < attribs.size(); i++) {
584 returnValue[i] = new ValidValue(attrib.getAttribId(), attrib.getAttribValue());
585 }
586
587 return returnValue;
588 }
589
590 /***
591 * Fetch any attributes of this node, of this type, in alpha order by value,
592 * using System ID to speed recall w/o permissions check.
593 *
594 * @return an array of Attributes; NEVER null, but could be empty
595 * @throws DBException upon database access error.
596 */
597 public Attribute[] getAttributesAssumeSecure(String attribType) throws
598 DBException {
599 Attribute attribute = new Attribute(User.getAdmin(getDataContext()));
600 attribute.setDataContext(getDataContext());
601 attribute.setField(Attribute.ATTRIBUTE_TYPE, attribType);
602 attribute.setField(Attribute.NODE_ID, getNodeId());
603
604 return (Attribute[]) attribute.searchAndRetrieveList(Attribute.ATTRIBUTE_ORDER).toArray(new Attribute[0]);
605 }
606
607 /***
608 * Fetch all attributes of this node OF ALL KINDS.
609 *
610 * @return a list of Attributes
611 * @throws DBException upon database access error.
612 */
613 public List getAttributes() throws DBException {
614 Attribute attribute = new Attribute(getRequestingUser());
615 attribute.setDataContext(getDataContext());
616 attribute.setField(Attribute.NODE_ID, getNodeId());
617
618 return attribute.searchAndRetrieveList(Attribute.ATTRIBUTE_ORDER);
619 }
620
621 public String getNodeAnnotation() throws DBException {
622 return getField(Node.NODE_ANNOTATION);
623 }
624
625 public String getNodeAnnotationRaw() throws DBException {
626 Filter old = setFilterClass(new RawFilter());
627 String raw = getNodeAnnotation();
628 setFilterClass(old);
629
630 return raw;
631 }
632
633 public String getNodeCommentRaw() throws DBException {
634 Filter old = setFilterClass(new RawFilter());
635 String raw = getNodeComment();
636 setFilterClass(old);
637
638 return raw;
639 }
640
641 public String getNodeComment() throws DBException {
642 return getField(Node.NODE_COMMENT);
643 }
644
645 public void setNodeTitle(String title) throws DBException {
646 setField(NODE_TITLE, title);
647 }
648
649 /***
650 * Creates and returns a deep copy of this object--everything, including
651 * links to "contained by" objects above this one.
652 *
653 * @param title The Node Title.
654 * @return a cloned sibling.
655 * @throws DBException upon database access error.
656 * @see #cloneOrphan
657 */
658 public Node cloneSibling(String title) throws DBException {
659 Node clone = cloneAllButRelations(title);
660 cloneRelations(clone);
661
662 return clone;
663 }
664
665 /***
666 * Fet relations for which my ID is marked as source.
667 *
668 * @return an array of relations.
669 * @throws DBException upon database access error.
670 */
671 public Relation[] getSrcRelations() throws DBException {
672 return getRelations(Relation.RELATION_SRC);
673 }
674
675 /***
676 * Get relations.
677 *
678 * @param srcOrDest field name for how to interpret getID():
679 * either as src (pass in Relation.RELATION_SRC) or dest (pass in Relation.RELATION_DEST)
680 * @return an array of relations, never null
681 * @throws DBException upon database access error.
682 * @see Relation#RELATION_SRC
683 * @see Relation#RELATION_DEST
684 */
685 private Relation[] getRelations(String srcOrDest) throws DBException {
686 Relation rel = new Relation();
687 rel.setField(srcOrDest, getNodeId());
688
689 return (Relation[]) rel.searchAndRetrieveList(Relation.RELATION_ORDER).toArray(new Relation[0]);
690 }
691
692 /***
693 * Get relations for which my ID is marked as destination.
694 *
695 * @return an array of relations, never null
696 * @throws DBException upon database access error.
697 */
698 public Relation[] getDestRelations() throws DBException {
699 return getRelations(Relation.RELATION_DEST);
700 }
701
702 /***
703 * Delete row.
704 *
705 * @throws DBException upon database access error.
706 */
707 public synchronized void delete(boolean deleteDetails) throws DBException {
708
709 /*** @todo fix expresso details so that this hack is unnecessary
710 * currently detail can only support one association between two tables
711 * so manually delete any destination relations
712 */
713 Relation relation = new Relation();
714 relation.setDataContext(getDataContext());
715 relation.setField(Relation.RELATION_DEST, getNodeId());
716
717 List list = relation.searchAndRetrieveList();
718
719 for (Iterator iter = list.iterator(); iter.hasNext();) {
720 Relation rel = (Relation) iter.next();
721 rel.delete(true);
722 }
723
724 super.delete(deleteDetails);
725 }
726
727
728
729 public String getNodeOwner() throws DBException {
730 return getField(Node.NODE_OWNER);
731 }
732
733 public String getNodeCreated() throws DBException {
734 return getField(Node.NODE_CREATED);
735 }
736
737 public String getNodeModified() throws DBException {
738 return getField(Node.NODE_MODIFIED);
739 }
740
741 /***
742 * @return version number for this node type
743 * @throws DBException upon database access error.
744 */
745 public String getVersion() throws DBException {
746 NodeType type = new NodeType(getRequestingUser());
747 type.setDataContext(getDataContext());
748 type.setEntityName(getNodeType());
749
750 if (!type.find()) {
751 return "1.0";
752 }
753
754 return type.getVersion();
755 }
756
757 /***
758 * We override to set dates
759 * (by superclass) but rather to add default permissions.
760 *
761 * @throws DBException upon database access error.
762 * @see #add(String, int) for a better way to add() with specific permissions
763 */
764 public synchronized void add() throws DBException {
765 String datestamp = DateTime.getDateTimeForDB();
766 setField(NODE_CREATED, datestamp);
767 setField(NODE_MODIFIED, datestamp);
768
769 super.add();
770
771
772 NodeType entity = getEntity();
773
774 if (entity.hasCustomHandler()) {
775 entity.getCustomHandler().init(this);
776 }
777 }
778
779 /***
780 * We override to set dates, and make sure perms are complete.
781 *
782 * @throws DBException upon database access error.
783 */
784 public synchronized void update() throws DBException {
785 String datestamp = DateTime.getDateTimeForDB();
786 setField(NODE_MODIFIED, datestamp);
787 super.update();
788
789
790 User user = User.getUserFromId(getRequestingUid(), getDataContext());
791 String userPrimaryGroup = user.getPrimaryGroup();
792 List nodegroups = getWriteGroups();
793
794 if (!nodegroups.contains(userPrimaryGroup)) {
795 addGroupPerm(userPrimaryGroup,
796 RowPermissions.OTHERS_READ_AND_GROUP_WRITES_PERMISSIONS);
797 }
798 }
799
800
801
802 /***
803 * Format XML for this node.
804 * Note that double quotes are removed from regular
805 * fields (varchar fields) by the JDBC escape handler. longvarchar fields,
806 * on the other hand, are not filtered by this handler, NOR by standard calls
807 * to getField which would normally use the FilterManager. so longvarchar
808 * fields must be filtered manually.
809 * <p/>
810 * This routine is intensive on DB. To lighten load, security checks are reduced after first node.
811 * related nodes are assumed readable, so we use "superuser"
812 * </p>
813 *
814 * @param alreadyIncluded list of nodes already included in XML; used to prevent infinite loop if a circular relation is found
815 * @param request the <code>ExpressoRequest</code> object.
816 * @return dom4j Element.
817 * @throws DBException upon database access error.
818 */
819 public Element getXML(ExpressoRequest request, ArrayList alreadyIncluded) throws DBException {
820 Filter old = setFilterClass(new XmlFilter());
821
822
823 if (alreadyIncluded.contains(this)) {
824 Element root = DocumentFactory.getInstance().createElement(REFERENCE);
825
826
827 root.addAttribute(REF_ID, getGlobalId()).addAttribute(Node.NODE_TITLE, getNodeTitle());
828
829 return root;
830 }
831
832 Element root = DocumentFactory.getInstance().createElement(getNodeType());
833 alreadyIncluded.add(this);
834
835
836 String title = getNodeTitle();
837 boolean isChangingTitle = request.getParameter(AddNodeAction.IS_CHANGE_TITLE) != null;
838
839 if (isChangingTitle) {
840 String append = request.getParameter(AddNodeAction.APPEND_TO_TITLE);
841
842
843 if (append != null) {
844 if (append.startsWith("s/") && append.endsWith("/")) {
845 String[] pieces = append.split("/");
846
847 if (pieces.length != 3) {
848 throw new DBException("found substitution string: " +
849 append +
850 " and expected it to parse into 3 pieces by key '/', " +
851 "but found num pieces: " + pieces.length);
852 }
853
854 title = title.replaceFirst(pieces[1], pieces[2]);
855 } else {
856 title += append;
857 }
858 }
859 }
860
861 root.addAttribute(Node.NODE_TITLE, title);
862
863 root.addAttribute(NodeType.NODE_TYPE_VERSION, getVersion());
864 root.addAttribute(IDENT_TAG_NAME, getGlobalId());
865
866
867
868
869 String comment = getNodeComment();
870
871 if (comment.length() > 0) {
872 root.addElement(Node.NODE_COMMENT).setText(comment);
873 }
874
875 String annotation = getNodeAnnotation();
876
877 if (annotation.length() > 0) {
878 root.addElement(Node.NODE_ANNOTATION).setText(annotation);
879 }
880
881 HashMap refsonlymap = getNodeTypesWithRefOnly_Map(request);
882 HashMap ignoredRelationsMap = getIgnoredRelations(request);
883
884 Part[] parts = PartsFactory.getParts(getNodeType());
885
886 for (int i = 0; i < parts.length; i++) {
887 Part part = parts[i];
888
889 if (part.isSharedNodeAttrib()) {
890 if (ignoredRelationsMap.get(part.getNodeRelation()) != null) {
891 continue;
892 }
893
894 Node[] related = getRelatedNodesAssumeSecure(part.
895 getNodeRelation(),
896 part.getPartType());
897
898 if (related.length > 0) {
899 Element relatedElmCollection = root.addElement(RELATED);
900 relatedElmCollection.addAttribute(Part.PART_LABEL, part.getPartLabel());
901 relatedElmCollection.addAttribute(Part.PART_TYPE, part.getPartType());
902 relatedElmCollection.addAttribute(Part.NODE_PART_RELATION_TYPE, part.getNodeRelation());
903 relatedElmCollection.addAttribute(Part.PART_NUM, part.getPartNum());
904
905 for (int j = 0; j < related.length; j++) {
906 Node aRelatedNode = related[j];
907 NodeType nodeType = aRelatedNode.getEntity();
908
909
910 boolean useIdForRef = refsonlymap.get(nodeType.getEntityName()) != null;
911
912 String order = null;
913
914 if (related.length > 1) {
915 order = "" + (j + 1);
916 }
917
918 if (useIdForRef) {
919
920 aRelatedNode.addRef(relatedElmCollection, order);
921 } else {
922
923 Element relXml = aRelatedNode.getXML(request, alreadyIncluded);
924
925
926 if (order != null) {
927 relXml.addAttribute(Relation.RELATION_ORDER, order);
928 }
929
930 relatedElmCollection.add(relXml);
931 }
932 }
933 }
934 } else {
935 Attribute[] attribs = getAttributesAssumeSecure(part.getPartType());
936 boolean addOrder = attribs.length > 1;
937
938 if (attribs.length > 0) {
939 if (part.isSingleValued() && (attribs.length > 1)) {
940
941 attribs = new Attribute[]{attribs[0]};
942 }
943
944
945 for (int j = 0; j < attribs.length; j++) {
946 Attribute attrib = attribs[j];
947 root.add(attrib.getXML(request, addOrder));
948 }
949 } else {
950
951
952 if (part.isHaveCustomHandler()) {
953 IPartHandler handler = part.getCustomHandler();
954 if (handler.isNeededInFullXML()) {
955
956 ReadOnlyUser oldUser = getRequestingUser();
957 this.setRequestingUser(User.getUserFromId(ownerID()));
958 Attribute attrib = AbstractMatrixHandler.createAttrib(this, part.getPartType());
959 this.setRequestingUser(oldUser);
960 getLogger().debug("creating new attribute of type: " +
961 attrib.getAttribType());
962
963 root.add(attrib.getXML(request, addOrder));
964 }
965 }
966 }
967
968 }
969 }
970
971 setFilterClass(old);
972
973 return root;
974 }
975
976 /***
977 * @param request the <code>ExpressoRequest</code> object.
978 * @return hashmap of param values which have name 'refs_only'
979 */
980 public static HashMap getNodeTypesWithRefOnly_Map(ExpressoRequest request) {
981 HashMap refs_only = new HashMap();
982
983 String[] ref_only = Controller.getParamValues((ServletControllerRequest)
984 request,
985 REFS_ONLY);
986
987 for (int i = 0; (ref_only != null) && (i < ref_only.length); i++) {
988 String referenceOnlyType = ref_only[i];
989 refs_only.put(referenceOnlyType, referenceOnlyType);
990 }
991
992 return refs_only;
993 }
994
995 /***
996 * @param request the <code>ExpressoRequest</code> object.
997 * @return map of param values which have name 'IGNORE_RELATIONS'
998 */
999 public static HashMap getIgnoredRelations(ExpressoRequest request) {
1000 HashMap ignoredRelmap = new HashMap();
1001
1002 String[] ignored = Controller.getParamValues((ServletControllerRequest) request, IGNORE_RELATIONS);
1003
1004 for (int i = 0; (ignored != null) && (i < ignored.length); i++) {
1005 String ignoredRel = ignored[i];
1006 ignoredRelmap.put(ignoredRel, ignoredRel);
1007 }
1008
1009 return ignoredRelmap;
1010 }
1011
1012 /***
1013 * Add a reference ID tag to the xml for this node.
1014 *
1015 * @throws DBException upon database access error.
1016 */
1017 private void addRef(Element elem, String order) throws DBException {
1018 elem.addElement(REFERENCE)
1019 .addAttribute(REF_ID, getGlobalId())
1020 .addAttribute(Node.NODE_TITLE, getNodeTitle());
1021
1022 if (order != null) {
1023 elem.addAttribute(Relation.RELATION_ORDER, order);
1024 }
1025 }
1026
1027 /***
1028 * Assuming all access is privileged, get related nodes.
1029 *
1030 * @return an array of related nodes.
1031 * @throws DBException upon database access error.
1032 */
1033 private Node[] getRelatedNodesAssumeSecure(String relation, String partType) throws DBException {
1034 List list = getRawRelatedAssumeSecure(getDataContext(), relation, partType);
1035 ArrayList resultList = new ArrayList();
1036
1037 for (Iterator iterator = list.iterator(); iterator.hasNext();) {
1038 MultiDBObject aMultiObject = (MultiDBObject) iterator.next();
1039 String id = aMultiObject.getField(Node.NODE_JOIN, Node.NODE_ID);
1040 Node node = new Node(SuperUser.INSTANCE);
1041 node.setNodeId(id);
1042 node.setDataContext(getDataContext());
1043 node.retrieve();
1044 resultList.add(node);
1045 }
1046
1047 return (Node[]) resultList.toArray(new Node[resultList.size()]);
1048 }
1049
1050 /***
1051 * For creating universally unique IDs ("global" ids)
1052 * by combining machine name with internal ID.
1053 *
1054 * @return node id.
1055 * @throws DBException upon database access error.
1056 */
1057 public String getGlobalId() throws DBException {
1058
1059
1060
1061 return getNodeId();
1062 }
1063
1064 public String getRecentEditor() throws DBException {
1065 return getField(NODE_OWNER);
1066 }
1067
1068 public void setRecentEditor(String username) throws DBException {
1069 setField(NODE_OWNER, username);
1070 }
1071
1072 /***
1073 * Get node type; will throw if node type is not set or cannot be found.
1074 *
1075 * @return entity named by the node type of this node
1076 * @throws DBException upon database access error.
1077 */
1078 public NodeType getEntity() throws DBException {
1079 NodeType type = new NodeType(getRequestingUser());
1080 type.setDataContext(getDataContext());
1081 type.setEntityName(getNodeType());
1082
1083 if (!type.find()) {
1084 throw new DBException("cannot find node type with entity name: " +
1085 getNodeType());
1086 }
1087
1088 return type;
1089 }
1090
1091 /***
1092 * Create clone which copies all EXCEPT "upstream" links.
1093 *
1094 * @param title The Node Title.
1095 * @return cloned Node
1096 * @throws DBException upon database access error.
1097 * @see #cloneSibling
1098 */
1099 public Node cloneOrphan(String title) throws DBException {
1100 Node clone = cloneAllButRelations(title);
1101 cloneOrphanRelations(clone);
1102
1103 return clone;
1104 }
1105
1106 private void cloneOrphanRelations(Node clone) throws DBException {
1107 cloneRelations(clone, false);
1108 }
1109
1110 private Node cloneAllButRelations(String title) throws DBException {
1111 Node clone = new Node(this);
1112 clone.setNodeId(getNodeId());
1113 clone.retrieve();
1114
1115 clone.setNodeTitle(title);
1116 clone.setNodeId(
1117 "0");
1118
1119
1120 clone.add();
1121
1122 copyAttribsInto(clone);
1123
1124 return clone;
1125 }
1126
1127 /***
1128 * Copies all the node attributes into the specified node.
1129 *
1130 * @param clone Node the node we're copying into.
1131 * @throws DBException upon error.
1132 */
1133 private void copyAttribsInto(Node clone) throws DBException {
1134 List attribs = getAttributes();
1135 for (Iterator iterator = attribs.iterator(); iterator.hasNext();) {
1136 Attribute attrib = (Attribute) iterator.next();
1137 attrib.clone(clone.getNodeId());
1138 }
1139 }
1140
1141 /***
1142 * Clone ALL relations.
1143 *
1144 * @param clone The node to clone.
1145 * @throws DBException upon database access error.
1146 */
1147 private void cloneRelations(Node clone) throws DBException {
1148 cloneRelations(clone, true);
1149 }
1150
1151 /***
1152 * clone relations--all or some, depending on boolean
1153 *
1154 * @param cloneNode The node to clone.
1155 * @param isCloneAll true if all relations are cloned;
1156 * false if we omit some in order to create an "orphan" clone
1157 * @throws DBException upon database access error.
1158 */
1159 private void cloneRelations(Node cloneNode, boolean isCloneAll) throws DBException {
1160 Relation[] src = getSrcRelations();
1161
1162 for (int i = 0; i < src.length; i++) {
1163 Relation relation = src[i];
1164
1165 if (!isCloneAll && relation.isUpstreamLink(Relation.RELATION_SRC)) {
1166 continue;
1167 }
1168
1169 relation.clone(cloneNode.getNodeId(), Relation.RELATION_SRC);
1170 }
1171
1172 Relation[] dest = getDestRelations();
1173
1174 for (int i = 0; i < dest.length; i++) {
1175 Relation relation = dest[i];
1176
1177 if (!isCloneAll && relation.isUpstreamLink(Relation.RELATION_DEST)) {
1178 continue;
1179 }
1180
1181 relation.clone(cloneNode.getNodeId(), Relation.RELATION_DEST);
1182 }
1183 }
1184
1185 /***
1186 * Compares this object with the specified object for order. Returns a
1187 * negative integer, zero, or a positive integer as this object is less
1188 * than, equal to, or greater than the specified object.<p>
1189 * <p/>
1190 * In the foregoing description, the notation
1191 * <tt>sgn(</tt><i>expression</i><tt>)</tt> designates the mathematical
1192 * <i>signum</i> function, which is defined to return one of <tt>-1</tt>,
1193 * <tt>0</tt>, or <tt>1</tt> according to whether
1194 * the value of <i>expression</i>
1195 * is negative, zero or positive.
1196 * <p/>
1197 * The implementor must ensure <tt>sgn(x.compareTo(y)) ==
1198 * -sgn(y.compareTo(x))</tt> for all <tt>x</tt> and <tt>y</tt>. (This
1199 * implies that <tt>x.compareTo(y)</tt> must throw an exception iff
1200 * <tt>y.compareTo(x)</tt> throws an exception.)<p>
1201 * <p/>
1202 * The implementor must also ensure that the relation is transitive:
1203 * <tt>(x.compareTo(y)>0 && y.compareTo(z)>0)</tt> implies
1204 * <tt>x.compareTo(z)>0</tt>.<p>
1205 * <p/>
1206 * Finally, the implementer must ensure that <tt>x.compareTo(y)==0</tt>
1207 * implies that <tt>sgn(x.compareTo(z)) == sgn(y.compareTo(z))</tt>, for
1208 * all <tt>z</tt>.<p>
1209 * <p/>
1210 * It is strongly recommended, but <i>not</i> strictly required that
1211 * <tt>(x.compareTo(y)==0) == (x.equals(y))</tt>. Generally speaking, any
1212 * class that implements the <tt>Comparable</tt> interface and violates
1213 * this condition should clearly indicate this fact. The recommended
1214 * language is "Note: this class has a natural ordering that is
1215 * inconsistent with equals."
1216 *
1217 * @param o the Object to be compared.
1218 * @return a negative integer, zero, or a positive integer as this object
1219 * is less than, equal to, or greater than the specified object.
1220 * @throws ClassCastException if the specified object's type prevents it
1221 * from being compared to this Object.
1222 */
1223 public int compareTo(Object o) {
1224 try {
1225 return getNodeTitle().compareTo(((Node) o).getNodeTitle());
1226 } catch (DBException e) {
1227 throw new RuntimeException(e);
1228 }
1229 }
1230
1231 /***
1232 * Allow a special hander to create custom view,
1233 * while providing default view also.
1234 *
1235 * @param defaultaction the default controller.
1236 * @param request the ExpressoRequest object.
1237 * @param response the ExpressoResponse object.
1238 * @throws DBException upon database access error.
1239 * @throws ControllerException upon controller element related error.
1240 */
1241 public void view(final AbstractDBController defaultaction,
1242 final ExpressoRequest request,
1243 final ExpressoResponse response) throws DBException, ControllerException {
1244
1245 NodeType entity = getEntity();
1246
1247 if (entity.hasCustomHandler()) {
1248 INodeHandler handler = entity.getCustomHandler();
1249 handler.view(this, defaultaction, (ControllerRequest) request, (ControllerResponse) response);
1250 } else {
1251
1252 ((AddNodeAction) defaultaction).view(this, (ControllerRequest) request, (ControllerResponse) response);
1253 }
1254 }
1255
1256 public void setNodeAnnotation(final String annotation) throws DBException {
1257 setField(NODE_ANNOTATION, annotation);
1258 }
1259
1260 public void setNodeComment(final String s) throws DBException {
1261 setField(NODE_COMMENT, s);
1262 }
1263
1264 /***
1265 * @param nodeOwner login name of last editor
1266 */
1267 public void setNodeOwner(final String nodeOwner) throws DBException {
1268 setField(NODE_OWNER, nodeOwner);
1269 }
1270
1271 /***
1272 * Find nodes contained just beneath this one.
1273 *
1274 * @param request the <code>ExpressoRequest</code> object.
1275 * @return array of nodes which have relation RelationType
1276 * .DEST_IS_PART_OF_SRC with our node as "src"
1277 * @throws DBException upon database access error.
1278 */
1279 public Node[] getShallowContainedNodes(final String type, final ExpressoRequest request) throws DBException {
1280 return getRelatedNodes(RelationType.DEST_IS_PART_OF_SRC, type);
1281 }
1282
1283 /***
1284 * Recursively clone contained tree beneath this node, including this node.
1285 *
1286 * @return cloned node instance.
1287 * @throws DBException upon database access error.
1288 */
1289 public Node cloneTree(final String suffix, final HashMap itemInfo) throws DBException {
1290
1291 Node queryNode = new Node(getRequestingUser());
1292 queryNode.setDataContext(getDataContext());
1293 queryNode.setNodeTitle(getNodeTitle() + suffix);
1294
1295 if (queryNode.find()) {
1296 throw new DBException("A node named: '" + queryNode.getNodeTitle() +
1297 "' already exists.");
1298 }
1299
1300 Relation[] src = getSrcRelations();
1301
1302 for (int i = 0; i < src.length; i++) {
1303 Relation relation = src[i];
1304
1305
1306
1307 if (RelationType.DEST_IS_PART_OF_SRC.equals(relation.getRelationTypeName())) {
1308 Node destNode = relation.getDestNode();
1309 queryNode.clear();
1310 queryNode.setNodeTitle(destNode.getNodeTitle() + suffix);
1311
1312 if (queryNode.find()) {
1313 throw new DBException("A node named: '" +
1314 queryNode.getNodeTitle() + "' already exists.");
1315 }
1316 }
1317 }
1318
1319 Relation[] dest = getDestRelations();
1320
1321 for (int i = 0; i < dest.length; i++) {
1322 Relation relation = dest[i];
1323
1324 if (RelationType.DEST_CONTAINS_SRC.equals(relation.getRelationTypeName())) {
1325 Node srcNode = relation.getSrcNode();
1326 queryNode.clear();
1327 queryNode.setNodeTitle(srcNode.getNodeTitle() + suffix);
1328
1329 if (queryNode.find()) {
1330 throw new DBException("A node named: '" +
1331 queryNode.getNodeTitle() + "' already exists.");
1332 }
1333 }
1334 }
1335
1336 return cloneAllButRelations(getNodeTitle() + suffix);
1337 }
1338
1339 /***
1340 * Parse xml to create relations; all nodes from xml are assumed
1341 * already created by
1342 * Node(xml) constructor.
1343 *
1344 * @param allNodesByXML_ID all nodes put in hash with key of the ID
1345 * in the import XML, to ease
1346 * relationship-building.
1347 * @return a list of nodes which where discovered, and which need processing
1348 * @throws Exception upon DOM or database error.
1349 */
1350 public List parseXMLRelations(final Map allNodesByXML_ID, final boolean isExternalRefRequired) throws Exception {
1351 ArrayList containedNodesNeedingParsing = new ArrayList();
1352 Element root = (Element) getAttribute("root");
1353
1354 if (root == null) {
1355 return containedNodesNeedingParsing;
1356 }
1357
1358 List list = root.selectNodes("./" + RELATED);
1359
1360 for (Iterator iterator = list.iterator(); iterator.hasNext();) {
1361 Element relElem = (Element) iterator.next();
1362 String reltype = relElem.attributeValue(Part.
1363 NODE_PART_RELATION_TYPE);
1364 String parttype = relElem.attributeValue(Part.PART_TYPE);
1365
1366
1367 Part part = PartsFactory.getPart(getNodeType(), parttype, reltype);
1368
1369 if (part == null) {
1370 throw new DBException("cannot find part/relation: '" +
1371 parttype + "/" + reltype +
1372 "' which is expected in node of type: '" + getNodeType() +
1373 "' because the node being processed, with title: '" +
1374 getNodeTitle() + "' contains the XML segment: " +
1375 AbstractDBController.getPrettyXML(relElem));
1376 }
1377
1378
1379 List children = relElem.elements();
1380
1381 for (Iterator childiter = children.iterator();
1382 childiter.hasNext();) {
1383 Element childElem = (Element) childiter.next();
1384
1385
1386
1387 Relation rel = new Relation();
1388 rel.setSrcId(getNodeId());
1389 rel.setRelationTypeName(part.getNodeRelation());
1390
1391 Node destnode = null;
1392 String order = null;
1393
1394 if (REFERENCE.equals(childElem.getName())) {
1395
1396
1397 String refid = childElem.attributeValue(Node.REF_ID);
1398 order = childElem.attributeValue(Relation.RELATION_ORDER);
1399 destnode = (Node) allNodesByXML_ID.get(refid);
1400
1401 if (destnode == null) {
1402
1403 if (isExternalRefRequired) {
1404
1405 Node searchnode = new Node(refid);
1406
1407 if (!searchnode.find()) {
1408 throw new DBException("Cannot find referenced node ID: " + refid +
1409 " neither inside XML nor in this system.");
1410 }
1411
1412 destnode = searchnode;
1413 } else {
1414
1415 if (getLogger().isDebugEnabled()) {
1416 getLogger().debug("Ignore external refs.");
1417 }
1418 }
1419 }
1420 } else {
1421
1422
1423 String id = childElem.attributeValue(IDENT_TAG_NAME);
1424 order = childElem.attributeValue(Relation.RELATION_ORDER);
1425 destnode = (Node) allNodesByXML_ID.get(id);
1426
1427 if (destnode == null) {
1428 throw new DBException("Cannot find inline node ID: "
1429 + id
1430 + " which was supposed to be already parsed and "
1431 + "waiting in allNodes hash.");
1432 }
1433
1434 containedNodesNeedingParsing.add(destnode);
1435 }
1436
1437
1438 if (destnode != null) {
1439
1440
1441
1442
1443 rel.setDestId(destnode.getNodeId());
1444
1445 if (!rel.find()) {
1446 if (order == null) {
1447 order = "1";
1448 }
1449
1450 rel.setOrder(order);
1451 rel.add();
1452 }
1453 }
1454 }
1455 }
1456
1457 return containedNodesNeedingParsing;
1458 }
1459
1460 /***
1461 * Parse xml to create relations and attributes; nodes are assumed already
1462 * created by Node(xml) constructor, and allNodes has map of all nodes.
1463 * <p><b>SIDE-EFFECT:</b> removes attribute for xml from this node--we are
1464 * finished with xml, and this is a flag that no more parsing is
1465 * required/allowed
1466 * </p>
1467 *
1468 * @param allNodesByXML_ID java.util.Map
1469 * @param request The ExpressoRequest Object
1470 * @param translatePicklistMap map of old ID to new picklist item, for translation
1471 * @throws Exception upon database or DOM error.
1472 */
1473 public void parseXMLAttributes(Map allNodesByXML_ID,
1474 ExpressoRequest request,
1475 Map translatePicklistMap
1476 ) throws Exception {
1477 Element root = (Element) getAttribute("root");
1478
1479 if (root == null) {
1480 return;
1481 }
1482
1483
1484 Part[] parts = PartsFactory.getParts(getNodeType());
1485
1486 for (int i = 0; i < parts.length; i++) {
1487 Part part = parts[i];
1488
1489 if (part.isOwnedAttribute()) {
1490
1491 List list = root.selectNodes("./" + part.getPartType());
1492
1493 for (Iterator iterator = list.iterator();
1494 iterator.hasNext();) {
1495 Element attElem = (Element) iterator.next();
1496 Attribute attrib = new Attribute();
1497 attrib.setAttributeType(part.getPartType());
1498 attrib.setParentNodeType(getNodeType());
1499 attrib.setParentNodeId(getNodeId());
1500
1501 String attcomment = attElem.attributeValue(Attribute.
1502 ATTRIBUTE_COMMENT);
1503
1504 if (attcomment != null) {
1505 attrib.setAttributeComment(attcomment);
1506 }
1507
1508 String value = attElem.attributeValue(Attribute.
1509 ATTRIBUTE_VALUE);
1510
1511 if (value != null) {
1512 attrib.setAttributeValue(value);
1513 if (attrib.hasPicklist() && translatePicklistMap != null) {
1514
1515 PickList item = (PickList) translatePicklistMap.get(value);
1516 if (item == null) {
1517 getLogger().error("Cannot find picklist translation for incoming picklist ID: " + value);
1518 } else {
1519 attrib.setAttributeValue(item.getID());
1520 }
1521 }
1522 }
1523
1524 String order = attElem.attributeValue(Attribute.
1525 ATTRIBUTE_ORDER);
1526
1527 if (order == null) {
1528 order = "1";
1529 }
1530
1531 attrib.setOrder(order);
1532
1533 attrib.add();
1534
1535 if (attrib.hasCustomHandler()) {
1536 attrib.getCustomHandler().parseXML(attElem, attrib,
1537 allNodesByXML_ID, (ControllerRequest) request);
1538 }
1539 }
1540 }
1541 }
1542
1543
1544 removeAttribute("root");
1545 }
1546
1547 /***
1548 * Get ALL related nodes in tree beneath this node EXCEPT types
1549 * indicated for omission
1550 * recurses into tree; side-effect: adds attribute 'level' with node
1551 * level w/i tree to each node.
1552 * <p/>
1553 * Uses optional INDENT attribute to determine formatting indentation
1554 * </p>
1555 *
1556 * @param request The ExpressoRequest object.
1557 * @param outputMap which will end up containing all nodes in tree;
1558 * hand in empty map to begin with
1559 * @throws DBException upon database access error.
1560 * @see #INDENT
1561 */
1562 public void getNodesInStronglyRelatedTree(ExpressoRequest request, HashMap outputMap) throws DBException {
1563 getNodesInStronglyRelatedTree(outputMap);
1564 }
1565
1566 /***
1567 * Get ALL related nodes in tree beneath this node EXCEPT types indicated
1568 * for omission
1569 * recurses into tree; side-effect: adds attribute 'level' with node level
1570 * w/i tree to each node.
1571 * <p/>
1572 * uses optional INDENT attribute to determine formatting indentation
1573 * </p>
1574 *
1575 * @param outputMap which will end up containing all nodes in tree;
1576 * hand in empty map to begin with
1577 * @throws DBException upon database access error.
1578 * @see #INDENT
1579 */
1580 public void getNodesInStronglyRelatedTree(Map outputMap) throws DBException {
1581 TreeSelectionFactory selectionFactory = new TreeSelectionFactory(this);
1582 outputMap.putAll(selectionFactory.getNodesInStronglyRelatedTree());
1583 }
1584
1585 /***
1586 * Get all parts within this node.
1587 *
1588 * @return An array of Parts for this node.
1589 * @throws DBException upon database error.
1590 */
1591 public Part[] getParts() throws DBException {
1592 return PartsFactory.getParts(getNodeType());
1593 }
1594
1595 /***
1596 * Set recent editor and modification date. To save bandwidth and
1597 * ease, this function will not update if all the conditions are true.
1598 * <ol>
1599 * <li>The current user is the same as the last editor </li>
1600 * <li>Less then a second has passed since the last update.</li>
1601 * </ol>
1602 *
1603 * @throws DBException upon database error.
1604 */
1605 public void touch() throws DBException {
1606
1607 if (RequestRegistry.getUser().getLoginName().equals(getRecentEditor())) {
1608 Date now = new Date();
1609 Date lastTouched = this.getFieldDate(NODE_MODIFIED);
1610
1611
1612 if (lastTouched != null && Math.abs(now.getTime() - lastTouched.getTime()) < 1000) {
1613
1614
1615 return;
1616 }
1617 }
1618
1619 setRecentEditor(RequestRegistry.getUser().getLoginName());
1620 update(true);
1621 }
1622
1623 /***
1624 * provide a transition for viewing this object, suitable for creating an
1625 * HTTP link
1626 *
1627 * @return transtion for viewing, including label for name of object; never null
1628 */
1629 public Transition getViewTrans() throws DBException {
1630 Transition trans = new Transition(getNodeTitle(), AddNodeAction.class, AddNodeAction.VIEW_NODE);
1631 trans.addParam(Node.NODE_ID, getNodeId());
1632 return trans;
1633 }
1634
1635 public void acceptVisitor(ModelVisitor visitor) {
1636 visitor.visitNode(this);
1637 }
1638
1639
1640 public String getNodeTitleRaw() throws DBException {
1641 Filter old = setFilterClass(new RawFilter());
1642 String raw = getNodeTitle();
1643 setFilterClass(old);
1644
1645 return raw;
1646 }
1647
1648 /***
1649 * Find all properly-typed nodes related to this source node, with
1650 * relation of given type.
1651 *
1652 * @param relationType the relationship type
1653 * @param targetNodeType nodes of this type (only) will be returned
1654 * @param toPopulateOldRemainingList (returned param) list to receive ordered list of
1655 * MultiDBObjects; pass in null if you don't want this list added
1656 * @return hash with key = node Id, value = node
1657 * @throws DBException upon error.
1658 */
1659 public Map getRelatedNodesHash(String relationType,
1660 String targetNodeType,
1661 List toPopulateOldRemainingList) throws DBException {
1662
1663 List list = this.getRawRelated(relationType, targetNodeType);
1664
1665 if (toPopulateOldRemainingList != null) {
1666 toPopulateOldRemainingList.addAll(list);
1667 }
1668
1669 Hashtable alreadySelected = new Hashtable(list.size());
1670
1671 for (int i = 0; i < list.size(); i++) {
1672 MultiDBObject aMultiObject = (MultiDBObject) list.get(i);
1673 String nodeId = aMultiObject.getField(Node.NODE_JOIN,
1674 Node.NODE_ID);
1675 alreadySelected.put(nodeId, aMultiObject);
1676 }
1677
1678 return alreadySelected;
1679 }
1680
1681 /***
1682 * Updates the specified node relation to reflect the new values specified
1683 * in the targetNodeIds.
1684 *
1685 * @param targetNodeType String
1686 * @param relationType String
1687 * @param targetNodeIds String[]
1688 * @throws DBException
1689 */
1690 public void updateNodeRelations(String targetNodeType, String relationType, String[] targetNodeIds) throws
1691 DBException {
1692
1693 List added = new ArrayList();
1694 List oldRemainingList = new ArrayList();
1695 Map toRemoveHash = getRelatedNodesHash(relationType,
1696 targetNodeType, oldRemainingList);
1697
1698
1699
1700
1701 for (int k = 0; (targetNodeIds != null) && (k < targetNodeIds.length); k++) {
1702 String nodeId = targetNodeIds[k];
1703 if (toRemoveHash.get(nodeId) != null) {
1704
1705
1706
1707 toRemoveHash.remove(nodeId);
1708
1709 continue;
1710 }
1711
1712
1713 Relation relation = new Relation(RequestRegistry.getUser());
1714 relation.setField(Relation.RELATION_SRC, this.getNodeId());
1715 relation.setField(Relation.RELATION_DEST, nodeId);
1716 relation.setField(Relation.RELATION_TYPE, relationType);
1717 relation.setOrder(oldRemainingList.size() + 1 + added.size());
1718 relation.add();
1719
1720 added.add(relation);
1721 }
1722
1723
1724
1725 for (Iterator enumeration = toRemoveHash.values().iterator();
1726 enumeration.hasNext();) {
1727 MultiDBObject join = (MultiDBObject) enumeration.next();
1728 String destId = join.getField(Node.RELATION_JOIN,
1729 Relation.RELATION_DEST);
1730 Relation relation = new Relation();
1731 relation.setField(Relation.RELATION_SRC, this.getNodeId());
1732 relation.setField(Relation.RELATION_DEST, destId);
1733 relation.setField(Relation.RELATION_TYPE, relationType);
1734 relation.delete();
1735 }
1736 }
1737
1738 /***
1739 * get all nodes that have this node as 'src' for some strong relation
1740 *
1741 * @return array, never null
1742 */
1743 public Node[] getStronglyRelatedNodes() throws DBException {
1744 MultiDBObject joinObject = new MultiDBObject();
1745 joinObject.setDataContext(getDataContext());
1746
1747
1748 joinObject.addDBObj(Relation.class.getName(), RELATION_JOIN);
1749
1750
1751 joinObject.addDBObj(Node.class.getName(), NODE_JOIN);
1752
1753
1754 joinObject.addDBObj(RelationType.class.getName(), RELATION_TYPE_JOIN);
1755
1756
1757 joinObject.setForeignKey(RELATION_JOIN, Relation.RELATION_DEST, NODE_JOIN, Node.NODE_ID);
1758 joinObject.setField(RELATION_JOIN, Relation.RELATION_SRC, getNodeId());
1759
1760
1761 joinObject.setForeignKey(RELATION_TYPE_JOIN, RelationType.RELATION_TYPE_NAME, RELATION_JOIN, Relation.RELATION_TYPE);
1762 joinObject.setField(RELATION_TYPE_JOIN, RelationType.RELATION_STRENGTH, RelationType.STRONG_RELATION);
1763
1764
1765 List list = joinObject.searchAndRetrieveList(Relation.RELATION_ORDER);
1766
1767 ArrayList resultList = new ArrayList();
1768
1769 for (Iterator iterator = list.iterator(); iterator.hasNext();) {
1770 MultiDBObject aMultiObject = (MultiDBObject) iterator.next();
1771 Node node = (Node) aMultiObject.getDBObject(NODE_JOIN);
1772 resultList.add(node);
1773 }
1774
1775 return (Node[]) resultList.toArray(new Node[resultList.size()]);
1776 }
1777 }