KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > objectstyle > cayenne > map > DbRelationship


1 /* ====================================================================
2  *
3  * The ObjectStyle Group Software License, version 1.1
4  * ObjectStyle Group - http://objectstyle.org/
5  *
6  * Copyright (c) 2002-2005, Andrei (Andrus) Adamchik and individual authors
7  * of the software. All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  * notice, this list of conditions and the following disclaimer.
15  *
16  * 2. Redistributions in binary form must reproduce the above copyright
17  * notice, this list of conditions and the following disclaimer in
18  * the documentation and/or other materials provided with the
19  * distribution.
20  *
21  * 3. The end-user documentation included with the redistribution, if any,
22  * must include the following acknowlegement:
23  * "This product includes software developed by independent contributors
24  * and hosted on ObjectStyle Group web site (http://objectstyle.org/)."
25  * Alternately, this acknowlegement may appear in the software itself,
26  * if and wherever such third-party acknowlegements normally appear.
27  *
28  * 4. The names "ObjectStyle Group" and "Cayenne" must not be used to endorse
29  * or promote products derived from this software without prior written
30  * permission. For written permission, email
31  * "andrus at objectstyle dot org".
32  *
33  * 5. Products derived from this software may not be called "ObjectStyle"
34  * or "Cayenne", nor may "ObjectStyle" or "Cayenne" appear in their
35  * names without prior written permission.
36  *
37  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40  * DISCLAIMED. IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
41  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48  * SUCH DAMAGE.
49  * ====================================================================
50  *
51  * This software consists of voluntary contributions made by many
52  * individuals and hosted on ObjectStyle Group web site. For more
53  * information on the ObjectStyle Group, please see
54  * <http://objectstyle.org/>.
55  */

56 package org.objectstyle.cayenne.map;
57
58 import java.util.ArrayList JavaDoc;
59 import java.util.Collection JavaDoc;
60 import java.util.Collections JavaDoc;
61 import java.util.HashMap JavaDoc;
62 import java.util.Iterator JavaDoc;
63 import java.util.List JavaDoc;
64 import java.util.Map JavaDoc;
65
66 import org.apache.commons.collections.CollectionUtils;
67 import org.apache.commons.collections.Transformer;
68 import org.objectstyle.cayenne.CayenneRuntimeException;
69 import org.objectstyle.cayenne.event.EventManager;
70 import org.objectstyle.cayenne.event.EventSubject;
71 import org.objectstyle.cayenne.map.event.RelationshipEvent;
72 import org.objectstyle.cayenne.util.Util;
73 import org.objectstyle.cayenne.util.XMLEncoder;
74
75 /**
76  * A DbRelationship is a descriptor of a database inter-table relationship based on one or
77  * more primary key/foreign key pairs.
78  *
79  * @author Misha Shengaout
80  * @author Andrei Adamchik
81  */

82 public class DbRelationship extends Relationship {
83
84     //DbRelationship events
85
public static final EventSubject PROPERTY_DID_CHANGE = EventSubject
86             .getSubject(DbRelationship.class, "PropertyDidChange");
87
88     // The columns through which the join is implemented.
89
protected List JavaDoc joins = new ArrayList JavaDoc();
90
91     // Is relationship from source to target points to dependent primary
92
// key (primary key column of destination table that is also a FK to the source
93
// column)
94
protected boolean toDependentPK;
95
96     public DbRelationship() {
97         super();
98     }
99
100     public DbRelationship(String JavaDoc name) {
101         super(name);
102     }
103
104     /**
105      * Prints itself as XML to the provided XMLEncoder.
106      *
107      * @since 1.1
108      */

109     public void encodeAsXML(XMLEncoder encoder) {
110         encoder.print("<db-relationship name=\"");
111         encoder.print(Util.encodeXmlAttribute(getName()));
112         encoder.print("\" source=\"");
113         encoder.print(getSourceEntity().getName());
114
115         if (getTargetEntityName() != null && getTargetEntity() != null) {
116             encoder.print("\" target=\"");
117             encoder.print(getTargetEntityName());
118         }
119
120         if (isToDependentPK() && isValidForDepPk()) {
121             encoder.print("\" toDependentPK=\"true");
122         }
123
124         encoder.print("\" toMany=\"");
125         encoder.print(isToMany());
126         encoder.println("\">");
127
128         encoder.indent(1);
129         encoder.print(getJoins());
130         encoder.indent(-1);
131
132         encoder.println("</db-relationship>");
133     }
134
135     /**
136      * Returns a target of this relationship. If relationship is not attached to a
137      * DbEntity, and DbEntity doesn't have a namcespace, and exception is thrown.
138      */

139     public Entity getTargetEntity() {
140         String JavaDoc targetName = getTargetEntityName();
141         if (targetName == null) {
142             return null;
143         }
144
145         return getNonNullNamespace().getDbEntity(targetName);
146     }
147
148     /**
149      * Returns a Collection of target attributes.
150      *
151      * @since 1.1
152      */

153     public Collection JavaDoc getTargetAttributes() {
154         if (joins.size() == 0) {
155             return Collections.EMPTY_LIST;
156         }
157
158         return CollectionUtils.collect(joins, JoinTransformers.targetExtractor);
159     }
160
161     /**
162      * Returns a Collection of source attributes.
163      *
164      * @since 1.1
165      */

166     public Collection JavaDoc getSourceAttributes() {
167         if (joins.size() == 0) {
168             return Collections.EMPTY_LIST;
169         }
170
171         return CollectionUtils.collect(joins, JoinTransformers.sourceExtractor);
172     }
173
174     /**
175      * Creates a new relationship with the same set of joins, but going in the opposite
176      * direction.
177      *
178      * @since 1.0.5
179      */

180     public DbRelationship createReverseRelationship() {
181         DbRelationship reverse = new DbRelationship();
182         reverse.setSourceEntity(getTargetEntity());
183         reverse.setTargetEntityName(getSourceEntity().getName());
184
185         // TODO: must set toDepPK correctly
186
// must set toMany correctly
187

188         reverse.setToMany(!toMany);
189
190         Iterator JavaDoc it = joins.iterator();
191         while (it.hasNext()) {
192             DbJoin join = (DbJoin) it.next();
193             DbJoin reverseJoin = join.createReverseJoin();
194             reverseJoin.setRelationship(reverse);
195             reverse.addJoin(reverseJoin);
196         }
197
198         return reverse;
199     }
200
201     /**
202      * Returns DbRelationship that is the opposite of this DbRelationship. This means a
203      * relationship from this target entity to this source entity with the same join
204      * semantics. Returns null if no such relationship exists.
205      */

206     public DbRelationship getReverseRelationship() {
207         Entity target = this.getTargetEntity();
208
209         if (target == null) {
210             return null;
211         }
212
213         Entity src = this.getSourceEntity();
214
215         // special case - relationship to self with no joins...
216
if (target == src && joins.size() == 0) {
217             return null;
218         }
219
220         TestJoin testJoin = new TestJoin(this);
221         Iterator JavaDoc it = target.getRelationships().iterator();
222         while (it.hasNext()) {
223             DbRelationship rel = (DbRelationship) it.next();
224             if (rel.getTargetEntity() != src)
225                 continue;
226
227             List JavaDoc otherJoins = rel.getJoins();
228             if (otherJoins.size() != joins.size()) {
229                 continue;
230             }
231
232             Iterator JavaDoc jit = otherJoins.iterator();
233             boolean joinsMatch = true;
234             while (jit.hasNext()) {
235                 DbJoin join = (DbJoin) jit.next();
236
237                 // flip join and try to find similar
238
testJoin.setSourceName(join.getTargetName());
239                 testJoin.setTargetName(join.getSourceName());
240                 if (!joins.contains(testJoin)) {
241                     joinsMatch = false;
242                     break;
243                 }
244             }
245
246             if (joinsMatch) {
247                 return rel;
248             }
249         }
250
251         return null;
252     }
253
254     /**
255      * Returns true if the relationship points to at least one of the PK columns of the
256      * target entity.
257      *
258      * @since 1.1
259      */

260     public boolean isToPK() {
261         Iterator JavaDoc it = getJoins().iterator();
262         while (it.hasNext()) {
263             DbJoin join = (DbJoin) it.next();
264             if (join.getTarget() == null) {
265                 return false;
266             }
267
268             if (join.getTarget().isPrimaryKey()) {
269                 return true;
270             }
271         }
272
273         return false;
274     }
275
276     /**
277      * Returns <code>true</code> if a method <code>isToDependentPK</code> of reverse
278      * relationship of this relationship returns <code>true</code>.
279      */

280     public boolean isToMasterPK() {
281         if (isToMany() || isToDependentPK()) {
282             return false;
283         }
284
285         DbRelationship revRel = getReverseRelationship();
286         return (revRel != null) ? revRel.isToDependentPK() : false;
287     }
288
289     /**
290      * Returns <code>true</code> if relationship from source to target points to
291      * dependent primary key. Dependent PK is a primary key column of the destination
292      * table that is also a FK to the source column.
293      */

294     public boolean isToDependentPK() {
295         return toDependentPK;
296     }
297
298     public void setToDependentPK(boolean flag) {
299         toDependentPK = flag;
300     }
301
302     /**
303      * @since 1.1
304      */

305     public boolean isValidForDepPk() {
306         Iterator JavaDoc it = getJoins().iterator();
307         // handle case with no joins
308
if (!it.hasNext()) {
309             return false;
310         }
311
312         while (it.hasNext()) {
313             DbJoin join = (DbJoin) it.next();
314             DbAttribute target = join.getTarget();
315             DbAttribute source = join.getSource();
316
317             if ((target != null && !target.isPrimaryKey())
318                     || (source != null && !source.isPrimaryKey())) {
319                 return false;
320             }
321         }
322
323         return true;
324     }
325
326     /**
327      * Returns a list of joins. List is returned by reference, so any modifications of the
328      * list will affect this relationship.
329      */

330     public List JavaDoc getJoins() {
331         return joins;
332     }
333
334     /**
335      * Adds a join.
336      *
337      * @since 1.1
338      */

339     public void addJoin(DbJoin join) {
340         if (join != null) {
341             joins.add(join);
342         }
343     }
344
345     public void removeJoin(DbJoin join) {
346         joins.remove(join);
347     }
348
349     public void removeAllJoins() {
350         joins.clear();
351     }
352
353     public void setJoins(Collection JavaDoc newJoins) {
354         this.removeAllJoins();
355
356         if (newJoins != null) {
357             joins.addAll(newJoins);
358         }
359     }
360
361     /**
362      * Creates a snapshot of primary key attributes of a target object of this
363      * relationship based on a snapshot of a source. Only "to-one" relationships are
364      * supported. Returns null if relationship does not point to an object. Throws
365      * CayenneRuntimeException if relationship is "to many" or if snapshot is missing id
366      * components.
367      */

368     public Map JavaDoc targetPkSnapshotWithSrcSnapshot(Map JavaDoc srcSnapshot) {
369
370         if (isToMany()) {
371             throw new CayenneRuntimeException(
372                     "Only 'to one' relationships support this method.");
373         }
374
375         Map JavaDoc idMap;
376
377         int numJoins = joins.size();
378         int foundNulls = 0;
379
380         // optimize for the most common single column join
381
if (numJoins == 1) {
382             DbJoin join = (DbJoin) joins.get(0);
383             Object JavaDoc val = srcSnapshot.get(join.getSourceName());
384             if (val == null) {
385                 foundNulls++;
386                 idMap = Collections.EMPTY_MAP;
387             }
388             else {
389                 idMap = Collections.singletonMap(join.getTargetName(), val);
390             }
391         }
392         // handle generic case: numJoins > 1
393
else {
394             idMap = new HashMap JavaDoc(numJoins * 2);
395             for (int i = 0; i < numJoins; i++) {
396                 DbJoin join = (DbJoin) joins.get(i);
397                 Object JavaDoc val = srcSnapshot.get(join.getSourceName());
398                 if (val == null) {
399                     foundNulls++;
400                 }
401                 else {
402                     idMap.put(join.getTargetName(), val);
403                 }
404             }
405         }
406
407         if (foundNulls == 0) {
408             return idMap;
409         }
410         else if (foundNulls == numJoins) {
411             return null;
412         }
413         else {
414             throw new CayenneRuntimeException("Some parts of FK are missing in snapshot,"
415                     + " relationship: "
416                     + this);
417         }
418     }
419
420     /**
421      * Common code to srcSnapshotWithTargetSnapshot. Both are functionally the same,
422      * except for the name, and whether they operate on a toMany or a toOne.
423      */

424     private Map JavaDoc srcSnapshotWithTargetSnapshot(Map JavaDoc targetSnapshot) {
425         int len = joins.size();
426
427         // optimize for the most common single column join
428
if (len == 1) {
429             DbJoin join = (DbJoin) joins.get(0);
430             Object JavaDoc val = targetSnapshot.get(join.getTargetName());
431             if (val == null) {
432                 throw new CayenneRuntimeException(
433                         "Some parts of FK are missing in snapshot, join: " + join);
434             }
435
436             return Collections.singletonMap(join.getSourceName(), val);
437
438         }
439
440         // general case
441
Map JavaDoc idMap = new HashMap JavaDoc(len * 2);
442         for (int i = 0; i < len; i++) {
443             DbJoin join = (DbJoin) joins.get(i);
444             Object JavaDoc val = targetSnapshot.get(join.getTargetName());
445             if (val == null) {
446                 throw new CayenneRuntimeException(
447                         "Some parts of FK are missing in snapshot, join: " + join);
448             }
449
450             idMap.put(join.getSourceName(), val);
451         }
452
453         return idMap;
454     }
455
456     /**
457      * Creates a snapshot of foreign key attributes of a source object of this
458      * relationship based on a snapshot of a target. Only "to-one" relationships are
459      * supported. Throws CayenneRuntimeException if relationship is "to many" or if
460      * snapshot is missing id components.
461      */

462     public Map JavaDoc srcFkSnapshotWithTargetSnapshot(Map JavaDoc targetSnapshot) {
463
464         if (isToMany())
465             throw new CayenneRuntimeException(
466                     "Only 'to one' relationships support this method.");
467         return srcSnapshotWithTargetSnapshot(targetSnapshot);
468     }
469
470     /**
471      * Creates a snapshot of primary key attributes of a source object of this
472      * relationship based on a snapshot of a target. Only "to-many" relationships are
473      * supported. Throws CayenneRuntimeException if relationship is "to one" or if
474      * snapshot is missing id components.
475      */

476     public Map JavaDoc srcPkSnapshotWithTargetSnapshot(Map JavaDoc targetSnapshot) {
477         if (!isToMany())
478             throw new CayenneRuntimeException(
479                     "Only 'to many' relationships support this method.");
480         return srcSnapshotWithTargetSnapshot(targetSnapshot);
481     }
482
483     /**
484      * Sets relationship multiplicity.
485      */

486     public void setToMany(boolean toMany) {
487         this.toMany = toMany;
488         this.firePropertyDidChange();
489     }
490
491     protected void firePropertyDidChange() {
492         RelationshipEvent event = new RelationshipEvent(this, this, this
493                 .getSourceEntity());
494         EventManager.getDefaultManager().postEvent(event, PROPERTY_DID_CHANGE);
495     }
496
497     final static class JoinTransformers {
498
499         static final Transformer targetExtractor = new Transformer() {
500
501             public Object JavaDoc transform(Object JavaDoc input) {
502                 return (input instanceof DbJoin) ? ((DbJoin) input).getTarget() : input;
503             }
504         };
505
506         static final Transformer sourceExtractor = new Transformer() {
507
508             public Object JavaDoc transform(Object JavaDoc input) {
509                 return (input instanceof DbJoin) ? ((DbJoin) input).getSource() : input;
510             }
511         };
512     }
513
514     // a join used for comparison
515
final static class TestJoin extends DbJoin {
516
517         TestJoin(DbRelationship relationship) {
518             super(relationship);
519         }
520
521         public boolean equals(Object JavaDoc o) {
522             if (o == null) {
523                 return false;
524             }
525
526             if (o == this) {
527                 return true;
528             }
529
530             if (!(o instanceof DbJoin)) {
531                 return false;
532             }
533
534             DbJoin j = (DbJoin) o;
535             return j.relationship == this.relationship
536                     && Util.nullSafeEquals(j.sourceName, this.sourceName)
537                     && Util.nullSafeEquals(j.targetName, this.targetName);
538         }
539     }
540 }
541
Popular Tags