001/*
002 * (C) Copyright 2006-2016 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Stephane Lacoin
018 *     Florent Guillaume
019 */
020package org.nuxeo.ecm.core.persistence;
021
022import java.io.IOException;
023import java.net.URL;
024import java.util.ArrayList;
025import java.util.Collections;
026import java.util.HashMap;
027import java.util.List;
028import java.util.Map;
029import java.util.Properties;
030
031import javax.naming.NamingException;
032import javax.persistence.EntityManagerFactory;
033import javax.persistence.spi.PersistenceUnitTransactionType;
034import javax.transaction.Transaction;
035import javax.transaction.TransactionManager;
036
037import org.hibernate.HibernateException;
038import org.hibernate.cfg.Environment;
039import org.hibernate.ejb.Ejb3Configuration;
040import org.hibernate.ejb.HibernatePersistence;
041import org.hibernate.ejb.transaction.JoinableCMTTransactionFactory;
042import org.hibernate.transaction.JDBCTransactionFactory;
043import org.hibernate.transaction.TransactionManagerLookup;
044import org.nuxeo.common.xmap.XMap;
045import org.nuxeo.common.xmap.annotation.XNode;
046import org.nuxeo.common.xmap.annotation.XNodeList;
047import org.nuxeo.common.xmap.annotation.XNodeMap;
048import org.nuxeo.common.xmap.annotation.XObject;
049import org.nuxeo.runtime.api.Framework;
050import org.nuxeo.runtime.datasource.DataSourceHelper;
051import org.nuxeo.runtime.jtajca.NamingContextFactory;
052import org.nuxeo.runtime.jtajca.NuxeoContainer;
053import org.nuxeo.runtime.transaction.TransactionHelper;
054
055/**
056 */
057@XObject("hibernateConfiguration")
058public class HibernateConfiguration implements EntityManagerFactoryProvider {
059
060    public static final String RESOURCE_LOCAL = PersistenceUnitTransactionType.RESOURCE_LOCAL.name();
061
062    public static final String JTA = PersistenceUnitTransactionType.JTA.name();
063
064    public static final String TXTYPE_PROPERTY_NAME = "org.nuxeo.runtime.txType";
065
066    @XNode("@name")
067    public String name;
068
069    @XNode("datasource")
070    public void setDatasource(String name) {
071        String expandedValue = Framework.expandVars(name);
072        if (expandedValue.startsWith("$")) {
073            throw new PersistenceError("Cannot expand " + name + " for datasource");
074        }
075        hibernateProperties.put("hibernate.connection.datasource", DataSourceHelper.getDataSourceJNDIName(name));
076    }
077
078    @XNodeMap(value = "properties/property", key = "@name", type = Properties.class, componentType = String.class)
079    public final Properties hibernateProperties = new Properties();
080
081    @XNodeList(value = "classes/class", type = ArrayList.class, componentType = Class.class)
082    public final List<Class<?>> annotedClasses = new ArrayList<Class<?>>();
083
084    public void addAnnotedClass(Class<?> annotedClass) {
085        annotedClasses.add(annotedClass);
086    }
087
088    public void removeAnnotedClass(Class<?> annotedClass) {
089        annotedClasses.remove(annotedClass);
090    }
091
092    protected Ejb3Configuration cfg;
093
094    public Ejb3Configuration setupConfiguration() {
095        return setupConfiguration(null);
096    }
097
098    public Ejb3Configuration setupConfiguration(Map<String, String> properties) {
099        cfg = new Ejb3Configuration();
100
101        if (properties != null) {
102            cfg.configure(name, properties);
103        } else {
104            cfg.configure(name, Collections.emptyMap());
105        }
106
107        // Load hibernate properties
108        cfg.addProperties(hibernateProperties);
109
110        // Add annnoted classes if any
111        for (Class<?> annotedClass : annotedClasses) {
112            cfg.addAnnotatedClass(annotedClass);
113        }
114
115        return cfg;
116    }
117
118    @Override
119    public EntityManagerFactory getFactory(String txType) {
120        Map<String, String> properties = new HashMap<String, String>();
121        if (txType == null) {
122            txType = getTxType();
123        }
124        properties.put(HibernatePersistence.TRANSACTION_TYPE, txType);
125        if (txType.equals(JTA)) {
126            properties.put(Environment.TRANSACTION_STRATEGY, JoinableCMTTransactionFactory.class.getName());
127            properties.put(Environment.TRANSACTION_MANAGER_STRATEGY, NuxeoTransactionManagerLookup.class.getName());
128        } else if (txType.equals(RESOURCE_LOCAL)) {
129            properties.put(Environment.TRANSACTION_STRATEGY, JDBCTransactionFactory.class.getName());
130        }
131        if (cfg == null) {
132            setupConfiguration(properties);
133        }
134        Properties props = cfg.getProperties();
135        if (props.get(Environment.URL) == null) {
136            // don't set up our connection provider for unit tests
137            // that use an explicit driver + connection URL and so use
138            // a DriverManagerConnectionProvider
139            props.put(Environment.CONNECTION_PROVIDER, NuxeoConnectionProvider.class.getName());
140        }
141        if (txType.equals(RESOURCE_LOCAL)) {
142            props.remove(Environment.DATASOURCE);
143        } else {
144            String dsname = props.getProperty(Environment.DATASOURCE);
145            dsname = DataSourceHelper.getDataSourceJNDIName(dsname);
146            props.put(Environment.DATASOURCE, dsname);
147            props.put(Environment.JNDI_CLASS, NamingContextFactory.class.getName());
148            props.put(Environment.JNDI_PREFIX.concat(".").concat(javax.naming.Context.URL_PKG_PREFIXES),
149                    NuxeoContainer.class.getPackage().getName());
150        }
151        return createEntityManagerFactory(properties);
152    }
153
154    // this must be executed always outside a transaction
155    // because SchemaUpdate tries to setAutoCommit(true)
156    // so we use a new thread
157    protected EntityManagerFactory createEntityManagerFactory(final Map<String, String> properties) {
158        Transaction tx = TransactionHelper.suspendTransaction();
159        try {
160            return cfg.createEntityManagerFactory(properties);
161        } finally {
162                TransactionHelper.resumeTransaction(tx);;
163        }
164    }
165
166    /**
167     * Hibernate Transaction Manager Lookup that uses our framework.
168     */
169    public static class NuxeoTransactionManagerLookup implements TransactionManagerLookup {
170        public NuxeoTransactionManagerLookup() {
171            // look up UserTransaction once to know its JNDI name
172            try {
173                TransactionHelper.lookupUserTransaction();
174            } catch (NamingException e) {
175                // ignore
176            }
177        }
178
179        @Override
180        public TransactionManager getTransactionManager(Properties props) {
181            try {
182                return TransactionHelper.lookupTransactionManager();
183            } catch (NamingException e) {
184                throw new HibernateException(e.getMessage(), e);
185            }
186        }
187
188        @Override
189        public String getUserTransactionName() {
190            return TransactionHelper.getUserTransactionJNDIName();
191        }
192
193        @Override
194        public Object getTransactionIdentifier(Transaction transaction) {
195            return transaction;
196        }
197    }
198
199    @Override
200    public EntityManagerFactory getFactory() {
201        return getFactory(null);
202    }
203
204    public static String getTxType() {
205        String txType;
206        if (Framework.isInitialized()) {
207            txType = Framework.getProperty(TXTYPE_PROPERTY_NAME);
208            if (txType == null) {
209                try {
210                    TransactionHelper.lookupTransactionManager();
211                    txType = JTA;
212                } catch (NamingException e) {
213                    txType = RESOURCE_LOCAL;
214                }
215            }
216        } else {
217            txType = RESOURCE_LOCAL;
218        }
219        return txType;
220    }
221
222    public static HibernateConfiguration load(URL location) {
223        XMap map = new XMap();
224        map.register(HibernateConfiguration.class);
225        try {
226            return (HibernateConfiguration) map.load(location);
227        } catch (IOException e) {
228            throw new PersistenceError("Cannot load hibernate configuration from " + location, e);
229        }
230    }
231
232    public void merge(HibernateConfiguration other) {
233        assert name.equals(other.name) : " cannot merge configuration that do not have the same persistence unit";
234        annotedClasses.addAll(other.annotedClasses);
235        hibernateProperties.clear();
236        hibernateProperties.putAll(other.hibernateProperties);
237    }
238
239}