/*
* IronJacamar, a Java EE Connector Architecture implementation
* Copyright 2015, Red Hat Inc, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the Eclipse Public License 1.0 as
* published by the Free Software Foundation.
*
* This software 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 Eclipse
* Public License for more details.
*
* You should have received a copy of the Eclipse Public License
* along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.ironjacamar.core.connectionmanager.ccm;
import org.ironjacamar.core.CoreBundle;
import org.ironjacamar.core.CoreLogger;
import org.ironjacamar.core.api.connectionmanager.ccm.CachedConnectionManager;
import org.ironjacamar.core.connectionmanager.Credential;
import org.ironjacamar.core.spi.transaction.TransactionIntegration;
import org.ironjacamar.core.spi.transaction.TxUtils;
import org.ironjacamar.core.tracer.Tracer;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.resource.ResourceException;
import javax.resource.spi.TransactionSupport.TransactionSupportLevel;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import org.jboss.logging.Logger;
import org.jboss.logging.Messages;
/**
* CacheConnectionManager.
*
* @author <a href="mailto:jesper.pedersen@ironjacamar.org">Jesper Pedersen</a>
*/
public class CachedConnectionManagerImpl implements CachedConnectionManager
{
/** The logger */
private static CoreLogger log = Logger.getMessageLogger(CoreLogger.class,
CachedConnectionManager.class.getName());
/** The bundle */
private static CoreBundle bundle = Messages.getBundle(CoreBundle.class);
/** Synchronization key */
private static final String CLOSE_CONNECTION_SYNCHRONIZATION = "CLOSE_CONNECTION_SYNCHRONIZATION";
/** Debug */
private boolean debug;
/** Error */
private boolean error;
/** Ignore unknown connections */
private boolean ignoreConnections;
/** Transaction integration */
private TransactionIntegration transactionIntegration;
/** Thread contexts - stack based */
private ThreadLocal<LinkedList<Context>> threadContexts = new ThreadLocal<LinkedList<Context>>();
/** Connection stack traces */
private Map<Object, Throwable> connectionStackTraces = new WeakHashMap<Object, Throwable>();
/**
* Constructor
* @param transactionIntegration The transaction integration
*/
public CachedConnectionManagerImpl(TransactionIntegration transactionIntegration)
{
this.debug = false;
this.error = false;
this.ignoreConnections = false;
this.transactionIntegration = transactionIntegration;
}
/**
* {@inheritDoc}
*/
public boolean isDebug()
{
return debug;
}
/**
* {@inheritDoc}
*/
public void setDebug(boolean v)
{
debug = v;
}
/**
* {@inheritDoc}
*/
public boolean isError()
{
return error;
}
/**
* {@inheritDoc}
*/
public void setError(boolean v)
{
error = v;
}
/**
* {@inheritDoc}
*/
public boolean isIgnoreUnknownConnections()
{
return ignoreConnections;
}
/**
* {@inheritDoc}
*/
public void setIgnoreUnknownConnections(boolean v)
{
ignoreConnections = v;
}
/**
* {@inheritDoc}
*/
public void userTransactionStarted() throws SystemException
{
Context context = currentContext();
log.tracef("user tx started, context: %s", context);
if (context != null)
{
for (org.ironjacamar.core.connectionmanager.ConnectionManager cm : context.getConnectionManagers())
{
if (cm.getTransactionSupport() != TransactionSupportLevel.NoTransaction)
{
List<org.ironjacamar.core.connectionmanager.listener.ConnectionListener> cls =
context.getConnectionListeners(cm);
if (!cls.isEmpty())
{
Map<Credential, org.ironjacamar.core.connectionmanager.listener.ConnectionListener> enlistmentMap =
new HashMap<>();
List<org.ironjacamar.core.connectionmanager.listener.ConnectionListener> cleanup =
new ArrayList<>();
try
{
for (org.ironjacamar.core.connectionmanager.listener.ConnectionListener cl : cls)
{
if (enlistmentMap.get(cl.getCredential()) == null)
{
enlistmentMap.put(cl.getCredential(), cl);
}
else
{
// Merge
org.ironjacamar.core.connectionmanager.listener.ConnectionListener existing =
enlistmentMap.get(cl.getCredential());
for (Object c : cl.getConnections())
{
existing.getManagedConnection().associateConnection(c);
existing.addConnection(c);
context.switchConnectionListener(c, cl, existing);
}
cl.clearConnections();
cleanup.add(cl);
}
}
// Enlist ConnectionListener's
for (org.ironjacamar.core.connectionmanager.listener.ConnectionListener cl :
enlistmentMap.values())
{
if (Tracer.isEnabled())
{
for (Object c : cl.getConnections())
{
Tracer.ccmUserTransaction(cl.getManagedConnectionPool().getPool()
.getConfiguration().getId(),
cl.getManagedConnectionPool(),
cl, c, context.toString());
}
}
cm.transactionStarted(cl);
}
// Do cleanup
for (org.ironjacamar.core.connectionmanager.listener.ConnectionListener cl : cleanup)
{
context.removeConnectionListener(cm, cl);
cm.returnConnectionListener(cl, false);
}
}
catch (Exception e)
{
SystemException se = new SystemException();
se.initCause(e);
throw se;
}
}
}
}
}
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public void pushContext(Object contextKey, Set unsharableResources) throws ResourceException
{
LinkedList<Context> stack = threadContexts.get();
Context context = new Context(contextKey);
if (stack == null)
{
log.tracef("push: new stack for context: %s", context);
stack = new LinkedList<Context>();
threadContexts.set(stack);
}
else if (stack.isEmpty())
{
log.tracef("push: new stack for context: %s", context);
}
else
{
log.tracef("push: old stack for context: %s", stack.getLast());
log.tracef("push: new stack for context: %s", context);
}
if (Tracer.isEnabled())
Tracer.pushCCMContext(context.toString(), new Throwable("CALLSTACK"));
stack.addLast(context);
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public void popContext(Set unsharableResources) throws ResourceException
{
LinkedList<Context> stack = threadContexts.get();
if (stack == null || stack.isEmpty())
return;
Context context = stack.removeLast();
if (log.isTraceEnabled())
{
if (!stack.isEmpty())
{
log.tracef("pop: old stack for context: %s", context);
log.tracef("pop: new stack for context: %s", stack.getLast());
}
else
{
log.tracef("pop: old stack for context: %s", context);
}
}
if (Tracer.isEnabled())
Tracer.popCCMContext(context.toString(), new Throwable("CALLSTACK"));
if (debug && closeAll(context) && error)
{
throw new ResourceException(bundle.someConnectionsWereNotClosed());
}
context.clear();
}
/**
* Look at the current context
* @return The value
*/
private Context currentContext()
{
LinkedList<Context> stack = threadContexts.get();
if (stack != null && !stack.isEmpty())
{
return stack.getLast();
}
return null;
}
/**
* {@inheritDoc}
*/
public void registerConnection(org.ironjacamar.core.api.connectionmanager.ConnectionManager cm,
org.ironjacamar.core.api.connectionmanager.listener.ConnectionListener cl,
Object connection)
{
if (debug)
{
synchronized (connectionStackTraces)
{
connectionStackTraces.put(connection, new Throwable("STACKTRACE"));
}
}
Context context = currentContext();
log.tracef("registering connection from connection manager: %s, connection : %s, context: %s",
cm, connection, context);
if (context != null)
{
// Use internal API
org.ironjacamar.core.connectionmanager.ConnectionManager iCm =
(org.ironjacamar.core.connectionmanager.ConnectionManager)cm;
org.ironjacamar.core.connectionmanager.listener.ConnectionListener iCl =
(org.ironjacamar.core.connectionmanager.listener.ConnectionListener)cl;
if (Tracer.isEnabled())
{
Tracer.registerCCMConnection(iCl.getManagedConnectionPool().getPool()
.getConfiguration().getId(),
iCl.getManagedConnectionPool(),
iCl, connection, context.toString());
}
context.registerConnection(iCm, iCl, connection);
}
}
/**
* {@inheritDoc}
*/
public void unregisterConnection(org.ironjacamar.core.api.connectionmanager.ConnectionManager cm,
org.ironjacamar.core.api.connectionmanager.listener.ConnectionListener cl,
Object connection)
{
if (debug)
{
CloseConnectionSynchronization ccs = getCloseConnectionSynchronization(false);
if (ccs != null)
{
ccs.remove(connection);
}
synchronized (connectionStackTraces)
{
connectionStackTraces.remove(connection);
}
}
Context context = currentContext();
log.tracef("unregistering connection from connection manager: %s, connection: %s, context: %s",
cm, connection, context);
if (context == null)
return;
// Use internal API
org.ironjacamar.core.connectionmanager.ConnectionManager iCm =
(org.ironjacamar.core.connectionmanager.ConnectionManager)cm;
org.ironjacamar.core.connectionmanager.listener.ConnectionListener iCl =
(org.ironjacamar.core.connectionmanager.listener.ConnectionListener)cl;
if (context.unregisterConnection(iCm, iCl, connection))
{
if (Tracer.isEnabled())
{
Tracer.unregisterCCMConnection(iCl.getManagedConnectionPool().getPool()
.getConfiguration().getId(),
iCl.getManagedConnectionPool(),
iCl, connection, context.toString());
}
}
else
{
if (Tracer.isEnabled())
{
Tracer.unknownCCMConnection(iCl.getManagedConnectionPool().getPool()
.getConfiguration().getId(),
iCl.getManagedConnectionPool(),
iCl, connection, context.toString());
}
if (!ignoreConnections)
throw new IllegalStateException(); //bundle.tryingToReturnUnknownConnection(connection.toString()));
}
}
/**
* {@inheritDoc}
*/
public int getNumberOfConnections()
{
if (!debug)
return 0;
synchronized (connectionStackTraces)
{
return connectionStackTraces.size();
}
}
/**
* {@inheritDoc}
*/
public Map<String, String> listConnections()
{
if (!debug)
return Collections.unmodifiableMap(Collections.EMPTY_MAP);
synchronized (connectionStackTraces)
{
Map<String, String> result = new HashMap<String, String>();
for (Map.Entry<Object, Throwable> entry : connectionStackTraces.entrySet())
{
Object key = entry.getKey();
Throwable stackTrace = entry.getValue();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(baos, true);
stackTrace.printStackTrace(ps);
result.put(key.toString(), baos.toString());
}
return Collections.unmodifiableMap(result);
}
}
/**
* {@inheritDoc}
*/
public void start()
{
if (transactionIntegration != null && transactionIntegration.getUserTransactionRegistry() != null)
transactionIntegration.getUserTransactionRegistry().addListener(this);
log.debugf("start: %s", this.toString());
}
/**
* {@inheritDoc}
*/
public void stop()
{
log.debugf("stop: %s", this.toString());
if (transactionIntegration != null && transactionIntegration.getUserTransactionRegistry() != null)
transactionIntegration.getUserTransactionRegistry().removeListener(this);
}
/**
* Close all connections for a context
* @param context The context
* @return True if connections were closed, otherwise false
*/
private boolean closeAll(Context context)
{
boolean unclosed = false;
CloseConnectionSynchronization ccs = getCloseConnectionSynchronization(true);
for (org.ironjacamar.core.connectionmanager.ConnectionManager cm : context.getConnectionManagers())
{
for (org.ironjacamar.core.connectionmanager.listener.ConnectionListener cl :
context.getConnectionListeners(cm))
{
for (Object c : context.getConnections(cl))
{
if (ccs == null)
{
unclosed = true;
if (Tracer.isEnabled())
{
Tracer.closeCCMConnection(cl.getManagedConnectionPool().getPool()
.getConfiguration().getId(),
cl.getManagedConnectionPool(),
cl, c, context.toString());
}
closeConnection(c);
}
else
{
ccs.add(c);
}
}
}
}
return unclosed;
}
/**
* Get the CloseConnectionSynchronization instance
* @param createIfNotFound Create if not found
* @return The value
*/
private CloseConnectionSynchronization getCloseConnectionSynchronization(boolean createIfNotFound)
{
try
{
Transaction tx = null;
if (transactionIntegration != null)
tx = transactionIntegration.getTransactionManager().getTransaction();
if (tx != null && TxUtils.isActive(tx))
{
CloseConnectionSynchronization ccs = (CloseConnectionSynchronization)
transactionIntegration.getTransactionSynchronizationRegistry().
getResource(CLOSE_CONNECTION_SYNCHRONIZATION);
if (ccs == null && createIfNotFound)
{
ccs = new CloseConnectionSynchronization();
transactionIntegration.getTransactionSynchronizationRegistry().
putResource(CLOSE_CONNECTION_SYNCHRONIZATION, ccs);
transactionIntegration.getTransactionSynchronizationRegistry().registerInterposedSynchronization(ccs);
}
return ccs;
}
}
catch (Throwable t)
{
log.debug("Unable to synchronize with transaction", t);
}
return null;
}
/**
* Close connection handle
* @param connectionHandle Connection handle
*/
private void closeConnection(Object connectionHandle)
{
try
{
Throwable exception = null;
synchronized (connectionStackTraces)
{
exception = connectionStackTraces.remove(connectionHandle);
}
Method m = SecurityActions.getMethod(connectionHandle.getClass(), "close", new Class[]{});
try
{
if (exception != null)
{
log.closingConnection(connectionHandle, exception);
}
else
{
log.closingConnection(connectionHandle);
}
m.invoke(connectionHandle, new Object[]{});
}
catch (Throwable t)
{
log.closingConnectionThrowable(t);
}
}
catch (NoSuchMethodException nsme)
{
log.closingConnectionNoClose(connectionHandle.getClass().getName());
}
}
/**
* Close unclosed connections in beforeCompletion
*/
private class CloseConnectionSynchronization implements Synchronization
{
/** Connection handles */
private List<Object> connections;
/** Closing flag */
private AtomicBoolean closing;
/**
* Constructor
*/
public CloseConnectionSynchronization()
{
this.connections = new ArrayList<Object>();
this.closing = new AtomicBoolean(false);
}
/**
* Add a connection handle
* @param c Connection handle
*/
public void add(Object c)
{
if (!closing.get())
connections.add(c);
}
/**
* Remove a connection handle
* @param c Connection handle
*/
public void remove(Object c)
{
if (!closing.get())
connections.remove(c);
}
/**
* {@inheritDoc}
*/
public void beforeCompletion()
{
closeAll();
}
/**
* {@inheritDoc}
*/
public void afterCompletion(int status)
{
// Rollback scenario
closeAll();
}
private void closeAll()
{
closing.set(true);
if (!connections.isEmpty())
{
for (Object c : connections)
{
closeConnection(c);
}
connections.clear();
}
}
}
/**
* String representation
* @return The string
*/
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("CachedConnectionManagerImpl@").append(Integer.toHexString(System.identityHashCode(this)));
sb.append("[debug=").append(debug);
sb.append(" error=").append(error);
sb.append(" ignoreConnections=").append(ignoreConnections);
sb.append(" transactionIntegration=").append(transactionIntegration);
sb.append(" threadContexts=").append(threadContexts.get());
sb.append(" connectionStackTraces=").append(connectionStackTraces);
sb.append("]");
return sb.toString();
}
}