/**********************************************************************
Copyright (c) 2007 Erik Bengtson and others. All rights reserved.
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.
Contributors:
2007 Andy Jefferson - javadocs, formatted, copyrighted
2007 Andy Jefferson - added lock/unlock/hasConnection/hasLockedConnection and enlisting
...
**********************************************************************/
package org.jpox;
import java.util.HashMap;
import java.util.Map;
import javax.transaction.xa.XAResource;
import org.jpox.exceptions.JPOXUserException;
import org.jpox.management.ManagementServer;
import org.jpox.management.runtime.ConnectionManagerRuntime;
import org.jpox.transaction.Transaction;
import org.jpox.util.ClassUtils;
import org.jpox.util.JPOXLogger;
import org.jpox.util.Localiser;
/**
* Manager of connections for an OMF, allowing ManagedConnection pooling, enlistment in transaction.
* The pool caches one connection per ObjectManager.
* The "allocate" method can create connections and enlist them (like most normal persistence operations need)
* or create a connection and return it.
*
* @version $Revision: 1.17 $
*/
public class ConnectionManagerImpl implements ConnectionManager
{
/** Localisation of messages. */
protected static final Localiser LOCALISER=Localiser.getInstance("org.jpox.Localisation",
ObjectManagerFactoryImpl.class.getClassLoader());
/** OMFContext for this connection manager. */
OMFContext omfContext;
ManagedConnectionPool connectionPool = new ManagedConnectionPool();
/** Connection Runtime. Used when providing management of services. */
ConnectionManagerRuntime connMgrRuntime = null;
/**
* Constructor.
* @param omfContext OMFContext for this manager.
*/
public ConnectionManagerImpl(OMFContext omfContext)
{
this.omfContext = omfContext;
if (omfContext.getManagement() != null)
{
// register MBean in MbeanServer
ManagementServer mgmtServer = this.omfContext.getManagement().getManagementServer();
connMgrRuntime = new ConnectionManagerRuntime();
String mbeanName = omfContext.getDomainName() + ":InstanceName=" + omfContext.getInstanceName() +
",Type=" + ClassUtils.getClassNameForClass(connMgrRuntime.getClass()) +
",Name=ConnectionManagerRuntime";
mgmtServer.registerMBean(connMgrRuntime, mbeanName);
}
}
/**
* Pool of managed connections for an ObjectManager.
* Each ObjectManager has its own pool of ManagedConnection's
*/
class ManagedConnectionPool
{
/**
* Connection pool keeps a reference to a connection for each ObjectManager (and so the connection
* used by each transaction).
* This permits reuse of connections in the same transaction, but not at same time!!!
* ManagedConnection must be released to return to pool.
* On transaction commit/rollback, connection pool is cleared
*
* For each combination of ObjectManager-ConnectionFactory there is 0 or 1 ManagedConnection:
* Map<ObjectManager, Map<ConnectionFactory,ManagedConnection>>
*/
Map connectionsPool = new HashMap();
/**
* Remove from pool
* @param factory The factory is the nested key
* @param om The om is the key for the ManagedConnection
*/
public void removeManagedConnection(ConnectionFactory factory, ObjectManager om)
{
synchronized (connectionsPool)
{
Map connectionsForOM = (Map) connectionsPool.get(om);
if (connectionsForOM != null)
{
if (connectionsForOM.remove(factory) != null && connMgrRuntime != null)
{
connMgrRuntime.decrementActiveConnections();
}
if (connectionsForOM.size() == 0)
{
// No connections remaining for this OM so remove the entry for the ObjectManager
connectionsPool.remove(om);
}
}
}
}
/**
* Object a ManagedConnection from pool
* @param factory
* @param om
* @return
*/
public ManagedConnection getManagedConnection(ConnectionFactory factory, ObjectManager om)
{
synchronized (connectionsPool)
{
Map connectionsForOM = (Map) connectionsPool.get(om);
if (connectionsForOM == null)
{
return null;
}
//obtain a ManagedConnection for an specific ConnectionFactory
ManagedConnection mconn = (ManagedConnection) connectionsForOM.get(factory);
if (mconn != null)
{
if (mconn.isLocked())
{
// Enlisted connection that is locked so throw exception
throw new JPOXUserException(LOCALISER.msg("009000"));
}
// Already registered enlisted connection present so return it
return mconn;
}
}
return null;
}
public void putManagedConnection(ConnectionFactory factory, ObjectManager om, ManagedConnection mconn)
{
synchronized (connectionsPool)
{
Map connectionsForOM = (Map) connectionsPool.get(om);
if (connectionsForOM == null)
{
connectionsForOM = new HashMap();
connectionsPool.put(om, connectionsForOM);
}
if (connectionsForOM.put(factory, mconn) == null && connMgrRuntime != null)
{
connMgrRuntime.incrementActiveConnections();
}
}
}
}
/**
* Method to return a connection for this ObjectManager.
* If a connection for the ObjectManager exists in the cache will return it.
* If no connection exists will create a new one using the ConnectionFactory.
* @param factory ConnectionFactory it relates to
* @param om The ObjectManager
* @param options Options for the connection (e.g isolation). These will override those of the txn itself
* @return The ManagedConnection
*/
public ManagedConnection allocateConnection(final ConnectionFactory factory, final ObjectManager om, Map options)
{
if (om != null)
{
ManagedConnection mconnFromPool = connectionPool.getManagedConnection(factory, om);
if (mconnFromPool != null)
{
if (JPOXLogger.CONNECTION.isDebugEnabled())
{
JPOXLogger.CONNECTION.debug("Connection found in the pool : "+mconnFromPool);
}
// Already registered enlisted connection present so return it
return mconnFromPool;
}
}
// Create new connection
Map txOptions = options;
if (options == null && om != null)
{
txOptions = om.getTransaction().getOptions();
}
ManagedConnection mconn = factory.createManagedConnection(om, txOptions);
configureManagedConnectionListener(om, mconn, factory);
// Enlist the connection in this transaction
if (om != null )
{
if( om.getTransaction().isActive())
{
configureTransactionEventListener(om,mconn);
Transaction tx = omfContext.getTransactionManager().getTransaction(om);
//must be set before getting the XAResource
mconn.setManagedResource();
enlistResource(mconn, tx, options);
}
// Register this connection against the ObjectManager - connection is valid
if (JPOXLogger.CONNECTION.isDebugEnabled())
{
JPOXLogger.CONNECTION.debug("Connection added to the pool : "+mconn);
}
connectionPool.putManagedConnection(factory, om, mconn);
}
return mconn;
}
private void configureManagedConnectionListener(final ObjectManager om, final ManagedConnection mconn, final ConnectionFactory factory)
{
mconn.addListener(new ManagedConnectionResourceListener()
{
public void managedConnectionPostClose()
{
if (om != null)
{
if (JPOXLogger.CONNECTION.isDebugEnabled())
{
JPOXLogger.CONNECTION.debug("Connection removed from the pool : "+mconn);
}
connectionPool.removeManagedConnection(factory, om); // Connection closed so remove
}
}
public void managedConnectionPreClose() {}
public void managedConnectionFlushed() {}
public void resourcePostClose() {}
});
}
/**
* Configure a TransactionEventListener that closes the managed connection when a
* transaction commits or rolls back
* @param om
* @param mconn
*/
private void configureTransactionEventListener(final ObjectManager om, final ManagedConnection mconn)
{
// Add handler for any enlisted connection to the transaction so we know when to close it
om.getTransaction().addTransactionEventListener(
new TransactionEventListener()
{
public void transactionStarted() {}
public void transactionRolledBack()
{
try
{
mconn.close();
}
finally
{
om.getTransaction().removeTransactionEventListener(this);
}
}
public void transactionCommitted()
{
try
{
mconn.close();
}
finally
{
om.getTransaction().removeTransactionEventListener(this);
}
}
public void transactionEnded()
{
try
{
mconn.close();
}
finally
{
om.getTransaction().removeTransactionEventListener(this);
}
}
public void transactionPreCommit()
{
if (mconn.isLocked())
{
// Enlisted connection that is locked so throw exception
throw new JPOXUserException(LOCALISER.msg("009000"));
}
}
public void transactionPreRollBack()
{
if (mconn.isLocked())
{
// Enlisted connection that is locked so throw exception
throw new JPOXUserException(LOCALISER.msg("009000"));
}
}
public void transactionFlushed()
{
mconn.flush();
}
});
}
/**
* Enlist the mconn in the transaction if using JPOX's transaction manager
* @param mconn
* @param tx
* @param options
*/
private void enlistResource(ManagedConnection mconn, Transaction tx, Map options)
{
XAResource res = mconn.getXAResource();
if (res != null)
{
// Enlist the connection resource if has enlistable resource
boolean enlistInLocalTM = true;
if (options != null && options.get("resource-type") != null &&
ResourceType.JTA.toString().equalsIgnoreCase((String)options.get("resource-type")))
{
//XAResource will be enlisted in an external JTA container,
//so we dont enlist it in the internal Transaction Manager
enlistInLocalTM = false;
}
if (enlistInLocalTM)
{
tx.enlistResource(res);
}
}
}
}