/* */ package com.googlecode.objectify; import com.googlecode.objectify.cache.PendingFutures; import com.googlecode.objectify.util.Closeable; import java.util.ArrayDeque; import java.util.Deque; /** * Holder of the master ObjectifyFactory and provider of the current thread-local Objectify instance. * Call {@code ofy()} at any point to get the current Objectify with the correct transaction context. * * @author Jeff Schnitzer */ public class ObjectifyService { /** */ private static ObjectifyFactory factory = new ObjectifyFactory(); /** */ public static void setFactory(ObjectifyFactory fact) { factory = fact; } /** * Thread local stack of Objectify instances corresponding to transaction depth */ private static final ThreadLocal<Deque<Objectify>> STACK = new ThreadLocal<Deque<Objectify>>() { @Override protected Deque<Objectify> initialValue() { return new ArrayDeque<>(); } }; /** * The method to call at any time to get the current Objectify, which may change depending on txn context */ public static Objectify ofy() { Deque<Objectify> stack = STACK.get(); if (stack.isEmpty()) throw new IllegalStateException("You have not started an Objectify context. You are probably missing the " + "ObjectifyFilter. If you are not running in the context of an http request, see the " + "ObjectifyService.run() method."); return stack.getLast(); } /** * @return the current factory */ public static ObjectifyFactory factory() { return factory; } /** * A shortcut for {@code ObjectifyFactory.register()} * * @see ObjectifyFactory#register(Class) */ public static void register(Class<?> clazz) { factory().register(clazz); } /** * <p>Runs one unit of work, making the root Objectify context available. This does not start a transaction, * but it makes the static ofy() method return an appropriate object.</p> * * <p>Normally you do not need to use this method. When servicing a normal request, the ObjectifyFilter * will run this for you. This method is useful for using Objectify outside of a normal request - * using the remote api, for example.</p> * * <p>Alternatively, you can use the begin() method and close the session manually.</p> * * @return the result of the work. */ public static <R> R run(Work<R> work) { try (Closeable closeable = begin()) { return work.run(); } } /** * <p>An alternative to run() which is somewhat easier to use with testing (ie, @Before and @After) frameworks. * You must close the return value at the end of the request in a finally block. It's better/safer to use run().</p> * * <p>This method is not typically necessary - in a normal request, the ObjectifyFilter takes care of this housekeeping * for you. However, in unit tests or remote API calls it can be useful.</p> */ public static Closeable begin() { final Deque<Objectify> stack = STACK.get(); // Request forwarding in the container runs all the filters again, including the ObjectifyFilter. Since we // have established a context already, we can't just throw an exception. We can't even really warn. Let's // just give them a new context; the bummer is that if programmers screw up and fail to close the context, // we have no way of warning them about the leak. //if (!stack.isEmpty()) // throw new IllegalStateException("You already have an initial Objectify context. Perhaps you want to use the ofy() method?"); final Objectify ofy = factory.begin(); stack.add(ofy); return new Closeable() { @Override public void close() { if (stack.isEmpty()) throw new IllegalStateException("You have already destroyed the Objectify context."); // Same comment as above - we can't make claims about the state of the stack beacuse of dispatch forwarding //if (stack.size() > 1) // throw new IllegalStateException("You are trying to close the root session before all transactions have been unwound."); // The order of these three operations is significant ofy.flush(); PendingFutures.completeAllPendingFutures(); stack.removeLast(); } }; } /** Pushes new context onto stack when a transaction starts. For internal housekeeping only. */ public static void push(Objectify ofy) { STACK.get().add(ofy); } /** Pops context off of stack after a transaction completes. For internal housekeeping only. */ public static void pop() { STACK.get().removeLast(); } }