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;
}
}