KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sun > jdo > api > persistence > model > util > ModelValidator


1 /*
2  * The contents of this file are subject to the terms
3  * of the Common Development and Distribution License
4  * (the License). You may not use this file except in
5  * compliance with the License.
6  *
7  * You can obtain a copy of the license at
8  * https://glassfish.dev.java.net/public/CDDLv1.0.html or
9  * glassfish/bootstrap/legal/CDDLv1.0.txt.
10  * See the License for the specific language governing
11  * permissions and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL
14  * Header Notice in each file and include the License file
15  * at glassfish/bootstrap/legal/CDDLv1.0.txt.
16  * If applicable, add the following below the CDDL Header,
17  * with the fields enclosed by brackets [] replaced by
18  * you own identifying information:
19  * "Portions Copyrighted [year] [name of copyright owner]"
20  *
21  * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
22  */

23
24 /*
25  * ModelValidator.java
26  *
27  * Created on September 22, 2000, 12:49 PM
28  */

29
30 package com.sun.jdo.api.persistence.model.util;
31
32 import java.util.*;
33 import java.lang.reflect.Modifier JavaDoc;
34
35 import org.netbeans.modules.dbschema.*;
36 import org.netbeans.modules.dbschema.util.NameUtil;
37 import org.netbeans.modules.dbschema.util.SQLTypeUtil;
38
39 import com.sun.jdo.api.persistence.model.Model;
40 import com.sun.jdo.api.persistence.model.jdo.*;
41 import com.sun.jdo.api.persistence.model.mapping.*;
42 import com.sun.jdo.spi.persistence.utility.*;
43 import com.sun.jdo.spi.persistence.utility.logging.Logger;
44
45 /**
46  *
47  * @author Rochelle Raccah
48  * @version %I%
49  */

50 public class ModelValidator
51 {
52     /** This field holds the model object used for validation */
53     private Model _model;
54
55     /** This field holds the name of the class being validated */
56     private String JavaDoc _className;
57
58     /** This field holds the class loader used to load class
59      * being validated (if available).
60      */

61     private ClassLoader JavaDoc _classLoader;
62
63     /** I18N message handler */
64     private ResourceBundle _messages;
65
66     public ModelValidator (Model model, String JavaDoc className, ResourceBundle bundle)
67     {
68         this(model, className, null, bundle);
69     }
70
71     /** Create a new model validator object.
72      * @param model model object used for validation
73      * @param className the name of the class being validated
74      */

75     public ModelValidator (Model model, String JavaDoc className,
76         ClassLoader JavaDoc classLoader, ResourceBundle bundle)
77     {
78         _model = model;
79         _className = className;
80         _classLoader = classLoader;
81         _messages = bundle;
82     }
83
84     /**
85      * Get the model object used for validation.
86      * @return the model object used for validation
87      */

88     public Model getModel () { return _model; }
89
90     /**
91      * Get the name of the class being validated.
92      * @return the name of the class being validated
93      */

94     public String JavaDoc getClassName () { return _className; }
95
96     /**
97      * Get the class loader used to load the class being validated.
98      * @return the class loader of the class being validated
99      */

100     public ClassLoader JavaDoc getClassLoader () { return _classLoader; }
101
102     /** @return I18N message handler for this element
103      */

104     protected ResourceBundle getMessages () { return _messages; }
105
106     /** Main method used for parsing the combination of java (or class)
107      * information and mapping/jdo information by running through a subset
108      * of the full validation check and aborting (and returning
109      * <code>false</code> at the first error or warning.
110      * @return <code>true</code> if no errors or warnings occur,
111      * <code>false</code> otherwise.
112      * @see #getBasicValidationList
113      */

114     public boolean parseCheck ()
115     {
116         Iterator iterator = getBasicValidationList().iterator();
117
118         try
119         {
120             while (iterator.hasNext())
121                 ((ValidationComponent)iterator.next()).validate();
122         }
123         catch (ModelValidationException e)
124         {
125             LogHelperModel.getLogger().log(Logger.FINER,
126                 "model.parse_error", e); // NOI18N
127

128             return false;
129         }
130
131         return true;
132     }
133
134     /** Main method used for validating the combination of java (or class)
135      * information and mapping/jdo information by running through the full
136      * validation check and returning a collection of
137      * ModelValidationExceptions containing any errors or warnings encountered.
138      * @return a collection of ModelValidationExceptions containing any
139      * errors or warnings encountered. If no errors or warnings were
140      * encountered, the collection will be empty, not <code>null</code>.
141      * @see #getFullValidationList
142      */

143     public Collection fullValidationCheck ()
144     {
145         ArrayList list = new ArrayList();
146         Iterator iterator = getFullValidationList().iterator();
147
148         while (iterator.hasNext())
149         {
150             try
151             {
152                 ((ValidationComponent)iterator.next()).validate();
153             }
154             catch (ModelValidationException e)
155             {
156                 list.add(e);
157             }
158         }
159
160         return Collections.unmodifiableCollection(list);
161     }
162
163     // ================ Validation list construction methods ===============
164

165     /** Computes and returns a collection of ValidationComponents
166      * representing the tests to be performed during parse.
167      * @return a collection of ValidationComponents representing the
168      * tests to be performed during parse.
169      * @see #getDatabaseValidationList
170      * @see #getFieldsValidationList
171      * @see #getFullValidationList
172      */

173     public Collection getBasicValidationList ()
174     {
175         ArrayList list = new ArrayList();
176         String JavaDoc className = getClassName();
177
178         list.add(createClassExistenceComponent(className));
179         list.add(createClassPersistenceComponent(className));
180
181         list.addAll(getDatabaseValidationList());
182         list.addAll(getFieldsValidationList());
183
184         return Collections.unmodifiableCollection(list);
185     }
186     
187     /** Computes and returns a collection of ValidationComponents
188      * representing the tests to be performed during validation. These
189      * include all those in the basic list plus those which check
190      * cardinality and the related classes in more detail.
191      * @return a collection of ValidationComponents representing the
192      * tests to be performed during validation.
193      * @see #getRelatedClassValidationList
194      * @see #getBasicValidationList
195      */

196     public Collection getFullValidationList ()
197     {
198         ArrayList list = new ArrayList(getBasicValidationList());
199         String JavaDoc className = getClassName();
200         PersistenceClassElement persistenceClass =
201             getPersistenceClass(className);
202
203         if (persistenceClass != null)
204         {
205             PersistenceFieldElement[] fields = persistenceClass.getFields();
206             int i, count = ((fields != null) ? fields.length : 0);
207
208             list.add(createSerializableClassComponent(className));
209             list.add(createKeyClassComponent(persistenceClass.getKeyClass()));
210             list.add(createClassMappingComponent(persistenceClass));
211             list.add(createKeyColumnMappingComponent(persistenceClass));
212
213             for (i = 0; i < count; i++)
214             {
215                 PersistenceFieldElement field = fields[i];
216
217                 list.add(createFieldCardinalityComponent(field));
218                 list.add(createFieldMappingComponent(field));
219                 list.add(createFieldBlobMappingComponent(field));
220                 list.addAll(getRelatedClassValidationList(field));
221             }
222         }
223
224         return Collections.unmodifiableCollection(list);
225     }
226
227     // ============= Validation list construction suppport methods ============
228

229     /** Computes and returns a collection of ValidationComponents
230      * representing the database tests to be performed.
231      * @return a collection of ValidationComponents representing the
232      * database tests to be performed.
233      */

234     private Collection getDatabaseValidationList ()
235     {
236         ArrayList list = new ArrayList();
237         String JavaDoc className = getClassName();
238         MappingClassElement mappingClass = getMappingClass(className);
239
240         if (mappingClass != null)
241         {
242             ArrayList tables = mappingClass.getTables();
243             int i, count = ((tables != null) ? tables.size() : 0);
244             MappingTableElement primaryTable = null;
245             Iterator iterator = null;
246
247             list.add(createSchemaExistenceComponent(className));
248
249             for (i = 0; i < count; i++)
250             {
251                 MappingTableElement nextTable =
252                     (MappingTableElement)tables.get(i);
253
254                 list.add(createTableExistenceComponent(nextTable.getTable()));
255
256                 if (i == 0)
257                 {
258                     primaryTable = nextTable;
259                     list.add(createPrimaryTableComponent(primaryTable));
260                 }
261                 else
262                 {
263                     MappingReferenceKeyElement referenceKey =
264                         findReferenceKey(primaryTable, nextTable);
265
266                     if (referenceKey != null)
267                     {
268                         iterator = referenceKey.getColumnPairNames().iterator();
269                         while (iterator.hasNext())
270                         {
271                             list.add(createColumnExistenceComponent(
272                                 (String JavaDoc)iterator.next()));
273                         }
274                     }
275                 }
276             }
277
278             list.add(createVersionConsistencyComponent(mappingClass));
279
280             iterator = mappingClass.getFields().iterator();
281             while (iterator.hasNext())
282             {
283                 MappingFieldElement nextField =
284                     (MappingFieldElement)iterator.next();
285                 ArrayList allColumns = new ArrayList();
286                 Iterator columnIterator = null;
287
288                 if (isRelationship(nextField))
289                 {
290                     allColumns.addAll(((MappingRelationshipElement)nextField).
291                         getAssociatedColumns());
292                 }
293
294                 allColumns.addAll(nextField.getColumns());
295
296                 columnIterator = allColumns.iterator();
297                 while (columnIterator.hasNext())
298                 {
299                     list.add(createColumnExistenceComponent(
300                         (String JavaDoc)columnIterator.next(), nextField));
301                 }
302             }
303         }
304
305         return list;
306     }
307
308     /** Computes and returns a collection of ValidationComponents
309      * representing the field and relationship tests to be performed.
310      * @return a collection of ValidationComponents representing the
311      * field and relationship tests to be performed.
312      */

313     private Collection getFieldsValidationList ()
314     {
315         ArrayList list = new ArrayList();
316         Model model = getModel();
317         String JavaDoc className = getClassName();
318         PersistenceClassElement persistenceClass =
319             getPersistenceClass(className);
320
321         if (persistenceClass != null)
322         {
323             PersistenceFieldElement[] fields = persistenceClass.getFields();
324             int i, count = ((fields != null) ? fields.length : 0);
325             Iterator iterator =
326                 getMappingClass(className).getFields().iterator();
327
328             for (i = 0; i < count; i++)
329             {
330                 PersistenceFieldElement field = fields[i];
331
332                 list.add(createFieldExistenceComponent(field));
333
334                 // even though this is really the validation step, we
335
// only want to add the others if the field exists
336
if (model.hasField(className, field.getName()))
337                 {
338                     list.add(createFieldPersistenceComponent(field));
339                     list.add(createFieldPersistenceTypeComponent(field));
340                     list.add(createFieldConsistencyComponent(field));
341
342                     if (isLegalRelationship(field))
343                     {
344                         RelationshipElement rel = (RelationshipElement)field;
345
346                         /* user modifiable collection class not yet supported
347                         list.add(createCollectionClassComponent(rel));*/

348                         list.add(createElementClassComponent(rel));
349                         list.add(createRelatedClassMatchesComponent(rel));
350                     }
351                 }
352             }
353
354             while (iterator.hasNext())
355             {
356                 MappingFieldElement field =
357                     (MappingFieldElement)iterator.next();
358                 String JavaDoc fieldName = field.getName();
359
360                 // only check this if it is not in the jdo model
361
if (persistenceClass.getField(fieldName) == null)
362                 {
363                     list.add(createFieldExistenceComponent(field));
364
365                     // even though this is really the validation step, we
366
// only want to add the others if the field exists
367
if (model.hasField(className, fieldName))
368                         list.add(createFieldConsistencyComponent(field));
369                 }
370
371                 if (!isRelationship(field))
372                     list.add(createColumnOverlapComponent(field));
373
374                 // preliminary fix for CR6239630
375
if (Boolean.getBoolean("AllowManagedFieldsInDefaultFetchGroup")) // NOI18N
376
{
377                                     // Do nothing - AllowManagedFieldsInDefaultFetchGroup:
378
// disabled single model validation test;
379
// may use checked read/write access to managed fields
380
}
381                 else
382                 {
383                     list.add(createFieldDefaultFetchGroupComponent(field));
384                 }
385             }
386         }
387
388         return list;
389     }
390
391     /** Computes and returns a collection of ValidationComponents
392      * representing the related class tests to be performed. Right now,
393      * these are only included as part of full validation, as they may
394      * be somewhat time intensive since they compute information about
395      * other classes as well as this class.
396      * @return a collection of ValidationComponents representing the
397      * related class tests to be performed.
398      */

399     private Collection getRelatedClassValidationList (
400         PersistenceFieldElement field)
401     {
402         String JavaDoc relatedClass = getRelatedClass(field);
403         ArrayList list = new ArrayList();
404
405         // even though this is really already included in the validation
406
// step, we only want to add the extra steps if the field exists
407
if ((relatedClass != null) &&
408             getModel().hasField(getClassName(), field.getName()))
409         {
410             MappingClassElement relatedClassElement =
411                 getMappingClass(relatedClass);
412
413             list.add(createClassExistenceComponent(relatedClass, field));
414             list.add(createClassPersistenceComponent(relatedClass, field));
415             list.add(createSchemaExistenceComponent(relatedClass, field));
416             list.add(createRelatedSchemaMatchesComponent(relatedClass, field));
417
418             if (relatedClassElement != null)
419             {
420                 ArrayList tables = relatedClassElement.getTables();
421                 MappingTableElement primaryTable = null;
422                 boolean hasTables = ((tables != null) && (tables.size() > 0));
423
424                 if (hasTables)
425                 {
426                     primaryTable = (MappingTableElement)tables.get(0);
427                     list.add(createTableExistenceComponent(
428                         primaryTable.getTable(), field));
429                 }
430
431                 if (isRelationship(field))
432                 {
433                     RelationshipElement relElement = (RelationshipElement)field;
434                     Object JavaDoc rel = getMappingClass(getClassName()).
435                         getField(field.getName());
436
437                     list.add(createInverseFieldComponent(relElement));
438                     list.add(createInverseMappingComponent(relElement));
439
440                     // verify that the columns from the primary table
441
// of the related class are actually from that table
442
// since it could have been changed
443
if ((rel != null) && isRelationship(rel))
444                     {
445                         MappingRelationshipElement relationship =
446                             (MappingRelationshipElement)rel;
447                         ArrayList columns =
448                             relationship.getAssociatedColumns();
449                         Iterator iterator = null;
450
451                         if ((columns == null) || (columns.size() == 0))
452                             columns = relationship.getColumns();
453
454                         if (columns != null)
455                         {
456                             List tableNames = new ArrayList();
457
458                             if (hasTables)
459                             {
460                                 Iterator tableIterator = tables.iterator();
461
462                                 while (tableIterator.hasNext())
463                                 {
464                                     tableNames.add(((MappingTableElement)
465                                         tableIterator.next()).getName());
466                                 }
467                             }
468
469                             iterator = columns.iterator();
470
471                             while (iterator.hasNext())
472                             {
473                                 list.add(createRelatedTableMatchesComponent(
474                                     relatedClass, field, tableNames,
475                                     (String JavaDoc)iterator.next()));
476                             }
477                         }
478                     }
479                 }
480             }
481         }
482
483         return list;
484     }
485
486     // ================ Validation Component inner classes ===============
487

488     /** Create a validation component which can check whether the class exists.
489      * @param className the class whose existence is being checked
490      * @param relatedField the relationship field whose class is being checked,
491      * may be <code>null</code> in which case we are probably checking the
492      * same class as the validator is checking overall
493      * @return the validation component
494      */

495     protected ValidationComponent createClassExistenceComponent (
496         final String JavaDoc className, final PersistenceFieldElement relatedField)
497     {
498         return new ValidationComponent ()
499         {
500             public void validate () throws ModelValidationException
501             {
502                 if ((className == null) ||
503                     !getModel().hasClass(className, getClassLoader()))
504                 {
505                     throw constructClassException(className, relatedField,
506                         "util.validation.class_not_found"); //NOI18N
507
}
508             }
509         };
510     }
511
512     /** Create a validation component which can check whether the class exists.
513      * @param className the class whose existence is being checked
514      * @return the validation component
515      */

516     protected ValidationComponent createClassExistenceComponent (
517         final String JavaDoc className)
518     {
519         return createClassExistenceComponent(className, null);
520     }
521
522     /** Create a validation component which can check the class persistence.
523      * @param className the class whose persistence is being checked
524      * @param relatedField the relationship field whose class is being checked,
525      * may be <code>null</code> in which case we are probably checking the
526      * same class as the validator is checking overall
527      * @return the validation component
528      */

529     protected ValidationComponent createClassPersistenceComponent (
530         final String JavaDoc className, final PersistenceFieldElement relatedField)
531     {
532         return new ValidationComponent ()
533         {
534             public void validate () throws ModelValidationException
535             {
536                 Model model = getModel();
537     
538                 if ((className != null) &&
539                      model.hasClass(className, getClassLoader()))
540                 {
541                     String JavaDoc key = null;
542
543                     if (!isPersistent(className))
544                         key = "util.validation.class_not_persistence_capable";//NOI18N
545
else if (!model.isPersistenceCapableAllowed(className))
546                         key = "util.validation.class_not_allowed";//NOI18N
547

548                     if (key != null)
549                     {
550                         throw constructClassException(
551                             className, relatedField, key);
552                     }
553                 }
554             }
555         };
556     }
557
558     /** Create a validation component which can check the class persistence.
559      * @param className the class whose persistence is being checked
560      * @return the validation component
561      */

562     protected ValidationComponent createClassPersistenceComponent (
563         final String JavaDoc className)
564     {
565         return createClassPersistenceComponent(className, null);
566     }
567
568     /** Create a validation component which can check whether the field exists.
569      * @param fieldName the field whose existence is being checked
570      * @return the validation component
571      */

572     protected ValidationComponent createFieldExistenceComponent (
573         final String JavaDoc fieldName)
574     {
575         return new ValidationComponent ()
576         {
577             public void validate () throws ModelValidationException
578             {
579                 if (!getModel().hasField(getClassName(), fieldName))
580                 {
581                     throw constructFieldException(fieldName,
582                         "util.validation.field_not_found"); //NOI18N
583
}
584             }
585         };
586     }
587
588     /** Create a validation component which can check whether the field exists.
589      * @param field the field whose existence is being checked
590      * @return the validation component
591      */

592     protected ValidationComponent createFieldExistenceComponent (Object JavaDoc field)
593     {
594         return createFieldExistenceComponent(field.toString());
595     }
596
597     /** Create a validation component which can check whether the field is
598      * persistent.
599      * @param field the field whose persistence is being checked
600      * @return the validation component
601      */

602     protected ValidationComponent createFieldPersistenceComponent (
603         final PersistenceFieldElement field)
604     {
605         return new ValidationComponent ()
606         {
607             public void validate () throws ModelValidationException
608             {
609                 boolean isPersistent = (PersistenceFieldElement.PERSISTENT ==
610                     field.getPersistenceType());
611                 String JavaDoc fieldName = field.getName();
612
613                 if (isPersistent &&
614                     !isPersistentAllowed(getClassName(), fieldName))
615                 {
616                     throw constructFieldException(fieldName,
617                         "util.validation.field_persistent_not_allowed");//NOI18N
618
}
619             }
620         };
621     }
622
623     /** Create a validation component which can check whether the field is
624      * consistent (field in both models or relationship in both).
625      * @param field the field whose consistency is being checked
626      * @return the validation component
627      */

628     protected ValidationComponent createFieldConsistencyComponent (
629         final PersistenceFieldElement field)
630     {
631         return new ValidationComponent ()
632         {
633             public void validate () throws ModelValidationException
634             {
635                 String JavaDoc fieldName = field.getName();
636                 String JavaDoc className = getClassName();
637                 boolean isLegallyPersistent =
638                     isPersistentAllowed(className, fieldName);
639
640                 if (isLegallyPersistent)
641                 {
642                     MappingClassElement mappingClass =
643                         getMappingClass(className);
644                     MappingFieldElement mappingElement =
645                         ((mappingClass != null) ?
646                         mappingClass.getField(fieldName) : null);
647
648                     if (mappingElement != null)
649                     {
650                         boolean jdoIsRelationship = isLegalRelationship(field);
651
652                         if (jdoIsRelationship != isRelationship(mappingElement))
653                         {
654                             throw constructFieldException(fieldName,
655                                 "util.validation.field_type_inconsistent"); //NOI18N
656
}
657                     }
658                 }
659             }
660         };
661     }
662
663     /** Create a validation component which can check whether the field is
664      * consistent (if in mapping model but not jdo, it is a problem).
665      * @param field the field whose consistency is being checked
666      * @return the validation component
667      */

668     protected ValidationComponent createFieldConsistencyComponent (
669         final MappingFieldElement field)
670     {
671         return new ValidationComponent ()
672         {
673             public void validate () throws ModelValidationException
674             {
675                 if (field != null)
676                 {
677                     String JavaDoc fieldName = field.getName();
678                     PersistenceClassElement persistenceClass =
679                         getPersistenceClass(getClassName());
680                     PersistenceFieldElement persistenceElement =
681                         ((persistenceClass != null) ?
682                         persistenceClass.getField(fieldName) : null);
683
684                     if (persistenceElement == null)
685                     {
686                         throw constructFieldException(fieldName,
687                             "util.validation.field_model_inconsistent");//NOI18N
688
}
689                 }
690             }
691         };
692     }
693
694     /** Create a validation component which can check the persistence type
695      * of the field (whether it is a relationship or not).
696      * @param field the field whose persistence type is being checked
697      * @return the validation component
698      */

699     protected ValidationComponent createFieldPersistenceTypeComponent (
700         final PersistenceFieldElement field)
701     {
702         return new ValidationComponent ()
703         {
704             public void validate () throws ModelValidationException
705             {
706                 String JavaDoc fieldName = field.getName();
707                 String JavaDoc className = getClassName();
708                 boolean isLegallyPersistent =
709                     isPersistentAllowed(className, fieldName);
710
711                 if (isLegallyPersistent)
712                 {
713                     boolean isRelationship = isRelationship(field);
714                     boolean mustBeRelationship = shouldBeRelationship(field);
715
716                     if (isRelationship && !mustBeRelationship)
717                     {
718                         throw constructFieldException(fieldName,
719                             "util.validation.field_relationship_not_allowed");//NOI18N
720
}
721                     else if (!isRelationship && mustBeRelationship)
722                     {
723                         throw constructFieldException(fieldName,
724                             "util.validation.field_type_not_allowed"); //NOI18N
725
}
726                 }
727             }
728         };
729     }
730
731     /** Create a validation component which can check whether the cardinality
732      * bounds are semantically valid given the relationship field type.
733      * @param field the relationship whose cardinality bounds are being checked
734      * @return the validation component
735      */

736     protected ValidationComponent createFieldCardinalityComponent (
737         final PersistenceFieldElement field)
738     {
739         return new ValidationComponent ()
740         {
741             public void validate () throws ModelValidationException
742             {
743                 if (isLegalRelationship(field))
744                 {
745                     RelationshipElement relationship =
746                         (RelationshipElement)field;
747                     String JavaDoc fieldName = field.getName();
748                     boolean nonCollectionRelationship =
749                         !isCollection(getClassName(), fieldName);
750                     int upperBound = (nonCollectionRelationship ?
751                         1 : relationship.getUpperBound());
752                     int lowerBound = relationship.getLowerBound();
753                     MappingRelationshipElement mapping = null;
754
755                     if ((lowerBound < 0) || (upperBound <= 0) ||
756                         (lowerBound > upperBound))
757                     {
758                         throw constructFieldException(fieldName,
759                             "util.validation.cardinality_invalid"); //NOI18N
760
}
761
762                     // now check specific lower bound requirements imposed
763
// by the mapping
764
mapping = getMappingRelationship(relationship);
765                     if (nonCollectionRelationship && (lowerBound != 1) &&
766                         (mapping != null) && !isJoin(mapping))
767                     {
768                         // If the non-collection relationship field is exactly
769
// mapped to a FK, we need to check the nullability
770
// of the columns. If there are any non-nullable
771
// columns the lower bound must be 1.
772
ForeignKeyElement fk = getMatchingFK(mapping);
773
774                         if ((fk != null) && hasNonNullableColumn(fk))
775                         {
776                             throw constructFieldException(fieldName,
777                                 "util.validation.lower_bound_invalid"); //NOI18N
778
}
779                     }
780                 }
781             }
782
783             /** Returns <code>true</code> if the specified FK has at least
784              * one non-nullable column. Please note that the caller is
785              * responsible for passing a non-null fk argument.
786              */

787             private boolean hasNonNullableColumn (ForeignKeyElement fk)
788             {
789                 ColumnElement[] localColumns = fk.getLocalColumns();
790                 int count = ((localColumns != null) ? localColumns.length : 0);
791                 
792                 for (int i = 0; i < count; i++)
793                 {
794                     if (!localColumns[i].isNullable())
795                         return true;
796                 }
797
798                 return false;
799             }
800
801             /** Checks whether the specified relationship is exactly mapped
802              * to a FK. If yes, the method returns the matching FK. If not,
803              * it returns <code>null</code>. Please note that the caller is
804              * responsible for passing a non-null mapping argument.
805              */

806             private ForeignKeyElement getMatchingFK (
807                 MappingRelationshipElement mapping)
808             {
809                 MappingClassElement mappingClass = mapping.
810                     getDeclaringClass();
811                 String JavaDoc databaseRoot = getSchemaForClass(getClassName());
812                 List pairNames = mapping.getColumns();
813                 List tables = mappingClass.getTables();
814                     
815                 if (tables != null)
816                 {
817                     for (Iterator i = tables.iterator(); i.hasNext();)
818                     {
819                         String JavaDoc tableName = ((MappingTableElement)i.next()).
820                             getName();
821                         TableElement table = getTable(tableName, databaseRoot);
822                         ForeignKeyElement fk = getMatchingFK(pairNames, table);
823                         
824                         if (fk != null)
825                             return fk;
826                     }
827                 }
828
829                 return null;
830             }
831
832             /** Checks whether the specified TableElement has a FK that
833              * exactly matches the list of column pair names.
834              * @return the matching FK if it exactly matches the list
835              * of column pairs; <code>null</code> otherwise.
836              */

837             private ForeignKeyElement getMatchingFK (List pairNames,
838                 TableElement table)
839             {
840                 ForeignKeyElement[] foreignKeys = (table != null) ?
841                     table.getForeignKeys() : null;
842                 int count = ((foreignKeys != null) ? foreignKeys.length : 0);
843
844                 for (int i = 0; i < count; i++)
845                 {
846                     if (matchesFK(pairNames, foreignKeys[i]))
847                         return foreignKeys[i];
848                 }
849
850                 return null;
851             }
852
853             /** Returns <code>true</code> if the specified list of column
854              * pair names matches exactly the specified FK.
855              */

856             private boolean matchesFK (List pairNames,
857                 ForeignKeyElement foreignKey)
858             {
859                 ColumnPairElement[] fkPairs = foreignKey.getColumnPairs();
860                 int fkCount = ((fkPairs != null) ? fkPairs.length : 0);
861                 int count = ((pairNames != null) ? pairNames.size() : 0);
862
863                 // First check whether the list of fk column pairs has the
864
// same size than the specified list of columns.
865
if (fkCount == count)
866                 {
867                     // Now check whether each fk column is included in the
868
// specified list of columns.
869
for (int i = 0; i < fkCount; i++)
870                     {
871                         String JavaDoc fkPairName = NameUtil.getRelativeMemberName(
872                             fkPairs[i].getName().getFullName());
873
874                         if (!pairNames.contains(fkPairName))
875                             return false;
876                     }
877
878                     return true;
879                 }
880
881                 return false;
882             }
883         };
884     }
885
886     /** Create a validation component which can check whether the field is
887      * unmapped.
888      * @param field the field whose mapping is being checked
889      * @return the validation component
890      */

891     protected ValidationComponent createFieldMappingComponent (
892         final PersistenceFieldElement field)
893     {
894         return new ValidationComponent ()
895         {
896             public void validate () throws ModelValidationException
897             {
898                 String JavaDoc fieldName = field.getName();
899                 MappingClassElement mappingClass =
900                     getMappingClass(getClassName());
901  
902                 if ((mappingClass != null) &&
903                     (mappingClass.getTables().size() > 0))
904                 {
905                     MappingFieldElement mappingField =
906                         mappingClass.getField(fieldName);
907
908                     if ((mappingField == null) ||
909                         (mappingField.getColumns().size() == 0))
910                     {
911                         throw constructFieldException(
912                             ModelValidationException.WARNING, fieldName,
913                             "util.validation.field_not_mapped"); //NOI18N
914
}
915                 }
916             }
917         };
918     }
919
920     /** Create a validation component which can check whether the field is
921      * mapped to a blob type and if so, whether it is a key field or belongs
922      * to the default fetch group. Note that it's somewhat important to check
923      * for the key field first because if a field is key, its fetch group
924      * value in the model is ignored.
925      * @param field the field whose mapping/key field/fetch group consistency
926      * is being checked
927      * @return the validation component
928      */

929     protected ValidationComponent createFieldBlobMappingComponent (
930         final PersistenceFieldElement field)
931     {
932         return new ValidationComponent ()
933         {
934             public void validate () throws ModelValidationException
935             {
936                 String JavaDoc className = getClassName();
937                 String JavaDoc fieldName = field.getName();
938                 MappingClassElement mappingClass = getMappingClass(className);
939                 MappingFieldElement mappingField = ((mappingClass != null) ?
940                     mappingClass.getField(fieldName) : null);
941
942                 if (mappingField != null)
943                 {
944                     boolean isKey = field.isKey();
945
946                     if (isKey || (MappingFieldElement.GROUP_DEFAULT ==
947                         mappingField.getFetchGroup()))
948                     {
949                         if (isMappedToBlob(mappingField,
950                             getSchemaForClass(className)))
951                         {
952                             throw constructFieldException(fieldName, (isKey ?
953                                 "util.validation.field_key_field_not_allowed" : //NOI18N
954
"util.validation.field_fetch_group_not_allowed")); // NOI18N
955
}
956                     }
957                 }
958             }
959             private boolean isMappedToBlob (MappingFieldElement mappingField,
960                 String JavaDoc schema)
961             {
962                 if (mappingField instanceof MappingRelationshipElement)
963                 {
964                     return isMappedToBlob(
965                         (MappingRelationshipElement)mappingField, schema);
966                 }
967                 else
968                 {
969                     Iterator iterator = mappingField.getColumns().iterator();
970
971                     while (iterator.hasNext())
972                     {
973                         String JavaDoc absoluteName = NameUtil.getAbsoluteMemberName(
974                             schema, (String JavaDoc)iterator.next());
975                         TableElement table = TableElement.forName(
976                             NameUtil.getTableName(absoluteName));
977                         ColumnElement columnElement = ((table != null) ?
978                             (ColumnElement)table.getMember(
979                             DBIdentifier.create(absoluteName)) : null);
980
981                         if (isMappedToBlob(columnElement))
982                             return true;
983                     }
984                 }
985
986                 return false;
987             }
988             private boolean isMappedToBlob (MappingRelationshipElement rel,
989                 String JavaDoc schema)
990             {
991                 Iterator iterator = rel.getColumns().iterator();
992
993                 while (iterator.hasNext())
994                 {
995                     ColumnPairElement pair =
996                         getPair((String JavaDoc)iterator.next(), schema);
997
998                     if (isMappedToBlob(pair))
999                         return true;
1000                }
1001
1002                // now check join columns
1003
iterator = rel.getAssociatedColumns().iterator();
1004                while (iterator.hasNext())
1005                {
1006                    ColumnPairElement pair =
1007                        getPair((String JavaDoc)iterator.next(), schema);
1008
1009                    if (isMappedToBlob(pair))
1010                        return true;
1011                }
1012
1013                return false;
1014            }
1015            private boolean isMappedToBlob (ColumnPairElement pair)
1016            {
1017                return ((pair == null) ? false :
1018                    isMappedToBlob(pair.getLocalColumn()) &&
1019                    isMappedToBlob(pair.getReferencedColumn()));
1020            }
1021            private boolean isMappedToBlob (ColumnElement column)
1022            {
1023                return ((column != null) &&
1024                    SQLTypeUtil.isBlob(column.getType()));
1025            }
1026        };
1027    }
1028
1029    /** Create a validation component which can check whether the collection
1030     * class is valid given the relationship field type.
1031     * @param field the relationship whose collection class is being checked
1032     * @return the validation component
1033     */

1034    protected ValidationComponent createCollectionClassComponent (
1035        final RelationshipElement field)
1036    {
1037        return new ValidationComponent ()
1038        {
1039            public void validate () throws ModelValidationException
1040            {
1041                String JavaDoc className = getClassName();
1042                String JavaDoc fieldName = field.getName();
1043
1044                if (isCollection(className, fieldName))
1045                {
1046                    Model model = getModel();
1047                    String JavaDoc collectionClass = field.getCollectionClass();
1048                    String JavaDoc fieldType = model.getFieldType(className, fieldName);
1049                    boolean missingCollectionClass =
1050                        StringHelper.isEmpty(collectionClass);
1051
1052                    if (!missingCollectionClass &&
1053                        !model.getSupportedCollectionClasses(fieldType).
1054                        contains(collectionClass))
1055                    {
1056                        throw constructFieldException(fieldName,
1057                            "util.validation.collection_class_invalid");//NOI18N
1058
}
1059                }
1060            }
1061        };
1062    }
1063
1064    /** Create a validation component which can check whether the
1065     * relationship is mapped to columns even though the element class is null.
1066     * @param field the relationship whose element class is being checked
1067     * @return the validation component
1068     */

1069    protected ValidationComponent createElementClassComponent (
1070        final RelationshipElement field)
1071    {
1072        return new ValidationComponent ()
1073        {
1074            public void validate () throws ModelValidationException
1075            {
1076                String JavaDoc className = getClassName();
1077                String JavaDoc fieldName = field.getName();
1078
1079                if (isCollection(className, fieldName))
1080                {
1081                    String JavaDoc elementClass = field.getElementClass();
1082
1083                    if (StringHelper.isEmpty(elementClass))
1084                    {
1085                        MappingClassElement mappingClass =
1086                            getMappingClass(className);
1087                        MappingFieldElement mappingElement =
1088                            ((mappingClass != null) ?
1089                            mappingClass.getField(fieldName) : null);
1090
1091                        if ((mappingElement != null) &&
1092                            (mappingElement.getColumns().size() > 0))
1093                        {
1094                            throw constructFieldException(fieldName,
1095                                "util.validation.element_class_not_found");//NOI18N
1096
}
1097                    }
1098                }
1099            }
1100        };
1101    }
1102
1103    /** Create a validation component which checks whether the rules for
1104     * version consistency are followed. This includes:
1105     * <ul>
1106     * <li> There must be exactly one version field defined.
1107     * <li> The version field must not be a relationship.
1108     * <li> The version field must not be a key field.
1109     * <li> The version field must be of java type (primitive) long.
1110     * <li> The version field must be in the default fetch group.
1111     * <li> The version field must be mapped to exactly 1 column from the
1112     * primary table.
1113     * <li> The column to which the version field is mapped must be of a
1114     * numeric type and non-nullable.
1115     * <li> The column to which the version field is mapped must not be a PK or
1116     * FK column.
1117     * </ul>
1118     * @param mappingClass the mapping class element whose consistency is being
1119     * checked
1120     * @return the validation component
1121     */

1122    protected ValidationComponent createVersionConsistencyComponent (
1123        final MappingClassElement mappingClass)
1124    {
1125        return new ValidationComponent ()
1126        {
1127            public void validate () throws ModelValidationException
1128            {
1129                // only bother to check for classes with version consistency
1130
if (MappingClassElement.VERSION_CONSISTENCY ==
1131                    mappingClass.getConsistencyLevel())
1132                {
1133                    MappingFieldElement versionField =
1134                         validateVersionFieldExistence();
1135                    String JavaDoc className = mappingClass.getName();
1136                    String JavaDoc fieldName = versionField.getName();
1137                    String JavaDoc columnName = null;
1138                    ColumnElement column = null;
1139
1140                    if (versionField instanceof MappingRelationshipElement)
1141                    {
1142                        throw constructFieldException(fieldName,
1143                            "util.validation.version_field_relationship_not_allowed");//NOI18N
1144
}
1145                    else if (MappingFieldElement.GROUP_DEFAULT !=
1146                        versionField.getFetchGroup()) // must be in DFG
1147
{
1148                        throw constructFieldException(fieldName,
1149                            "util.validation.version_field_fetch_group_invalid");//NOI18N
1150
}
1151
1152                    validatePersistenceFieldAttributes(className, fieldName);
1153                    columnName = validateVersionFieldMapping(versionField);
1154                    column = validateTableMatch(className, fieldName, columnName);
1155                    validateColumnAttributes(className, fieldName, column);
1156                }
1157            }
1158            /** Helper method validating the existence of the exactly one
1159             * version field.
1160             */

1161            private MappingFieldElement validateVersionFieldExistence ()
1162                throws ModelValidationException
1163            {
1164                List versionFields = mappingClass.getVersionFields();
1165
1166                // must have exactly 1 version field (for this release)
1167
if (versionFields.size() != 1)
1168                {
1169                    throw constructClassException(mappingClass.getName(),
1170                        null, "util.validation.version_field_cardinality"); //NOI18N
1171
}
1172
1173                return (MappingFieldElement)versionFields.get(0);
1174            }
1175            /** Helper method validating the attributes of the field in the
1176             * jdo model which corresponds to the version field.
1177             */

1178            private void validatePersistenceFieldAttributes (String JavaDoc className,
1179                String JavaDoc fieldName) throws ModelValidationException
1180            {
1181                Class JavaDoc fieldType = JavaTypeHelper.getPrimitiveClass(
1182                    getModel().getFieldType(className, fieldName));
1183                String JavaDoc keyName = null;
1184
1185                // must not be a key field
1186
if (getPersistenceClass(className).getField(fieldName).isKey())
1187                    keyName = "util.validation.version_field_key_field_not_allowed";//NOI18N
1188
else if (Long.TYPE != fieldType) // must be type long
1189
keyName = "util.validation.version_field_type_not_allowed";//NOI18N
1190

1191                if (keyName != null)
1192                    throw constructFieldException(fieldName, keyName);
1193            }
1194            /** Helper method validating the column name of the
1195             * version field mapping.
1196             */

1197            private String JavaDoc validateVersionFieldMapping (
1198                MappingFieldElement versionField)
1199                throws ModelValidationException
1200            {
1201                List columns = versionField.getColumns();
1202
1203                // must be mapped to exactly 1 column (for this release)
1204
if (columns.size() != 1)
1205                {
1206                    throw constructFieldException(versionField.getName(),
1207                        "util.validation.version_field_not_mapped"); //NOI18N
1208
}
1209
1210                return (String JavaDoc)columns.get(0);
1211            }
1212            /** Helper method validating the column mapping of the version
1213             * field is from the primary table.
1214             */

1215            private ColumnElement validateTableMatch (String JavaDoc className,
1216                String JavaDoc fieldName, String JavaDoc columnName)
1217                throws ModelValidationException
1218            {
1219                String JavaDoc schema = getSchemaForClass(className);
1220                String JavaDoc absoluteName =
1221                    NameUtil.getAbsoluteMemberName(schema, columnName);
1222                TableElement table =
1223                    TableElement.forName(NameUtil.getTableName(absoluteName));
1224                String JavaDoc primaryName = ((MappingTableElement)mappingClass.
1225                    getTables().get(0)).getName();
1226                TableElement pTable = getTable(primaryName, schema);
1227
1228                // column must be from the PT
1229
if (table != pTable)
1230                {
1231                    throw new ModelValidationException(
1232                        getModel().getField(className, fieldName),
1233                        I18NHelper.getMessage(getMessages(),
1234                        "util.validation.version_field_table_mismatch", //NOI18N
1235
new Object JavaDoc[]{columnName, fieldName, className}));
1236                }
1237
1238                return ((table != null) ? (ColumnElement)table.getMember(
1239                    DBIdentifier.create(absoluteName)) : null);
1240            }
1241            /** Helper method validating the attributes of the column of the
1242             * version field mapping.
1243             */

1244            private void validateColumnAttributes (String JavaDoc className,
1245                String JavaDoc fieldName, ColumnElement column)
1246                throws ModelValidationException
1247            {
1248                String JavaDoc keyName = null;
1249
1250                // column must be numeric type and non-nullable
1251
if (column.isNullable() || !column.isNumericType())
1252                    keyName = "util.validation.version_field_column_type_invalid"; // NOI18N
1253
else // column must be non-PK and non-FK column
1254
{
1255                    TableElement table = column.getDeclaringTable();
1256                    UniqueKeyElement[] uks = table.getUniqueKeys();
1257                    ForeignKeyElement[] fks = table.getForeignKeys();
1258                    int i, count = ((uks != null) ? uks.length : 0);
1259
1260                    for (i = 0; i < count; i++)
1261                    {
1262                        UniqueKeyElement uk = uks[i];
1263
1264                        if (uk.isPrimaryKey() && Arrays.asList(
1265                            uk.getColumns()).contains(column))
1266                        {
1267                            keyName = "util.validation.version_field_column_pk_invalid"; // NOI18N
1268
break;
1269                        }
1270                    }
1271
1272                    count = ((fks != null) ? fks.length : 0);
1273                    for (i = 0; i < count; i++)
1274                    {
1275                        ForeignKeyElement fk = fks[i];
1276
1277                        if (Arrays.asList(fk.getLocalColumns()).
1278                            contains(column))
1279                        {
1280                            keyName = "util.validation.version_field_column_fk_invalid"; // NOI18N
1281
break;
1282                        }
1283                    }
1284                }
1285
1286                if (keyName != null)
1287                {
1288                    throw new ModelValidationException(
1289                        getModel().getField(className, fieldName),
1290                        I18NHelper.getMessage(getMessages(), keyName,
1291                        new Object JavaDoc[]{column.getName(), fieldName, className}));
1292                }
1293            }
1294        };
1295    }
1296
1297    /** Create a validation component which can check whether the inverse of
1298     * the inverse of the relationship is the relationship itself.
1299     * @param field the relationship whose inverse relationship is being checked
1300     * @return the validation component
1301     */

1302    protected ValidationComponent createInverseFieldComponent (
1303        final RelationshipElement field)
1304    {
1305        return new ValidationComponent ()
1306        {
1307            public void validate () throws ModelValidationException
1308            {
1309                Model model = getModel();
1310                RelationshipElement inverse =
1311                    field.getInverseRelationship(model);
1312                RelationshipElement inverseInverse = ((inverse != null) ?
1313                    inverse.getInverseRelationship(model) : null);
1314
1315                if ((inverse != null) &&
1316                    (!field.equals(inverseInverse) || (inverseInverse == null)))
1317                {
1318                    String JavaDoc fieldName = field.getName();
1319
1320                    throw new ModelValidationException(
1321                        model.getField(getClassName(), fieldName),
1322                        I18NHelper.getMessage(getMessages(),
1323                        "util.validation.inverse_field_invalid", //NOI18N
1324
new Object JavaDoc[]{fieldName, inverse.getName()}));
1325                }
1326            }
1327        };
1328    }
1329
1330    /** Create a validation component which can check whether the inverse of
1331     * the relationship belongs to the related class (type or element class
1332     * depending on cardinality).
1333     * @param field the relationship whose inverse relationship is being checked
1334     * @return the validation component
1335     */

1336    protected ValidationComponent createRelatedClassMatchesComponent (
1337        final RelationshipElement field)
1338    {
1339        return new ValidationComponent ()
1340        {
1341            public void validate () throws ModelValidationException
1342            {
1343                String JavaDoc inverseName =
1344                    field.getInverseRelationshipName();
1345
1346                if (!StringHelper.isEmpty(inverseName))
1347                {
1348                    Model model = getModel();
1349                    RelationshipElement inverse =
1350                        field.getInverseRelationship(model);
1351
1352                    if (inverse == null) // no such field in that related class
1353
{
1354                        String JavaDoc relatedClass = getRelatedClass(field);
1355                        String JavaDoc fieldName = field.getName();
1356                        String JavaDoc key = ((relatedClass != null) ?
1357                            "util.validation.related_class_mismatch" : //NOI18N
1358
"util.validation.related_class_not_found");//NOI18N
1359
Object JavaDoc[] args = ((relatedClass != null) ?
1360                            new Object JavaDoc[]{fieldName, inverseName, relatedClass}
1361                            : new Object JavaDoc[]{fieldName, inverseName});
1362                            
1363                        throw new ModelValidationException(
1364                            model.getField(getClassName(), fieldName),
1365                            I18NHelper.getMessage(getMessages(), key, args));
1366                    }
1367                }
1368            }
1369        };
1370    }
1371
1372    /** Create a validation component which can check whether the mapping of
1373     * the relationship and the mapping of its inverse are inverses of each
1374     * other.
1375     * @param field the relationship whose inverse relationship is being checked
1376     * @return the validation component
1377     */

1378    protected ValidationComponent createInverseMappingComponent (
1379        final RelationshipElement field)
1380    {
1381        return new ValidationComponent ()
1382        {
1383            public void validate () throws ModelValidationException
1384            {
1385                Model model = getModel();
1386                RelationshipElement inverse =
1387                    field.getInverseRelationship(model);
1388
1389                if ((inverse != null) && !isInverseMapping(field, inverse))
1390                {
1391                    String JavaDoc fieldName = field.getName();
1392
1393                    throw new ModelValidationException(
1394                        model.getField(getClassName(), fieldName),
1395                        I18NHelper.getMessage(getMessages(),
1396                        "util.validation.inverse_mapping_mismatch", //NOI18N
1397
new Object JavaDoc[]{fieldName, inverse.getName()}));
1398                }
1399            }
1400            private boolean hasMappingRows (MappingRelationshipElement field2)
1401            {
1402                if (field2 != null)
1403                {
1404                    ArrayList columns = field2.getColumns();
1405                    
1406                    return ((columns != null) && !columns.isEmpty());
1407                }
1408
1409                return false;
1410            }
1411            private boolean isInverseMapping (RelationshipElement jdoField1,
1412                RelationshipElement jdoField2)
1413            {
1414                MappingRelationshipElement field1 =
1415                    getMappingRelationship(jdoField1);
1416                MappingRelationshipElement field2 =
1417                    getMappingRelationship(jdoField2);
1418                boolean field1HasMapping = hasMappingRows(field1);
1419                boolean field2HasMapping = hasMappingRows(field2);
1420
1421                // if both have rows, they must be exact inverses
1422
if (field1HasMapping && field2HasMapping)
1423                {
1424                    boolean field1IsJoin = isJoin(field1);
1425
1426                    if (field1IsJoin == isJoin(field2))
1427                    {
1428                        ArrayList pairs1 = field1.getColumns();
1429                        ArrayList pairs2 = field2.getColumns();
1430
1431                        return ((!field1IsJoin) ? isInverse(pairs1, pairs2) :
1432                            (isInverse(pairs1,
1433                            field2.getAssociatedColumns()) &&
1434                            isInverse(field1.getAssociatedColumns(), pairs2)));
1435                    }
1436
1437                    return false;
1438                }
1439
1440                // if neither have rows that's fine
1441
return (field1HasMapping == field2HasMapping);
1442            }
1443            private boolean isInverse (ArrayList pairs1, ArrayList pairs2)
1444            {
1445                int i, size1 = pairs1.size(), size2 = pairs2.size();
1446
1447                if (size1 == size2)
1448                {
1449                    for (i = 0; i < size1; i++)
1450                    {
1451                        String JavaDoc nextPair = (String JavaDoc)pairs1.get(i);
1452                        String JavaDoc inversePair = (String JavaDoc)pairs2.get(i);
1453                        int semicolonIndex1 = nextPair.indexOf(';');
1454                        int semicolonIndex2 = inversePair.indexOf(';');
1455
1456                        if (((semicolonIndex1 == -1) || (semicolonIndex2 == -1))
1457                            || (!nextPair.substring(0, semicolonIndex1).equals(
1458                            inversePair.substring(semicolonIndex2 + 1)) ||
1459                            !nextPair.substring(semicolonIndex1 + 1).equals(
1460                            inversePair.substring(0, semicolonIndex2))))
1461                        {
1462                            return false;
1463                        }
1464                    }
1465
1466                    return true;
1467                }
1468
1469                return false;
1470            }
1471        };
1472    }
1473
1474    /** Create a validation component which can check whether the field is
1475     * part of a managed (multiple fields to same column) group and in an
1476     * illegal fetch group. If the field is in one of these groups, it is
1477     * not allowed to be in the default fetch group.
1478     * @param field the field whose fetch group is being checked
1479     * @return the validation component
1480     */

1481    protected ValidationComponent createFieldDefaultFetchGroupComponent (
1482        final MappingFieldElement field)
1483    {
1484        return new ValidationComponent ()
1485        {
1486            public void validate () throws ModelValidationException
1487            {
1488                if (field != null)
1489                {
1490                    String JavaDoc fieldName = field.getName();
1491                    PersistenceClassElement persistenceClass =
1492                        getPersistenceClass(getClassName());
1493                    PersistenceFieldElement pElement =
1494                        ((persistenceClass != null) ?
1495                        persistenceClass.getField(fieldName) : null);
1496
1497                    if ((pElement != null) && !pElement.isKey() &&
1498                        (MappingFieldElement.GROUP_DEFAULT ==
1499                        field.getFetchGroup()))
1500                    {
1501                        MappingClassElement mappingClass =
1502                            field.getDeclaringClass();
1503                        boolean isVersionField =
1504                            ((MappingClassElement.VERSION_CONSISTENCY ==
1505                                mappingClass.getConsistencyLevel()) &&
1506                                field.isVersion());
1507                        Iterator iterator = mappingClass.getFields().iterator();
1508                        String JavaDoc exceptionKey = (!isVersionField ?
1509                            "util.validation.field_fetch_group_invalid"://NOI18N
1510
"util.validation.version_field_column_invalid");//NOI18N
1511

1512                        /* rules:
1513                         * primitive, primitive -> error if exact match of
1514                         * columns
1515                         * primitive, relationship OR
1516                         * relationship, primitive -> error if non-collection
1517                         * relationship and none of the relationship's
1518                         * columns are PK columns and any are present in
1519                         * the primitive's list
1520                         * relationship, relationship -> error if exact
1521                         * match of mapping (local, join, associated),
1522                         * but order is not important
1523                         */

1524                        while (iterator.hasNext())
1525                        {
1526                            MappingFieldElement testField =
1527                                (MappingFieldElement)iterator.next();
1528
1529                            if (isManaged(field, testField) ||
1530                                isManaged(testField, field))
1531                            {
1532                                throw constructFieldException(
1533                                    fieldName, exceptionKey);
1534                            }
1535                            else if (!testField.equals(field) && isExactMatch(
1536                                field, testField))
1537                            {
1538                                throw constructFieldException(
1539                                    fieldName, exceptionKey);
1540                            }
1541                        }
1542                    }
1543                }
1544            }
1545            private boolean isManaged (MappingFieldElement primField,
1546                MappingFieldElement relField)
1547            {
1548                String JavaDoc className = getClassName();
1549
1550                if (!isRelationship(primField) && isRelationship(relField) &&
1551                    !isCollection(className, relField.getName()))
1552                {
1553                    ArrayList columns = primField.getColumns();
1554                    Iterator iterator = relField.getColumns().iterator();
1555                    String JavaDoc databaseRoot = getSchemaForClass(className);
1556
1557                    while (iterator.hasNext())
1558                    {
1559                        if (!testColumn(getLocalColumn((String JavaDoc)iterator.next(),
1560                            databaseRoot), columns))
1561                        {
1562                            return true;
1563                        }
1564                    }
1565                }
1566
1567                return false;
1568            }
1569            private boolean testColumn (ColumnElement column,
1570                ArrayList masterList)
1571            {
1572                if ((column != null) && !isPrimaryKeyColumn(column))
1573                {
1574                    return !masterList.contains(NameUtil.
1575                        getRelativeMemberName(column.getName().getFullName()));
1576                }
1577
1578                return true;
1579            }
1580            private ColumnElement getLocalColumn (String JavaDoc pairName,
1581                String JavaDoc databaseRoot)
1582            {
1583                ColumnPairElement pair = getPair(pairName, databaseRoot);
1584
1585                return ((pair != null) ? pair.getLocalColumn() : null);
1586            }
1587            private boolean isPrimaryKeyColumn (ColumnElement column)
1588            {
1589                if (column != null)
1590                {
1591                    KeyElement key = column.getDeclaringTable().getPrimaryKey();
1592
1593                    return ((key != null) &&
1594                        (key.getColumn(column.getName()) != null));
1595                }
1596
1597                return false;
1598            }
1599            private boolean isExactMatch (ArrayList columns1,
1600                ArrayList columns2)
1601            {
1602                int count = columns1.size();
1603
1604                if ((count > 0) && (count == columns2.size()))
1605                    return getDifference(columns1, columns2).isEmpty();
1606
1607                return false;
1608            }
1609            private boolean isExactMatch (MappingFieldElement field1,
1610                MappingFieldElement field2)
1611            {
1612                boolean field1IsRel = isRelationship(field1);
1613                boolean match = false;
1614
1615                // both primitives, or both relationships
1616
if (field1IsRel == isRelationship(field2))
1617                {
1618                    match = isExactMatch(field1.getColumns(),
1619                        field2.getColumns());
1620
1621                    if (match && field1IsRel)
1622                    {
1623                        MappingRelationshipElement rel1 =
1624                            (MappingRelationshipElement)field1;
1625                        MappingRelationshipElement rel2 =
1626                            (MappingRelationshipElement)field2;
1627                        boolean field1IsJoin = isJoin(rel1);
1628
1629                        // both join relationships or both direct
1630
if (field1IsJoin == isJoin(rel2))
1631                        {
1632                            if (field1IsJoin)
1633                            {
1634                                match = isExactMatch(
1635                                    rel1.getAssociatedColumns(),
1636                                    rel2.getAssociatedColumns());
1637                            }
1638                        }
1639                        else
1640                            match = false;
1641                    }
1642                }
1643                
1644                return match;
1645            }
1646        };
1647    }
1648
1649    /** Create a validation component which can check whether the schema of
1650     * the related class matches that of the class we are checking.
1651     * @param relatedClass the class whose schema is being checked
1652     * @param relatedField the relationship field whose schema is being
1653     * compared
1654     * @return the validation component
1655     */

1656    protected ValidationComponent createRelatedSchemaMatchesComponent (
1657        final String JavaDoc relatedClass, final PersistenceFieldElement relatedField)
1658    {
1659        return new ValidationComponent ()
1660        {
1661            public void validate () throws ModelValidationException
1662            {
1663                if (relatedClass != null)
1664                {
1665                    String JavaDoc className = getClassName();
1666                    String JavaDoc mySchema = getSchemaForClass(className);
1667                    String JavaDoc relatedSchema = getSchemaForClass(relatedClass);
1668
1669                    if ((mySchema != null) && (relatedSchema != null) &&
1670                        !(relatedSchema.equals(mySchema)))
1671                    {
1672                        String JavaDoc fieldName = relatedField.getName();
1673
1674                        throw new ModelValidationException(
1675                            getModel().getField(className, fieldName),
1676                            I18NHelper.getMessage(getMessages(),
1677                            "util.validation.schema_mismatch", //NOI18N
1678
new Object JavaDoc[]{className, relatedClass, fieldName}));
1679                    }
1680                }
1681            }
1682        };
1683    }
1684
1685    /** Create a validation component which can check whether any of
1686     * the supplied tables of the related class (which includes primary
1687     * and secondary tables) contains the table of the column stored in
1688     * the relationship definition.
1689     * @param relatedClass the class whose table is being checked
1690     * @param relatedField the relationship field whose table is being compared
1691     * @param tableNames the list of names of the tables we expect the
1692     * column to match
1693     * @param pairName the name of the pair whose reference column is to
1694     * be checked
1695     * @return the validation component
1696     */

1697    protected ValidationComponent createRelatedTableMatchesComponent (
1698        final String JavaDoc relatedClass, final PersistenceFieldElement relatedField,
1699        final List tableNames, final String JavaDoc pairName)
1700    {
1701        return new ValidationComponent ()
1702        {
1703            public void validate () throws ModelValidationException
1704            {
1705                ColumnPairElement pair = getPair(pairName,
1706                    getSchemaForClass(relatedClass));
1707
1708                if (pair != null)
1709                {
1710                    ColumnElement column = pair.getReferencedColumn();
1711
1712                    if (!matchesTable(tableNames, column))
1713                    {
1714                        String JavaDoc fieldName = relatedField.getName();
1715
1716                        throw new ModelValidationException(
1717                            getModel().getField(getClassName(), fieldName),
1718                            I18NHelper.getMessage(getMessages(),
1719                            getKey(
1720                            "util.validation.table_mismatch", //NOI18N
1721
relatedField),
1722                            new Object JavaDoc[]{column.getName().getFullName(),
1723                            fieldName, relatedClass}));
1724                    }
1725                }
1726            }
1727        };
1728    }
1729
1730    /** Create a validation component which can check whether the schema of
1731     * the given class exists.
1732     * @param className the class whose mapped schema's existence is
1733     * being checked
1734     * @return the validation component
1735     */

1736    protected ValidationComponent createSchemaExistenceComponent (
1737        final String JavaDoc className)
1738    {
1739        return createSchemaExistenceComponent(className, null);
1740    }
1741
1742    /** Create a validation component which can check whether the schema of
1743     * the given class exists.
1744     * @param className the class whose mapped schema's existence is
1745     * being checked
1746     * @param relatedField the relationship field whose class'
1747     * mapped schema is being checked, may be <code>null</code> in which
1748     * case we are probably checking the same class as the validator is
1749     * checking overall
1750     * @return the validation component
1751     */

1752    protected ValidationComponent createSchemaExistenceComponent (
1753        final String JavaDoc className, final PersistenceFieldElement relatedField)
1754    {
1755        return new ValidationComponent ()
1756        {
1757            public void validate () throws ModelValidationException
1758            {
1759                String JavaDoc schemaName = getSchemaForClass(className);
1760
1761                if ((schemaName != null) &&
1762                    (SchemaElement.forName(schemaName) == null))
1763                {
1764                    Object JavaDoc[] args = (relatedField == null) ?
1765                        new Object JavaDoc[]{schemaName, className} :
1766                        new Object JavaDoc[]{schemaName, className, relatedField};
1767
1768                    throw new ModelValidationException(
1769                        ModelValidationException.WARNING,
1770                        getOffendingObject(relatedField),
1771                        I18NHelper.getMessage(getMessages(), getKey(
1772                            "util.validation.schema_not_found", //NOI18N
1773
relatedField), args));
1774                }
1775            }
1776        };
1777    }
1778
1779    /** Create a validation component which can check whether the
1780     * class is mapped to tables even though the schema is null or the
1781     * class is mapped to a primary table without a primary key.
1782     * @param primaryTable the primary table for the class
1783     * @return the validation component
1784     */

1785    protected ValidationComponent createPrimaryTableComponent (
1786        final MappingTableElement primaryTable)
1787    {
1788        return new ValidationComponent ()
1789        {
1790            public void validate () throws ModelValidationException
1791            {
1792                if (primaryTable != null)
1793                {
1794                    String JavaDoc className = getClassName();
1795                    String JavaDoc schemaName = getSchemaForClass(className);
1796
1797                    if (schemaName == null)
1798                    {
1799                        throw constructClassException(className, null,
1800                            "util.validation.schema_not_set"); //NOI18N
1801
}
1802                    else
1803                    {
1804                        String JavaDoc tableName = primaryTable.getName();
1805                        TableElement table = getTable(tableName, schemaName);
1806
1807                        if ((table != null) && (table.getPrimaryKey() == null))
1808                        {
1809                            throw new ModelValidationException(
1810                                getOffendingObject(null),
1811                                I18NHelper.getMessage(getMessages(),
1812                                    "util.validation.table_no_primarykey", //NOI18N
1813
new Object JavaDoc[]{tableName, className}));
1814                        }
1815                    }
1816                }
1817            }
1818        };
1819    }
1820
1821    /** Create a validation component which can check whether the given table
1822     * exists.
1823     * @param tableName the table whose existence is being checked
1824     * @return the validation component
1825     */

1826    protected ValidationComponent createTableExistenceComponent (
1827        final String JavaDoc tableName)
1828    {
1829        return createTableExistenceComponent(tableName, null);
1830    }
1831
1832    /** Create a validation component which can check whether the given table
1833     * exists.
1834     * @param tableName the table whose existence is being checked
1835     * @param relatedField the relationship field whose class'
1836     * table is being checked, may be <code>null</code> in which
1837     * case we are probably checking the same class as the validator is
1838     * checking overall
1839     * @return the validation component
1840     */

1841    protected ValidationComponent createTableExistenceComponent (
1842        final String JavaDoc tableName, final PersistenceFieldElement relatedField)
1843    {
1844        return new ValidationComponent ()
1845        {
1846            public void validate () throws ModelValidationException
1847            {
1848                if (tableName != null)
1849                {
1850                    String JavaDoc className = getClassName();
1851                    boolean noRelated = (relatedField == null);
1852                    TableElement table = getTable(tableName,
1853                        getSchemaForClass((noRelated ? className :
1854                        getRelatedClass(relatedField))));
1855
1856                    if (table == null)
1857                    {
1858                        Object JavaDoc[] args = noRelated ?
1859                            new Object JavaDoc[]{tableName, className} :
1860                            new Object JavaDoc[]{tableName, relatedField};
1861
1862                        throw new ModelValidationException(
1863                            ModelValidationException.WARNING,
1864                            getOffendingObject(relatedField),
1865                            I18NHelper.getMessage(getMessages(), getKey(
1866                                "util.validation.table_not_found", //NOI18N
1867
relatedField), args));
1868                    }
1869                }
1870            }
1871        };
1872    }
1873
1874    /** Create a validation component which can check whether the given column
1875     * exists.
1876     * @param columnName the column whose existence is being checked
1877     * @return the validation component
1878     */

1879    protected ValidationComponent createColumnExistenceComponent (
1880        final String JavaDoc columnName)
1881    {
1882        return createColumnExistenceComponent(columnName, null);
1883    }
1884
1885    /** Create a validation component which can check whether the given
1886     * column or column pair exists.
1887     * @param columnName the column or pair whose existence is being checked
1888     * @param relatedField the field whose class' column is being checked,
1889     * may be <code>null</code> in which case we are probably checking the
1890     * same secondary table setup
1891     * @return the validation component
1892     */

1893    protected ValidationComponent createColumnExistenceComponent (
1894        final String JavaDoc columnName, final MappingFieldElement relatedField)
1895    {
1896        return new ValidationComponent ()
1897        {
1898            public void validate () throws ModelValidationException
1899            {
1900                if (columnName != null)
1901                {
1902                    String JavaDoc className = getClassName();
1903                    String JavaDoc absoluteName = NameUtil.getAbsoluteMemberName(
1904                        getSchemaForClass(className), columnName);
1905                    TableElement table = TableElement.forName(
1906                        NameUtil.getTableName(absoluteName));
1907                    boolean foundTable = (table != null);
1908                    DBMemberElement columnElement = ((foundTable) ?
1909                        table.getMember(DBIdentifier.create(absoluteName)) :
1910                        null);
1911                    boolean noRelated = (relatedField == null);
1912
1913                    if (foundTable)
1914                    {
1915                        boolean isRelationship =
1916                            (!noRelated && isRelationship(relatedField));
1917                        boolean noColumn = (columnElement == null);
1918
1919                        if (!isRelationship && noColumn)
1920                        {
1921                            Object JavaDoc[] args = (noRelated) ?
1922                                new Object JavaDoc[]{columnName, className} :
1923                                new Object JavaDoc[]{columnName, relatedField,
1924                                className};
1925
1926                            throw new ModelValidationException(
1927                                ModelValidationException.WARNING,
1928                                getOffendingObject(relatedField),
1929                                I18NHelper.getMessage(getMessages(), getKey(
1930                                    "util.validation.column_not_found", //NOI18N
1931
relatedField), args));
1932                        }
1933                        else if (isRelationship &&
1934                            (noColumn || !isPairComplete(columnElement)))
1935                        {
1936                            throw new ModelValidationException(
1937                                ModelValidationException.WARNING,
1938                                getOffendingObject(relatedField),
1939                                I18NHelper.getMessage(getMessages(),
1940                                    "util.validation.column_invalid", //NOI18N
1941
new Object JavaDoc[]{columnName, relatedField,
1942                                    className}));
1943                        }
1944                    }
1945                }
1946            }
1947            private boolean isPairComplete (DBMemberElement member)
1948            {
1949                return ((member instanceof ColumnPairElement) &&
1950                    (((ColumnPairElement)member).getLocalColumn() != null) &&
1951                    (((ColumnPairElement)member).getReferencedColumn()
1952                    != null));
1953            }
1954        };
1955    }
1956
1957    /** Create a validation component which can check whether the field is
1958     * one of a set mapped to overlapping columns
1959     * @param field the field whose column mapping is being checked
1960     * @return the validation component
1961     */

1962    protected ValidationComponent createColumnOverlapComponent (
1963        final MappingFieldElement field)
1964    {
1965        return new ValidationComponent ()
1966        {
1967            public void validate () throws ModelValidationException
1968            {
1969                MappingClassElement mappingClass = field.getDeclaringClass();
1970                Iterator iterator = mappingClass.getFields().iterator();
1971                ArrayList myColumns = field.getColumns();
1972
1973                while (iterator.hasNext())
1974                {
1975                    MappingFieldElement testField =
1976                        (MappingFieldElement)iterator.next();
1977
1978                    if (!testField.equals(field) && !isRelationship(testField)
1979                        && isPartialMatch(myColumns, testField.getColumns()))
1980                    {
1981                        String JavaDoc fieldName = field.getName();
1982
1983                        throw new ModelValidationException(getModel().getField(
1984                            getClassName(), fieldName),
1985                            I18NHelper.getMessage(getMessages(),
1986                            "util.validation.field_mapping_invalid", //NOI18N
1987
new Object JavaDoc[]{fieldName, testField.getName()}));
1988                    }
1989                }
1990            }
1991            private boolean isPartialMatch (ArrayList columns1,
1992                ArrayList columns2)
1993            {
1994                int count = columns1.size();
1995
1996                if (count > 0)
1997                {
1998                    ArrayList difference = getDifference(columns1, columns2);
1999
2000                    return (!difference.isEmpty() &&
2001                        (columns2.size() != difference.size()));
2002                }
2003
2004                return false;
2005            }
2006        };
2007    }
2008    
2009    /** Create a validation component which can check whether the key class
2010     * of the persistence capable class is valid. This includes:
2011     * <ul>
2012     * <li> The key class must be public.
2013     * <li> The key class must implement Serializable.
2014     * <li> If the key class is an inner class, it must be static.
2015     * <li> The key class must have a public constructor, which might be
2016     * the default constructor or a no-arg constructor.
2017     * <li> The field types of all non-static fields in the key class must be
2018     * of valid types.
2019     * <li> All serializable non-static fields in the key class must be public.
2020     * <li> The names of the non-static fields in the key class must include the
2021     * names of the primary key fields in the JDO class, and the types of the
2022     * common fields must be identical
2023     * <li> The key class must redefine equals and hashCode.
2024     * </ul>
2025     */

2026    protected ValidationComponent createKeyClassComponent (
2027        final String JavaDoc className)
2028    {
2029        return new ValidationComponent ()
2030        {
2031            /** The class element of the key class */
2032            private Object JavaDoc keyClass;
2033
2034            /** The fully qualified name of the key class */
2035            private String JavaDoc keyClassName;
2036
2037            public void validate () throws ModelValidationException
2038            {
2039                // checks the key class name
2040
keyClassName = validateKeyClassName(className);
2041                // initilialize keyClass field
2042
keyClass = getModel().getClass(keyClassName, getClassLoader());
2043                validateClass();
2044                validateConstructor();
2045                validateFields();
2046                validateMethods();
2047            }
2048
2049            /** Helper method validating the key class itself:
2050             * public, serializable, static.
2051             */

2052            private void validateClass () throws ModelValidationException
2053            {
2054                Model model = getModel();
2055                int modifiers = model.getModifiersForClass(keyClassName);
2056                boolean hasKeyClassName = !StringHelper.isEmpty(keyClassName);
2057                boolean isInnerClass =
2058                    (hasKeyClassName && (keyClassName.indexOf('$') != -1));
2059                String JavaDoc pcClassName = getClassName();
2060
2061                // check for key class existence
2062
if (keyClass == null)
2063                {
2064                    throw new ModelValidationException(
2065                        ModelValidationException.WARNING,
2066                        model.getClass(pcClassName),
2067                        I18NHelper.getMessage(getMessages(),
2068                        "util.validation.key_class_missing", //NOI18N
2069
keyClassName, pcClassName));
2070                }
2071
2072                // check for public class modifier
2073
if (!Modifier.isPublic(modifiers))
2074                {
2075                    throw new ModelValidationException(keyClass,
2076                        I18NHelper.getMessage(getMessages(),
2077                        "util.validation.key_class_public", //NOI18N
2078
keyClassName, pcClassName));
2079                }
2080                
2081                // check for Serializable
2082
/* This check is disabled because of Boston backward
2083                   compatibility. In Boston there was no requirement for a
2084                   key class being serializable, thus key classes from pc
2085                   classes mapped with boston are not serializable.
2086
2087                if (!model.implementsInterface(keyClass,
2088                    "java.io.Serializable")) //NOI18N
2089                {
2090                    throw new ModelValidationException(keyClass,
2091                        I18NHelper.getMessage(getMessages(),
2092                        "util.validation.key_class_serializable", //NOI18N
2093                        keyClassName, pcClassName));
2094                }
2095                */

2096
2097                // if inner class it must be static
2098
if (isInnerClass && !Modifier.isStatic(modifiers))
2099                {
2100                    throw new ModelValidationException(keyClass,
2101                        I18NHelper.getMessage(getMessages(),
2102                        "util.validation.key_class_static", //NOI18N
2103
keyClassName, pcClassName));
2104                }
2105            }
2106
2107            /** Helper method validating the fields of the key class.
2108             */

2109            private void validateFields () throws ModelValidationException
2110            {
2111                String JavaDoc pcClassName = getClassName();
2112                Model model = getModel();
2113                // check for valid typed public non-static fields
2114
List keyClassFieldNames = model.getAllFields(keyClassName);
2115                Map keyFields = getKeyFields();
2116
2117                for (Iterator i = keyClassFieldNames.iterator(); i.hasNext();)
2118                {
2119                    String JavaDoc keyClassFieldName = (String JavaDoc)i.next();
2120                    Object JavaDoc keyClassField =
2121                        getKeyClassField(keyClassName, keyClassFieldName);
2122                    int keyClassFieldModifiers =
2123                        model.getModifiers(keyClassField);
2124                    String JavaDoc keyClassFieldType = model.getType(keyClassField);
2125                    Object JavaDoc keyField = keyFields.get(keyClassFieldName);
2126
2127                    if (Modifier.isStatic(keyClassFieldModifiers))
2128                        // we are not interested in static fields
2129
continue;
2130
2131                    if (!model.isValidKeyType(keyClassName, keyClassFieldName))
2132                    {
2133                        throw new ModelValidationException(keyClassField,
2134                            I18NHelper.getMessage(getMessages(),
2135                            "util.validation.key_field_type_invalid", //NOI18N
2136
keyClassFieldName, keyClassName));
2137                    }
2138                    
2139                    if (!Modifier.isPublic(keyClassFieldModifiers))
2140                    {
2141                        throw new ModelValidationException(keyClassField,
2142                            I18NHelper.getMessage(getMessages(),
2143                            "util.validation.key_field_public", //NOI18N
2144
keyClassFieldName, keyClassName));
2145                    }
2146
2147                    if (keyField == null)
2148                        continue;
2149                    
2150                    if (!keyClassFieldType.equals(model.getType(keyField)))
2151                    {
2152                        throw new ModelValidationException(keyClassField,
2153                            I18NHelper.getMessage(getMessages(),
2154                            "util.validation.key_field_type_mismatch", //NOI18N
2155
keyClassFieldName, keyClassName, pcClassName));
2156                    }
2157
2158                    // remove handled keyField from the list of keyFields
2159
keyFields.remove(keyClassFieldName);
2160                }
2161
2162                // check whether there are any unhandled key fields
2163
if (!keyFields.isEmpty())
2164                {
2165                    Object JavaDoc pcClass = model.getClass(pcClassName);
2166                    String JavaDoc fieldNames = StringHelper.arrayToSeparatedList(
2167                        new ArrayList(keyFields.keySet()));
2168
2169                    throw new ModelValidationException(pcClass,
2170                        I18NHelper.getMessage(getMessages(),
2171                        "util.validation.key_field_missing", //NOI18N
2172
pcClassName, keyClassName, fieldNames));
2173                }
2174            }
2175
2176            /** Helper method validating the key class constructors.
2177             */

2178            private void validateConstructor () throws ModelValidationException
2179            {
2180                // no constructor or no arg constructor
2181
Model model = getModel();
2182                boolean hasConstr = model.hasConstructor(keyClassName);
2183                Object JavaDoc noArgConstr =
2184                    model.getConstructor(keyClassName, Model.NO_ARGS);
2185                int modifiers = model.getModifiers(noArgConstr);
2186
2187                if (hasConstr &&
2188                    ((noArgConstr == null) || !Modifier.isPublic(modifiers)))
2189                {
2190                    throw new ModelValidationException(keyClass,
2191                        I18NHelper.getMessage(getMessages(),
2192                        "util.validation.key_class_constructor", //NOI18N
2193
keyClassName, getClassName()));
2194                }
2195            }
2196
2197            /** Helper method validating the key class methods.
2198             */

2199            private void validateMethods () throws ModelValidationException
2200            {
2201                Model model = getModel();
2202                Object JavaDoc equalsMethod = getNonObjectMethod(keyClassName,
2203                    "equals", Model.EQUALS_ARGS); //NOI18N
2204
Object JavaDoc hashCodeMethod = getNonObjectMethod(keyClassName,
2205                    "hashCode", Model.NO_ARGS); //NOI18N
2206

2207                // check equals method
2208
if (!matchesMethod(equalsMethod, Modifier.PUBLIC,
2209                    0, "boolean")) //NOI18N
2210
{
2211                    throw new ModelValidationException(keyClass,
2212                        I18NHelper.getMessage(getMessages(),
2213                        "util.validation.key_class_equals", //NOI18N
2214
keyClassName, getClassName()));
2215                }
2216                
2217                // check hashCode method
2218
if (!matchesMethod(hashCodeMethod, Modifier.PUBLIC,
2219                    0, "int")) //NOI18N
2220
{
2221                    throw new ModelValidationException(keyClass,
2222                        I18NHelper.getMessage(getMessages(),
2223                        "util.validation.key_class_hashcode", //NOI18N
2224
keyClassName, getClassName()));
2225                }
2226            }
2227            
2228            /** Helper method validating the name of the key class.
2229             */

2230            private String JavaDoc validateKeyClassName (String JavaDoc keyClassName)
2231                throws ModelValidationException
2232            {
2233                String JavaDoc pcClassName = getClassName();
2234                Model model = getModel();
2235                boolean hasKeyClassName = !StringHelper.isEmpty(keyClassName);
2236                boolean hasPrefix;
2237                String JavaDoc nameSuffix;
2238                boolean isOIDNameSuffix;
2239
2240                // check for existence of key class name
2241
if (!hasKeyClassName)
2242                {
2243                    throw new ModelValidationException(
2244                        ModelValidationException.WARNING,
2245                        model.getClass(pcClassName),
2246                        I18NHelper.getMessage(getMessages(),
2247                        "util.validation.key_class_unset", //NOI18N
2248
pcClassName));
2249                }
2250
2251                keyClassName = keyClassName.trim();
2252                hasPrefix = keyClassName.startsWith(pcClassName);
2253                nameSuffix = (hasPrefix ?
2254                   keyClassName.substring(pcClassName.length()) : keyClassName);
2255                isOIDNameSuffix =
2256                    (nameSuffix.equalsIgnoreCase(".OID") || // NOI18N
2257
nameSuffix.equalsIgnoreCase("$OID")); // NOI18N
2258

2259                if (!hasPrefix ||
2260                    (!nameSuffix.equalsIgnoreCase("Key") && // NOI18N
2261
!isOIDNameSuffix))
2262                {
2263                    Object JavaDoc pcClass = getModel().getClass(pcClassName);
2264                    throw new ModelValidationException(pcClass,
2265                        I18NHelper.getMessage(getMessages(),
2266                        "util.validation.key_class_invalid", //NOI18N
2267
keyClassName, pcClassName));
2268                }
2269                if (isOIDNameSuffix)
2270                {
2271                    StringBuffer JavaDoc buf = new StringBuffer JavaDoc(keyClassName);
2272                    buf.setCharAt(keyClassName.length() - 4, '$');
2273                    return buf.toString();
2274                }
2275                return keyClassName;
2276            }
2277
2278            // helper method which returns a field object from the
2279
// given class or one of its superclasses
2280
private Object JavaDoc getKeyClassField (String JavaDoc keyClassName,
2281                String JavaDoc keyClassFieldName)
2282            {
2283                Model model = getModel();
2284                Object JavaDoc keyClassField =
2285                    model.getField(keyClassName, keyClassFieldName);
2286
2287                if (keyClassField == null) // this is an inherited field
2288
{
2289                    keyClassField = model.getInheritedField(
2290                        keyClassName, keyClassFieldName);
2291                }
2292
2293                return keyClassField;
2294            }
2295
2296            /** Helper method returning the key fields of the pc class as a map.
2297             */

2298            private Map getKeyFields ()
2299            {
2300                Model model = getModel();
2301                String JavaDoc pcClassName = getClassName();
2302                PersistenceClassElement pce =
2303                    model.getPersistenceClass(pcClassName);
2304                PersistenceFieldElement[] fields = pce.getFields();
2305                Map keyFields = new HashMap();
2306
2307                if (fields != null)
2308                {
2309                    for (int i = 0; i < fields.length; i++)
2310                    {
2311                        PersistenceFieldElement pfe = fields[i];
2312                        if (pfe.isKey())
2313                        {
2314                            String JavaDoc name = pfe.getName();
2315                            keyFields.put(name,
2316                                model.getField(pcClassName, name));
2317                        }
2318                    }
2319                }
2320                
2321                return keyFields;
2322            }
2323            // helper method which returns a method object from the
2324
// given class or one of its superclasses provided it
2325
// is not java.lang.Object
2326
private Object JavaDoc getNonObjectMethod (String JavaDoc className,
2327                String JavaDoc methodName, String JavaDoc[] argTypeNames)
2328            {
2329                Model model = getModel();
2330                Object JavaDoc method =
2331                    model.getMethod(className, methodName, argTypeNames);
2332
2333                if (method == null) // look for an inherited method
2334
{
2335                    method = model.getInheritedMethod(
2336                        className, methodName, argTypeNames);
2337
2338                    if ((method != null) && model.getDeclaringClass(method).
2339                        equals("java.lang.Object")) // NOI18N
2340
{
2341                        method = null;
2342                    }
2343                }
2344
2345                return method;
2346            }
2347        };
2348    }
2349
2350    /** Create a validation component which can check that the persistence
2351     * capable class implement methods readObject and writeObject, if the class
2352     * implements the intreface java.io.Serializable
2353     * @param className the class whose methods are checked
2354     * @return the validation component
2355     */

2356    protected ValidationComponent createSerializableClassComponent (
2357        final String JavaDoc className)
2358    {
2359        return new ValidationComponent ()
2360        {
2361            public void validate () throws ModelValidationException
2362            {
2363                Model model = getModel();
2364                Object JavaDoc pcClass = null;
2365
2366                if (className == null)
2367                    return;
2368                pcClass = model.getClass(className);
2369                if (pcClass == null)
2370                    return;
2371
2372                if (model.implementsInterface(pcClass, "java.io.Serializable")) //NOI18N
2373
{
2374                    // check readObject method
2375
Object JavaDoc readMethod = model.getMethod(className,
2376                        "readObject", Model.READ_OBJECT_ARGS); //NOI18N
2377

2378                    if (!matchesMethod(readMethod, Modifier.PRIVATE,
2379                        Modifier.SYNCHRONIZED, "void")) // NOI18N
2380
{
2381                        throw new ModelValidationException(pcClass,
2382                            I18NHelper.getMessage(getMessages(),
2383                            "util.validation.class_readobject", //NOI18N
2384
className));
2385                    }
2386                    
2387                    // check writeObject method
2388
Object JavaDoc writeMethod = model.getMethod(className,
2389                        "writeObject", Model.WRITE_OBJECT_ARGS); //NOI18N
2390

2391                    if (!matchesMethod(writeMethod, Modifier.PRIVATE,
2392                        Modifier.SYNCHRONIZED, "void")) // NOI18N
2393
{
2394                        throw new ModelValidationException(pcClass,
2395                            I18NHelper.getMessage(getMessages(),
2396                            "util.validation.class_writeobject", //NOI18N
2397
className));
2398                    }
2399                }
2400            }
2401        };
2402    }
2403
2404    /** Create a validation component which can check whether the class is
2405     * unmapped.
2406     * @param persistenceClass the class whose mapping is being checked
2407     * @return the validation component
2408     */

2409    protected ValidationComponent createClassMappingComponent (
2410        final PersistenceClassElement persistenceClass)
2411    {
2412        return new ValidationComponent ()
2413        {
2414            public void validate () throws ModelValidationException
2415            {
2416                PersistenceFieldElement[] fields = persistenceClass.getFields();
2417                String JavaDoc className = getClassName();
2418
2419                if ((fields == null) || fields.length == 0)
2420                {
2421                    throw constructClassException(
2422                        ModelValidationException.WARNING, className, null,
2423                        "util.validation.class_no_fields"); //NOI18N
2424
}
2425                else // has fields, check for primary table
2426
{
2427                    MappingClassElement mappingClass =
2428                        getMappingClass(className);
2429
2430                    if ((mappingClass == null) ||
2431                        (mappingClass.getTables().size() == 0))
2432                    {
2433                        throw constructClassException(
2434                            ModelValidationException.WARNING, className, null,
2435                            "util.validation.class_not_mapped"); //NOI18N
2436
}
2437                }
2438            }
2439        };
2440    }
2441
2442    /** Create a validation component which can check whether the class
2443     * contains field mappings for all primary key columns.
2444     * @param persistenceClass the class whose mapping is being checked
2445     * @return the validation component
2446     */

2447    protected ValidationComponent createKeyColumnMappingComponent (
2448        final PersistenceClassElement persistenceClass)
2449    {
2450        return new ValidationComponent ()
2451        {
2452            public void validate () throws ModelValidationException
2453            {
2454                String JavaDoc className = getClassName();
2455                MappingClassElement mappingClass = getMappingClass(className);
2456
2457                if (mappingClass != null)
2458                {
2459                    List tables = mappingClass.getTables();
2460
2461                    if (tables.size() > 0)
2462                    {
2463                        String JavaDoc tableName =
2464                            ((MappingTableElement)tables.get(0)).getName();
2465                        TableElement table = getTable(tableName,
2466                            getSchemaForClass(className));
2467                        List columns = getUnmappedColumnNames(
2468                            ((table != null) ? table.getPrimaryKey() : null),
2469                            mappingClass);
2470
2471                        if ((columns != null) && (columns.size() > 0))
2472                        {
2473                            throw new ModelValidationException(
2474                                ModelValidationException.WARNING,
2475                                getOffendingObject(null),
2476                                I18NHelper.getMessage(getMessages(),
2477                                "util.validation.class_key_column_missing", //NOI18N
2478
className, tableName,
2479                                StringHelper.arrayToSeparatedList(columns)));
2480                        }
2481                    }
2482                }
2483            }
2484            private List getUnmappedColumnNames (KeyElement primaryKey,
2485                MappingClassElement mappingClass)
2486            {
2487                List unmappedColumns = null;
2488
2489                if (primaryKey != null) // check if primary table has a pk
2490
{
2491                    ColumnElement[] columns = primaryKey.getColumns();
2492                    int count = ((columns != null) ? columns.length : 0);
2493
2494                    // all columns in the pk should be mapped to key fields
2495
if (count > 0)
2496                    {
2497                        List mappingFields = mappingClass.getFields();
2498                        Iterator iterator = mappingFields.iterator();
2499
2500                        unmappedColumns = getRelativeColumnNames(columns);
2501
2502                        while (iterator.hasNext())
2503                        {
2504                            MappingFieldElement field =
2505                                (MappingFieldElement)iterator.next();
2506
2507                            if (isKeyField(field))
2508                                unmappedColumns.removeAll(field.getColumns());
2509                        }
2510                    }
2511                }
2512
2513                return unmappedColumns;
2514            }
2515            private List getRelativeColumnNames (ColumnElement[] columns)
2516            {
2517                int i, count = ((columns != null) ? columns.length : 0);
2518                List columnNames = new ArrayList(count);
2519                                
2520                for (i = 0; i < count; i++)
2521                {
2522                    columnNames.add(NameUtil.getRelativeMemberName(
2523                        columns[i].getName().getFullName()));
2524                }
2525
2526                return columnNames;
2527            }
2528            private boolean isKeyField (MappingFieldElement field)
2529            {
2530                PersistenceFieldElement persistenceField =
2531                    persistenceClass.getField(field.getName());
2532
2533                return ((persistenceField != null) && persistenceField.isKey());
2534            }
2535        };
2536    }
2537
2538    //========== Convenience methods for exception construction ============
2539

2540    /** Computes the offending object to be used in the construction of a
2541     * ModelValidationException as follows: if a non-null field is supplied,
2542     * the corresponding org.openide.src.FieldElement is returned, otherwise,
2543     * corresponding org.openide.src.ClassElement is returned.
2544     * @param field the field object which caused the problem - may be
2545     * <code>null</code>
2546     * @return the offending object
2547     */

2548    private Object JavaDoc getOffendingObject (Object JavaDoc field)
2549    {
2550        return ((field == null) ?
2551            getModel().getClass(getClassName(), getClassLoader()) :
2552            getModel().getField(getClassName(), field.toString()));
2553    }
2554
2555    /** Computes the key for the i18n string to be used in the construction
2556     * of a ModelValidationException as follows: if a non-null field is
2557     * supplied, "_related" is appending to the supplied key base, otherwise,
2558     * the key base is returned as is.
2559     * @param keyBase the base key to be used for the i18n string
2560     * @param field the field object which caused the problem - may be
2561     * <code>null</code>
2562     * @return the key
2563     */

2564    private String JavaDoc getKey (String JavaDoc keyBase, Object JavaDoc field)
2565    {
2566        return ((field == null) ? keyBase : (keyBase + "_related")); //NOI18N
2567
}
2568
2569    /** Computes the arguments for the i18n string to be used in the
2570     * construction of a ModelValidationException as follows: if a
2571     * non-null field is supplied, an array containing the supplied className
2572     * and field is returned, otherwise, an array containing only the supplied
2573     * className is returned.
2574     * @param className the name of the class which caused the problem
2575     * @param field the field object which caused the problem - may be
2576     * <code>null</code>
2577     * @return the argument array
2578     */

2579    private Object JavaDoc[] getArguments (String JavaDoc className, Object JavaDoc field)
2580    {
2581        return ((field == null) ? new Object JavaDoc[]{className} :
2582            new Object JavaDoc[]{className, field});
2583    }
2584
2585    /** Constructs a ModelValidationException for class validation tests
2586     * using the supplied class name, related field, and key base.
2587     * @param className the name of the class which caused the problem
2588     * @param field the field object which caused the problem - may be
2589     * <code>null</code>
2590     * @param keyBase the base key to be used for the i18n string
2591     * @return the ModelValidationException
2592     * @see #getOffendingObject
2593     * @see #getKey
2594     * @see #getArguments
2595     * @see #constructFieldException
2596     */

2597    private ModelValidationException constructClassException (String JavaDoc className,
2598        Object JavaDoc relatedField, String JavaDoc keyBase)
2599    {
2600        return constructClassException(ModelValidationException.ERROR,
2601            className, relatedField, keyBase);
2602    }
2603
2604    /** Constructs a ModelValidationException for class validation tests
2605     * using the supplied class name, related field, and key base.
2606     * @param errorType the type of error -- one of
2607     * {@link ModelValidationException#ERROR} or
2608     * {@link ModelValidationException#WARNING}.
2609     * @param className the name of the class which caused the problem
2610     * @param field the field object which caused the problem - may be
2611     * <code>null</code>
2612     * @param keyBase the base key to be used for the i18n string
2613     * @return the ModelValidationException
2614     * @see #getOffendingObject
2615     * @see #getKey
2616     * @see #getArguments
2617     * @see #constructFieldException
2618     */

2619    private ModelValidationException constructClassException (int errorType,
2620        String JavaDoc className, Object JavaDoc relatedField, String JavaDoc keyBase)
2621    {
2622        return new ModelValidationException(errorType,
2623            getOffendingObject(relatedField), I18NHelper.getMessage(
2624            getMessages(), getKey(keyBase, relatedField),
2625            getArguments(className, relatedField)));
2626    }
2627
2628    /** Constructs a ModelValidationException for field validation tests
2629     * using the supplied field name and key.
2630     * @param fieldName the name of the field which caused the problem
2631     * @param keyBase the base key to be used for the i18n string
2632     * @return the ModelValidationException
2633     * @see #constructClassException
2634     */

2635    private ModelValidationException constructFieldException (String JavaDoc fieldName,
2636        String JavaDoc key)
2637    {
2638        return constructFieldException(ModelValidationException.ERROR,
2639            fieldName, key);
2640    }
2641
2642    /** Constructs a ModelValidationException for field validation tests
2643     * using the supplied field name and key.
2644     * @param errorType the type of error -- one of
2645     * {@link ModelValidationException#ERROR} or
2646     * {@link ModelValidationException#WARNING}.
2647     * @param fieldName the name of the field which caused the problem
2648     * @param keyBase the base key to be used for the i18n string
2649     * @return the ModelValidationException
2650     * @see #constructClassException
2651     */

2652    private ModelValidationException constructFieldException (int errorType,
2653        String JavaDoc fieldName, String JavaDoc key)
2654    {
2655        return new ModelValidationException(errorType,
2656            getModel().getField(getClassName(), fieldName),
2657            I18NHelper.getMessage(getMessages(), key, fieldName));
2658    }
2659
2660    //=============== Misc. private convenience methods ================
2661

2662    /** Checks whether the specified method element exists and if so whether it
2663     * has the expected modifiers and the expected return type.
2664     * @param method the method element to be checked
2665     * @param expectedModifiers the modifiers the method should have
2666     * @param optionalModifiers additional modifiers the method might have
2667     * @param expectedReturnType the return type the method should have
2668     * @return <code>true</code> if the method matches,
2669     * <code>false</code> otherwise.
2670     */

2671    private boolean matchesMethod (final Object JavaDoc method,
2672        final int expectedModifiers, final int optionalModifiers,
2673        final String JavaDoc expectedReturnType)
2674    {
2675        boolean matches = false;
2676
2677        if (method != null)
2678        {
2679            Model model = getModel();
2680            int modifiers = model.getModifiers(method);
2681
2682            matches = (((modifiers == expectedModifiers) ||
2683                (modifiers == (expectedModifiers | optionalModifiers))) &&
2684                expectedReturnType.equals(model.getType(method)));
2685        }
2686
2687        return matches;
2688    }
2689
2690    /** Check if the table of the column matches one of the list of tables.
2691     * @param tableNames A list of table names in which to check for a match
2692     * @param column A ColumnElement object to be checked
2693     * @return <code>true</code> if the column belongs to a table found
2694     * in the supplied list of table names, <code>false</code> otherwise
2695     */

2696    private boolean matchesTable (List tableNames, ColumnElement column)
2697    {
2698        return ((column == null) ? true : tableNames.contains(
2699            column.getDeclaringTable().getName().getName()));
2700    }
2701
2702    private boolean isRelationship (Object JavaDoc field)
2703    {
2704        return ((field instanceof RelationshipElement) ||
2705            (field instanceof MappingRelationshipElement));
2706    }
2707
2708    private boolean shouldBeRelationship (PersistenceFieldElement field)
2709    {
2710        Model model = getModel();
2711        String JavaDoc fieldType = model.getFieldType(getClassName(), field.getName());
2712
2713        return (isPersistent(fieldType) || model.isCollection(fieldType));
2714    }
2715
2716    private boolean isLegalRelationship (PersistenceFieldElement field)
2717    {
2718        return (isRelationship(field) ? shouldBeRelationship(field) : false);
2719    }
2720
2721    private boolean isCollection (String JavaDoc className, String JavaDoc fieldName)
2722    {
2723        Model model = getModel();
2724
2725        return model.isCollection(model.getFieldType(className, fieldName));
2726    }
2727
2728    private String JavaDoc getRelatedClass (PersistenceFieldElement field)
2729    {
2730        if (isLegalRelationship(field))
2731            return getModel().getRelatedClass((RelationshipElement)field);
2732
2733        return null;
2734    }
2735
2736    private String JavaDoc getSchemaForClass (String JavaDoc className)
2737    {
2738        MappingClassElement mappingClass = getMappingClass(className);
2739        String JavaDoc schema = ((mappingClass != null) ?
2740            mappingClass.getDatabaseRoot() : null);
2741
2742        return (StringHelper.isEmpty(schema) ? null : schema.trim());
2743    }
2744
2745    private MappingRelationshipElement getMappingRelationship (
2746        RelationshipElement jdoElement)
2747    {
2748        MappingRelationshipElement mappingElement = null;
2749
2750        if (jdoElement != null)
2751        {
2752            MappingClassElement mappingClass = getMappingClass(
2753                jdoElement.getDeclaringClass().getName());
2754
2755            if (mappingClass != null)
2756            {
2757                MappingFieldElement fieldElement =
2758                    mappingClass.getField(jdoElement.getName());
2759
2760                if (isRelationship(fieldElement))
2761                    mappingElement = (MappingRelationshipElement)fieldElement;
2762            }
2763        }
2764
2765        return mappingElement;
2766    }
2767
2768    private boolean isJoin (MappingRelationshipElement field)
2769    {
2770        if (field != null)
2771        {
2772            ArrayList columns = field.getAssociatedColumns();
2773            
2774            return ((columns != null) && !columns.isEmpty());
2775        }
2776        
2777        return false;
2778    }
2779
2780    private MappingReferenceKeyElement findReferenceKey (
2781        MappingTableElement primaryTable, MappingTableElement secondaryTable)
2782    {
2783        if ((primaryTable != null) && (secondaryTable != null))
2784        {
2785            Iterator iterator = primaryTable.getReferencingKeys().iterator();
2786
2787            while (iterator.hasNext())
2788            {
2789                MappingReferenceKeyElement testKey =
2790                    (MappingReferenceKeyElement)iterator.next();
2791
2792                if (testKey.getTable().equals(secondaryTable))
2793                    return testKey;
2794            }
2795        }
2796
2797        return null;
2798    }
2799
2800    private TableElement getTable (String JavaDoc tableName, String JavaDoc databaseRoot)
2801    {
2802        String JavaDoc absoluteName = NameUtil.getAbsoluteTableName(databaseRoot,
2803            tableName);
2804        return TableElement.forName(absoluteName);
2805    }
2806
2807    private ColumnPairElement getPair (String JavaDoc pairName, String JavaDoc databaseRoot)
2808    {
2809        String JavaDoc absoluteName = NameUtil.getAbsoluteMemberName(
2810            databaseRoot, pairName);
2811        TableElement tableElement = TableElement.forName(
2812            NameUtil.getTableName(absoluteName));
2813        DBMemberElement pair = ((tableElement == null) ? null :
2814            tableElement.getMember(DBIdentifier.create(absoluteName)));
2815
2816        return ((pair instanceof ColumnPairElement) ?
2817            ((ColumnPairElement)pair) : null);
2818    }
2819
2820    private ArrayList getDifference (ArrayList columns1, ArrayList columns2)
2821    {
2822        ArrayList differenceColumns = new ArrayList(columns2);
2823
2824        differenceColumns.removeAll(columns1);
2825
2826        return differenceColumns;
2827    }
2828
2829    /**
2830     * Convenience method to call Model.getMappingClass.
2831     */

2832    private MappingClassElement getMappingClass (String JavaDoc className)
2833    {
2834        return getModel().getMappingClass(className, getClassLoader());
2835    }
2836
2837    /**
2838     * Convenience method to call Model.getPersistenceClass.
2839     */

2840    private PersistenceClassElement getPersistenceClass (String JavaDoc className)
2841    {
2842        return getModel().getPersistenceClass(className, getClassLoader());
2843    }
2844
2845    /**
2846     * Convenience method to call Model.isPersistent
2847     */

2848    private boolean isPersistent (String JavaDoc className)
2849    {
2850        return getModel().isPersistent(className, getClassLoader());
2851    }
2852
2853    /**
2854     * Convenience method to call Model.isPersistentAllowed
2855     */

2856    private boolean isPersistentAllowed (String JavaDoc className, String JavaDoc fieldName)
2857    {
2858        return getModel().isPersistentAllowed(className, getClassLoader(),
2859            fieldName);
2860    }
2861    // ================== Validation component support =================
2862

2863    /** Abstraction of component tests for validation.
2864     */

2865    static abstract class ValidationComponent
2866    {
2867        /** Constructs a new ValidationComponent
2868         */

2869        public ValidationComponent ()
2870        {
2871        }
2872        /** Method which validates this component
2873         * @exception ModelValidationException when the validation fails.
2874         */

2875        public abstract void validate () throws ModelValidationException;
2876    }
2877}
2878
Popular Tags