/** * JBoss, Home of Professional Open Source. * Copyright 2012, Red Hat Middleware LLC, 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 GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * 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 GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General 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.jboss.jca.adapters.sap.spi.impl; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.UUID; 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.ManagedConnectionMetaData; import javax.resource.spi.ResourceAdapterAssociation; import javax.resource.spi.security.PasswordCredential; import javax.security.auth.Subject; import javax.transaction.xa.XAResource; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.impl.EObjectImpl; import org.jboss.jca.adapters.sap.cci.Connection; import org.jboss.jca.adapters.sap.cci.impl.ConnectionImpl; import org.jboss.jca.adapters.sap.spi.ManagedConnection; import org.jboss.jca.adapters.sap.spi.ManagedConnectionFactory; import com.sap.conn.jco.JCoContext; import com.sap.conn.jco.JCoDestination; import com.sap.conn.jco.JCoDestinationManager; import com.sap.conn.jco.JCoException; import com.sap.conn.jco.JCoFunction; import com.sap.conn.jco.ext.DestinationDataProvider; /** * <!-- begin-user-doc --> * Implements the {@link ManagedConnectionFactory } and {@link ResourceAdapterAssociation } interfaces for the JBoss SAP * JCA Connector. * * @author William Collins * * @version $Id: 072aa4773ba397c4422f79001a81a6204b12a560 $ * <!-- end-user-doc --> * <p> * </p> * * @generated */ public class ManagedConnectionImpl extends EObjectImpl implements ManagedConnection { /** * Remote function module called to commit transaction * * @generated NOT */ private static final String COMMIT_FUNCTION = "BAPI_TRANSACTION_COMMIT"; /** * Name of import variable set in remote function call to commit transaction that causes synchronous commit * * @generated NOT */ private static final String SYNC_COMMIT_PARAM = "WAIT"; /** * Value pass in remote function call to commit transaction that causes synchronous commit. * * @generated NOT */ private static final String SYNC_COMMIT_PARAM_VAL = "X"; /** * Remote function module called to rollback transaction * * @generated NOT */ private static final String ROLLBACK_FUNCTION = "BAPI_TRANSACTION_ROLLBACK"; /** * States of a manage connection. * * @generated NOT */ public static enum State { ACTIVE, /* Valid managed connection with active physical connection to SAP system */ DESTROYED; /* Invalid managed connection */ } /** * The logwriter set by application server * * @generated NOT */ private PrintWriter logwriter; /** * The factory this managed connection is associated with * * @generated NOT */ private ManagedConnectionFactoryImpl managedConnectionFactory; /** * The application server call backs observing this connection * * @generated NOT */ private List<ConnectionEventListener> listeners; /** * Set of active connection handles associated with this managed connection * * @generated NOT */ private final Set<Connection> handles = new HashSet<Connection>(); /** * Unique name of destination associated with this managed connection in JCo runtime * * @generated NOT */ private String destinationName; /** * Physical connection handle to SAP system in JCo runtime * * @generated NOT */ private JCoDestination destination; /** * Meta data describing this managed connection * * @generated NOT */ private ManagedConnectionMetaDataImpl connectionMetaData = null; /** * State of connection. * * Managed connection starts in <code>ACTIVE</code> state when created and transitions to final <code>DESTROYED</code> state * when destroyed by application server. * * @generated NOT */ private State state = State.ACTIVE; /** * The local transaction object that enables application server and clients to manage connections transaction context. * * @generated NOT */ private final LocalTransactionImpl localTransaction = new LocalTransactionImpl(this); /** * Flag indicating whether this managed connection is in transaction. * * @generated NOT */ private boolean inTransaction = false; /** * <!-- begin-user-doc --> * Create an empty instance of a {@link ManagedConnection}. * * <p> Note this should only be used for serialization * * <!-- end-user-doc --> * @generated */ protected ManagedConnectionImpl() { super(); } /** * Construct a managed connection with specified connection request info and associated with the specified managed * connection factory. * * @param mcf - the associated managed connection factory * @param connectionRequestInfo - the connection request info configuration * @throws ResourceException * * @generated NOT */ public ManagedConnectionImpl(ManagedConnectionFactoryImpl mcf, ConnectionRequestInfoImpl connectionRequestInfo) throws ResourceException { this.managedConnectionFactory = mcf; this.logwriter = null; this.listeners = Collections.synchronizedList(new ArrayList<ConnectionEventListener>(1)); // Create unique destination name for destination property configuration. destinationName = UUID.randomUUID().toString(); // Make destination configuration available to JCo runtime this.managedConnectionFactory.getResourceAdapter().getDestinationDataProvider() .addConnectionRequestInfo(destinationName, connectionRequestInfo); // Attempt to connect to SAP system. try { this.destination = JCoDestinationManager.getDestination(destinationName); if (connectionRequestInfo.getPingOnCreate().equals("true")) this.destination.ping(); } catch (JCoException e) { this.destroy(); throw ExceptionBundle.EXCEPTIONS.failedToCreateManagedConnection(e); } this.managedConnectionFactory.associateConnection(this); } /** * <!-- begin-user-doc --> * <p> * {@inheritDoc} * </p> * <!-- end-user-doc --> * @generated */ @Override protected EClass eStaticClass() { return SpiPackageImpl.Literals.MANAGED_CONNECTION; } /** * <!-- begin-user-doc --> * <p> * {@inheritDoc} * </p> * <!-- end-user-doc --> * @generated NOT */ public boolean equals(Object other) { if (state == State.DESTROYED) return false; if (other == null) return false; if (other == this) return true; if (!(other instanceof ManagedConnectionImpl)) return false; ManagedConnectionImpl obj = (ManagedConnectionImpl) other; return destination.getProperties().equals(obj.destination.getProperties()); } /** * <!-- begin-user-doc --> * <p> * {@inheritDoc} * </p> * <!-- end-user-doc --> * @generated NOT */ public int hashCode() { if (state == State.DESTROYED) return 7; return destination.getProperties().hashCode(); } /** * <!-- begin-user-doc --> * <p> * {@inheritDoc} * </p> * <!-- end-user-doc --> * @generated NOT */ public synchronized Object getConnection(Subject subject, ConnectionRequestInfo connectionRequestInfo) throws ResourceException { checkState(); if (connectionRequestInfo != null && !(connectionRequestInfo instanceof ConnectionRequestInfoImpl)) throw new ResourceException("managed-connection-impl-invalid-connection-request-info-type"); // Validate subject credentials match those of this connection. if (subject != null) { boolean foundMatchingCredential = false; searchPrivateCredentials: for (PasswordCredential credential : subject.getPrivateCredentials(PasswordCredential.class)) { if (credential.getManagedConnectionFactory().equals(this)) { if (!getConnectionRequestInfo().getUserName() .equals(credential.getUserName())) { continue searchPrivateCredentials; } else if (!getConnectionRequestInfo().getPassword() .equals(new String(credential.getPassword()))) { continue searchPrivateCredentials; } // Found matching credentials. foundMatchingCredential = true; break searchPrivateCredentials; } } if (!foundMatchingCredential) throw ExceptionBundle.EXCEPTIONS.failedToFindMatchingSecurityCredentialsInSubject(); } if (connectionRequestInfo != null) { ConnectionRequestInfoImpl jCxRequestInfo = (ConnectionRequestInfoImpl) connectionRequestInfo; searchConnectionRequestProperties: for (Entry<Object, Object> entry : jCxRequestInfo.getProperties().entrySet()) { if (subject != null && (entry.getKey().equals(DestinationDataProvider.JCO_USER) || entry.getKey().equals( DestinationDataProvider.JCO_PASSWD))) // Already checked management credentials against subject credentials which override // connection request info credentials. continue searchConnectionRequestProperties; if (!getProperties().get(entry.getKey()).equals(entry.getValue())) throw ExceptionBundle.EXCEPTIONS.connectionRequestInfoParameterDoesNotMatch(entry.getValue().toString(), entry.getKey().toString(), getProperties().get(entry.getKey()).toString()); } } Connection connection = new ConnectionImpl(this); return connection; } /** * <!-- begin-user-doc --> * <p> * {@inheritDoc} * </p> * <!-- end-user-doc --> * @generated NOT */ public void addConnectionEventListener(ConnectionEventListener listener) { if (listener == null) throw ExceptionBundle.EXCEPTIONS.connectionEventListenerIsNull(); synchronized (listeners) { listeners.add(listener); } } /** * <!-- begin-user-doc --> * <p> * {@inheritDoc} * </p> * <!-- end-user-doc --> * @generated NOT */ public void removeConnectionEventListener(ConnectionEventListener listener) { if (listener == null) throw ExceptionBundle.EXCEPTIONS.connectionEventListenerIsNull(); synchronized (listeners) { listeners.remove(listener); } } /** * <!-- begin-user-doc --> * <p> * {@inheritDoc} * </p> * <!-- end-user-doc --> * @generated NOT */ public LocalTransaction getLocalTransaction() throws ResourceException { return localTransaction; } /** * <!-- begin-user-doc --> * <p> * {@inheritDoc} * </p> * <!-- end-user-doc --> * @generated NOT */ public XAResource getXAResource() throws ResourceException { throw ExceptionBundle.EXCEPTIONS.xaResourceIsNotSupported(); } /** * <!-- begin-user-doc --> * <p> * {@inheritDoc} * </p> * <!-- end-user-doc --> * @generated NOT */ public synchronized ManagedConnectionMetaData getMetaData() throws ResourceException { checkState(); if (connectionMetaData == null) connectionMetaData = new ManagedConnectionMetaDataImpl(destination); return connectionMetaData; } /** * <!-- begin-user-doc --> * <p> * {@inheritDoc} * </p> * <!-- end-user-doc --> * @generated NOT */ public PrintWriter getLogWriter() throws ResourceException { return logwriter; } /** * <!-- begin-user-doc --> * <p> * {@inheritDoc} * </p> * <!-- end-user-doc --> * @generated NOT */ public void setLogWriter(PrintWriter out) throws ResourceException { logwriter = out; } /** * <!-- begin-user-doc --> * <p> * {@inheritDoc} * </p> * <!-- end-user-doc --> * @generated NOT */ public synchronized void associateConnection(Object connection) throws ResourceException { checkState(); if (!(connection instanceof ConnectionImpl)) throw ExceptionBundle.EXCEPTIONS.invalidConnectionTypeAssociatedWithManagedConnection(connection == null ? "null": connection.getClass().getName()); ConnectionImpl cciConnection = (ConnectionImpl) connection; cciConnection.associateManagedConnection(this); } /** * <!-- begin-user-doc --> * <p> * {@inheritDoc} * </p> * <!-- end-user-doc --> * @generated NOT */ public synchronized void dissociateConnections() throws ResourceException { checkState(); Collection<Connection> copy = null; if (handles.size() > 0) copy = new HashSet<Connection>(handles); if (copy != null) { for (Connection cciConnection : copy) { ((ConnectionImpl)cciConnection).dissociateManagedConnection(); } } } /** * <!-- begin-user-doc --> * <p> * {@inheritDoc} * </p> * <!-- end-user-doc --> * @generated NOT */ public synchronized void cleanup() throws ResourceException { Collection<Connection> copy = null; if (handles.size() > 0) copy = new HashSet<Connection>(handles); if (copy != null) { for (Connection cciConnection : copy) { cciConnection.close(); } } // End any stateful session. internalEndStatefulSession(); } /** * <!-- begin-user-doc --> * <p> * {@inheritDoc} * </p> * <!-- end-user-doc --> * @generated NOT */ public synchronized void destroy() throws ResourceException { if (state == State.DESTROYED) return; state = State.DESTROYED; // Cleanup any outstanding connection handles. cleanup(); this.managedConnectionFactory.dissociateConnection(this); // Remove destination configuration from JCo runtime this.managedConnectionFactory.getResourceAdapter().getDestinationDataProvider() .removeConnectionRequestInfo(destinationName); managedConnectionFactory = null; destination = null; } /** * <!-- begin-user-doc --> * <p> * {@inheritDoc} * </p> * <!-- end-user-doc --> * @generated NOT */ public Properties getProperties() throws ResourceException { checkState(); return managedConnectionFactory.getResourceAdapter().getDestinationDataProvider().getDestinationProperties(destinationName); } /** * <!-- begin-user-doc --> * <p> * {@inheritDoc} * </p> * <!-- end-user-doc --> * @generated NOT */ public org.jboss.jca.adapters.sap.spi.ConnectionRequestInfo getConnectionRequestInfo() throws ResourceException { return managedConnectionFactory.getResourceAdapter().getDestinationDataProvider().getConnectionRequestInfo(destinationName); } /** * <!-- begin-user-doc --> * <p> * {@inheritDoc} * </p> * <!-- end-user-doc --> * @generated NOT */ public JCoDestination getDestination() { return destination; } /** * <!-- begin-user-doc --> * <p> * {@inheritDoc} * </p> * <!-- end-user-doc --> * @generated NOT */ public ManagedConnectionFactory getManagedConnectionFactory() throws ResourceException { checkState(); return managedConnectionFactory; } /** * <!-- begin-user-doc --> * <p> * {@inheritDoc} * </p> * <!-- end-user-doc --> * @generated NOT */ public boolean isStateful() { if (state == State.DESTROYED) return false; return JCoContext.isStateful(destination); } /** * <!-- begin-user-doc --> * <p> * {@inheritDoc} * </p> * <!-- end-user-doc --> * @generated NOT */ public synchronized void beginStatefulSession() throws ResourceException { checkState(); JCoContext.begin(destination); } /** * <!-- begin-user-doc --> * <p> * {@inheritDoc} * </p> * <!-- end-user-doc --> * * @generated NOT */ public synchronized void endStatefulSession() throws ResourceException { checkState(); if (inTransaction) throw ExceptionBundle.EXCEPTIONS.connectionHasOutstandingTransaction(); internalEndStatefulSession(); } /** * Internal logic to end stateful session. * <p>NB: logic refactored into internal method in order to call without state and transaction checks from {@link #cleanup() }. * @throws ResourceException * * @generated NOT */ protected void internalEndStatefulSession() throws ResourceException { if (!JCoContext.isStateful(destination)) return; try { JCoContext.end(destination); } catch (JCoException e) { throw new ResourceException(e); } } /** * <!-- begin-user-doc --> * <p> * {@inheritDoc} * </p> * <!-- end-user-doc --> * * @generated NOT */ public void ping() throws ResourceException { checkState(); try { destination.ping(); } catch (JCoException e) { throw ExceptionBundle.EXCEPTIONS.pingFailed(e); } } /** * <!-- begin-user-doc --> * <p> * {@inheritDoc} * </p> * <!-- end-user-doc --> * * @generated NOT */ public synchronized void associateHandle(Connection handle) throws ResourceException { checkState(); handles.add(handle); } /** * <!-- begin-user-doc --> * <p> * {@inheritDoc} * </p> * <!-- end-user-doc --> * * @generated NOT */ public synchronized void dissociateHandle(Connection handle) throws ResourceException { checkState(); handles.remove(handle); } /** * <!-- begin-user-doc --> * <p> * {@inheritDoc} * </p> * <!-- end-user-doc --> * * @generated NOT */ public void closeHandle(Connection handle) throws ResourceException { dissociateHandle(handle); Collection<ConnectionEventListener> copy = null; synchronized (listeners) { if (listeners != null && listeners.size() > 0) copy = new ArrayList<ConnectionEventListener>(listeners); } if (copy != null) { ConnectionEvent event = new ConnectionEvent(this, ConnectionEvent.CONNECTION_CLOSED); event.setConnectionHandle(handle); for (ConnectionEventListener cel : copy) { cel.connectionClosed(event); } } } /** * Begins stateful session in SAP system and informs application server that transaction has begun. * * @throws ResourceException If connection has already begun a transaction. * * @generated NOT */ protected synchronized void beginLocalTransaction() throws ResourceException { checkState(); if (inTransaction) // JCA 1.5 Specification: 7.8.3 throw ExceptionBundle.EXCEPTIONS.connectionHasOutstandingTransaction(); inTransaction = true; // Start stateful session for transaction beginStatefulSession(); // Notify connection event listeners of local transaction start. Collection<ConnectionEventListener> copy = null; synchronized (listeners) { if (listeners != null && listeners.size() > 0) copy = new ArrayList<ConnectionEventListener>(listeners); } if (copy != null) { // JCA 1.5 Specification: 7.7.2.1 ConnectionEvent event = new ConnectionEvent(this, ConnectionEvent.LOCAL_TRANSACTION_STARTED); for (ConnectionEventListener cel : copy) { cel.localTransactionStarted(event); } } } /** * Invokes commit RFM in SAP system, ends stateful session and informs application server that transaction has committed. * * @throws ResourceException If connection has not begun a transaction or error occurs in JCo runtime. * * @generated NOT */ protected synchronized void commitLocalTransaction() throws ResourceException { checkState(); if (!inTransaction) // JCA 1.5 Specification: 7.8.3 throw ExceptionBundle.EXCEPTIONS.connectionHasNoOutstandingTransaction(); inTransaction = false; // Commit transaction synchronously in SAP system try { JCoFunction commit = destination.getRepository().getFunction(COMMIT_FUNCTION); commit.getImportParameterList().setValue(SYNC_COMMIT_PARAM, SYNC_COMMIT_PARAM_VAL); commit.execute(destination); } catch (JCoException e) { // Note transaction should be implicitly rolled back in SAP system on error. throw ExceptionBundle.EXCEPTIONS.commitFailed(e); } finally { // End stateful session for transaction. endStatefulSession(); } // Notify connection event listeners of local transaction commit. Collection<ConnectionEventListener> copy = null; synchronized (listeners) { if (listeners != null && listeners.size() > 0) copy = new ArrayList<ConnectionEventListener>(listeners); } if (copy != null) { // JCA 1.5 Specification: 7.7.2.1 ConnectionEvent event = new ConnectionEvent(this, ConnectionEvent.LOCAL_TRANSACTION_COMMITTED); for (ConnectionEventListener cel : copy) { cel.localTransactionCommitted(event); } } } /** * Invokes rollback RFM in SAP system, ends stateful session and informs application server that transaction has rolled back. * * @throws ResourceException If connection has not begun a transaction or error occurs in JCo runtime. * * @generated NOT */ protected synchronized void rollbackLocalTransaction() throws ResourceException { checkState(); if (!inTransaction) // JCA 1.5 Specification: 7.8.3 throw ExceptionBundle.EXCEPTIONS.connectionHasNoOutstandingTransaction(); inTransaction = false; // Commit transaction synchronously in SAP system try { JCoFunction rollback = destination.getRepository().getFunction(ROLLBACK_FUNCTION); rollback.execute(destination); } catch (JCoException e) { // Note transaction should be implicitly rolled back in SAP system on error. throw ExceptionBundle.EXCEPTIONS.rollBackFailed(e); } finally { // End stateful session for transaction. endStatefulSession(); } // Notify connection event listeners of local transaction rollback. Collection<ConnectionEventListener> copy = null; synchronized (listeners) { if (listeners != null && listeners.size() > 0) copy = new ArrayList<ConnectionEventListener>(listeners); } if (copy != null) { // JCA 1.5 Specification: 7.7.2.1 ConnectionEvent event = new ConnectionEvent(this, ConnectionEvent.LOCAL_TRANSACTION_ROLLEDBACK); for (ConnectionEventListener cel : copy) { cel.localTransactionRolledback(event); } } } /** * Internal helper method used by public methods to check the state of this connection before performing an operation on it. This * method prevents operations from being performed on a connection in a <code>DESTROYED</code> state. * * @throws ResourceException if connection is in a <code>DESTROYED</code> state. * * @generated NOT */ private void checkState() throws ResourceException { if (state == State.DESTROYED) { throw ExceptionBundle.EXCEPTIONS.connectionIsDestroyed(); } } } //ManagedConnectionImpl