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