package org.apache.ojb.broker.accesslayer;
/* Copyright 2003-2004 The Apache Software Foundation
*
* 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.
*/
import org.apache.ojb.broker.PBKey;
import org.apache.ojb.broker.PersistenceBroker;
import org.apache.ojb.broker.PersistenceBrokerException;
import org.apache.ojb.broker.TransactionAbortedException;
import org.apache.ojb.broker.TransactionInProgressException;
import org.apache.ojb.broker.TransactionNotInProgressException;
import org.apache.ojb.broker.OJBRuntimeException;
import org.apache.ojb.broker.metadata.JdbcConnectionDescriptor;
import org.apache.ojb.broker.metadata.MetadataManager;
import org.apache.ojb.broker.platforms.Platform;
import org.apache.ojb.broker.platforms.PlatformFactory;
import org.apache.ojb.broker.util.batch.BatchConnection;
import org.apache.ojb.broker.util.logging.Logger;
import org.apache.ojb.broker.util.logging.LoggerFactory;
import java.sql.Connection;
import java.sql.SQLException;
/**
* Manages Connection ressources.
*
* @see ConnectionManagerIF
* @author Thomas Mahler
* @version $Id: ConnectionManagerImpl.java,v 1.17 2004/06/19 22:58:53 arminw Exp $
*/
public class ConnectionManagerImpl implements ConnectionManagerIF
{
private Logger log = LoggerFactory.getLogger(ConnectionManagerImpl.class);
private PersistenceBroker broker = null;
private ConnectionFactory connectionFactory;
private JdbcConnectionDescriptor jcd;
private Platform platform;
private Connection con = null;
private PBKey pbKey;
private boolean originalAutoCommitState;
private boolean isInLocalTransaction;
private boolean batchMode;
private BatchConnection batchCon = null;
public ConnectionManagerImpl(PersistenceBroker broker)
{
this.broker = broker;
this.pbKey = broker.getPBKey();
this.jcd = MetadataManager.getInstance().connectionRepository().getDescriptor(pbKey);
this.connectionFactory = ConnectionFactoryFactory.getInstance().createConnectionFactory();
this.platform = PlatformFactory.getPlatformFor(jcd);
/*
by default batch mode is not enabled and after use of a PB
instance, before instance was returned to pool, batch mode
was set to false again (PB implementation close method)
Be carefully in modify this behaviour, changes could cause
unexpected behaviour
*/
setBatchMode(false);
}
/**
* Returns the associated {@link org.apache.ojb.broker.metadata.JdbcConnectionDescriptor}
*/
public JdbcConnectionDescriptor getConnectionDescriptor()
{
return jcd;
}
public Platform getSupportedPlatform()
{
return this.platform;
}
/**
* Returns the underlying connection, requested at the
* {@link org.apache.ojb.broker.accesslayer.ConnectionFactory}.
* PB.beginTransaction() opens a single jdbc connection via
* serviceConnectionManager().localBegin().
* If you call ask serviceConnectionManager().getConnection() later
* it returns the already opened connection.
* the PB instance will close the connection during
* commitTransaction() or abortTransaction().
*/
public Connection getConnection() throws LookupException
{
// if (con == null || !isAlive(con))
// isAlive check, how do we react if false and we in tx?
// create a new connection isn't the right way I think.
if (con == null)
{
con = this.connectionFactory.lookupConnection(jcd);
if (con == null) throw new PersistenceBrokerException("Cannot get connection for " + jcd);
if (jcd.getUseAutoCommit() == JdbcConnectionDescriptor.AUTO_COMMIT_SET_TRUE_AND_TEMPORARY_FALSE)
{
try
{
this.originalAutoCommitState = con.getAutoCommit();
}
catch (SQLException e)
{
throw new PersistenceBrokerException("Cannot request autoCommit state on the connection", e);
}
}
if (log.isDebugEnabled()) log.debug("Request new connection from ConnectionFactory: " + con);
}
if (isBatchMode())
{
if (batchCon == null)
{
batchCon = new BatchConnection(con, broker);
}
return batchCon;
}
else
{
return con;
}
}
/**
* Start transaction on the underlying connection.
*/
public void localBegin()
{
if (this.isInLocalTransaction)
{
throw new TransactionInProgressException("Connection is already in transaction");
}
Connection connection = null;
try
{
connection = this.getConnection();
}
catch (LookupException e)
{
/**
* must throw to notify user that we couldn't start a connection
*/
throw new PersistenceBrokerException("Can't lookup a connection", e);
}
if (log.isDebugEnabled()) log.debug("localBegin was called for con " + connection);
if (jcd.getUseAutoCommit() == JdbcConnectionDescriptor.AUTO_COMMIT_SET_TRUE_AND_TEMPORARY_FALSE)
{
if (log.isDebugEnabled()) log.debug("Try to change autoCommit state to 'false'");
platform.changeAutoCommitState(jcd, connection, false);
}
this.isInLocalTransaction = true;
}
/**
* Call commit on the underlying connection.
*/
public void localCommit()
{
if (log.isDebugEnabled()) log.debug("commit was called");
if (!this.isInLocalTransaction)
{
throw new TransactionNotInProgressException("Not in transaction, call begin() before commit()");
}
try
{
if (batchCon != null)
{
batchCon.commit();
}
else if (con != null)
{
con.commit();
}
}
catch (SQLException e)
{
log.error("Commit on underlying connection failed, try to rollback connection", e);
this.localRollback();
throw new TransactionAbortedException("Commit on connection failed", e);
}
finally
{
this.isInLocalTransaction = false;
restoreAutoCommitState();
this.releaseConnection();
}
}
/**
* Call rollback on the underlying connection.
*/
public void localRollback()
{
log.info("Rollback was called, do rollback on current connection " + con);
if (!this.isInLocalTransaction)
{
throw new PersistenceBrokerException("Not in transaction, cannot abort");
}
try
{
//truncate the local transaction
this.isInLocalTransaction = false;
if (batchCon != null)
{
batchCon.rollback();
}
else if (con != null && !con.isClosed())
{
con.rollback();
}
}
catch (SQLException e)
{
log.error("Rollback on the underlying connection failed", e);
}
finally
{
try
{
restoreAutoCommitState();
}
catch(OJBRuntimeException ignore)
{
// Ignore or log exception
}
releaseConnection();
}
}
/**
* Reset autoCommit state.
*/
protected void restoreAutoCommitState()
{
try
{
if (jcd.getUseAutoCommit() == JdbcConnectionDescriptor.AUTO_COMMIT_SET_TRUE_AND_TEMPORARY_FALSE
&& originalAutoCommitState == true && con != null && !con.isClosed())
{
platform.changeAutoCommitState(jcd, con, true);
}
}
catch (SQLException e)
{
// should never be reached
throw new OJBRuntimeException("Restore of connection autocommit state failed", e);
}
}
/**
* Check if underlying connection was alive.
*/
public boolean isAlive(Connection conn)
{
try
{
return con != null ? !con.isClosed() : false;
}
catch (SQLException e)
{
log.error("IsAlive check failed, running connection was invalid!!", e);
return false;
}
}
public boolean isInLocalTransaction()
{
return this.isInLocalTransaction;
}
/**
* Release connection to the {@link org.apache.ojb.broker.accesslayer.ConnectionFactory}, make
* sure that you call the method in either case, it's the only way to free the connection.
*/
public void releaseConnection()
{
if (this.con == null)
{
//if (log.isDebugEnabled()) log.debug("No connection to release");
return;
}
if(isInLocalTransaction())
{
log.error("Release connection: connection is in local transaction, missing 'localCommit' or" +
" 'localRollback' call - try to rollback the connection");
localRollback();
}
else
{
this.connectionFactory.releaseConnection(this.jcd, this.con);
this.con = null;
this.batchCon = null;
}
}
/**
* Returns the underlying used {@link org.apache.ojb.broker.accesslayer.ConnectionFactory}
* implementation.
*/
public ConnectionFactory getUnderlyingConnectionFactory()
{
return connectionFactory;
}
/**
* Sets the batch mode on or off - this
* switch only works if you set attribute <code>batch-mode</code>
* in <code>jdbc-connection-descriptor</code> true and your database
* support batch mode.
*
* @param mode the batch mode
*/
public void setBatchMode(boolean mode)
{
/*
arminw:
if batch mode was set 'false' in repository,
never enable it.
There are many users having weird problems
when batch mode was enabled behind the scenes
*/
batchMode = mode && jcd.getBatchMode();
}
/**
* @return the batch mode.
*/
public boolean isBatchMode()
{
return batchMode && platform.supportsBatchOperations();
}
/**
* Execute batch (if the batch mode where used).
*/
public void executeBatch() throws OJBBatchUpdateException
{
if (batchCon != null)
{
try
{
batchCon.executeBatch();
}
catch (Throwable th)
{
throw new OJBBatchUpdateException(th);
}
}
}
/**
* Execute batch if the number of statements in it
* exceeded the limit (if the batch mode where used).
*/
public void executeBatchIfNecessary() throws OJBBatchUpdateException
{
if (batchCon != null)
{
try
{
batchCon.executeBatchIfNecessary();
}
catch (Throwable th)
{
throw new OJBBatchUpdateException(th);
}
}
}
/**
* Clear batch (if the batch mode where used).
*/
public void clearBatch()
{
if (batchCon != null)
{
batchCon.clearBatch();
}
}
}