package com.metrink.croquet.hibernate; import java.io.Serializable; import javax.annotation.Nullable; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.spi.PersistenceUnitTransactionType; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import org.hibernate.jpa.HibernateEntityManagerFactory; import org.hibernate.jpa.internal.EntityManagerFactoryImpl; import org.hibernate.service.ServiceRegistry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; import com.google.inject.name.Named; import com.google.inject.persist.PersistService; import com.google.inject.persist.UnitOfWork; import com.metrink.croquet.AbstractSettings; /** * A PersistService, UnitOfWork, and Provider<EntityManager> implementation that configures Hibernate. * * The persist.xml file is NOT used to configure Hibernate. Everything is driven through the DatabaseSettings class. */ @Singleton class CroquetPersistService implements Provider<EntityManager>, UnitOfWork, PersistService { private static final Logger LOG = LoggerFactory.getLogger(CroquetPersistService.class); private static final String TRUE_STRING = "true"; private static final String FALSE_STRING = "false"; //CHECKSTYLE:OFF stupid stuff private final AbstractSettings settings; //CHECKSTYLE:ON private final String persistenceUnitName; private final ConnectionProvider connectionProvider; private final ThreadLocal<EntityManager> entityManager = new ThreadLocal<EntityManager>(); private volatile EntityManagerFactory entityManagerFactory; /** * Constructs the {@link CroquetPersistService}. * @param settings the settings to configure everything with. * @param connectionProvider the {@link ConnectionProvider} to use. */ @Inject public CroquetPersistService(final AbstractSettings settings, @Nullable @Named("jpa-unit-name") final String persistenceUnitName, final ConnectionProvider connectionProvider) { this.settings = settings; this.persistenceUnitName = persistenceUnitName; this.connectionProvider = connectionProvider; } public String getPersistenceUnitName() { return persistenceUnitName; } /** * Returns the current {@link EntityManagerFactory}. * @return {@link EntityManagerFactory}. */ protected EntityManagerFactory getEntityManagerFactory() { return entityManagerFactory; } /** * Sets the {@link EntityManagerFactory}. * @param entityManagerFactory the {@link EntityManagerFactory} to set. */ protected void setEntityManagerFactory(final EntityManagerFactory entityManagerFactory) { this.entityManagerFactory = entityManagerFactory; } /* * This is called when init() in the Servlet filter is called. * @see com.google.inject.persist.PersistService#start() */ @Override public void start() { if(null != entityManagerFactory) { throw new IllegalStateException("Persistence service was already initialized."); } final Configuration configuration = new Configuration(); // // We'll want to map these to settings // configuration.setProperty(AvailableSettings.CURRENT_SESSION_CONTEXT_CLASS, "managed"); configuration.setProperty(AvailableSettings.USE_GET_GENERATED_KEYS, TRUE_STRING); configuration.setProperty(AvailableSettings.USE_REFLECTION_OPTIMIZER, TRUE_STRING); configuration.setProperty(AvailableSettings.ORDER_UPDATES, TRUE_STRING); configuration.setProperty(AvailableSettings.ORDER_INSERTS, TRUE_STRING); configuration.setProperty(AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, TRUE_STRING); configuration.setProperty("jadira.usertype.autoRegisterUserTypes", TRUE_STRING); configuration.setProperty(AvailableSettings.DIALECT, settings.getDatabaseSettings().getDialectClass()); // turn on and off features based upon log level if(LOG.isDebugEnabled()) { configuration.setProperty(AvailableSettings.GENERATE_STATISTICS, TRUE_STRING); configuration.setProperty(AvailableSettings.SHOW_SQL, TRUE_STRING); } else { configuration.setProperty(AvailableSettings.GENERATE_STATISTICS, FALSE_STRING); configuration.setProperty(AvailableSettings.SHOW_SQL, FALSE_STRING); } // add in all the entities for (final Class<? extends Serializable> entity : settings.getDatabaseSettings().getEntities()) { LOG.debug("Adding entity: {}", entity.getCanonicalName()); configuration.addAnnotatedClass(entity); } final ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder() .applySettings(configuration.getProperties()) // set our connection provider .addService(ConnectionProvider.class, connectionProvider) .build(); // create the actual entity manager this.entityManagerFactory = new EntityManagerFactoryImpl(PersistenceUnitTransactionType.RESOURCE_LOCAL, false, null, configuration, serviceRegistry, persistenceUnitName); } /* * This is called when destroy in the Servlet filter is called. * @see com.google.inject.persist.PersistService#stop() */ @Override public void stop() { if(!entityManagerFactory.isOpen()) { throw new IllegalStateException("Persistence service was already shut down."); } entityManagerFactory.close(); } /* * This is called as the first thing in doFilter method in the Servlet filter. * @see com.google.inject.persist.UnitOfWork#begin() */ @Override public void begin() { if(null != entityManager.get()) { throw new IllegalStateException("Work already begun on this thread. " + "Looks like you have called UnitOfWork.begin() twice without a balancing call to end() in between."); } entityManager.set(EntityManagerProxyFactory.createProxy((HibernateEntityManagerFactory)entityManagerFactory)); } /* * This is called as the last thing in the doFilter method in the Servlet filter. * @see com.google.inject.persist.UnitOfWork#end() */ @Override public void end() { final EntityManager em = entityManager.get(); // Let's not penalize users for calling end() multiple times. if (null == em) { return; } final EntityTransaction tx = em.getTransaction(); if(tx.isActive()) { LOG.warn("There was an active transaction at the end of a request"); tx.commit(); } em.close(); entityManager.remove(); } @Override public EntityManager get() { // check to see if our ThreadLocal has already been set if(entityManager.get() == null) { begin(); // if it hasn't then we call begin() to set it } final EntityManager em = entityManager.get(); if(null == em) { throw new IllegalStateException("Requested EntityManager outside work unit. " + "Try calling UnitOfWork.begin() first, or use a PersistFilter if you " + "are inside a servlet environment."); } return em; } /** * A wrapper class around the CroquetPersistService because we cannot implement Provider twice. */ @Singleton public static class EntityManagerFactoryProvider implements Provider<EntityManagerFactory> { private final CroquetPersistService croquetPersistService; /** * Wraps the {@link CroquetPersistService} to act as a provider. * @param croquetPersistService the {@link CroquetPersistService} to wrap. */ @Inject public EntityManagerFactoryProvider(final CroquetPersistService croquetPersistService) { this.croquetPersistService = croquetPersistService; } @Override public EntityManagerFactory get() { return croquetPersistService.entityManagerFactory; } } }