PageRenderTime 87ms CodeModel.GetById 20ms app.highlight 56ms RepoModel.GetById 2ms app.codeStats 0ms

/src/com/google/appengine/datanucleus/StoreFieldManager.java

http://datanucleus-appengine.googlecode.com/
Java | 1102 lines | 822 code | 97 blank | 183 comment | 349 complexity | 423d5070d0aa07703e8d54f597b6b15e MD5 | raw file
   1/**********************************************************************
   2Copyright (c) 2011 Google Inc.
   3
   4Licensed under the Apache License, Version 2.0 (the "License");
   5you may not use this file except in compliance with the License.
   6You may obtain a copy of the License at
   7
   8http://www.apache.org/licenses/LICENSE-2.0
   9
  10Unless required by applicable law or agreed to in writing, software
  11distributed under the License is distributed on an "AS IS" BASIS,
  12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13See the License for the specific language governing permissions and
  14limitations under the License.
  15**********************************************************************/
  16package com.google.appengine.datanucleus;
  17
  18import java.lang.reflect.Array;
  19import java.util.Arrays;
  20import java.util.Collection;
  21import java.util.Iterator;
  22import java.util.List;
  23import java.util.Map;
  24
  25import org.datanucleus.ClassLoaderResolver;
  26import org.datanucleus.exceptions.NucleusDataStoreException;
  27import org.datanucleus.exceptions.NucleusFatalUserException;
  28import org.datanucleus.exceptions.NucleusUserException;
  29import org.datanucleus.identity.OID;
  30import org.datanucleus.metadata.AbstractClassMetaData;
  31import org.datanucleus.metadata.AbstractMemberMetaData;
  32import org.datanucleus.metadata.ArrayMetaData;
  33import org.datanucleus.metadata.CollectionMetaData;
  34import org.datanucleus.metadata.ColumnMetaData;
  35import org.datanucleus.metadata.DiscriminatorMetaData;
  36import org.datanucleus.metadata.EmbeddedMetaData;
  37import org.datanucleus.metadata.IdentityStrategy;
  38import org.datanucleus.metadata.IdentityType;
  39import org.datanucleus.metadata.NullValue;
  40import org.datanucleus.metadata.RelationType;
  41import org.datanucleus.ExecutionContext;
  42import org.datanucleus.state.ObjectProvider;
  43import org.datanucleus.store.exceptions.NotYetFlushedException;
  44import org.datanucleus.store.mapped.mapping.ArrayMapping;
  45import org.datanucleus.store.mapped.mapping.EmbeddedPCMapping;
  46import org.datanucleus.store.mapped.mapping.InterfaceMapping;
  47import org.datanucleus.store.mapped.mapping.JavaTypeMapping;
  48import org.datanucleus.store.mapped.mapping.MapMapping;
  49import org.datanucleus.store.mapped.mapping.MappingCallbacks;
  50import org.datanucleus.store.mapped.mapping.PersistableMapping;
  51import org.datanucleus.store.mapped.mapping.SerialisedPCMapping;
  52import org.datanucleus.store.mapped.mapping.SerialisedReferenceMapping;
  53import org.datanucleus.store.types.TypeManager;
  54import org.datanucleus.store.types.SCO;
  55import org.datanucleus.util.Localiser;
  56
  57import com.google.appengine.api.datastore.Entity;
  58import com.google.appengine.api.datastore.Key;
  59import com.google.appengine.api.datastore.KeyFactory;
  60import com.google.appengine.api.datastore.Text;
  61import com.google.appengine.datanucleus.mapping.DatastoreTable;
  62
  63/**
  64 * FieldManager to handle the putting of fields from a managed object into an Entity.
  65 * There are typically two steps to use of this field manager.
  66 * <ul>
  67 * <li>Call <pre>op.provideFields(fieldNumbers, fieldMgr);</pre></li>
  68 * <li>Call <pre>fieldMgr.storeRelations(keyRegistry);</pre>
  69 * </ul>
  70 * The first step will process the requested fields and set them in the Entity as required, and will also
  71 * register any relation fields for later processing. The second step will take the relation fields (if any)
  72 * from step 1, perform cascade-persist on them, and set child keys in the (parent) Entity where required.
  73 */
  74public class StoreFieldManager extends DatastoreFieldManager {
  75  protected static final Localiser GAE_LOCALISER = Localiser.getInstance(
  76      "com.google.appengine.datanucleus.Localisation", DatastoreManager.class.getClassLoader());
  77
  78  private static final String PARENT_ALREADY_SET =
  79    "Cannot set both the primary key and a parent pk field.  If you want the datastore to "
  80    + "generate an id for you, set the parent pk field to be the value of your parent key "
  81    + "and leave the primary key field blank.  If you wish to "
  82    + "provide a named key, leave the parent pk field blank and set the primary key to be a "
  83    + "Key object made up of both the parent key and the named child.";
  84
  85  public static final int IS_FK_VALUE = -2;
  86  private static final int[] IS_FK_VALUE_ARR = {IS_FK_VALUE};
  87
  88  protected final boolean insert;
  89
  90  protected boolean parentAlreadySet = false;
  91
  92  protected boolean keyAlreadySet = false;
  93
  94  /** Cache of relation information for processing later in "storeRelations". */
  95  private final List<RelationStoreInformation> relationStoreInfos = Utils.newArrayList();
  96
  97  /**
  98   * Constructor for a StoreFieldManager when inserting a new object.
  99   * The Entity will be constructed.
 100   * @param op ObjectProvider of the object being stored
 101   * @param kind Kind of entity
 102   */
 103  public StoreFieldManager(ObjectProvider op, String kind) {
 104    super(op, new Entity(kind), null);
 105    this.insert = true;
 106  }
 107
 108  /**
 109   * Constructor for a StoreFieldManager when updating an object.
 110   * The Entity will be passed in (to be updated).
 111   * @param op ObjectProvider of the object being stored
 112   * @param datastoreEntity The Entity to update with the field values
 113   * @param fieldNumbers The field numbers being updated in the Entity
 114   */
 115  public StoreFieldManager(ObjectProvider op, Entity datastoreEntity, int[] fieldNumbers) {
 116    super(op, datastoreEntity, fieldNumbers);
 117    this.insert = false;
 118  }
 119
 120  public void storeBooleanField(int fieldNumber, boolean value) {
 121    storeFieldInEntity(fieldNumber, value);
 122  }
 123
 124  public void storeByteField(int fieldNumber, byte value) {
 125    storeFieldInEntity(fieldNumber, value);
 126  }
 127
 128  public void storeCharField(int fieldNumber, char value) {
 129    storeFieldInEntity(fieldNumber, value);
 130  }
 131
 132  public void storeDoubleField(int fieldNumber, double value) {
 133    storeFieldInEntity(fieldNumber, value);
 134  }
 135
 136  public void storeFloatField(int fieldNumber, float value) {
 137    storeFieldInEntity(fieldNumber, value);
 138  }
 139
 140  public void storeIntField(int fieldNumber, int value) {
 141    storeFieldInEntity(fieldNumber, value);
 142  }
 143
 144  public void storeLongField(int fieldNumber, long value) {
 145    if (isPK(fieldNumber)) {
 146      storePrimaryKey(fieldNumber, value);
 147    } else {
 148      storeFieldInEntity(fieldNumber, value);
 149    }
 150  }
 151
 152  public void storeShortField(int fieldNumber, short value) {
 153    storeFieldInEntity(fieldNumber, value);
 154  }
 155
 156  public void storeStringField(int fieldNumber, String value) {
 157    if (isPK(fieldNumber)) {
 158      storeStringPKField(fieldNumber, value);
 159    } else if (MetaDataUtils.isParentPKField(getClassMetaData(), fieldNumber)) {
 160      storeParentStringField(value);
 161    } else if (MetaDataUtils.isPKNameField(getClassMetaData(), fieldNumber)) {
 162      storePKNameField(fieldNumber, value);
 163    } else {
 164      // could be a JPA "lob" field, in which case we want to store it as Text.
 165      // DataNucleus sets a cmd with a jdbc type of CLOB if this is the case.
 166      Object valueToStore = value;
 167      AbstractMemberMetaData mmd = getMetaData(fieldNumber);
 168      if (mmd.getColumnMetaData() != null &&
 169          mmd.getColumnMetaData().length == 1) {
 170        if ("CLOB".equals(mmd.getColumnMetaData()[0].getJdbcType())) {
 171          valueToStore = new Text(value);
 172        }/* else if (mmd.getColumnMetaData()[0].getLength() > 500) {
 173          // Can only store up to 500 characters in String, so use Text
 174          valueToStore = new Text(value);
 175        }*/
 176      }
 177
 178      storeFieldInEntity(fieldNumber, valueToStore);
 179    }
 180  }
 181
 182  public void storeObjectField(int fieldNumber, Object value) {
 183    if (isPK(fieldNumber)) {
 184      storePrimaryKey(fieldNumber, value);
 185    } else if (MetaDataUtils.isParentPKField(getClassMetaData(), fieldNumber)) {
 186      storeParentField(fieldNumber, value);
 187    } else if (MetaDataUtils.isPKIdField(getClassMetaData(), fieldNumber)) {
 188      storePKIdField(fieldNumber, value);
 189    } else {
 190      storeFieldInEntity(fieldNumber, value);
 191    }
 192  }
 193
 194  protected boolean isStorable(AbstractMemberMetaData mmd) {
 195    return ((insert && mmd.isInsertable()) || (!insert && mmd.isUpdateable()));
 196  }
 197
 198  /**
 199   * Method to store the provided value in the Entity for the specified field.
 200   * @param fieldNumber The absolute field number
 201   * @param value Value to store (or rather to manipulate into a suitable form for the datastore).
 202   */
 203  private void storeFieldInEntity(int fieldNumber, Object value) {
 204    AbstractMemberMetaData mmd = getMetaData(fieldNumber);
 205    if (!isStorable(mmd)) {
 206      return;
 207    }
 208
 209    ClassLoaderResolver clr = getClassLoaderResolver();
 210    RelationType relationType = mmd.getRelationType(clr);
 211
 212    if (RelationType.isRelationSingleValued(relationType) && mmd.getEmbeddedMetaData() != null) {
 213      // Embedded persistable object
 214      ObjectProvider embeddedOP = getEmbeddedObjectProvider(mmd.getType(), fieldNumber, value);
 215
 216      fieldManagerStateStack.addFirst(new FieldManagerState(embeddedOP, mmd.getEmbeddedMetaData()));
 217      embeddedOP.provideFields(embeddedOP.getClassMetaData().getAllMemberPositions(), this);
 218      fieldManagerStateStack.removeFirst();
 219
 220      return;
 221    } else if (RelationType.isRelationMultiValued(relationType) && mmd.isEmbedded()) {
 222      // Embedded container field
 223      if (mmd.hasCollection()) {
 224        // Embedded collections
 225        // This is stored flat with all property names for the element class gaining a suffix ".{index}"
 226        // so we have properties like "NAME.0", "PRICE.0", "NAME.1", "PRICE.1" etc.
 227        Class elementType = clr.classForName(mmd.getCollection().getElementType());
 228        Collection valueColl = (Collection) value;
 229        AbstractClassMetaData elemCmd = mmd.getCollection().getElementClassMetaData(clr, ec.getMetaDataManager());
 230        EmbeddedMetaData embmd = 
 231          mmd.getElementMetaData() != null ? mmd.getElementMetaData().getEmbeddedMetaData() : null;
 232
 233        // Add property for size of collection
 234        String collPropName = getPropertyNameForMember(mmd) + ".size";
 235        EntityUtils.setEntityProperty(datastoreEntity, mmd, collPropName,
 236            (valueColl != null ? valueColl.size() : -1));
 237
 238        // Add discriminator for elements if defined
 239        String collDiscName = null;
 240        if (elemCmd.hasDiscriminatorStrategy()) {
 241          collDiscName = elemCmd.getDiscriminatorColumnName();
 242          if (embmd != null && embmd.getDiscriminatorMetaData() != null) {
 243            // Override if specified under <embedded>
 244            DiscriminatorMetaData dismd = embmd.getDiscriminatorMetaData();
 245            ColumnMetaData discolmd = dismd.getColumnMetaData();
 246            if (discolmd != null && discolmd.getName() != null) {
 247              collDiscName = discolmd.getName();
 248            }
 249          }
 250          if (collDiscName == null) {
 251            collDiscName = getPropertyNameForMember(mmd) + ".discrim";
 252          }
 253        }
 254
 255        if (valueColl != null) {
 256          Iterator collIter = valueColl.iterator();
 257          int index = 0;
 258          while (collIter.hasNext()) {
 259            Object element = collIter.next();
 260
 261            ObjectProvider embeddedOP = getEmbeddedObjectProvider(elementType, fieldNumber, element);
 262            fieldManagerStateStack.addFirst(new FieldManagerState(embeddedOP, embmd, index));
 263            embeddedOP.provideFields(embeddedOP.getClassMetaData().getAllMemberPositions(), this);
 264
 265            // Add discriminator for elements if defined
 266            if (collDiscName != null) {
 267              EntityUtils.setEntityProperty(datastoreEntity, elemCmd.getDiscriminatorMetaDataRoot(), 
 268                  collDiscName + "." + index, embeddedOP.getClassMetaData().getDiscriminatorValue());
 269            }
 270
 271            fieldManagerStateStack.removeFirst();
 272            index++;
 273          }
 274        }
 275        return;
 276      } else if (mmd.hasArray()) {
 277        // Embedded arrays
 278        // This is stored flat with all property names for the element class gaining a suffix ".{index}"
 279        // so we have properties like "NAME.0", "PRICE.0", "NAME.1", "PRICE.1" etc.
 280        Class elementType = clr.classForName(mmd.getArray().getElementType());
 281        AbstractClassMetaData elemCmd = mmd.getArray().getElementClassMetaData(clr, ec.getMetaDataManager());
 282        EmbeddedMetaData embmd = 
 283          mmd.getElementMetaData() != null ? mmd.getElementMetaData().getEmbeddedMetaData() : null;
 284
 285        // Add property for size of array
 286        String arrBaseName = getPropertyNameForMember(mmd);
 287        EntityUtils.setEntityProperty(datastoreEntity, mmd, arrBaseName + ".size",
 288            (value != null ? Array.getLength(value) : -1));
 289
 290        // Add discriminator for elements if defined
 291        String arrDiscName = null;
 292        if (elemCmd.hasDiscriminatorStrategy()) {
 293          arrDiscName = elemCmd.getDiscriminatorColumnName();
 294          if (embmd != null && embmd.getDiscriminatorMetaData() != null) {
 295            // Override if specified under <embedded>
 296            DiscriminatorMetaData dismd = embmd.getDiscriminatorMetaData();
 297            ColumnMetaData discolmd = dismd.getColumnMetaData();
 298            if (discolmd != null && discolmd.getName() != null) {
 299              arrDiscName = discolmd.getName();
 300            }
 301          }
 302          if (arrDiscName == null) {
 303            arrDiscName = getPropertyNameForMember(mmd) + ".discrim";
 304          }
 305        }
 306
 307        if (value != null) {
 308          for (int i=0;i<Array.getLength(value);i++) {
 309            Object element = Array.get(value, i);
 310
 311            ObjectProvider embeddedOP = getEmbeddedObjectProvider(elementType, fieldNumber, element);
 312            fieldManagerStateStack.addFirst(new FieldManagerState(embeddedOP, embmd, i));
 313            embeddedOP.provideFields(embeddedOP.getClassMetaData().getAllMemberPositions(), this);
 314
 315            // Add discriminator for elements if defined
 316            if (arrDiscName != null) {
 317              EntityUtils.setEntityProperty(datastoreEntity, elemCmd.getDiscriminatorMetaDataRoot(),
 318                  arrDiscName + "." + i, embeddedOP.getClassMetaData().getDiscriminatorValue());
 319            }
 320
 321            fieldManagerStateStack.removeFirst();
 322          }
 323        }
 324        return;
 325      } else if (mmd.hasMap()) {
 326        // TODO Support embedded maps
 327        throw new NucleusUserException("Don't currently support embedded Maps (" + mmd.getFullFieldName() + ")");
 328      }
 329    }
 330
 331    if (mmd.isSerialized()) {
 332      if (value != null) {
 333        // Serialise the field (producing a Blob)
 334        value = getStoreManager().getSerializationManager().serialize(clr, mmd, value);
 335      } else {
 336        // Make sure we can have a null property for this field
 337        checkSettingToNullValue(mmd, value);
 338      }
 339      EntityUtils.setEntityProperty(datastoreEntity, mmd, getPropertyNameForMember(mmd), value);
 340      return;
 341    }
 342
 343    if (relationType == RelationType.NONE) {
 344      // Basic field
 345      if (value == null) {
 346        checkSettingToNullValue(mmd, value);
 347      } else {
 348        // Perform any conversions from the field type to the stored-type
 349        TypeManager typeMgr = ec.getNucleusContext().getTypeManager();
 350        value = ((DatastoreManager) ec.getStoreManager())
 351            .getTypeConversionUtils()
 352            .pojoValueToDatastoreValue(typeMgr, clr, value, mmd);
 353
 354        if (value instanceof SCO) {
 355          // Use the unwrapped value so the datastore doesn't fail on unknown types
 356          value = ((SCO)value).getValue();
 357        }
 358      }
 359
 360      EntityUtils.setEntityProperty(datastoreEntity, mmd, getPropertyNameForMember(mmd), value);
 361      return;
 362    }
 363
 364    // Register this relation field for later update
 365    relationStoreInfos.add(new RelationStoreInformation(mmd, value));
 366
 367    boolean owned = MetaDataUtils.isOwnedRelation(mmd, getStoreManager());
 368    if (owned) {
 369      if (!getStoreManager().storageVersionAtLeast(StorageVersion.WRITE_OWNED_CHILD_KEYS_TO_PARENTS)) {
 370        // don't write child keys to the parent if the storage version isn't high enough
 371        return;
 372      }
 373
 374      // Skip out for all situations where this isn't the owner (since our key has the parent key)
 375      if (relationType == RelationType.MANY_TO_ONE_BI) {
 376        // We don't store any "FK" of the parent
 377        return;
 378      } else if (relationType == RelationType.ONE_TO_ONE_BI && mmd.getMappedBy() != null) {
 379        // We don't store any "FK" of the other side
 380        return;
 381      }
 382    }
 383
 384    if (insert) {
 385      if (value == null) {
 386        // Nothing to extract
 387        checkSettingToNullValue(mmd, value);
 388      } else if (RelationType.isRelationSingleValued(relationType)) {
 389        Key key = EntityUtils.extractChildKey(value, ec, owned ? datastoreEntity : null);
 390        if (key != null && owned && !datastoreEntity.getKey().equals(key.getParent())) {
 391          // Detect attempt to add an object with its key set (and hence parent set) on owned field
 392          throw new NucleusFatalUserException(GAE_LOCALISER.msg("AppEngine.OwnedChildCannotChangeParent",
 393              key, datastoreEntity.getKey()));
 394        }
 395        value = key;
 396      } else if (RelationType.isRelationMultiValued(relationType)) {
 397        if (mmd.hasCollection()) {
 398          if (value != null) {
 399            value = getDatastoreObjectForCollection(mmd, (Collection)value, ec, owned, false);
 400          }
 401        } else if (mmd.hasArray()) {
 402          if (value != null) {
 403            value = getDatastoreObjectForArray(mmd, value, ec, owned, false);
 404          }
 405        } else if (mmd.hasMap()) {
 406          if (value != null) {
 407            value = getDatastoreObjectForMap(mmd, (Map)value, ec, clr, owned, false);
 408          }
 409        }
 410
 411        if (value instanceof SCO) {
 412          // Use the unwrapped value so the datastore doesn't fail on unknown types
 413          value = ((SCO)value).getValue();
 414        }
 415      }
 416
 417      EntityUtils.setEntityProperty(datastoreEntity, mmd, getPropertyNameForMember(mmd), value);
 418    }
 419  }
 420
 421  /**
 422   * Convenience method to process a related persistable object, persisting and flushing it as necessary
 423   * and returning the persistent object.
 424   * @param mmd Field where the object is stored.
 425   * @param pc The object to process
 426   * @return The persisted form of the object (or null if deleted)
 427   */
 428  Object processPersistable(AbstractMemberMetaData mmd, Object pc) {
 429    if (ec.getApiAdapter().isDeleted(pc)) {
 430      // Child is deleted, so return null
 431      return null;
 432    }
 433
 434    Object childPC = pc;
 435    if (ec.getApiAdapter().isDetached(pc)) {
 436      // Child is detached, so attach it
 437      childPC = ec.persistObjectInternal(pc, null, -1, ObjectProvider.PC);
 438    }
 439    ObjectProvider childOP = ec.findObjectProvider(childPC);
 440    if (childOP == null) {
 441      // Not yet persistent, so persist it
 442      childPC = ec.persistObjectInternal(childPC, null, -1, ObjectProvider.PC);
 443      childOP = ec.findObjectProvider(childPC);
 444    }
 445
 446    if (mmd.getAbstractClassMetaData().getIdentityType() == IdentityType.DATASTORE) {
 447      OID oid = (OID)childOP.getInternalObjectId();
 448      if (oid == null) {
 449        // Object has not yet flushed to the datastore
 450        childOP.flush();
 451      }
 452    } else {
 453      Object primaryKey = ec.getApiAdapter().getTargetKeyForSingleFieldIdentity(childOP.getInternalObjectId());
 454      if (primaryKey == null) {
 455        // Object has not yet flushed to the datastore
 456        childOP.flush();
 457      }
 458    }
 459    return childPC;
 460  }
 461
 462  void storeParentField(int fieldNumber, Object value) {
 463    AbstractMemberMetaData mmd = getMetaData(fieldNumber);
 464    if (mmd.getType().equals(Key.class)) {
 465      storeParentKeyPK((Key) value);
 466    } else {
 467      throw exceptionForUnexpectedKeyType("Parent primary key", fieldNumber);
 468    }
 469  }
 470
 471  private void storePrimaryKey(int fieldNumber, Object value) {
 472    AbstractMemberMetaData mmd = getMetaData(fieldNumber);
 473
 474    if (mmd.getType().equals(long.class)) {
 475      Key key = null;
 476      if (mmd.getValueStrategy() == IdentityStrategy.IDENTITY) {
 477        // Key being generated by datastore so put null key in
 478        key = null;
 479      } else {
 480        key = KeyFactory.createKey(datastoreEntity.getKind(), (Long) value);
 481      }
 482      storeKeyPK(key);
 483    } else if (mmd.getType().equals(Long.class)) {
 484      Key key = null;
 485      if (value != null) {
 486        // TODO This is actually against the JDO/JPA specs; having IDENTITY strategy means the DB will (always) choose
 487        key = KeyFactory.createKey(datastoreEntity.getKind(), (Long) value);
 488      }
 489      storeKeyPK(key);
 490    } else if (mmd.getType().equals(Key.class)) {
 491      Key key = (Key) value;
 492      if (key != null && key.getParent() != null && parentAlreadySet) {
 493        throw new NucleusFatalUserException(PARENT_ALREADY_SET);
 494      }
 495      storeKeyPK((Key) value);
 496    } else {
 497      throw exceptionForUnexpectedKeyType("Primary key", fieldNumber);
 498    }
 499  }
 500
 501  void storePKIdField(int fieldNumber, Object value) {
 502    AbstractMemberMetaData mmd = getMetaData(fieldNumber);
 503    if (!mmd.getType().equals(Long.class)) {
 504      throw new NucleusFatalUserException(
 505          "Field with \"" + DatastoreManager.PK_ID + "\" extension must be of type Long");
 506    }
 507    Key key = null;
 508    if (value != null) {
 509      key = KeyFactory.createKey(datastoreEntity.getKind(), (Long) value);
 510    }
 511    storeKeyPK(key);
 512  }
 513
 514  private void storePKNameField(int fieldNumber, String value) {
 515    // TODO(maxr) make sure the pk is an encoded string
 516    AbstractMemberMetaData mmd = getMetaData(fieldNumber);
 517    if (!mmd.getType().equals(String.class)) {
 518      throw new NucleusFatalUserException(
 519          "Field with \"" + DatastoreManager.PK_NAME + "\" extension must be of type String");
 520    }
 521    Key key = null;
 522    if (value != null) {
 523      key = KeyFactory.createKey(datastoreEntity.getParent(), datastoreEntity.getKind(), value);
 524    }
 525    storeKeyPK(key);
 526  }
 527
 528  private void storeParentStringField(String value) {
 529    Key key = null;
 530    if (value != null) {
 531      try {
 532        key = KeyFactory.stringToKey(value);
 533      } catch (IllegalArgumentException iae) {
 534        throw new NucleusFatalUserException("Attempt was made to set parent to " + value +
 535            " but this cannot be converted into a Key.");
 536      }
 537    }
 538    storeParentKeyPK(key);
 539  }
 540
 541  private void storeParentKeyPK(Key key) {
 542    if (key != null && parentAlreadySet) {
 543      throw new NucleusFatalUserException(PARENT_ALREADY_SET);
 544    }
 545    if (datastoreEntity.getParent() != null) {
 546      // update is ok if it's a no-op
 547      if (!datastoreEntity.getParent().equals(key)) {
 548        if (!parentAlreadySet) {
 549          throw new NucleusFatalUserException(
 550              "Attempt was made to modify the parent of an object of type "
 551              + getObjectProvider().getClassMetaData().getFullClassName() + " identified by "
 552              + "key " + datastoreEntity.getKey() + ".  Parents are immutable (changed value is " + key + ").");
 553        }
 554      }
 555    } else if (key != null) {
 556      if (!insert) {
 557        // Shouldn't even happen.
 558        throw new NucleusFatalUserException("You can only rely on this class to properly handle "
 559            + "parent pks if you instantiated the class without providing a datastore "
 560            + "entity to the constructor.");
 561      }
 562
 563      if (keyAlreadySet) {
 564        throw new NucleusFatalUserException(PARENT_ALREADY_SET);
 565      }
 566
 567      // If this field is labeled as a parent PK we need to recreate the Entity, passing
 568      // the value of this field as an arg to the Entity constructor and then moving all
 569      // properties on the old entity to the new entity.
 570      datastoreEntity = EntityUtils.recreateEntityWithParent(key, datastoreEntity);
 571      parentAlreadySet = true;
 572    } else {
 573      // Null parent.  Parent is defined on a per-instance basis so
 574      // annotating a field as a parent is not necessarily a commitment
 575      // to always having a parent.  Null parent is fine.
 576    }
 577  }
 578
 579  private void storeStringPKField(int fieldNumber, String value) {
 580    Key key = null;
 581    if (MetaDataUtils.isEncodedPKField(getClassMetaData(), fieldNumber)) {
 582      if (value != null) {
 583        try {
 584          key = KeyFactory.stringToKey(value);
 585        } catch (IllegalArgumentException iae) {
 586          throw new NucleusFatalUserException(
 587              "Invalid primary key for " + getClassMetaData().getFullClassName() + ".  The "
 588              + "primary key field is an encoded String but an unencoded value has been provided. "
 589              + "If you want to set an unencoded value on this field you can either change its "
 590              + "type to be an unencoded String (remove the \"" + DatastoreManager.ENCODED_PK
 591              + "\" extension), change its type to be a " + Key.class.getName() + " and then set "
 592              + "the Key's name field, or create a separate String field for the name component "
 593              + "of your primary key and add the \"" + DatastoreManager.PK_NAME
 594              + "\" extension.");
 595        }
 596      }
 597    } else {
 598      if (value == null) {
 599        throw new NucleusFatalUserException(
 600            "Invalid primary key for " + getClassMetaData().getFullClassName() + ".  Cannot have "
 601            + "a null primary key field if the field is unencoded and of type String.  "
 602            + "Please provide a value or, if you want the datastore to generate an id on your "
 603            + "behalf, change the type of the field to Long.");
 604      }
 605      if (value != null) {
 606        if (datastoreEntity.getParent() != null) {
 607          key = new Entity(datastoreEntity.getKey().getKind(), value, datastoreEntity.getParent()).getKey();
 608        } else {
 609          key = new Entity(datastoreEntity.getKey().getKind(), value).getKey();
 610        }
 611      }
 612    }
 613    storeKeyPK(key);
 614  }
 615
 616  private void storeKeyPK(Key key) {
 617    if (key != null && !datastoreEntity.getKind().equals(key.getKind())) {
 618      throw new NucleusFatalUserException(
 619          "Attempt was made to set the primary key of an entity with kind "
 620          + datastoreEntity.getKind() + " to a key with kind " + key.getKind());
 621    }
 622    if (datastoreEntity.getKey().isComplete()) {
 623      // this modification is only okay if it's actually a no-op
 624      if (!datastoreEntity.getKey().equals(key)) {
 625        if (!keyAlreadySet) {
 626          // Different key provided so the update isn't allowed.
 627          throw new NucleusFatalUserException(
 628              "Attempt was made to modify the primary key of an object of type "
 629              + getObjectProvider().getClassMetaData().getFullClassName() + " identified by "
 630              + "key " + datastoreEntity.getKey() + "  Primary keys are immutable.  "
 631              + "(New value: " + key);
 632        }
 633      }
 634    } else if (key != null) {
 635      Entity old = datastoreEntity;
 636      if (key.getParent() != null) {
 637        if (keyAlreadySet) {
 638          // can't provide a key and a parent - one or the other
 639          throw new NucleusFatalUserException(PARENT_ALREADY_SET);
 640        }
 641        parentAlreadySet = true;
 642      }
 643      datastoreEntity = new Entity(key);
 644      EntityUtils.copyProperties(old, datastoreEntity);
 645      keyAlreadySet = true;
 646    }
 647  }
 648
 649  private void checkSettingToNullValue(AbstractMemberMetaData mmd, Object value) {
 650    if (value == null) {
 651      if (mmd.getNullValue() == NullValue.EXCEPTION) {
 652        // JDO spec 18.15, throw XXXUserException when trying to store null and have handler set to EXCEPTION
 653        throw new NucleusUserException("Field/Property " + mmd.getFullFieldName() +
 654          " is null, but is mandatory as it's described in the jdo metadata");
 655      }
 656
 657      ColumnMetaData[] colmds = mmd.getColumnMetaData();
 658      if (colmds != null && colmds.length > 0) {
 659        if (colmds[0].getAllowsNull() == Boolean.FALSE) {
 660          // Column specifically marked as not-nullable
 661          throw new NucleusDataStoreException("Field/Property " + mmd.getFullFieldName() +
 662            " is null, but the column is specified as not-nullable");
 663        }
 664      }
 665    }
 666  }
 667
 668  /**
 669   * Method to process all relations that have been identified by earlier call(s) of op.provideField(...).
 670   * Registers the parent key against any owned child objects, performs cascade-persist, and then stores
 671   * child keys in the parent where the storageVersion requires it.
 672   * @param keyRegistry The key registry to set any parent information in
 673   * @return {@code true} if the entity has had properties updated during this method, {@code false} otherwise.
 674   */
 675  boolean storeRelations(KeyRegistry keyRegistry) {
 676    if (relationStoreInfos.isEmpty()) {
 677      // No relations waiting to be persisted
 678      return false;
 679    }
 680
 681    ObjectProvider op = getObjectProvider();
 682    DatastoreTable table = getDatastoreTable();
 683    if (datastoreEntity.getKey() != null) {
 684      Key key = datastoreEntity.getKey();
 685      AbstractClassMetaData acmd = op.getClassMetaData();
 686      int[] relationFieldNums = acmd.getRelationMemberPositions(ec.getClassLoaderResolver(), ec.getMetaDataManager());
 687      if (relationFieldNums != null) {
 688        for (int i=0;i<relationFieldNums.length;i++) {
 689          AbstractMemberMetaData mmd = acmd.getMetaDataForManagedMemberAtAbsolutePosition(relationFieldNums[i]);
 690          boolean owned = MetaDataUtils.isOwnedRelation(mmd, getStoreManager());
 691          if (owned) {
 692            // Register parent key for all owned related objects
 693            Object childValue = op.provideField(mmd.getAbsoluteFieldNumber());
 694            if (childValue != null) {
 695              if (childValue instanceof Object[]) {
 696                childValue = Arrays.asList((Object[]) childValue);
 697              }
 698
 699              String expectedType = mmd.getTypeName();
 700              if (mmd.getCollection() != null) {
 701                CollectionMetaData cmd = mmd.getCollection();
 702                expectedType = cmd.getElementType();
 703              } else if (mmd.getArray() != null) {
 704                ArrayMetaData amd = mmd.getArray();
 705                expectedType = amd.getElementType();
 706              }
 707
 708              if (childValue instanceof Iterable) {
 709                // TODO(maxr): Make sure we're not pulling back unnecessary data when we iterate over the values.
 710                for (Object element : (Iterable) childValue) {
 711                  addToParentKeyMap(keyRegistry, element, key, ec, expectedType, true);
 712                }
 713              } else if (childValue.getClass().isArray()) {
 714                for (int j=0;j<Array.getLength(childValue);i++) {
 715                  addToParentKeyMap(keyRegistry, Array.get(childValue, i), key, ec, expectedType, true);
 716                }
 717              } else if (childValue instanceof Map) {
 718                boolean persistableKey = (mmd.getMap().getKeyClassMetaData(ec.getClassLoaderResolver(), ec.getMetaDataManager()) != null);
 719                boolean persistableVal = (mmd.getMap().getValueClassMetaData(ec.getClassLoaderResolver(), ec.getMetaDataManager()) != null);
 720                Iterator entryIter = ((Map)childValue).entrySet().iterator();
 721                while (entryIter.hasNext()) {
 722                  Map.Entry entry = (Map.Entry)entryIter.next();
 723                  if (persistableKey) {
 724                    addToParentKeyMap(keyRegistry, entry.getKey(), key, ec, expectedType, true);
 725                  }
 726                  if (persistableVal) {
 727                    addToParentKeyMap(keyRegistry, entry.getValue(), key, ec, expectedType, true);
 728                  }
 729                }
 730              } else {
 731                addToParentKeyMap(keyRegistry, childValue, key, ec, expectedType, 
 732                    !table.isParentKeyProvider(mmd));
 733              }
 734            }
 735          } else {
 736            // Register that related object(s) is unowned
 737            Object childValue = op.provideField(mmd.getAbsoluteFieldNumber());
 738            if (childValue != null) {
 739              if (childValue instanceof Object[]) {
 740                childValue = Arrays.asList((Object[]) childValue);
 741              }
 742
 743              if (childValue instanceof Iterable) {
 744                // TODO(maxr): Make sure we're not pulling back unnecessary data when we iterate over the values.
 745                for (Object element : (Iterable) childValue) {
 746                  keyRegistry.registerUnownedObject(element);
 747                }
 748              } else {
 749                keyRegistry.registerUnownedObject(childValue);
 750              }
 751            }
 752          }
 753        }
 754      }
 755    }
 756
 757    boolean modifiedEntity = false;
 758
 759    // Stage 1 : process FKs
 760    for (RelationStoreInformation relInfo : relationStoreInfos) {
 761      AbstractMemberMetaData mmd = relInfo.mmd;
 762      try {
 763        JavaTypeMapping mapping = table.getMemberMappingInDatastoreClass(relInfo.mmd);
 764        if (mapping instanceof EmbeddedPCMapping ||
 765            mapping instanceof SerialisedPCMapping ||
 766            mapping instanceof SerialisedReferenceMapping ||
 767            mapping instanceof PersistableMapping ||
 768            mapping instanceof InterfaceMapping) {
 769          boolean owned = MetaDataUtils.isOwnedRelation(mmd, getStoreManager());
 770          RelationType relationType = mmd.getRelationType(ec.getClassLoaderResolver());
 771          if (owned) {
 772            // Owned relation
 773            boolean owner = false;
 774            if (relationType == RelationType.ONE_TO_ONE_UNI || relationType == RelationType.ONE_TO_MANY_UNI ||
 775                relationType == RelationType.ONE_TO_MANY_BI) {
 776              owner = true;
 777            } else if (relationType == RelationType.ONE_TO_ONE_BI && mmd.getMappedBy() == null) {
 778              owner = true;
 779            }
 780
 781            if (!table.isParentKeyProvider(mmd)) {
 782              // Make sure the parent key is set properly between parent and child objects
 783              if (!owner) {
 784                ObjectProvider parentOP = ec.findObjectProvider(relInfo.value);
 785                EntityUtils.checkParentage(op.getObject(), parentOP);
 786              } else {
 787                EntityUtils.checkParentage(relInfo.value, op);
 788              }
 789              mapping.setObject(ec, datastoreEntity, IS_FK_VALUE_ARR, relInfo.value, op, mmd.getAbsoluteFieldNumber());
 790            }
 791          } else {
 792            // Unowned relation
 793            mapping.setObject(ec, datastoreEntity, IS_FK_VALUE_ARR, relInfo.value, op, mmd.getAbsoluteFieldNumber());
 794          }
 795        }
 796      } catch (NotYetFlushedException e) {
 797        // Ignore this. We always have the object in the datastore, at least partially to get the key
 798      }
 799    }
 800
 801    // Stage 2 : postInsert/postUpdate
 802    for (RelationStoreInformation relInfo : relationStoreInfos) {
 803      try {
 804        JavaTypeMapping mapping = table.getMemberMappingInDatastoreClass(relInfo.mmd);
 805        if (mapping instanceof ArrayMapping || mapping instanceof MapMapping) {
 806          // Ignore postInsert/update for arrays/maps since don't support backing stores
 807        } else if (mapping instanceof MappingCallbacks) {
 808          if (insert) {
 809            ((MappingCallbacks)mapping).postInsert(op);
 810          } else {
 811            ((MappingCallbacks)mapping).postUpdate(op);
 812          }
 813        }
 814      } catch (NotYetFlushedException e) {
 815        // Ignore this. We always have the object in the datastore, at least partially to get the key
 816      }
 817    }
 818
 819    // Stage 3 : set child keys in parent
 820    for (RelationStoreInformation relInfo : relationStoreInfos) {
 821      AbstractMemberMetaData mmd = relInfo.mmd;
 822      RelationType relationType = mmd.getRelationType(ec.getClassLoaderResolver());
 823
 824      boolean owned = MetaDataUtils.isOwnedRelation(mmd, getStoreManager());
 825      if (owned) {
 826        // Owned relations only store child keys if storageVersion high enough, and at "owner" side.
 827        if (!getStoreManager().storageVersionAtLeast(StorageVersion.WRITE_OWNED_CHILD_KEYS_TO_PARENTS)) {
 828          // don't write child keys to the parent if the storage version isn't high enough
 829          continue;
 830        }
 831        if (relationType == RelationType.MANY_TO_ONE_BI) {
 832          // We don't store any "FK" of the parent at the child side (use parent key instead)
 833          continue;
 834        } else if (relationType == RelationType.ONE_TO_ONE_BI && mmd.getMappedBy() != null) {
 835          // We don't store any "FK" at the non-owner side (use parent key instead)
 836          continue;
 837        }
 838      }
 839
 840      Object value = relInfo.value;
 841      String propName = EntityUtils.getPropertyName(getStoreManager().getIdentifierFactory(), mmd);
 842      if (value == null) {
 843        // Nothing to extract
 844        checkSettingToNullValue(mmd, value);
 845        if (!datastoreEntity.hasProperty(propName) || datastoreEntity.getProperty(propName) != null) {
 846          modifiedEntity = true;
 847          EntityUtils.setEntityProperty(datastoreEntity, mmd, propName, value);
 848        }
 849      } else if (RelationType.isRelationSingleValued(relationType)) {
 850        if (ec.getApiAdapter().isDeleted(value)) {
 851          value = null;
 852        } else {
 853          Key key = EntityUtils.extractChildKey(value, ec, owned ? datastoreEntity : null);
 854          if (key == null) {
 855            Object childPC = processPersistable(mmd, value);
 856            if (childPC != value) {
 857              // Child object has been persisted/attached, so update it in the owner
 858              op.replaceField(mmd.getAbsoluteFieldNumber(), childPC);
 859            }
 860            key = EntityUtils.extractChildKey(childPC, ec, owned ? datastoreEntity : null);
 861          }
 862          if (owned) {
 863            // Check that we aren't assigning an owned child with different parent
 864            if (!datastoreEntity.getKey().equals(key.getParent())) {
 865              throw new NucleusFatalUserException(GAE_LOCALISER.msg("AppEngine.OwnedChildCannotChangeParent",
 866                  key, datastoreEntity.getKey()));
 867            }
 868          }
 869          value = key;
 870          if (!datastoreEntity.hasProperty(propName) || !value.equals(datastoreEntity.getProperty(propName))) {
 871            modifiedEntity = true;
 872            EntityUtils.setEntityProperty(datastoreEntity, mmd, propName, value);
 873          }
 874        }
 875      } else if (RelationType.isRelationMultiValued(relationType)) {
 876        if (mmd.hasCollection()) {
 877          value = getDatastoreObjectForCollection(mmd, (Collection)value, ec, owned, true);
 878          if (!datastoreEntity.hasProperty(propName) || !value.equals(datastoreEntity.getProperty(propName))) {
 879            modifiedEntity = true;
 880            EntityUtils.setEntityProperty(datastoreEntity, mmd, propName, value);
 881          }
 882        } else if (mmd.hasArray()) {
 883          value = getDatastoreObjectForArray(mmd, value, ec, owned, true);
 884          if (!datastoreEntity.hasProperty(propName) || !value.equals(datastoreEntity.getProperty(propName))) {
 885            modifiedEntity = true;
 886            EntityUtils.setEntityProperty(datastoreEntity, mmd, propName, value);
 887          }
 888        } else if (mmd.hasMap()) {
 889          value = getDatastoreObjectForMap(mmd, (Map)value, ec, ec.getClassLoaderResolver(), owned, true);
 890          if (!datastoreEntity.hasProperty(propName) || !value.equals(datastoreEntity.getProperty(propName))) {
 891            modifiedEntity = true;
 892            EntityUtils.setEntityProperty(datastoreEntity, mmd, propName, value);
 893          }
 894        }
 895      }
 896    }
 897
 898    relationStoreInfos.clear();
 899
 900    return modifiedEntity;
 901  }
 902
 903  // Nonsense about registering parent key
 904  private void addToParentKeyMap(KeyRegistry keyRegistry, Object childValue, Key key, ExecutionContext ec,
 905      String expectedType, boolean checkForPolymorphism) {
 906
 907    boolean throwException = getStoreManager().getBooleanProperty("datanucleus.appengine.throwExceptionOnUnexpectedPolymorphism", true);
 908    if (checkForPolymorphism && !throwException) {
 909      // Override the throw of an exception for something that is illogical (to me)
 910      checkForPolymorphism = false;
 911    }
 912
 913    // TODO Remove this check when we dump the old storageVersion. We're storing the Key of the other object
 914    // in the owner; there are no remote FKs with the latest storageVersion so why should this check be here?
 915    if (checkForPolymorphism && childValue != null && !childValue.getClass().getName().equals(expectedType)) {
 916      AbstractClassMetaData acmd = ec.getMetaDataManager().getMetaDataForClass(childValue.getClass(),
 917          ec.getClassLoaderResolver());
 918      if (!MetaDataUtils.isNewOrSuperclassTableInheritanceStrategy(acmd)) {
 919        throw new UnsupportedOperationException(
 920            "Received a child of type " + childValue.getClass().getName() + " for a field of type " +
 921            expectedType + ". Unfortunately polymorphism in relationships is only supported for the " +
 922            "superclass-table inheritance mapping strategy.");
 923      }
 924    }
 925
 926    keyRegistry.registerParentKeyForOwnedObject(childValue, key);
 927  }
 928
 929  private class RelationStoreInformation {
 930    AbstractMemberMetaData mmd;
 931    Object value;
 932
 933    public RelationStoreInformation(AbstractMemberMetaData mmd, Object val) {
 934      this.mmd = mmd;
 935      this.value = val;
 936    }
 937  }
 938
 939  /**
 940   * Method to make sure that the Entity has its parentKey assigned.
 941   * Will update the Entity of this StoreFieldManager as necessary for the parent key.
 942   * Returns the assigned parent PK (when we have a "gae.parent-pk" field/property in this class).
 943   * @return The parent key if the pojo class has a parent property. Note that a return value of {@code null} 
 944   *   does not mean that an entity group was not established, it just means the pojo doesn't have a distinct
 945   *   field for the parent.
 946   */
 947  Object establishEntityGroup() {
 948    Key parentKey = datastoreEntity.getParent();
 949    if (parentKey == null) {
 950      KeyRegistry keyReg = KeyRegistry.getKeyRegistry(ec);
 951      if (keyReg.isUnowned(getObjectProvider().getObject())) {
 952        return null;
 953      }
 954
 955      parentKey = EntityUtils.getParentKey(datastoreEntity, getObjectProvider());
 956      if (parentKey != null) {
 957        datastoreEntity = EntityUtils.recreateEntityWithParent(parentKey, datastoreEntity);
 958      }
 959    }
 960
 961    AbstractMemberMetaData parentPkMmd = ((DatastoreManager)getStoreManager()).getMetaDataForParentPK(getClassMetaData());
 962    if (parentKey != null && parentPkMmd != null) {
 963      return parentPkMmd.getType().equals(Key.class) ? parentKey : KeyFactory.keyToString(parentKey);
 964    }
 965    return null;
 966  }
 967
 968  /**
 969   * Convenience method to convert a collection field into a form suitable for the datastore.
 970   * Converts the collection into a List, and any persistable elements become just the Key for that object.
 971   * @param mmd Metadata for the map field
 972   * @param map The map value
 973   * @param ec Execution Context
 974   * @param owned Whether the field is owned
 975   * @param cascadePersist Whether to cascade persist any persistable keys/values in the collection that arent yet persistent
 976   * @return The datastore object
 977   */
 978  protected Object getDatastoreObjectForCollection(AbstractMemberMetaData mmd, Collection coll,
 979      ExecutionContext ec, boolean owned, boolean cascadePersist) {
 980    List<Key> keys = Utils.newArrayList();
 981    for (Object obj : coll) {
 982      if (!ec.getApiAdapter().isDeleted(obj)) {
 983        Key key = EntityUtils.extractChildKey(obj, ec, owned ? datastoreEntity : null);
 984        if (key != null) {
 985          keys.add(key);
 986        } else if (cascadePersist) {
 987          Object childPC = processPersistable(mmd, obj);
 988          key = EntityUtils.extractChildKey(childPC, ec, owned ? datastoreEntity : null);
 989          keys.add(key);
 990        } else {
 991          keys.add(null);
 992        }
 993
 994        if (owned) {
 995          // Check that we aren't assigning an owned child with different parent
 996          if (key != null && !datastoreEntity.getKey().equals(key.getParent())) {
 997            throw new NucleusFatalUserException(GAE_LOCALISER.msg("AppEngine.OwnedChildCannotChangeParent",
 998                key, datastoreEntity.getKey()));
 999          }
1000        }
1001      }
1002    }
1003    return keys;
1004  }
1005
1006  /**
1007   * Convenience method to convert an array field into a form suitable for the datastore.
1008   * Converts the array into a List, and any persistable elements become just the Key for that object.
1009   * @param mmd Metadata for the map field
1010   * @param map The map value
1011   * @param ec Execution Context
1012   * @param owned Whether the field is owned
1013   * @param cascadePersist Whether to cascade persist any persistable keys/values in the array that arent yet persistent
1014   * @return The datastore object
1015   */
1016  protected List getDatastoreObjectForArray(AbstractMemberMetaData mmd, Object arr,
1017      ExecutionContext ec, boolean owned, boolean cascadePersist) {
1018    List keys = Utils.newArrayList();
1019    for (int i=0;i<Array.getLength(arr);i++) {
1020      Object obj = Array.get(arr, i);
1021      if (!ec.getApiAdapter().isDeleted(obj)) {
1022        Key key = EntityUtils.extractChildKey(obj, ec, owned ? datastoreEntity : null);
1023        if (key != null) {
1024          keys.add(key);
1025        } else if (cascadePersist) {
1026          Object childPC = processPersistable(mmd, obj);
1027          key = EntityUtils.extractChildKey(childPC, ec, owned ? datastoreEntity : null);
1028          keys.add(key);
1029        } else {
1030          keys.add(null);
1031        }
1032
1033        if (owned) {
1034          // Check that we aren't assigning an owned child with different parent
1035          if (key != null && !datastoreEntity.getKey().equals(key.getParent())) {
1036            throw new NucleusFatalUserException(GAE_LOCALISER.msg("AppEngine.OwnedChildCannotChangeParent",
1037                key, datastoreEntity.getKey()));
1038          }
1039        }
1040      }
1041    }
1042    return keys;
1043  }
1044
1045  /**
1046   * Convenience method to convert a Map field into a form suitable for the datastore.
1047   * Converts the Map into a List, and any persistable keys/values become just the Key for that object.
1048   * @param mmd Metadata for the map field
1049   * @param map The map value
1050   * @param ec Execution Context
1051   * @param clr ClassLoader resolver
1052   * @param owned Whether the field is owned
1053   * @param cascadePersist Whether to cascade persist any persistable keys/values in the map that arent yet persistent
1054   * @return The datastore object
1055   */
1056  protected List getDatastoreObjectForMap(AbstractMemberMetaData mmd, Map map,
1057      ExecutionContext ec, ClassLoaderResolver clr, boolean owned, boolean cascadePersist) {
1058    if (map == null) {
1059      return null;
1060    }
1061
1062    boolean persistableKey = (mmd.getMap().getKeyClassMetaData(clr, ec.getMetaDataManager()) != null);
1063    boolean persistableVal = (mmd.getMap().getValueClassMetaData(clr, ec.getMetaDataManager()) != null);
1064    List keysValues = Utils.newArrayList();
1065    Iterator<Map.Entry> entryIter = map.entrySet().iterator();
1066    while (entryIter.hasNext()) {
1067      Map.Entry entry = entryIter.next();
1068      if (persistableKey) {
1069        Key key = EntityUtils.extractChildKey(entry.getKey(), ec, owned ? datastoreEntity : null);
1070        if (key != null) {
1071          keysValues.add(key);
1072        } else if (cascadePersist) {
1073          Object childPC = processPersistable(mmd, entry.getKey());
1074          key = EntityUtils.extractChildKey(childPC, ec, owned ? datastoreEntity : null);
1075          keysValues.add(key);
1076        } else {
1077          keysValues.add(null);
1078        }
1079      } else {
1080        // TODO Make use of TypeConversionUtils
1081        keysValues.add(entry.getKey());
1082      }
1083
1084      if (persistableVal) {
1085        Key key = EntityUtils.extractChildKey(entry.getValue(), ec, owned ? datastoreEntity : null);
1086        if (key != null) {
1087          keysValues.add(key);
1088        } else if (cascadePersist) {
1089          Object childPC = processPersistable(mmd, entry.getValue());
1090          key = EntityUtils.extractChildKey(childPC, ec, owned ? datastoreEntity : null);
1091          keysValues.add(key);
1092        } else {
1093          keysValues.add(null);
1094        }
1095      } else {
1096        // TODO Make use of TypeConversionUtils
1097        keysValues.add(entry.getValue());
1098      }
1099    }
1100    return keysValues;
1101  }
1102}