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.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
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
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
172 Relation clone = new Relation(this);
173 clone.setRelationTypeName(getRelationTypeName());
174 clone.setSrcId(getSrcId());
175 clone.setDestId(getDestId());
176 clone.retrieve();
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197 clone.setRelationTypeName(relType);
198 clone.setField(fieldname, cloneNodeId);
199 clone.addIfNeeded();
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
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
264 Relation inverse = getInverseRel();
265
266
267
268 if (!inverse.find()) {
269 inverse.add();
270 }
271 }
272 }
273
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);
287 }
288
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
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
341 Relation inverse = new Relation();
342 inverse.setRequestingUser(getRequestingUser());
343 inverse.setDataContext(getDataContext());
344
345
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
352
353 if (inverse.find()) {
354 inverse.delete();
355 }
356 }
357
358
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);
381 }
382 }
383
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
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)>0 && y.compareTo(z)>0)</tt> implies
438 * <tt>x.compareTo(z)>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
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 }