All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.googlecode.objectify.ObjectifyFactory Maven / Gradle / Ivy

Go to download

*** THIS VERSION UPLOADED FOR USE WITH CEDAR-COMMON, TO AVOID DEPENDENCIES ON GOOGLE CODE-BASED MAVEN REPOSITORIES. *** The simplest convenient interface to the Google App Engine datastore

The newest version!
package com.googlecode.objectify;

import java.lang.reflect.Field;

import com.google.appengine.api.datastore.AsyncDatastoreService;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreService.KeyRangeState;
import com.google.appengine.api.datastore.DatastoreServiceConfig;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.ReadPolicy;
import com.google.appengine.api.datastore.Transaction;
import com.google.appengine.api.datastore.TransactionOptions;
import com.googlecode.objectify.cache.CachingAsyncDatastoreService;
import com.googlecode.objectify.cache.CachingDatastoreService;
import com.googlecode.objectify.cache.EntityMemcache;
import com.googlecode.objectify.impl.AsyncObjectifyImpl;
import com.googlecode.objectify.impl.CacheControlImpl;
import com.googlecode.objectify.impl.EntityMemcacheStats;
import com.googlecode.objectify.impl.EntityMetadata;
import com.googlecode.objectify.impl.ObjectifyImpl;
import com.googlecode.objectify.impl.Registrar;
import com.googlecode.objectify.impl.SessionCachingAsyncObjectifyImpl;
import com.googlecode.objectify.impl.conv.Conversions;
import com.googlecode.objectify.impl.conv.ConverterSaveContext;
import com.googlecode.objectify.util.FutureHelper;

/**
 * 

Factory which allows us to construct implementations of the Objectify interface. * Just call {@code begin()}.

* *

Note that unlike the DatastoreService, there is no implicit transaction management. * You either create an Objectify without a transaction (by calling {@code begin()} or you * create one with a transaction (by calling {@code beginTransaction()}. If you create * an Objectify with a transaction, you should use it like this:

*
 * 	Objectify data = factory.beginTransaction()
 * 	try {
 * 		// do work
 * 		data.getTxn().commit();
 * 	}
 * 	finally {
 * 		if (data.getTxn().isActive()) data.getTxn().rollback();
 * 	}
 * 
* *

It would be fairly easy for someone to implement a ScanningObjectifyFactory * on top of this class that looks for @Entity annotations based on Scannotation or * Reflections, but this would add extra dependency jars and need a hook for * application startup.

* * @author Jeff Schnitzer */ public class ObjectifyFactory { /** Default memcache namespace; override getRawMemcacheService() to change */ public static final String MEMCACHE_NAMESPACE = "ObjectifyCache"; /** Encapsulates entity registration info */ protected Registrar registrar = new Registrar(this); /** All the various converters */ protected Conversions conversions = new Conversions(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); /** * Creates the default options for begin() and beginTransaction(). You can * override this if, for example, you wanted to enable session caching by default. */ protected ObjectifyOpts createDefaultOpts() { return new ObjectifyOpts(); } /** * Override this in your factory if you wish to use a different impl, say, * one based on the ObjectifyWrapper. * * @param ds the DatastoreService * @param opts the options for creating this Objectify * @return an instance of Objectify configured appropriately */ protected Objectify createObjectify(AsyncDatastoreService ds, ObjectifyOpts opts) { TransactionOptions txnOpts = opts.getTransactionOptions(); Transaction txn = (txnOpts == null) ? null : FutureHelper.quietGet(ds.beginTransaction(txnOpts)); Objectify ofy = (opts.getSessionCache()) ? new ObjectifyImpl(opts, new SessionCachingAsyncObjectifyImpl(this, ds, txn)) : new ObjectifyImpl(opts, new AsyncObjectifyImpl(this, ds, txn)); return ofy; } /** * Make a datastore service config that corresponds to the specified options. * Note that not all options are defined by the config; some options (e.g. caching) * have no analogue in the native datastore. */ protected DatastoreServiceConfig makeConfig(ObjectifyOpts opts) { DatastoreServiceConfig cfg = DatastoreServiceConfig.Builder.withReadPolicy(new ReadPolicy(opts.getConsistency())); if (opts.getDeadline() != null) cfg.deadline(opts.getDeadline()); return cfg; } /** * Get a DatastoreService facade appropriate to the options. Note that * Objectify does not itself use DatastoreService; this method solely * exists to support Objectify.getDatastore(). * * @return a DatastoreService configured per the specified options. */ public DatastoreService getDatastoreService(ObjectifyOpts opts) { DatastoreServiceConfig cfg = this.makeConfig(opts); DatastoreService ds = this.getRawDatastoreService(cfg); if (opts.getGlobalCache() && this.registrar.isCacheEnabled()) { CachingAsyncDatastoreService async = new CachingAsyncDatastoreService(this.getRawAsyncDatastoreService(cfg), this.entityMemcache); return new CachingDatastoreService(ds, async); } else { return ds; } } /** * Get an AsyncDatastoreService facade appropriate to the options. All Objectify * datastore interaction goes through an AsyncDatastoreService, even the synchronous * methods. The GAE SDK works the same way; DatastoreService is a facade around * AsyncDatastoreService. * * @return an AsyncDatastoreService configured per the specified options. */ public AsyncDatastoreService getAsyncDatastoreService(ObjectifyOpts opts) { DatastoreServiceConfig cfg = this.makeConfig(opts); AsyncDatastoreService ads = this.getRawAsyncDatastoreService(cfg); if (opts.getGlobalCache() && 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 DatastoreService getRawDatastoreService(DatastoreServiceConfig cfg) { return DatastoreServiceFactory.getDatastoreService(cfg); } /** * You can override this to add behavior at the raw datastoreservice level. */ protected AsyncDatastoreService getRawAsyncDatastoreService(DatastoreServiceConfig cfg) { return DatastoreServiceFactory.getAsyncDatastoreService(cfg); } /** * Create a lightweight Objectify instance with the default options. * Equivalent to begin(new ObjectifyOpts()). */ public Objectify begin() { return this.begin(this.createDefaultOpts()); } /** * @return an Objectify from the DatastoreService with the specified options. * This is a lightweight operation and can be used freely. */ public Objectify begin(ObjectifyOpts opts) { AsyncDatastoreService ds = this.getAsyncDatastoreService(opts); return this.createObjectify(ds, opts); } /** * @return an Objectify which uses a transaction. The transaction supports cross-group access, but * this has no extra overhead for a single-entity-group transaction. */ public Objectify beginTransaction() { return this.begin(this.createDefaultOpts().setBeginTransaction(true)); } /** *

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.

*/ public void register(Class clazz) { this.registrar.register(clazz); } /** * Get the object that tracks memcache stats. */ public EntityMemcacheStats getMemcacheStats() { return this.memcacheStats; } // // Stuff which should only be necessary internally, but might be useful to others. // /** * @return the metadata for a kind of entity based on its key * @throws IllegalArgumentException if the kind has not been registered */ public EntityMetadata getMetadata(com.google.appengine.api.datastore.Key key) { return this.getMetadata(key.getKind()); } /** * @return the metadata for a kind of entity based on its key * @throws IllegalArgumentException if the kind has not been registered */ public EntityMetadata getMetadata(Key key) { return this.getMetadata(key.getKind()); } /** * @return the metadata for a kind of typed object * @throws IllegalArgumentException if the kind has not been registered */ public EntityMetadata getMetadata(Class clazz) { EntityMetadata metadata = this.registrar.getMetadata(clazz); if (metadata == null) throw new IllegalArgumentException("No class '" + clazz.getName() + "' was registered"); else return metadata; } /** * Gets metadata for the specified kind, or throws an exception if the kind is unknown */ public EntityMetadata getMetadata(String kind) { EntityMetadata metadata = this.registrar.getMetadata(kind); if (metadata == null) throw new IllegalArgumentException("No class with kind '" + kind + "' was registered"); else return metadata; } /** * 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 EntityMetadata getMetadataForEntity(T obj) { // Type erasure sucks ass return (EntityMetadata)this.getMetadata(obj.getClass()); } /** *

Gets the Key given an object that might be a Key, Key, or entity.

* * @param keyOrEntity must be a Key, Key, or registered entity. * @throws NullPointerException if keyOrEntity is null * @throws IllegalArgumentException if keyOrEntity is not a Key, Key, or registered entity */ @SuppressWarnings("unchecked") public Key getKey(Object keyOrEntity) { if (keyOrEntity instanceof Key) return (Key)keyOrEntity; else if (keyOrEntity instanceof com.google.appengine.api.datastore.Key) return new Key((com.google.appengine.api.datastore.Key)keyOrEntity); else return new Key(this.getMetadataForEntity(keyOrEntity).getRawKey(keyOrEntity)); } /** *

Gets the raw datstore Key given an object that might be a Key, Key, or entity.

* * @param keyOrEntity must be a Key, Key, or registered entity. * @throws NullPointerException if keyOrEntity is null * @throws IllegalArgumentException if keyOrEntity is not a Key, Key, or registered entity */ public com.google.appengine.api.datastore.Key getRawKey(Object keyOrEntity) { if (keyOrEntity instanceof com.google.appengine.api.datastore.Key) return (com.google.appengine.api.datastore.Key)keyOrEntity; else if (keyOrEntity instanceof Key) return ((Key)keyOrEntity).getRaw(); else return this.getMetadataForEntity(keyOrEntity).getRawKey(keyOrEntity); } /** This is used just for makeFilterable() */ private static final ConverterSaveContext NO_CONTEXT = new ConverterSaveContext() { @Override public boolean inEmbeddedCollection() { return false; } @Override public Field getField() { return null; } }; /** * Translate Key or Entity objects into something that can be used in a filter clause. * Anything unknown (including null) is simply returned as-is and we hope that the filter works. * * @return whatever can be put into a filter clause. */ public Object makeFilterable(Object keyOrEntityOrOther) { if (keyOrEntityOrOther == null) return null; // Very important that we use the class rather than the Kind; many unregistered // classes would otherwise collide with real kinds eg User vs User. EntityMetadata meta = this.registrar.getMetadata(keyOrEntityOrOther.getClass()); if (meta == null) return this.getConversions().forDatastore(keyOrEntityOrOther, NO_CONTEXT); else return meta.getRawKey(keyOrEntityOrOther); } /** *

Converts a Key into a web-safe string suitable for http parameters * in URLs. Note that you can convert back and forth with the {@code keyToString()} * and {@code stringToKey()} methods.

* *

The String is actually generated by using the KeyFactory {@code keyToString()} * method on a raw version of the datastore key. You can, if you wanted, use * these web safe strings interchangeably.

* * @param key is any Objectify key * @return a simple String which does not need urlencoding */ public String keyToString(Key key) { return KeyFactory.keyToString(key.getRaw()); } /** * Converts a String generated with {@code keyToString()} back into an Objectify * Key. The String could also have been generated by the GAE {@code KeyFactory}. * * @param stringifiedKey is generated by either {@code ObjectifyFactory.keyToString()} or * {@code KeyFactory.keyToString()}. * @return a Key */ public Key stringToKey(String stringifiedKey) { return new Key(KeyFactory.stringToKey(stringifiedKey)); } /** * 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. */ public long allocateId(Class clazz) { return allocateIds(clazz, 1).iterator().next().getId(); } /** * 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. */ public long allocateId(Object parentKeyOrEntity, Class clazz) { return allocateIds(parentKeyOrEntity, clazz, 1).iterator().next().getId(); } /** * 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 KeyRange allocateIds(Class 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 KeyRange allocateIds(Object parentKeyOrEntity, Class clazz, long num) { Key parent = this.getKey(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 KeyRangeState allocateIdRange(KeyRange range) { return DatastoreServiceFactory.getDatastoreService().allocateIdRange(range.getRaw()); } /** * @return the repository of Converter objects */ public Conversions getConversions() { return this.conversions; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy