org.nuxeo.ecm.core.persistence.HibernateConfiguration.java Source code

Java tutorial

Introduction

Here is the source code for org.nuxeo.ecm.core.persistence.HibernateConfiguration.java

Source

/*
 * (C) Copyright 2006-2013 Nuxeo SA (http://nuxeo.com/) and contributors.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser General Public License
 * (LGPL) version 2.1 which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl.html
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * Contributors:
 *     Stephane Lacoin
 *     Florent Guillaume
 */
package org.nuxeo.ecm.core.persistence;

import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.naming.NamingException;
import javax.persistence.EntityManagerFactory;
import javax.persistence.spi.PersistenceUnitTransactionType;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.ConnectionReleaseMode;
import org.hibernate.HibernateException;
import org.hibernate.cfg.Environment;
import org.hibernate.ejb.Ejb3Configuration;
import org.hibernate.ejb.HibernatePersistence;
import org.hibernate.ejb.transaction.JoinableCMTTransaction;
import org.hibernate.ejb.transaction.JoinableCMTTransactionFactory;
import org.hibernate.jdbc.JDBCContext;
import org.hibernate.transaction.JDBCTransactionFactory;
import org.hibernate.transaction.TransactionFactory;
import org.hibernate.transaction.TransactionManagerLookup;
import org.nuxeo.common.xmap.XMap;
import org.nuxeo.common.xmap.annotation.XNode;
import org.nuxeo.common.xmap.annotation.XNodeList;
import org.nuxeo.common.xmap.annotation.XNodeMap;
import org.nuxeo.common.xmap.annotation.XObject;
import org.nuxeo.runtime.api.Framework;
import org.nuxeo.runtime.datasource.ConnectionHelper;
import org.nuxeo.runtime.datasource.DataSourceHelper;
import org.nuxeo.runtime.transaction.TransactionHelper;

/**
 */
@XObject("hibernateConfiguration")
public class HibernateConfiguration implements EntityManagerFactoryProvider {

    public static final String RESOURCE_LOCAL = PersistenceUnitTransactionType.RESOURCE_LOCAL.name();

    public static final String JTA = PersistenceUnitTransactionType.JTA.name();

    public static final String TXTYPE_PROPERTY_NAME = "org.nuxeo.runtime.txType";

    private static final Log log = LogFactory.getLog(HibernateConfiguration.class);

    @XNode("@name")
    public String name;

    @XNode("datasource")
    public void setDatasource(String name) {
        String expandedValue = Framework.expandVars(name);
        if (expandedValue.startsWith("$")) {
            throw new PersistenceError("Cannot expand " + name + " for datasource");
        }
        hibernateProperties.put("hibernate.connection.datasource", DataSourceHelper.getDataSourceJNDIName(name));
    }

    @XNodeMap(value = "properties/property", key = "@name", type = Properties.class, componentType = String.class)
    public final Properties hibernateProperties = new Properties();

    @XNodeList(value = "classes/class", type = ArrayList.class, componentType = Class.class)
    public final List<Class<?>> annotedClasses = new ArrayList<Class<?>>();

    public void addAnnotedClass(Class<?> annotedClass) {
        annotedClasses.add(annotedClass);
    }

    public void removeAnnotedClass(Class<?> annotedClass) {
        annotedClasses.remove(annotedClass);
    }

    protected Ejb3Configuration cfg;

    public Ejb3Configuration setupConfiguration() {
        return setupConfiguration(null);
    }

    public Ejb3Configuration setupConfiguration(Map<String, String> properties) {
        cfg = new Ejb3Configuration();

        if (properties != null) {
            cfg.configure(name, properties);
        } else {
            cfg.configure(name, Collections.emptyMap());
        }

        // Load hibernate properties
        cfg.addProperties(hibernateProperties);

        // Add annnoted classes if any
        for (Class<?> annotedClass : annotedClasses) {
            cfg.addAnnotatedClass(annotedClass);
        }

        return cfg;
    }

    @Override
    public EntityManagerFactory getFactory(String txType) {
        Map<String, String> properties = new HashMap<String, String>();
        if (txType == null) {
            txType = getTxType();
        }
        properties.put(HibernatePersistence.TRANSACTION_TYPE, txType);
        if (txType.equals(JTA)) {
            Class<?> transactionFactoryClass;
            if (ConnectionHelper.useSingleConnection(null)) {
                transactionFactoryClass = NuxeoTransactionFactory.class;
            } else {
                transactionFactoryClass = JoinableCMTTransactionFactory.class;
            }
            properties.put(Environment.TRANSACTION_STRATEGY, transactionFactoryClass.getName());
            properties.put(Environment.TRANSACTION_MANAGER_STRATEGY, NuxeoTransactionManagerLookup.class.getName());
        } else if (txType.equals(RESOURCE_LOCAL)) {
            properties.put(Environment.TRANSACTION_STRATEGY, JDBCTransactionFactory.class.getName());
        }
        if (cfg == null) {
            setupConfiguration(properties);
        }
        Properties props = cfg.getProperties();
        if (props.get(Environment.URL) == null) {
            // don't set up our connection provider for unit tests
            // that use an explicit driver + connection URL and so use
            // a DriverManagerConnectionProvider
            props.put(Environment.CONNECTION_PROVIDER, NuxeoConnectionProvider.class.getName());
        }
        if (txType.equals(RESOURCE_LOCAL)) {
            props.remove(Environment.DATASOURCE);
        } else {
            String dsname = props.getProperty(Environment.DATASOURCE);
            dsname = DataSourceHelper.getDataSourceJNDIName(dsname);
            props.put(Environment.DATASOURCE, dsname);
        }
        return createEntityManagerFactory(properties);
    }

    /**
     * Don't close the connection aggressively after each statement.
     */
    // needs to be public as hibernate calls newInstance
    public static class NuxeoTransactionFactory extends JoinableCMTTransactionFactory {
        @Override
        public ConnectionReleaseMode getDefaultReleaseMode() {
            return ConnectionReleaseMode.AFTER_TRANSACTION;
        }

        @Override
        public org.hibernate.Transaction createTransaction(JDBCContext jdbcContext,
                TransactionFactory.Context transactionContext) throws HibernateException {
            return new NuxeoHibernateTransaction(jdbcContext, transactionContext);
        }
    }

    /**
     * Hibernate transaction that will register a synchronization that runs
     * before the one from ConnectionHelper in single-datasource mode.
     * <p>
     * Needed because the sync from org.hibernate.ejb.EntityManagerImpl#close
     * must run before the one from ConnectionHelper.
     */
    public static class NuxeoHibernateTransaction extends JoinableCMTTransaction {
        public NuxeoHibernateTransaction(JDBCContext jdbcContext, TransactionFactory.Context transactionContext) {
            super(jdbcContext, transactionContext);
        }

        @Override
        public void registerSynchronization(Synchronization sync) throws HibernateException {
            boolean registered;
            try {
                registered = ConnectionHelper.registerSynchronization(sync);
            } catch (SystemException e) {
                throw new HibernateException(e);
            }
            if (!registered) {
                super.registerSynchronization(sync);
            }
        }
    }

    // this must be executed always outside a transaction
    // because SchemaUpdate tries to setAutoCommit(true)
    // so we use a new thread
    protected EntityManagerFactory createEntityManagerFactory(final Map<String, String> properties) {
        final EntityManagerFactory[] emf = new EntityManagerFactory[1];
        Thread t = new Thread("persistence-init-" + name) {
            @SuppressWarnings("deprecation")
            @Override
            public void run() {
                emf[0] = cfg.createEntityManagerFactory(properties);
            };
        };
        try {
            t.start();
            t.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return emf[0];
    }

    /**
     * Hibernate Transaction Manager Lookup that uses our framework.
     */
    public static class NuxeoTransactionManagerLookup implements TransactionManagerLookup {
        public NuxeoTransactionManagerLookup() {
            // look up UserTransaction once to know its JNDI name
            try {
                TransactionHelper.lookupUserTransaction();
            } catch (NamingException e) {
                // ignore
            }
        }

        @Override
        public TransactionManager getTransactionManager(Properties props) {
            try {
                return TransactionHelper.lookupTransactionManager();
            } catch (NamingException e) {
                throw new HibernateException(e.getMessage(), e);
            }
        }

        @Override
        public String getUserTransactionName() {
            return TransactionHelper.getUserTransactionJNDIName();
        }

        @Override
        public Object getTransactionIdentifier(Transaction transaction) {
            return transaction;
        }
    }

    @Override
    public EntityManagerFactory getFactory() {
        return getFactory(null);
    }

    public static String getTxType() {
        String txType;
        if (Framework.isInitialized()) {
            txType = Framework.getProperty(TXTYPE_PROPERTY_NAME);
            if (txType == null) {
                try {
                    TransactionHelper.lookupTransactionManager();
                    txType = JTA;
                } catch (NamingException e) {
                    txType = RESOURCE_LOCAL;
                }
            }
        } else {
            txType = RESOURCE_LOCAL;
        }
        return txType;
    }

    public static HibernateConfiguration load(URL location) {
        XMap map = new XMap();
        map.register(HibernateConfiguration.class);
        try {
            return (HibernateConfiguration) map.load(location);
        } catch (Exception e) {
            throw new PersistenceError("Cannot load hibernate configuration from " + location, e);
        }
    }

    public void merge(HibernateConfiguration other) {
        assert name.equals(other.name) : " cannot merge configuration that do not have the same persistence unit";
        annotedClasses.addAll(other.annotedClasses);
        hibernateProperties.clear();
        hibernateProperties.putAll(other.hibernateProperties);
    }

}