PageRenderTime 120ms CodeModel.GetById 77ms app.highlight 35ms RepoModel.GetById 1ms app.codeStats 1ms

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

http://datanucleus-appengine.googlecode.com/
Java | 736 lines | 464 code | 78 blank | 194 comment | 146 complexity | 32abb3ce49195783cdd66e7050508fe2 MD5 | raw file
  1/**********************************************************************
  2Copyright (c) 2009 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 com.google.appengine.api.datastore.Entity;
 19import com.google.appengine.api.datastore.EntityNotFoundException;
 20import com.google.appengine.api.datastore.Key;
 21import com.google.appengine.api.datastore.KeyFactory;
 22import com.google.appengine.datanucleus.mapping.DatastoreTable;
 23import com.google.appengine.datanucleus.mapping.DependentDeleteRequest;
 24import com.google.appengine.datanucleus.mapping.FetchMappingConsumer;
 25
 26import org.datanucleus.ClassLoaderResolver;
 27import org.datanucleus.PropertyNames;
 28import org.datanucleus.exceptions.NucleusObjectNotFoundException;
 29import org.datanucleus.exceptions.NucleusOptimisticException;
 30import org.datanucleus.metadata.AbstractClassMetaData;
 31import org.datanucleus.metadata.AbstractMemberMetaData;
 32import org.datanucleus.metadata.ColumnMetaData;
 33import org.datanucleus.metadata.DiscriminatorMetaData;
 34import org.datanucleus.metadata.IdentityType;
 35import org.datanucleus.metadata.VersionMetaData;
 36import org.datanucleus.metadata.VersionStrategy;
 37import org.datanucleus.store.AbstractPersistenceHandler;
 38import org.datanucleus.ExecutionContext;
 39import org.datanucleus.state.ObjectProvider;
 40import org.datanucleus.store.PersistenceBatchType;
 41import org.datanucleus.store.StoreManager;
 42import org.datanucleus.store.VersionHelper;
 43import org.datanucleus.store.mapped.DatastoreClass;
 44import org.datanucleus.store.mapped.DatastoreField;
 45import org.datanucleus.store.mapped.mapping.ArrayMapping;
 46import org.datanucleus.store.mapped.mapping.CollectionMapping;
 47import org.datanucleus.store.mapped.mapping.IndexMapping;
 48import org.datanucleus.store.mapped.mapping.JavaTypeMapping;
 49import org.datanucleus.store.mapped.mapping.MapMapping;
 50import org.datanucleus.store.mapped.mapping.MappingCallbacks;
 51import org.datanucleus.store.schema.naming.ColumnType;
 52import org.datanucleus.store.types.SCO;
 53import org.datanucleus.util.Localiser;
 54import org.datanucleus.util.NucleusLogger;
 55import org.datanucleus.util.StringUtils;
 56
 57import java.sql.Timestamp;
 58import java.util.Collection;
 59import java.util.Collections;
 60import java.util.Iterator;
 61import java.util.List;
 62import java.util.Map;
 63import java.util.Set;
 64import java.util.concurrent.ConcurrentHashMap;
 65
 66/**
 67 * Handler for persistence requests for GAE/J datastore. Lifecycle management processes persists, updates, deletes
 68 * and field access and hands them off here to interface with the datastore.
 69 * No method in here should be called from anywhere other than DataNucleus core.
 70 * <h3>Persistence Process</h3>
 71 * Receive calls to the following from DataNucleus core for persistence events. All persistence events arrive in 
 72 * the store plugin in the order they are performed by the user. Optimistic operations are queued until flush().
 73 * <p>
 74 * <b>PersistenceHandler.insertObject</b><br/>
 75 * <ol>
 76 * <li>CREATE Entity belonging to the appropriate entity group to represent the object.</li>
 77 * 
 78 * <li>If the entity is “owned” the entity group must be established before the Entity is initially put(),
 79 * there is no way to adjust it after. So when persisting an Entity that is a child of some other Entity, you need to
 80 * figure out who its parent is before you can do this first put.
 81 * <ol>
 82 * <li>Key 'id' can be assigned by datastore (long, Long)</li>
 83 * <li>Key 'name' can be assigned by the application (or value-generator) (String)</li>
 84 * </ol></li>
 85 * 
 86 * <li>Create StoreFieldManager, and set properties in Entity for all fields which have values ready.
 87 * <ol>
 88 * <li>If “identity” not set on this related object, make note of and skip relation fields.</li>
 89 * <li>If related object is detached, note its field number</li>
 90 * <li>If “identity” set on this related object, and related object not persistent, flush the related object(s) to 
 91 * get their Key(s).</li>
 92 * </ol></li>
 93 * 
 94 * <li>PUT the Entity in datastore</li>
 95 * <li>Set any generated id back on the Entity</li>
 96 * <li>If fields noted in step 3.1
 97 * <ol>
 98 * <li>Reuse StoreFieldManager from above, and process fields noted earlier.
 99 * <ol>
100 * <li>Attach any detached related objects</li>
101 * <li>Persist and flush new related object(s), and add property(s) for relation fields to the Entity</li>
102 * </ol></li>
103 * <li>PUT the updated Entity in datastore</li>
104 * </ol></li>
105 * 
106 * </ol>
107 * </p>
108 * 
109 * <p>
110 * <b>PersistenceHandler.updateObject</b><br/>
111 * <ol>
112 * <li>GET Entity that represents the object</li>
113 * <li>Populate all updated fields that have values ready, forcing the flush of any relation fields that don't have 
114 * their id present, and attach any detached related objects</li>
115 * <li>PUT the updated Entity in datastore</li>
116 * </ol>
117 * </p>
118 * <p>
119 * <b>PersistenceHandler.deleteObject</b><br/>
120 * <ol>
121 * <li>GET Entity that represents the object</li>
122 * <li>Handle any cascade deletion</li>
123 * <li>DELETE the Entity from datastore</li>
124 * </ol>
125 * </p>
126 * 
127 * @author Max Ross <maxr@google.com>
128 * @author Andy Jefferson
129 */
130public class DatastorePersistenceHandler extends AbstractPersistenceHandler {
131  protected static final Localiser GAE_LOCALISER = Localiser.getInstance(
132      "com.google.appengine.datanucleus.Localisation", DatastoreManager.class.getClassLoader());
133
134  private final Map<ExecutionContext, BatchPutManager> batchPutManagerByExecutionContext = new ConcurrentHashMap();
135
136  private final Map<ExecutionContext, BatchDeleteManager> batchDeleteManagerByExecutionContext = new ConcurrentHashMap();
137
138  private final DatastoreManager datastoreMgr;
139
140  /**
141   * Constructor.
142   * @param storeMgr The StoreManager to use.
143   */
144  public DatastorePersistenceHandler(StoreManager storeMgr) {
145    super(storeMgr);
146    this.datastoreMgr = (DatastoreManager) storeMgr;
147  }
148
149  public void close() {}
150
151  protected BatchPutManager getBatchPutManager(ExecutionContext ec) {
152    BatchPutManager putMgr = batchPutManagerByExecutionContext.get(ec);
153    if (putMgr == null) {
154      putMgr = new BatchPutManager();
155      batchPutManagerByExecutionContext.put(ec, putMgr);
156    }
157    return putMgr;
158  }
159
160  protected BatchDeleteManager getBatchDeleteManager(ExecutionContext ec) {
161    BatchDeleteManager deleteMgr = batchDeleteManagerByExecutionContext.get(ec);
162    if (deleteMgr == null) {
163      deleteMgr = new BatchDeleteManager(ec);
164      batchDeleteManagerByExecutionContext.put(ec, deleteMgr);
165    }
166    return deleteMgr;
167  }
168
169  /* (non-Javadoc)
170   * @see org.datanucleus.store.AbstractPersistenceHandler#batchStart(org.datanucleus.store.ExecutionContext, org.datanucleus.store.PersistenceBatchType)
171   */
172  @Override
173  public void batchStart(ExecutionContext ec, PersistenceBatchType batchType) {
174    if (batchType == PersistenceBatchType.PERSIST) {
175      getBatchPutManager(ec).start();
176    }
177    else if (batchType == PersistenceBatchType.DELETE) {
178      getBatchDeleteManager(ec).start();
179    }
180  }
181
182  /* (non-Javadoc)
183   * @see org.datanucleus.store.AbstractPersistenceHandler#batchEnd(org.datanucleus.store.ExecutionContext, org.datanucleus.store.PersistenceBatchType)
184   */
185  @Override
186  public void batchEnd(ExecutionContext ec, PersistenceBatchType batchType) {
187    if (batchType == PersistenceBatchType.PERSIST) {
188      getBatchPutManager(ec).finish(this);
189      batchPutManagerByExecutionContext.remove(ec);
190    }
191    else if (batchType == PersistenceBatchType.DELETE) {
192      getBatchDeleteManager(ec).finish(this);
193      batchDeleteManagerByExecutionContext.remove(ec);
194    }
195  }
196
197  /**
198   * Method to insert the specified managed object into the datastore.
199   * @param op ObjectProvider for the managed object
200   */
201  public void insertObject(ObjectProvider op) {
202    // Make sure writes are permitted
203    assertReadOnlyForUpdateOfObject(op);
204    datastoreMgr.validateMetaDataForClass(op.getClassMetaData());
205
206    // If we're in the middle of a batch operation just register the ObjectProvider that needs the insertion
207    BatchPutManager batchPutMgr = getBatchPutManager(op.getExecutionContext());
208    if (batchPutMgr.batchOperationInProgress()) {
209      batchPutMgr.add(op);
210      return;
211    }
212
213    insertObjectsInternal(Collections.singletonList(op));
214  }
215
216  /**
217   * Method to perform the work of inserting the specified objects. If multiple are to be inserted then
218   * performs it initially as a batch PUT. If some of these need subsequent work (e.g forcing the persist of 
219   * children followed by a repersist to link to the children) then this is done one-by-one.
220   */
221  void insertObjectsInternal(List<ObjectProvider> opsToInsert) {
222    if (opsToInsert == null || opsToInsert.isEmpty()) {
223      return;
224    }
225
226    // All must be in same ExecutionContext
227    ExecutionContext ec = opsToInsert.get(0).getExecutionContext();
228    List<PutState> putStateList = Utils.newArrayList();
229    for (ObjectProvider op : opsToInsert) {
230      AbstractClassMetaData cmd = op.getClassMetaData();
231
232      // Create the Entity, and populate all fields that can be populated (this will omit any owned child objects 
233      // if we don't have the key of this object yet).
234      StoreFieldManager fieldMgr =
235        new StoreFieldManager(op, EntityUtils.determineKind(cmd, ec));
236      op.provideFields(op.getClassMetaData().getAllMemberPositions(), fieldMgr);
237
238      // Make sure the Entity parent is set (if any)
239      Object assignedParentPk = fieldMgr.establishEntityGroup();
240      Entity entity = fieldMgr.getEntity();
241
242      if (!datastoreMgr.storageVersionAtLeast(StorageVersion.READ_OWNED_CHILD_KEYS_FROM_PARENTS)) {
243        // Older storage versions : store list positions in the element
244        DatastoreTable table = datastoreMgr.getDatastoreClass(op.getClassMetaData().getFullClassName(),
245            ec.getClassLoaderResolver());
246        Collection<JavaTypeMapping> orderMappings = table.getExternalOrderMappings().values();
247        for (JavaTypeMapping orderMapping : orderMappings) {
248          if (orderMapping instanceof IndexMapping) {
249            Object orderValue = op.getAssociatedValue(orderMapping);
250            if (orderValue != null) {
251              // Set order index on the entity
252              DatastoreField indexProp = orderMapping.getDatastoreMapping(0).getDatastoreField();
253              entity.setProperty(indexProp.getIdentifier().toString(), orderValue); // Is this indexed in the datastore?
254            } else {
255              // Element has been persisted and has the owner set, but not positioned, so leave til user does it
256            }
257          }
258        }
259      }
260
261      // Set version
262      handleVersioningBeforeWrite(op, entity, true, "inserting");
263
264      // Set discriminator
265      if (op.getClassMetaData().hasDiscriminatorStrategy()) {
266        DiscriminatorMetaData dismd = op.getClassMetaData().getDiscriminatorMetaDataRoot();
267        EntityUtils.setEntityProperty(entity, dismd, 
268            EntityUtils.getDiscriminatorPropertyName(datastoreMgr.getIdentifierFactory(), dismd), 
269            op.getClassMetaData().getDiscriminatorValue());
270      }
271
272      // Add Multi-tenancy discriminator if applicable
273      if (storeMgr.getStringProperty(PropertyNames.PROPERTY_TENANT_ID) != null) {
274          if ("true".equalsIgnoreCase(cmd.getValueForExtension("multitenancy-disable"))) {
275              // Don't bother with multitenancy for this class
276          }
277          else {
278            String name = storeMgr.getNamingFactory().getColumnName(cmd, ColumnType.MULTITENANCY_COLUMN);
279            EntityUtils.setEntityProperty(entity, cmd, name, storeMgr.getStringProperty(PropertyNames.PROPERTY_TENANT_ID));
280          }
281      }
282
283      // Update parent PK field on pojo
284      AbstractMemberMetaData parentPkMmd = datastoreMgr.getMetaDataForParentPK(cmd);
285      if (assignedParentPk != null) {
286        // we automatically assigned a parent to the entity so make sure that makes it back on to the pojo
287        op.replaceField(parentPkMmd.getAbsoluteFieldNumber(), assignedParentPk);
288      }
289
290      // Add the "state" for this put to the list.
291      putStateList.add(new PutState(op, fieldMgr, entity));
292    }
293
294    // PUT all entities in single call
295    if (!putStateList.isEmpty()) {
296      DatastoreTransaction txn = null;
297      AbstractClassMetaData acmd = null;
298      List<Entity> entityList = Utils.newArrayList();
299      for (PutState putState : putStateList) {
300        if (txn == null) {
301          txn = datastoreMgr.getDatastoreTransaction(ec);
302        }
303        if (acmd == null) {
304          acmd = putState.op.getClassMetaData();
305        }
306        entityList.add(putState.entity);
307      }
308
309      EntityUtils.putEntitiesIntoDatastore(ec, entityList);
310      for (PutState putState : putStateList) {
311        putState.op.setAssociatedValue(txn, putState.entity);
312      }
313    }
314
315    // Post-processing for all puts
316    for (PutState putState : putStateList) {
317      AbstractClassMetaData cmd = putState.op.getClassMetaData();
318
319      // Set the generated key back on the pojo.  If the pk field is a Key just set it on the field directly. 
320      // If the pk field is a String, convert the Key to a String, similarly for long.
321      // Assumes we only have a single pk member position
322      Object newId = null;
323      Class pkType = null;
324      boolean identityStrategyUsed = false;
325      if (cmd.pkIsDatastoreAttributed(storeMgr)) {
326        if (cmd.getIdentityType() == IdentityType.APPLICATION) {
327          // Assume only 1 PK field
328          identityStrategyUsed = true;
329          pkType = cmd.getMetaDataForManagedMemberAtAbsolutePosition(cmd.getPKMemberPositions()[0]).getType();
330        } else if (cmd.getIdentityType() == IdentityType.DATASTORE) {
331          identityStrategyUsed = true;
332          pkType = Key.class;
333          ColumnMetaData colmd = cmd.getIdentityMetaData().getColumnMetaData();
334          if (colmd != null) {
335            if ("varchar".equalsIgnoreCase(colmd.getJdbcType()) || "char".equalsIgnoreCase(colmd.getJdbcType())) {
336              pkType = String.class;
337            } else if ("integer".equalsIgnoreCase(colmd.getJdbcType()) || "numeric".equalsIgnoreCase(colmd.getJdbcType())) {
338              pkType = Long.class;
339            }
340          }
341        }
342      }
343
344      if (identityStrategyUsed) {
345        // Update the identity of the object with the datastore-assigned id
346        if (pkType.equals(Key.class)) {
347          newId = putState.entity.getKey();
348        } else if (pkType.equals(String.class)) {
349          if (MetaDataUtils.hasEncodedPKField(cmd)) {
350            newId = KeyFactory.keyToString(putState.entity.getKey());
351          } else {
352            newId = putState.entity.getKey().getName();
353          }
354        } else if (pkType.equals(Long.class) || pkType.equals(long.class)) {
355          newId = putState.entity.getKey().getId();
356        }
357
358        putState.op.setPostStoreNewObjectId(newId);
359      }
360
361      // Update relation fields (including cascade-persist etc)
362      if (putState.fieldMgr.storeRelations(KeyRegistry.getKeyRegistry(ec))) {
363        // PUT Entity into datastore with these changes
364        EntityUtils.putEntityIntoDatastore(ec, putState.entity);
365      }
366
367      putState.op.replaceAllLoadedSCOFieldsWithWrappers();
368
369      if (ec.getStatistics() != null) {
370        ec.getStatistics().incrementInsertCount();
371      }
372    }
373  }
374
375  /**
376   * Method to update the specified fields of the managed object in the datastore.
377   * @param op ObjectProvider of the managed object
378   * @param fieldNumbers Fields to be updated in the datastore
379   */
380  public void updateObject(ObjectProvider op, int fieldNumbers[]) {
381    if (op.getLifecycleState().isDeleted()) {
382      // don't perform updates on objects that are already deleted - this will cause them to be recreated
383      // This happens with JPAOneToOneTest/JDOOneToOneTest when deleting, called from DependentDeleteRequest
384      return;
385    }
386
387    // Make sure writes are permitted
388    assertReadOnlyForUpdateOfObject(op);
389    datastoreMgr.validateMetaDataForClass(op.getClassMetaData());
390
391    AbstractClassMetaData cmd = op.getClassMetaData();
392    long startTime = System.currentTimeMillis();
393    if (NucleusLogger.DATASTORE_PERSIST.isDebugEnabled()) {
394      StringBuffer fieldStr = new StringBuffer();
395      for (int i=0;i<fieldNumbers.length;i++) {
396        if (i > 0) {
397          fieldStr.append(",");
398        }
399        fieldStr.append(cmd.getMetaDataForManagedMemberAtAbsolutePosition(fieldNumbers[i]).getName());
400      }
401      NucleusLogger.DATASTORE_PERSIST.debug(GAE_LOCALISER.msg("AppEngine.Update.Start", 
402          StringUtils.toJVMIDString(op.getObject()), op.getInternalObjectId(), fieldStr.toString()));
403    }
404
405    ExecutionContext ec = op.getExecutionContext();
406    Entity entity = (Entity) op.getAssociatedValue(datastoreMgr.getDatastoreTransaction(ec));
407    if (entity == null) {
408      // Corresponding entity hasn't been fetched yet, so get it.
409      Key key = EntityUtils.getPkAsKey(op);
410      entity = EntityUtils.getEntityFromDatastore(datastoreMgr.getDatastoreServiceForReads(ec), op, key);
411    }
412
413    // Update the Entity with the specified fields
414    StoreFieldManager fieldMgr = new StoreFieldManager(op, entity, fieldNumbers);
415    op.provideFields(fieldNumbers, fieldMgr);
416
417    // Check and update the version
418    handleVersioningBeforeWrite(op, entity, true, "updating");
419
420    // Update relation fields (including cascade-persist etc)
421    fieldMgr.storeRelations(KeyRegistry.getKeyRegistry(op.getExecutionContext()));
422
423    // PUT Entity into datastore
424    DatastoreTransaction txn = EntityUtils.putEntityIntoDatastore(ec, entity);
425    op.setAssociatedValue(txn, entity);
426
427    op.replaceAllLoadedSCOFieldsWithWrappers();
428
429    if (NucleusLogger.DATASTORE_PERSIST.isDebugEnabled()) {
430      NucleusLogger.DATASTORE_PERSIST.debug(GAE_LOCALISER.msg("AppEngine.ExecutionTime", 
431        (System.currentTimeMillis() - startTime)));
432    }
433    if (ec.getStatistics() != null) {
434      ec.getStatistics().incrementUpdateCount();
435    }
436  }
437
438  /**
439   * Method to delete the specified managed object from the datastore.
440   * @param op ObjectProvider of the managed object
441   */
442  public void deleteObject(ObjectProvider op) {
443    // Make sure writes are permitted
444    assertReadOnlyForUpdateOfObject(op);
445    datastoreMgr.validateMetaDataForClass(op.getClassMetaData());
446
447    long startTime = System.currentTimeMillis();
448    if (NucleusLogger.DATASTORE_PERSIST.isDebugEnabled()) {
449      NucleusLogger.DATASTORE_PERSIST.debug(GAE_LOCALISER.msg("AppEngine.Delete.Start", 
450          StringUtils.toJVMIDString(op.getObject()), op.getInternalObjectId()));
451    }
452
453    ExecutionContext ec = op.getExecutionContext();
454    Entity entity = (Entity) op.getAssociatedValue(datastoreMgr.getDatastoreTransaction(ec));
455    if (entity == null) {
456      // Corresponding entity hasn't been fetched yet, so get it.
457      Key key = EntityUtils.getPkAsKey(op);
458      entity = EntityUtils.getEntityFromDatastore(datastoreMgr.getDatastoreServiceForReads(ec), op, key);
459    }
460
461    DatastoreTransaction txn = datastoreMgr.getDatastoreTransaction(ec);
462    if (txn != null) {
463      txn.addDeletedKey(entity.getKey());
464    }
465
466    // Check the version is valid to delete; any updates since read?
467    handleVersioningBeforeWrite(op, entity, false, "deleting");
468
469    // first handle any dependent deletes that need deleting before we delete this object
470    ClassLoaderResolver clr = ec.getClassLoaderResolver();
471    DatastoreClass dc = datastoreMgr.getDatastoreClass(op.getObject().getClass().getName(), clr);
472    DependentDeleteRequest req = new DependentDeleteRequest(dc, op.getClassMetaData(), clr);
473    Set relatedObjectsToDelete = req.execute(op, entity);
474
475    Key keyToDelete = EntityUtils.getPkAsKey(op);
476
477    // If we're in the middle of a batch operation just register the key that needs the delete
478    BatchDeleteManager bdm = getBatchDeleteManager(ec);
479    if (bdm.batchOperationInProgress()) {
480      bdm.add(new BatchDeleteManager.BatchDeleteState(txn, keyToDelete));
481
482      if (relatedObjectsToDelete != null && !relatedObjectsToDelete.isEmpty()) {
483        // Delete any related objects that need deleting after the delete of this object
484        Iterator iter = relatedObjectsToDelete.iterator();
485        while (iter.hasNext()) {
486          Object relatedObject = iter.next();
487          ec.deleteObjectInternal(relatedObject);
488        }
489      }
490      if (ec.getStatistics() != null) {
491        ec.getStatistics().incrementDeleteCount();
492      }
493
494      return;
495    }
496
497    // Delete this object
498    EntityUtils.deleteEntitiesFromDatastore(ec, Collections.singletonList(keyToDelete));
499
500    if (relatedObjectsToDelete != null && !relatedObjectsToDelete.isEmpty()) {
501      // Delete any related objects that need deleting after the delete of this object
502      Iterator iter = relatedObjectsToDelete.iterator();
503      while (iter.hasNext()) {
504        Object relatedObject = iter.next();
505        ec.deleteObjectInternal(relatedObject);
506      }
507    }
508    if (ec.getStatistics() != null) {
509      ec.getStatistics().incrementDeleteCount();
510    }
511
512    if (NucleusLogger.DATASTORE_PERSIST.isDebugEnabled()) {
513      NucleusLogger.DATASTORE_PERSIST.debug(GAE_LOCALISER.msg("AppEngine.ExecutionTime", 
514        (System.currentTimeMillis() - startTime)));
515    }
516  }
517
518  /**
519   * Method to fetch the specified fields of the managed object from the datastore.
520   * @param op ObjectProvider of the object whose fields need fetching
521   * @param fieldNumbers Fields to fetch
522   */
523  public void fetchObject(ObjectProvider op, int fieldNumbers[]) {
524    if (fieldNumbers == null || fieldNumbers.length == 0) {
525      return;
526    }
527
528    AbstractClassMetaData cmd = op.getClassMetaData();
529    datastoreMgr.validateMetaDataForClass(cmd);
530
531    // We always fetch the entire object, so if the state manager
532    // already has an associated Entity we know that associated
533    // Entity has all the fields.
534    ExecutionContext ec = op.getExecutionContext();
535    Entity entity = (Entity) op.getAssociatedValue(datastoreMgr.getDatastoreTransaction(ec));
536    if (entity == null) {
537      Key pk = EntityUtils.getPkAsKey(op);
538      entity = EntityUtils.getEntityFromDatastore(datastoreMgr.getDatastoreServiceForReads(ec), op, pk); // Throws NucleusObjectNotFoundException if necessary
539    }
540
541    if (NucleusLogger.DATASTORE_RETRIEVE.isDebugEnabled()) {
542      // Debug information about what we are retrieving
543      StringBuffer str = new StringBuffer("Fetching object \"");
544      str.append(StringUtils.toJVMIDString(op.getObject())).append("\" (id=");
545      str.append(op.getInternalObjectId()).append(")").append(" fields [");
546      for (int i=0;i<fieldNumbers.length;i++) {
547        if (i > 0) {
548          str.append(",");
549        }
550        str.append(cmd.getMetaDataForManagedMemberAtAbsolutePosition(fieldNumbers[i]).getName());
551      }
552      str.append("]");
553      NucleusLogger.DATASTORE_RETRIEVE.debug(str);
554    }
555
556    long startTime = System.currentTimeMillis();
557    if (NucleusLogger.DATASTORE_RETRIEVE.isDebugEnabled()) {
558      NucleusLogger.DATASTORE_RETRIEVE.debug(GAE_LOCALISER.msg("AppEngine.Fetch.Start", 
559          StringUtils.toJVMIDString(op.getObject()), op.getInternalObjectId()));
560    }
561
562    op.replaceFields(fieldNumbers, new FetchFieldManager(op, entity, fieldNumbers));
563
564    // Refresh version in case not yet set (e.g created HOLLOW object, and this is first fetch)
565    VersionMetaData vmd = cmd.getVersionMetaDataForClass();
566    if (cmd.isVersioned()) {
567      Object versionValue = entity.getProperty(EntityUtils.getVersionPropertyName(datastoreMgr.getIdentifierFactory(), vmd));
568      if (vmd.getVersionStrategy() == VersionStrategy.DATE_TIME) {
569        versionValue = new Timestamp((Long)versionValue);
570      }
571      op.setVersion(versionValue);
572    }
573
574    // Run post-fetch mapping callbacks. What is this actually achieving?
575    AbstractMemberMetaData[] fmds = new AbstractMemberMetaData[fieldNumbers.length];
576    for (int i = 0; i < fmds.length; i++) {
577      fmds[i] = op.getClassMetaData().getMetaDataForManagedMemberAtAbsolutePosition(fieldNumbers[i]);
578    }
579    ClassLoaderResolver clr = ec.getClassLoaderResolver();
580    DatastoreClass dc = datastoreMgr.getDatastoreClass(op.getObject().getClass().getName(), clr);
581    FetchMappingConsumer consumer = new FetchMappingConsumer(op.getClassMetaData());
582    dc.provideMappingsForMembers(consumer, fmds, true);
583    dc.provideDatastoreIdMappings(consumer);
584    dc.providePrimaryKeyMappings(consumer);
585    for (MappingCallbacks callback : consumer.getMappingCallbacks()) {
586      // Arrays and Maps don't use backing stores
587      if (callback instanceof ArrayMapping || callback instanceof MapMapping) {
588        // Do nothing since arrays and maps are stored in the parent property and loaded above using FetchFieldManager
589      } else if (callback instanceof CollectionMapping) {
590        CollectionMapping m = (CollectionMapping)callback;
591        Object val = op.provideField(m.getMemberMetaData().getAbsoluteFieldNumber());
592        if (val == null || !(val instanceof SCO)) {
593          // Not yet wrapped, so make sure we wrap it
594          callback.postFetch(op);
595        }
596      } else {
597        callback.postFetch(op);
598      }
599    }
600
601    if (NucleusLogger.DATASTORE_RETRIEVE.isDebugEnabled()) {
602      NucleusLogger.DATASTORE_RETRIEVE.debug(GAE_LOCALISER.msg("AppEngine.ExecutionTime",
603            (System.currentTimeMillis() - startTime)));
604    }
605    if (ec.getStatistics() != null) {
606      ec.getStatistics().incrementFetchCount();
607    }
608  }
609
610  /**
611   * Method to locate the specified managed objects in the datastore.
612   * @param ops ObjectProviders for the managed objects
613   * @throws NucleusObjectNotFoundException if any of the objects aren't found in the datastore
614   */
615  public void locateObjects(ObjectProvider[] ops) {
616    if (ops == null) {
617      return;
618    }
619
620    List<Key> keysToLocate = Utils.newArrayList();
621    for (int i=0;i<ops.length;i++) {
622      Key key = EntityUtils.getPkAsKey(ops[i]);
623      keysToLocate.add(key);
624    }
625    EntityUtils.getEntitiesFromDatastore(datastoreMgr.getDatastoreServiceForReads(ops[0].getExecutionContext()),
626        keysToLocate, ops[0].getExecutionContext());
627  }
628
629  /**
630   * Method to locate the specified managed object in the datastore.
631   * @param op ObjectProvider for the managed object
632   * @throws NucleusObjectNotFoundException if the object isn't found in the datastore
633   */
634  public void locateObject(ObjectProvider op) {
635    datastoreMgr.validateMetaDataForClass(op.getClassMetaData());
636    EntityUtils.getEntityFromDatastore(datastoreMgr.getDatastoreServiceForReads(op.getExecutionContext()), op, 
637        EntityUtils.getPkAsKey(op));
638  }
639
640  /**
641   * Implementation of this operation is optional and is intended for
642   * datastores that instantiate the model objects themselves (as opposed
643   * to letting datanucleus do it).  The App Engine datastore lets
644   * datanucleus instantiate the model objects so we just return null.
645   */
646  public Object findObject(ExecutionContext ec, Object id) {
647    return null;
648  }
649
650  /**
651   * Method to check optimistic versioning, and to set the version on the entity when required.
652   * @param op ObjectProvider for the object
653   * @param entity The entity being updated
654   * @param versionBehavior Behaviour required for versioning here
655   * @param operation Convenience string for messages
656   */
657  private void handleVersioningBeforeWrite(ObjectProvider op, Entity entity, boolean increment, String operation) {
658    AbstractClassMetaData cmd = op.getClassMetaData();
659    VersionMetaData vmd = cmd.getVersionMetaDataForClass();
660    if (cmd.isVersioned()) {
661      ExecutionContext ec = op.getExecutionContext();
662      String versionPropertyName = EntityUtils.getVersionPropertyName(datastoreMgr.getIdentifierFactory(), vmd);
663      Object curVersion = op.getVersion();
664      if (curVersion != null) {
665        // Fetch the latest and greatest version of the entity from the datastore
666        // to see if anyone has made a change underneath us.  We need to execute
667        // the fetch outside a txn to guarantee that we see the latest version.
668        if (NucleusLogger.DATASTORE_NATIVE.isDebugEnabled()) {
669          NucleusLogger.DATASTORE_NATIVE.debug("Getting entity with key " + entity.getKey());
670        }
671        Entity refreshedEntity;
672        try {
673          if (ec.getStatistics() != null) {
674            ec.getStatistics().incrementNumReads();
675          }
676          refreshedEntity = datastoreMgr.getDatastoreServiceForReads(op.getExecutionContext()).get(entity.getKey());
677        } catch (EntityNotFoundException e) {
678          // someone deleted out from under us
679          throw new NucleusOptimisticException(GAE_LOCALISER.msg("AppEngine.OptimisticError.EntityHasBeenDeleted", operation,
680              cmd.getFullClassName(), entity.getKey()));
681        }
682
683        Object datastoreVersion = refreshedEntity.getProperty(versionPropertyName);
684        if (vmd.getVersionStrategy() == VersionStrategy.DATE_TIME) {
685          datastoreVersion = new Timestamp((Long) datastoreVersion);
686        }
687
688        if (!datastoreVersion.equals(curVersion)) {
689          throw new NucleusOptimisticException(GAE_LOCALISER.msg("AppEngine.OptimisticError.EntityHasBeenUpdated", operation,
690              cmd.getFullClassName(), entity.getKey()));
691        }
692      }
693
694      Object nextVersion = VersionHelper.getNextVersion(vmd.getVersionStrategy(), curVersion);
695      op.setTransactionalVersion(nextVersion);
696      if (vmd.getVersionStrategy() == VersionStrategy.DATE_TIME) {
697        EntityUtils.setEntityProperty(entity, vmd, versionPropertyName, ((Timestamp)nextVersion).getTime());
698      } else {
699        EntityUtils.setEntityProperty(entity, vmd, versionPropertyName, nextVersion);
700      }
701
702      // Version field - update the version on the object
703      if (increment && vmd.getFieldName() != null) {
704        AbstractMemberMetaData verfmd =
705            ((AbstractClassMetaData)vmd.getParent()).getMetaDataForMember(vmd.getFieldName());
706        if (nextVersion instanceof Number) {
707          // Version can be long, Long, int, Integer, short, Short (or Timestamp).
708          Number nextNumber = (Number) nextVersion;
709          if (verfmd.getType().equals(Long.class) || verfmd.getType().equals(Long.TYPE)) {
710            nextVersion = nextNumber.longValue();
711          } else if (verfmd.getType().equals(Integer.class) || verfmd.getType().equals(Integer.TYPE)) {
712            nextVersion = nextNumber.intValue();
713          } else if (verfmd.getType().equals(Short.class) || verfmd.getType().equals(Short.TYPE)) {
714            nextVersion = nextNumber.shortValue();
715          }
716        }
717        op.replaceField(verfmd.getAbsoluteFieldNumber(), nextVersion);
718      }
719    }
720  }
721
722  /**
723   * All the information needed to perform a put on an Entity.
724   */
725  private static final class PutState {
726    private final ObjectProvider op;
727    private final StoreFieldManager fieldMgr;
728    private final Entity entity;
729
730    private PutState(ObjectProvider op, StoreFieldManager fieldMgr, Entity entity) {
731      this.op = op;
732      this.fieldMgr = fieldMgr;
733      this.entity = entity;
734    }
735  }
736}