/*
* Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Florent Guillaume
*/
package org.eclipse.ecr.core.storage.sql.ra;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.Set;
import javax.resource.ResourceException;
import javax.resource.cci.Connection;
import javax.resource.cci.ConnectionFactory;
import javax.resource.spi.ConnectionEvent;
import javax.resource.spi.ConnectionEventListener;
import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.LocalTransaction;
import javax.resource.spi.ManagedConnection;
import javax.resource.spi.ManagedConnectionFactory;
import javax.resource.spi.ManagedConnectionMetaData;
import javax.security.auth.Subject;
import javax.transaction.xa.XAResource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.ecr.core.storage.Credentials;
import org.eclipse.ecr.core.storage.sql.ConnectionSpecImpl;
import org.eclipse.ecr.core.storage.sql.SessionImpl;
import org.nuxeo.common.collections.ListenerList;
/**
* The managed connection represents an actual physical connection to the
* underlying storage. It is created by the {@link ManagedConnectionFactory},
* and then encapsulated into a {@link Connection} which is then returned to the
* application (via the {@link ConnectionFactory}).
* <p>
* If sharing is allowed, several different {@link Connection}s may be
* associated to a given {@link ManagedConnection}, although not at the same
* time.
*
* @author Florent Guillaume
*/
public class ManagedConnectionImpl implements ManagedConnection,
ManagedConnectionMetaData {
private static final Log log = LogFactory.getLog(ManagedConnectionImpl.class);
private PrintWriter out;
private final ManagedConnectionFactoryImpl managedConnectionFactory;
private final ConnectionSpecImpl connectionSpec;
/**
* All the {@link Connection} handles for this managed connection. There is
* usually only one, unless sharing is in effect.
*/
private final Set<ConnectionImpl> connections;
/**
* The low-level session managed by this connection.
*/
private final SessionImpl session;
/**
* The wrapped session as a connection-aware xaresource.
*/
private final ConnectionAwareXAResource xaresource;
/**
* List of listeners set by the application server which we must notify of
* all activity happening on our {@link Connection}.
*/
private final ListenerList listeners;
/**
* Creates a new physical connection to the underlying storage. Called by
* the {@link ManagedConnectionFactory} when it needs a new connection.
*
* @throws ResourceException
*/
public ManagedConnectionImpl(
ManagedConnectionFactoryImpl managedConnectionFactory,
ConnectionRequestInfoImpl connectionRequestInfo)
throws ResourceException {
log.debug("construct: " + this);
if (log.isTraceEnabled()) {
log.trace("debug stack trace", new Exception());
}
out = managedConnectionFactory.getLogWriter();
this.managedConnectionFactory = managedConnectionFactory;
this.connectionSpec = connectionRequestInfo.connectionSpec;
connections = new HashSet<ConnectionImpl>();
listeners = new ListenerList();
// create the underlying session
session = managedConnectionFactory.getConnection(connectionSpec);
xaresource = new ConnectionAwareXAResource(session.getXAResource(),
this);
}
/*
* ----- javax.resource.spi.ManagedConnection -----
*/
/**
* Creates a new {@link Connection} handle to this {@link ManagedConnection}
* .
*/
@Override
public synchronized Connection getConnection(Subject subject,
ConnectionRequestInfo connectionRequestInfo)
throws ResourceException {
log.debug("getConnection: " + this);
assert connectionRequestInfo instanceof ConnectionRequestInfoImpl;
ConnectionImpl connection = new ConnectionImpl(this);
addConnection(connection);
return connection;
}
/**
* Cleans up the physical connection, so that it may be reused.
* <p>
* Called by the application server before putting back this
* {@link ManagedConnection} into the application server pool.
* <p>
* Later, the application server may call {@link #getConnection} again.
*/
@Override
public void cleanup() {
log.debug("cleanup: " + this);
if (log.isTraceEnabled()) {
log.trace("debug stack trace", new Exception());
}
synchronized (connections) {
// TODO session.cancel
connections.clear();
}
}
/**
* Destroys the physical connection.
* <p>
* Called by the application server before this {@link ManagedConnection} is
* destroyed.
*/
@Override
public void destroy() throws ResourceException {
log.debug("destroy: " + this);
cleanup();
session.close();
}
/**
* Called by the application server to change the association of an
* application-level {@link Connection} handle with a
* {@link ManagedConnection} instance.
*/
@Override
public void associateConnection(Object object) throws ResourceException {
ConnectionImpl connection = (ConnectionImpl) object;
log.debug("associateConnection: " + this + ", connection: "
+ connection);
ManagedConnectionImpl other = connection.getManagedConnection();
if (other != this) {
log.debug("associateConnection other: " + other);
// deassociate it from other ManagedConnection
other.removeConnection(connection);
// reassociate it with this
connection.setManagedConnection(this);
addConnection(connection);
}
}
@Override
public XAResource getXAResource() {
return xaresource;
}
@Override
public LocalTransaction getLocalTransaction() {
throw new UnsupportedOperationException(
"Local transactions not supported");
}
/**
* Called by the application server to add a listener who should be notified
* of all relevant events on this connection.
*/
@Override
public void addConnectionEventListener(ConnectionEventListener listener) {
listeners.add(listener);
}
/**
* Called by the application server to remove a listener.
*/
@Override
public void removeConnectionEventListener(ConnectionEventListener listener) {
listeners.remove(listener);
}
@Override
public ManagedConnectionMetaData getMetaData() {
return this;
}
@Override
public void setLogWriter(PrintWriter out) {
this.out = out;
}
@Override
public PrintWriter getLogWriter() {
return out;
}
/*
* ----- javax.resource.spi.ManagedConnectionMetaData -----
*/
@Override
public String getEISProductName() {
return "Nuxeo Core SQL Storage";
}
@Override
public String getEISProductVersion() {
return "1.0.0";
}
@Override
public int getMaxConnections() {
return Integer.MAX_VALUE; // or lower?
}
@Override
public String getUserName() throws ResourceException {
Credentials credentials = connectionSpec.getCredentials();
if (credentials == null) {
return ""; // XXX
}
return credentials.getUserName();
}
/*
* ----- Internal -----
*/
/**
* Adds a connection to those using this managed connection.
*/
private void addConnection(ConnectionImpl connection) {
synchronized (connections) {
log.debug("addConnection: " + connection);
connections.add(connection);
connection.associate(session);
}
}
/**
* Removes a connection from those of this managed connection.
*/
private void removeConnection(ConnectionImpl connection) {
synchronized (connections) {
log.debug("removeConnection: " + connection);
connection.disassociate();
connections.remove(connection);
}
}
/**
* Called by {@link ConnectionImpl#close} when the connection is closed.
*/
protected void close(ConnectionImpl connection) {
log.debug("close: " + this);
removeConnection(connection);
sendClosedEvent(connection);
}
/**
* Called by {@link ConnectionAwareXAResource} at the end of a transaction.
*/
protected void closeConnections() {
log.debug("closeConnections: " + this);
synchronized (connections) {
// copy to avoid ConcurrentModificationException
ConnectionImpl[] array = new ConnectionImpl[connections.size()];
for (ConnectionImpl connection : connections.toArray(array)) {
log.debug("closing connection: " + connection);
connection.disassociate();
sendClosedEvent(connection);
}
connections.clear();
}
}
/**
* Called by {@link ManagedConnectionFactoryImpl#matchManagedConnections}.
*/
protected ManagedConnectionFactoryImpl getManagedConnectionFactory() {
return managedConnectionFactory;
}
/*
* ----- Event management -----
*/
private void sendClosedEvent(ConnectionImpl connection) {
sendEvent(ConnectionEvent.CONNECTION_CLOSED, connection, null);
}
protected void sendTxStartedEvent(ConnectionImpl connection) {
sendEvent(ConnectionEvent.LOCAL_TRANSACTION_STARTED, connection, null);
}
protected void sendTxCommittedEvent(ConnectionImpl connection) {
sendEvent(ConnectionEvent.LOCAL_TRANSACTION_COMMITTED, connection, null);
}
protected void sendTxRolledbackEvent(ConnectionImpl connection) {
sendEvent(ConnectionEvent.LOCAL_TRANSACTION_ROLLEDBACK, connection,
null);
}
protected void sendErrorEvent(ConnectionImpl connection, Exception cause) {
sendEvent(ConnectionEvent.CONNECTION_ERROR_OCCURRED, connection, cause);
}
private void sendEvent(int type, ConnectionImpl connection, Exception cause) {
ConnectionEvent event = new ConnectionEvent(this, type, cause);
if (connection != null) {
event.setConnectionHandle(connection);
}
sendEvent(event);
}
/**
* Notifies the application server, through the
* {@link ConnectionEventListener}s it has registered with us, of what
* happens with this connection.
*/
private void sendEvent(ConnectionEvent event) {
for (Object object : listeners.getListeners()) {
ConnectionEventListener listener = (ConnectionEventListener) object;
switch (event.getId()) {
case ConnectionEvent.CONNECTION_CLOSED:
listener.connectionClosed(event);
break;
case ConnectionEvent.LOCAL_TRANSACTION_STARTED:
listener.localTransactionStarted(event);
break;
case ConnectionEvent.LOCAL_TRANSACTION_COMMITTED:
listener.localTransactionCommitted(event);
break;
case ConnectionEvent.LOCAL_TRANSACTION_ROLLEDBACK:
listener.localTransactionRolledback(event);
break;
case ConnectionEvent.CONNECTION_ERROR_OCCURRED:
listener.connectionErrorOccurred(event);
break;
}
}
}
}