package com.googlecode.objectify; import com.google.appengine.api.datastore.AsyncDatastoreService; import com.google.appengine.api.datastore.DatastoreService.KeyRangeState; import com.google.appengine.api.datastore.DatastoreServiceConfig; import com.google.appengine.api.datastore.DatastoreServiceFactory; import com.googlecode.objectify.cache.CachingAsyncDatastoreService; import com.googlecode.objectify.cache.EntityMemcache; import com.googlecode.objectify.impl.CacheControlImpl; import com.googlecode.objectify.impl.EntityMemcacheStats; import com.googlecode.objectify.impl.EntityMetadata; import com.googlecode.objectify.impl.Forge; import com.googlecode.objectify.impl.Keys; import com.googlecode.objectify.impl.ObjectifyImpl; import com.googlecode.objectify.impl.Registrar; import com.googlecode.objectify.impl.TypeUtils; import com.googlecode.objectify.impl.translate.Translators; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; /** * <p>Factory which allows us to construct implementations of the Objectify interface. * You should usually use the ObjectifyService to access Objectify.</p> * * <p>ObjectifyFactory is designed to be subclassed; much default behavior can be changed * by overriding methods. In particular, see createObjectify(), construct(), getAsyncDatastoreService().</p> * * @author Jeff Schnitzer <jeff@infohazard.org> */ public class ObjectifyFactory implements Forge { /** Default memcache namespace */ public static final String MEMCACHE_NAMESPACE = "ObjectifyCache"; /** Encapsulates entity registration info */ protected Registrar registrar = new Registrar(this); /** Some useful bits for working with keys */ protected Keys keys = new Keys(registrar); /** All the various loaders */ protected Translators translators = new Translators(this); /** Tracks stats */ protected EntityMemcacheStats memcacheStats = new EntityMemcacheStats(); /** Manages caching of entities at a low level */ protected EntityMemcache entityMemcache = new EntityMemcache(MEMCACHE_NAMESPACE, new CacheControlImpl(this), this.memcacheStats); /** * <p>Construct an instance of the specified type. Objectify uses this method whenever possible to create * instances of entities, condition classes, or other types; by overriding this method you can substitute Guice or other * dependency injection mechanisms. By default it constructs with a simple no-args constructor.</p> */ @Override public <T> T construct(Class<T> type) { // We do this instead of calling newInstance directly because this lets us work around accessiblity Constructor<T> ctor = TypeUtils.getNoArgConstructor(type); return TypeUtils.newInstance(ctor); } /** * <p>Construct a collection of the specified type and the specified size for use on a POJO field. You can override * this with Guice or whatnot.</p> * * <p>The default is to call construct(Class), with one twist - if a Set, SortedSet, or List interface is presented, * Objectify will construct a HashSet, TreeSet, or ArrayList (respectively). If you override this method with * dependency injection and you use uninitialized fields of these interface types in your entity pojos, you will * need to bind these interfaces to concrete types.</p> */ @SuppressWarnings("unchecked") public <T extends Collection<?>> T constructCollection(Class<T> type, int size) { if ((Class<?>)type == List.class || (Class<?>)type == Collection.class) return (T)new ArrayList<>(size); else if ((Class<?>)type == Set.class) return (T)new HashSet<>((int)(size * 1.5)); else if ((Class<?>)type == SortedSet.class) return (T)new TreeSet<>(); else return construct(type); } /** * <p>Construct a map of the specified type for use on a POJO field. You can override this with Guice or whatnot.</p> * * <p>The default is to call construct(Class), with one twist - if a Map or SortedMap List interface is presented, * Objectify will construct a HashMap or TreeMap (respectively). If you override this method with * dependency injection and you use uninitialized fields of these interface types in your entity pojos, you will * need to bind these interfaces to concrete types.</p> */ @SuppressWarnings("unchecked") public <T extends Map<?, ?>> T constructMap(Class<T> type) { if ((Class<?>)type == Map.class) return (T)new HashMap<>(); else if ((Class<?>)type == SortedMap.class) return (T)new TreeMap<>(); else return construct(type); } /** * Get an AsyncDatastoreService facade appropriate to the options. All Objectify * datastore interaction goes through an AsyncDatastoreService. This might or * might not produce a CachingAsyncDatastoreService. * * @return an AsyncDatastoreService configured per the specified options. */ public AsyncDatastoreService createAsyncDatastoreService(DatastoreServiceConfig cfg, boolean globalCache) { AsyncDatastoreService ads = this.createRawAsyncDatastoreService(cfg); if (globalCache && this.registrar.isCacheEnabled()) return new CachingAsyncDatastoreService(ads, this.entityMemcache); else return ads; } /** * You can override this to add behavior at the raw datastoreservice level. */ protected AsyncDatastoreService createRawAsyncDatastoreService(DatastoreServiceConfig cfg) { return DatastoreServiceFactory.getAsyncDatastoreService(cfg); } /** * This is the beginning of any Objectify session. It creates an Objectify instance with the default * options, unless you override this method to alter the options. You can also override this method * to produce a wholly different Objectify implementation (possibly using ObjectifyWrapper). * * <p>The default options are:</p> * * <ul> * <li>Do NOT begin a transaction.</li> * <li>DO use a global cache.</li> * <li>Use STRONG consistency.</li> * <li>Apply no deadline to calls.</li> * </ul> * * <p>Note that when using Objectify you will almost never directly call this method. Instead you * should call the static ofy() method on ObjectifyService.</p> * * @return a new Objectify instance */ public Objectify begin() { return new ObjectifyImpl<>(this); } /** * <p>All POJO entity classes which are to be managed by Objectify * must be registered first. This method must be called in a single-threaded * mode sometime around application initialization.</p> * * <p>Any extra translators must be added to the Translators *before* * entity classes are registered.</p> * * <p>Attempts to re-register entity classes are ignored.</p> */ public <T> void register(Class<T> clazz) { this.registrar.register(clazz); } /** * Get the object that tracks memcache stats. */ public EntityMemcacheStats getMemcacheStats() { return this.memcacheStats; } /** * Sets the error handler for the main memcache object. */ @SuppressWarnings("deprecation") public void setMemcacheErrorHandler(com.google.appengine.api.memcache.ErrorHandler handler) { this.entityMemcache.setErrorHandler(handler); } // // Stuff which should only be necessary internally, but might be useful to others. // /** * @return the metadata for a kind of typed object * @throws IllegalArgumentException if the kind has not been registered */ public <T> EntityMetadata<T> getMetadata(Class<T> clazz) throws IllegalArgumentException { return this.registrar.getMetadataSafe(clazz); } /** * @return the metadata for a kind of entity based on its key * @throws IllegalArgumentException if the kind has not been registered */ public <T> EntityMetadata<T> getMetadata(com.google.appengine.api.datastore.Key key) throws IllegalArgumentException { return this.registrar.getMetadataSafe(key.getKind()); } /** * @return the metadata for a kind of entity based on its key * @throws IllegalArgumentException if the kind has not been registered */ public <T> EntityMetadata<T> getMetadata(Key<T> key) throws IllegalArgumentException { return this.registrar.getMetadataSafe(key.getKind()); } /** * Gets metadata for the specified kind, returning null if nothing registered. This method is not like * the others because it returns null instead of throwing an exception if the kind is not found. * @return null if the kind is not registered. */ public <T> EntityMetadata<T> getMetadata(String kind) { return this.registrar.getMetadata(kind); } /** * Named differently so you don't accidentally use the Object form * @return the metadata for a kind of typed object. * @throws IllegalArgumentException if the kind has not been registered */ @SuppressWarnings("unchecked") public <T> EntityMetadata<T> getMetadataForEntity(T obj) throws IllegalArgumentException { // Type erasure sucks ass return (EntityMetadata<T>)this.getMetadata(obj.getClass()); } /** * Allocates a single id from the allocator for the specified kind. Safe to use in concert * with the automatic generator. This is just a convenience method for allocateIds(). * * @param clazz must be a registered entity class with a Long or long id field. * @return a key with an id that is unique to the kind */ public <T> Key<T> allocateId(Class<T> clazz) { return allocateIds(clazz, 1).iterator().next(); } /** * Allocates a single id from the allocator for the specified kind. Safe to use in concert * with the automatic generator. This is just a convenience method for allocateIds(). * * Note that the id is only unique within the parent, not across the entire kind. * * @param parentKeyOrEntity must be a legitimate parent for the class type. It need not * point to an existent entity, but it must be the correct type for clazz. * @param clazz must be a registered entity class with a Long or long id field, and * a parent key of the correct type. * @return a key with a new id unique to the kind and parent */ public <T> Key<T> allocateId(Object parentKeyOrEntity, Class<T> clazz) { return allocateIds(parentKeyOrEntity, clazz, 1).iterator().next(); } /** * Preallocate a contiguous range of unique ids within the namespace of the * specified entity class. These ids can be used in concert with the normal * automatic allocation of ids when put()ing entities with null Long id fields. * * @param clazz must be a registered entity class with a Long or long id field. * @param num must be >= 1 and <= 1 billion */ public <T> KeyRange<T> allocateIds(Class<T> clazz, long num) { // Feels a little weird going directly to the DatastoreServiceFactory but the // allocateIds() method really is optionless. String kind = Key.getKind(clazz); return new KeyRange<>(DatastoreServiceFactory.getDatastoreService().allocateIds(kind, num)); } /** * Preallocate a contiguous range of unique ids within the namespace of the * specified entity class and the parent key. These ids can be used in concert with the normal * automatic allocation of ids when put()ing entities with null Long id fields. * * @param parentKeyOrEntity must be a legitimate parent for the class type. It need not * point to an existent entity, but it must be the correct type for clazz. * @param clazz must be a registered entity class with a Long or long id field, and * a parent key of the correct type. * @param num must be >= 1 and <= 1 billion */ public <T> KeyRange<T> allocateIds(Object parentKeyOrEntity, Class<T> clazz, long num) { Key<?> parent = keys().anythingToKey(parentKeyOrEntity); String kind = Key.getKind(clazz); // Feels a little weird going directly to the DatastoreServiceFactory but the // allocateIds() method really is optionless. return new KeyRange<>(DatastoreServiceFactory.getDatastoreService().allocateIds(parent.getRaw(), kind, num)); } /** * Allocates a user-specified contiguous range of unique IDs, preventing the allocator from * giving them out to entities (with autogeneration) or other calls to allocate methods. * This lets you specify a specific range to block out (for example, you are bulk-loading a * collection of pre-existing entities). If you don't care about what id is allocated, use * one of the other allocate methods. */ public <T> KeyRangeState allocateIdRange(KeyRange<T> range) { return DatastoreServiceFactory.getDatastoreService().allocateIdRange(range.getRaw()); } /** * <p>Gets the master list of all registered TranslatorFactory objects. By adding Translators, Objectify * can process additional field types which are not part of the standard GAE SDK. <b>You must * add translators *before* registering entity pojo classes.</b></p> * * @return the repository of TranslatorFactory objects, to which you can optionally add translators */ public Translators getTranslators() { return this.translators; } /** * Some tools for working with keys. This is an internal Objectify API and subject to change without * notice. You probably want the Key.create() methods instead. */ public Keys keys() { return keys; } }