Package com.google.appengine.datanucleus

Source Code of com.google.appengine.datanucleus.MetaDataValidator

/**********************************************************************
Copyright (c) 2009 Google Inc.

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.
**********************************************************************/
package com.google.appengine.datanucleus;

import com.google.appengine.api.datastore.Key;

import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.ColumnMetaData;
import org.datanucleus.metadata.IdentityStrategy;
import org.datanucleus.metadata.IdentityType;
import org.datanucleus.metadata.InvalidMetaDataException;
import org.datanucleus.metadata.MetaDataManager;
import org.datanucleus.metadata.OrderMetaData;
import org.datanucleus.metadata.RelationType;
import org.datanucleus.metadata.SequenceMetaData;
import org.datanucleus.util.Localiser;
import org.datanucleus.util.NucleusLogger;

import java.util.Map;
import java.util.Set;

/**
* AppEngine-specific rules validator for Meta Data.
*
* @author Max Ross <maxr@google.com>
*/
public class MetaDataValidator {
  protected static final Localiser GAE_LOCALISER = Localiser.getInstance(
        "com.google.appengine.datanucleus.Localisation", DatastoreManager.class.getClassLoader());

  private static final Set<String> ONE_OR_ZERO_EXTENSIONS =
      Utils.newHashSet(
          DatastoreManager.PK_ID,
          DatastoreManager.ENCODED_PK,
          DatastoreManager.PK_NAME,
          DatastoreManager.PARENT_PK);

  private static final Set<String> NOT_PRIMARY_KEY_EXTENSIONS =
      Utils.newHashSet(
          DatastoreManager.PK_ID,
          DatastoreManager.PK_NAME,
          DatastoreManager.PARENT_PK);

  private static final Set<String> REQUIRES_ENCODED_STRING_PK_EXTENSIONS =
      Utils.newHashSet(
          DatastoreManager.PK_ID,
          DatastoreManager.PK_NAME);

  /**
   * Defines the various actions we can take when we encounter ignorable meta-data.
   */
  enum IgnorableMetaDataBehavior {
    NONE, // Do nothing at all.
    WARN, // Log a warning.
    ERROR;// Throw an exception.

    private static IgnorableMetaDataBehavior valueOf(String val, IgnorableMetaDataBehavior returnIfNull) {
      if (val == null) {
        return returnIfNull;
      }
      return valueOf(val);
    }
  }

  /**
   * Config property that determines the action we take when we encounter ignorable meta-data.
   */
  private static final String IGNORABLE_META_DATA_BEHAVIOR_PROPERTY = "datanucleus.appengine.ignorableMetaDataBehavior";

  /**
   * This message is appended to every ignorable meta-data warning so users
   * know they can configure it.
   */
  static final String ADJUST_WARNING_MSG =
      String.format("You can modify this warning by setting the %s property in your config.  "
                    + "A value of %s will silence the warning.  "
                    + "A value of %s will turn the warning into an exception.",
                    IGNORABLE_META_DATA_BEHAVIOR_PROPERTY,
                    IgnorableMetaDataBehavior.NONE,
                    IgnorableMetaDataBehavior.ERROR);

  private static final String ALLOW_MULTIPLE_RELATIONS_OF_SAME_TYPE =
      "datanucleus.appengine.allowMultipleRelationsOfSameType";

  private final DatastoreManager storeMgr;
  private final MetaDataManager metaDataManager;
  private final ClassLoaderResolver clr;

  public MetaDataValidator(DatastoreManager storeMgr, MetaDataManager metaDataManager, ClassLoaderResolver clr) {
    this.storeMgr = storeMgr;
    this.metaDataManager = metaDataManager;
    this.clr = clr;
  }

  /**
   * validate the metadata for the provided class to add GAE/J restrictions.
   * @param acmd Metadata for the class to validate
   */
  public void validate(AbstractClassMetaData acmd) {
    if (acmd.isEmbeddedOnly()) {
      // Nothing to check
      return;
    }

    NucleusLogger.METADATA.info("Performing appengine-specific metadata validation for " + acmd.getFullClassName());

    // validate inheritance
    // TODO Put checks on supported inheritance here

    AbstractMemberMetaData pkMemberMetaData = null;
    Class<?> pkType = null;
    boolean noParentAllowed = false;

    if (acmd.getIdentityType() == IdentityType.DATASTORE) {
      pkType = Key.class;
      ColumnMetaData colmd = acmd.getIdentityMetaData().getColumnMetaData();
      if (colmd != null) {
        if ("varchar".equalsIgnoreCase(colmd.getJdbcType()) || "char".equalsIgnoreCase(colmd.getJdbcType())) {
          pkType = String.class;
        } else  if ("integer".equalsIgnoreCase(colmd.getJdbcType()) || "numeric".equalsIgnoreCase(colmd.getJdbcType())) {
          pkType = Long.class;
        }
      }
      if (pkType == Long.class) {
        noParentAllowed = true;
      }
    } else if (acmd.getIdentityType() == IdentityType.APPLICATION) {
      // Validate primary-key
      int[] pkPositions = acmd.getPKMemberPositions();
      if (pkPositions == null) {
        throw new InvalidMetaDataException(GAE_LOCALISER, "AppEngine.MetaData.NoPkFields", acmd.getFullClassName());
      }
      if (pkPositions.length != 1) {
        throw new InvalidMetaDataException(GAE_LOCALISER, "AppEngine.MetaData.CompositePKNotSupported", acmd.getFullClassName());
      }

      // TODO Support composite PKs
      int pkPos = pkPositions[0];
      pkMemberMetaData = acmd.getMetaDataForManagedMemberAtAbsolutePosition(pkPos);

      pkType = pkMemberMetaData.getType();
      if (pkType.equals(Long.class) || pkType.equals(long.class) ||
          pkType.equals(Integer.class) || pkType.equals(int.class)) {
        // Allow Long, long, Integer, int numeric PK types
        noParentAllowed = true;
      } else if (pkType.equals(String.class)) {
        if (!MetaDataUtils.isEncodedPKField(acmd, pkPos)) {
          noParentAllowed = true;
        } else {
          // encoded string pk
          if (hasIdentityStrategy(IdentityStrategy.SEQUENCE, pkMemberMetaData)) {
            throw new InvalidMetaDataException(GAE_LOCALISER, "AppEngine.MetaData.SequenceInvalidForEncodedStringPK",
                pkMemberMetaData.getFullFieldName());
          }
        }
      } else if (pkType.equals(Key.class)) {
        if (hasIdentityStrategy(IdentityStrategy.SEQUENCE, pkMemberMetaData)) {
          throw new InvalidMetaDataException(GAE_LOCALISER, "AppEngine.MetaData.SequenceInvalidForPKType",
              pkMemberMetaData.getFullFieldName(), Key.class.getName());
        }
      } else {
        throw new InvalidMetaDataException(GAE_LOCALISER, "AppEngine.MetaData.InvalidPKTypeForField",
            pkMemberMetaData.getFullFieldName(), pkType.getName());
      }
    }

    // Validate fields
    Set<String> foundOneOrZeroExtensions = Utils.newHashSet();
    Map<Class<?>, String> nonRepeatableRelationTypes = Utils.newHashMap();

    // the constraints that we check across all fields apply to the entire
    // persistent class hierarchy so we're going to validate every field
    // at every level of the hierarchy.  As an example, this lets us detect
    // multiple one-to-many relationships at different levels of the class hierarchy
    AbstractClassMetaData curCmd = acmd;
    do {
      for (AbstractMemberMetaData ammd : curCmd.getManagedMembers()) {
        validateField(acmd, pkMemberMetaData, noParentAllowed, pkType, foundOneOrZeroExtensions,
            nonRepeatableRelationTypes, ammd);
      }
      curCmd = curCmd.getSuperAbstractClassMetaData();
    } while (curCmd != null);

    // Look for uniqueness constraints.  Not supported but not necessarily an error
    if (acmd.getUniqueMetaData() != null && acmd.getUniqueMetaData().length > 0) {
      handleIgnorableMapping(acmd, null, "AppEngine.MetaData.UniqueConstraintsNotSupported",
      "The constraint definition will be ignored.");
    }

    NucleusLogger.METADATA.info("Finished performing appengine-specific metadata validation for " + acmd.getFullClassName());
  }

  private void validateField(AbstractClassMetaData acmd, AbstractMemberMetaData pkMemberMetaData, boolean noParentAllowed,
                             Class<?> pkClass, Set<String> foundOneOrZeroExtensions,
                             Map<Class<?>, String> nonRepeatableRelationTypes, AbstractMemberMetaData ammd) {

    // can only have one field with this extension
    for (String extension : ONE_OR_ZERO_EXTENSIONS) {
      if (ammd.hasExtension(extension)) {
        if (!foundOneOrZeroExtensions.add(extension)) {
          throw new InvalidMetaDataException(GAE_LOCALISER, "AppEngine.MetaData.MoreThanOneFieldWithExtension",
              acmd.getFullClassName(), extension);
        }
      }
    }

    if (ammd.hasExtension(DatastoreManager.ENCODED_PK)) {
      if (!ammd.isPrimaryKey() || !ammd.getType().equals(String.class)) {
        throw new InvalidMetaDataException(GAE_LOCALISER, "AppEngine.MetaData.ExtensionForStringPK",
            ammd.getFullFieldName(), DatastoreManager.ENCODED_PK);
      }
    }

    if (ammd.hasExtension(DatastoreManager.PK_NAME)) {
      if (!ammd.getType().equals(String.class)) {
        throw new InvalidMetaDataException(GAE_LOCALISER, "AppEngine.MetaData.ExtensionForStringField",
            ammd.getFullFieldName(), DatastoreManager.PK_NAME);
      }
    }

    if (ammd.hasExtension(DatastoreManager.PK_ID)) {
      if (!ammd.getType().equals(Long.class) && !ammd.getType().equals(long.class)) {
        throw new InvalidMetaDataException(GAE_LOCALISER, "AppEngine.MetaData.ExtensionForLongField",
            ammd.getFullFieldName(), DatastoreManager.PK_ID);
      }
    }

    if (ammd.hasExtension(DatastoreManager.PARENT_PK)) {
      if (noParentAllowed) {
        throw new InvalidMetaDataException(GAE_LOCALISER, "AppEngine.MetaData.PKAndParentPKInvalid",
            ammd.getFullFieldName(), pkClass.getName());
      }
      if (!ammd.getType().equals(String.class) && !ammd.getType().equals(Key.class)) {
        throw new InvalidMetaDataException(GAE_LOCALISER, "AppEngine.MetaData.ParentPKType",
            ammd.getFullFieldName());
      }
    }

    for (String extension : NOT_PRIMARY_KEY_EXTENSIONS) {
      if (ammd.hasExtension(extension) && ammd.isPrimaryKey()) {
        throw new InvalidMetaDataException(GAE_LOCALISER, "AppEngine.MetaData.FieldWithExtensionNotPK",
            ammd.getFullFieldName(), extension);
      }
    }

    // pk-name and pk-id only supported in conjunction with an encoded string
    if (pkMemberMetaData != null) {
      for (String extension : REQUIRES_ENCODED_STRING_PK_EXTENSIONS) {
        if (ammd.hasExtension(extension)) {
          if (!pkMemberMetaData.hasExtension(DatastoreManager.ENCODED_PK)) {
            // we've already verified that encoded-pk is on a a String pk field
            // so we don't need to check the type of the pk here.
            throw new InvalidMetaDataException(GAE_LOCALISER, "AppEngine.MetaData.FieldWithExtensionForEncodedString",
                ammd.getFullFieldName(), extension);
          }
        }
      }
    }

    if (ammd.hasCollection() && ammd.getCollection().isSerializedElement()) {
      throw new InvalidMetaDataException(GAE_LOCALISER, "AppEngine.MetaData.CollectionWithSerializedElementInvalid",
          ammd.getFullFieldName());
    }
    else if (ammd.hasArray() && ammd.getArray().isSerializedElement()) {
      throw new InvalidMetaDataException(GAE_LOCALISER, "AppEngine.MetaData.ArrayWithSerializedElementInvalid",
          ammd.getFullFieldName());
    }


    checkForIllegalChildField(ammd, noParentAllowed);

    if (ammd.getRelationType(clr) != RelationType.NONE) {
      // Look for "eager" relationships.  Not supported but not necessarily an error
      // since we can always fall back to "lazy."
      if (ammd.isDefaultFetchGroup() && !ammd.isEmbedded()) {
          warn(String.format(
              "Meta-data warning for %s.%s: %s  %s  %s",
              acmd.getFullClassName(), ammd.getName(), GAE_LOCALISER.msg("AppEngine.MetaData.JoinsNotSupported", ammd.getFullFieldName()), "The field will be fetched lazily on first access.", ADJUST_WARNING_MSG));
//        handleIgnorableMapping(acmd, ammd, "AppEngine.MetaData.JoinsNotSupported", "The field will be fetched lazily on first access.");
      }

      if (ammd.getRelationType(clr) == RelationType.MANY_TO_MANY_BI && MetaDataUtils.isOwnedRelation(ammd, storeMgr)) {
        // We only support many-to-many for unowned relations
        throw new InvalidMetaDataException(GAE_LOCALISER, "AppEngine.MetaData.ManyToManyRelationNotSupported",
            ammd.getFullFieldName());
      }

      RelationType relType = ammd.getRelationType(clr);
      if (ammd.getEmbeddedMetaData() == null &&
          (relType == RelationType.ONE_TO_ONE_UNI || relType == RelationType.ONE_TO_ONE_BI ||
           relType == RelationType.ONE_TO_MANY_UNI || relType == RelationType.ONE_TO_MANY_BI) &&
          !getBooleanConfigProperty(ALLOW_MULTIPLE_RELATIONS_OF_SAME_TYPE) &&
          !storeMgr.storageVersionAtLeast(StorageVersion.READ_OWNED_CHILD_KEYS_FROM_PARENTS)) {
        // Check on multiple relations of the same type for early storage versions
        Class<?> relationClass;
        if (ammd.getCollection() != null) {
          relationClass = clr.classForName(ammd.getCollection().getElementType());
        } else if (ammd.getArray() != null) {
          relationClass = clr.classForName(ammd.getArray().getElementType());
        } else {
          relationClass = clr.classForName(ammd.getTypeName());
        }

        // Add the actual type of the field to the list of types that can't
        // repeat.  If that type was already present, problem.
        for (Class<?> existingRelationClass : nonRepeatableRelationTypes.keySet()) {
          if (existingRelationClass.isAssignableFrom(relationClass) ||
              relationClass.isAssignableFrom(existingRelationClass)) {
            throw new InvalidMetaDataException(GAE_LOCALISER, "AppEngine.MetaData.ClassWithMultipleFieldsOfType",
                acmd.getFullClassName(), relationClass.getName(), ammd.getName(), nonRepeatableRelationTypes.get(existingRelationClass));
          }
        }
        nonRepeatableRelationTypes.put(relationClass, ammd.getName());
      }
    }

    if (ammd.getValueGeneratorName() != null) {
      SequenceMetaData sequenceMetaData = metaDataManager.getMetaDataForSequence(clr, ammd.getValueGeneratorName());
      if (sequenceMetaData != null && sequenceMetaData.getInitialValue() != 1) {
        handleIgnorableMapping(acmd, ammd, "AppEngine.MetaData.SequenceInitialSizeNotSupported",
            "The first value for this sequence will be 1.");
      }
    }
  }

  private void checkForIllegalChildField(AbstractMemberMetaData ammd, boolean noParentAllowed) {
    if (!MetaDataUtils.isOwnedRelation(ammd, storeMgr)) {
      // The check only applies to owned relations
      return;
    }

    // Figure out if this field is the owning side of a one to one or a one to
    // many.  If it is, look at the mapping of the child class and make sure their
    // pk isn't Long or unencoded String.
    RelationType relationType = ammd.getRelationType(clr);
    if (relationType == RelationType.NONE || ammd.isEmbedded()) {
      return;
    }
    AbstractClassMetaData childAcmd = null;
    if (relationType == RelationType.ONE_TO_MANY_BI || relationType == RelationType.ONE_TO_MANY_UNI) {
      if (ammd.getCollection() != null) {
        childAcmd = ammd.getCollection().getElementClassMetaData(clr, metaDataManager);
      } else if (ammd.getArray() != null) {
        childAcmd = ammd.getArray().getElementClassMetaData(clr, metaDataManager);
      } else {
        // don't know how to verify
        NucleusLogger.METADATA.warn("Unable to validate one-to-many relation " + ammd.getFullFieldName());
      }
      if (ammd.getOrderMetaData() != null) {
        verifyOneToManyOrderBy(ammd, childAcmd);
      }
    } else if (relationType == RelationType.ONE_TO_ONE_BI || relationType == RelationType.ONE_TO_ONE_UNI) {
      childAcmd = metaDataManager.getMetaDataForClass(ammd.getType(), clr);
    }
    if (childAcmd == null) {
      return;
    }

    // Get the type of the primary key of the child
    if (childAcmd.getIdentityType() == IdentityType.DATASTORE) {
      Class pkType = Long.class;
      ColumnMetaData colmd = childAcmd.getIdentityMetaData().getColumnMetaData();
      if (colmd != null &&
          ("varchar".equalsIgnoreCase(colmd.getJdbcType()) || "char".equalsIgnoreCase(colmd.getJdbcType()))) {
        pkType = String.class;
      }
      if (noParentAllowed && pkType.equals(Long.class)) {
        throw new InvalidMetaDataException(GAE_LOCALISER, "AppEngine.MetaData.ChildWithPKTypeInvalid",
            childAcmd.getFullClassName()+".[ID]", pkType.getName(), ammd.getFullFieldName());
      }
    } else {
      int[] pkPositions = childAcmd.getPKMemberPositions();
      if (pkPositions == null) {
        // don't know how to verify
        NucleusLogger.METADATA.warn("Unable to validate relation " + ammd.getFullFieldName());
        return;
      }
      int pkPos = pkPositions[0];
      AbstractMemberMetaData pkMemberMetaData = childAcmd.getMetaDataForManagedMemberAtAbsolutePosition(pkPos);
      Class<?> pkType = pkMemberMetaData.getType();
      if (noParentAllowed && (pkType.equals(Long.class) || pkType.equals(long.class) ||
          (pkType.equals(String.class) && !pkMemberMetaData.hasExtension(DatastoreManager.ENCODED_PK)))) {
        throw new InvalidMetaDataException(GAE_LOCALISER, "AppEngine.MetaData.ChildWithPKTypeInvalid",
            pkMemberMetaData.getFullFieldName(), pkType.getName(), ammd.getFullFieldName());
      }
    }
  }

  private void verifyOneToManyOrderBy(AbstractMemberMetaData ammd, AbstractClassMetaData childAcmd) {
    OrderMetaData omd = ammd.getOrderMetaData();
    OrderMetaData.FieldOrder[] fieldOrders = omd.getFieldOrders();
    if (fieldOrders == null) {
      return;
    }
    for (OrderMetaData.FieldOrder fieldOrder : omd.getFieldOrders()) {
      String propertyName = fieldOrder.getFieldName();
      AbstractMemberMetaData orderField = childAcmd.getMetaDataForMember(propertyName);
      if (orderField.hasExtension(DatastoreManager.PK_ID) ||
          orderField.hasExtension(DatastoreManager.PK_NAME)) {
        throw new InvalidMetaDataException(GAE_LOCALISER, "AppEngine.MetaData.OrderPartOfPK",
            ammd.getFullFieldName(), propertyName);
      }
    }
  }

  private static boolean hasIdentityStrategy(IdentityStrategy strat, AbstractMemberMetaData ammd) {
    return ammd.getValueStrategy() != null && ammd.getValueStrategy().equals(strat);
  }

  private boolean getBooleanConfigProperty(String configProperty) {
    return metaDataManager.getNucleusContext().getPersistenceConfiguration().getBooleanProperty(configProperty);
  }

  private IgnorableMetaDataBehavior getIgnorableMetaDataBehavior() {
    return IgnorableMetaDataBehavior.valueOf(
        metaDataManager.getNucleusContext().getStoreManager()
            .getStringProperty(IGNORABLE_META_DATA_BEHAVIOR_PROPERTY), IgnorableMetaDataBehavior.WARN);
  }

  void handleIgnorableMapping(AbstractClassMetaData acmd, AbstractMemberMetaData ammd, String localiserKey, String warningOnlyMsg) {
    switch (getIgnorableMetaDataBehavior()) {
      case WARN:
        if (ammd == null) {
          warn(String.format(
              "Meta-data warning for %s: %s  %s  %s",
              acmd.getFullClassName(), GAE_LOCALISER.msg(localiserKey), warningOnlyMsg, ADJUST_WARNING_MSG));         
        } else {
          warn(String.format(
              "Meta-data warning for %s.%s: %s  %s  %s",
              acmd.getFullClassName(), ammd.getName(), GAE_LOCALISER.msg(localiserKey, ammd.getFullFieldName()), warningOnlyMsg, ADJUST_WARNING_MSG));
        }
        break;
      case ERROR:
        if (ammd == null) {
          throw new InvalidMetaDataException(GAE_LOCALISER, localiserKey, acmd.getFullClassName());
        }
        throw new InvalidMetaDataException(GAE_LOCALISER, localiserKey, ammd.getFullFieldName());
      case NONE:
        // Do nothing
    }
  }

  // broken out for testing
  void warn(String msg) {
    NucleusLogger.METADATA.warn(msg);
  }
}
TOP

Related Classes of com.google.appengine.datanucleus.MetaDataValidator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.