package au.com.vaadinutils.dao; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.concurrent.Callable; import javax.persistence.EntityManager; import javax.validation.ConstraintViolationException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import com.google.common.base.Preconditions; import au.com.vaadinutils.errorHandling.ErrorWindow; /** * The class is a place holder to allow access to an 'non-injected' entity * manager. * * Normally you need a EM for each thread and the EntityManagerProvider provides * a simple mechanism to inject an EM into a thread. * * NOTE: You need to initialise this Provider during application startup by * calling EntityManagerProvider.setEntityManagerFactory(). * * You shouldn't use this provider directly but rather use it via one of: * * EntityManagerCallable EntityManagerRunnable EntityManagerThread * * EntityManagerInjectorFilter AtmosphereFilter * * Each of these class correctly sets up the EM, starts a Transaction and * finally clears the threads EM once the thread is complete. * * If you want to use EntityManagerProvider directly (DON'T): * * Then for each thread you need to inject a thread local EntityManager by * calling setCurrentEntityManager and then clearing it when the thread shuts * down by calling setCurrentEntityManager with a null. You will also need wrap * the entity in a Transaction block before calling setCurrentEntityManager. * * e.g. * * @formatter:off EntityManager em = * EntityManagerProvider.createEntityManager(); Transaction t = * null; Transaction t = new Transaction(em); try { // Create and * set the entity manager * EntityManagerProvider.setCurrentEntityManager(em); // Handle * the request filterChain.doFilter(servletRequest, * servletResponse); * * t.commit(); } finally { if (t!= null) t.close(); // Reset the * entity manager * EntityManagerProvider.setCurrentEntityManager(null); } * * @formatter:on * * You can use the @Link * au.com.vaadinutils.filter.EntityManagerInjectorFilter and @Link * au.com.vaadinutils.filter.AtmosphereFilter to do the injection * or make up your own methods. * * @author bsutton * */ public enum EntityManagerProvider { INSTANCE; private static final Logger logger = LogManager.getLogger(); /** * provides a mechanism to register actions that should happen should happen * before a transaction is started and after it is committed. */ private static List<EMAction> registeredPreActions = new ArrayList<>(); private static List<Runnable> registeredPostActions = new ArrayList<>(); private ThreadLocal<EntityManager> entityManagerThreadLocal = new ThreadLocal<>(); private javax.persistence.EntityManagerFactory emf; /** * Get the entity manager attached to this thread. * * @return */ public static EntityManager getEntityManager() { return INSTANCE.entityManagerThreadLocal.get(); } /** * Set an entity manager for this thread. * * @param em */ public static void setCurrentEntityManager(EntityManager em) { EntityManager oldem = INSTANCE.entityManagerThreadLocal.get(); Preconditions.checkArgument(em == null || (oldem == null && em != null), "Can not replace the current entity manager, close it and set it to null first!!!!"); /** * Before you try to clear or replace the current entity manager you * should do something similar to: * * @formatter:off try { transaction.commit(); transaction.close(); * em.close(); } finally { * EntityManagerProvider.setCurrentEntityManager(null); } * @formatter:on * */ Preconditions.checkArgument(oldem == null || !oldem.isOpen(), "Current entity manager is still open, commit and close any transactions and then close the EntityManager first"); if (em == null) { logger.debug("Clearing entity manager for thread {}", Thread.currentThread().getId()); } else { logger.debug("Setting entity manager for thread {}", Thread.currentThread().getId()); if (INSTANCE.entityManagerThreadLocal.get() != null) { logger.error("Setting the entitymanager but the entityManager is already Set."); } } INSTANCE.entityManagerThreadLocal.set(em); if (em != null) { runPreActions(em); } else { runPostActions(); } } /** * Call this method to initialise the EntityManagerProvider so that it can * hand out EntityManagers to worker threads. Dont forget to close the * entitymanager This should normally be called from a servlet Context * Listener. * * @param emf */ public static void setEntityManagerFactory(javax.persistence.EntityManagerFactory emf) { INSTANCE.emf = emf; } /** * T return type from EntityWorker. * * @param worker * @return * @throws Exception */ public static <T> T setThreadLocalEntityManager(EntityWorker<T> worker) throws Exception { try (AutoCloseable closer = EntityManagerProvider.setThreadLocalEntityManagerTryWithResources()) { return worker.exec(); } } /** * provides the same functionality as * setThreadLocalEntityManager(EntityWorker w), without the need for an * anonymous inner class<br> * example usage:<br> * <br> * try(AutoCloseable closer = * EntityManagerProvider.setThreadLocalEntityManagerTryWithResources())<br> * {<br> * ...<br> * }<br> * * @return */ public static AutoCloseable setThreadLocalEntityManagerTryWithResources() { final EntityManager em; if (getEntityManager() == null) { em = createEntityManager(); setCurrentEntityManager(em); em.getTransaction().begin(); } else { // entityManager already existed, no need to create one or start a // transaction em = null; } return new AutoCloseable() { @Override public void close() throws Exception { if (em != null) { try { em.getTransaction().commit(); } catch (ConstraintViolationException e) { // ensure we get the cause of an underlying constraint // violation ErrorWindow.showErrorWindow(e); throw e; } finally { try { try { if (em.getTransaction().isActive()) { logger.error("Rolling back transaction"); em.getTransaction().rollback(); } } finally { if (em.isOpen()) { em.close(); } } } finally { setCurrentEntityManager(null); } } } } }; } /** * Allows you to pass a Runnable to wrap in an entity manager. A new * Runnable is returned which should then be called to run your runnable. * i.e. don't run you own runnable directly rather use the returned * Runnable. * * @param runnable * - the runnable to run as contains an entity manager. * @return */ public static Runnable setThreadLocalEntityManager(final Runnable runnable) { return new Runnable() { @Override public void run() { try (AutoCloseable closer = EntityManagerProvider.setThreadLocalEntityManagerTryWithResources()) { runnable.run(); } catch (Exception e) { ErrorWindow.showErrorWindow(e); } } }; } /** * Allows you to pass in a Callable to wrap in an entity manager. A new * Callable is returned which should then be called to run your Callable. * i.e. don't run you own Callable directly rather use the returned * Callable. * * @param Callable * - the Callable to run as contains an entity manager. * @return */ public static <T> Callable<T> setThreadLocalEntityManager(final Callable<T> callable) { return new Callable<T>() { @Override public T call() throws Exception { try (AutoCloseable closer = EntityManagerProvider.setThreadLocalEntityManagerTryWithResources()) { return callable.call(); } } }; } /** * If you have a worker thread then it won't have access to a thread local * entity manager (as they are injected by the servlet request filters * mentioned above. <br> * <br> * <b>For worker threads preferably use setThreadLocalEntityManager</b> <br> * <br> * Otherwise you need to call this method to get an entity manager. You will * also need to call close when done * * @return */ public static EntityManager createEntityManager() { if (INSTANCE.emf == null) { throw new IllegalStateException("Context is not initialized yet."); } // EntityManager entityManager = new // EntityManagerTrackerWrapper(INSTANCE.emf.createEntityManager()); EntityManager entityManager = INSTANCE.emf.createEntityManager(); return entityManager; // you might want to use this if your having deadlocks... // don't ever use JPAFactory to build your JPAContainers // return new EntityManagerWrapper(entityManager); } /** * convienece method * * @param entity */ public static <T> T merge(T entity) { return getEntityManager().merge(entity); } public static <T> void remove(T entity) { getEntityManager().remove(entity); } public static <T> void persist(T record) { getEntityManager().persist(record); } public static <T> void refresh(T record) { getEntityManager().refresh(record); } public static <T> void detach(T record) { getEntityManager().detach(record); } private static ThreadLocal<List<Runnable>> transientPostTransactionActions = new ThreadLocal<>(); /** * deprecated - use registerTransientPostAction(Runnable runnable) * * @param runnable */ @Deprecated public static void performAfterTransactionCompletes(Runnable runnable) { registerTransientPostAction(runnable); } /** * Adds a runnable to the list of Actions that will be performed after the * entity manager for this thread has been cleared. NOTE: as the EM has been * cleared the Runnable must NOT try any database operations as they will * fail. * * @param runnable * The Action to run when the em is cleared. */ public static void registerTransientPostAction(Runnable runnable) { Preconditions.checkNotNull(getEntityManager()); Preconditions.checkState(getEntityManager().isOpen()); List<Runnable> actionList = transientPostTransactionActions.get(); if (actionList == null) { actionList = new LinkedList<>(); transientPostTransactionActions.set(actionList); } actionList.add(runnable); } private static void runTransientPostActions() { List<Runnable> actions = transientPostTransactionActions.get(); // These actions are transient, so clear them out. transientPostTransactionActions.set(null); runRunnableActions(actions); } private static void runRunnableActions(List<Runnable> actions) { if (actions != null) { for (Runnable action : actions) { try { action.run(); } catch (Throwable e) { logger.error(e, e); } } } } /** * this is useful when you need to run code in a separate transaction and * thread after the current transaction commits */ public static void registerTransientPostActionOnNewThread(final Runnable runnable) { registerTransientPostAction(new Runnable() { @Override public void run() { new Thread(runnable).start(); } }); } /** * Register an action that will be executed before begin is called on the * transaction. Registered actions are global and will be run across all * threads every time an entity manager is set via void * setCurrentEntityManager(EntityManager em) * * @param action * The action to run before begin is called on a transaction. */ public static void registerPreAction(EMAction action) { synchronized (registeredPreActions) { registeredPreActions.add(action); } } /** * Register an action that will be executed after commit is called on the * transaction. * * Registered actions are global and will be run across all threads every * time an entity manager is set via a call to * * @formatter:off void setCurrentEntityManager(EntityManager em) * @formatter:on * * @param action * The action to run before begin is called on a transaction. */ public static void registerPostAction(Runnable action) { synchronized (registeredPostActions) { registeredPostActions.add(action); } } public static void runPreActions(EntityManager em) { synchronized (registeredPreActions) { runActions(registeredPreActions, em); } } public static void runPostActions() { runTransientPostActions(); synchronized (registeredPostActions) { runRunnableActions(registeredPostActions); } } public static void runActions(List<EMAction> actions, EntityManager em) { if (actions != null) { for (EMAction action : actions) { try { action.run(em); } catch (Throwable e) { logger.error(e, e); } } } } static public abstract class EMAction { abstract public void run(EntityManager em); } /** * Commits the current active transaction and starts a new one */ public static void commitAndContinue() { INSTANCE.entityManagerThreadLocal.get().getTransaction().commit(); INSTANCE.entityManagerThreadLocal.get().getTransaction().begin(); } }