Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

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 }