xdoclet.modules.ojb.constraints.ModelConstraints.java Source code

Java tutorial

Introduction

Here is the source code for xdoclet.modules.ojb.constraints.ModelConstraints.java

Source

package xdoclet.modules.ojb.constraints;

/* Copyright 2004-2005 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

import org.apache.commons.collections.SequencedHashMap;

import xdoclet.modules.ojb.CommaListIterator;
import xdoclet.modules.ojb.LogHelper;
import xdoclet.modules.ojb.model.ClassDescriptorDef;
import xdoclet.modules.ojb.model.CollectionDescriptorDef;
import xdoclet.modules.ojb.model.FeatureDescriptorDef;
import xdoclet.modules.ojb.model.FieldDescriptorDef;
import xdoclet.modules.ojb.model.ModelDef;
import xdoclet.modules.ojb.model.PropertyHelper;
import xdoclet.modules.ojb.model.ReferenceDescriptorDef;

/**
 * Checks constraints that span deal with parts of the model, not just with one class.
 * This for instance means relationships (collections, references).
 *
 * @author <a href="mailto:tomdz@users.sourceforge.net">Thomas Dudziak (tomdz@users.sourceforge.net)</a>
 */
public class ModelConstraints extends ConstraintsBase {
    /**
     * Checks the given model.
     * 
     * @param modelDef   The model
     * @param checkLevel The amount of checks to perform
     * @exception ConstraintException If a constraint has been violated
     */
    public void check(ModelDef modelDef, String checkLevel) throws ConstraintException {
        ensureReferencedKeys(modelDef, checkLevel);
        checkReferenceForeignkeys(modelDef, checkLevel);
        checkCollectionForeignkeys(modelDef, checkLevel);
        checkKeyModifications(modelDef, checkLevel);
    }

    /**
     * Ensures that the primary/foreign keys referenced by references/collections are present
     * in the target type even if generate-table-info="false", by evaluating the subtypes
     * of the target type.
     * 
     * @param modelDef   The model
     * @param checkLevel The current check level (this constraint is always checked)
     * @throws ConstraintException If there is an error with the keys of the subtypes or there
     *                             ain't any subtypes 
     */
    private void ensureReferencedKeys(ModelDef modelDef, String checkLevel) throws ConstraintException {
        ClassDescriptorDef classDef;
        CollectionDescriptorDef collDef;
        ReferenceDescriptorDef refDef;

        for (Iterator it = modelDef.getClasses(); it.hasNext();) {
            classDef = (ClassDescriptorDef) it.next();
            for (Iterator refIt = classDef.getReferences(); refIt.hasNext();) {
                refDef = (ReferenceDescriptorDef) refIt.next();
                if (!refDef.getBooleanProperty(PropertyHelper.OJB_PROPERTY_IGNORE, false)) {
                    ensureReferencedPKs(modelDef, refDef);
                }
            }
            for (Iterator collIt = classDef.getCollections(); collIt.hasNext();) {
                collDef = (CollectionDescriptorDef) collIt.next();
                if (!collDef.getBooleanProperty(PropertyHelper.OJB_PROPERTY_IGNORE, false)) {
                    if (collDef.hasProperty(PropertyHelper.OJB_PROPERTY_INDIRECTION_TABLE)) {
                        ensureReferencedPKs(modelDef, collDef);
                    } else {
                        ensureReferencedFKs(modelDef, collDef);
                    }
                }
            }
        }
    }

    /**
     * Ensures that the primary keys required by the given reference are present in the referenced class.
     * 
     * @param modelDef The model
     * @param refDef   The reference
     * @throws ConstraintException If there is a conflict between the primary keys
     */
    private void ensureReferencedPKs(ModelDef modelDef, ReferenceDescriptorDef refDef) throws ConstraintException {
        String targetClassName = refDef.getProperty(PropertyHelper.OJB_PROPERTY_CLASS_REF);
        ClassDescriptorDef targetClassDef = modelDef.getClass(targetClassName);

        ensurePKsFromHierarchy(targetClassDef);
    }

    /**
     * Ensures that the primary keys required by the given collection with indirection table are present in
     * the element class.
     * 
     * @param modelDef The model
     * @param collDef  The collection
     * @throws ConstraintException If there is a problem with the fitting collection (if any) or the primary keys
     */
    private void ensureReferencedPKs(ModelDef modelDef, CollectionDescriptorDef collDef)
            throws ConstraintException {
        String elementClassName = collDef.getProperty(PropertyHelper.OJB_PROPERTY_ELEMENT_CLASS_REF);
        ClassDescriptorDef elementClassDef = modelDef.getClass(elementClassName);
        String indirTable = collDef.getProperty(PropertyHelper.OJB_PROPERTY_INDIRECTION_TABLE);
        String localKey = collDef.getProperty(PropertyHelper.OJB_PROPERTY_FOREIGNKEY);
        String remoteKey = collDef.getProperty(PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY);
        boolean hasRemoteKey = remoteKey != null;
        ArrayList fittingCollections = new ArrayList();

        // we're checking for the fitting remote collection(s) and also
        // use their foreignkey as remote-foreignkey in the original collection definition
        for (Iterator it = elementClassDef.getAllExtentClasses(); it.hasNext();) {
            ClassDescriptorDef subTypeDef = (ClassDescriptorDef) it.next();

            // find the collection in the element class that has the same indirection table
            for (Iterator collIt = subTypeDef.getCollections(); collIt.hasNext();) {
                CollectionDescriptorDef curCollDef = (CollectionDescriptorDef) collIt.next();

                if (indirTable.equals(curCollDef.getProperty(PropertyHelper.OJB_PROPERTY_INDIRECTION_TABLE))
                        && (collDef != curCollDef)
                        && (!hasRemoteKey || CommaListIterator.sameLists(remoteKey,
                                curCollDef.getProperty(PropertyHelper.OJB_PROPERTY_FOREIGNKEY)))
                        && (!curCollDef.hasProperty(PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY)
                                || CommaListIterator.sameLists(localKey,
                                        curCollDef.getProperty(PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY)))) {
                    fittingCollections.add(curCollDef);
                }
            }
        }
        if (!fittingCollections.isEmpty()) {
            // if there is more than one, check that they match, i.e. that they all have the same foreignkeys
            if (!hasRemoteKey && (fittingCollections.size() > 1)) {
                CollectionDescriptorDef firstCollDef = (CollectionDescriptorDef) fittingCollections.get(0);
                String foreignKey = firstCollDef.getProperty(PropertyHelper.OJB_PROPERTY_FOREIGNKEY);

                for (int idx = 1; idx < fittingCollections.size(); idx++) {
                    CollectionDescriptorDef curCollDef = (CollectionDescriptorDef) fittingCollections.get(idx);

                    if (!CommaListIterator.sameLists(foreignKey,
                            curCollDef.getProperty(PropertyHelper.OJB_PROPERTY_FOREIGNKEY))) {
                        throw new ConstraintException(
                                "Cannot determine the element-side collection that corresponds to the collection "
                                        + collDef.getName() + " in type " + collDef.getOwner().getName()
                                        + " because there are at least two different collections that would fit."
                                        + " Specifying remote-foreignkey in the original collection "
                                        + collDef.getName() + " will perhaps help");
                    }
                }
                // store the found keys at the collections
                collDef.setProperty(PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY, foreignKey);
                for (int idx = 0; idx < fittingCollections.size(); idx++) {
                    CollectionDescriptorDef curCollDef = (CollectionDescriptorDef) fittingCollections.get(idx);

                    curCollDef.setProperty(PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY, localKey);
                }
            }
        }

        // copy subclass pk fields into target class (if not already present)
        ensurePKsFromHierarchy(elementClassDef);
    }

    /**
     * Ensures that the foreign keys required by the given collection are present in the element class.
     * 
     * @param modelDef The model
     * @param collDef  The collection
     * @throws ConstraintException If there is a problem with the foreign keys
     */
    private void ensureReferencedFKs(ModelDef modelDef, CollectionDescriptorDef collDef)
            throws ConstraintException {
        String elementClassName = collDef.getProperty(PropertyHelper.OJB_PROPERTY_ELEMENT_CLASS_REF);
        ClassDescriptorDef elementClassDef = modelDef.getClass(elementClassName);
        String fkFieldNames = collDef.getProperty(PropertyHelper.OJB_PROPERTY_FOREIGNKEY);
        ArrayList missingFields = new ArrayList();
        SequencedHashMap fkFields = new SequencedHashMap();

        // first we gather all field names
        for (CommaListIterator it = new CommaListIterator(fkFieldNames); it.hasNext();) {
            String fieldName = (String) it.next();
            FieldDescriptorDef fieldDef = elementClassDef.getField(fieldName);

            if (fieldDef == null) {
                missingFields.add(fieldName);
            }
            fkFields.put(fieldName, fieldDef);
        }

        // next we traverse all sub types and gather fields as we go
        for (Iterator it = elementClassDef.getAllExtentClasses(); it.hasNext() && !missingFields.isEmpty();) {
            ClassDescriptorDef subTypeDef = (ClassDescriptorDef) it.next();

            for (int idx = 0; idx < missingFields.size();) {
                FieldDescriptorDef fieldDef = subTypeDef.getField((String) missingFields.get(idx));

                if (fieldDef != null) {
                    fkFields.put(fieldDef.getName(), fieldDef);
                    missingFields.remove(idx);
                } else {
                    idx++;
                }
            }
        }
        if (!missingFields.isEmpty()) {
            throw new ConstraintException(
                    "Cannot find field " + missingFields.get(0).toString() + " in the hierarchy with root type "
                            + elementClassDef.getName() + " which is used as foreignkey in collection "
                            + collDef.getName() + " in " + collDef.getOwner().getName());
        }

        // copy the found fields into the element class
        ensureFields(elementClassDef, fkFields.values());
    }

    /**
     * Gathers the pk fields from the hierarchy of the given class, and copies them into the class.
     * 
     * @param classDef The root of the hierarchy
     * @throws ConstraintException If there is a conflict between the pk fields 
     */
    private void ensurePKsFromHierarchy(ClassDescriptorDef classDef) throws ConstraintException {
        SequencedHashMap pks = new SequencedHashMap();

        for (Iterator it = classDef.getAllExtentClasses(); it.hasNext();) {
            ClassDescriptorDef subTypeDef = (ClassDescriptorDef) it.next();

            ArrayList subPKs = subTypeDef.getPrimaryKeys();

            // check against already present PKs
            for (Iterator pkIt = subPKs.iterator(); pkIt.hasNext();) {
                FieldDescriptorDef fieldDef = (FieldDescriptorDef) pkIt.next();
                FieldDescriptorDef foundPKDef = (FieldDescriptorDef) pks.get(fieldDef.getName());

                if (foundPKDef != null) {
                    if (!isEqual(fieldDef, foundPKDef)) {
                        throw new ConstraintException(
                                "Cannot pull up the declaration of the required primary key " + fieldDef.getName()
                                        + " because its definitions in " + fieldDef.getOwner().getName() + " and "
                                        + foundPKDef.getOwner().getName() + " differ");
                    }
                } else {
                    pks.put(fieldDef.getName(), fieldDef);
                }
            }
        }

        ensureFields(classDef, pks.values());
    }

    /**
     * Ensures that the specified fields are present in the given class.
     * 
     * @param classDef The class to copy the fields into
     * @param fields   The fields to copy
     * @throws ConstraintException If there is a conflict between the new fields and fields in the class  
     */
    private void ensureFields(ClassDescriptorDef classDef, Collection fields) throws ConstraintException {
        boolean forceVirtual = !classDef.getBooleanProperty(PropertyHelper.OJB_PROPERTY_GENERATE_REPOSITORY_INFO,
                true);

        for (Iterator it = fields.iterator(); it.hasNext();) {
            FieldDescriptorDef fieldDef = (FieldDescriptorDef) it.next();

            // First we check whether this field is already present in the class
            FieldDescriptorDef foundFieldDef = classDef.getField(fieldDef.getName());

            if (foundFieldDef != null) {
                if (isEqual(fieldDef, foundFieldDef)) {
                    if (forceVirtual) {
                        foundFieldDef.setProperty(PropertyHelper.OJB_PROPERTY_VIRTUAL_FIELD, "true");
                    }
                    continue;
                } else {
                    throw new ConstraintException("Cannot pull up the declaration of the required field "
                            + fieldDef.getName() + " from type " + fieldDef.getOwner().getName() + " to basetype "
                            + classDef.getName() + " because there is already a different field of the same name");
                }
            }

            // perhaps a reference or collection ?
            if (classDef.getCollection(fieldDef.getName()) != null) {
                throw new ConstraintException("Cannot pull up the declaration of the required field "
                        + fieldDef.getName() + " from type " + fieldDef.getOwner().getName() + " to basetype "
                        + classDef.getName() + " because there is already a collection of the same name");
            }
            if (classDef.getReference(fieldDef.getName()) != null) {
                throw new ConstraintException("Cannot pull up the declaration of the required field "
                        + fieldDef.getName() + " from type " + fieldDef.getOwner().getName() + " to basetype "
                        + classDef.getName() + " because there is already a reference of the same name");
            }
            classDef.addFieldClone(fieldDef);
            classDef.getField(fieldDef.getName()).setProperty(PropertyHelper.OJB_PROPERTY_VIRTUAL_FIELD, "true");
        }
    }

    /**
     * Tests whether the two field descriptors are equal, i.e. have same name, same column
     * and same jdbc-type.
     * 
     * @param first  The first field
     * @param second The second field
     * @return <code>true</code> if they are equal
     */
    private boolean isEqual(FieldDescriptorDef first, FieldDescriptorDef second) {
        return first.getName().equals(second.getName())
                && first.getProperty(PropertyHelper.OJB_PROPERTY_COLUMN)
                        .equals(second.getProperty(PropertyHelper.OJB_PROPERTY_COLUMN))
                && first.getProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE)
                        .equals(second.getProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE));
    }

    /**
     * Checks the foreignkeys of all collections in the model.
     * 
     * @param modelDef   The model
     * @param checkLevel The current check level (this constraint is checked in basic and strict)
     * @exception ConstraintException If the value for foreignkey is invalid
     */
    private void checkCollectionForeignkeys(ModelDef modelDef, String checkLevel) throws ConstraintException {
        if (CHECKLEVEL_NONE.equals(checkLevel)) {
            return;
        }

        ClassDescriptorDef classDef;
        CollectionDescriptorDef collDef;

        for (Iterator it = modelDef.getClasses(); it.hasNext();) {
            classDef = (ClassDescriptorDef) it.next();
            for (Iterator collIt = classDef.getCollections(); collIt.hasNext();) {
                collDef = (CollectionDescriptorDef) collIt.next();
                if (!collDef.getBooleanProperty(PropertyHelper.OJB_PROPERTY_IGNORE, false)) {
                    if (collDef.hasProperty(PropertyHelper.OJB_PROPERTY_INDIRECTION_TABLE)) {
                        checkIndirectionTable(modelDef, collDef);
                    } else {
                        checkCollectionForeignkeys(modelDef, collDef);
                    }
                }
            }
        }
    }

    /**
     * Checks the indirection-table and foreignkey of the collection. This constraint also ensures that
     * for the collections on both ends (if they exist), the remote-foreignkey property is set correctly.
     * 
     * @param modelDef The model
     * @param collDef  The collection descriptor
     * @exception ConstraintException If the value for foreignkey is invalid
     */
    private void checkIndirectionTable(ModelDef modelDef, CollectionDescriptorDef collDef)
            throws ConstraintException {
        String foreignkey = collDef.getProperty(PropertyHelper.OJB_PROPERTY_FOREIGNKEY);

        if ((foreignkey == null) || (foreignkey.length() == 0)) {
            throw new ConstraintException("The collection " + collDef.getName() + " in class "
                    + collDef.getOwner().getName() + " has no foreignkeys");
        }

        // we know that the class is present because the collection constraints have been checked already
        // TODO: we must check whether there is a collection at the other side; if the type does not map to a
        // table then we have to check its subtypes
        String elementClassName = collDef.getProperty(PropertyHelper.OJB_PROPERTY_ELEMENT_CLASS_REF);
        ClassDescriptorDef elementClass = modelDef.getClass(elementClassName);
        CollectionDescriptorDef remoteCollDef = collDef.getRemoteCollection();

        if (remoteCollDef == null) {
            // error if there is none and we don't have remote-foreignkey specified
            if (!collDef.hasProperty(PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY)) {
                throw new ConstraintException("The collection " + collDef.getName() + " in class "
                        + collDef.getOwner().getName()
                        + " must specify remote-foreignkeys as the class on the other side of the m:n association has no corresponding collection");
            }
        } else {
            String remoteKeys2 = remoteCollDef.getProperty(PropertyHelper.OJB_PROPERTY_FOREIGNKEY);

            if (collDef.hasProperty(PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY)) {
                // check that the specified remote-foreignkey equals the remote foreignkey setting
                String remoteKeys1 = collDef.getProperty(PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY);

                if (!CommaListIterator.sameLists(remoteKeys1, remoteKeys2)) {
                    throw new ConstraintException("The remote-foreignkey property specified for collection "
                            + collDef.getName() + " in class " + collDef.getOwner().getName()
                            + " doesn't match the foreignkey property of the corresponding collection "
                            + remoteCollDef.getName() + " in class " + elementClass.getName());
                }
            } else {
                // ensure the remote-foreignkey setting
                collDef.setProperty(PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY, remoteKeys2);
            }
        }

        // issue a warning if the foreignkey and remote-foreignkey columns are the same (issue OJB-67)
        String remoteForeignkey = collDef.getProperty(PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY);

        if (CommaListIterator.sameLists(foreignkey, remoteForeignkey)) {
            LogHelper.warn(true, getClass(), "checkIndirectionTable",
                    "The remote foreignkey (" + remoteForeignkey + ") for the collection " + collDef.getName()
                            + " in class " + collDef.getOwner().getName()
                            + " is identical (ignoring case) to the foreign key (" + foreignkey + ").");
        }

        // for torque we generate names for the m:n relation that are unique across inheritance
        // but only if we don't have inherited collections
        if (collDef.getOriginal() != null) {
            CollectionDescriptorDef origDef = (CollectionDescriptorDef) collDef.getOriginal();
            CollectionDescriptorDef origRemoteDef = origDef.getRemoteCollection();

            // we're removing any torque relation name properties from the base collection
            origDef.setProperty(PropertyHelper.TORQUE_PROPERTY_RELATION_NAME, null);
            origDef.setProperty(PropertyHelper.TORQUE_PROPERTY_INV_RELATION_NAME, null);
            if (origRemoteDef != null) {
                origRemoteDef.setProperty(PropertyHelper.TORQUE_PROPERTY_RELATION_NAME, null);
                origRemoteDef.setProperty(PropertyHelper.TORQUE_PROPERTY_INV_RELATION_NAME, null);
            }
        } else if (!collDef.hasProperty(PropertyHelper.TORQUE_PROPERTY_RELATION_NAME)) {
            if (remoteCollDef == null) {
                collDef.setProperty(PropertyHelper.TORQUE_PROPERTY_RELATION_NAME, collDef.getName());
                collDef.setProperty(PropertyHelper.TORQUE_PROPERTY_INV_RELATION_NAME,
                        "inverse " + collDef.getName());
            } else {
                String relName = collDef.getName() + "-" + remoteCollDef.getName();

                collDef.setProperty(PropertyHelper.TORQUE_PROPERTY_RELATION_NAME, relName);
                remoteCollDef.setProperty(PropertyHelper.TORQUE_PROPERTY_INV_RELATION_NAME, relName);

                relName = remoteCollDef.getName() + "-" + collDef.getName();

                collDef.setProperty(PropertyHelper.TORQUE_PROPERTY_INV_RELATION_NAME, relName);
                remoteCollDef.setProperty(PropertyHelper.TORQUE_PROPERTY_RELATION_NAME, relName);
            }
        }
    }

    /**
     * Checks the foreignkeys of the collection.
     * 
     * @param modelDef The model
     * @param collDef  The collection descriptor
     * @exception ConstraintException If the value for foreignkey is invalid
     */
    private void checkCollectionForeignkeys(ModelDef modelDef, CollectionDescriptorDef collDef)
            throws ConstraintException {
        String foreignkey = collDef.getProperty(PropertyHelper.OJB_PROPERTY_FOREIGNKEY);

        if ((foreignkey == null) || (foreignkey.length() == 0)) {
            throw new ConstraintException("The collection " + collDef.getName() + " in class "
                    + collDef.getOwner().getName() + " has no foreignkeys");
        }

        String remoteForeignkey = collDef.getProperty(PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY);

        if ((remoteForeignkey != null) && (remoteForeignkey.length() > 0)) {
            // warning because a remote-foreignkey was specified for a 1:n collection (issue OJB-67)
            LogHelper.warn(true, getClass(), "checkCollectionForeignkeys",
                    "For the collection " + collDef.getName() + " in class " + collDef.getOwner().getName()
                            + ", a remote foreignkey was specified though it is a 1:n, not a m:n collection");
        }

        ClassDescriptorDef ownerClass = (ClassDescriptorDef) collDef.getOwner();
        ArrayList primFields = ownerClass.getPrimaryKeys();
        String elementClassName = collDef.getProperty(PropertyHelper.OJB_PROPERTY_ELEMENT_CLASS_REF);
        ArrayList queue = new ArrayList();
        ClassDescriptorDef elementClass;
        ArrayList keyFields;
        FieldDescriptorDef keyField;
        FieldDescriptorDef primField;
        String primType;
        String keyType;

        // we know that the class is present because the collection constraints have been checked already
        queue.add(modelDef.getClass(elementClassName));
        while (!queue.isEmpty()) {
            elementClass = (ClassDescriptorDef) queue.get(0);
            queue.remove(0);

            for (Iterator it = elementClass.getExtentClasses(); it.hasNext();) {
                queue.add(it.next());
            }
            if (!elementClass.getBooleanProperty(PropertyHelper.OJB_PROPERTY_GENERATE_REPOSITORY_INFO, true)) {
                continue;
            }
            try {
                keyFields = elementClass.getFields(foreignkey);
            } catch (NoSuchFieldException ex) {
                throw new ConstraintException("The collection " + collDef.getName() + " in class "
                        + collDef.getOwner().getName() + " specifies a foreignkey " + ex.getMessage()
                        + " that is not a persistent field in the element class (or its subclass) "
                        + elementClass.getName());
            }
            if (primFields.size() != keyFields.size()) {
                throw new ConstraintException("The number of foreignkeys (" + keyFields.size()
                        + ") of the collection " + collDef.getName() + " in class " + collDef.getOwner().getName()
                        + " doesn't match the number of primarykeys (" + primFields.size() + ") of its owner class "
                        + ownerClass.getName());
            }
            for (int idx = 0; idx < keyFields.size(); idx++) {
                keyField = (FieldDescriptorDef) keyFields.get(idx);
                if (keyField.getBooleanProperty(PropertyHelper.OJB_PROPERTY_IGNORE, false)) {
                    throw new ConstraintException("The collection " + collDef.getName() + " in class "
                            + ownerClass.getName() + " uses the field " + keyField.getName()
                            + " as foreignkey although this field is ignored in the element class (or its subclass) "
                            + elementClass.getName());
                }
            }
            // the jdbc types of the primary keys must match the jdbc types of the foreignkeys (in the correct order)
            for (int idx = 0; idx < primFields.size(); idx++) {
                keyField = (FieldDescriptorDef) keyFields.get(idx);
                if (keyField.getBooleanProperty(PropertyHelper.OJB_PROPERTY_IGNORE, false)) {
                    throw new ConstraintException("The collection " + collDef.getName() + " in class "
                            + ownerClass.getName() + " uses the field " + keyField.getName()
                            + " as foreignkey although this field is ignored in the element class (or its subclass) "
                            + elementClass.getName());
                }
                primField = (FieldDescriptorDef) primFields.get(idx);
                primType = primField.getProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE);
                keyType = keyField.getProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE);
                if (!primType.equals(keyType)) {
                    throw new ConstraintException("The jdbc-type of foreignkey " + keyField.getName()
                            + " in the element class (or its subclass) " + elementClass.getName()
                            + " used by the collection " + collDef.getName() + " in class " + ownerClass.getName()
                            + " doesn't match the jdbc-type of the corresponding primarykey "
                            + primField.getName());
                }
            }
        }
    }

    /**
     * Checks the foreignkeys of all references in the model.
     * 
     * @param modelDef   The model
     * @param checkLevel The current check level (this constraint is checked in basic and strict)
     * @exception ConstraintException If the value for foreignkey is invalid
     */
    private void checkReferenceForeignkeys(ModelDef modelDef, String checkLevel) throws ConstraintException {
        if (CHECKLEVEL_NONE.equals(checkLevel)) {
            return;
        }

        ClassDescriptorDef classDef;
        ReferenceDescriptorDef refDef;

        for (Iterator it = modelDef.getClasses(); it.hasNext();) {
            classDef = (ClassDescriptorDef) it.next();
            for (Iterator refIt = classDef.getReferences(); refIt.hasNext();) {
                refDef = (ReferenceDescriptorDef) refIt.next();
                if (!refDef.getBooleanProperty(PropertyHelper.OJB_PROPERTY_IGNORE, false)) {
                    checkReferenceForeignkeys(modelDef, refDef);
                }
            }
        }
    }

    /**
     * Checks the foreignkeys of a reference.
     * 
     * @param modelDef The model
     * @param refDef   The reference descriptor
     * @exception ConstraintException If the value for foreignkey is invalid
     */
    private void checkReferenceForeignkeys(ModelDef modelDef, ReferenceDescriptorDef refDef)
            throws ConstraintException {
        String foreignkey = refDef.getProperty(PropertyHelper.OJB_PROPERTY_FOREIGNKEY);

        if ((foreignkey == null) || (foreignkey.length() == 0)) {
            throw new ConstraintException("The reference " + refDef.getName() + " in class "
                    + refDef.getOwner().getName() + " has no foreignkeys");
        }

        // we know that the class is present because the reference constraints have been checked already
        ClassDescriptorDef ownerClass = (ClassDescriptorDef) refDef.getOwner();
        ArrayList keyFields;
        FieldDescriptorDef keyField;

        try {
            keyFields = ownerClass.getFields(foreignkey);
        } catch (NoSuchFieldException ex) {
            throw new ConstraintException("The reference " + refDef.getName() + " in class "
                    + refDef.getOwner().getName() + " specifies a foreignkey " + ex.getMessage()
                    + " that is not a persistent field in its owner class " + ownerClass.getName());
        }
        for (int idx = 0; idx < keyFields.size(); idx++) {
            keyField = (FieldDescriptorDef) keyFields.get(idx);
            if (keyField.getBooleanProperty(PropertyHelper.OJB_PROPERTY_IGNORE, false)) {
                throw new ConstraintException("The reference " + refDef.getName() + " in class "
                        + ownerClass.getName() + " uses the field " + keyField.getName()
                        + " as foreignkey although this field is ignored in this class");
            }
        }

        // for the referenced class and any subtype that is instantiable (i.e. not an interface or abstract class)
        // there must be the same number of primary keys and the jdbc types of the primary keys must
        // match the jdbc types of the foreignkeys (in the correct order)
        String targetClassName = refDef.getProperty(PropertyHelper.OJB_PROPERTY_CLASS_REF);
        ArrayList queue = new ArrayList();
        ClassDescriptorDef referencedClass;
        ArrayList primFields;
        FieldDescriptorDef primField;
        String primType;
        String keyType;

        queue.add(modelDef.getClass(targetClassName));

        while (!queue.isEmpty()) {
            referencedClass = (ClassDescriptorDef) queue.get(0);
            queue.remove(0);

            for (Iterator it = referencedClass.getExtentClasses(); it.hasNext();) {
                queue.add(it.next());
            }
            if (!referencedClass.getBooleanProperty(PropertyHelper.OJB_PROPERTY_GENERATE_REPOSITORY_INFO, true)) {
                continue;
            }
            primFields = referencedClass.getPrimaryKeys();
            if (primFields.size() != keyFields.size()) {
                throw new ConstraintException("The number of foreignkeys (" + keyFields.size()
                        + ") of the reference " + refDef.getName() + " in class " + refDef.getOwner().getName()
                        + " doesn't match the number of primarykeys (" + primFields.size()
                        + ") of the referenced class (or its subclass) " + referencedClass.getName());
            }
            for (int idx = 0; idx < primFields.size(); idx++) {
                keyField = (FieldDescriptorDef) keyFields.get(idx);
                primField = (FieldDescriptorDef) primFields.get(idx);
                primType = primField.getProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE);
                keyType = keyField.getProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE);
                if (!primType.equals(keyType)) {
                    throw new ConstraintException("The jdbc-type of foreignkey " + keyField.getName()
                            + " of the reference " + refDef.getName() + " in class " + refDef.getOwner().getName()
                            + " doesn't match the jdbc-type of the corresponding primarykey " + primField.getName()
                            + " of the referenced class (or its subclass) " + referencedClass.getName());
                }
            }
        }
    }

    /**
     * Checks the modifications of fields used as foreignkeys in references/collections or the corresponding primarykeys,
     * e.g. that the jdbc-type is not changed etc.
     * 
     * @param modelDef   The model to check
     * @param checkLevel The current check level (this constraint is checked in basic and strict)
     * @throws ConstraintException If such a field has invalid modifications
     */
    private void checkKeyModifications(ModelDef modelDef, String checkLevel) throws ConstraintException {
        if (CHECKLEVEL_NONE.equals(checkLevel)) {
            return;
        }

        ClassDescriptorDef classDef;
        FieldDescriptorDef fieldDef;

        // we check for every inherited field
        for (Iterator classIt = modelDef.getClasses(); classIt.hasNext();) {
            classDef = (ClassDescriptorDef) classIt.next();
            for (Iterator fieldIt = classDef.getFields(); fieldIt.hasNext();) {
                fieldDef = (FieldDescriptorDef) fieldIt.next();
                if (fieldDef.isInherited()) {
                    checkKeyModifications(modelDef, fieldDef);
                }
            }
        }
    }

    /**
     * Checks the modifications of the given inherited field if it is used as a foreignkey in a
     * reference/collection or as the corresponding primarykey, e.g. that the jdbc-type is not changed etc.
     * 
     * @param modelDef The model to check
     * @throws ConstraintException If the field has invalid modifications
     */
    private void checkKeyModifications(ModelDef modelDef, FieldDescriptorDef keyDef) throws ConstraintException {
        // we check the field if it changes the primarykey-status or the jdbc-type
        FieldDescriptorDef baseFieldDef = (FieldDescriptorDef) keyDef.getOriginal();
        boolean isIgnored = keyDef.getBooleanProperty(PropertyHelper.OJB_PROPERTY_IGNORE, false);
        boolean changesJdbcType = !baseFieldDef.getProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE)
                .equals(keyDef.getProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE));
        boolean changesPrimary = baseFieldDef.getBooleanProperty(PropertyHelper.OJB_PROPERTY_PRIMARYKEY,
                false) != keyDef.getBooleanProperty(PropertyHelper.OJB_PROPERTY_PRIMARYKEY, false);

        if (isIgnored || changesJdbcType || changesPrimary) {
            FeatureDescriptorDef usingFeature = null;

            do {
                usingFeature = usedByReference(modelDef, baseFieldDef);
                if (usingFeature != null) {
                    if (isIgnored) {
                        throw new ConstraintException("Cannot ignore field " + keyDef.getName() + " in class "
                                + keyDef.getOwner().getName() + " because it is used in class "
                                + baseFieldDef.getOwner().getName() + " by the reference " + usingFeature.getName()
                                + " from class " + usingFeature.getOwner().getName());
                    } else if (changesJdbcType) {
                        throw new ConstraintException("Modification of the jdbc-type for the field "
                                + keyDef.getName() + " in class " + keyDef.getOwner().getName()
                                + " is not allowed because it is used in class " + baseFieldDef.getOwner().getName()
                                + " by the reference " + usingFeature.getName() + " from class "
                                + usingFeature.getOwner().getName());
                    } else {
                        throw new ConstraintException("Cannot change the primarykey status of field "
                                + keyDef.getName() + " in class " + keyDef.getOwner().getName()
                                + " as primarykeys are used in class " + baseFieldDef.getOwner().getName()
                                + " by the reference " + usingFeature.getName() + " from class "
                                + usingFeature.getOwner().getName());
                    }
                }

                usingFeature = usedByCollection(modelDef, baseFieldDef, changesPrimary);
                if (usingFeature != null) {
                    if (isIgnored) {
                        throw new ConstraintException("Cannot ignore field " + keyDef.getName() + " in class "
                                + keyDef.getOwner().getName() + " because it is used in class "
                                + baseFieldDef.getOwner().getName() + " as a foreignkey of the collection "
                                + usingFeature.getName() + " from class " + usingFeature.getOwner().getName());
                    } else if (changesJdbcType) {
                        throw new ConstraintException("Modification of the jdbc-type for the field "
                                + keyDef.getName() + " in class " + keyDef.getOwner().getName()
                                + " is not allowed because it is used in class " + baseFieldDef.getOwner().getName()
                                + " as a foreignkey of the collecton " + usingFeature.getName() + " from class "
                                + usingFeature.getOwner().getName());
                    } else {
                        throw new ConstraintException("Cannot change the primarykey status of field "
                                + keyDef.getName() + " in class " + keyDef.getOwner().getName()
                                + " as primarykeys are used in class " + baseFieldDef.getOwner().getName()
                                + " by the collection " + usingFeature.getName() + " from class "
                                + usingFeature.getOwner().getName());
                    }
                }

                baseFieldDef = (FieldDescriptorDef) baseFieldDef.getOriginal();
            } while (baseFieldDef != null);
        }
    }

    /**
     * Checks whether the given field definition is used as a remote-foreignkey in an m:n
     * association where the class owning the field has no collection for the association.
     * 
     * @param modelDef             The model
     * @param fieldDef             The current field descriptor def
     * @param elementClassSuffices Whether it suffices that the owner class of the field is an
     *                             element class of a collection (for primary key tests)
     * @return The collection that uses the field or <code>null</code> if the field is not
     *         used in this way
     */
    private CollectionDescriptorDef usedByCollection(ModelDef modelDef, FieldDescriptorDef fieldDef,
            boolean elementClassSuffices) {
        ClassDescriptorDef ownerClass = (ClassDescriptorDef) fieldDef.getOwner();
        String ownerClassName = ownerClass.getQualifiedName();
        String name = fieldDef.getName();
        ClassDescriptorDef classDef;
        CollectionDescriptorDef collDef;
        String elementClassName;

        for (Iterator classIt = modelDef.getClasses(); classIt.hasNext();) {
            classDef = (ClassDescriptorDef) classIt.next();
            for (Iterator collIt = classDef.getCollections(); collIt.hasNext();) {
                collDef = (CollectionDescriptorDef) collIt.next();
                elementClassName = collDef.getProperty(PropertyHelper.OJB_PROPERTY_ELEMENT_CLASS_REF).replace('$',
                        '.');
                // if the owner class of the field is the element class of a normal collection
                // and the field is a foreignkey of this collection
                if (ownerClassName.equals(elementClassName)) {
                    if (collDef.hasProperty(PropertyHelper.OJB_PROPERTY_INDIRECTION_TABLE)) {
                        if (elementClassSuffices) {
                            return collDef;
                        }
                    } else if (new CommaListIterator(collDef.getProperty(PropertyHelper.OJB_PROPERTY_FOREIGNKEY))
                            .contains(name)) {
                        // if the field is a foreignkey of this normal 1:n collection
                        return collDef;
                    }
                }
            }
        }
        return null;
    }

    /**
     * Checks whether the given field definition is used as the primary key of a class referenced by
     * a reference.
     * 
     * @param modelDef The model
     * @param fieldDef The current field descriptor def
     * @return The reference that uses the field or <code>null</code> if the field is not used in this way
     */
    private ReferenceDescriptorDef usedByReference(ModelDef modelDef, FieldDescriptorDef fieldDef) {
        String ownerClassName = ((ClassDescriptorDef) fieldDef.getOwner()).getQualifiedName();
        ClassDescriptorDef classDef;
        ReferenceDescriptorDef refDef;
        String targetClassName;

        // only relevant for primarykey fields
        if (PropertyHelper.toBoolean(fieldDef.getProperty(PropertyHelper.OJB_PROPERTY_PRIMARYKEY), false)) {
            for (Iterator classIt = modelDef.getClasses(); classIt.hasNext();) {
                classDef = (ClassDescriptorDef) classIt.next();
                for (Iterator refIt = classDef.getReferences(); refIt.hasNext();) {
                    refDef = (ReferenceDescriptorDef) refIt.next();
                    targetClassName = refDef.getProperty(PropertyHelper.OJB_PROPERTY_CLASS_REF).replace('$', '.');
                    if (ownerClassName.equals(targetClassName)) {
                        // the field is a primary key of the class referenced by this reference descriptor
                        return refDef;
                    }
                }
            }
        }
        return null;
    }
}