/*
* (C) Copyright 2006-2016 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Stephane Lacoin
* Florent Guillaume
*/
package org.nuxeo.ecm.core.persistence;
import java.io.IOException;
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.Transaction;
import javax.transaction.TransactionManager;
import org.hibernate.HibernateException;
import org.hibernate.cfg.Environment;
import org.hibernate.ejb.Ejb3Configuration;
import org.hibernate.ejb.HibernatePersistence;
import org.hibernate.ejb.transaction.JoinableCMTTransactionFactory;
import org.hibernate.transaction.JDBCTransactionFactory;
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.DataSourceHelper;
import org.nuxeo.runtime.jtajca.NamingContextFactory;
import org.nuxeo.runtime.jtajca.NuxeoContainer;
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";
@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)) {
properties.put(Environment.TRANSACTION_STRATEGY, JoinableCMTTransactionFactory.class.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);
props.put(Environment.JNDI_CLASS, NamingContextFactory.class.getName());
props.put(Environment.JNDI_PREFIX.concat(".").concat(javax.naming.Context.URL_PKG_PREFIXES),
NuxeoContainer.class.getPackage().getName());
}
return createEntityManagerFactory(properties);
}
// 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) {
Transaction tx = TransactionHelper.suspendTransaction();
try {
return cfg.createEntityManagerFactory(properties);
} finally {
TransactionHelper.resumeTransaction(tx);;
}
}
/**
* 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 (IOException 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);
}
}