package com.googlecode.objectify.impl;
import com.google.appengine.api.datastore.AsyncDatastoreService;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Transaction;
import com.google.common.collect.Lists;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Result;
import com.googlecode.objectify.impl.translate.SaveContext;
import com.googlecode.objectify.util.ResultWrapper;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This is the master logic for saving and deleting entities from the datastore. It provides the
* fundamental operations that enable the rest of the API. One of these engines is created for every operation;
* upon completion, it is thrown away.
*
* @author Jeff Schnitzer <jeff@infohazard.org>
*/
public class WriteEngine
{
/** */
private static final Logger log = Logger.getLogger(WriteEngine.class.getName());
/** */
protected final ObjectifyImpl<?> ofy;
/** */
protected final AsyncDatastoreService ads;
/** */
protected final Session session;
/** */
protected final Deferrer deferrer;
/**
*/
public WriteEngine(ObjectifyImpl<?> ofy, AsyncDatastoreService ads, Session session, Deferrer deferrer) {
this.ofy = ofy;
this.ads = ads;
this.session = session;
this.deferrer = deferrer;
}
/** @return the transaction, or null if not */
private Transaction getTransactionRaw() {
return (ofy.getTransaction() == null) ? null : ofy.getTransaction().getRaw();
}
/**
* The fundamental put() operation.
*/
public <E> Result<Map<Key<E>, E>> save(Iterable<? extends E> entities) {
if (log.isLoggable(Level.FINEST))
log.finest("Saving " + entities);
final SaveContext ctx = new SaveContext();
final List<Entity> entityList = new ArrayList<>();
for (E obj: entities) {
if (obj == null)
throw new NullPointerException("Attempted to save a null entity");
deferrer.undefer(obj);
if (obj instanceof Entity) {
entityList.add((Entity)obj);
} else {
EntityMetadata<E> metadata = ofy.factory().getMetadataForEntity(obj);
entityList.add(metadata.save(obj, ctx));
}
}
// Need to make a copy of the original list because someone might clear it while we are async
final List<? extends E> original = Lists.newArrayList(entities);
// The CachingDatastoreService needs its own raw transaction
Future<List<com.google.appengine.api.datastore.Key>> raw = ads.put(getTransactionRaw(), entityList);
Result<List<com.google.appengine.api.datastore.Key>> adapted = new ResultAdapter<>(raw);
Result<Map<Key<E>, E>> result = new ResultWrapper<List<com.google.appengine.api.datastore.Key>, Map<Key<E>, E>>(adapted) {
private static final long serialVersionUID = 1L;
@Override
protected Map<Key<E>, E> wrap(List<com.google.appengine.api.datastore.Key> base) {
Map<Key<E>, E> result = new LinkedHashMap<>(base.size() * 2);
// One pass through the translated pojos to patch up any generated ids in the original objects
// Iterator order should be exactly the same for keys and values
Iterator<com.google.appengine.api.datastore.Key> keysIt = base.iterator();
for (E obj: original)
{
com.google.appengine.api.datastore.Key k = keysIt.next();
if (!(obj instanceof Entity)) {
KeyMetadata<E> metadata = ofy.factory().keys().getMetadataSafe(obj);
if (metadata.isIdGeneratable())
metadata.setLongId(obj, k.getId());
}
Key<E> key = Key.create(k);
result.put(key, obj);
// Also stuff this in the session
session.addValue(key, obj);
}
if (log.isLoggable(Level.FINEST))
log.finest("Saved " + base);
return result;
}
};
if (ofy.getTransaction() != null)
ofy.getTransaction().enlist(result);
return result;
}
/**
* The fundamental delete() operation.
*/
public Result<Void> delete(final Iterable<com.google.appengine.api.datastore.Key> keys) {
for (com.google.appengine.api.datastore.Key key: keys)
deferrer.undefer(Key.create(key));
Future<Void> fut = ads.delete(getTransactionRaw(), keys);
Result<Void> adapted = new ResultAdapter<>(fut);
Result<Void> result = new ResultWrapper<Void, Void>(adapted) {
private static final long serialVersionUID = 1L;
@Override
protected Void wrap(Void orig) {
for (com.google.appengine.api.datastore.Key key: keys)
session.addValue(Key.create(key), null);
return orig;
}
};
if (ofy.getTransaction() != null)
ofy.getTransaction().enlist(result);
return result;
}
}