1
2
3
4
5
6
7
8
9
10 package com.sri.emo.dbobj;
11
12 import com.jcorporate.expresso.core.controller.ControllerRequest;
13 import com.jcorporate.expresso.core.controller.ExpressoRequest;
14 import com.jcorporate.expresso.core.controller.Output;
15 import com.jcorporate.expresso.core.controller.Transition;
16 import com.jcorporate.expresso.core.dataobjects.BaseDataObject;
17 import com.jcorporate.expresso.core.dataobjects.DataFieldMetaData;
18 import com.jcorporate.expresso.core.db.DBConnection;
19 import com.jcorporate.expresso.core.db.DBException;
20 import com.jcorporate.expresso.core.dbobj.DBField;
21 import com.jcorporate.expresso.core.dbobj.RowSecuredDBObject;
22 import com.jcorporate.expresso.core.misc.DateTime;
23 import com.jcorporate.expresso.core.security.ReadOnlyUser;
24 import com.jcorporate.expresso.core.security.SuperUser;
25 import com.jcorporate.expresso.core.security.filters.AllowedHtmlPlusURLFilter;
26 import com.jcorporate.expresso.core.security.filters.Filter;
27 import com.jcorporate.expresso.core.security.filters.RawFilter;
28 import com.jcorporate.expresso.core.security.filters.XmlFilter;
29 import com.sri.emo.controller.AddNodeAction;
30 import com.sri.emo.controller.NodeAction;
31 import com.sri.emo.dbobj.model_tree.ModelVisitable;
32 import com.sri.emo.dbobj.model_tree.ModelVisitor;
33 import org.dom4j.DocumentFactory;
34 import org.dom4j.Element;
35
36 import java.util.Calendar;
37 import java.util.Map;
38
39
40 /***
41 * encapsulate a name-value pair which is associated with a Node object
42 *
43 * @author larry hamel
44 */
45 public class Attribute extends RowSecuredDBObject implements ModelVisitable, IViewable {
46 /***
47 *
48 */
49 private static final long serialVersionUID = 1L;
50 /***
51 * incrementing PK
52 */
53 public static final String ATTRIBUTE_ID = "ATTRIBUTE_ID";
54 public static final String ATTRIBUTE_TAG = "ATTRIBUTE";
55 public static final String NODE_ID = Node.NODE_ID;
56
57 /***
58 * Field NODE_TYPE breaks normalization by storing data in two places,
59 * but is convenient for finding
60 * all attributes for a given node type;
61 *
62 * @todo remove this abomination; use join where only nodetype is used for searching
63 */
64 public static final String NODE_TYPE = Node.NODE_TYPE;
65
66 /***
67 * Name is internal string, type of attribute (would be PK with nodeId
68 * if we didn't save multiple attributes of same type per node ).
69 */
70 public static final String ATTRIBUTE_TYPE = "ATTRIBUTE_TYPE";
71 public static final String ATTRIBUTE_VALUE = "ATTRIBUTE_VALUE";
72 public static final String ATTRIBUTE_CREATED = "ATTRIBUTE_CREATED";
73 public static final String ATTRIBUTE_MODIFIED = "ATTRIBUTE_MODIFIED";
74 public static final String ATTRIBUTE_COMMENT = "ATTRIBUTE_COMMENT";
75 public static final String ATTRIBUTE_PICKLIST_DISPLAY = "ATTRIB_PLIST_DISP";
76 public static final String ATTRIBUTE_ORDER = "ATTRIBUTE_ORDER";
77
78 /***
79 * For now, attribute display name is stored in
80 * subclasses of PartDescriptor, as implemented in PartDescriptor.getParts().
81 */
82 public static final String ATTRIBUTE_DISPLAY_NAME = "ATTRIBUTE_DISPLAY_NAME";
83
84
85
86
87 public static final String ATTRIBUTE_VALUE_PREFIX = "ATTR_VALUE_";
88 public static final String ATTRIBUTE_COMMENT_PREFIX = "ATTR_COMMENT_";
89 public static final String ATTRIBUTE_ID_PREFIX = "ATTR_ID_";
90
91 /***
92 * Attribute id is used to discern between multiple attributes with the
93 * same name for the same owner node. before an attribute is added to the
94 * node, this placeholder value is used to indicate that the attribute is
95 * new--unsaved as yet.
96 */
97 public static final String ATTRIBUTE_ID_UNKNOWN = "-1";
98 public static final String ATTRIBUTE_DESCRIPTION = "Attribute";
99 public static final String CUSTOM_ATTRIBUTE = "CUSTOM_ATTRIBUTE";
100
101
102 /***
103 * Field that defined the value for getPart() explicitly, potentially
104 * saving database lookups to retrieve it.
105 */
106 private Part explicitPart = null;
107 public static final String TABLE_NAME = "attribute";
108
109 /***
110 * Default constructor for <code>Attribute</code>
111 * creates a new object of this type with no connection
112 * yet allocated.
113 *
114 * @throws DBException If the new object cannot be
115 * created
116 */
117 public Attribute() throws DBException {
118 }
119
120 /***
121 * Constructor
122 *
123 * @param theConnection DBConnection to be used to
124 * communicate with the database
125 * @param theUser User name attempting to access the
126 * object
127 * @throws DBException If the user cannot access this
128 * object or the object cannot be initialized
129 */
130 public Attribute(DBConnection theConnection, int theUser)
131 throws DBException {
132 super(theConnection, theUser);
133 }
134
135 public Attribute(String attribId)
136 throws DBException {
137 if (attribId != null) {
138 setAttributeId(attribId);
139 }
140 }
141
142 /***
143 * attribute for this parent
144 */
145 public Attribute(Node parent) throws DBException {
146 super(parent.getRequestingUser());
147 setDBName(parent.getDataContext());
148 setParentNodeType(parent.getNodeType());
149 setParentNodeId(parent.getNodeId());
150 }
151
152
153 /***
154 * Constructor
155 *
156 * @param theUser User name attempting to access the object
157 * @throws DBException If the user cannot access this
158 * object or the object cannot be initialized
159 */
160 public Attribute(ReadOnlyUser theUser) throws DBException {
161 super(theUser);
162 }
163
164 /***
165 * Defines the database table name and fields for this DB object
166 *
167 * @throws DBException if the operation cannot be performed
168 */
169 protected synchronized void setupFields() throws DBException {
170 setTargetTable(TABLE_NAME);
171 setDescription(ATTRIBUTE_DESCRIPTION);
172 addField(NODE_ID, "int", 0, false, "foreign key to node table");
173
174 /*** field NODE_TYPE breaks normal form, but is convenient for finding
175 all attributes for a given node type; since node type
176 is never expected to change after node creation, this seems reasonable
177 */
178 addField(NODE_TYPE, DBField.VARCHAR_TYPE, 255, false, "Node Type");
179 addField(ATTRIBUTE_TYPE, DBField.VARCHAR_TYPE, 255, false, "Name");
180 addField(ATTRIBUTE_VALUE, DBField.LONGVARCHAR_TYPE, 0, true, "Value");
181
182 DataFieldMetaData fieldMeta = getMetaData().getFieldMetadata(ATTRIBUTE_VALUE);
183 ((DBField) fieldMeta).setFilterClass(AllowedHtmlPlusURLFilter.class);
184
185 addField(ATTRIBUTE_CREATED, DBField.DATETIME_TYPE, 0, false, "Created");
186 addField(ATTRIBUTE_MODIFIED, DBField.DATETIME_TYPE, 0, false,
187 "Modified Timestamp");
188
189 addField(ATTRIBUTE_COMMENT, DBField.LONGVARCHAR_TYPE, 0, true, "Comment");
190 fieldMeta = getMetaData().getFieldMetadata(ATTRIBUTE_COMMENT);
191 ((DBField) fieldMeta).setFilterClass(AllowedHtmlPlusURLFilter.class);
192
193 addField(ATTRIBUTE_ORDER, DBField.INT_TYPE, 0, true, "Order");
194 addField(ATTRIBUTE_ID, DBField.AUTOINC_TYPE, 0, false, "auto-inc ID");
195
196
197 addVirtualField(ATTRIBUTE_PICKLIST_DISPLAY, DBField.VARCHAR_TYPE, 253,
198 ATTRIBUTE_VALUE, "Display value for picklist");
199
200
201
202 addKey(ATTRIBUTE_ID);
203
204
205 addIndex("attrib_nodeid_attrName_idx", NODE_ID + "," + ATTRIBUTE_TYPE,
206 false);
207 addIndex("attrib_nodeid__idx", NODE_ID, false);
208 addIndex("attrib_order_idx", ATTRIBUTE_ORDER, false);
209
210
211
212
213 addDetail(MatrixCell.class.getName(), ATTRIBUTE_ID, MatrixCell.ATTRIB_ID);
214 }
215
216 /***
217 * Extends the normal getField method to handle virtual fields.
218 * Note that OWNER_USERNAME requires that OWNER_ID is already retrieved and
219 * IMAGE_FILE_PATH requires IMAGE_FILE_ID.
220 *
221 * @param fieldName Name of the database field for which a value set is
222 * requested
223 * @return The value for the field
224 * @throws DBException If the operation cannot be performed
225 * @todo This code only works with Picklist_single_allowed cardinality.
226 * It doesn't handle multiple attributes or free-text attributes.
227 */
228 public synchronized String getField(String fieldName)
229 throws DBException {
230 String result = null;
231
232 if (ATTRIBUTE_PICKLIST_DISPLAY.equals(fieldName)) {
233 String val = getAttribValue();
234 try {
235 PickList listItem = new PickList();
236 listItem.setField(PickList.LIST_ID, getAttribValue());
237
238 if (listItem.find()) {
239 result = listItem.getField(PickList.DISPLAY_IN_PICKLIST);
240 } else {
241 getLogger().error("Can't find picklist item with id: " +
242 getAttribValue());
243 result = "CANNOT FIND PICKLIST ITEM WITH ID=" +
244 getAttribValue();
245 }
246
247 } catch (NumberFormatException ex) {
248
249
250 getLogger().info("Not an integer value: " + val);
251 return "";
252 }
253 } else {
254 result = super.getField(fieldName);
255 }
256
257 return result;
258 }
259
260 public String getAttribId() throws DBException {
261 return getField(ATTRIBUTE_ID);
262 }
263
264 public String getAttribValue() throws DBException {
265 return getField(ATTRIBUTE_VALUE);
266 }
267
268 public void setAttributeId(String id) throws DBException {
269 setField(ATTRIBUTE_ID, id);
270 }
271
272 public String getParentNodeId() throws DBException {
273 return getField(NODE_ID);
274 }
275
276 /***
277 * @return node which is parent of this attribute, or throw exception if not found.
278 * @throws DBException upon database access error.
279 */
280 public Node getParentNode() throws DBException {
281 if (getParentNodeId().length() == 0) {
282 throw new DBException("cannot find parent node because attribute lacks parentNodeId info.");
283 }
284
285 Node node = new Node(this);
286 node.setDBName(getDataContext());
287 node.setNodeId(getParentNodeId());
288 node.setRequestingUser(getRequestingUser());
289 node.retrieve();
290
291 return node;
292 }
293
294 public String getOrder() throws DBException {
295 return getField(ATTRIBUTE_ORDER);
296 }
297
298 public String getAttribComment() throws DBException {
299 return getField(ATTRIBUTE_COMMENT);
300 }
301
302 public void setAttributeComment(String comment) throws DBException {
303 setField(ATTRIBUTE_COMMENT, comment);
304 }
305
306 public String getAttribType() throws DBException {
307 return getField(ATTRIBUTE_TYPE);
308 }
309
310 /***
311 * note: new nodes need BOTH id and type from parent
312 *
313 * @see #setParentNode(Node)
314 */
315 public void setParentNodeId(String parentId) throws DBException {
316 setField(NODE_ID, parentId);
317 }
318
319 /***
320 * note: new nodes need BOTH id and type from parent
321 *
322 * @see #setParentNode(Node)
323 */
324 public void setParentNodeType(String parentType) throws DBException {
325 setField(Attribute.NODE_TYPE, parentType);
326 }
327
328 public void setAttributeType(String attribType) throws DBException {
329 setField(Attribute.ATTRIBUTE_TYPE, attribType);
330 }
331
332 public String getAttribCreated() throws DBException {
333 return getField(ATTRIBUTE_CREATED);
334 }
335
336 public String getAttribModified() throws DBException {
337 return getField(ATTRIBUTE_MODIFIED);
338 }
339
340 public IPartHandler getCustomHandler() throws DBException {
341 AbstractAttributeHandler result = null;
342 Part part = null;
343 if (getParentNodeId().length() > 0) {
344 try {
345 if (getParentNode() != null) {
346 part = getPart();
347 }
348 } catch (Exception e) {
349 getLogger().error("Cannot get parent node or part for attribute id=" + getAttribId());
350 }
351 }
352
353 if (part != null) {
354 result = (AbstractAttributeHandler) part.getCustomHandler();
355 if (result != null) {
356 result.setParentAttrib(this);
357 }
358 } else {
359 getLogger().error("cannot find part with parent type/attrib type: " +
360 getParentNode().getNodeType() + "/" + getAttribType());
361 }
362
363 return result;
364 }
365
366 /***
367 * deep copy fields to new attribute into attribute which
368 * attaches to node with given ID; expects fully saved Attribute (with ID) as source.
369 * will treat cardinality=single attributes with care to make sure there
370 * is only one in final node (e.g., if constructor already added a "type" attrib).
371 * <p/>
372 * side-effect: deletes this node if it is found to be impossibly valued (i.e., no part of this type w/ this parent)
373 *
374 * @param cloneNodeId
375 * @return cloned attribute, ALREADY SAVED and thereby holding a valid ID; null if this node is impossible to have relational integrity
376 * @throws DBException upon database access error.
377 */
378 public Attribute clone(String cloneNodeId) throws DBException {
379 return clone(cloneNodeId, getAttribType());
380 }
381
382 /***
383 * deep copy fields to new attribute into attribute which
384 * attaches to node with given ID; expects fully saved Attribute (with ID) as source.
385 * will treat cardinality=single attributes with care to make sure there
386 * is only one in final node (e.g., if constructor already added a "type" attrib).
387 * <p/>
388 * side-effect: deletes this node if it is found to be impossibly valued (i.e., no part of this type w/ this parent)
389 *
390 * @param cloneNodeId
391 * @return cloned attribute, ALREADY SAVED and thereby holding a valid ID; null if this node is impossible to have relational integrity
392 * @throws DBException upon database access error.
393 */
394 public Attribute clone(String cloneNodeId, String destAttribType)
395 throws DBException {
396
397 Attribute clone = null;
398 Attribute orig = new Attribute(getRequestingUser());
399 orig.setDBName(getDataContext());
400 orig.setAttributeId(getAttribId());
401 orig.retrieve();
402
403 Part part = getPart();
404 if (part == null) {
405
406 getLogger().error("No part found for attribute with type: " + getAttribType()
407 + " which has parent node of type: " + getParentNode().getNodeType()
408 + " so deleting this attribute.");
409 this.delete();
410 return null;
411 }
412
413 Node destinationNode = new Node(this);
414 destinationNode.setNodeId(cloneNodeId);
415 destinationNode.retrieve();
416
417
418
419 boolean exists = false;
420
421 if (part.isSingleValued()) {
422
423 Attribute[] existing = destinationNode.getAttributes(destAttribType);
424
425 if (existing.length > 0) {
426
427 clone = existing[0];
428 clone.setAttributeValue(getAttribValue());
429 clone.setAttributeComment(getAttribComment());
430 clone.update();
431 exists = true;
432 }
433 }
434
435 if (!exists) {
436 clone = orig;
437 clone.setParentNodeId(cloneNodeId);
438 clone.setParentNodeType(destinationNode.getNodeType());
439 clone.setAttributeType(destAttribType);
440 clone.setAttributeId("0");
441
442
443 clone.add();
444 }
445
446 IPartHandler handler = getCustomHandler();
447
448 if (handler != null) {
449
450 handler.clone(this, clone);
451 }
452
453 return clone;
454 }
455
456 /***
457 * We override to set dates and provide default ordering if nec.
458 *
459 * @throws DBException upon database access error.
460 */
461 public synchronized void add() throws DBException {
462 String datestamp = DateTime.getDateTimeForDB();
463 String created = getAttribCreated();
464 setField(Attribute.ATTRIBUTE_CREATED, datestamp);
465
466
467 if (created.equals(getAttribModified())) {
468 setField(Attribute.ATTRIBUTE_MODIFIED, datestamp);
469 } else {
470
471 Calendar cal = Calendar.getInstance();
472 cal.add(Calendar.SECOND, 1);
473 datestamp = DateTime.getDateTimeForDB(cal.getTime());
474 setField(Attribute.ATTRIBUTE_MODIFIED, datestamp);
475 }
476 if (isFieldNull(ATTRIBUTE_ORDER)) {
477
478 Attribute[] attribs = getParentNode().getAttributes(getAttribType());
479 setOrder(attribs.length + 1);
480 }
481
482 super.add();
483 }
484
485 /***
486 * OVERRIDE to use parent node permissions
487 *
488 * @param requestedFunction The code of the requested function. The codes are:
489 * <ol><li>A: Add<li>
490 * <li>S: Search<li>
491 * <li>U: Update<li>
492 * <li>D: Delete<li>
493 * </ol>
494 * @throws com.jcorporate.expresso.core.db.DBException
495 * If the requested operation is not permitted to this user
496 * @throws SecurityException if the user is not allowed access to the object.
497 */
498 public void isAllowed(String requestedFunction) throws SecurityException, DBException {
499 if (getRequestingUser() == SuperUser.INSTANCE) {
500 return;
501 }
502
503 if (getRequestingUser().isAdmin()) {
504 return;
505 }
506
507
508 try {
509
510 Node node = getParentNode();
511 node.isAllowed(requestedFunction);
512 } catch (DBException e) {
513
514 super.isAllowed(requestedFunction);
515 }
516 }
517
518 /***
519 * We override to set dates and fix ordering if nec.
520 *
521 * @throws DBException upon database access error.
522 */
523 public synchronized void update() throws DBException {
524 String datestamp = DateTime.getDateTimeForDB();
525 setField(Attribute.ATTRIBUTE_MODIFIED, datestamp);
526
527 boolean orderChanged = getDataField(ATTRIBUTE_ORDER).isChanged();
528 super.update();
529
530
531 if (orderChanged) {
532 reorderAttribs();
533 }
534 }
535
536 /***
537 * We override to provide call to handler, if any, and reorder remaining, if nec.
538 *
539 * @throws DBException upon database access error.
540 */
541 public synchronized void delete(boolean deleteDetails)
542 throws DBException {
543 super.delete(deleteDetails);
544
545 if (deleteDetails) {
546 IPartHandler handler = getCustomHandler();
547
548 if (handler != null) {
549
550 handler.delete(this);
551 }
552 }
553
554 reorderAttribs();
555 }
556
557 private void reorderAttribs() throws DBException {
558 Attribute[] attribs = getParentNode().getAttributes(getAttribType());
559 int index = 1;
560
561 for (int i = 0; i < attribs.length; i++) {
562 Attribute attrib = attribs[i];
563
564 if (attrib.getOrderInt() != index) {
565 attrib.setOrder(index);
566 attrib.updateOrder();
567 }
568
569 index++;
570 }
571 }
572
573 /***
574 * Update without triggering reordering; useful for 'transaction' where order of part A should be set before manipulating order of part B,
575 * as opposed to a deletion or otherwise, where reordering of all remaining is required.
576 *
577 * @throws DBException upon database access error.
578 */
579 public void updateOrder() throws DBException {
580 super.update();
581 }
582
583 public Element getXML(ExpressoRequest request, boolean addOrder)
584 throws DBException {
585 Filter old = setFilterClass(new XmlFilter());
586 Element attribElm = DocumentFactory.getInstance().createElement(getAttribType());
587 IPartHandler handler = getCustomHandler();
588 Part part = getPart();
589 attribElm.addAttribute(Part.PART_LABEL, part.getPartLabel());
590
591
592 attribElm.addAttribute(Attribute.ATTRIBUTE_ID, getAttribId());
593
594 if (getAttribComment().length() > 0) {
595 attribElm.addAttribute(Attribute.ATTRIBUTE_COMMENT,
596 getAttribComment());
597 }
598
599 if (addOrder) {
600 attribElm.addAttribute(ATTRIBUTE_ORDER, getOrder());
601 }
602
603 if (handler == null) {
604 attribElm.addAttribute(Attribute.ATTRIBUTE_VALUE, getAttribValue());
605
606
607 if (part.hasPicklist()) {
608 attribElm.addAttribute(PickList.DISPLAY_IN_PICKLIST,
609 PickList.getPickFast(getAttribValue(), getDataContext()));
610
611
612 }
613 } else {
614 handler.addXML(this, attribElm, (ControllerRequest) request);
615 }
616
617
618
619 setFilterClass(old);
620
621 return attribElm;
622 }
623
624 /***
625 * Retrieve the part why which this attribute belongs to.
626 *
627 * @return Part part, or possibly null if no part is found.
628 * @throws DBException upon error.
629 */
630 public Part getPart() throws DBException {
631 Part returnValue = this.getExplicitPart();
632 if (returnValue == null) {
633 return PartsFactory.getPart(getParentNode().getNodeType(),
634 getAttribType(), null);
635 } else {
636 return returnValue;
637 }
638
639 }
640
641 public void setAttributeValue(String value) throws DBException {
642 setField(Attribute.ATTRIBUTE_VALUE, value);
643 }
644
645 public void setOrder(String value) throws DBException {
646 setOrder(Integer.parseInt(value));
647 }
648
649 public void setOrder(int order) throws DBException {
650 if (order < 1) {
651 order = 1;
652 }
653
654 setField(Attribute.ATTRIBUTE_ORDER, order);
655 }
656
657 public void setParentNode(Node node) throws DBException {
658 setParentNodeId(node.getNodeId());
659 setParentNodeType(node.getNodeType());
660 }
661
662 public String getAttribCommentRaw() throws DBException {
663 Filter old = setFilterClass(new RawFilter());
664 String raw = getAttribComment();
665 setFilterClass(old);
666
667 return raw;
668 }
669
670 public String getAttribValueRaw() throws DBException {
671 Filter old = setFilterClass(new RawFilter());
672 String raw = getAttribValue();
673 setFilterClass(old);
674
675 return raw;
676 }
677
678 public boolean hasCustomHandler() throws DBException {
679 return getCustomHandler() != null;
680 }
681
682 public boolean hasPicklist() throws DBException {
683 return getPart().hasPicklist();
684 }
685
686 public int getOrderInt() throws DBException {
687 return getFieldInt(ATTRIBUTE_ORDER);
688 }
689
690 /***
691 * Provide a transition for viewing this object, suitable for creating an
692 * HTTP link.
693 *
694 * @return transition for viewing, including label for name of object;
695 * @throws DBException upon database access error.
696 */
697 public Transition getViewTrans() throws DBException {
698 Transition trans = null;
699
700 if (hasCustomHandler()) {
701 IPartHandler handler = getCustomHandler();
702 trans = handler.getViewTransition(IPartHandler.NO_PARAMETERS);
703 }
704
705
706 if (trans == null) {
707
708
709
710 String value = getAttribValueRaw();
711
712
713 Part part = getPart();
714 if (part.hasPicklist()) {
715 value = getField(Attribute.ATTRIBUTE_PICKLIST_DISPLAY);
716 }
717
718
719
720 if (BaseDataObject.STATUS_NEW == getStatus() || canRequesterWrite()) {
721 trans = new Transition(value, NodeAction.class, NodeAction.PROMPT_EDIT_ATTRIB);
722 trans.addParam(ATTRIBUTE_TYPE, getAttribType());
723 } else {
724 trans = new Transition(value, NodeAction.class, NodeAction.VIEW_SINGLE_ATTRIBUTE);
725 trans.addParam(ATTRIBUTE_ID, getAttribId());
726 NodeAction.addEmbeddedParameter(trans);
727 }
728 trans.addParam(Node.NODE_ID, getParentNodeId());
729 }
730 return trans;
731 }
732
733 /***
734 * Provide a transition for viewing this object, suitable for creating an
735 * HTTP link.
736 *
737 * @return transtion for viewing, including label for name of object;
738 * @throws DBException upon database access error.
739 */
740 public Transition getViewTrans(Map reqParams) throws DBException {
741 Transition trans = null;
742
743 if (hasCustomHandler()) {
744 IPartHandler handler = getCustomHandler();
745 trans = handler.getViewTransition(reqParams);
746 }
747
748
749 if (trans == null) {
750 trans = getViewTrans();
751 }
752 return trans;
753 }
754
755 /***
756 * Provide a transition for SPECIAL editing of this object, suitable for creating an
757 * HTTP link. Return null if no special editing transition
758 *
759 * @return transtion for editing, including label for name of object;
760 * @throws DBException upon database access error.
761 */
762 public Transition getEditTrans(Map reqParams) throws DBException {
763 Transition trans = null;
764
765 if (hasCustomHandler()) {
766 trans = getCustomHandler().getEditTransition(reqParams);
767 }
768
769 return trans;
770 }
771
772 /***
773 * Provide a transition for editing of this object, suitable for creating an
774 * HTTP link.
775 *
776 * @return transtion for editing, including label for name of object;
777 * @throws DBException upon database access error.
778 */
779 public Transition getEditTrans() throws DBException {
780 Transition trans = null;
781
782
783 if (hasCustomHandler()) {
784 trans = getCustomHandler().getEditTransition(IPartHandler.NO_PARAMETERS);
785 } else {
786 String value = getAttribValueRaw();
787
788
789 Part part = getPart();
790 if (part.hasPicklist()) {
791 value = getField(Attribute.ATTRIBUTE_PICKLIST_DISPLAY);
792 }
793
794 if (BaseDataObject.STATUS_NEW == getStatus() || canRequesterWrite()) {
795 trans = new Transition(value, NodeAction.class, NodeAction.PROMPT_EDIT_ATTRIB);
796 trans.addParam(ATTRIBUTE_TYPE, getAttribType());
797 } else {
798 throw new DBException("User does not have permission to edit attribute with ID=" + getAttribId());
799 }
800 }
801
802 return trans;
803 }
804
805 public void acceptVisitor(ModelVisitor visitor) {
806 visitor.visitAttribute(this);
807 }
808
809
810 public String getParentNodeType() throws DBException {
811 return getField(NODE_TYPE);
812 }
813
814 /***
815 * override to use parent node perms for attrib.
816 *
817 * @param requestedFunction code for function -- Add, Update, Delete, Search (read)
818 * @return true if this function is allowed for this requesting user
819 * @throws SecurityException (unchecked) if not allowed
820 * @throws com.jcorporate.expresso.core.db.DBException
821 * for other data-related errors.
822 */
823 public boolean isRowAllowed(String requestedFunction) throws DBException {
824 if (SuperUser.SYSTEM_UID == getRequestingUid() || getRequestingUser().isAdmin()) {
825 return true;
826 }
827
828 boolean result = false;
829
830 try {
831
832 Node node = getParentNode();
833 result = node.isRowAllowed(requestedFunction);
834 } catch (DBException e) {
835
836 result = super.isRowAllowed(requestedFunction);
837 }
838
839 return result;
840 }
841
842 /***
843 * Retrieve the view comment as an output.
844 *
845 * @param params map of ExpressoRequest params.
846 * @return Output the resulting comment.
847 * @throws DBException upon Output construction error.
848 */
849 public Output getViewComment(Map params) throws DBException {
850 Output result = null;
851 if (hasCustomHandler()) {
852 result = getCustomHandler().getViewComment(params);
853 }
854
855 if (result == null) {
856 result = new Output(Attribute.ATTRIBUTE_COMMENT, AddNodeAction.str(getAttribComment()));
857 }
858
859 return result;
860 }
861
862 /***
863 * Returns the value for an explicit Part if it has been set.
864 *
865 * @return Part or null if it has never been set by any sort of factory
866 * object.
867 */
868 public Part getExplicitPart() {
869 return explicitPart;
870 }
871
872 /***
873 * Sets the part for this attribute explicitly. While this is not
874 * necessary, it can yield performance gains if set by factory objects.
875 *
876 * @param explicitPart Part
877 */
878 public void setExplicitPart(Part explicitPart) {
879 this.explicitPart = explicitPart;
880 }
881
882 }