001/* 002 * (C) Copyright 2006-2013 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Stephane Lacoin 016 * Florent Guillaume 017 */ 018package org.nuxeo.ecm.core.persistence; 019 020import java.io.IOException; 021import java.net.URL; 022import java.util.ArrayList; 023import java.util.Collections; 024import java.util.HashMap; 025import java.util.List; 026import java.util.Map; 027import java.util.Properties; 028 029import javax.naming.NamingException; 030import javax.persistence.EntityManagerFactory; 031import javax.persistence.spi.PersistenceUnitTransactionType; 032import javax.transaction.Synchronization; 033import javax.transaction.SystemException; 034import javax.transaction.Transaction; 035import javax.transaction.TransactionManager; 036 037import org.hibernate.ConnectionReleaseMode; 038import org.hibernate.HibernateException; 039import org.hibernate.cfg.Environment; 040import org.hibernate.ejb.Ejb3Configuration; 041import org.hibernate.ejb.HibernatePersistence; 042import org.hibernate.ejb.transaction.JoinableCMTTransaction; 043import org.hibernate.ejb.transaction.JoinableCMTTransactionFactory; 044import org.hibernate.jdbc.JDBCContext; 045import org.hibernate.transaction.JDBCTransactionFactory; 046import org.hibernate.transaction.TransactionFactory; 047import org.hibernate.transaction.TransactionManagerLookup; 048import org.nuxeo.common.xmap.XMap; 049import org.nuxeo.common.xmap.annotation.XNode; 050import org.nuxeo.common.xmap.annotation.XNodeList; 051import org.nuxeo.common.xmap.annotation.XNodeMap; 052import org.nuxeo.common.xmap.annotation.XObject; 053import org.nuxeo.runtime.api.Framework; 054import org.nuxeo.runtime.datasource.ConnectionHelper; 055import org.nuxeo.runtime.datasource.DataSourceHelper; 056import org.nuxeo.runtime.jtajca.NamingContextFactory; 057import org.nuxeo.runtime.jtajca.NuxeoContainer; 058import org.nuxeo.runtime.transaction.TransactionHelper; 059 060/** 061 */ 062@XObject("hibernateConfiguration") 063public class HibernateConfiguration implements EntityManagerFactoryProvider { 064 065 public static final String RESOURCE_LOCAL = PersistenceUnitTransactionType.RESOURCE_LOCAL.name(); 066 067 public static final String JTA = PersistenceUnitTransactionType.JTA.name(); 068 069 public static final String TXTYPE_PROPERTY_NAME = "org.nuxeo.runtime.txType"; 070 071 @XNode("@name") 072 public String name; 073 074 @XNode("datasource") 075 public void setDatasource(String name) { 076 String expandedValue = Framework.expandVars(name); 077 if (expandedValue.startsWith("$")) { 078 throw new PersistenceError("Cannot expand " + name + " for datasource"); 079 } 080 hibernateProperties.put("hibernate.connection.datasource", DataSourceHelper.getDataSourceJNDIName(name)); 081 } 082 083 @XNodeMap(value = "properties/property", key = "@name", type = Properties.class, componentType = String.class) 084 public final Properties hibernateProperties = new Properties(); 085 086 @XNodeList(value = "classes/class", type = ArrayList.class, componentType = Class.class) 087 public final List<Class<?>> annotedClasses = new ArrayList<Class<?>>(); 088 089 public void addAnnotedClass(Class<?> annotedClass) { 090 annotedClasses.add(annotedClass); 091 } 092 093 public void removeAnnotedClass(Class<?> annotedClass) { 094 annotedClasses.remove(annotedClass); 095 } 096 097 protected Ejb3Configuration cfg; 098 099 public Ejb3Configuration setupConfiguration() { 100 return setupConfiguration(null); 101 } 102 103 public Ejb3Configuration setupConfiguration(Map<String, String> properties) { 104 cfg = new Ejb3Configuration(); 105 106 if (properties != null) { 107 cfg.configure(name, properties); 108 } else { 109 cfg.configure(name, Collections.emptyMap()); 110 } 111 112 // Load hibernate properties 113 cfg.addProperties(hibernateProperties); 114 115 // Add annnoted classes if any 116 for (Class<?> annotedClass : annotedClasses) { 117 cfg.addAnnotatedClass(annotedClass); 118 } 119 120 return cfg; 121 } 122 123 @Override 124 public EntityManagerFactory getFactory(String txType) { 125 Map<String, String> properties = new HashMap<String, String>(); 126 if (txType == null) { 127 txType = getTxType(); 128 } 129 properties.put(HibernatePersistence.TRANSACTION_TYPE, txType); 130 if (txType.equals(JTA)) { 131 Class<?> transactionFactoryClass; 132 if (ConnectionHelper.useSingleConnection(null)) { 133 transactionFactoryClass = NuxeoTransactionFactory.class; 134 } else { 135 transactionFactoryClass = JoinableCMTTransactionFactory.class; 136 } 137 properties.put(Environment.TRANSACTION_STRATEGY, transactionFactoryClass.getName()); 138 properties.put(Environment.TRANSACTION_MANAGER_STRATEGY, NuxeoTransactionManagerLookup.class.getName()); 139 } else if (txType.equals(RESOURCE_LOCAL)) { 140 properties.put(Environment.TRANSACTION_STRATEGY, JDBCTransactionFactory.class.getName()); 141 } 142 if (cfg == null) { 143 setupConfiguration(properties); 144 } 145 Properties props = cfg.getProperties(); 146 if (props.get(Environment.URL) == null) { 147 // don't set up our connection provider for unit tests 148 // that use an explicit driver + connection URL and so use 149 // a DriverManagerConnectionProvider 150 props.put(Environment.CONNECTION_PROVIDER, NuxeoConnectionProvider.class.getName()); 151 } 152 if (txType.equals(RESOURCE_LOCAL)) { 153 props.remove(Environment.DATASOURCE); 154 } else { 155 String dsname = props.getProperty(Environment.DATASOURCE); 156 dsname = DataSourceHelper.getDataSourceJNDIName(dsname); 157 props.put(Environment.DATASOURCE, dsname); 158 props.put(Environment.JNDI_CLASS, NamingContextFactory.class.getName()); 159 props.put(Environment.JNDI_PREFIX.concat(".").concat(javax.naming.Context.URL_PKG_PREFIXES), 160 NuxeoContainer.class.getPackage().getName()); 161 } 162 return createEntityManagerFactory(properties); 163 } 164 165 /** 166 * Don't close the connection aggressively after each statement. 167 */ 168 // needs to be public as hibernate calls newInstance 169 public static class NuxeoTransactionFactory extends JoinableCMTTransactionFactory { 170 @Override 171 public ConnectionReleaseMode getDefaultReleaseMode() { 172 return ConnectionReleaseMode.AFTER_TRANSACTION; 173 } 174 175 @Override 176 public org.hibernate.Transaction createTransaction(JDBCContext jdbcContext, 177 TransactionFactory.Context transactionContext) throws HibernateException { 178 return new NuxeoHibernateTransaction(jdbcContext, transactionContext); 179 } 180 } 181 182 /** 183 * Hibernate transaction that will register a synchronization that runs before the one from ConnectionHelper in 184 * single-datasource mode. 185 * <p> 186 * Needed because the sync from org.hibernate.ejb.EntityManagerImpl#close must run before the one from 187 * ConnectionHelper. 188 */ 189 public static class NuxeoHibernateTransaction extends JoinableCMTTransaction { 190 public NuxeoHibernateTransaction(JDBCContext jdbcContext, TransactionFactory.Context transactionContext) { 191 super(jdbcContext, transactionContext); 192 } 193 194 @Override 195 public void registerSynchronization(Synchronization sync) throws HibernateException { 196 boolean registered; 197 try { 198 registered = ConnectionHelper.registerSynchronization(sync); 199 } catch (SystemException e) { 200 throw new HibernateException(e); 201 } 202 if (!registered) { 203 super.registerSynchronization(sync); 204 } 205 } 206 } 207 208 // this must be executed always outside a transaction 209 // because SchemaUpdate tries to setAutoCommit(true) 210 // so we use a new thread 211 protected EntityManagerFactory createEntityManagerFactory(final Map<String, String> properties) { 212 final EntityManagerFactory[] emf = new EntityManagerFactory[1]; 213 Thread t = new Thread("persistence-init-" + name) { 214 @SuppressWarnings("deprecation") 215 @Override 216 public void run() { 217 emf[0] = cfg.createEntityManagerFactory(properties); 218 }; 219 }; 220 try { 221 t.start(); 222 t.join(); 223 } catch (InterruptedException e) { 224 throw new RuntimeException(e); 225 } 226 return emf[0]; 227 } 228 229 /** 230 * Hibernate Transaction Manager Lookup that uses our framework. 231 */ 232 public static class NuxeoTransactionManagerLookup implements TransactionManagerLookup { 233 public NuxeoTransactionManagerLookup() { 234 // look up UserTransaction once to know its JNDI name 235 try { 236 TransactionHelper.lookupUserTransaction(); 237 } catch (NamingException e) { 238 // ignore 239 } 240 } 241 242 @Override 243 public TransactionManager getTransactionManager(Properties props) { 244 try { 245 return TransactionHelper.lookupTransactionManager(); 246 } catch (NamingException e) { 247 throw new HibernateException(e.getMessage(), e); 248 } 249 } 250 251 @Override 252 public String getUserTransactionName() { 253 return TransactionHelper.getUserTransactionJNDIName(); 254 } 255 256 @Override 257 public Object getTransactionIdentifier(Transaction transaction) { 258 return transaction; 259 } 260 } 261 262 @Override 263 public EntityManagerFactory getFactory() { 264 return getFactory(null); 265 } 266 267 public static String getTxType() { 268 String txType; 269 if (Framework.isInitialized()) { 270 txType = Framework.getProperty(TXTYPE_PROPERTY_NAME); 271 if (txType == null) { 272 try { 273 TransactionHelper.lookupTransactionManager(); 274 txType = JTA; 275 } catch (NamingException e) { 276 txType = RESOURCE_LOCAL; 277 } 278 } 279 } else { 280 txType = RESOURCE_LOCAL; 281 } 282 return txType; 283 } 284 285 public static HibernateConfiguration load(URL location) { 286 XMap map = new XMap(); 287 map.register(HibernateConfiguration.class); 288 try { 289 return (HibernateConfiguration) map.load(location); 290 } catch (IOException e) { 291 throw new PersistenceError("Cannot load hibernate configuration from " + location, e); 292 } 293 } 294 295 public void merge(HibernateConfiguration other) { 296 assert name.equals(other.name) : " cannot merge configuration that do not have the same persistence unit"; 297 annotedClasses.addAll(other.annotedClasses); 298 hibernateProperties.clear(); 299 hibernateProperties.putAll(other.hibernateProperties); 300 } 301 302}