/*
* (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);
}
}