package com.googlecode.objectify.impl; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import com.google.appengine.api.datastore.DatastoreService; import com.google.appengine.api.datastore.Entity; import com.google.appengine.api.datastore.EntityNotFoundException; import com.google.appengine.api.datastore.Transaction; import com.googlecode.objectify.Key; import com.googlecode.objectify.NotFoundException; import com.googlecode.objectify.Objectify; import com.googlecode.objectify.ObjectifyFactory; import com.googlecode.objectify.Query; /** * Implementation of the Objectify interface. Note we *always* use the DatastoreService * methods that use transactions to avoid the confusion of implicit transactions. * * @author Jeff Schnitzer <jeff@infohazard.org> */ public class ObjectifyImpl implements Objectify { /** The factory that produced us */ protected ObjectifyFactory factory; /** The google object that does the actual heavy lifting */ protected DatastoreService ds; /** The transaction to use. If null, do not use transactions. */ protected Transaction txn; /** * Protected constructor creates a wrapper on the datastore with * the specified txn. * * @param txn can be null to not use transactions. */ public ObjectifyImpl(ObjectifyFactory fact, DatastoreService ds, Transaction txn) { this.factory = fact; this.ds = ds; this.txn = txn; } /* (non-Javadoc) * @see com.google.code.objectify.Objectify#get(java.lang.Iterable) */ @Override @SuppressWarnings("unchecked") public <T> Map<Key<T>, T> get(Iterable<? extends Key<? extends T>> keys) { // First we need to turn the keys into raw keys List<com.google.appengine.api.datastore.Key> rawKeys = new ArrayList<com.google.appengine.api.datastore.Key>(); for (Key<? extends T> obKey: keys) rawKeys.add(this.factory.typedKeyToRawKey(obKey)); Map<com.google.appengine.api.datastore.Key, Entity> entities = this.ds.get(this.txn, rawKeys); Map<Key<T>, T> result = new LinkedHashMap<Key<T>, T>(entities.size() * 2); for (com.google.appengine.api.datastore.Key rawKey : rawKeys) { Entity entity = entities.get(rawKey); if (entity != null) { EntityMetadata metadata = this.factory.getMetadata(rawKey); Key<T> obKey = this.factory.rawKeyToTypedKey(rawKey); result.put(obKey, (T)metadata.toObject(entity, this)); } } return result; } /* (non-Javadoc) * @see com.google.code.objectify.Objectify#get(com.google.appengine.api.datastore.Key) */ @Override public <T> T get(Key<? extends T> key) throws NotFoundException { // The actual implementation is find(). T value = this.find(key); if (value != null) return value; else throw new NotFoundException(key); } /* (non-Javadoc) * @see com.googlecode.objectify.Objectify#get(java.lang.Class, long) */ @Override public <T> T get(Class<? extends T> clazz, long id) throws NotFoundException { // The cast gets rid of "no unique maximal instance exists" compiler error return (T)this.get(new Key<T>(clazz, id)); } /* (non-Javadoc) * @see com.googlecode.objectify.Objectify#get(java.lang.Class, java.lang.String) */ @Override public <T> T get(Class<? extends T> clazz, String name) throws NotFoundException { // The cast gets rid of "no unique maximal instance exists" compiler error return (T)this.get(new Key<T>(clazz, name)); } /* (non-Javadoc) * @see com.googlecode.objectify.Objectify#get(java.lang.Class, java.lang.Iterable) */ @Override @SuppressWarnings("unchecked") public <S, T> Map<S, T> get(Class<? extends T> clazz, Iterable<S> ids) { List<Key<? extends T>> keys = new ArrayList<Key<? extends T>>(); for (Object id: ids) { if (id instanceof Long) keys.add(new Key<T>(clazz, (Long)id)); else if (id instanceof String) keys.add(new Key<T>(clazz, (String)id)); else throw new IllegalArgumentException("Only Long or String is allowed, not " + id.getClass().getName() + " (" + id + ")"); } Map<Key<T>, T> fetched = this.get(keys); Map<S, T> result = new LinkedHashMap<S, T>(fetched.size() * 2); for (Map.Entry<Key<T>, T> entry: fetched.entrySet()) { Object mapKey = entry.getKey().getName() != null ? entry.getKey().getName() : entry.getKey().getId(); result.put((S)mapKey, entry.getValue()); } return result; } /* (non-Javadoc) * @see com.googlecode.objectify.Objectify#get(java.lang.Class, S[]) */ @Override public <S, T> Map<S, T> get(Class<? extends T> clazz, S... idsOrNames) { return this.get(clazz, Arrays.asList(idsOrNames)); } /* (non-Javadoc) * @see com.googlecode.objectify.Objectify#find(com.google.appengine.api.datastore.Key) */ @Override public <T> T find(Key<? extends T> key) { try { Entity ent = this.ds.get(this.txn, this.factory.typedKeyToRawKey(key)); return this.factory.getMetadata(key).toObject(ent, this); } catch (EntityNotFoundException e) { return null; } } /* (non-Javadoc) * @see com.googlecode.objectify.Objectify#find(java.lang.Class, long) */ @Override public <T> T find(Class<? extends T> clazz, long id) { return this.find(new Key<T>(clazz, id)); } /* (non-Javadoc) * @see com.googlecode.objectify.Objectify#find(java.lang.Class, java.lang.String) */ @Override public <T> T find(Class<? extends T> clazz, String name) { return this.find(new Key<T>(clazz, name)); } /* (non-Javadoc) * @see com.google.code.objectify.Objectify#put(java.lang.Object) */ @Override public <T> Key<T> put(T obj) { EntityMetadata<T> metadata = this.factory.getMetadataForEntity(obj); Entity ent = metadata.toEntity(obj, this); com.google.appengine.api.datastore.Key rawKey = this.ds.put(this.txn, ent); // Need to reset the key value in case the value was generated metadata.setKey(obj, rawKey); return this.factory.rawKeyToTypedKey(rawKey); } /* (non-Javadoc) * @see com.google.code.objectify.Objectify#put(java.lang.Iterable) */ @Override public <T> Map<Key<T>, T> put(Iterable<? extends T> objs) { List<Entity> entityList = new ArrayList<Entity>(); for (T obj: objs) { EntityMetadata<T> metadata = this.factory.getMetadataForEntity(obj); entityList.add(metadata.toEntity(obj, this)); } List<com.google.appengine.api.datastore.Key> rawKeys = this.ds.put(this.txn, entityList); Map<Key<T>, T> result = new LinkedHashMap<Key<T>, T>(rawKeys.size() * 2); // Patch up any generated keys in the original objects while building new key list Iterator<com.google.appengine.api.datastore.Key> keysIt = rawKeys.iterator(); for (T obj: objs) { com.google.appengine.api.datastore.Key k = keysIt.next(); EntityMetadata<T> metadata = this.factory.getMetadataForEntity(obj); metadata.setKey(obj, k); Key<T> obKey = this.factory.rawKeyToTypedKey(k); result.put(obKey, obj); } return result; } /* (non-Javadoc) * @see com.googlecode.objectify.Objectify#put(T[]) */ @Override public <T> Map<Key<T>, T> put(T... objs) { return this.put(Arrays.asList(objs)); } /* (non-Javadoc) * @see com.googlecode.objectify.Objectify#delete(java.lang.Object) */ @Override public void delete(Object keyOrEntity) { this.ds.delete(this.txn, this.factory.getRawKey(keyOrEntity)); } /* (non-Javadoc) * @see com.googlecode.objectify.Objectify#delete(java.lang.Class, long) */ @Override public <T> void delete(Class<T> clazz, long id) { this.delete(new Key<T>(clazz, id)); } /* (non-Javadoc) * @see com.googlecode.objectify.Objectify#delete(Class, String) */ @Override public <T> void delete(Class<T> clazz, String name) { this.delete(new Key<T>(clazz, name)); } /* (non-Javadoc) * @see com.google.code.objectify.Objectify#delete(java.lang.Iterable) */ @Override public void delete(Iterable<?> keysOrEntities) { // We have to be careful here, objs could contain raw Keys or Keys or entity objects or both! List<com.google.appengine.api.datastore.Key> keys = new ArrayList<com.google.appengine.api.datastore.Key>(); for (Object obj: keysOrEntities) keys.add(this.factory.getRawKey(obj)); this.ds.delete(this.txn, keys); } /* (non-Javadoc) * @see com.googlecode.objectify.Objectify#query() */ @Override public <T> Query<T> query() { return new QueryImpl<T>(this.factory, this); } /* (non-Javadoc) * @see com.googlecode.objectify.Objectify#query(java.lang.Class) */ @Override public <T> Query<T> query(Class<T> clazz) { return new QueryImpl<T>(this.factory, this, clazz); } /* (non-Javadoc) * @see com.googlecode.objectify.Objectify#getTxn() */ @Override public Transaction getTxn() { return this.txn; } /* (non-Javadoc) * @see com.google.code.objectify.Objectify#getDatastore() */ @Override public DatastoreService getDatastore() { return this.ds; } /* (non-Javadoc) * @see com.googlecode.objectify.Objectify#getFactory() */ @Override public ObjectifyFactory getFactory() { return this.factory; } }