0001: package xdoclet.modules.ojb.constraints;
0002:
0003: /* Copyright 2004-2005 The Apache Software Foundation
0004: *
0005: * Licensed under the Apache License, Version 2.0 (the "License");
0006: * you may not use this file except in compliance with the License.
0007: * You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017:
0018: import java.util.ArrayList;
0019: import java.util.Collection;
0020: import java.util.Iterator;
0021:
0022: import org.apache.commons.collections.SequencedHashMap;
0023:
0024: import xdoclet.modules.ojb.CommaListIterator;
0025: import xdoclet.modules.ojb.LogHelper;
0026: import xdoclet.modules.ojb.model.ClassDescriptorDef;
0027: import xdoclet.modules.ojb.model.CollectionDescriptorDef;
0028: import xdoclet.modules.ojb.model.FeatureDescriptorDef;
0029: import xdoclet.modules.ojb.model.FieldDescriptorDef;
0030: import xdoclet.modules.ojb.model.ModelDef;
0031: import xdoclet.modules.ojb.model.PropertyHelper;
0032: import xdoclet.modules.ojb.model.ReferenceDescriptorDef;
0033:
0034: /**
0035: * Checks constraints that span deal with parts of the model, not just with one class.
0036: * This for instance means relationships (collections, references).
0037: *
0038: * @author <a href="mailto:tomdz@users.sourceforge.net">Thomas Dudziak (tomdz@users.sourceforge.net)</a>
0039: */
0040: public class ModelConstraints extends ConstraintsBase {
0041: /**
0042: * Checks the given model.
0043: *
0044: * @param modelDef The model
0045: * @param checkLevel The amount of checks to perform
0046: * @exception ConstraintException If a constraint has been violated
0047: */
0048: public void check(ModelDef modelDef, String checkLevel)
0049: throws ConstraintException {
0050: ensureReferencedKeys(modelDef, checkLevel);
0051: checkReferenceForeignkeys(modelDef, checkLevel);
0052: checkCollectionForeignkeys(modelDef, checkLevel);
0053: checkKeyModifications(modelDef, checkLevel);
0054: }
0055:
0056: /**
0057: * Ensures that the primary/foreign keys referenced by references/collections are present
0058: * in the target type even if generate-table-info="false", by evaluating the subtypes
0059: * of the target type.
0060: *
0061: * @param modelDef The model
0062: * @param checkLevel The current check level (this constraint is always checked)
0063: * @throws ConstraintException If there is an error with the keys of the subtypes or there
0064: * ain't any subtypes
0065: */
0066: private void ensureReferencedKeys(ModelDef modelDef,
0067: String checkLevel) throws ConstraintException {
0068: ClassDescriptorDef classDef;
0069: CollectionDescriptorDef collDef;
0070: ReferenceDescriptorDef refDef;
0071:
0072: for (Iterator it = modelDef.getClasses(); it.hasNext();) {
0073: classDef = (ClassDescriptorDef) it.next();
0074: for (Iterator refIt = classDef.getReferences(); refIt
0075: .hasNext();) {
0076: refDef = (ReferenceDescriptorDef) refIt.next();
0077: if (!refDef.getBooleanProperty(
0078: PropertyHelper.OJB_PROPERTY_IGNORE, false)) {
0079: ensureReferencedPKs(modelDef, refDef);
0080: }
0081: }
0082: for (Iterator collIt = classDef.getCollections(); collIt
0083: .hasNext();) {
0084: collDef = (CollectionDescriptorDef) collIt.next();
0085: if (!collDef.getBooleanProperty(
0086: PropertyHelper.OJB_PROPERTY_IGNORE, false)) {
0087: if (collDef
0088: .hasProperty(PropertyHelper.OJB_PROPERTY_INDIRECTION_TABLE)) {
0089: ensureReferencedPKs(modelDef, collDef);
0090: } else {
0091: ensureReferencedFKs(modelDef, collDef);
0092: }
0093: }
0094: }
0095: }
0096: }
0097:
0098: /**
0099: * Ensures that the primary keys required by the given reference are present in the referenced class.
0100: *
0101: * @param modelDef The model
0102: * @param refDef The reference
0103: * @throws ConstraintException If there is a conflict between the primary keys
0104: */
0105: private void ensureReferencedPKs(ModelDef modelDef,
0106: ReferenceDescriptorDef refDef) throws ConstraintException {
0107: String targetClassName = refDef
0108: .getProperty(PropertyHelper.OJB_PROPERTY_CLASS_REF);
0109: ClassDescriptorDef targetClassDef = modelDef
0110: .getClass(targetClassName);
0111:
0112: ensurePKsFromHierarchy(targetClassDef);
0113: }
0114:
0115: /**
0116: * Ensures that the primary keys required by the given collection with indirection table are present in
0117: * the element class.
0118: *
0119: * @param modelDef The model
0120: * @param collDef The collection
0121: * @throws ConstraintException If there is a problem with the fitting collection (if any) or the primary keys
0122: */
0123: private void ensureReferencedPKs(ModelDef modelDef,
0124: CollectionDescriptorDef collDef) throws ConstraintException {
0125: String elementClassName = collDef
0126: .getProperty(PropertyHelper.OJB_PROPERTY_ELEMENT_CLASS_REF);
0127: ClassDescriptorDef elementClassDef = modelDef
0128: .getClass(elementClassName);
0129: String indirTable = collDef
0130: .getProperty(PropertyHelper.OJB_PROPERTY_INDIRECTION_TABLE);
0131: String localKey = collDef
0132: .getProperty(PropertyHelper.OJB_PROPERTY_FOREIGNKEY);
0133: String remoteKey = collDef
0134: .getProperty(PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY);
0135: boolean hasRemoteKey = remoteKey != null;
0136: ArrayList fittingCollections = new ArrayList();
0137:
0138: // we're checking for the fitting remote collection(s) and also
0139: // use their foreignkey as remote-foreignkey in the original collection definition
0140: for (Iterator it = elementClassDef.getAllExtentClasses(); it
0141: .hasNext();) {
0142: ClassDescriptorDef subTypeDef = (ClassDescriptorDef) it
0143: .next();
0144:
0145: // find the collection in the element class that has the same indirection table
0146: for (Iterator collIt = subTypeDef.getCollections(); collIt
0147: .hasNext();) {
0148: CollectionDescriptorDef curCollDef = (CollectionDescriptorDef) collIt
0149: .next();
0150:
0151: if (indirTable
0152: .equals(curCollDef
0153: .getProperty(PropertyHelper.OJB_PROPERTY_INDIRECTION_TABLE))
0154: && (collDef != curCollDef)
0155: && (!hasRemoteKey || CommaListIterator
0156: .sameLists(
0157: remoteKey,
0158: curCollDef
0159: .getProperty(PropertyHelper.OJB_PROPERTY_FOREIGNKEY)))
0160: && (!curCollDef
0161: .hasProperty(PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY) || CommaListIterator
0162: .sameLists(
0163: localKey,
0164: curCollDef
0165: .getProperty(PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY)))) {
0166: fittingCollections.add(curCollDef);
0167: }
0168: }
0169: }
0170: if (!fittingCollections.isEmpty()) {
0171: // if there is more than one, check that they match, i.e. that they all have the same foreignkeys
0172: if (!hasRemoteKey && (fittingCollections.size() > 1)) {
0173: CollectionDescriptorDef firstCollDef = (CollectionDescriptorDef) fittingCollections
0174: .get(0);
0175: String foreignKey = firstCollDef
0176: .getProperty(PropertyHelper.OJB_PROPERTY_FOREIGNKEY);
0177:
0178: for (int idx = 1; idx < fittingCollections.size(); idx++) {
0179: CollectionDescriptorDef curCollDef = (CollectionDescriptorDef) fittingCollections
0180: .get(idx);
0181:
0182: if (!CommaListIterator
0183: .sameLists(
0184: foreignKey,
0185: curCollDef
0186: .getProperty(PropertyHelper.OJB_PROPERTY_FOREIGNKEY))) {
0187: throw new ConstraintException(
0188: "Cannot determine the element-side collection that corresponds to the collection "
0189: + collDef.getName()
0190: + " in type "
0191: + collDef.getOwner().getName()
0192: + " because there are at least two different collections that would fit."
0193: + " Specifying remote-foreignkey in the original collection "
0194: + collDef.getName()
0195: + " will perhaps help");
0196: }
0197: }
0198: // store the found keys at the collections
0199: collDef.setProperty(
0200: PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY,
0201: foreignKey);
0202: for (int idx = 0; idx < fittingCollections.size(); idx++) {
0203: CollectionDescriptorDef curCollDef = (CollectionDescriptorDef) fittingCollections
0204: .get(idx);
0205:
0206: curCollDef
0207: .setProperty(
0208: PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY,
0209: localKey);
0210: }
0211: }
0212: }
0213:
0214: // copy subclass pk fields into target class (if not already present)
0215: ensurePKsFromHierarchy(elementClassDef);
0216: }
0217:
0218: /**
0219: * Ensures that the foreign keys required by the given collection are present in the element class.
0220: *
0221: * @param modelDef The model
0222: * @param collDef The collection
0223: * @throws ConstraintException If there is a problem with the foreign keys
0224: */
0225: private void ensureReferencedFKs(ModelDef modelDef,
0226: CollectionDescriptorDef collDef) throws ConstraintException {
0227: String elementClassName = collDef
0228: .getProperty(PropertyHelper.OJB_PROPERTY_ELEMENT_CLASS_REF);
0229: ClassDescriptorDef elementClassDef = modelDef
0230: .getClass(elementClassName);
0231: String fkFieldNames = collDef
0232: .getProperty(PropertyHelper.OJB_PROPERTY_FOREIGNKEY);
0233: ArrayList missingFields = new ArrayList();
0234: SequencedHashMap fkFields = new SequencedHashMap();
0235:
0236: // first we gather all field names
0237: for (CommaListIterator it = new CommaListIterator(fkFieldNames); it
0238: .hasNext();) {
0239: String fieldName = (String) it.next();
0240: FieldDescriptorDef fieldDef = elementClassDef
0241: .getField(fieldName);
0242:
0243: if (fieldDef == null) {
0244: missingFields.add(fieldName);
0245: }
0246: fkFields.put(fieldName, fieldDef);
0247: }
0248:
0249: // next we traverse all sub types and gather fields as we go
0250: for (Iterator it = elementClassDef.getAllExtentClasses(); it
0251: .hasNext()
0252: && !missingFields.isEmpty();) {
0253: ClassDescriptorDef subTypeDef = (ClassDescriptorDef) it
0254: .next();
0255:
0256: for (int idx = 0; idx < missingFields.size();) {
0257: FieldDescriptorDef fieldDef = subTypeDef
0258: .getField((String) missingFields.get(idx));
0259:
0260: if (fieldDef != null) {
0261: fkFields.put(fieldDef.getName(), fieldDef);
0262: missingFields.remove(idx);
0263: } else {
0264: idx++;
0265: }
0266: }
0267: }
0268: if (!missingFields.isEmpty()) {
0269: throw new ConstraintException("Cannot find field "
0270: + missingFields.get(0).toString()
0271: + " in the hierarchy with root type "
0272: + elementClassDef.getName()
0273: + " which is used as foreignkey in collection "
0274: + collDef.getName() + " in "
0275: + collDef.getOwner().getName());
0276: }
0277:
0278: // copy the found fields into the element class
0279: ensureFields(elementClassDef, fkFields.values());
0280: }
0281:
0282: /**
0283: * Gathers the pk fields from the hierarchy of the given class, and copies them into the class.
0284: *
0285: * @param classDef The root of the hierarchy
0286: * @throws ConstraintException If there is a conflict between the pk fields
0287: */
0288: private void ensurePKsFromHierarchy(ClassDescriptorDef classDef)
0289: throws ConstraintException {
0290: SequencedHashMap pks = new SequencedHashMap();
0291:
0292: for (Iterator it = classDef.getAllExtentClasses(); it.hasNext();) {
0293: ClassDescriptorDef subTypeDef = (ClassDescriptorDef) it
0294: .next();
0295:
0296: ArrayList subPKs = subTypeDef.getPrimaryKeys();
0297:
0298: // check against already present PKs
0299: for (Iterator pkIt = subPKs.iterator(); pkIt.hasNext();) {
0300: FieldDescriptorDef fieldDef = (FieldDescriptorDef) pkIt
0301: .next();
0302: FieldDescriptorDef foundPKDef = (FieldDescriptorDef) pks
0303: .get(fieldDef.getName());
0304:
0305: if (foundPKDef != null) {
0306: if (!isEqual(fieldDef, foundPKDef)) {
0307: throw new ConstraintException(
0308: "Cannot pull up the declaration of the required primary key "
0309: + fieldDef.getName()
0310: + " because its definitions in "
0311: + fieldDef.getOwner().getName()
0312: + " and "
0313: + foundPKDef.getOwner()
0314: .getName() + " differ");
0315: }
0316: } else {
0317: pks.put(fieldDef.getName(), fieldDef);
0318: }
0319: }
0320: }
0321:
0322: ensureFields(classDef, pks.values());
0323: }
0324:
0325: /**
0326: * Ensures that the specified fields are present in the given class.
0327: *
0328: * @param classDef The class to copy the fields into
0329: * @param fields The fields to copy
0330: * @throws ConstraintException If there is a conflict between the new fields and fields in the class
0331: */
0332: private void ensureFields(ClassDescriptorDef classDef,
0333: Collection fields) throws ConstraintException {
0334: boolean forceVirtual = !classDef.getBooleanProperty(
0335: PropertyHelper.OJB_PROPERTY_GENERATE_REPOSITORY_INFO,
0336: true);
0337:
0338: for (Iterator it = fields.iterator(); it.hasNext();) {
0339: FieldDescriptorDef fieldDef = (FieldDescriptorDef) it
0340: .next();
0341:
0342: // First we check whether this field is already present in the class
0343: FieldDescriptorDef foundFieldDef = classDef
0344: .getField(fieldDef.getName());
0345:
0346: if (foundFieldDef != null) {
0347: if (isEqual(fieldDef, foundFieldDef)) {
0348: if (forceVirtual) {
0349: foundFieldDef
0350: .setProperty(
0351: PropertyHelper.OJB_PROPERTY_VIRTUAL_FIELD,
0352: "true");
0353: }
0354: continue;
0355: } else {
0356: throw new ConstraintException(
0357: "Cannot pull up the declaration of the required field "
0358: + fieldDef.getName()
0359: + " from type "
0360: + fieldDef.getOwner().getName()
0361: + " to basetype "
0362: + classDef.getName()
0363: + " because there is already a different field of the same name");
0364: }
0365: }
0366:
0367: // perhaps a reference or collection ?
0368: if (classDef.getCollection(fieldDef.getName()) != null) {
0369: throw new ConstraintException(
0370: "Cannot pull up the declaration of the required field "
0371: + fieldDef.getName()
0372: + " from type "
0373: + fieldDef.getOwner().getName()
0374: + " to basetype "
0375: + classDef.getName()
0376: + " because there is already a collection of the same name");
0377: }
0378: if (classDef.getReference(fieldDef.getName()) != null) {
0379: throw new ConstraintException(
0380: "Cannot pull up the declaration of the required field "
0381: + fieldDef.getName()
0382: + " from type "
0383: + fieldDef.getOwner().getName()
0384: + " to basetype "
0385: + classDef.getName()
0386: + " because there is already a reference of the same name");
0387: }
0388: classDef.addFieldClone(fieldDef);
0389: classDef.getField(fieldDef.getName()).setProperty(
0390: PropertyHelper.OJB_PROPERTY_VIRTUAL_FIELD, "true");
0391: }
0392: }
0393:
0394: /**
0395: * Tests whether the two field descriptors are equal, i.e. have same name, same column
0396: * and same jdbc-type.
0397: *
0398: * @param first The first field
0399: * @param second The second field
0400: * @return <code>true</code> if they are equal
0401: */
0402: private boolean isEqual(FieldDescriptorDef first,
0403: FieldDescriptorDef second) {
0404: return first.getName().equals(second.getName())
0405: && first
0406: .getProperty(PropertyHelper.OJB_PROPERTY_COLUMN)
0407: .equals(
0408: second
0409: .getProperty(PropertyHelper.OJB_PROPERTY_COLUMN))
0410: && first
0411: .getProperty(
0412: PropertyHelper.OJB_PROPERTY_JDBC_TYPE)
0413: .equals(
0414: second
0415: .getProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE));
0416: }
0417:
0418: /**
0419: * Checks the foreignkeys of all collections in the model.
0420: *
0421: * @param modelDef The model
0422: * @param checkLevel The current check level (this constraint is checked in basic and strict)
0423: * @exception ConstraintException If the value for foreignkey is invalid
0424: */
0425: private void checkCollectionForeignkeys(ModelDef modelDef,
0426: String checkLevel) throws ConstraintException {
0427: if (CHECKLEVEL_NONE.equals(checkLevel)) {
0428: return;
0429: }
0430:
0431: ClassDescriptorDef classDef;
0432: CollectionDescriptorDef collDef;
0433:
0434: for (Iterator it = modelDef.getClasses(); it.hasNext();) {
0435: classDef = (ClassDescriptorDef) it.next();
0436: for (Iterator collIt = classDef.getCollections(); collIt
0437: .hasNext();) {
0438: collDef = (CollectionDescriptorDef) collIt.next();
0439: if (!collDef.getBooleanProperty(
0440: PropertyHelper.OJB_PROPERTY_IGNORE, false)) {
0441: if (collDef
0442: .hasProperty(PropertyHelper.OJB_PROPERTY_INDIRECTION_TABLE)) {
0443: checkIndirectionTable(modelDef, collDef);
0444: } else {
0445: checkCollectionForeignkeys(modelDef, collDef);
0446: }
0447: }
0448: }
0449: }
0450: }
0451:
0452: /**
0453: * Checks the indirection-table and foreignkey of the collection. This constraint also ensures that
0454: * for the collections on both ends (if they exist), the remote-foreignkey property is set correctly.
0455: *
0456: * @param modelDef The model
0457: * @param collDef The collection descriptor
0458: * @exception ConstraintException If the value for foreignkey is invalid
0459: */
0460: private void checkIndirectionTable(ModelDef modelDef,
0461: CollectionDescriptorDef collDef) throws ConstraintException {
0462: String foreignkey = collDef
0463: .getProperty(PropertyHelper.OJB_PROPERTY_FOREIGNKEY);
0464:
0465: if ((foreignkey == null) || (foreignkey.length() == 0)) {
0466: throw new ConstraintException("The collection "
0467: + collDef.getName() + " in class "
0468: + collDef.getOwner().getName()
0469: + " has no foreignkeys");
0470: }
0471:
0472: // we know that the class is present because the collection constraints have been checked already
0473: // TODO: we must check whether there is a collection at the other side; if the type does not map to a
0474: // table then we have to check its subtypes
0475: String elementClassName = collDef
0476: .getProperty(PropertyHelper.OJB_PROPERTY_ELEMENT_CLASS_REF);
0477: ClassDescriptorDef elementClass = modelDef
0478: .getClass(elementClassName);
0479: CollectionDescriptorDef remoteCollDef = collDef
0480: .getRemoteCollection();
0481:
0482: if (remoteCollDef == null) {
0483: // error if there is none and we don't have remote-foreignkey specified
0484: if (!collDef
0485: .hasProperty(PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY)) {
0486: throw new ConstraintException(
0487: "The collection "
0488: + collDef.getName()
0489: + " in class "
0490: + collDef.getOwner().getName()
0491: + " must specify remote-foreignkeys as the class on the other side of the m:n association has no corresponding collection");
0492: }
0493: } else {
0494: String remoteKeys2 = remoteCollDef
0495: .getProperty(PropertyHelper.OJB_PROPERTY_FOREIGNKEY);
0496:
0497: if (collDef
0498: .hasProperty(PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY)) {
0499: // check that the specified remote-foreignkey equals the remote foreignkey setting
0500: String remoteKeys1 = collDef
0501: .getProperty(PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY);
0502:
0503: if (!CommaListIterator.sameLists(remoteKeys1,
0504: remoteKeys2)) {
0505: throw new ConstraintException(
0506: "The remote-foreignkey property specified for collection "
0507: + collDef.getName()
0508: + " in class "
0509: + collDef.getOwner().getName()
0510: + " doesn't match the foreignkey property of the corresponding collection "
0511: + remoteCollDef.getName()
0512: + " in class "
0513: + elementClass.getName());
0514: }
0515: } else {
0516: // ensure the remote-foreignkey setting
0517: collDef.setProperty(
0518: PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY,
0519: remoteKeys2);
0520: }
0521: }
0522:
0523: // issue a warning if the foreignkey and remote-foreignkey columns are the same (issue OJB-67)
0524: String remoteForeignkey = collDef
0525: .getProperty(PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY);
0526:
0527: if (CommaListIterator.sameLists(foreignkey, remoteForeignkey)) {
0528: LogHelper
0529: .warn(
0530: true,
0531: getClass(),
0532: "checkIndirectionTable",
0533: "The remote foreignkey ("
0534: + remoteForeignkey
0535: + ") for the collection "
0536: + collDef.getName()
0537: + " in class "
0538: + collDef.getOwner().getName()
0539: + " is identical (ignoring case) to the foreign key ("
0540: + foreignkey + ").");
0541: }
0542:
0543: // for torque we generate names for the m:n relation that are unique across inheritance
0544: // but only if we don't have inherited collections
0545: if (collDef.getOriginal() != null) {
0546: CollectionDescriptorDef origDef = (CollectionDescriptorDef) collDef
0547: .getOriginal();
0548: CollectionDescriptorDef origRemoteDef = origDef
0549: .getRemoteCollection();
0550:
0551: // we're removing any torque relation name properties from the base collection
0552: origDef.setProperty(
0553: PropertyHelper.TORQUE_PROPERTY_RELATION_NAME, null);
0554: origDef.setProperty(
0555: PropertyHelper.TORQUE_PROPERTY_INV_RELATION_NAME,
0556: null);
0557: if (origRemoteDef != null) {
0558: origRemoteDef.setProperty(
0559: PropertyHelper.TORQUE_PROPERTY_RELATION_NAME,
0560: null);
0561: origRemoteDef
0562: .setProperty(
0563: PropertyHelper.TORQUE_PROPERTY_INV_RELATION_NAME,
0564: null);
0565: }
0566: } else if (!collDef
0567: .hasProperty(PropertyHelper.TORQUE_PROPERTY_RELATION_NAME)) {
0568: if (remoteCollDef == null) {
0569: collDef.setProperty(
0570: PropertyHelper.TORQUE_PROPERTY_RELATION_NAME,
0571: collDef.getName());
0572: collDef
0573: .setProperty(
0574: PropertyHelper.TORQUE_PROPERTY_INV_RELATION_NAME,
0575: "inverse " + collDef.getName());
0576: } else {
0577: String relName = collDef.getName() + "-"
0578: + remoteCollDef.getName();
0579:
0580: collDef.setProperty(
0581: PropertyHelper.TORQUE_PROPERTY_RELATION_NAME,
0582: relName);
0583: remoteCollDef
0584: .setProperty(
0585: PropertyHelper.TORQUE_PROPERTY_INV_RELATION_NAME,
0586: relName);
0587:
0588: relName = remoteCollDef.getName() + "-"
0589: + collDef.getName();
0590:
0591: collDef
0592: .setProperty(
0593: PropertyHelper.TORQUE_PROPERTY_INV_RELATION_NAME,
0594: relName);
0595: remoteCollDef.setProperty(
0596: PropertyHelper.TORQUE_PROPERTY_RELATION_NAME,
0597: relName);
0598: }
0599: }
0600: }
0601:
0602: /**
0603: * Checks the foreignkeys of the collection.
0604: *
0605: * @param modelDef The model
0606: * @param collDef The collection descriptor
0607: * @exception ConstraintException If the value for foreignkey is invalid
0608: */
0609: private void checkCollectionForeignkeys(ModelDef modelDef,
0610: CollectionDescriptorDef collDef) throws ConstraintException {
0611: String foreignkey = collDef
0612: .getProperty(PropertyHelper.OJB_PROPERTY_FOREIGNKEY);
0613:
0614: if ((foreignkey == null) || (foreignkey.length() == 0)) {
0615: throw new ConstraintException("The collection "
0616: + collDef.getName() + " in class "
0617: + collDef.getOwner().getName()
0618: + " has no foreignkeys");
0619: }
0620:
0621: String remoteForeignkey = collDef
0622: .getProperty(PropertyHelper.OJB_PROPERTY_REMOTE_FOREIGNKEY);
0623:
0624: if ((remoteForeignkey != null)
0625: && (remoteForeignkey.length() > 0)) {
0626: // warning because a remote-foreignkey was specified for a 1:n collection (issue OJB-67)
0627: LogHelper
0628: .warn(
0629: true,
0630: getClass(),
0631: "checkCollectionForeignkeys",
0632: "For the collection "
0633: + collDef.getName()
0634: + " in class "
0635: + collDef.getOwner().getName()
0636: + ", a remote foreignkey was specified though it is a 1:n, not a m:n collection");
0637: }
0638:
0639: ClassDescriptorDef ownerClass = (ClassDescriptorDef) collDef
0640: .getOwner();
0641: ArrayList primFields = ownerClass.getPrimaryKeys();
0642: String elementClassName = collDef
0643: .getProperty(PropertyHelper.OJB_PROPERTY_ELEMENT_CLASS_REF);
0644: ArrayList queue = new ArrayList();
0645: ClassDescriptorDef elementClass;
0646: ArrayList keyFields;
0647: FieldDescriptorDef keyField;
0648: FieldDescriptorDef primField;
0649: String primType;
0650: String keyType;
0651:
0652: // we know that the class is present because the collection constraints have been checked already
0653: queue.add(modelDef.getClass(elementClassName));
0654: while (!queue.isEmpty()) {
0655: elementClass = (ClassDescriptorDef) queue.get(0);
0656: queue.remove(0);
0657:
0658: for (Iterator it = elementClass.getExtentClasses(); it
0659: .hasNext();) {
0660: queue.add(it.next());
0661: }
0662: if (!elementClass
0663: .getBooleanProperty(
0664: PropertyHelper.OJB_PROPERTY_GENERATE_REPOSITORY_INFO,
0665: true)) {
0666: continue;
0667: }
0668: try {
0669: keyFields = elementClass.getFields(foreignkey);
0670: } catch (NoSuchFieldException ex) {
0671: throw new ConstraintException(
0672: "The collection "
0673: + collDef.getName()
0674: + " in class "
0675: + collDef.getOwner().getName()
0676: + " specifies a foreignkey "
0677: + ex.getMessage()
0678: + " that is not a persistent field in the element class (or its subclass) "
0679: + elementClass.getName());
0680: }
0681: if (primFields.size() != keyFields.size()) {
0682: throw new ConstraintException(
0683: "The number of foreignkeys ("
0684: + keyFields.size()
0685: + ") of the collection "
0686: + collDef.getName()
0687: + " in class "
0688: + collDef.getOwner().getName()
0689: + " doesn't match the number of primarykeys ("
0690: + primFields.size()
0691: + ") of its owner class "
0692: + ownerClass.getName());
0693: }
0694: for (int idx = 0; idx < keyFields.size(); idx++) {
0695: keyField = (FieldDescriptorDef) keyFields.get(idx);
0696: if (keyField.getBooleanProperty(
0697: PropertyHelper.OJB_PROPERTY_IGNORE, false)) {
0698: throw new ConstraintException(
0699: "The collection "
0700: + collDef.getName()
0701: + " in class "
0702: + ownerClass.getName()
0703: + " uses the field "
0704: + keyField.getName()
0705: + " as foreignkey although this field is ignored in the element class (or its subclass) "
0706: + elementClass.getName());
0707: }
0708: }
0709: // the jdbc types of the primary keys must match the jdbc types of the foreignkeys (in the correct order)
0710: for (int idx = 0; idx < primFields.size(); idx++) {
0711: keyField = (FieldDescriptorDef) keyFields.get(idx);
0712: if (keyField.getBooleanProperty(
0713: PropertyHelper.OJB_PROPERTY_IGNORE, false)) {
0714: throw new ConstraintException(
0715: "The collection "
0716: + collDef.getName()
0717: + " in class "
0718: + ownerClass.getName()
0719: + " uses the field "
0720: + keyField.getName()
0721: + " as foreignkey although this field is ignored in the element class (or its subclass) "
0722: + elementClass.getName());
0723: }
0724: primField = (FieldDescriptorDef) primFields.get(idx);
0725: primType = primField
0726: .getProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE);
0727: keyType = keyField
0728: .getProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE);
0729: if (!primType.equals(keyType)) {
0730: throw new ConstraintException(
0731: "The jdbc-type of foreignkey "
0732: + keyField.getName()
0733: + " in the element class (or its subclass) "
0734: + elementClass.getName()
0735: + " used by the collection "
0736: + collDef.getName()
0737: + " in class "
0738: + ownerClass.getName()
0739: + " doesn't match the jdbc-type of the corresponding primarykey "
0740: + primField.getName());
0741: }
0742: }
0743: }
0744: }
0745:
0746: /**
0747: * Checks the foreignkeys of all references in the model.
0748: *
0749: * @param modelDef The model
0750: * @param checkLevel The current check level (this constraint is checked in basic and strict)
0751: * @exception ConstraintException If the value for foreignkey is invalid
0752: */
0753: private void checkReferenceForeignkeys(ModelDef modelDef,
0754: String checkLevel) throws ConstraintException {
0755: if (CHECKLEVEL_NONE.equals(checkLevel)) {
0756: return;
0757: }
0758:
0759: ClassDescriptorDef classDef;
0760: ReferenceDescriptorDef refDef;
0761:
0762: for (Iterator it = modelDef.getClasses(); it.hasNext();) {
0763: classDef = (ClassDescriptorDef) it.next();
0764: for (Iterator refIt = classDef.getReferences(); refIt
0765: .hasNext();) {
0766: refDef = (ReferenceDescriptorDef) refIt.next();
0767: if (!refDef.getBooleanProperty(
0768: PropertyHelper.OJB_PROPERTY_IGNORE, false)) {
0769: checkReferenceForeignkeys(modelDef, refDef);
0770: }
0771: }
0772: }
0773: }
0774:
0775: /**
0776: * Checks the foreignkeys of a reference.
0777: *
0778: * @param modelDef The model
0779: * @param refDef The reference descriptor
0780: * @exception ConstraintException If the value for foreignkey is invalid
0781: */
0782: private void checkReferenceForeignkeys(ModelDef modelDef,
0783: ReferenceDescriptorDef refDef) throws ConstraintException {
0784: String foreignkey = refDef
0785: .getProperty(PropertyHelper.OJB_PROPERTY_FOREIGNKEY);
0786:
0787: if ((foreignkey == null) || (foreignkey.length() == 0)) {
0788: throw new ConstraintException("The reference "
0789: + refDef.getName() + " in class "
0790: + refDef.getOwner().getName()
0791: + " has no foreignkeys");
0792: }
0793:
0794: // we know that the class is present because the reference constraints have been checked already
0795: ClassDescriptorDef ownerClass = (ClassDescriptorDef) refDef
0796: .getOwner();
0797: ArrayList keyFields;
0798: FieldDescriptorDef keyField;
0799:
0800: try {
0801: keyFields = ownerClass.getFields(foreignkey);
0802: } catch (NoSuchFieldException ex) {
0803: throw new ConstraintException(
0804: "The reference "
0805: + refDef.getName()
0806: + " in class "
0807: + refDef.getOwner().getName()
0808: + " specifies a foreignkey "
0809: + ex.getMessage()
0810: + " that is not a persistent field in its owner class "
0811: + ownerClass.getName());
0812: }
0813: for (int idx = 0; idx < keyFields.size(); idx++) {
0814: keyField = (FieldDescriptorDef) keyFields.get(idx);
0815: if (keyField.getBooleanProperty(
0816: PropertyHelper.OJB_PROPERTY_IGNORE, false)) {
0817: throw new ConstraintException(
0818: "The reference "
0819: + refDef.getName()
0820: + " in class "
0821: + ownerClass.getName()
0822: + " uses the field "
0823: + keyField.getName()
0824: + " as foreignkey although this field is ignored in this class");
0825: }
0826: }
0827:
0828: // for the referenced class and any subtype that is instantiable (i.e. not an interface or abstract class)
0829: // there must be the same number of primary keys and the jdbc types of the primary keys must
0830: // match the jdbc types of the foreignkeys (in the correct order)
0831: String targetClassName = refDef
0832: .getProperty(PropertyHelper.OJB_PROPERTY_CLASS_REF);
0833: ArrayList queue = new ArrayList();
0834: ClassDescriptorDef referencedClass;
0835: ArrayList primFields;
0836: FieldDescriptorDef primField;
0837: String primType;
0838: String keyType;
0839:
0840: queue.add(modelDef.getClass(targetClassName));
0841:
0842: while (!queue.isEmpty()) {
0843: referencedClass = (ClassDescriptorDef) queue.get(0);
0844: queue.remove(0);
0845:
0846: for (Iterator it = referencedClass.getExtentClasses(); it
0847: .hasNext();) {
0848: queue.add(it.next());
0849: }
0850: if (!referencedClass
0851: .getBooleanProperty(
0852: PropertyHelper.OJB_PROPERTY_GENERATE_REPOSITORY_INFO,
0853: true)) {
0854: continue;
0855: }
0856: primFields = referencedClass.getPrimaryKeys();
0857: if (primFields.size() != keyFields.size()) {
0858: throw new ConstraintException(
0859: "The number of foreignkeys ("
0860: + keyFields.size()
0861: + ") of the reference "
0862: + refDef.getName()
0863: + " in class "
0864: + refDef.getOwner().getName()
0865: + " doesn't match the number of primarykeys ("
0866: + primFields.size()
0867: + ") of the referenced class (or its subclass) "
0868: + referencedClass.getName());
0869: }
0870: for (int idx = 0; idx < primFields.size(); idx++) {
0871: keyField = (FieldDescriptorDef) keyFields.get(idx);
0872: primField = (FieldDescriptorDef) primFields.get(idx);
0873: primType = primField
0874: .getProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE);
0875: keyType = keyField
0876: .getProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE);
0877: if (!primType.equals(keyType)) {
0878: throw new ConstraintException(
0879: "The jdbc-type of foreignkey "
0880: + keyField.getName()
0881: + " of the reference "
0882: + refDef.getName()
0883: + " in class "
0884: + refDef.getOwner().getName()
0885: + " doesn't match the jdbc-type of the corresponding primarykey "
0886: + primField.getName()
0887: + " of the referenced class (or its subclass) "
0888: + referencedClass.getName());
0889: }
0890: }
0891: }
0892: }
0893:
0894: /**
0895: * Checks the modifications of fields used as foreignkeys in references/collections or the corresponding primarykeys,
0896: * e.g. that the jdbc-type is not changed etc.
0897: *
0898: * @param modelDef The model to check
0899: * @param checkLevel The current check level (this constraint is checked in basic and strict)
0900: * @throws ConstraintException If such a field has invalid modifications
0901: */
0902: private void checkKeyModifications(ModelDef modelDef,
0903: String checkLevel) throws ConstraintException {
0904: if (CHECKLEVEL_NONE.equals(checkLevel)) {
0905: return;
0906: }
0907:
0908: ClassDescriptorDef classDef;
0909: FieldDescriptorDef fieldDef;
0910:
0911: // we check for every inherited field
0912: for (Iterator classIt = modelDef.getClasses(); classIt
0913: .hasNext();) {
0914: classDef = (ClassDescriptorDef) classIt.next();
0915: for (Iterator fieldIt = classDef.getFields(); fieldIt
0916: .hasNext();) {
0917: fieldDef = (FieldDescriptorDef) fieldIt.next();
0918: if (fieldDef.isInherited()) {
0919: checkKeyModifications(modelDef, fieldDef);
0920: }
0921: }
0922: }
0923: }
0924:
0925: /**
0926: * Checks the modifications of the given inherited field if it is used as a foreignkey in a
0927: * reference/collection or as the corresponding primarykey, e.g. that the jdbc-type is not changed etc.
0928: *
0929: * @param modelDef The model to check
0930: * @throws ConstraintException If the field has invalid modifications
0931: */
0932: private void checkKeyModifications(ModelDef modelDef,
0933: FieldDescriptorDef keyDef) throws ConstraintException {
0934: // we check the field if it changes the primarykey-status or the jdbc-type
0935: FieldDescriptorDef baseFieldDef = (FieldDescriptorDef) keyDef
0936: .getOriginal();
0937: boolean isIgnored = keyDef.getBooleanProperty(
0938: PropertyHelper.OJB_PROPERTY_IGNORE, false);
0939: boolean changesJdbcType = !baseFieldDef
0940: .getProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE)
0941: .equals(
0942: keyDef
0943: .getProperty(PropertyHelper.OJB_PROPERTY_JDBC_TYPE));
0944: boolean changesPrimary = baseFieldDef.getBooleanProperty(
0945: PropertyHelper.OJB_PROPERTY_PRIMARYKEY, false) != keyDef
0946: .getBooleanProperty(
0947: PropertyHelper.OJB_PROPERTY_PRIMARYKEY, false);
0948:
0949: if (isIgnored || changesJdbcType || changesPrimary) {
0950: FeatureDescriptorDef usingFeature = null;
0951:
0952: do {
0953: usingFeature = usedByReference(modelDef, baseFieldDef);
0954: if (usingFeature != null) {
0955: if (isIgnored) {
0956: throw new ConstraintException(
0957: "Cannot ignore field "
0958: + keyDef.getName()
0959: + " in class "
0960: + keyDef.getOwner().getName()
0961: + " because it is used in class "
0962: + baseFieldDef.getOwner()
0963: .getName()
0964: + " by the reference "
0965: + usingFeature.getName()
0966: + " from class "
0967: + usingFeature.getOwner()
0968: .getName());
0969: } else if (changesJdbcType) {
0970: throw new ConstraintException(
0971: "Modification of the jdbc-type for the field "
0972: + keyDef.getName()
0973: + " in class "
0974: + keyDef.getOwner().getName()
0975: + " is not allowed because it is used in class "
0976: + baseFieldDef.getOwner()
0977: .getName()
0978: + " by the reference "
0979: + usingFeature.getName()
0980: + " from class "
0981: + usingFeature.getOwner()
0982: .getName());
0983: } else {
0984: throw new ConstraintException(
0985: "Cannot change the primarykey status of field "
0986: + keyDef.getName()
0987: + " in class "
0988: + keyDef.getOwner().getName()
0989: + " as primarykeys are used in class "
0990: + baseFieldDef.getOwner()
0991: .getName()
0992: + " by the reference "
0993: + usingFeature.getName()
0994: + " from class "
0995: + usingFeature.getOwner()
0996: .getName());
0997: }
0998: }
0999:
1000: usingFeature = usedByCollection(modelDef, baseFieldDef,
1001: changesPrimary);
1002: if (usingFeature != null) {
1003: if (isIgnored) {
1004: throw new ConstraintException(
1005: "Cannot ignore field "
1006: + keyDef.getName()
1007: + " in class "
1008: + keyDef.getOwner().getName()
1009: + " because it is used in class "
1010: + baseFieldDef.getOwner()
1011: .getName()
1012: + " as a foreignkey of the collection "
1013: + usingFeature.getName()
1014: + " from class "
1015: + usingFeature.getOwner()
1016: .getName());
1017: } else if (changesJdbcType) {
1018: throw new ConstraintException(
1019: "Modification of the jdbc-type for the field "
1020: + keyDef.getName()
1021: + " in class "
1022: + keyDef.getOwner().getName()
1023: + " is not allowed because it is used in class "
1024: + baseFieldDef.getOwner()
1025: .getName()
1026: + " as a foreignkey of the collecton "
1027: + usingFeature.getName()
1028: + " from class "
1029: + usingFeature.getOwner()
1030: .getName());
1031: } else {
1032: throw new ConstraintException(
1033: "Cannot change the primarykey status of field "
1034: + keyDef.getName()
1035: + " in class "
1036: + keyDef.getOwner().getName()
1037: + " as primarykeys are used in class "
1038: + baseFieldDef.getOwner()
1039: .getName()
1040: + " by the collection "
1041: + usingFeature.getName()
1042: + " from class "
1043: + usingFeature.getOwner()
1044: .getName());
1045: }
1046: }
1047:
1048: baseFieldDef = (FieldDescriptorDef) baseFieldDef
1049: .getOriginal();
1050: } while (baseFieldDef != null);
1051: }
1052: }
1053:
1054: /**
1055: * Checks whether the given field definition is used as a remote-foreignkey in an m:n
1056: * association where the class owning the field has no collection for the association.
1057: *
1058: * @param modelDef The model
1059: * @param fieldDef The current field descriptor def
1060: * @param elementClassSuffices Whether it suffices that the owner class of the field is an
1061: * element class of a collection (for primary key tests)
1062: * @return The collection that uses the field or <code>null</code> if the field is not
1063: * used in this way
1064: */
1065: private CollectionDescriptorDef usedByCollection(ModelDef modelDef,
1066: FieldDescriptorDef fieldDef, boolean elementClassSuffices) {
1067: ClassDescriptorDef ownerClass = (ClassDescriptorDef) fieldDef
1068: .getOwner();
1069: String ownerClassName = ownerClass.getQualifiedName();
1070: String name = fieldDef.getName();
1071: ClassDescriptorDef classDef;
1072: CollectionDescriptorDef collDef;
1073: String elementClassName;
1074:
1075: for (Iterator classIt = modelDef.getClasses(); classIt
1076: .hasNext();) {
1077: classDef = (ClassDescriptorDef) classIt.next();
1078: for (Iterator collIt = classDef.getCollections(); collIt
1079: .hasNext();) {
1080: collDef = (CollectionDescriptorDef) collIt.next();
1081: elementClassName = collDef.getProperty(
1082: PropertyHelper.OJB_PROPERTY_ELEMENT_CLASS_REF)
1083: .replace('$', '.');
1084: // if the owner class of the field is the element class of a normal collection
1085: // and the field is a foreignkey of this collection
1086: if (ownerClassName.equals(elementClassName)) {
1087: if (collDef
1088: .hasProperty(PropertyHelper.OJB_PROPERTY_INDIRECTION_TABLE)) {
1089: if (elementClassSuffices) {
1090: return collDef;
1091: }
1092: } else if (new CommaListIterator(
1093: collDef
1094: .getProperty(PropertyHelper.OJB_PROPERTY_FOREIGNKEY))
1095: .contains(name)) {
1096: // if the field is a foreignkey of this normal 1:n collection
1097: return collDef;
1098: }
1099: }
1100: }
1101: }
1102: return null;
1103: }
1104:
1105: /**
1106: * Checks whether the given field definition is used as the primary key of a class referenced by
1107: * a reference.
1108: *
1109: * @param modelDef The model
1110: * @param fieldDef The current field descriptor def
1111: * @return The reference that uses the field or <code>null</code> if the field is not used in this way
1112: */
1113: private ReferenceDescriptorDef usedByReference(ModelDef modelDef,
1114: FieldDescriptorDef fieldDef) {
1115: String ownerClassName = ((ClassDescriptorDef) fieldDef
1116: .getOwner()).getQualifiedName();
1117: ClassDescriptorDef classDef;
1118: ReferenceDescriptorDef refDef;
1119: String targetClassName;
1120:
1121: // only relevant for primarykey fields
1122: if (PropertyHelper.toBoolean(fieldDef
1123: .getProperty(PropertyHelper.OJB_PROPERTY_PRIMARYKEY),
1124: false)) {
1125: for (Iterator classIt = modelDef.getClasses(); classIt
1126: .hasNext();) {
1127: classDef = (ClassDescriptorDef) classIt.next();
1128: for (Iterator refIt = classDef.getReferences(); refIt
1129: .hasNext();) {
1130: refDef = (ReferenceDescriptorDef) refIt.next();
1131: targetClassName = refDef.getProperty(
1132: PropertyHelper.OJB_PROPERTY_CLASS_REF)
1133: .replace('$', '.');
1134: if (ownerClassName.equals(targetClassName)) {
1135: // the field is a primary key of the class referenced by this reference descriptor
1136: return refDef;
1137: }
1138: }
1139: }
1140: }
1141: return null;
1142: }
1143: }
|