/* * Copyright 2005 Werner Guttmann * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.exolab.castor.jdo.engine; import java.sql.Connection; import javax.transaction.Status; import javax.transaction.Synchronization; import javax.transaction.SystemException; import javax.transaction.Transaction; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.castor.core.util.Messages; import org.castor.persist.GlobalTransactionContext; import org.exolab.castor.jdo.DatabaseNotFoundException; import org.exolab.castor.jdo.OQLQuery; import org.exolab.castor.jdo.PersistenceException; import org.exolab.castor.jdo.TransactionAbortedException; import org.exolab.castor.jdo.TransactionNotInProgressException; import org.exolab.castor.persist.spi.CallbackInterceptor; import org.exolab.castor.persist.spi.InstanceFactory; /** * An implementation of the JDO database supporting explicit transaction * demarcation. * * @author <a href="werner DOT guttmann AT gmx DOT net">Werner Guttmann</a> * @version $Revision$ $Date: 2006-04-10 16:39:24 -0600 (Mon, 10 Apr 2006) $ */ public class GlobalDatabaseImpl extends AbstractDatabaseImpl implements Synchronization { /** The <a href="http://jakarta.apache.org/commons/logging/">Jakarta * Commons Logging</a> instance used for all logging. */ private static Log _log = LogFactory.getFactory().getInstance(GlobalDatabaseImpl.class); /** The XA transaction to which this Database instance is attached. */ private Transaction _transaction; /** The transaction to database map for database pooling. */ private TxDatabaseMap _txMap; /** Flag to indicate whether Database instances should be cached on a per transaction base. */ private boolean _isPoolInUseForGlobalTransactions = false; /** * Creates an instance of this class. * * @param dbName Database name. * @param lockTimeout Lock timeout. * @param callback Callback interceptors. * @param instanceFactory Instance factory to use. * @param transaction Current XA transaction. * @param classLoader Current class loader. * @param autoStore True if auto-storing is enabled. * @param isPoolInUseForGlobalTransactions True if Database instanced should be cached. * @throws DatabaseNotFoundException If the specified database cannot be found. */ public GlobalDatabaseImpl(final String dbName, final int lockTimeout, final CallbackInterceptor callback, final InstanceFactory instanceFactory, final Transaction transaction, final ClassLoader classLoader, final boolean autoStore, final boolean isPoolInUseForGlobalTransactions) throws DatabaseNotFoundException { super (dbName, lockTimeout, callback, instanceFactory, classLoader, autoStore); _isPoolInUseForGlobalTransactions = isPoolInUseForGlobalTransactions; _transaction = transaction; try { _ctx = new GlobalTransactionContext(this); _ctx.setStatus(transaction.getStatus()); } catch (javax.transaction.SystemException se) { throw new DatabaseNotFoundException(se); } _ctx.setLockTimeout(_lockTimeout); _ctx.setAutoStore(_autoStore); _ctx.setCallback(_callback); _ctx.setInstanceFactory(_instanceFactory); _classLoader = classLoader; } /** * @inheritDoc * @see org.exolab.castor.jdo.Database#close() */ public synchronized void close() throws PersistenceException { try { try { _ctx.close(); } catch (Exception e) { throw new PersistenceException(e.getMessage(), e); } } finally { _scope = null; } } /** * Overrides Object.finalize(). * * Outputs a warning message to the logs if the current DatabaseImpl * instance still has valid scope. In this condition - a condition that * ideally should not occur at all - we close the instance as well to * free up resources. * * @see java.lang.Object#finalize() */ protected void finalize() throws Throwable { if (_scope != null || !isActive()) { return; } if (!_isPoolInUseForGlobalTransactions) { // retrieve SQL bound to this Database instance OQLQuery oqlQuery = getOQLQuery(); String sql = ((OQLQueryImpl) oqlQuery).getSQL(); _log.warn(Messages.format("jdo.finalize_close", this.toString(), _dbName, sql)); } close(); } /** * @inheritDoc */ public void begin() throws PersistenceException { throw new IllegalStateException(Messages.message("jdo.txInJ2EE")); } /** * @inheritDoc */ public void commit() throws TransactionNotInProgressException, TransactionAbortedException { throw new IllegalStateException(Messages.message("jdo.txInJ2EE")); } /** * @inheritDoc */ public void rollback() throws TransactionNotInProgressException { throw new IllegalStateException(Messages.message("jdo.txInJ2EE")); } /** * @inheritDoc */ public void beforeCompletion() { // XXX [SMH]: Find another test for txNotInProgress if (_transaction == null || _ctx == null || !_ctx.isOpen()) { throw new IllegalStateException(Messages.message("jdo.txNotInProgress")); } registerSynchronizables(); if (_ctx.getStatus() == Status.STATUS_MARKED_ROLLBACK) { try { _transaction.setRollbackOnly(); } catch (SystemException except) { _log.warn(Messages.format("jdo.warnException", except)); } return; } try { _ctx.prepare(); } catch (TransactionAbortedException tae) { _log.error(Messages.format("jdo.txAbortedMarkRollback", tae.getMessage()), tae); try { _transaction.setRollbackOnly(); } catch (SystemException se) { _log.fatal(Messages.format("jdo.txMarkRollbackFailure", se.getMessage()), se); } _ctx.rollback(); } } /** * @inheritDoc * @see javax.transaction.Synchronization#afterCompletion(int) */ public void afterCompletion(final int status) { try { // XXX [SMH]: Find another test for txNotInProgress if (_transaction == null || _ctx == null) { throw new IllegalStateException(Messages.message("jdo.txNotInProgress")); } if (_ctx.getStatus() == Status.STATUS_ROLLEDBACK) { return; } if (_ctx.getStatus() != Status.STATUS_PREPARED && status != Status.STATUS_ROLLEDBACK) { throw new IllegalStateException( "Unexpected state: afterCompletion called at status " + _ctx.getStatus()); } switch (status) { case Status.STATUS_COMMITTED: try { _ctx.commit(); } catch (TransactionAbortedException except) { _log.fatal(Messages.format("jdo.fatalException", except)); _ctx.rollback(); } return; case Status.STATUS_ROLLEDBACK: _ctx.rollback(); return; default: _ctx.rollback(); throw new IllegalStateException( "Unexpected state: afterCompletion called with status " + status); } } finally { unregisterSynchronizables(); if (_txMap != null && _transaction != null) { _txMap.remove(_transaction); _txMap = null; } } } void setTxMap(final TxDatabaseMap txMap) { _txMap = txMap; } /** * @inheritDoc */ public Connection getJdbcConnection() throws PersistenceException { return _ctx.getConnection(_scope.getLockEngine()); } }