KickJava   Java API By Example, From Geeks To Geeks.

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


1 /*****************************************************************
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements. See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership. The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License. You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied. See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  ****************************************************************/

19
20 package org.apache.cayenne.map;
21
22 import java.util.ArrayList JavaDoc;
23 import java.util.Collection JavaDoc;
24 import java.util.Collections JavaDoc;
25 import java.util.HashMap JavaDoc;
26 import java.util.Iterator JavaDoc;
27 import java.util.List JavaDoc;
28 import java.util.Map JavaDoc;
29
30 import org.apache.commons.collections.CollectionUtils;
31 import org.apache.commons.collections.Transformer;
32 import org.apache.cayenne.CayenneRuntimeException;
33 import org.apache.cayenne.event.EventManager;
34 import org.apache.cayenne.event.EventSubject;
35 import org.apache.cayenne.map.event.RelationshipEvent;
36 import org.apache.cayenne.util.Util;
37 import org.apache.cayenne.util.XMLEncoder;
38
39 /**
40  * A DbRelationship is a descriptor of a database inter-table relationship based on one or
41  * more primary key/foreign key pairs.
42  *
43  * @author Misha Shengaout
44  * @author Andrus Adamchik
45  */

46 public class DbRelationship extends Relationship {
47
48     // DbRelationship events
49
public static final EventSubject PROPERTY_DID_CHANGE = EventSubject.getSubject(
50             DbRelationship.class,
51             "PropertyDidChange");
52
53     // The columns through which the join is implemented.
54
protected List JavaDoc joins = new ArrayList JavaDoc();
55
56     // Is relationship from source to target points to dependent primary
57
// key (primary key column of destination table that is also a FK to the source
58
// column)
59
protected boolean toDependentPK;
60
61     public DbRelationship() {
62         super();
63     }
64
65     public DbRelationship(String JavaDoc name) {
66         super(name);
67     }
68
69     /**
70      * Prints itself as XML to the provided XMLEncoder.
71      *
72      * @since 1.1
73      */

74     public void encodeAsXML(XMLEncoder encoder) {
75         encoder.print("<db-relationship name=\"");
76         encoder.print(Util.encodeXmlAttribute(getName()));
77         encoder.print("\" source=\"");
78         encoder.print(getSourceEntity().getName());
79
80         if (getTargetEntityName() != null && getTargetEntity() != null) {
81             encoder.print("\" target=\"");
82             encoder.print(getTargetEntityName());
83         }
84
85         if (isToDependentPK() && isValidForDepPk()) {
86             encoder.print("\" toDependentPK=\"true");
87         }
88
89         encoder.print("\" toMany=\"");
90         encoder.print(isToMany());
91         encoder.println("\">");
92
93         encoder.indent(1);
94         encoder.print(getJoins());
95         encoder.indent(-1);
96
97         encoder.println("</db-relationship>");
98     }
99
100     /**
101      * Returns a target of this relationship. If relationship is not attached to a
102      * DbEntity, and DbEntity doesn't have a namcespace, and exception is thrown.
103      */

104     public Entity getTargetEntity() {
105         String JavaDoc targetName = getTargetEntityName();
106         if (targetName == null) {
107             return null;
108         }
109
110         return getNonNullNamespace().getDbEntity(targetName);
111     }
112
113     /**
114      * Returns a Collection of target attributes.
115      *
116      * @since 1.1
117      */

118     public Collection JavaDoc getTargetAttributes() {
119         if (joins.size() == 0) {
120             return Collections.EMPTY_LIST;
121         }
122
123         return CollectionUtils.collect(joins, JoinTransformers.targetExtractor);
124     }
125
126     /**
127      * Returns a Collection of source attributes.
128      *
129      * @since 1.1
130      */

131     public Collection JavaDoc getSourceAttributes() {
132         if (joins.size() == 0) {
133             return Collections.EMPTY_LIST;
134         }
135
136         return CollectionUtils.collect(joins, JoinTransformers.sourceExtractor);
137     }
138
139     /**
140      * Creates a new relationship with the same set of joins, but going in the opposite
141      * direction.
142      *
143      * @since 1.0.5
144      */

145     public DbRelationship createReverseRelationship() {
146         DbRelationship reverse = new DbRelationship();
147         reverse.setSourceEntity(getTargetEntity());
148         reverse.setTargetEntityName(getSourceEntity().getName());
149
150         // TODO: must set toDepPK correctly
151
// must set toMany correctly
152

153         reverse.setToMany(!toMany);
154
155         Iterator JavaDoc it = joins.iterator();
156         while (it.hasNext()) {
157             DbJoin join = (DbJoin) it.next();
158             DbJoin reverseJoin = join.createReverseJoin();
159             reverseJoin.setRelationship(reverse);
160             reverse.addJoin(reverseJoin);
161         }
162
163         return reverse;
164     }
165
166     /**
167      * Returns DbRelationship that is the opposite of this DbRelationship. This means a
168      * relationship from this target entity to this source entity with the same join
169      * semantics. Returns null if no such relationship exists.
170      */

171     public DbRelationship getReverseRelationship() {
172         Entity target = this.getTargetEntity();
173
174         if (target == null) {
175             return null;
176         }
177
178         Entity src = this.getSourceEntity();
179
180         // special case - relationship to self with no joins...
181
if (target == src && joins.size() == 0) {
182             return null;
183         }
184
185         TestJoin testJoin = new TestJoin(this);
186         Iterator JavaDoc it = target.getRelationships().iterator();
187         while (it.hasNext()) {
188             DbRelationship rel = (DbRelationship) it.next();
189             if (rel.getTargetEntity() != src)
190                 continue;
191
192             List JavaDoc otherJoins = rel.getJoins();
193             if (otherJoins.size() != joins.size()) {
194                 continue;
195             }
196
197             Iterator JavaDoc jit = otherJoins.iterator();
198             boolean joinsMatch = true;
199             while (jit.hasNext()) {
200                 DbJoin join = (DbJoin) jit.next();
201
202                 // flip join and try to find similar
203
testJoin.setSourceName(join.getTargetName());
204                 testJoin.setTargetName(join.getSourceName());
205                 if (!joins.contains(testJoin)) {
206                     joinsMatch = false;
207                     break;
208                 }
209             }
210
211             if (joinsMatch) {
212                 return rel;
213             }
214         }
215
216         return null;
217     }
218
219     /**
220      * Returns true if the relationship points to at least one of the PK columns of the
221      * target entity.
222      *
223      * @since 1.1
224      */

225     public boolean isToPK() {
226         Iterator JavaDoc it = getJoins().iterator();
227         while (it.hasNext()) {
228             DbJoin join = (DbJoin) it.next();
229             if (join.getTarget() == null) {
230                 return false;
231             }
232
233             if (join.getTarget().isPrimaryKey()) {
234                 return true;
235             }
236         }
237
238         return false;
239     }
240
241     /**
242      * Returns <code>true</code> if a method <code>isToDependentPK</code> of reverse
243      * relationship of this relationship returns <code>true</code>.
244      */

245     public boolean isToMasterPK() {
246         if (isToMany() || isToDependentPK()) {
247             return false;
248         }
249
250         DbRelationship revRel = getReverseRelationship();
251         return (revRel != null) ? revRel.isToDependentPK() : false;
252     }
253
254     /**
255      * Returns <code>true</code> if relationship from source to target points to
256      * dependent primary key. Dependent PK is a primary key column of the destination
257      * table that is also a FK to the source column.
258      */

259     public boolean isToDependentPK() {
260         return toDependentPK;
261     }
262
263     public void setToDependentPK(boolean toDependentPK) {
264         if (this.toDependentPK != toDependentPK) {
265             this.toDependentPK = toDependentPK;
266             firePropertyDidChange();
267         }
268     }
269
270     /**
271      * @since 1.1
272      */

273     public boolean isValidForDepPk() {
274         Iterator JavaDoc it = getJoins().iterator();
275         // handle case with no joins
276
if (!it.hasNext()) {
277             return false;
278         }
279
280         while (it.hasNext()) {
281             DbJoin join = (DbJoin) it.next();
282             DbAttribute target = join.getTarget();
283             DbAttribute source = join.getSource();
284
285             if ((target != null && !target.isPrimaryKey())
286                     || (source != null && !source.isPrimaryKey())) {
287                 return false;
288             }
289         }
290
291         return true;
292     }
293
294     /**
295      * Returns a list of joins. List is returned by reference, so any modifications of the
296      * list will affect this relationship.
297      */

298     public List JavaDoc getJoins() {
299         return joins;
300     }
301
302     /**
303      * Adds a join.
304      *
305      * @since 1.1
306      */

307     public void addJoin(DbJoin join) {
308         if (join != null) {
309             joins.add(join);
310         }
311     }
312
313     public void removeJoin(DbJoin join) {
314         joins.remove(join);
315     }
316
317     public void removeAllJoins() {
318         joins.clear();
319     }
320
321     public void setJoins(Collection JavaDoc newJoins) {
322         this.removeAllJoins();
323
324         if (newJoins != null) {
325             joins.addAll(newJoins);
326         }
327     }
328
329     /**
330      * Creates a snapshot of primary key attributes of a target object of this
331      * relationship based on a snapshot of a source. Only "to-one" relationships are
332      * supported. Returns null if relationship does not point to an object. Throws
333      * CayenneRuntimeException if relationship is "to many" or if snapshot is missing id
334      * components.
335      */

336     public Map JavaDoc targetPkSnapshotWithSrcSnapshot(Map JavaDoc srcSnapshot) {
337
338         if (isToMany()) {
339             throw new CayenneRuntimeException(
340                     "Only 'to one' relationships support this method.");
341         }
342
343         Map JavaDoc idMap;
344
345         int numJoins = joins.size();
346         int foundNulls = 0;
347
348         // optimize for the most common single column join
349
if (numJoins == 1) {
350             DbJoin join = (DbJoin) joins.get(0);
351             Object JavaDoc val = srcSnapshot.get(join.getSourceName());
352             if (val == null) {
353                 foundNulls++;
354                 idMap = Collections.EMPTY_MAP;
355             }
356             else {
357                 idMap = Collections.singletonMap(join.getTargetName(), val);
358             }
359         }
360         // handle generic case: numJoins > 1
361
else {
362             idMap = new HashMap JavaDoc(numJoins * 2);
363             for (int i = 0; i < numJoins; i++) {
364                 DbJoin join = (DbJoin) joins.get(i);
365                 DbAttribute source = join.getSource();
366                 Object JavaDoc val = srcSnapshot.get(join.getSourceName());
367
368                 if (val == null) {
369
370                     // some keys may be nulls and some not in case of multi-key
371
// relationships where PK and FK partially overlap (see CAY-284)
372
if (!source.isMandatory()) {
373                         return null;
374                     }
375
376                     foundNulls++;
377                 }
378                 else {
379                     idMap.put(join.getTargetName(), val);
380                 }
381             }
382         }
383
384         if (foundNulls == 0) {
385             return idMap;
386         }
387         else if (foundNulls == numJoins) {
388             return null;
389         }
390         else {
391             throw new CayenneRuntimeException("Some parts of FK are missing in snapshot,"
392                     + " relationship: "
393                     + this);
394         }
395     }
396
397     /**
398      * Common code to srcSnapshotWithTargetSnapshot. Both are functionally the same,
399      * except for the name, and whether they operate on a toMany or a toOne.
400      */

401     private Map JavaDoc srcSnapshotWithTargetSnapshot(Map JavaDoc targetSnapshot) {
402         int len = joins.size();
403
404         // optimize for the most common single column join
405
if (len == 1) {
406             DbJoin join = (DbJoin) joins.get(0);
407             Object JavaDoc val = targetSnapshot.get(join.getTargetName());
408             return Collections.singletonMap(join.getSourceName(), val);
409
410         }
411
412         // general case
413
Map JavaDoc idMap = new HashMap JavaDoc(len * 2);
414         for (int i = 0; i < len; i++) {
415             DbJoin join = (DbJoin) joins.get(i);
416             Object JavaDoc val = targetSnapshot.get(join.getTargetName());
417             idMap.put(join.getSourceName(), val);
418         }
419
420         return idMap;
421     }
422
423     /**
424      * Creates a snapshot of foreign key attributes of a source object of this
425      * relationship based on a snapshot of a target. Only "to-one" relationships are
426      * supported. Throws CayenneRuntimeException if relationship is "to many".
427      */

428     public Map JavaDoc srcFkSnapshotWithTargetSnapshot(Map JavaDoc targetSnapshot) {
429
430         if (isToMany()) {
431             throw new CayenneRuntimeException(
432                     "Only 'to one' relationships support this method.");
433         }
434
435         return srcSnapshotWithTargetSnapshot(targetSnapshot);
436     }
437
438     /**
439      * Creates a snapshot of primary key attributes of a source object of this
440      * relationship based on a snapshot of a target. Only "to-many" relationships are
441      * supported. Throws CayenneRuntimeException if relationship is "to one".
442      */

443     public Map JavaDoc srcPkSnapshotWithTargetSnapshot(Map JavaDoc targetSnapshot) {
444         if (!isToMany())
445             throw new CayenneRuntimeException(
446                     "Only 'to many' relationships support this method.");
447         return srcSnapshotWithTargetSnapshot(targetSnapshot);
448     }
449
450     /**
451      * Sets relationship multiplicity.
452      */

453     public void setToMany(boolean toMany) {
454         if (this.toMany != toMany) {
455             this.toMany = toMany;
456             this.firePropertyDidChange();
457         }
458     }
459
460     protected void firePropertyDidChange() {
461         RelationshipEvent event = new RelationshipEvent(this, this, this
462                 .getSourceEntity());
463         EventManager.getDefaultManager().postEvent(event, PROPERTY_DID_CHANGE);
464     }
465
466     final static class JoinTransformers {
467
468         static final Transformer targetExtractor = new Transformer() {
469
470             public Object JavaDoc transform(Object JavaDoc input) {
471                 return (input instanceof DbJoin) ? ((DbJoin) input).getTarget() : input;
472             }
473         };
474
475         static final Transformer sourceExtractor = new Transformer() {
476
477             public Object JavaDoc transform(Object JavaDoc input) {
478                 return (input instanceof DbJoin) ? ((DbJoin) input).getSource() : input;
479             }
480         };
481     }
482
483     // a join used for comparison
484
final static class TestJoin extends DbJoin {
485
486         TestJoin(DbRelationship relationship) {
487             super(relationship);
488         }
489
490         public boolean equals(Object JavaDoc o) {
491             if (o == null) {
492                 return false;
493             }
494
495             if (o == this) {
496                 return true;
497             }
498
499             if (!(o instanceof DbJoin)) {
500                 return false;
501             }
502
503             DbJoin j = (DbJoin) o;
504             return j.relationship == this.relationship
505                     && Util.nullSafeEquals(j.sourceName, this.sourceName)
506                     && Util.nullSafeEquals(j.targetName, this.targetName);
507         }
508     }
509 }
510
Popular Tags