/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package org.apache.activemq.ra; import java.io.PrintWriter; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import javax.jms.Connection; import javax.jms.ExceptionListener; import javax.jms.JMSException; import javax.resource.ResourceException; 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.ManagedConnectionMetaData; import javax.security.auth.Subject; import javax.transaction.xa.XAResource; import org.apache.activemq.ActiveMQConnection; import org.apache.activemq.LocalTransactionEventListener; import org.apache.activemq.TransactionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * ActiveMQManagedConnection maps to real physical connection to the server. * Since a ManagedConnection has to provide a transaction managment interface to * the physical connection, and sessions are the objects implement transaction * managment interfaces in the JMS API, this object also maps to a singe * physical JMS session. <p/> The side-effect is that JMS connection the * application gets will allways create the same session object. This is good if * running in an app server since the sessions are elisted in the context * transaction. This is bad if used outside of an app server since the user may * be trying to create 2 different sessions to coordinate 2 different uow. * * */ public class ActiveMQManagedConnection implements ManagedConnection, ExceptionListener { // TODO: // , // DissociatableManagedConnection // { private static final Logger LOG = LoggerFactory.getLogger(ActiveMQManagedConnection.class); private PrintWriter logWriter; private final ActiveMQConnection physicalConnection; private final TransactionContext transactionContext; private final List<ManagedConnectionProxy> proxyConnections = new CopyOnWriteArrayList<ManagedConnectionProxy>(); private final List<ConnectionEventListener> listeners = new CopyOnWriteArrayList<ConnectionEventListener>(); private final LocalAndXATransaction localAndXATransaction; private Subject subject; private ActiveMQConnectionRequestInfo info; private boolean destroyed; public ActiveMQManagedConnection(Subject subject, ActiveMQConnection physicalConnection, ActiveMQConnectionRequestInfo info) throws ResourceException { try { this.subject = subject; this.info = info; this.physicalConnection = physicalConnection; this.transactionContext = new TransactionContext(physicalConnection); this.localAndXATransaction = new LocalAndXATransaction(transactionContext) { public void setInManagedTx(boolean inManagedTx) throws JMSException { super.setInManagedTx(inManagedTx); for (ManagedConnectionProxy proxy:proxyConnections) { proxy.setUseSharedTxContext(inManagedTx); } } }; this.transactionContext.setLocalTransactionEventListener(new LocalTransactionEventListener() { public void beginEvent() { fireBeginEvent(); } public void commitEvent() { fireCommitEvent(); } public void rollbackEvent() { fireRollbackEvent(); } }); physicalConnection.setExceptionListener(this); } catch (JMSException e) { throw new ResourceException("Could not create a new connection: " + e.getMessage(), e); } } public boolean isInManagedTx() { return localAndXATransaction.isInManagedTx(); } public static boolean matches(Object x, Object y) { if (x == null ^ y == null) { return false; } if (x != null && !x.equals(y)) { return false; } return true; } public void associate(Subject subject, ActiveMQConnectionRequestInfo info) throws JMSException { // Do we need to change the associated userid/password if (!matches(info.getUserName(), this.info.getUserName()) || !matches(info.getPassword(), this.info.getPassword())) { physicalConnection.changeUserInfo(info.getUserName(), info.getPassword()); } // Do we need to set the clientId? if (info.getClientid() != null && info.getClientid().length() > 0) { physicalConnection.setClientID(info.getClientid()); } this.subject = subject; this.info = info; } public Connection getPhysicalConnection() { return physicalConnection; } private void fireBeginEvent() { ConnectionEvent event = new ConnectionEvent(ActiveMQManagedConnection.this, ConnectionEvent.LOCAL_TRANSACTION_STARTED); for(ConnectionEventListener l:listeners) { l.localTransactionStarted(event); } } private void fireCommitEvent() { ConnectionEvent event = new ConnectionEvent(ActiveMQManagedConnection.this, ConnectionEvent.LOCAL_TRANSACTION_COMMITTED); for(ConnectionEventListener l:listeners) { l.localTransactionCommitted(event); } } private void fireRollbackEvent() { ConnectionEvent event = new ConnectionEvent(ActiveMQManagedConnection.this, ConnectionEvent.LOCAL_TRANSACTION_ROLLEDBACK); for(ConnectionEventListener l:listeners) { l.localTransactionRolledback(event); } } private void fireCloseEvent(ManagedConnectionProxy proxy) { ConnectionEvent event = new ConnectionEvent(ActiveMQManagedConnection.this, ConnectionEvent.CONNECTION_CLOSED); event.setConnectionHandle(proxy); for(ConnectionEventListener l:listeners) { l.connectionClosed(event); } } private void fireErrorOccurredEvent(Exception error) { ConnectionEvent event = new ConnectionEvent(ActiveMQManagedConnection.this, ConnectionEvent.CONNECTION_ERROR_OCCURRED, error); for(ConnectionEventListener l:listeners) { l.connectionErrorOccurred(event); } } /** * @see javax.resource.spi.ManagedConnection#getConnection(javax.security.auth.Subject, * javax.resource.spi.ConnectionRequestInfo) */ public Object getConnection(Subject subject, ConnectionRequestInfo info) throws ResourceException { ManagedConnectionProxy proxy = new ManagedConnectionProxy(this, info); proxyConnections.add(proxy); return proxy; } private boolean isDestroyed() { return destroyed; } /** * Close down the physical connection to the server. * * @see javax.resource.spi.ManagedConnection#destroy() */ public void destroy() throws ResourceException { // Have we already been destroyed?? if (isDestroyed()) { return; } try { cleanup(); } finally { try { physicalConnection.close(); destroyed = true; } catch (JMSException e) { LOG.trace("Error occurred during close of a JMS connection.", e); } } } /** * Cleans up all proxy handles attached to this physical connection so that * they cannot be used anymore. * * @see javax.resource.spi.ManagedConnection#cleanup() */ public void cleanup() throws ResourceException { // Have we already been destroyed?? if (isDestroyed()) { return; } for (ManagedConnectionProxy proxy:proxyConnections) { proxy.cleanup(); } proxyConnections.clear(); try { physicalConnection.doCleanup(physicalConnection.isUserSpecifiedClientID()); } catch (JMSException e) { throw new ResourceException("Could not cleanup the ActiveMQ connection: " + e, e); } finally { // defer transaction cleanup till after close so that close is aware of the current tx localAndXATransaction.cleanup(); } } /** * @see javax.resource.spi.ManagedConnection#associateConnection(java.lang.Object) */ public void associateConnection(Object connection) throws ResourceException { if (connection instanceof ManagedConnectionProxy) { ManagedConnectionProxy proxy = (ManagedConnectionProxy)connection; proxyConnections.add(proxy); } else { throw new ResourceException("Not supported : associating connection instance of " + connection.getClass().getName()); } } /** * @see javax.resource.spi.ManagedConnection#addConnectionEventListener(javax.resource.spi.ConnectionEventListener) */ public void addConnectionEventListener(ConnectionEventListener listener) { listeners.add(listener); } /** * @see javax.resource.spi.ManagedConnection#removeConnectionEventListener(javax.resource.spi.ConnectionEventListener) */ public void removeConnectionEventListener(ConnectionEventListener listener) { listeners.remove(listener); } /** * @see javax.resource.spi.ManagedConnection#getXAResource() */ public XAResource getXAResource() throws ResourceException { return localAndXATransaction; } /** * @see javax.resource.spi.ManagedConnection#getLocalTransaction() */ public LocalTransaction getLocalTransaction() throws ResourceException { return localAndXATransaction; } /** * @see javax.resource.spi.ManagedConnection#getMetaData() */ public ManagedConnectionMetaData getMetaData() throws ResourceException { return new ManagedConnectionMetaData() { public String getEISProductName() throws ResourceException { if (physicalConnection == null) { throw new ResourceException("Not connected."); } try { return physicalConnection.getMetaData().getJMSProviderName(); } catch (JMSException e) { throw new ResourceException("Error accessing provider.", e); } } public String getEISProductVersion() throws ResourceException { if (physicalConnection == null) { throw new ResourceException("Not connected."); } try { return physicalConnection.getMetaData().getProviderVersion(); } catch (JMSException e) { throw new ResourceException("Error accessing provider.", e); } } public int getMaxConnections() throws ResourceException { if (physicalConnection == null) { throw new ResourceException("Not connected."); } return Integer.MAX_VALUE; } public String getUserName() throws ResourceException { if (physicalConnection == null) { throw new ResourceException("Not connected."); } try { return physicalConnection.getClientID(); } catch (JMSException e) { throw new ResourceException("Error accessing provider.", e); } } }; } /** * @see javax.resource.spi.ManagedConnection#setLogWriter(java.io.PrintWriter) */ public void setLogWriter(PrintWriter logWriter) throws ResourceException { this.logWriter = logWriter; } /** * @see javax.resource.spi.ManagedConnection#getLogWriter() */ public PrintWriter getLogWriter() throws ResourceException { return logWriter; } /** * @param subject subject to match * @param info cri to match * @return whether the subject and cri match sufficiently to allow using this connection under the new circumstances */ public boolean matches(Subject subject, ConnectionRequestInfo info) { // Check to see if it is our info class if (info == null) { return false; } if (info.getClass() != ActiveMQConnectionRequestInfo.class) { return false; } // Do the subjects match? if (subject == null ^ this.subject == null) { return false; } if (subject != null && !subject.equals(this.subject)) { return false; } // Does the info match? return info.equals(this.info); } /** * When a proxy is closed this cleans up the proxy and notifies the * ConnectionEventListeners that a connection closed. * * @param proxy */ public void proxyClosedEvent(ManagedConnectionProxy proxy) { proxyConnections.remove(proxy); proxy.cleanup(); fireCloseEvent(proxy); } public void onException(JMSException e) { LOG.warn("Connection failed: " + e); LOG.debug("Cause: ", e); for (ManagedConnectionProxy proxy:proxyConnections) { proxy.onException(e); } // Let the container know that the error occurred. fireErrorOccurredEvent(e); } /** * @return Returns the transactionContext. */ public TransactionContext getTransactionContext() { return transactionContext; } @Override public String toString() { return "[" + super.toString() + "," + physicalConnection +"]"; } }