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

com.googlecode.objectify.impl.LoadEngine Maven / Gradle / Ivy

There is a newer version: 6.1.2
Show newest version
package com.googlecode.objectify.impl;

import com.google.appengine.api.datastore.AsyncDatastoreService;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Transaction;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Ref;
import com.googlecode.objectify.Result;
import com.googlecode.objectify.impl.ref.LiveRef;
import com.googlecode.objectify.impl.translate.LoadContext;
import com.googlecode.objectify.util.ResultCache;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Represents one "batch" of loading.  Get a number of Result objects, then execute().  Some work is done
 * right away, some work is done on the first get().  There might be multiple rounds of execution to process
 * all the @Load groups, but that is invisible outside this class.
 *
 * @author Jeff Schnitzer 
 */
public class LoadEngine
{
	/** */
	private static final Logger log = Logger.getLogger(LoadEngine.class.getName());

	/** */
	ObjectifyImpl ofy;
	AsyncDatastoreService ads;
	Session session;
	LoadArrangement loadArrangement;

	/** The current round, replaced whenever the round executes */
	Round round;

	/**
	 */
	public LoadEngine(ObjectifyImpl ofy, Session session, AsyncDatastoreService ads, LoadArrangement loadArrangement) {
		this.ofy = ofy;
		this.session = session;
		this.ads = ads;
		this.loadArrangement = loadArrangement;

		this.round = new Round(this, 0);

		if (log.isLoggable(Level.FINEST))
			log.finest("Starting load engine with groups " + loadArrangement);
	}

	/**
	 * Gets the result, possibly from the session, putting it in the session if necessary.
	 * Also will recursively prepare the session with @Load parents as appropriate.
	 * @throws NullPointerException if key is null
	 */
	public  Result load(Key key) {
		if (key == null)
			throw new NullPointerException("You tried to load a null key!");

		Result result = round.get(key);

		// If we are running a transaction, enlist the result so that it gets processed on commit even
		// if the client never materializes the result.
		if (ofy.getTransaction() != null)
			ofy.getTransaction().enlist(result);

		// Now check to see if we need to recurse and add our parent(s) to the round
		if (key.getParent() != null) {
			KeyMetadata meta = ofy.factory().keys().getMetadata(key);
			// Is it really possible for this to be null?
			if (meta != null) {
				if (meta.shouldLoadParent(loadArrangement)) {
					load(key.getParent());
				}
			}
		}

		return result;
	}

	/**
	 * Starts asychronous fetching of the batch.
	 */
	public void execute() {
		if (round.needsExecution()) {
			Round old = round;
			round = old.next();
			old.execute();
		}
	}

	/**
	 * Create a Ref for the key, and maybe start a load operation depending on current load groups.
	 *
	 * @param rootEntity is the entity key which holds this property (possibly through some level of embedded objects)
	 */
	public  Ref makeRef(Key rootEntity, LoadConditions loadConditions, Key key) {
		Ref ref = new LiveRef<>(key, ofy);

		if (shouldLoad(loadConditions)) {
			load(key);
		}

		return ref;
	}

	/**
	 * @return true if the specified property should be loaded in this batch
	 */
	public boolean shouldLoad(LoadConditions loadConditions) {
		return loadConditions.shouldLoad(loadArrangement);
	}

	/**
	 * Stuffs an Entity into a place where values in the round can be obtained instead of going to the datastore.
	 * Called by non-hybrid queries to add results and eliminate batch fetching.
	 */
	public void stuff(Entity ent) {
		round.stuff(ent);
	}

	/**
	 * Asynchronously translate raw to processed; might produce successive load operations as refs are filled in
	 */
	public Result, Object>> translate(final Result> raw) {
		return new ResultCache, Object>>() {

			/** */
			LoadContext ctx;

			/** */
			@Override
			public Map, Object> nowUncached() {
				Map, Object> result = new HashMap<>(raw.now().size() * 2);

				ctx = new LoadContext(LoadEngine.this);

				for (Entity ent: raw.now().values()) {
					Key key = Key.create(ent.getKey());
					Object entity = load(ent, ctx);
					result.put(key, entity);
				}

				return result;
			}

			/**
			 * We need to execute the done() after the translated value has been set, otherwise we
			 * can produce an infinite recursion problem.
			 */
			@Override
			protected void postExecuteHook() {
				ctx.done();
				ctx = null;
			}
		};
	}

	/**
	 * Fetch the keys from the async datastore using the current transaction context
	 */
	public Result> fetch(Set keys) {
		Transaction txn = (ofy.getTransaction() == null) ? null : ofy.getTransaction().getRaw();

		Future> fut = ads.get(txn, keys);
		return ResultAdapter.create(fut);
	}

	/**
	 * Converts a datastore entity into a typed pojo object
	 * @return an assembled pojo, or the Entity itself if the kind is not registered, or null if the input value was null
	 */
	@SuppressWarnings("unchecked")
	public  T load(Entity ent, LoadContext ctx) {
		if (ent == null)
			return null;

		EntityMetadata meta = ofy.factory().getMetadata(ent.getKind());
		if (meta == null)
			return (T)ent;
		else
			return meta.load(ent, ctx);
	}

	/** */
	public Session getSession() {
		return session;
	}

	/** */
	public LoadArrangement getLoadArrangement() {
		return loadArrangement;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy