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

com.oneandone.ejbcdiunit.persistence.PersistenceFactory Maven / Gradle / Ivy

Go to download

A module that can be used together with cdiunit to build en ejb-test-environment.

The newest version!
package com.oneandone.ejbcdiunit.persistence;

import com.oneandone.ejbcdiunit.SupportEjbExtended;
import org.apache.commons.dbcp2.BasicDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.ejb.TransactionAttributeType;
import javax.enterprise.context.ApplicationScoped;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.TransactionRequiredException;
import javax.sql.DataSource;
import javax.transaction.Status;
import javax.transaction.SystemException;
import java.util.HashSet;
import java.util.Map;
import java.util.Stack;
import java.util.concurrent.ConcurrentLinkedQueue;


/**
 * The PersistenceFactory provides {@link EntityManager}s for tests.
 * Additionally this PersistenceFactory allows to initiate Transactions according to @see TransactionAttributeType
 * so that normal CDI-Test-Environments can simulate EJB-Transaction-Handling.
 *
 * @author aschoerkfalse)
 */
@SupportEjbExtended
@ApplicationScoped
public abstract class PersistenceFactory {

    private static final HashSet PERSISTENCE_UNIT_NAMES = new HashSet<>();
    private final Logger logger = LoggerFactory.getLogger(PersistenceFactory.class);
    private final ThreadLocal> emStackThreadLocal = new ThreadLocal<>();
    private EntityManagerFactory emf = null;
    private SimulatedTransactionManager transactionManager = new SimulatedTransactionManager();
    private ConcurrentLinkedQueue> threadlocalStacks = new ConcurrentLinkedQueue<>();

    /**
     * allow to reset between Tests.
     */
    public static void clearPersistenceUnitNames() {
        PERSISTENCE_UNIT_NAMES.clear();
    }

    protected abstract String getPersistenceUnitName();

    private ThreadLocal> getEmStackThreadLocal() {
        return emStackThreadLocal;
    }

    private EntityManagerFactory getEmf() {
        return emf;
    }

    private void setEmf(EntityManagerFactory emfP) {
        this.emf = emfP;
    }

    /**
     * prepare EntityManagerFactory
     */
    @PostConstruct
    public void construct() {
        logger.info("creating persistence factory {}", getPersistenceUnitName());
        synchronized (PERSISTENCE_UNIT_NAMES) {
            if (PERSISTENCE_UNIT_NAMES.contains(getPersistenceUnitName())) {
                throw new RuntimeException("Repeated construction of currently existing PersistenceFactory for " + getPersistenceUnitName());
            } else {
                setEmf(createEntityManagerFactory());
                PERSISTENCE_UNIT_NAMES.add(getPersistenceUnitName());
            }
        }
    }


    /**
     * make sure all connections will be closed
     */
    @PreDestroy
    public void destroy() {
        logger.info("destroying persistence factory {}", getPersistenceUnitName());
        synchronized (PERSISTENCE_UNIT_NAMES) {
            if (!PERSISTENCE_UNIT_NAMES.contains(getPersistenceUnitName())) {
                throw new RuntimeException("Expected PersistenceFactory for " + getPersistenceUnitName());
            } else {
                for (Stack stack : threadlocalStacks) {
                    for (EntityManager em : stack) {
                        if (em.getTransaction().isActive()) {
                            try {
                                em.getTransaction().rollback();
                            } catch (Throwable thw) {
                                logger.error("Throwable during closing of pending transaction", thw);
                            }
                        }
                        em.close();
                    }
                    stack.clear();
                }
                if (getEmf() != null && getEmf().isOpen()) {
                    getEmf().close();
                }
                PERSISTENCE_UNIT_NAMES.remove(getPersistenceUnitName());
            }
        }
    }

    /**
     * Looks for the current entity manager and returns it. If no entity manager was found, this method logs a warn message and returns null. This
     * will cause a NullPointerException in most cases and will cause a stack trace starting from your service method.
     *
     * @return the currently used entity manager on top of stack. Don't use this in producers!
     * @param expectTransaction
     *            if no transaction is running throw transaction required exception
     */
    EntityManager getTransactional(boolean expectTransaction) {
        try {
            if (expectTransaction && transactionManager.getStatus() == Status.STATUS_NO_TRANSACTION)
                throw new TransactionRequiredException("Expected, but no transaction during Ejb-Simulation");
        } catch (SystemException e) {
            throw new RuntimeException(e);
        }
        transactionManager.takePart(this);
        EntityManager result = get();
        if (expectTransaction && !result.getTransaction().isActive()) {
            throw new TransactionRequiredException("Ejb-Simulation");
        }
        return result;
    }

    /**
     * Looks for the current entity manager and returns it. If no entity manager was found, this method logs a warn
     * message and returns null. This will cause a NullPointerException in most cases and will cause a stack trace
     * starting from your service method.
     *
     * @return the currently used entity manager on top of stack. Don't use this in producers!
     */
    EntityManager get() {
        final Stack entityManagerStack = getEmStackThreadLocal().get();
        if (entityManagerStack == null || entityManagerStack.isEmpty()) {
            return getEntityManager();
        }
        return getEmStackThreadLocal().get().peek();
    }

    EntityManager getEntityManager() {
        final Stack entityManagerStack = getEmStackThreadLocal().get();
        if (entityManagerStack == null || entityManagerStack.isEmpty()) {
            createAndRegister(); // throw new RuntimeException("Should never be null!!!");
        }
        return getEmStackThreadLocal().get().peek();
    }


    /**
     * Creates an entity manager and stores it in a stack. The use of a stack allows to implement transaction with a 'requires new' behaviour.
     *
     */
    void createAndRegister() {
        logger.trace("Creating and registering an entity manager for " + Thread.currentThread().getName());
        Stack entityManagerStack = getEmStackThreadLocal().get();
        if (entityManagerStack == null) {
            entityManagerStack = new Stack<>();
            getEmStackThreadLocal().set(entityManagerStack);
            threadlocalStacks.add(entityManagerStack);
        }

        final EntityManager entityManager = getEmf().createEntityManager();
        entityManagerStack.push(entityManager);
    }

    /**
     * Removes the current entity manager from the thread local stack.
     *
     * @throws IllegalStateException
     *             in case the entity manager was not found on the stack
     */
    void unRegister() {
        logger.trace("UnRegistering an entity manager");
        final Stack entityManagerStack = getEmStackThreadLocal().get();
        if (entityManagerStack == null || entityManagerStack.isEmpty()) {
            throw new IllegalStateException("Removing of entity manager failed. Your entity manager was not found.");
        }

        final EntityManager entityManager = entityManagerStack.pop();
        if (entityManager.getTransaction().isActive()) {
            // entityManager.getTransaction().commit();
            // logger.info("Dropping EntityManager with active Transaction");
            throw new IllegalStateException("Popping with active transaction");
        }
        entityManager.close();
    }

    /**
     * Create a transaction-object as Java-Resource. The current entityManager is accordingly handled
     * @param transactionAttribute allows to simulate the ejb-transaction-handling
     * @return a resource-object that handles the transaction in the block correctly.
     */
    public TestTransaction transaction(TransactionAttributeType transactionAttribute) {
        return new TestTransaction(transactionAttribute);
    }

    /**
     * Create a transaction-object as Java-Resource. The current entityManager is accordingly handled
     * @param transactionAttribute allows to simulate the ejb-transaction-handling
     * @param runnable the code to be done during the transaction.
     */
    public void transaction(TransactionAttributeType transactionAttribute, TestClosure runnable) {
        try (TestTransaction tra = transaction(transactionAttribute)) {
            try {
                runnable.execute();
            } catch (Throwable e) {
                throw new TestTransactionException(e);
            }
        } catch (Exception e) {
            throw new TestTransactionException(e);
        }
    }

    /**
     * returns EntityManager, to be injected and used so that the current threadSpecific context is correctly handled
     *
     * @return the EntityManager as it is returnable by producers.
     */
    public EntityManager produceEntityManager() {
        return new EntityManagerDelegate(this);
    }

    /**
     * create a jdbc-Datasource using the same driver url user and password as the entityManager
     *
     * @return a jdbc-Datasource using the same driver url user and password as the entityManager
     */
    public DataSource createDataSource() {
        Map props = emf.getProperties();
        DataSource emfDatasource = (DataSource) props.get("hibernate.connection.datasource");
        if (emfDatasource != null) {
            return emfDatasource;
        } else {
            BasicDataSource newDataSource = new BasicDataSource();
            newDataSource.setDriverClassName((String) props.get("javax.persistence.jdbc.driver"));
            newDataSource.setUrl((String) props.get("javax.persistence.jdbc.url"));
            return newDataSource;
        }
    }

    public DataSource produceDataSource() {
        return new DataSourceDelegate(this);
    }

    protected EntityManagerFactory createEntityManagerFactory() {
        return Persistence.createEntityManagerFactory(getPersistenceUnitName());
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }

        PersistenceFactory that = (PersistenceFactory) obj;

        return getPersistenceUnitName() != null ? getPersistenceUnitName().equals(that.getPersistenceUnitName())
                : that.getPersistenceUnitName() == null;
    }

    @Override
    public int hashCode() {
        return getPersistenceUnitName() != null ? getPersistenceUnitName().hashCode() : 0;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy