/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Middleware LLC.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*
*/
package org.hibernate.transaction;
import java.util.Properties;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.transaction.SystemException;
import javax.transaction.UserTransaction;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.ConnectionReleaseMode;
import org.hibernate.HibernateException;
import org.hibernate.Transaction;
import org.hibernate.TransactionException;
import org.hibernate.jdbc.JDBCContext;
import org.hibernate.cfg.Environment;
import org.hibernate.util.NamingHelper;
import org.hibernate.util.JTAHelper;
/**
* Factory for {@link JTATransaction} instances.
* <p/>
* To be completely accurate to the JTA spec, JTA implementations should
* publish their contextual {@link UserTransaction} reference into JNDI.
* However, in practice there are quite a few <tt>stand-alone</tt>
* implementations intended for use outside of J2EE/JEE containers and
* which therefore do not publish their {@link UserTransaction} references
* into JNDI but which otherwise follow the aspects of the JTA specification.
* This {@link TransactionFactory} implementation can support both models.
* <p/>
* For complete JTA implementations (including dependence on JNDI), the
* {@link UserTransaction} reference is obtained by a call to
* {@link #resolveInitialContext}. Hibernate will then attempt to locate the
* {@link UserTransaction} within this resolved
* {@link InitialContext} based on the namespace returned by
* {@link #resolveUserTransactionName}.
* <p/>
* For the so-called <tt>stand-alone</tt> implementations, we do not care at
* all about the JNDI aspects just described. Here, the implementation would
* have a specific manner to obtain a reference to its contextual
* {@link UserTransaction}; usually this would be a static code reference, but
* again it varies. Anyway, for each implementation the integration would need
* to override the {@link #getUserTransaction} method and return the appropriate
* thing.
*
* @author Gavin King
* @author Steve Ebersole
* @author Les Hazlewood
*/
public class JTATransactionFactory implements TransactionFactory {
public static final String DEFAULT_USER_TRANSACTION_NAME = "java:comp/UserTransaction";
private static final Log log = LogFactory.getLog(JTATransactionFactory.class);
protected InitialContext initialContext;
protected String userTransactionName;
/**
* Configure this transaction factory. Specifically here we are attempting to
* resolve both an {@link #getInitialContext InitialContext} as well as the
* {@link #getUserTransactionName() JNDI namespace} for the {@link UserTransaction}.
*
* @param props The configuration properties
*
* @exception HibernateException
*/
public void configure(Properties props) throws HibernateException {
this.initialContext = resolveInitialContext( props );
this.userTransactionName = resolveUserTransactionName( props );
log.trace( "Configured JTATransactionFactory to use [" + userTransactionName + "] for UserTransaction JDNI namespace" );
}
/**
* Given the lot of Hibernate configuration properties, resolve appropriate
* reference to JNDI {@link InitialContext}.
* <p/>
* In general, the properties in which we are interested here all begin with
* <tt>hibernate.jndi</tt>. Especially important depending on your
* environment are {@link Environment#JNDI_URL hibernate.jndi.url} and
* {@link Environment#JNDI_CLASS hibernate.jndi.class}
*
* @param properties The Hibernate config properties.
* @return The resolved InitialContext.
*/
protected final InitialContext resolveInitialContext(Properties properties) {
try {
return NamingHelper.getInitialContext( properties );
}
catch ( NamingException ne ) {
throw new HibernateException( "Could not obtain initial context", ne );
}
}
/**
* Given the lot of Hibernate configuration properties, resolve appropriate
* JNDI namespace to use for {@link UserTransaction} resolution.
* <p/>
* We determine the namespace to use by<ol>
* <li>Any specified {@link Environment#USER_TRANSACTION jta.UserTransaction} config property</li>
* <li>If a {@link TransactionManagerLookup} was indicated, use its
* {@link TransactionManagerLookup#getUserTransactionName}</li>
* <li>finally, as a last resort, we use {@link #DEFAULT_USER_TRANSACTION_NAME}</li>
* </ol>
*
* @param properties The Hibernate config properties.
* @return The resolved {@link UserTransaction} namespace
*/
protected final String resolveUserTransactionName(Properties properties) {
String utName = properties.getProperty( Environment.USER_TRANSACTION );
if ( utName == null ) {
TransactionManagerLookup lookup = TransactionManagerLookupFactory.getTransactionManagerLookup( properties );
if ( lookup != null ) {
utName = lookup.getUserTransactionName();
}
}
return utName == null ? DEFAULT_USER_TRANSACTION_NAME : utName;
}
/**
* {@inheritDoc}
*/
public Transaction createTransaction(JDBCContext jdbcContext, Context transactionContext)
throws HibernateException {
UserTransaction ut = getUserTransaction();
return new JTATransaction( ut, jdbcContext, transactionContext );
}
/**
* Get the {@link UserTransaction} reference.
*
* @return The appropriate {@link UserTransaction} reference.
*/
protected UserTransaction getUserTransaction() {
final String utName = getUserTransactionName();
log.trace( "Attempting to locate UserTransaction via JNDI [" + utName + "]" );
try {
UserTransaction ut = ( UserTransaction ) getInitialContext().lookup( utName );
if ( ut == null ) {
throw new TransactionException( "Naming service lookup for UserTransaction returned null [" + utName +"]" );
}
log.trace( "Obtained UserTransaction" );
return ut;
}
catch ( NamingException ne ) {
throw new TransactionException( "Could not find UserTransaction in JNDI [" + utName + "]", ne );
}
}
/**
* Getter for property 'initialContext'.
*
* @return Value for property 'initialContext'.
*/
protected InitialContext getInitialContext() {
return initialContext;
}
/**
* Getter for property 'userTransactionName'.
* The algorithm here is
*
* @return Value for property 'userTransactionName'.
*/
protected String getUserTransactionName() {
return userTransactionName;
}
/**
* {@inheritDoc}
*/
public ConnectionReleaseMode getDefaultReleaseMode() {
return ConnectionReleaseMode.AFTER_STATEMENT;
}
/**
* {@inheritDoc}
*/
public boolean isTransactionManagerRequired() {
return false;
}
/**
* {@inheritDoc}
*/
public boolean areCallbacksLocalToHibernateTransactions() {
return false;
}
/**
* {@inheritDoc}
*/
public boolean isTransactionInProgress(
JDBCContext jdbcContext,
Context transactionContext,
Transaction transaction) {
try {
// Essentially:
// 1) If we have a local (Hibernate) transaction in progress
// and it already has the UserTransaction cached, use that
// UserTransaction to determine the status.
// 2) If a transaction manager has been located, use
// that transaction manager to determine the status.
// 3) Finally, as the last resort, try to lookup the
// UserTransaction via JNDI and use that to determine the
// status.
if ( transaction != null ) {
UserTransaction ut = ( ( JTATransaction ) transaction ).getUserTransaction();
if ( ut != null ) {
return JTAHelper.isInProgress( ut.getStatus() );
}
}
if ( jdbcContext.getFactory().getTransactionManager() != null ) {
return JTAHelper.isInProgress( jdbcContext.getFactory().getTransactionManager().getStatus() );
}
else {
UserTransaction ut = getUserTransaction();
return ut != null && JTAHelper.isInProgress( ut.getStatus() );
}
}
catch ( SystemException se ) {
throw new TransactionException( "Unable to check transaction status", se );
}
}
}
|