package com.googlecode.objectify.impl;
import com.google.appengine.api.datastore.Entity;
import com.google.common.base.Predicates;
import com.google.common.collect.Maps;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.LoadResult;
import com.googlecode.objectify.Objectify;
import com.googlecode.objectify.Ref;
import com.googlecode.objectify.Result;
import com.googlecode.objectify.cmd.LoadType;
import com.googlecode.objectify.cmd.Loader;
import com.googlecode.objectify.impl.translate.LoadContext;
import com.googlecode.objectify.util.ResultCache;
import com.googlecode.objectify.util.ResultNowFunction;
import com.googlecode.objectify.util.ResultProxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* <p>Implementation of the Loader interface. This is also suitable for subclassing; you
* can return your own subclass by overriding ObjectifyImpl.load().</p>
*
* @author Jeff Schnitzer <jeff@infohazard.org>
*/
public class LoaderImpl<L extends Loader> extends Queryable<Object> implements Loader, Cloneable
{
/** */
protected ObjectifyImpl<?> ofy;
/** */
protected LoadArrangement loadArrangement = new LoadArrangement();
/** */
public LoaderImpl(ObjectifyImpl<?> ofy) {
super(null);
this.ofy = ofy;
}
/* (non-Javadoc)
* @see com.googlecode.objectify.impl.cmd.QueryDefinition#createQuery()
*/
@Override
QueryImpl<Object> createQuery() {
return new QueryImpl<>(this);
}
/* (non-Javadoc)
* @see com.googlecode.objectify.cmd.Loader#group(java.lang.Class<?>[])
*/
@Override
@SuppressWarnings("unchecked")
public L group(Class<?>... groups) {
LoaderImpl<L> clone = this.clone();
clone.loadArrangement = new LoadArrangement();
clone.loadArrangement.addAll(Arrays.asList(groups));
clone.loadArrangement.addAll(this.loadArrangement);
return (L)clone;
}
/* (non-Javadoc)
* @see com.googlecode.objectify.cmd.Loader#type(java.lang.Class)
*/
@Override
public <E> LoadType<E> type(Class<E> type) {
return new LoadTypeImpl<>(this, Key.getKind(type), type);
}
@Override
public <E> LoadType<E> kind(String kind) {
return new LoadTypeImpl<>(this, kind, null);
}
/* (non-Javadoc)
* @see com.googlecode.objectify.cmd.Loader#ref(com.googlecode.objectify.Ref)
*/
@Override
public <E> LoadResult<E> ref(Ref<E> ref) {
return key(ref.key());
}
/* (non-Javadoc)
* @see com.googlecode.objectify.cmd.Loader#refs(com.googlecode.objectify.Ref<?>[])
*/
@Override
@SuppressWarnings("unchecked")
public <E> Map<Key<E>, E> refs(Ref<? extends E>... refs) {
return refs(Arrays.asList((Ref<E>[])refs));
}
/* (non-Javadoc)
* @see com.googlecode.objectify.cmd.Loader#refs(java.lang.Iterable)
*/
@Override
public <E> Map<Key<E>, E> refs(final Iterable<Ref<E>> refs) {
return values(refs);
}
/* (non-Javadoc)
* @see com.googlecode.objectify.cmd.Loader#entity(com.googlecode.objectify.Key)
*/
@Override
public <E> LoadResult<E> key(Key<E> key) {
return new LoadResult<>(key, createLoadEngine().load(key));
}
/* (non-Javadoc)
* @see com.googlecode.objectify.cmd.Loader#entity(java.lang.Object)
*/
@Override
public <E> LoadResult<E> entity(E entity) {
return key(ofy.factory().keys().keyOf(entity));
}
/* (non-Javadoc)
* @see com.googlecode.objectify.cmd.Loader#value(java.lang.Object)
*/
@Override
@SuppressWarnings("unchecked")
public <E> LoadResult<E> value(Object key) {
return (LoadResult<E>)key(ofy.factory().keys().anythingToKey(key));
}
/* (non-Javadoc)
* @see com.googlecode.objectify.cmd.Loader#keys(com.googlecode.objectify.Key<E>[])
*/
@Override
@SuppressWarnings("unchecked")
public <E> Map<Key<E>, E> keys(Key<? extends E>... keys) {
return this.keys(Arrays.asList((Key<E>[])keys));
}
/* (non-Javadoc)
* @see com.googlecode.objectify.cmd.Loader#keys(java.lang.Iterable)
*/
@Override
public <E> Map<Key<E>, E> keys(Iterable<Key<E>> keys) {
return values(keys);
}
/* (non-Javadoc)
* @see com.googlecode.objectify.cmd.Loader#entities(E[])
*/
@Override
public <E> Map<Key<E>, E> entities(E... entities) {
return this.entities(Arrays.asList(entities));
}
/* (non-Javadoc)
* @see com.googlecode.objectify.cmd.Loader#entities(java.lang.Iterable)
*/
@Override
public <E> Map<Key<E>, E> entities(Iterable<E> values) {
return values(values);
}
/* (non-Javadoc)
* @see com.googlecode.objectify.cmd.Loader#values(java.lang.Object[])
*/
@Override
public <E> Map<Key<E>, E> values(Object... values) {
return values(Arrays.asList(values));
}
/* (non-Javadoc)
* @see com.googlecode.objectify.cmd.Loader#values(java.lang.Iterable)
*/
@Override
@SuppressWarnings("unchecked")
public <E> Map<Key<E>, E> values(Iterable<?> values) {
// Do this in a separate pass so any errors converting keys will show up before we try loading something
List<Key<E>> keys = new ArrayList<>();
for (Object keyish: values)
keys.add((Key<E>)ofy.factory().keys().anythingToKey(keyish));
LoadEngine engine = createLoadEngine();
final Map<Key<E>, Result<E>> results = new LinkedHashMap<>();
for (Key<E> key: keys)
results.put(key, engine.load(key));
engine.execute();
// Now asynchronously translate into a normal-looking map. We must be careful to exclude results with
// null (missing) values because that is the contract established by DatastoreService.get().
// We use the ResultProxy and create a new map because the performance of filtered views is questionable.
return ResultProxy.create(Map.class, new ResultCache<Map<Key<E>, E>>() {
@Override
public Map<Key<E>, E> nowUncached() {
return
Maps.newLinkedHashMap(
Maps.filterValues(
Maps.transformValues(results, ResultNowFunction.<E>instance()),
Predicates.notNull()));
}
});
}
/* (non-Javadoc)
* @see com.googlecode.objectify.cmd.Loader#getObjectify()
*/
@Override
public Objectify getObjectify() {
return ofy;
}
/* (non-Javadoc)
* @see com.googlecode.objectify.cmd.Loader#getLoadGroups()
*/
@Override
public Set<Class<?>> getLoadGroups() {
return Collections.unmodifiableSet(loadArrangement);
}
/** */
public ObjectifyImpl<?> getObjectifyImpl() {
return this.ofy;
}
/**
* Use this once for one operation and then throw it away
* @return a fresh engine that handles fundamental datastore operations for load commands
*/
LoadEngine createLoadEngine() {
return new LoadEngine(ofy, ofy.getSession(), ofy.createAsyncDatastoreService(), loadArrangement);
}
/**
* Use this once for one operation and then throw it away
* @return a fresh engine that handles fundamental datastore operations for queries
*/
QueryEngine createQueryEngine() {
return new QueryEngine(this, ofy.createAsyncDatastoreService(), ofy.getTransaction() == null ? null : ofy.getTransaction().getRaw());
}
/* (non-Javadoc)
* @see com.googlecode.objectify.cmd.Loader#now(com.googlecode.objectify.Key)
*/
@Override
public <E> E now(Key<E> key) {
return createLoadEngine().load(key).now();
}
/* (non-Javadoc)
* @see com.googlecode.objectify.Objectify#toPojo(com.google.appengine.api.datastore.Entity)
*/
@Override
public <T> T fromEntity(Entity entity) {
LoadEngine engine = createLoadEngine();
LoadContext context = new LoadContext(engine);
T result = engine.load(entity, context);
context.done();
return result;
}
/* (non-Javadoc)
* @see java.lang.Object#clone()
*/
@SuppressWarnings("unchecked")
protected LoaderImpl<L> clone() {
try {
return (LoaderImpl<L>)super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e); // impossible
}
}
}