Source code: org/objectstyle/cayenne/map/ObjRelationship.java
1 /* ====================================================================
2 *
3 * The ObjectStyle Group Software License, Version 1.0
4 *
5 * Copyright (c) 2002-2003 The ObjectStyle Group
6 * and individual authors of the software. All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 *
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in
17 * the documentation and/or other materials provided with the
18 * distribution.
19 *
20 * 3. The end-user documentation included with the redistribution, if
21 * any, must include the following acknowlegement:
22 * "This product includes software developed by the
23 * ObjectStyle Group (http://objectstyle.org/)."
24 * Alternately, this acknowlegement may appear in the software itself,
25 * if and wherever such third-party acknowlegements normally appear.
26 *
27 * 4. The names "ObjectStyle Group" and "Cayenne"
28 * must not be used to endorse or promote products derived
29 * from this software without prior written permission. For written
30 * permission, please contact andrus@objectstyle.org.
31 *
32 * 5. Products derived from this software may not be called "ObjectStyle"
33 * nor may "ObjectStyle" appear in their names without prior written
34 * permission of the ObjectStyle Group.
35 *
36 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39 * DISCLAIMED. IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
40 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47 * SUCH DAMAGE.
48 * ====================================================================
49 *
50 * This software consists of voluntary contributions made by many
51 * individuals on behalf of the ObjectStyle Group. For more
52 * information on the ObjectStyle Group, please see
53 * <http://objectstyle.org/>.
54 *
55 */
56 package org.objectstyle.cayenne.map;
57
58 import java.util.ArrayList;
59 import java.util.Collections;
60 import java.util.EventListener;
61 import java.util.Iterator;
62 import java.util.List;
63
64 import org.objectstyle.cayenne.CayenneRuntimeException;
65 import org.objectstyle.cayenne.event.EventManager;
66 import org.objectstyle.cayenne.map.event.RelationshipEvent;
67
68 /**
69 * Metadata for the navigational association between the data objects. For
70 * example, if class "Employee" you may need to get to the department entity by
71 * calling "employee.getDepartment()". In this case you navigate from data
72 * object class Employee to Department. In this case Employee is source and
73 * Department is target. The navigation from Department to the list of
74 * employees would be expressed by another instance of ObjRelationship.
75 * ObjRelationship class also stores the navigation information in terms of the
76 * database entity relationships. The ObjRelationship objects are stored in the
77 * ObjEntities.
78 */
79 public class ObjRelationship extends Relationship implements EventListener {
80
81 // What to do with any inverse relationship when the source object
82 // is deleted
83 private int deleteRule = DeleteRule.NO_ACTION;
84
85 // Not flattened initially;
86 // will be set when dbRels are added that make it flattened
87 private boolean isFlattened = false;
88
89 // Initially all relationships are read/write;
90 // a flattened relationship may be readonly (in certain circumstances),
91 // will be set in that case
92 private boolean isReadOnly = false;
93
94 private List dbRelationships = new ArrayList();
95 private List dbRelationshipsRef = Collections.unmodifiableList(dbRelationships);
96
97 public ObjRelationship() {
98 super();
99 }
100
101 public ObjRelationship(String name) {
102 super(name);
103 }
104
105
106 public ObjRelationship(ObjEntity source, ObjEntity target, boolean toMany) {
107 this();
108 this.setSourceEntity(source);
109 this.setTargetEntity(target);
110 if (toMany) {
111 this.setName(target.getName() + "Array");
112 }
113 else {
114 this.setName("to" + target.getName());
115 }
116 }
117
118 public Entity getTargetEntity() {
119 if (getTargetEntityName() == null) {
120 return null;
121 }
122
123 Entity src = getSourceEntity();
124 if (src == null) {
125 return null;
126 }
127
128 DataMap map = src.getDataMap();
129 if (map == null) {
130 return null;
131 }
132
133 return map.getObjEntity(getTargetEntityName(), true);
134 }
135
136 /**
137 * Returns true if underlying DbRelationships point to dependent entity.
138 */
139 public boolean isToDependentEntity() {
140 return ((DbRelationship)dbRelationships.get(0)).isToDependentPK();
141 }
142
143 /**
144 * Returns ObjRelationship that is the opposite of this ObjRelationship.
145 * returns null if no such relationship exists.
146 */
147 public ObjRelationship getReverseRelationship() {
148 // reverse the list
149 List reversed = new ArrayList();
150 Iterator rit = this.getDbRelationships().iterator();
151 while (rit.hasNext()) {
152 DbRelationship rel = (DbRelationship)rit.next();
153 DbRelationship reverse = rel.getReverseRelationship();
154 if (reverse == null)
155 return null;
156
157 reversed.add(0, reverse);
158 }
159
160 Entity target = this.getTargetEntity();
161 Entity src = this.getSourceEntity();
162
163 Iterator it = target.getRelationships().iterator();
164 while (it.hasNext()) {
165 ObjRelationship rel = (ObjRelationship)it.next();
166 if (rel.getTargetEntity() != src)
167 continue;
168
169 List otherRels = rel.getDbRelationships();
170 if (reversed.size() != otherRels.size())
171 continue;
172
173 int len = reversed.size();
174 boolean relsMatch = true;
175 for (int i = 0; i < len; i++) {
176 if (otherRels.get(i) != reversed.get(i)) {
177 relsMatch = false;
178 break;
179 }
180 }
181
182 if (relsMatch)
183 return rel;
184 }
185
186 return null;
187 }
188
189 /**
190 * Returns a list of underlying DbRelationships.
191 */
192 public List getDbRelationships() {
193 return dbRelationshipsRef;
194 }
195
196 /** Appends a DbRelationship to the existing list of DbRelationships.*/
197 public void addDbRelationship(DbRelationship dbRel) {
198 //Adding a second is creating a flattened relationship.
199 //Ensure that the new relationship properly continues on the flattened path
200 int numDbRelationships = dbRelationships.size();
201 if (numDbRelationships > 0) {
202 DbRelationship lastRel =
203 (DbRelationship)dbRelationships.get(numDbRelationships - 1);
204 if (!lastRel
205 .getTargetEntityName()
206 .equals(dbRel.getSourceEntity().getName())) {
207 throw new CayenneRuntimeException(
208 "Error adding db relationship "
209 + dbRel
210 + " to ObjRelationship "
211 + this
212 + " because the source of the newly added relationship "
213 + "is not the target of the previous relationship "
214 + "in the chain");
215 }
216 isFlattened = true;
217 //Now there will be more than one dbRel - this is a flattened relationship
218 }
219 try {
220 EventManager.getDefaultManager().addListener(
221 this,
222 "dbRelationshipDidChange",
223 RelationshipEvent.class,
224 DbRelationship.PROPERTY_DID_CHANGE,
225 dbRel);
226 } catch (NoSuchMethodException e) {
227 //Really, not going to happen, but we have to catch it
228 throw new CayenneRuntimeException(e);
229 }
230 dbRelationships.add(dbRel);
231 //Recalculate whether this relationship is readOnly,
232 this.calculateReadOnlyValue();
233 // and whether it is toMany
234 this.calculateToManyValue();
235 }
236
237 /** Removes the relationship <code>dbRel</code> from the list of relationships. */
238 public void removeDbRelationship(DbRelationship dbRel) {
239 dbRelationships.remove(dbRel);
240 //Do not listen any more
241 EventManager.getDefaultManager().removeListener(
242 this,
243 DbRelationship.PROPERTY_DID_CHANGE,
244 dbRel);
245 //If we removed all but one dbRel, then it's no longer flattened
246 if (dbRelationships.size() <= 1) {
247 isFlattened = false;
248 }
249 this.calculateReadOnlyValue();
250 this.calculateToManyValue();
251 }
252
253 public void clearDbRelationships() {
254 dbRelationships.clear();
255 this.isReadOnly = false;
256 this.toMany = false;
257 }
258
259 //Recalculates whether a relationship is toMany or toOne, based on the
260 // underlying db relationships
261 private void calculateToManyValue() {
262 //If there is a single toMany along the path, then the flattend
263 // rel is toMany. If all are toOne, then the rel is toOne.
264 // Simple (non-flattened) relationships form the degenerate case
265 // taking the value of the single underlying dbrel.
266 Iterator dbRelIterator = this.dbRelationships.iterator();
267 while (dbRelIterator.hasNext()) {
268 DbRelationship thisRel = (DbRelationship) dbRelIterator.next();
269 if (thisRel.isToMany()) {
270 this.toMany=true;
271 return;
272 }
273 }
274 this.toMany=false;
275 }
276
277 //Implements logic to calculate a new readonly value after having added/removed dbRelationships
278 private void calculateReadOnlyValue() {
279 //Quickly filter the single dbrel case
280 if (dbRelationships.size() < 2) {
281 this.isReadOnly=false;
282 return;
283 }
284
285 //Also quickly filter any really complex db rel cases
286 if (dbRelationships.size() > 2) {
287 this.isReadOnly=true;
288 return;
289 }
290
291 //Now check for a toMany -> toOne series (to return false)
292 DbRelationship firstRel = (DbRelationship) dbRelationships.get(0);
293 DbRelationship secondRel = (DbRelationship) dbRelationships.get(1);
294
295 //First toOne or second toMany means read only
296 if (!firstRel.isToMany() || secondRel.isToMany()) {
297 this.isReadOnly=true;
298 return;
299 }
300
301 //Relationship type is in order, now we only have to check the intermediate table
302 DataMap map = firstRel.getTargetEntity().getDataMap();
303 if (map == null) {
304 throw new CayenneRuntimeException(
305 this.getClass().getName()
306 + " could not obtain a DataMap for the destination of "
307 + firstRel.getName());
308 }
309
310 DbEntity intermediateEntity = map.getDbEntity(firstRel.getTargetEntityName(), true);
311 List pkAttribs = intermediateEntity.getPrimaryKey();
312
313 Iterator allAttribs = intermediateEntity.getAttributes().iterator();
314 while (allAttribs.hasNext()) {
315 if (!pkAttribs.contains(allAttribs.next())) {
316 this.isReadOnly=true;
317 return;
318 //one of the attributes of intermediate entity is not in the pk. Must be readonly
319 }
320 }
321 this.isReadOnly=false;
322 }
323
324 /**
325 * Returns true if the relationship is a "flattened" relationship.
326 * This means that the ObjRelationship represents a series of DbRelationships (a relationship path)
327 * transparently. All flattened relationships are "readable", but only those formed across a many-many link table
328 * (with no custom attributes other than foreign keys) can be automatically written. isReadOnly handles that
329 * @see #isReadOnly
330 * @return flag indicating if the relationship is flattened or not
331 */
332 public boolean isFlattened() {
333 return isFlattened;
334 }
335
336 /**
337 * Returns true if the relationship is flattened, but is not of the single case that can have automatic write support
338 * Otherwise, it returns false.
339 * @return flag indicating if the relationship is read only or not
340 */
341 public boolean isReadOnly() {
342 return isReadOnly;
343 }
344
345 /**
346 * Returns the deleteRule.
347 * The delete rule is a constant from the DeleteRule class, and specifies
348 * what should happen to the destination object when the source object is
349 * deleted.
350 * @return int a constant from DeleteRule
351 * @see #setDeleteRule
352 */
353 public int getDeleteRule() {
354 return deleteRule;
355 }
356
357 /**
358 * Sets the deleteRule.
359 * @param deleteRule The deleteRule to set
360 * @see #getDeleteRule
361 * @throws IllegalArgumentException if the value is not a known value.
362 */
363 public void setDeleteRule(int value) {
364 if ((value != DeleteRule.CASCADE)
365 && (value != DeleteRule.DENY)
366 && (value != DeleteRule.NULLIFY)
367 && (value != DeleteRule.NO_ACTION)) {
368
369 throw new IllegalArgumentException(
370 "Delete rule value "
371 + value
372 + " is not a constant from the DeleteRule class");
373 }
374 this.deleteRule = value;
375 }
376
377 public void dbRelationshipDidChange(RelationshipEvent event) {
378 this.calculateToManyValue();
379 }
380 }