KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > xdoclet > modules > ojb > constraints > ModelConstraints


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

17
18 import java.util.ArrayList JavaDoc;
19 import java.util.Collection JavaDoc;
20 import java.util.Iterator JavaDoc;
21
22 import org.apache.commons.collections.SequencedHashMap;
23
24 import xdoclet.modules.ojb.CommaListIterator;
25 import xdoclet.modules.ojb.LogHelper;
26 import xdoclet.modules.ojb.model.ClassDescriptorDef;
27 import xdoclet.modules.ojb.model.CollectionDescriptorDef;
28 import xdoclet.modules.ojb.model.FeatureDescriptorDef;
29 import xdoclet.modules.ojb.model.FieldDescriptorDef;
30 import xdoclet.modules.ojb.model.ModelDef;
31 import xdoclet.modules.ojb.model.PropertyHelper;
32 import xdoclet.modules.ojb.model.ReferenceDescriptorDef;
33
34 /**
35  * Checks constraints that span deal with parts of the model, not just with one class.
36  * This for instance means relationships (collections, references).
37  *
38  * @author <a HREF="mailto:tomdz@users.sourceforge.net">Thomas Dudziak (tomdz@users.sourceforge.net)</a>
39  */

40 public class ModelConstraints extends ConstraintsBase
41 {
42     /**
43      * Checks the given model.
44      *
45      * @param modelDef The model
46      * @param checkLevel The amount of checks to perform
47      * @exception ConstraintException If a constraint has been violated
48      */

49     public void check(ModelDef modelDef, String JavaDoc checkLevel) throws ConstraintException
50     {
51         ensureReferencedKeys(modelDef, checkLevel);
52         checkReferenceForeignkeys(modelDef, checkLevel);
53         checkCollectionForeignkeys(modelDef, checkLevel);
54         checkKeyModifications(modelDef, checkLevel);
55     }
56
57     /**
58      * Ensures that the primary/foreign keys referenced by references/collections are present
59      * in the target type even if generate-table-info="false", by evaluating the subtypes
60      * of the target type.
61      *
62      * @param modelDef The model
63      * @param checkLevel The current check level (this constraint is always checked)
64      * @throws ConstraintException If there is an error with the keys of the subtypes or there
65      * ain't any subtypes
66      */

67     private void ensureReferencedKeys(ModelDef modelDef, String JavaDoc checkLevel) throws ConstraintException
68     {
69         ClassDescriptorDef classDef;
70         CollectionDescriptorDef collDef;
71         ReferenceDescriptorDef refDef;
72
73         for (Iterator JavaDoc it = modelDef.getClasses(); it.hasNext();)
74         {
75             classDef = (ClassDescriptorDef)it.next();
76             for (Iterator JavaDoc refIt = classDef.getReferences(); refIt.hasNext();)
77             {
78                 refDef = (ReferenceDescriptorDef)refIt.next();
79                 if (!refDef.getBooleanProperty(PropertyHelper.OJB_PROPERTY_IGNORE, false))
80                 {
81                     ensureReferencedPKs(modelDef, refDef);
82                 }
83             }
84             for (Iterator JavaDoc collIt = classDef.getCollections(); collIt.hasNext();)
85             {
86                 collDef = (CollectionDescriptorDef)collIt.next();
87                 if (!collDef.getBooleanProperty(PropertyHelper.OJB_PROPERTY_IGNORE, false))
88                 {
89                     if (collDef.hasProperty(PropertyHelper.OJB_PROPERTY_INDIRECTION_TABLE))
90                     {
91                         ensureReferencedPKs(modelDef, collDef);
92                     }
93                     else
94                     {
95                         ensureReferencedFKs(modelDef, collDef);
96                     }
97                 }
98             }
99         }
100     }
101
102     /**
103      * Ensures that the primary keys required by the given reference are present in the referenced class.
104      *
105      * @param modelDef The model
106      * @param refDef The reference
107      * @throws ConstraintException If there is a conflict between the primary keys
108      */

109     private void ensureReferencedPKs(ModelDef modelDef, ReferenceDescriptorDef refDef) throws ConstraintException
110     {
111         String JavaDoc targetClassName = refDef.getProperty(PropertyHelper.OJB_PROPERTY_CLASS_REF);
112         ClassDescriptorDef targetClassDef = modelDef.getClass(targetClassName);
113
114         ensurePKsFromHierarchy(targetClassDef);
115     }
116
117     /**
118      * Ensures that the primary keys required by the given collection with indirection table are present in
119      * the element class.
120      *
121      * @param modelDef The model
122      * @param collDef The collection
123      * @throws ConstraintException If there is a problem with the fitting collection (if any) or the primary keys
124      */

125     private void ensureReferencedPKs(ModelDef modelDef, CollectionDescriptorDef collDef) throws ConstraintException
126     {
127         String JavaDoc elementClassName = collDef.getProperty(PropertyHelper.OJB_PROPERTY_ELEMENT_CLASS_REF);
128         ClassDescriptorDef elementClassDef = modelDef.getClass(elementClassName);
129         String JavaDoc indirTable = collDef.getProperty(PropertyHelper.OJB_PROPERTY_INDIRECTION_TABLE);
130         String JavaDoc localKey = collDef.getProperty(PropertyHelper.OJB_PROPERTY_FOREIGNKEY);
131         String JavaDoc remoteKey = collDef.getProperty(PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY);
132         boolean hasRemoteKey = remoteKey != null;
133         ArrayList JavaDoc fittingCollections = new ArrayList JavaDoc();
134
135         // we're checking for the fitting remote collection(s) and also
136
// use their foreignkey as remote-foreignkey in the original collection definition
137
for (Iterator JavaDoc it = elementClassDef.getAllExtentClasses(); it.hasNext();)
138         {
139             ClassDescriptorDef subTypeDef = (ClassDescriptorDef)it.next();
140
141             // find the collection in the element class that has the same indirection table
142
for (Iterator JavaDoc collIt = subTypeDef.getCollections(); collIt.hasNext();)
143             {
144                 CollectionDescriptorDef curCollDef = (CollectionDescriptorDef)collIt.next();
145
146                 if (indirTable.equals(curCollDef.getProperty(PropertyHelper.OJB_PROPERTY_INDIRECTION_TABLE)) &&
147                     (collDef != curCollDef) &&
148                     (!hasRemoteKey || CommaListIterator.sameLists(remoteKey, curCollDef.getProperty(PropertyHelper.OJB_PROPERTY_FOREIGNKEY))) &&
149                     (!curCollDef.hasProperty(PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY) ||
150                          CommaListIterator.sameLists(localKey, curCollDef.getProperty(PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY))))
151                 {
152                     fittingCollections.add(curCollDef);
153                 }
154             }
155         }
156         if (!fittingCollections.isEmpty())
157         {
158             // if there is more than one, check that they match, i.e. that they all have the same foreignkeys
159
if (!hasRemoteKey && (fittingCollections.size() > 1))
160             {
161                 CollectionDescriptorDef firstCollDef = (CollectionDescriptorDef)fittingCollections.get(0);
162                 String JavaDoc foreignKey = firstCollDef.getProperty(PropertyHelper.OJB_PROPERTY_FOREIGNKEY);
163
164                 for (int idx = 1; idx < fittingCollections.size(); idx++)
165                 {
166                     CollectionDescriptorDef curCollDef = (CollectionDescriptorDef)fittingCollections.get(idx);
167
168                     if (!CommaListIterator.sameLists(foreignKey, curCollDef.getProperty(PropertyHelper.OJB_PROPERTY_FOREIGNKEY)))
169                     {
170                         throw new ConstraintException("Cannot determine the element-side collection that corresponds to the collection "+
171                                                       collDef.getName()+" in type "+collDef.getOwner().getName()+
172                                                       " because there are at least two different collections that would fit."+
173                                                       " Specifying remote-foreignkey in the original collection "+collDef.getName()+
174                                                       " will perhaps help");
175                     }
176                 }
177                 // store the found keys at the collections
178
collDef.setProperty(PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY, foreignKey);
179                 for (int idx = 0; idx < fittingCollections.size(); idx++)
180                 {
181                     CollectionDescriptorDef curCollDef = (CollectionDescriptorDef)fittingCollections.get(idx);
182
183                     curCollDef.setProperty(PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY, localKey);
184                 }
185             }
186         }
187
188         // copy subclass pk fields into target class (if not already present)
189
ensurePKsFromHierarchy(elementClassDef);
190     }
191
192     /**
193      * Ensures that the foreign keys required by the given collection are present in the element class.
194      *
195      * @param modelDef The model
196      * @param collDef The collection
197      * @throws ConstraintException If there is a problem with the foreign keys
198      */

199     private void ensureReferencedFKs(ModelDef modelDef, CollectionDescriptorDef collDef) throws ConstraintException
200     {
201         String JavaDoc elementClassName = collDef.getProperty(PropertyHelper.OJB_PROPERTY_ELEMENT_CLASS_REF);
202         ClassDescriptorDef elementClassDef = modelDef.getClass(elementClassName);
203         String JavaDoc fkFieldNames = collDef.getProperty(PropertyHelper.OJB_PROPERTY_FOREIGNKEY);
204         ArrayList JavaDoc missingFields = new ArrayList JavaDoc();
205         SequencedHashMap fkFields = new SequencedHashMap();
206
207         // first we gather all field names
208
for (CommaListIterator it = new CommaListIterator(fkFieldNames); it.hasNext();)
209         {
210             String JavaDoc fieldName = (String JavaDoc)it.next();
211             FieldDescriptorDef fieldDef = elementClassDef.getField(fieldName);
212
213             if (fieldDef == null)
214             {
215                 missingFields.add(fieldName);
216             }
217             fkFields.put(fieldName, fieldDef);
218         }
219
220         // next we traverse all sub types and gather fields as we go
221
for (Iterator JavaDoc it = elementClassDef.getAllExtentClasses(); it.hasNext() && !missingFields.isEmpty();)
222         {
223             ClassDescriptorDef subTypeDef = (ClassDescriptorDef)it.next();
224
225             for (int idx = 0; idx < missingFields.size();)
226             {
227                 FieldDescriptorDef fieldDef = subTypeDef.getField((String JavaDoc)missingFields.get(idx));
228
229                 if (fieldDef != null)
230                 {
231                     fkFields.put(fieldDef.getName(), fieldDef);
232                     missingFields.remove(idx);
233                 }
234                 else
235                 {
236                     idx++;
237                 }
238             }
239         }
240         if (!missingFields.isEmpty())
241         {
242             throw new ConstraintException("Cannot find field "+missingFields.get(0).toString()+" in the hierarchy with root type "+
243                                           elementClassDef.getName()+" which is used as foreignkey in collection "+
244                                           collDef.getName()+" in "+collDef.getOwner().getName());
245         }
246
247         // copy the found fields into the element class
248
ensureFields(elementClassDef, fkFields.values());
249     }
250
251     /**
252      * Gathers the pk fields from the hierarchy of the given class, and copies them into the class.
253      *
254      * @param classDef The root of the hierarchy
255      * @throws ConstraintException If there is a conflict between the pk fields
256      */

257     private void ensurePKsFromHierarchy(ClassDescriptorDef classDef) throws ConstraintException
258     {
259         SequencedHashMap pks = new SequencedHashMap();
260
261         for (Iterator JavaDoc it = classDef.getAllExtentClasses(); it.hasNext();)
262         {
263             ClassDescriptorDef subTypeDef = (ClassDescriptorDef)it.next();
264
265             ArrayList JavaDoc subPKs = subTypeDef.getPrimaryKeys();
266
267             // check against already present PKs
268
for (Iterator JavaDoc pkIt = subPKs.iterator(); pkIt.hasNext();)
269             {
270                 FieldDescriptorDef fieldDef = (FieldDescriptorDef)pkIt.next();
271                 FieldDescriptorDef foundPKDef = (FieldDescriptorDef)pks.get(fieldDef.getName());
272
273                 if (foundPKDef != null)
274                 {
275                     if (!isEqual(fieldDef, foundPKDef))
276                     {
277                         throw new ConstraintException("Cannot pull up the declaration of the required primary key "+fieldDef.getName()+
278                                                       " because its definitions in "+fieldDef.getOwner().getName()+" and "+
279                                                       foundPKDef.getOwner().getName()+" differ");
280                     }
281                 }
282                 else
283                 {
284                     pks.put(fieldDef.getName(), fieldDef);
285                 }
286             }
287         }
288
289         ensureFields(classDef, pks.values());
290     }
291     
292     /**
293      * Ensures that the specified fields are present in the given class.
294      *
295      * @param classDef The class to copy the fields into
296      * @param fields The fields to copy
297      * @throws ConstraintException If there is a conflict between the new fields and fields in the class
298      */

299     private void ensureFields(ClassDescriptorDef classDef, Collection JavaDoc fields) throws ConstraintException
300     {
301         boolean forceVirtual = !classDef.getBooleanProperty(PropertyHelper.OJB_PROPERTY_GENERATE_REPOSITORY_INFO, true);
302
303         for (Iterator JavaDoc it = fields.iterator(); it.hasNext();)
304         {
305             FieldDescriptorDef fieldDef = (FieldDescriptorDef)it.next();
306
307             // First we check whether this field is already present in the class
308
FieldDescriptorDef foundFieldDef = classDef.getField(fieldDef.getName());
309
310             if (foundFieldDef != null)
311             {
312                 if (isEqual(fieldDef, foundFieldDef))
313                 {
314                     if (forceVirtual)
315                     {
316                         foundFieldDef.setProperty(PropertyHelper.OJB_PROPERTY_VIRTUAL_FIELD, "true");
317                     }
318                     continue;
319                 }
320                 else
321                 {
322                     throw new ConstraintException("Cannot pull up the declaration of the required field "+fieldDef.getName()+
323                             " from type "+fieldDef.getOwner().getName()+" to basetype "+classDef.getName()+
324                             " because there is already a different field of the same name");
325                 }
326             }
327
328             // perhaps a reference or collection ?
329
if (classDef.getCollection(fieldDef.getName()) != null)
330             {
331                 throw new ConstraintException("Cannot pull up the declaration of the required field "+fieldDef.getName()+
332                                               " from type "+fieldDef.getOwner().getName()+" to basetype "+classDef.getName()+
333                                               " because there is already a collection of the same name");
334             }
335             if (classDef.getReference(fieldDef.getName()) != null)
336             {
337                 throw new ConstraintException("Cannot pull up the declaration of the required field "+fieldDef.getName()+
338                                               " from type "+fieldDef.getOwner().getName()+" to basetype "+classDef.getName()+
339                                               " because there is already a reference of the same name");
340             }
341             classDef.addFieldClone(fieldDef);
342             classDef.getField(fieldDef.getName()).setProperty(PropertyHelper.OJB_PROPERTY_VIRTUAL_FIELD, "true");
343         }
344     }
345
346     /**
347      * Tests whether the two field descriptors are equal, i.e. have same name, same column
348      * and same jdbc-type.
349      *
350      * @param first The first field
351      * @param second The second field
352      * @return <code>true</code> if they are equal
353      */

354     private boolean isEqual(FieldDescriptorDef first, FieldDescriptorDef second)
355     {
356         return first.getName().equals(second.getName()) &&
357                first.getProperty(PropertyHelper.OJB_PROPERTY_COLUMN).equals(second.getProperty(PropertyHelper.OJB_PROPERTY_COLUMN)) &&
358                first.getProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE).equals(second.getProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE));
359     }
360
361     /**
362      * Checks the foreignkeys of all collections in the model.
363      *
364      * @param modelDef The model
365      * @param checkLevel The current check level (this constraint is checked in basic and strict)
366      * @exception ConstraintException If the value for foreignkey is invalid
367      */

368     private void checkCollectionForeignkeys(ModelDef modelDef, String JavaDoc checkLevel) throws ConstraintException
369     {
370         if (CHECKLEVEL_NONE.equals(checkLevel))
371         {
372             return;
373         }
374
375         ClassDescriptorDef classDef;
376         CollectionDescriptorDef collDef;
377
378         for (Iterator JavaDoc it = modelDef.getClasses(); it.hasNext();)
379         {
380             classDef = (ClassDescriptorDef)it.next();
381             for (Iterator JavaDoc collIt = classDef.getCollections(); collIt.hasNext();)
382             {
383                 collDef = (CollectionDescriptorDef)collIt.next();
384                 if (!collDef.getBooleanProperty(PropertyHelper.OJB_PROPERTY_IGNORE, false))
385                 {
386                     if (collDef.hasProperty(PropertyHelper.OJB_PROPERTY_INDIRECTION_TABLE))
387                     {
388                         checkIndirectionTable(modelDef, collDef);
389                     }
390                     else
391                     {
392                         checkCollectionForeignkeys(modelDef, collDef);
393                     }
394                 }
395             }
396         }
397     }
398
399     /**
400      * Checks the indirection-table and foreignkey of the collection. This constraint also ensures that
401      * for the collections on both ends (if they exist), the remote-foreignkey property is set correctly.
402      *
403      * @param modelDef The model
404      * @param collDef The collection descriptor
405      * @exception ConstraintException If the value for foreignkey is invalid
406      */

407     private void checkIndirectionTable(ModelDef modelDef, CollectionDescriptorDef collDef) throws ConstraintException
408     {
409         String JavaDoc foreignkey = collDef.getProperty(PropertyHelper.OJB_PROPERTY_FOREIGNKEY);
410
411         if ((foreignkey == null) || (foreignkey.length() == 0))
412         {
413             throw new ConstraintException("The collection "+collDef.getName()+" in class "+collDef.getOwner().getName()+" has no foreignkeys");
414         }
415
416         // we know that the class is present because the collection constraints have been checked already
417
// TODO: we must check whether there is a collection at the other side; if the type does not map to a
418
// table then we have to check its subtypes
419
String JavaDoc elementClassName = collDef.getProperty(PropertyHelper.OJB_PROPERTY_ELEMENT_CLASS_REF);
420         ClassDescriptorDef elementClass = modelDef.getClass(elementClassName);
421         CollectionDescriptorDef remoteCollDef = collDef.getRemoteCollection();
422
423         if (remoteCollDef == null)
424         {
425             // error if there is none and we don't have remote-foreignkey specified
426
if (!collDef.hasProperty(PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY))
427             {
428                 throw new ConstraintException("The collection "+collDef.getName()+" in class "+collDef.getOwner().getName()+" must specify remote-foreignkeys as the class on the other side of the m:n association has no corresponding collection");
429             }
430         }
431         else
432         {
433             String JavaDoc remoteKeys2 = remoteCollDef.getProperty(PropertyHelper.OJB_PROPERTY_FOREIGNKEY);
434
435             if (collDef.hasProperty(PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY))
436             {
437                 // check that the specified remote-foreignkey equals the remote foreignkey setting
438
String JavaDoc remoteKeys1 = collDef.getProperty(PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY);
439
440                 if (!CommaListIterator.sameLists(remoteKeys1, remoteKeys2))
441                 {
442                     throw new ConstraintException("The remote-foreignkey property specified for collection "+collDef.getName()+" in class "+collDef.getOwner().getName()+" doesn't match the foreignkey property of the corresponding collection "+remoteCollDef.getName()+" in class "+elementClass.getName());
443                 }
444             }
445             else
446             {
447                 // ensure the remote-foreignkey setting
448
collDef.setProperty(PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY, remoteKeys2);
449             }
450         }
451
452         // issue a warning if the foreignkey and remote-foreignkey columns are the same (issue OJB-67)
453
String JavaDoc remoteForeignkey = collDef.getProperty(PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY);
454
455         if (CommaListIterator.sameLists(foreignkey, remoteForeignkey))
456         {
457             LogHelper.warn(true,
458                            getClass(),
459                            "checkIndirectionTable",
460                            "The remote foreignkey ("+remoteForeignkey+") for the collection "+collDef.getName()+" in class "+collDef.getOwner().getName()+" is identical (ignoring case) to the foreign key ("+foreignkey+").");
461         }
462
463         // for torque we generate names for the m:n relation that are unique across inheritance
464
// but only if we don't have inherited collections
465
if (collDef.getOriginal() != null)
466         {
467             CollectionDescriptorDef origDef = (CollectionDescriptorDef)collDef.getOriginal();
468             CollectionDescriptorDef origRemoteDef = origDef.getRemoteCollection();
469
470             // we're removing any torque relation name properties from the base collection
471
origDef.setProperty(PropertyHelper.TORQUE_PROPERTY_RELATION_NAME, null);
472             origDef.setProperty(PropertyHelper.TORQUE_PROPERTY_INV_RELATION_NAME, null);
473             if (origRemoteDef != null)
474             {
475                 origRemoteDef.setProperty(PropertyHelper.TORQUE_PROPERTY_RELATION_NAME, null);
476                 origRemoteDef.setProperty(PropertyHelper.TORQUE_PROPERTY_INV_RELATION_NAME, null);
477             }
478         }
479         else if (!collDef.hasProperty(PropertyHelper.TORQUE_PROPERTY_RELATION_NAME))
480         {
481             if (remoteCollDef == null)
482             {
483                 collDef.setProperty(PropertyHelper.TORQUE_PROPERTY_RELATION_NAME, collDef.getName());
484                 collDef.setProperty(PropertyHelper.TORQUE_PROPERTY_INV_RELATION_NAME, "inverse "+collDef.getName());
485             }
486             else
487             {
488                 String JavaDoc relName = collDef.getName()+"-"+remoteCollDef.getName();
489     
490                 collDef.setProperty(PropertyHelper.TORQUE_PROPERTY_RELATION_NAME, relName);
491                 remoteCollDef.setProperty(PropertyHelper.TORQUE_PROPERTY_INV_RELATION_NAME, relName);
492     
493                 relName = remoteCollDef.getName()+"-"+collDef.getName();
494     
495                 collDef.setProperty(PropertyHelper.TORQUE_PROPERTY_INV_RELATION_NAME, relName);
496                 remoteCollDef.setProperty(PropertyHelper.TORQUE_PROPERTY_RELATION_NAME, relName);
497             }
498         }
499     }
500
501     /**
502      * Checks the foreignkeys of the collection.
503      *
504      * @param modelDef The model
505      * @param collDef The collection descriptor
506      * @exception ConstraintException If the value for foreignkey is invalid
507      */

508     private void checkCollectionForeignkeys(ModelDef modelDef, CollectionDescriptorDef collDef) throws ConstraintException
509     {
510         String JavaDoc foreignkey = collDef.getProperty(PropertyHelper.OJB_PROPERTY_FOREIGNKEY);
511
512         if ((foreignkey == null) || (foreignkey.length() == 0))
513         {
514             throw new ConstraintException("The collection "+collDef.getName()+" in class "+collDef.getOwner().getName()+" has no foreignkeys");
515         }
516
517         String JavaDoc remoteForeignkey = collDef.getProperty(PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY);
518
519         if ((remoteForeignkey != null) && (remoteForeignkey.length() > 0))
520         {
521             // warning because a remote-foreignkey was specified for a 1:n collection (issue OJB-67)
522
LogHelper.warn(true,
523                            getClass(),
524                            "checkCollectionForeignkeys",
525                            "For the collection "+collDef.getName()+" in class "+collDef.getOwner().getName()+", a remote foreignkey was specified though it is a 1:n, not a m:n collection");
526         }
527         
528         ClassDescriptorDef ownerClass = (ClassDescriptorDef)collDef.getOwner();
529         ArrayList JavaDoc primFields = ownerClass.getPrimaryKeys();
530         String JavaDoc elementClassName = collDef.getProperty(PropertyHelper.OJB_PROPERTY_ELEMENT_CLASS_REF);
531         ArrayList JavaDoc queue = new ArrayList JavaDoc();
532         ClassDescriptorDef elementClass;
533         ArrayList JavaDoc keyFields;
534         FieldDescriptorDef keyField;
535         FieldDescriptorDef primField;
536         String JavaDoc primType;
537         String JavaDoc keyType;
538         
539         // we know that the class is present because the collection constraints have been checked already
540
queue.add(modelDef.getClass(elementClassName));
541         while (!queue.isEmpty())
542         {
543             elementClass = (ClassDescriptorDef)queue.get(0);
544             queue.remove(0);
545
546             for (Iterator JavaDoc it = elementClass.getExtentClasses(); it.hasNext();)
547             {
548                 queue.add(it.next());
549             }
550             if (!elementClass.getBooleanProperty(PropertyHelper.OJB_PROPERTY_GENERATE_REPOSITORY_INFO, true))
551             {
552                 continue;
553             }
554             try
555             {
556                 keyFields = elementClass.getFields(foreignkey);
557             }
558             catch (NoSuchFieldException JavaDoc ex)
559             {
560                 throw new ConstraintException("The collection "+collDef.getName()+" in class "+collDef.getOwner().getName()+" specifies a foreignkey "+ex.getMessage()+" that is not a persistent field in the element class (or its subclass) "+elementClass.getName());
561             }
562             if (primFields.size() != keyFields.size())
563             {
564                 throw new ConstraintException("The number of foreignkeys ("+keyFields.size()+") of the collection "+collDef.getName()+" in class "+collDef.getOwner().getName()+" doesn't match the number of primarykeys ("+primFields.size()+") of its owner class "+ownerClass.getName());
565             }
566             for (int idx = 0; idx < keyFields.size(); idx++)
567             {
568                 keyField = (FieldDescriptorDef)keyFields.get(idx);
569                 if (keyField.getBooleanProperty(PropertyHelper.OJB_PROPERTY_IGNORE, false))
570                 {
571                     throw new ConstraintException("The collection "+collDef.getName()+" in class "+ownerClass.getName()+" uses the field "+keyField.getName()+" as foreignkey although this field is ignored in the element class (or its subclass) "+elementClass.getName());
572                 }
573             }
574             // the jdbc types of the primary keys must match the jdbc types of the foreignkeys (in the correct order)
575
for (int idx = 0; idx < primFields.size(); idx++)
576             {
577                 keyField = (FieldDescriptorDef)keyFields.get(idx);
578                 if (keyField.getBooleanProperty(PropertyHelper.OJB_PROPERTY_IGNORE, false))
579                 {
580                     throw new ConstraintException("The collection "+collDef.getName()+" in class "+ownerClass.getName()+" uses the field "+keyField.getName()+" as foreignkey although this field is ignored in the element class (or its subclass) "+elementClass.getName());
581                 }
582                 primField = (FieldDescriptorDef)primFields.get(idx);
583                 primType = primField.getProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE);
584                 keyType = keyField.getProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE);
585                 if (!primType.equals(keyType))
586                 {
587                     throw new ConstraintException("The jdbc-type of foreignkey "+keyField.getName()+" in the element class (or its subclass) "+elementClass.getName()+" used by the collection "+collDef.getName()+" in class "+ownerClass.getName()+" doesn't match the jdbc-type of the corresponding primarykey "+primField.getName());
588                 }
589             }
590         }
591     }
592
593     /**
594      * Checks the foreignkeys of all references in the model.
595      *
596      * @param modelDef The model
597      * @param checkLevel The current check level (this constraint is checked in basic and strict)
598      * @exception ConstraintException If the value for foreignkey is invalid
599      */

600     private void checkReferenceForeignkeys(ModelDef modelDef, String JavaDoc checkLevel) throws ConstraintException
601     {
602         if (CHECKLEVEL_NONE.equals(checkLevel))
603         {
604             return;
605         }
606
607         ClassDescriptorDef classDef;
608         ReferenceDescriptorDef refDef;
609
610         for (Iterator JavaDoc it = modelDef.getClasses(); it.hasNext();)
611         {
612             classDef = (ClassDescriptorDef)it.next();
613             for (Iterator JavaDoc refIt = classDef.getReferences(); refIt.hasNext();)
614             {
615                 refDef = (ReferenceDescriptorDef)refIt.next();
616                 if (!refDef.getBooleanProperty(PropertyHelper.OJB_PROPERTY_IGNORE, false))
617                 {
618                     checkReferenceForeignkeys(modelDef, refDef);
619                 }
620             }
621         }
622     }
623
624     /**
625      * Checks the foreignkeys of a reference.
626      *
627      * @param modelDef The model
628      * @param refDef The reference descriptor
629      * @exception ConstraintException If the value for foreignkey is invalid
630      */

631     private void checkReferenceForeignkeys(ModelDef modelDef, ReferenceDescriptorDef refDef) throws ConstraintException
632     {
633         String JavaDoc foreignkey = refDef.getProperty(PropertyHelper.OJB_PROPERTY_FOREIGNKEY);
634
635         if ((foreignkey == null) || (foreignkey.length() == 0))
636         {
637             throw new ConstraintException("The reference "+refDef.getName()+" in class "+refDef.getOwner().getName()+" has no foreignkeys");
638         }
639
640         // we know that the class is present because the reference constraints have been checked already
641
ClassDescriptorDef ownerClass = (ClassDescriptorDef)refDef.getOwner();
642         ArrayList JavaDoc keyFields;
643         FieldDescriptorDef keyField;
644         
645         try
646         {
647             keyFields = ownerClass.getFields(foreignkey);
648         }
649         catch (NoSuchFieldException JavaDoc ex)
650         {
651             throw new ConstraintException("The reference "+refDef.getName()+" in class "+refDef.getOwner().getName()+" specifies a foreignkey "+ex.getMessage()+" that is not a persistent field in its owner class "+ownerClass.getName());
652         }
653         for (int idx = 0; idx < keyFields.size(); idx++)
654         {
655             keyField = (FieldDescriptorDef)keyFields.get(idx);
656             if (keyField.getBooleanProperty(PropertyHelper.OJB_PROPERTY_IGNORE, false))
657             {
658                 throw new ConstraintException("The reference "+refDef.getName()+" in class "+ownerClass.getName()+" uses the field "+keyField.getName()+" as foreignkey although this field is ignored in this class");
659             }
660         }
661             
662         // for the referenced class and any subtype that is instantiable (i.e. not an interface or abstract class)
663
// there must be the same number of primary keys and the jdbc types of the primary keys must
664
// match the jdbc types of the foreignkeys (in the correct order)
665
String JavaDoc targetClassName = refDef.getProperty(PropertyHelper.OJB_PROPERTY_CLASS_REF);
666         ArrayList JavaDoc queue = new ArrayList JavaDoc();
667         ClassDescriptorDef referencedClass;
668         ArrayList JavaDoc primFields;
669         FieldDescriptorDef primField;
670         String JavaDoc primType;
671         String JavaDoc keyType;
672         
673         queue.add(modelDef.getClass(targetClassName));
674
675         while (!queue.isEmpty())
676         {
677             referencedClass = (ClassDescriptorDef)queue.get(0);
678             queue.remove(0);
679
680             for (Iterator JavaDoc it = referencedClass.getExtentClasses(); it.hasNext();)
681             {
682                 queue.add(it.next());
683             }
684             if (!referencedClass.getBooleanProperty(PropertyHelper.OJB_PROPERTY_GENERATE_REPOSITORY_INFO, true))
685             {
686                 continue;
687             }
688             primFields = referencedClass.getPrimaryKeys();
689             if (primFields.size() != keyFields.size())
690             {
691                 throw new ConstraintException("The number of foreignkeys ("+keyFields.size()+") of the reference "+refDef.getName()+" in class "+refDef.getOwner().getName()+" doesn't match the number of primarykeys ("+primFields.size()+") of the referenced class (or its subclass) "+referencedClass.getName());
692             }
693             for (int idx = 0; idx < primFields.size(); idx++)
694             {
695                 keyField = (FieldDescriptorDef)keyFields.get(idx);
696                 primField = (FieldDescriptorDef)primFields.get(idx);
697                 primType = primField.getProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE);
698                 keyType = keyField.getProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE);
699                 if (!primType.equals(keyType))
700                 {
701                     throw new ConstraintException("The jdbc-type of foreignkey "+keyField.getName()+" of the reference "+refDef.getName()+" in class "+refDef.getOwner().getName()+" doesn't match the jdbc-type of the corresponding primarykey "+primField.getName()+" of the referenced class (or its subclass) "+referencedClass.getName());
702                 }
703             }
704         }
705     }
706
707     /**
708      * Checks the modifications of fields used as foreignkeys in references/collections or the corresponding primarykeys,
709      * e.g. that the jdbc-type is not changed etc.
710      *
711      * @param modelDef The model to check
712      * @param checkLevel The current check level (this constraint is checked in basic and strict)
713      * @throws ConstraintException If such a field has invalid modifications
714      */

715     private void checkKeyModifications(ModelDef modelDef, String JavaDoc checkLevel) throws ConstraintException
716     {
717         if (CHECKLEVEL_NONE.equals(checkLevel))
718         {
719             return;
720         }
721
722         ClassDescriptorDef classDef;
723         FieldDescriptorDef fieldDef;
724
725         // we check for every inherited field
726
for (Iterator JavaDoc classIt = modelDef.getClasses(); classIt.hasNext();)
727         {
728             classDef = (ClassDescriptorDef)classIt.next();
729             for (Iterator JavaDoc fieldIt = classDef.getFields(); fieldIt.hasNext();)
730             {
731                 fieldDef = (FieldDescriptorDef)fieldIt.next();
732                 if (fieldDef.isInherited())
733                 {
734                     checkKeyModifications(modelDef, fieldDef);
735                 }
736             }
737         }
738     }
739
740     /**
741      * Checks the modifications of the given inherited field if it is used as a foreignkey in a
742      * reference/collection or as the corresponding primarykey, e.g. that the jdbc-type is not changed etc.
743      *
744      * @param modelDef The model to check
745      * @throws ConstraintException If the field has invalid modifications
746      */

747     private void checkKeyModifications(ModelDef modelDef, FieldDescriptorDef keyDef) throws ConstraintException
748     {
749         // we check the field if it changes the primarykey-status or the jdbc-type
750
FieldDescriptorDef baseFieldDef = (FieldDescriptorDef)keyDef.getOriginal();
751         boolean isIgnored = keyDef.getBooleanProperty(PropertyHelper.OJB_PROPERTY_IGNORE, false);
752         boolean changesJdbcType = !baseFieldDef.getProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE).equals(keyDef.getProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE));
753         boolean changesPrimary = baseFieldDef.getBooleanProperty(PropertyHelper.OJB_PROPERTY_PRIMARYKEY, false) !=
754                                              keyDef.getBooleanProperty(PropertyHelper.OJB_PROPERTY_PRIMARYKEY, false) ;
755
756         if (isIgnored || changesJdbcType || changesPrimary)
757         {
758             FeatureDescriptorDef usingFeature = null;
759
760             do
761             {
762                 usingFeature = usedByReference(modelDef, baseFieldDef);
763                 if (usingFeature != null)
764                 {
765                     if (isIgnored)
766                     {
767                         throw new ConstraintException("Cannot ignore field "+keyDef.getName()+" in class "+keyDef.getOwner().getName()+
768                                                       " because it is used in class "+baseFieldDef.getOwner().getName()+
769                                                       " by the reference "+usingFeature.getName()+" from class "+
770                                                       usingFeature.getOwner().getName());
771                     }
772                     else if (changesJdbcType)
773                     {
774                         throw new ConstraintException("Modification of the jdbc-type for the field "+keyDef.getName()+" in class "+
775                                                       keyDef.getOwner().getName()+" is not allowed because it is used in class "+
776                                                       baseFieldDef.getOwner().getName()+" by the reference "+usingFeature.getName()+
777                                                       " from class "+usingFeature.getOwner().getName());
778                     }
779                     else
780                     {
781                         throw new ConstraintException("Cannot change the primarykey status of field "+keyDef.getName()+" in class "+
782                                                       keyDef.getOwner().getName()+" as primarykeys are used in class "+
783                                                       baseFieldDef.getOwner().getName()+" by the reference "+usingFeature.getName()+
784                                                       " from class "+usingFeature.getOwner().getName());
785                     }
786                 }
787
788                 usingFeature = usedByCollection(modelDef, baseFieldDef, changesPrimary);
789                 if (usingFeature != null)
790                 {
791                     if (isIgnored)
792                     {
793                         throw new ConstraintException("Cannot ignore field "+keyDef.getName()+" in class "+keyDef.getOwner().getName()+
794                                                       " because it is used in class "+baseFieldDef.getOwner().getName()+
795                                                       " as a foreignkey of the collection "+usingFeature.getName()+" from class "+
796                                                       usingFeature.getOwner().getName());
797                     }
798                     else if (changesJdbcType)
799                     {
800                         throw new ConstraintException("Modification of the jdbc-type for the field "+keyDef.getName()+" in class "+
801                                                       keyDef.getOwner().getName()+" is not allowed because it is used in class "+
802                                                       baseFieldDef.getOwner().getName()+" as a foreignkey of the collecton "+
803                                                       usingFeature.getName()+" from class "+usingFeature.getOwner().getName());
804                     }
805                     else
806                     {
807                         throw new ConstraintException("Cannot change the primarykey status of field "+keyDef.getName()+" in class "+
808                                                       keyDef.getOwner().getName()+" as primarykeys are used in class "+
809                                                       baseFieldDef.getOwner().getName()+" by the collection "+usingFeature.getName()+
810                                                       " from class "+usingFeature.getOwner().getName());
811                     }
812                 }
813
814                 baseFieldDef = (FieldDescriptorDef)baseFieldDef.getOriginal();
815             }
816             while (baseFieldDef != null);
817         }
818     }
819
820     /**
821      * Checks whether the given field definition is used as a remote-foreignkey in an m:n
822      * association where the class owning the field has no collection for the association.
823      *
824      * @param modelDef The model
825      * @param fieldDef The current field descriptor def
826      * @param elementClassSuffices Whether it suffices that the owner class of the field is an
827      * element class of a collection (for primary key tests)
828      * @return The collection that uses the field or <code>null</code> if the field is not
829      * used in this way
830      */

831     private CollectionDescriptorDef usedByCollection(ModelDef modelDef, FieldDescriptorDef fieldDef, boolean elementClassSuffices)
832     {
833         ClassDescriptorDef ownerClass = (ClassDescriptorDef)fieldDef.getOwner();
834         String JavaDoc ownerClassName = ownerClass.getQualifiedName();
835         String JavaDoc name = fieldDef.getName();
836         ClassDescriptorDef classDef;
837         CollectionDescriptorDef collDef;
838         String JavaDoc elementClassName;
839
840         for (Iterator JavaDoc classIt = modelDef.getClasses(); classIt.hasNext();)
841         {
842             classDef = (ClassDescriptorDef)classIt.next();
843             for (Iterator JavaDoc collIt = classDef.getCollections(); collIt.hasNext();)
844             {
845                 collDef = (CollectionDescriptorDef)collIt.next();
846                 elementClassName = collDef.getProperty(PropertyHelper.OJB_PROPERTY_ELEMENT_CLASS_REF).replace('$', '.');
847                 // if the owner class of the field is the element class of a normal collection
848
// and the field is a foreignkey of this collection
849
if (ownerClassName.equals(elementClassName))
850                 {
851                     if (collDef.hasProperty(PropertyHelper.OJB_PROPERTY_INDIRECTION_TABLE))
852                     {
853                         if (elementClassSuffices)
854                         {
855                             return collDef;
856                         }
857                     }
858                     else if (new CommaListIterator(collDef.getProperty(PropertyHelper.OJB_PROPERTY_FOREIGNKEY)).contains(name))
859                     {
860                         // if the field is a foreignkey of this normal 1:n collection
861
return collDef;
862                     }
863                 }
864             }
865         }
866         return null;
867     }
868
869     /**
870      * Checks whether the given field definition is used as the primary key of a class referenced by
871      * a reference.
872      *
873      * @param modelDef The model
874      * @param fieldDef The current field descriptor def
875      * @return The reference that uses the field or <code>null</code> if the field is not used in this way
876      */

877     private ReferenceDescriptorDef usedByReference(ModelDef modelDef, FieldDescriptorDef fieldDef)
878     {
879         String JavaDoc ownerClassName = ((ClassDescriptorDef)fieldDef.getOwner()).getQualifiedName();
880         ClassDescriptorDef classDef;
881         ReferenceDescriptorDef refDef;
882         String JavaDoc targetClassName;
883
884         // only relevant for primarykey fields
885
if (PropertyHelper.toBoolean(fieldDef.getProperty(PropertyHelper.OJB_PROPERTY_PRIMARYKEY), false))
886         {
887             for (Iterator JavaDoc classIt = modelDef.getClasses(); classIt.hasNext();)
888             {
889                 classDef = (ClassDescriptorDef)classIt.next();
890                 for (Iterator JavaDoc refIt = classDef.getReferences(); refIt.hasNext();)
891                 {
892                     refDef = (ReferenceDescriptorDef)refIt.next();
893                     targetClassName = refDef.getProperty(PropertyHelper.OJB_PROPERTY_CLASS_REF).replace('$', '.');
894                     if (ownerClassName.equals(targetClassName))
895                     {
896                         // the field is a primary key of the class referenced by this reference descriptor
897
return refDef;
898                     }
899                 }
900             }
901         }
902         return null;
903     }
904 }
905
Popular Tags