View Javadoc

1   /* ===================================================================
2    * Copyright 2002-04 SRI International.
3    * Released under the MOZILLA PUBLIC LICENSE Version 1.1
4    * which can be obtained at http://www.mozilla.org/MPL/MPL-1.1.html
5    * This software is distributed on an "AS IS"
6    * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.
7    * See the License for the specific language governing rights and
8    * limitations under the License.
9    * =================================================================== */
10  package com.sri.emo.dbobj;
11  
12  import com.jcorporate.expresso.core.controller.ControllerRequest;
13  import com.jcorporate.expresso.core.controller.Transition;
14  import com.jcorporate.expresso.core.db.DBConnection;
15  import com.jcorporate.expresso.core.db.DBException;
16  import com.jcorporate.expresso.core.dbobj.DBField;
17  import com.jcorporate.expresso.core.dbobj.MultiDBObject;
18  import com.jcorporate.expresso.core.dbobj.SecuredDBObject;
19  import com.jcorporate.expresso.core.misc.DateTime;
20  import com.jcorporate.expresso.core.security.ReadOnlyUser;
21  import com.sri.emo.controller.NodeAction;
22  import com.sri.emo.dbobj.model_tree.ModelVisitable;
23  import com.sri.emo.dbobj.model_tree.ModelVisitor;
24  
25  import java.util.Iterator;
26  import java.util.List;
27  
28  /***
29   * Encapsulate a linkage between two nodes.
30   * <p/>
31   * Relation should NOT be subclassed from RowSecuredDBObject
32   * because a relationship can be established in either direction,
33   * by either owning party, and if the source or destination node is removed,
34   * the relationship should be removed.
35   * In other words, the relationship should not be owned by anyone,
36   * so that it may be removed by either "owning" party.
37   *
38   * @author larry hamel
39   */
40  public class Relation extends SecuredDBObject implements Comparable, ModelVisitable, IViewable {
41      /***
42  	 * 
43  	 */
44  	private static final long serialVersionUID = 1L;
45  	public static final String RELATION_SRC = "RELATION_SRC";
46      public static final String RELATION_DEST = "RELATION_DEST";
47      public static final String RELATION_TYPE = "RELATION_TYPE";
48      public static final String RELATION_ANNOTATION = "RELATION_ANNOT";
49      public static final String RELATION_OWNER = "RELATION_OWNER";
50      public static final String RELATION_CREATED = "RELATION_CREATED";
51      public static final String RELATION_MODIFIED = "RELATION_MODIFIED";
52      public static final String RELATION_ORDER = "RELATION_ORDER";
53  
54      /***
55       * Default constructor for <code>Relation</code>
56       * creates a new object of this type with no connection
57       * yet allocated.
58       *
59       * @throws DBException If the new object cannot be
60       *                     created
61       */
62      public Relation() throws DBException {
63      }
64  
65      /***
66       * Constructor
67       *
68       * @param theConnection Database connection to
69       *                      communicate with the database
70       * @throws DBException If the new object cannot be
71       *                     created
72       */
73      public Relation(DBConnection theConnection) throws DBException {
74          super(theConnection);
75      }
76  
77      /***
78       * Constructor
79       *
80       * @param theConnection DBConnection to be used to
81       *                      communicate with the database
82       * @param theUser       User name attempting to access the
83       *                      object
84       * @throws DBException If the user cannot access this
85       *                     object or the object cannot be initialized
86       */
87      public Relation(DBConnection theConnection, int theUser) throws DBException {
88          super(theConnection, theUser);
89      }
90  
91      /***
92       * Constructor.
93       *
94       * @param request ControllerRequest
95       * @throws DBException
96       * @deprecated Use RequestRegistry to propage these parameters instead.
97       */
98      public Relation(final ControllerRequest request) throws DBException {
99          super(request);
100     }
101 
102     /***
103      * Constructor for security setup.
104      *
105      * @param userSecurity ReadOnlyUser security context.
106      * @throws DBException upon construction error.
107      */
108     public Relation(final ReadOnlyUser userSecurity) throws DBException {
109         super(userSecurity);
110     }
111 
112     public Relation(final SecuredDBObject obj) throws DBException {
113         super();
114         setRequestingUser(obj.getRequestingUser());
115         setDataContext(obj.getDataContext());
116     }
117 
118     /***
119      * Defines the database table name and fields for this DB object
120      *
121      * @throws DBException if the operation cannot be performed
122      */
123     protected synchronized void setupFields() throws DBException {
124         setTargetTable("relation");
125         setDescription("Relation");
126         addField(RELATION_SRC, "int", 0, false, "Node of relation source");
127         addField(RELATION_DEST, "int", 0, false, "Node of relation destination");
128         addField(RELATION_TYPE, "varchar", 255, false, "Type of relation");
129         addField(RELATION_OWNER, "int", 0, false, "Author");
130         addField(RELATION_ANNOTATION, "longvarchar", 0, true, "Annotation");
131         addField(RELATION_CREATED, "datetime", 0, false, "Created");
132         addField(RELATION_MODIFIED, "datetime", 0, false, "Modified Timestamp");
133         addField(RELATION_ORDER, DBField.INT_TYPE, 0, true,
134                 "Order number of related item, in list of peers");
135 
136         // PK is combination of src,dest,type
137         addKey(RELATION_SRC);
138         addKey(RELATION_DEST);
139         addKey(RELATION_TYPE);
140 
141         addIndex("relat_src_idx", RELATION_SRC, false);
142         addIndex("relat_dest_idx", RELATION_DEST, false);
143         addIndex("order_idx", RELATION_ORDER, false);
144     }
145     /* setupFields() */
146 
147     /***
148      * Deep copy fields.
149      *
150      * @param cloneNodeId the id of the node being cloned
151      * @param fieldname   within this Relation object, the name of the Relation field (RELATION_SRC or RELATION_DEST)
152      *                    which the cloned node occupies; in other words, clone the other field, replace this field
153      * @return a cloned relation.
154      * @throws DBException upon database error.
155      */
156     public Relation clone(String cloneNodeId, String fieldname) throws DBException {
157         return clone(cloneNodeId, fieldname, getRelationTypeName());
158     }
159 
160     /***
161      * Deep copy fields.
162      *
163      * @param cloneNodeId the id of the node being cloned
164      * @param fieldname   within this Relation object, the name of the Relation field (RELATION_SRC or RELATION_DEST)
165      *                    which the cloned node occupies; in other words, clone the other field, replace this field
166      * @param relType     The relationship type.
167      * @return a cloned relation.
168      * @throws DBException upon database error.
169      */
170     public Relation clone(String cloneNodeId, String fieldname, String relType) throws DBException {
171         // make copy by retrieving "this" again
172         Relation clone = new Relation(this);
173         clone.setRelationTypeName(getRelationTypeName());
174         clone.setSrcId(getSrcId());
175         clone.setDestId(getDestId());
176         clone.retrieve();
177 
178         // sanity check for some uses of this routine: copies that are occuring outside of clone context
179         //        if ( relNodeType != null ) {
180         //
181         //            // RELATION_SRC mean src ID will be changed to our cloneNodeId, so it
182         //            // the src node should be the same as our part's nodeType
183         //            if ( RELATION_SRC.equals(fieldname)) {
184         //                if (!getSrcNode().getNodeType().equals(relNodeType)) {
185         //                    throw new DBException("cloning relation fails because src node " + getSrcId() + " has type: "
186         //                            + getSrcNode().getNodeType()
187         //                            + " while expecting src node of type: " + relNodeType);
188         //                }
189         //            } else {
190         //                if (!getDestNode().getNodeType().equals(relNodeType)) {
191         //                    throw new DBException("cloning relation fails because dest node has type: "
192         //                            + getDestNode().getNodeType()
193         //                            + " while expecting dest node of type: " + relNodeType);
194         //                }
195         //            }
196         //        }
197         clone.setRelationTypeName(relType);
198         clone.setField(fieldname, cloneNodeId);
199         clone.addIfNeeded(); // no problem if relation already exists
200 
201         return clone;
202     }
203 
204     public void setSrcId(String id) throws DBException {
205         setField(RELATION_SRC, id);
206     }
207 
208     public void setDestId(String id) throws DBException {
209         setField(RELATION_DEST, id);
210     }
211 
212     public String getRelationTypeName() throws DBException {
213         return getField(RELATION_TYPE);
214     }
215 
216     public void setRelationTypeName(String relationType) throws DBException {
217         setField(RELATION_TYPE, relationType);
218     }
219 
220     public String getDestId() throws DBException {
221         return getField(RELATION_DEST);
222     }
223 
224     public String getSrcId() throws DBException {
225         return getField(RELATION_SRC);
226     }
227 
228     public Node getSrcNode() throws DBException {
229         Node node = new Node(this);
230         node.setNodeId(getSrcId());
231         node.retrieve();
232 
233         return node;
234     }
235 
236     /***
237      * handle inverse too
238      *
239      * @throws DBException If the user is not permitted to add
240      *                     or if the add fails
241      */
242     public synchronized void add() throws DBException {
243         String datestamp = DateTime.getDateTimeForDB();
244         setField(Relation.RELATION_CREATED, datestamp);
245         setField(Relation.RELATION_MODIFIED, datestamp);
246 
247         if (isFieldNull(Relation.RELATION_OWNER)) {
248             setField(Relation.RELATION_OWNER, getRequestingUid());
249         }
250 
251         if (isFieldNull(RELATION_ORDER)) {
252             // find out next order number
253             Node srcNode = getSrcNode();
254             Node destNode = getDestNode();
255             List list = srcNode.getRawRelated(getRelationTypeName(),
256                     destNode.getNodeType());
257             setOrder(list.size() + 1);
258         }
259 
260         super.add();
261 
262         if (isReflexive()) {
263             // write inverse relation too
264             Relation inverse = getInverseRel();
265 
266             // important: to avoid infinite loop of adding inverses,
267             // use find() here first
268             if (!inverse.find()) {
269                 inverse.add();
270             }
271         }
272     }
273     /* add() */
274 
275     /***
276      * normal update is disallowed, but update JUST for order is ok since
277      * we are not updating primary key that way
278      *
279      * @throws com.jcorporate.expresso.core.db.DBException
280      *          if the update to the database fails due to
281      *          a database error
282      */
283     public void updateOrder() throws DBException {
284         String datestamp = DateTime.getDateTimeForDB();
285         setField(Relation.RELATION_MODIFIED, datestamp);
286         super.update(false); // we could have blind update from multiDBObject
287     }
288     /*  update() */
289 
290     private Relation getInverseRel() throws DBException {
291         Relation inverse = new Relation();
292         inverse.setRequestingUser(getRequestingUser());
293         inverse.setDataContext(getDataContext());
294         inverse.setField(Relation.RELATION_SRC, getDestId());
295         inverse.setField(Relation.RELATION_DEST, getSrcId());
296         inverse.setField(Relation.RELATION_TYPE, getRelType().getInverseRel());
297 
298         return inverse;
299     }
300 
301     public boolean isReflexive() throws DBException {
302         boolean result = false;
303 
304         RelationType type = null;
305         try {
306             type = getRelType();
307             result = type.isReflexive();
308         } catch (DBException e) {
309             // do not throw
310             getLogger().error("Cannot determine reflexivity, assuming 'no': ", e);
311         }
312 
313         return result;
314     }
315 
316     private RelationType getRelType() throws DBException {
317         RelationType type = new RelationType();
318         type.setRequestingUser(getRequestingUser());
319         type.setDataContext(getDataContext());
320         type.setRelType(getRelationTypeName());
321         type.retrieve();
322 
323         return type;
324     }
325 
326     public RelationType getType() throws DBException {
327         return getRelType();
328     }
329 
330     /***
331      * Handle inverse too.
332      *
333      * @param deleteDetails true if detail fields should be deteled as well.
334      * @throws DBException if delete is not allowed for the current user
335      */
336     public synchronized void delete(boolean deleteDetails) throws DBException {
337         super.delete(deleteDetails);
338 
339         if (deleteDetails && isReflexive()) {
340             // del inverse relation too
341             Relation inverse = new Relation();
342             inverse.setRequestingUser(getRequestingUser());
343             inverse.setDataContext(getDataContext());
344 
345             // use reverse src/dest to capture inverse
346             inverse.setField(Relation.RELATION_SRC, getDestId());
347             inverse.setField(Relation.RELATION_DEST, getSrcId());
348             inverse.setField(Relation.RELATION_TYPE,
349                     getRelType().getInverseRel());
350 
351             // important: to avoid infinite loop of adding inverses,
352             // use find() here first
353             if (inverse.find()) {
354                 inverse.delete();
355             }
356         }
357 
358         // handle ordering in remaining, inside try/catch because we won't throw if an ID cannot be found
359         try {
360             Node srcNode = getSrcNode();
361             Node destNode = getDestNode();
362             List list = srcNode.getRawRelated(getRelationTypeName(),
363                     destNode.getNodeType());
364             int index = 1;
365 
366             for (Iterator iterator = list.iterator(); iterator.hasNext();) {
367                 MultiDBObject multi = (MultiDBObject) iterator.next();
368                 Relation rel = (Relation) multi.getDBObject(Node.RELATION_JOIN);
369 
370                 if (rel.getOrderInt() != index) {
371                     rel.setOrder(index);
372                     rel.updateOrder();
373                 }
374 
375                 index++;
376             }
377         } catch (DBException e) {
378             getLogger().error("Problem resetting order of related nodes when deleting a Relation; " +
379                     "can be ignored if a node ID is stale (i.e., node doesn't exist).",
380                     e); // complain in log, but do not abort deletion of relation--IDs in relation could be old
381         }
382     }
383     /* delete() */
384 
385     /***
386      * we override to handle case where relation type is manipulated.
387      * easier to delete and add;
388      *
389      * @throws DBException because this operation disallowed, in favor of delete() + add()
390      */
391     public synchronized void update() throws DBException {
392         throw new DBException(
393                 "do no use update with relations. instead, use delete + add, so that inverse relations are handled properly.");
394     }
395     /*  update() */
396 
397     public Node getDestNode() throws DBException {
398         Node node = new Node(this);
399         node.setNodeId(getDestId());
400         node.retrieve();
401 
402         return node;
403     }
404 
405     /***
406      * @param srcOrDest use either Relation.RELATION_DEST or Relation.RELATION_SRC to indicate
407      *                  what kind of containment relation (there are 2 relations that are inverses)
408      * @return true if this relation is a 'containership' type relation, and our node is
409      *         contained by this relation;
410      * @throws DBException upon database error.
411      */
412     public boolean isUpstreamLink(String srcOrDest) throws DBException {
413         if (Relation.RELATION_DEST.equals(srcOrDest)) {
414             return RelationType.DEST_IS_PART_OF_SRC.equals(getRelationTypeName());
415         } else {
416             return RelationType.DEST_CONTAINS_SRC.equals(getRelationTypeName());
417         }
418     }
419 
420     /***
421      * Compares this object with the specified object for order.  Returns a
422      * negative integer, zero, or a positive integer as this object is less
423      * than, equal to, or greater than the specified object.<p>
424      * <p/>
425      * In the foregoing description, the notation
426      * <tt>sgn(</tt><i>expression</i><tt>)</tt> designates the mathematical
427      * <i>signum</i> function, which is defined to return one of <tt>-1</tt>,
428      * <tt>0</tt>, or <tt>1</tt> according to whether the value of <i>expression</i>
429      * is negative, zero or positive.
430      * <p/>
431      * The implementor must ensure <tt>sgn(x.compareTo(y)) ==
432      * -sgn(y.compareTo(x))</tt> for all <tt>x</tt> and <tt>y</tt>.  (This
433      * implies that <tt>x.compareTo(y)</tt> must throw an exception iff
434      * <tt>y.compareTo(x)</tt> throws an exception.)<p>
435      * <p/>
436      * The implementor must also ensure that the relation is transitive:
437      * <tt>(x.compareTo(y)&gt;0 &amp;&amp; y.compareTo(z)&gt;0)</tt> implies
438      * <tt>x.compareTo(z)&gt;0</tt>.<p>
439      * <p/>
440      * Finally, the implementer must ensure that <tt>x.compareTo(y)==0</tt>
441      * implies that <tt>sgn(x.compareTo(z)) == sgn(y.compareTo(z))</tt>, for
442      * all <tt>z</tt>.<p>
443      * <p/>
444      * It is strongly recommended, but <i>not</i> strictly required that
445      * <tt>(x.compareTo(y)==0) == (x.equals(y))</tt>.  Generally speaking, any
446      * class that implements the <tt>Comparable</tt> interface and violates
447      * this condition should clearly indicate this fact.  The recommended
448      * language is "Note: this class has a natural ordering that is
449      * inconsistent with equals."
450      *
451      * @param o the Object to be compared.
452      * @return a negative integer, zero, or a positive integer as this object
453      *         is less than, equal to, or greater than the specified object.
454      * @throws ClassCastException if the specified object's type prevents it
455      *                            from being compared to this Object.
456      */
457     public int compareTo(Object o) {
458         int result = 0;
459 
460         Relation rel = (Relation) o;
461 
462         try {
463             if (rel.isFieldNull(RELATION_ORDER) &&
464                     isFieldNull(RELATION_ORDER)) {
465                 // use src title
466                 result = getSrcNode().getNodeTitle().compareTo(rel.getSrcNode()
467                         .getNodeTitle());
468             } else {
469                 result = new Integer(getOrderInt()).compareTo(new Integer(rel.getOrderInt()));
470             }
471         } catch (DBException e) {
472             e.printStackTrace();
473         }
474 
475         return result;
476     }
477 
478     public int getOrderInt() throws DBException {
479         if (getField(RELATION_ORDER).length() == 0) {
480             return 0;
481         }
482 
483         return getFieldInt(RELATION_ORDER);
484     }
485 
486     public void setOrder(String order) throws DBException {
487         setOrder(Integer.parseInt(order));
488     }
489 
490     public void setOrder(int order) throws DBException {
491         if (order < 1) {
492             order = 1;
493         }
494 
495         setField(RELATION_ORDER, order);
496     }
497 
498     public String getOrder() throws DBException {
499         return getField(RELATION_ORDER);
500     }
501 
502     /***
503      * This version assumes that we're looking at the node that is the
504      * source node since we don't have enough information to choose otherwise.
505      *
506      * @return transtion for viewing, including label for name of object;
507      *         never null
508      * @throws DBException if src or dest IDs not found
509      * @todo Test me
510      */
511     public Transition getViewTrans() throws DBException {
512         Node sourceNode = this.getSrcNode();
513         Transition returnValue = new Transition("edit", "edit", NodeAction.class, NodeAction.PROMPT_PICKLIST_NODE);
514         returnValue.addParam(RELATION_TYPE, this.getRelType().getRelTypeName());
515         returnValue.addParam(Node.NODE_ID, sourceNode.getNodeId());
516         returnValue.addParam(Node.NODE_TYPE, getDestNode().getNodeType());
517 
518         return returnValue;
519     }
520 
521 
522     public void acceptVisitor(ModelVisitor visitor) {
523         visitor.visitRelation(this);
524     }
525 
526 
527     public boolean isStrong() throws DBException {
528         return getRelType().isStrong();
529     }
530 } // fini