/src/com/google/appengine/datanucleus/MetaDataValidator.java
http://datanucleus-appengine.googlecode.com/ · Java · 462 lines · 345 code · 50 blank · 67 comment · 147 complexity · caca2dd66cb67c220bf3e1c866bec669 MD5 · raw file
- /**********************************************************************
- 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);
- }
- }