/* * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. * * 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: * Florent Guillaume */ package org.nuxeo.ecm.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.nuxeo.common.collections.ListenerList; import org.nuxeo.ecm.core.storage.sql.SessionImpl; /** * 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; /** * 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 XAResource 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) throws ResourceException { log.debug("construct: " + this); out = managedConnectionFactory.getLogWriter(); this.managedConnectionFactory = managedConnectionFactory; connections = new HashSet<ConnectionImpl>(); listeners = new ListenerList(); // create the underlying session session = managedConnectionFactory.getConnection(); xaresource = session.getXAResource(); } /* * ----- 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 { // connectionRequestInfo unused log.debug("getConnection: " + this); 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); 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); try { session.close(); } finally { cleanup(); } } /** * 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); other.removeConnection(connection); 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 { return null; } /* * ----- Internal ----- */ /** * Adds a connection to those using this managed connection. */ private void addConnection(ConnectionImpl connection) { log.debug("addConnection: " + connection); if (connections.add(connection) == false) { throw new IllegalStateException("already known connection " + connection + " in " + this); } connection.setManagedConnection(this); connection.associate(session); } /** * Removes a connection to those using this managed connection. */ private void removeConnection(ConnectionImpl connection) { log.debug("removeConnection: " + connection); if (connections.remove(connection) == false) { throw new IllegalStateException("unknown connection " + connection + " in " + this); } connection.setManagedConnection(null); connection.disassociate(); } /** * Called by {@link ConnectionImpl#close} when the connection is closed. */ protected void close(ConnectionImpl connection) { removeConnection(connection); sendClosedEvent(connection); } /** * Called by {@link ManagedConnectionFactoryImpl#matchManagedConnections}. */ protected ManagedConnectionFactoryImpl getManagedConnectionFactory() { return managedConnectionFactory; } /* * ----- Event management ----- */ private void sendClosedEvent(ConnectionImpl connection) { log.debug("closing a connection " + 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; } } } }