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}