package com.googlecode.objectify.impl;
import com.google.appengine.api.datastore.Entity;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Objectify;
import com.googlecode.objectify.Result;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Manages all the logic of deferring operations
*/
public class Deferrer {
/** */
private final Objectify ofy;
/** */
private final Session session;
/** Values of null mean "delete" */
private final Map<Key<?>, Object> operations = new HashMap<>();
/** Entities with autogenerated (null) ids can't be put in the map, they don't have keys */
private final List<Object> autogeneratedIdSaves = new ArrayList<>();
/** */
public Deferrer(Objectify ofy, Session session) {
this.ofy = ofy;
this.session = session;
}
/**
* Eliminate any deferred operations against the entity. Used when an explicit save (or delete) was
* executed against the key, so we no longer need the deferred operation.
*
* @param keyOrEntity can be a Key, Key<?>, Entity, or entity pojo
*/
public void undefer(Object keyOrEntity) {
if (keyOrEntity instanceof Key<?>)
operations.remove((Key<?>)keyOrEntity);
else if (keyOrEntity instanceof com.google.appengine.api.datastore.Key)
operations.remove(Key.create((com.google.appengine.api.datastore.Key)keyOrEntity));
else if (keyOrEntity instanceof Entity)
operations.remove(Key.create(((Entity)keyOrEntity).getKey()));
else if (ofy.factory().keys().requiresAutogeneratedId(keyOrEntity)) {
autogeneratedIdSaves.remove(keyOrEntity);
} else {
Key<?> key = ofy.factory().keys().keyOf(keyOrEntity);
operations.remove(key);
}
}
/**
*/
public void deferSave(Object entity) {
if (entity instanceof Entity) {
com.google.appengine.api.datastore.Key key = ((Entity)entity).getKey();
if (key.isComplete()) {
Key<?> ofyKey = Key.create(key);
session.addValue(ofyKey, entity);
operations.put(ofyKey, entity);
} else {
autogeneratedIdSaves.add(entity);
}
} else {
if (ofy.factory().keys().requiresAutogeneratedId(entity)) {
autogeneratedIdSaves.add(entity);
} else {
Key<?> key = ofy.factory().keys().keyOf(entity);
session.addValue(key, entity);
operations.put(key, entity);
}
}
}
public void deferDelete(Key<?> key) {
session.addValue(key, null);
operations.put(key, null);
}
public void flush() {
final List<Result<?>> futures = new ArrayList<>();
// Need to do this in a loop because @OnSave methods can enlist more deferred operations. Execution
// of save or delete will undefer() all the relevant items, so both lists empty mean we're done.
while (!operations.isEmpty() || !autogeneratedIdSaves.isEmpty()) {
// Sort into two batch operations: one for save, one for delete.
List<Object> saves = new ArrayList<>();
List<Key<?>> deletes = new ArrayList<>();
for (Map.Entry<Key<?>, Object> entry : operations.entrySet()) {
if (entry.getValue() == null)
deletes.add(entry.getKey());
else
saves.add(entry.getValue());
}
saves.addAll(autogeneratedIdSaves);
if (!saves.isEmpty())
futures.add(ofy.save().entities(saves));
if (!deletes.isEmpty())
futures.add(ofy.delete().keys(deletes));
}
// Complete any pending operations
for (final Result<?> future : futures) {
future.now();
}
}
}