All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.firebirdsql.jca.FBManagedConnection Maven / Gradle / Ivy

The newest version!
/*
 * Firebird Open Source J2ee connector - jdbc driver
 *
 * Distributable under LGPL license.
 * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html
 *
 * This program 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
 * LGPL License for more details.
 *
 * This file was created by members of the firebird development team.
 * All individual contributions remain the Copyright (C) of those
 * individuals.  Contributors to this file are either listed here or
 * can be obtained from a CVS history command.
 *
 * All rights reserved.
 */
package org.firebirdsql.jca;

import java.io.ByteArrayInputStream;
import java.io.PrintWriter;
import java.sql.*;
import java.util.*;

import javax.resource.ResourceException;
import javax.resource.spi.*;
import javax.resource.spi.security.PasswordCredential;
import javax.security.auth.Subject;
import javax.transaction.xa.*;

import org.firebirdsql.gds.*;
import org.firebirdsql.gds.impl.AbstractIscStmtHandle;
import org.firebirdsql.gds.impl.AbstractIscTrHandle;
import org.firebirdsql.gds.impl.GDSHelper;
import org.firebirdsql.gds.impl.GDSHelper.GDSHelperErrorListener;
import org.firebirdsql.jdbc.*;
import org.firebirdsql.jdbc.field.FBField;
import org.firebirdsql.jdbc.field.FieldDataProvider;
import org.firebirdsql.logging.Logger;
import org.firebirdsql.logging.LoggerFactory;
import org.firebirdsql.util.SQLExceptionChainBuilder;

/**
 * The class FBManagedConnection implements both the
 * ManagedConnection and XAResource interfaces.
 * 
 * @author David Jencks 
 * @version 1.0
 */
public class FBManagedConnection implements ManagedConnection, XAResource, GDSHelperErrorListener {

    public static final String WARNING_NO_CHARSET = "WARNING: No connection characterset specified (property lc_ctype, encoding, charSet or localEncoding), defaulting to characterset NONE";

    private static final Logger log = LoggerFactory.getLogger(FBManagedConnection.class, false);

    private final FBManagedConnectionFactory mcf;

    private final ArrayList connectionEventListeners = new ArrayList();
    private final ArrayList connectionHandles = new ArrayList();

    private int timeout = 0;

    private final Map xidMap = Collections.synchronizedMap(new HashMap());
    
    private final GDS gds;
    private final IscDbHandle dbHandle;
    private GDSHelper gdsHelper;

    private final FBConnectionRequestInfo cri;
    private FBTpb tpb;
    private int transactionIsolation;

    private volatile boolean managedEnvironment = true;
    private volatile boolean connectionSharing = true;
    private final Set preparedXid = Collections.synchronizedSet(new HashSet());
    private volatile boolean inDistributedTransaction = false;

    FBManagedConnection(Subject subject, ConnectionRequestInfo cri,
            FBManagedConnectionFactory mcf) throws ResourceException {
        
        this.mcf = mcf;
        this.gds = mcf.getGDS();
        this.cri = getCombinedConnectionRequestInfo(subject, cri);
        this.tpb = mcf.getDefaultTpb();
        this.transactionIsolation = mcf.getDefaultTransactionIsolation();
        
        //TODO: XIDs in limbo should be loaded so that XAER_DUPID can be thrown appropriately
        
        try {
            dbHandle = gds.createIscDbHandle();

            DatabaseParameterBuffer dpb = this.cri.getDpb();
            if (dpb.getArgumentAsString(DatabaseParameterBuffer.LC_CTYPE) == null) {
                if (log != null) {
                    log.warn(WARNING_NO_CHARSET);
                }
                dbHandle.addWarning(new GDSWarning(WARNING_NO_CHARSET));
            }
            
            if (!dpb.hasArgument(DatabaseParameterBuffer.CONNECT_TIMEOUT) && DriverManager.getLoginTimeout() > 0) {
                dpb.addArgument(DatabaseParameterBuffer.CONNECT_TIMEOUT, DriverManager.getLoginTimeout());
            }
            
            gds.iscAttachDatabase(mcf.getDatabase(), dbHandle, dpb);
            
            gdsHelper = new GDSHelper(gds, dpb, dbHandle, this);
        } catch(GDSException ex) {
            throw new FBResourceException(ex);
        }
    }

    /**
     * Notify GDS container that error occured, if the ex 
     * represents a "fatal" one
     * 
     * @see FatalGDSErrorHelper#isFatal(GDSException)
     */
    public void errorOccured(GDSException ex) {
        
        if (log != null) log.trace(ex.getMessage());
        
        if (!FatalGDSErrorHelper.isFatal(ex))
            return;
        
        ConnectionEvent event = new ConnectionEvent(
            FBManagedConnection.this, 
            ConnectionEvent.CONNECTION_ERROR_OCCURRED, ex);
        
        FBManagedConnection.this.notify(
            connectionErrorOccurredNotifier, event);
    }

    
    private FBConnectionRequestInfo getCombinedConnectionRequestInfo(
            Subject subject, ConnectionRequestInfo cri)
            throws ResourceException {
        if (cri == null) {
            cri = mcf.getDefaultConnectionRequestInfo();
        }
        try {
            FBConnectionRequestInfo fbcri = (FBConnectionRequestInfo) cri;
            if (subject != null) {
                // see connector spec, section 8.2.6, contract for
                // ManagedConnectinFactory, option A.
                for (Iterator i = subject.getPrivateCredentials().iterator(); i
                        .hasNext();) {
                    Object cred = i.next();
                    if (cred instanceof PasswordCredential
                            && mcf.equals(((PasswordCredential) cred)
                                    .getManagedConnectionFactory())) {
                        PasswordCredential pcred = (PasswordCredential) cred;
                        String user = pcred.getUserName();
                        String password = new String(pcred.getPassword());
                        fbcri.setPassword(password);
                        fbcri.setUserName(user);
                        break;
                    } 
                } 
            } 
    
            return fbcri;
        } catch (ClassCastException cce) {
            throw new FBResourceException(
                    "Incorrect ConnectionRequestInfo class supplied");
        }
    }
    
    /**
     * Get instance of {@link GDSHelper} connected with this managed connection.
     * 
     * @return instance of {@link GDSHelper}.
     * @throws GDSException If this connection has no GDSHelper
     */
    public GDSHelper getGDSHelper() throws GDSException {
        if (gdsHelper == null)
            throw new GDSException(ISCConstants.isc_arg_gds, ISCConstants.isc_req_no_trans);
        
        return gdsHelper;
    }
    
    public String getDatabase() {
        return mcf.getDatabase();
    }

    public boolean isManagedEnvironment() {
        return managedEnvironment;
    }
    
    public boolean inTransaction() {
        return gdsHelper != null && gdsHelper.inTransaction();
    }
    
    public void setManagedEnvironment(boolean managedEnvironment) throws ResourceException{
        this.managedEnvironment = managedEnvironment;
        
        // if connection sharing is not enabled, notify currently associated
        // connection handle about the state change.
        if (!connectionSharing) {
            if (connectionHandles.size() > 1)
                throw new javax.resource.spi.IllegalStateException(
                    "Multiple connections associated with this managed " +
                    "connection in non-sharing mode.");
            
            // there will be at most one connection.
            for (Iterator iter = connectionHandles.iterator(); iter.hasNext();) {
                AbstractConnection connection = (AbstractConnection) iter.next();

                try {
                    connection.setManagedEnvironment(managedEnvironment);
                } catch(SQLException ex) {
                    throw new FBResourceException(ex);
                }
            }
        }
    }
    
    /**
     * Check if connection sharing is enabled. When connection sharing is 
     * enabled, multiple connection handles ({@link AbstractConnection} instances)
     * can access this managed connection in thread-safe manner (they synchronize
     * on this instance). This feature can be enabled only in JCA environment,
     * any other environment must not use connection sharing.
     * 
     * @return true if connection sharing is enabled.
     */
    public boolean isConnectionSharing() {
        return connectionSharing;
    }
    
    /**
     * Enable or disable connection sharing. See {@link #isConnectionSharing()}
     * method for details.
     * 
     * @param connectionSharing true if connection sharing must be
     * enabled.
     * @throws ResourceException If connection sharing state cannot be changed
     */
    public void setConnectionSharing(boolean connectionSharing) throws ResourceException {
        if (!connectionHandles.isEmpty())
            throw new javax.resource.spi.IllegalStateException(
                "Cannot change connection sharing with active connection handles.");
        
        this.connectionSharing = connectionSharing;
    }
    /**
     * Returns a javax.resource.spi.LocalTransaction instance.
     * The LocalTransaction interface is used by the container to manage local
     * transactions for a RM instance.
     * 
     * @return LocalTransaction instance
     * @throws ResourceException
     *             generic exception if operation fails
     * @throws javax.resource.NotSupportedException
     *             if the operation is not supported
     * @throws ResourceAdapterInternalException
     *             resource adapter internal error condition
     */
    public LocalTransaction getLocalTransaction() {
        return new FBLocalTransaction(this, null);
    }

    /**
     * Gets the metadata information for this connection's underlying EIS
     * resource manager instance. The ManagedConnectionMetaData interface
     * provides information about the underlying EIS instance associated with
     * the ManagedConenction instance.
     * 
     * @return ManagedConnectionMetaData instance
     * @throws ResourceException
     *             generic exception if operation fails
     * @throws javax.resource.NotSupportedException
     *             if the operation is not supported
     */
    public ManagedConnectionMetaData getMetaData() throws ResourceException {
        return new FBManagedConnectionMetaData(this);
    }

    /**
     * Sets the log writer for this ManagedConnection instance.
     * 

* The log writer is a character output stream to which all logging and * tracing messages for this ManagedConnection instance will be printed. * Application Server manages the association of output stream with the * ManagedConnection instance based on the connection pooling requirements. *

* When a ManagedConnection object is initially created, the default log * writer associated with this instance is obtained from the * ManagedConnectionFactory. An application server can set a * log writer specific to this ManagedConnection to log/trace this instance * using setLogWriter method. * * @param out * Character Output stream to be associated * @throws ResourceException * generic exception if operation fails * @throws ResourceAdapterInternalException * resource adapter related error condition */ public void setLogWriter(PrintWriter out) { // ignore, we are using log4j. } /** * Gets the log writer for this ManagedConnection instance. *

* The log writer is a character output stream to which all logging and * tracing messages for this ManagedConnection instance will be printed. * ConnectionManager manages the association of output stream * with the ManagedConnection instance based on the * connection pooling requirements. *

* The Log writer associated with a ManagedConnection * instance can be one set as default from the ManagedConnectionFactory * (that created this connection) or one set specifically for this instance * by the application server. * * @return Character ourput stream associated with this * ManagedConnection * @throws ResourceException * generic exception if operation fails */ public PrintWriter getLogWriter() { return null;// we are using log4j. } /** * Add an ConnectionEventListener listener. The listener will * be notified when a ConnectionEvent occurs. * * @param listener * The ConnectionEventListener to be added */ public void addConnectionEventListener(ConnectionEventListener listener) { connectionEventListeners.add(listener); } /** * Remove a ConnectionEventListner from the listing of * listeners that will be notified for a ConnectionEvent. * * @param listener * The ConnectionEventListener to be removed */ public void removeConnectionEventListener(ConnectionEventListener listener) { connectionEventListeners.remove(listener); } /** * Used by the container to change the association of an application-level * connection handle with a ManagedConneciton instance. The container should * find the right ManagedConnection instance and call the * associateConnection method. *

* The resource adapter is required to implement the associateConnection * method. The method implementation for a ManagedConnection should * dissociate the connection handle (passed as a parameter) from its * currently associated ManagedConnection and associate the new connection * handle with itself. * * @param connection * Application-level connection handle * @throws ResourceException * Failed to associate the connection handle with this * ManagedConnection instance * @throws javax.resource.spi.IllegalStateException * Illegal state for invoking this method * @throws ResourceAdapterInternalException * Resource adapter internal error condition */ public void associateConnection(Object connection) throws ResourceException { if (!connectionSharing) disassociateConnections(); try { ((AbstractConnection) connection).setManagedConnection(this); connectionHandles.add(connection); } catch (ClassCastException cce) { throw new FBResourceException( "invalid connection supplied to associateConnection.", cce); } } /** * Application server calls this method to force any cleanup on the * ManagedConnection instance. *

* The method {@link ManagedConnection#cleanup}initiates a cleanup of the * any client-specific state as maintained by a ManagedConnection instance. * The cleanup should invalidate all connection handles that had been * created using this ManagedConnection instance. Any attempt * by an application component to use the connection handle after cleanup of * the underlying ManagedConnection should result in an * exception. *

* The cleanup of ManagedConnection is always driven by an application * server. An application server should not invoke * {@link ManagedConnection#cleanup}when there is an uncompleted * transaction (associated with a ManagedConnection instance) in progress. *

* The invocation of {@link ManagedConnection#cleanup}method on an already * cleaned-up connection should not throw an exception. * * The cleanup of ManagedConnection instance resets its * client specific state and prepares the connection to be put back in to a * connection pool. The cleanup method should not cause resource adapter to * close the physical pipe and reclaim system resources associated with the * physical connection. * * @throws ResourceException * generic exception if operation fails * @throws ResourceAdapterInternalException * resource adapter internal error condition * @throws javax.resource.spi.IllegalStateException * Illegal state for calling connection cleanup. Example - if a * local transaction is in progress that doesn't allow * connection cleanup */ public void cleanup() throws ResourceException { disassociateConnections(); if (gdsHelper != null) gdsHelper.setCurrentTrHandle(null); // reset the TPB from the previous transaction. tpb = mcf.getDefaultTpb(); transactionIsolation = mcf.getDefaultTransactionIsolation(); } /** * Disassociate connections from current managed connection. * */ private void disassociateConnections() throws ResourceException { SQLExceptionChainBuilder chain = new SQLExceptionChainBuilder(); // Iterate over copy of list as connection.close() will remove connection List connectionHandleCopy = new ArrayList(connectionHandles); for (Iterator i = connectionHandleCopy.iterator(); i.hasNext();) { AbstractConnection connection = (AbstractConnection) i.next(); try { connection.close(); } catch(SQLException sqlex) { chain.append(sqlex); } } connectionHandles.clear(); if (chain.hasException()) throw new FBResourceException(chain.getException()); } /** * Creates a new connection handle for the underlying physical connection * represented by the ManagedConnection instance. This * connection handle is used by the application code to refer to the * underlying physical connection. A connection handle is tied to its * ManagedConnection instance in a resource adapter * implementation specific way. *

* * The ManagedConnection uses the Subject and additional * ConnectionRequestInfo (which is specific to resource * adapter and opaque to application server) to set the state of the * physical connection. * * @param subject * security context as JAAS subject * @param cri * ConnectionRequestInfo instance * @return generic Object instance representing the * connection handle. For CCI, the connection handle created by a * ManagedConnection instance is of the type * javax.resource.cci.Connection. * @throws ResourceException * generic exception if operation fails * @throws ResourceAdapterInternalException * resource adapter internal error condition * @throws javax.resource.spi.SecurityException * security related error condition * @throws CommException * failed communication with EIS instance * @throws EISSystemException * internal error condition in EIS instance - used if EIS * instance is involved in setting state of * ManagedConnection */ public Object getConnection(Subject subject, ConnectionRequestInfo cri) throws ResourceException { if (!matches(subject, cri)) throw new FBResourceException("Incompatible subject or " + "ConnectionRequestInfo in getConnection!"); if (!connectionSharing) disassociateConnections(); AbstractConnection c = mcf.newConnection(this); try { c.setManagedEnvironment(isManagedEnvironment()); connectionHandles.add(c); return c; } catch(SQLException ex) { throw new FBResourceException(ex); } } /** * Destroys the physical connection to the underlying resource manager. To * manage the size of the connection pool, an application server can * explictly call {@link ManagedConnection#destroy}to destroy a physical * connection. A resource adapter should destroy all allocated system * resources for this ManagedConnection instance when the * method destroy is called. * * @throws ResourceException * generic exception if operation failed * @throws javax.resource.spi.IllegalStateException * illegal state for destroying connection */ public void destroy() throws ResourceException { if (gdsHelper == null) return; if (gdsHelper.inTransaction()) throw new javax.resource.spi.IllegalStateException( "Can't destroy managed connection with active transaction"); try { gdsHelper.detachDatabase(); } catch (GDSException ge) { throw new FBResourceException("Can't detach from db.", ge); } finally { gdsHelper = null; } } /** * Return an XA resource to the caller. *

* In both javax.sql.XAConnection and * javax.resource.spi.MangagedConnection. * * @return the XAResource */ public XAResource getXAResource() { if (log != null) log.debug("XAResource requested from FBManagedConnection"); return this; } // -------------------------------------------------------------- // XAResource implementation // -------------------------------------------------------------- boolean isXidActive(Xid xid) { IscTrHandle trHandle = (IscTrHandle)xidMap.get(xid); //mcf.getTrHandleForXid(xid); if (trHandle == null) return false; IscDbHandle dbHandle = trHandle.getDbHandle(); if (dbHandle == null) return false; return dbHandle.isValid(); } /** * Commits a transaction. * * @throws XAException * Occurs when the state was not correct (end never called), the * transaction ID is wrong, the connection was set to * Auto-Commit, or the commit on the underlying connection * fails. The error code differs depending on the exact * situation. */ public void commit(Xid id, boolean onePhase) throws XAException { try { mcf.notifyCommit(this, id, onePhase); } catch (GDSException ge) { throw new XAException(ge.getXAErrorCode()); } } /** * The internalCommit method performs the requested commit * and may throw a GDSException to be interpreted by the caller. * * @param xid * a Xid value * @param onePhase * a boolean value * @exception GDSException * if an error occurs */ void internalCommit(Xid xid, boolean onePhase) throws XAException, GDSException { if (log != null) log.trace("Commit called: " + xid); AbstractIscTrHandle committingTr = (AbstractIscTrHandle)xidMap.get(xid); // check that prepare has NOT been called when onePhase = true if (onePhase && isPrepared(xid)) throw new FBXAException("Cannot commit one-phase when transaction has been prepared", XAException.XAER_PROTO); // check that prepare has been called when onePhase = false if (!onePhase && !isPrepared(xid)) throw new FBXAException("Cannot commit two-phase when transaction has not been prepared", XAException.XAER_PROTO); if (committingTr == null) throw new FBXAException("Commit called with unknown transaction", XAException.XAER_NOTA); if (committingTr == getGDSHelper().getCurrentTrHandle()) throw new FBXAException("Commit called with non-ended xid", XAException.XAER_PROTO); try { committingTr.forgetResultSets(); try { getGDSHelper().commitTransaction(committingTr); } catch (GDSException ge) { if (gdsHelper != null) { try { gdsHelper.rollbackTransaction(committingTr); } catch (GDSException ge2) { if (log != null) log.debug("Exception rolling back failed tx: ", ge2); } } else if (log != null) { log.warn("Unable to rollback failed tx, connection closed or lost"); } throw ge; } finally { xidMap.remove(xid); preparedXid.remove(xid); } } catch (GDSException ge) { ge.setXAErrorCode(XAException.XAER_RMERR); throw ge; } } private boolean isPrepared(Xid xid) { return preparedXid.contains(xid); } /** * Dissociates a resource from a global transaction. * * @throws XAException * Occurs when the state was not correct (end called twice), or * the transaction ID is wrong. */ public void end(Xid id, int flags) throws XAException { if (flags != XAResource.TMSUCCESS && flags != XAResource.TMFAIL && flags != XAResource.TMSUSPEND) throw new FBXAException("flag not allowed in this context: " + flags + ", valid flags are TMSUCCESS, TMFAIL, TMSUSPEND", XAException.XAER_PROTO); internalEnd(id, flags); mcf.notifyEnd(this, id); inDistributedTransaction = false; try { // This will reset the managed environment of the associated connections and set the transaction coordinator to local // TODO This is a bit of a hack; need to find a better way; this doesn't work with connectionSharing = true setManagedEnvironment(isManagedEnvironment()); } catch (ResourceException ex) { throw new FBXAException("Reset of managed state failed", XAException.XAER_RMERR); } } /** * The internalEnd method ends the xid as requested if * appropriate and throws a GDSException including the appropriate XA error * code and a message if not. The caller can decode the exception as * necessary. * * @param xid * a Xid value * @param flags * an int value * @exception XAException * if an error occurs */ void internalEnd(Xid xid, int flags) throws XAException { if (log != null) log.debug("End called: " + xid); IscTrHandle endingTr = (IscTrHandle)xidMap.get(xid); if (endingTr == null) throw new FBXAException("Unrecognized transaction", XAException.XAER_NOTA); if (flags == XAResource.TMFAIL) { try { gds.iscRollbackTransaction(endingTr); getGDSHelper().setCurrentTrHandle(null); } catch (GDSException ex) { throw new FBXAException("can't rollback transaction", XAException.XAER_RMFAIL, ex); } } else if (flags == XAResource.TMSUCCESS) { if (endingTr == gdsHelper.getCurrentTrHandle()) gdsHelper.setCurrentTrHandle(null); else throw new FBXAException("You are trying to end a transaction " + "that is not the current transaction", XAException.XAER_INVAL); } else if (flags == XAResource.TMSUSPEND) { if (endingTr == gdsHelper.getCurrentTrHandle()) gdsHelper.setCurrentTrHandle(null); else throw new FBXAException("You are trying to suspend a transaction " + "that is not the current transaction", XAException.XAER_INVAL); } } private final static String FORGET_FIND_QUERY = "SELECT RDB$TRANSACTION_ID, RDB$TRANSACTION_DESCRIPTION " + "FROM RDB$TRANSACTIONS WHERE RDB$TRANSACTION_STATE IN (2, 3)"; private final static String FORGET_DELETE_QUERY = "DELETE FROM RDB$TRANSACTIONS WHERE RDB$TRANSACTION_ID = "; /** * Indicates that no further action will be taken on behalf of this * transaction (after a heuristic failure). It is assumed this will be * called after a failed commit or rollback. * * @throws XAException * Occurs when the state was not correct (end never called), or * the transaction ID is wrong. */ public void forget(Xid id) throws XAException { long inLimboId = -1; try { // find XID AbstractIscTrHandle trHandle2 = (AbstractIscTrHandle)gds.createIscTrHandle(); gds.iscStartTransaction(trHandle2, getGDSHelper().getCurrentDbHandle(), tpb.getTransactionParameterBuffer()); AbstractIscStmtHandle stmtHandle2 = (AbstractIscStmtHandle)gds.createIscStmtHandle(); gds.iscDsqlAllocateStatement(getGDSHelper().getCurrentDbHandle(), stmtHandle2); GDSHelper gdsHelper2 = new GDSHelper(gds, getGDSHelper().getDatabaseParameterBuffer(), getGDSHelper().getCurrentDbHandle(), null); gdsHelper2.setCurrentTrHandle(trHandle2); gdsHelper2.prepareStatement(stmtHandle2, FORGET_FIND_QUERY, false); gdsHelper2.executeStatement(stmtHandle2, false); gdsHelper2.fetch(stmtHandle2, 10); DataProvider dataProvider0 = new DataProvider(stmtHandle2, 0); DataProvider dataProvider1 = new DataProvider(stmtHandle2, 1); FBField field0 = FBField.createField(stmtHandle2.getOutSqlda().sqlvar[0], dataProvider0, gdsHelper2, false); FBField field1 = FBField.createField(stmtHandle2.getOutSqlda().sqlvar[1], dataProvider1, gdsHelper2, false); field0.setConnection(gdsHelper2); field1.setConnection(gdsHelper2); int row = 0; while(row < stmtHandle2.getRows().length) { if (stmtHandle2.getRows()[row] == null) { row++; continue; } dataProvider0.setRow(row); dataProvider1.setRow(row); long inLimboTxId = field0.getLong(); byte[] inLimboMessage = field1.getBytes(); try { FBXid xid = new FBXid(new ByteArrayInputStream(inLimboMessage), inLimboTxId); boolean gtridEquals = Arrays.equals(xid.getGlobalTransactionId(), id.getGlobalTransactionId()); boolean bqualEquals = Arrays.equals(xid.getBranchQualifier(), id.getBranchQualifier()); if (gtridEquals && bqualEquals) { inLimboId = inLimboTxId; break; } } catch(FBIncorrectXidException ex) { if (log != null) log.warn("incorrect XID format in RDB$TRANSACTIONS where RDB$TRANSACTION_ID=" + inLimboTxId, ex); } row++; } gdsHelper2.closeStatement(stmtHandle2, true); gds.iscCommitTransaction(trHandle2); } catch (GDSException ex) { if (log != null) log.debug("can't perform query to fetch xids", ex); throw new FBXAException(XAException.XAER_RMFAIL, ex); } catch (SQLException ex) { if (log != null) log.debug("can't perform query to fetch xids", ex); throw new FBXAException(XAException.XAER_RMFAIL, ex); } catch (ResourceException ex) { if (log != null) log.debug("can't perform query to fetch xids", ex); throw new FBXAException(XAException.XAER_RMFAIL, ex); } if (inLimboId == -1) throw new FBXAException("XID not found", XAException.XAER_NOTA); // TODO: is XAER_NOTA the proper error code ? try { // delete XID AbstractIscTrHandle trHandle2 = (AbstractIscTrHandle)gds.createIscTrHandle(); gds.iscStartTransaction(trHandle2, getGDSHelper().getCurrentDbHandle(), tpb.getTransactionParameterBuffer()); AbstractIscStmtHandle stmtHandle2 = (AbstractIscStmtHandle)gds.createIscStmtHandle(); gds.iscDsqlAllocateStatement(getGDSHelper().getCurrentDbHandle(), stmtHandle2); stmtHandle2 = (AbstractIscStmtHandle)gds.createIscStmtHandle(); gds.iscDsqlAllocateStatement(getGDSHelper().getCurrentDbHandle(), stmtHandle2); GDSHelper gdsHelper2 = new GDSHelper(gds, getGDSHelper().getDatabaseParameterBuffer(), getGDSHelper().getCurrentDbHandle(), null); gdsHelper2.setCurrentTrHandle(trHandle2); gdsHelper2.prepareStatement(stmtHandle2, FORGET_DELETE_QUERY + inLimboId, false); gdsHelper2.executeStatement(stmtHandle2, false); gdsHelper2.closeStatement(stmtHandle2, true); gds.iscCommitTransaction(trHandle2); } catch (GDSException ex) { throw new FBXAException("can't perform query to fetch xids", XAException.XAER_RMFAIL, ex); } catch (SQLException ex) { throw new FBXAException("can't perform query to fetch xids", XAException.XAER_RMFAIL, ex); } } /** * Gets the transaction timeout. */ public int getTransactionTimeout() throws javax.transaction.xa.XAException { return timeout; } /** * Retrieve whether this FBManagedConnection uses the same * ResourceManager as res. This method relies on * res being a Firebird implementation of * XAResource. * * @param res * The other XAResource to compare to * @return true if res uses the same * ResourceManager, false otherwise */ public boolean isSameRM(XAResource res) throws XAException { return (res instanceof FBManagedConnection) && (dbHandle.equals(((FBManagedConnection) res).dbHandle)); } /** * Prepares a transaction to commit. * * @throws XAException * Occurs when the state was not correct (end never called), the * transaction ID is wrong, or the connection was set to * Auto-Commit. */ public int prepare(Xid xid) throws javax.transaction.xa.XAException { try { return mcf.notifyPrepare(this, xid); } catch (GDSException ge) { throw new FBXAException(XAException.XAER_RMERR, ge); } } int internalPrepare(Xid xid) throws FBXAException, GDSException { if (log != null) log.trace("prepare called: " + xid); AbstractIscTrHandle committingTr = (AbstractIscTrHandle)xidMap.get(xid); if (committingTr == null) throw new FBXAException("Prepare called with unknown transaction", XAException.XAER_NOTA); if (committingTr == getGDSHelper().getCurrentTrHandle()) throw new FBXAException("Prepare called with non-ended xid", XAException.XAER_PROTO); try { FBXid fbxid; if (xid instanceof FBXid) { fbxid = (FBXid) xid; } else { fbxid = new FBXid(xid); } byte[] message = fbxid.toBytes(); getGDSHelper().prepareTransaction(committingTr, message); } catch (GDSException ge) { try { if (gdsHelper != null) { gdsHelper.rollbackTransaction(committingTr); } else if (log != null) { log.warn("Unable to rollback failed tx, connection closed or lost"); } } catch (GDSException ge2) { if (log != null) log.debug("Exception rolling back failed tx: ", ge2); } finally { xidMap.remove(xid); } if (log != null) log.warn("error in prepare", ge); throw ge; } preparedXid.add(xid); return XA_OK; } private static final String RECOVERY_QUERY = "SELECT RDB$TRANSACTION_ID, RDB$TRANSACTION_DESCRIPTION " + "FROM RDB$TRANSACTIONS"; /** * Obtain a list of prepared transaction branches from a resource manager. * The transaction manager calls this method during recovery to obtain the * list of transaction branches that are currently in prepared or * heuristically completed states. * * @param flags * One of TMSTARTRSCAN, TMENDRSCAN, TMNOFLAGS. TMNOFLAGS must be * used when no other flags are set in flags. * @return The resource manager returns zero or more XIDs for the * transaction branches that are currently in a prepared or * heuristically completed state. If an error occurs during the * operation, the resource manager should throw the appropriate * XAException. * @throws XAException * An error has occurred. Possible values are XAER_RMERR, * XAER_RMFAIL, XAER_INVAL, and XAER_PROTO. */ public Xid[] recover(int flags) throws javax.transaction.xa.XAException { if (flags != XAResource.TMSTARTRSCAN && flags != XAResource.TMENDRSCAN && flags != XAResource.TMNOFLAGS && flags != (XAResource.TMSTARTRSCAN|XAResource.TMENDRSCAN)) throw new FBXAException("flag not allowed in this context: " + flags + ", valid flags are TMSTARTRSCAN, TMENDRSCAN, TMNOFLAGS, TMSTARTRSCAN|TMENDRSCAN", XAException.XAER_PROTO); try { // if (!((flags & XAResource.TMSTARTRSCAN) == 0)) // if ((flags & XAResource.TMENDRSCAN) == 0 && (flags & XAResource.TMNOFLAGS) == 0) // return new Xid[0]; ArrayList xids = new ArrayList(); AbstractIscTrHandle trHandle2 = (AbstractIscTrHandle)gds.createIscTrHandle(); gds.iscStartTransaction(trHandle2, getGDSHelper().getCurrentDbHandle(), tpb.getTransactionParameterBuffer()); AbstractIscStmtHandle stmtHandle2 = (AbstractIscStmtHandle)gds.createIscStmtHandle(); gds.iscDsqlAllocateStatement(getGDSHelper().getCurrentDbHandle(), stmtHandle2); GDSHelper gdsHelper2 = new GDSHelper(gds, getGDSHelper().getDatabaseParameterBuffer(), getGDSHelper().getCurrentDbHandle(), null); gdsHelper2.setCurrentTrHandle(trHandle2); gdsHelper2.prepareStatement(stmtHandle2, RECOVERY_QUERY, false); gdsHelper2.executeStatement(stmtHandle2, false); gdsHelper2.fetch(stmtHandle2, 10); DataProvider dataProvider0 = new DataProvider(stmtHandle2, 0); DataProvider dataProvider1 = new DataProvider(stmtHandle2, 1); FBField field0 = FBField.createField(stmtHandle2.getOutSqlda().sqlvar[0], dataProvider0, gdsHelper2, false); FBField field1 = FBField.createField(stmtHandle2.getOutSqlda().sqlvar[1], dataProvider1, gdsHelper2, false); field0.setConnection(gdsHelper2); field1.setConnection(gdsHelper2); int row = 0; while(row < stmtHandle2.getRows().length) { if (stmtHandle2.getRows()[row] == null) { row++; continue; } dataProvider0.setRow(row); dataProvider1.setRow(row); long inLimboTxId = field0.getLong(); byte[] inLimboMessage = field1.getBytes(); try { FBXid xid = new FBXid(new ByteArrayInputStream(inLimboMessage), inLimboTxId); xids.add(xid); } catch(FBIncorrectXidException ex) { if (log != null) log.warn("ignoring XID stored with invalid format in RDB$TRANSACTIONS for RDB$TRANSACTION_ID=" + inLimboTxId); } row++; } gdsHelper2.closeStatement(stmtHandle2, true); gds.iscCommitTransaction(trHandle2); return (FBXid[])xids.toArray(new FBXid[xids.size()]); } catch(GDSException ex) { throw new FBXAException("can't perform query to fetch xids", XAException.XAER_RMFAIL, ex); } catch (SQLException sqle) { throw new FBXAException("can't perform query to fetch xids", XAException.XAER_RMFAIL, sqle); } catch (ResourceException re) { throw new FBXAException("can't perform query to fetch xids", XAException.XAER_RMFAIL, re); } } private static final String RECOVERY_QUERY_PARAMETRIZED = "SELECT RDB$TRANSACTION_ID, RDB$TRANSACTION_DESCRIPTION " + "FROM RDB$TRANSACTIONS " + "WHERE RDB$TRANSACTION_DESCRIPTION = CAST(? AS VARCHAR(32764) CHARACTER SET OCTETS)"; /** * Obtain a single prepared transaction branch from a resource manager, based on a Xid * * @param externalXid * The Xid to find * @return The Xid if found, otherwise null. * @throws XAException * An error has occurred. Possible values are XAER_RMERR, * XAER_RMFAIL, XAER_INVAL, and XAER_PROTO. */ protected Xid findSingleXid(Xid externalXid) throws javax.transaction.xa.XAException { try { AbstractIscTrHandle trHandle2 = (AbstractIscTrHandle)gds.createIscTrHandle(); gds.iscStartTransaction(trHandle2, getGDSHelper().getCurrentDbHandle(), tpb.getTransactionParameterBuffer()); AbstractIscStmtHandle stmtHandle2 = (AbstractIscStmtHandle)gds.createIscStmtHandle(); gds.iscDsqlAllocateStatement(getGDSHelper().getCurrentDbHandle(), stmtHandle2); GDSHelper gdsHelper2 = new GDSHelper(gds, getGDSHelper().getDatabaseParameterBuffer(), getGDSHelper().getCurrentDbHandle(), null); gdsHelper2.setCurrentTrHandle(trHandle2); gdsHelper2.prepareStatement(stmtHandle2, RECOVERY_QUERY_PARAMETRIZED, true); FBXid tempXid = new FBXid(externalXid); stmtHandle2.getInSqlda().sqlvar[0].sqldata = tempXid.toBytes(); gdsHelper2.executeStatement(stmtHandle2, false); gdsHelper2.fetch(stmtHandle2, 1); DataProvider dataProvider0 = new DataProvider(stmtHandle2, 0); DataProvider dataProvider1 = new DataProvider(stmtHandle2, 1); FBField field0 = FBField.createField(stmtHandle2.getOutSqlda().sqlvar[0], dataProvider0, gdsHelper2, false); FBField field1 = FBField.createField(stmtHandle2.getOutSqlda().sqlvar[1], dataProvider1, gdsHelper2, false); field0.setConnection(gdsHelper2); field1.setConnection(gdsHelper2); FBXid xid = null; if (stmtHandle2.getRows().length > 0) { dataProvider0.setRow(0); dataProvider1.setRow(0); long inLimboTxId = field0.getLong(); byte[] inLimboMessage = field1.getBytes(); try { xid = new FBXid(new ByteArrayInputStream(inLimboMessage), inLimboTxId); } catch(FBIncorrectXidException ex) { if (log != null) log.warn("ignoring XID stored with invalid format in RDB$TRANSACTIONS for RDB$TRANSACTION_ID=" + inLimboTxId); } } gdsHelper2.closeStatement(stmtHandle2, true); gds.iscCommitTransaction(trHandle2); return xid; } catch(GDSException ex) { throw new FBXAException("can't perform query to fetch xids", XAException.XAER_RMFAIL, ex); } catch (SQLException sqle) { throw new FBXAException("can't perform query to fetch xids", XAException.XAER_RMFAIL, sqle); } catch (ResourceException re) { throw new FBXAException("can't perform query to fetch xids", XAException.XAER_RMFAIL, re); } } private static class DataProvider implements FieldDataProvider { private AbstractIscStmtHandle stmtHandle; private int fieldPos; private int row; private DataProvider(AbstractIscStmtHandle stmtHandle, int fieldPos) { this.stmtHandle = stmtHandle; this.fieldPos = fieldPos; } public void setRow(int row) { this.row = row; } public byte[] getFieldData() { return ((byte[][])stmtHandle.getRows()[row])[fieldPos]; } public void setFieldData(byte[] data) { throw new UnsupportedOperationException(); } } /** * Rolls back the work, assuming it was done on behalf of the specified * transaction. * * @throws XAException * Occurs when the state was not correct (end never called), the * transaction ID is wrong, the connection was set to * Auto-Commit, or the rollback on the underlying connection * fails. The error code differs depending on the exact * situation. */ public void rollback(Xid xid) throws XAException { try { mcf.notifyRollback(this, xid); } catch (GDSException ge) { throw new FBXAException(ge.getXAErrorCode(), ge); } } void internalRollback(Xid xid) throws XAException, GDSException { if (log != null) log.trace("rollback called: " + xid); AbstractIscTrHandle committingTr = (AbstractIscTrHandle)xidMap.get(xid); //mcf.getTrHandleForXid(id); if (committingTr == null) { throw new FBXAException ("Rollback called with unknown transaction: " + xid); } if (committingTr == getGDSHelper().getCurrentTrHandle()) throw new FBXAException("Rollback called with non-ended xid", XAException.XAER_PROTO); try { committingTr.forgetResultSets(); try { getGDSHelper().rollbackTransaction(committingTr); } finally { xidMap.remove(xid); preparedXid.remove(xid); } } catch (GDSException ge) { if (log != null) log.debug("Exception in rollback", ge); ge.setXAErrorCode(XAException.XAER_RMERR); throw ge; } } /** * Sets the transaction timeout. This is saved, but the value is not used by * the current implementation. * * @param timeout * The timeout to be set in seconds */ public boolean setTransactionTimeout(int timeout) throws javax.transaction.xa.XAException { this.timeout = timeout; return true; } public boolean inDistributedTransaction() { return inDistributedTransaction; } /** * Associates a JDBC connection with a global transaction. We assume that * end will be called followed by prepare, commit, or rollback. If start is * called after end but before commit or rollback, there is no way to * distinguish work done by different transactions on the same connection). * If start is called more than once before end, either it's a duplicate * transaction ID or illegal transaction ID (since you can't have two * transactions associated with one DB connection). * * * @param id * A global transaction identifier to be associated with the * resource * @param flags * One of TMNOFLAGS, TMJOIN, or TMRESUME * @throws XAException * Occurs when the state was not correct (start called twice), * the transaction ID is wrong, or the instance has already been * closed. */ public void start(Xid id, int flags) throws XAException { if (flags != XAResource.TMNOFLAGS && flags != XAResource.TMJOIN && flags != XAResource.TMRESUME) throw new FBXAException("flag not allowed in this context: " + flags + ", valid flags are TMNOFLAGS, TMJOIN, TMRESUME", XAException.XAER_PROTO); if (flags == XAResource.TMJOIN) throw new FBXAException("Joining two transactions is not supported", XAException.XAER_RMFAIL); try { // reset the transaction parameters for the managed scenario setTransactionIsolation(mcf.getDefaultTransactionIsolation()); internalStart(id, flags); mcf.notifyStart(this, id); inDistributedTransaction = true; // This will reset the managed environment of the associated connections and set the transaction coordinator to managed // TODO This is a bit of a hack; need to find a better way; this doesn't work with connectionSharing = true setManagedEnvironment(isManagedEnvironment()); } catch (GDSException ge) { throw new FBXAException(ge.getXAErrorCode()); } catch(ResourceException ex) { throw new FBXAException(XAException.XAER_RMERR, ex); } } /** * Perform the internal processing to start associate a JDBC connection with * a global transaction. * * @see #start(Xid, int) * @param id * A global transaction identifier to be associated with the * resource * @param flags * One of TMNOFLAGS, TMJOIN, or TMRESUME * @throws XAException If the transaction is already started, or this connection cannot participate in the distributed transaction * @throws GDSException */ public void internalStart(Xid id, int flags) throws XAException, GDSException { if (log != null) log.trace("start called: " + id); if (getGDSHelper().getCurrentTrHandle() != null) throw new FBXAException("Transaction already started", XAException.XAER_PROTO); findIscTrHandle(id, flags); } // FB public methods. Could be package if packages reorganized. /** * Close this connection with regards to a wrapping * AbstractConnection. * * @param c * The AbstractConnection that is being closed */ public void close(AbstractConnection c) { c.setManagedConnection(null); connectionHandles.remove(c); ConnectionEvent ce = new ConnectionEvent(this, ConnectionEvent.CONNECTION_CLOSED, null); ce.setConnectionHandle(c); notify(connectionClosedNotifier, ce); } /** * Get information about the current connection parameters. * * @return instance of {@link FBConnectionRequestInfo}. */ public FBConnectionRequestInfo getConnectionRequestInfo() { return cri; } public TransactionParameterBuffer getTransactionParameters() { return tpb.getTransactionParameterBuffer(); } public void setTransactionParameters(TransactionParameterBuffer transactionParameters) { tpb.setTransactionParameterBuffer(transactionParameters); } public TransactionParameterBuffer getTransactionParameters(int isolation) { return mcf.getTransactionParameters(isolation); } public void setTransactionParameters(int isolation, TransactionParameterBuffer transactionParams) { mcf.setTransactionParameters(isolation, transactionParams); } // -------------------------------------------------------------------- // package visibility // -------------------------------------------------------------------- private void findIscTrHandle(Xid xid, int flags) throws GDSException, XAException { // FIXME return old tr handle if it is still valid before proceeding getGDSHelper().setCurrentTrHandle(null); if (flags == XAResource.TMRESUME) { AbstractIscTrHandle trHandle = (AbstractIscTrHandle) xidMap.get(xid); if (trHandle == null) { throw new FBXAException( "You are trying to resume a transaction that is not attached to this XAResource", XAException.XAER_INVAL); } getGDSHelper().setCurrentTrHandle(trHandle); return; } Iterator it = xidMap.keySet().iterator(); while (it.hasNext()) { Xid knownXid = (Xid) it.next(); boolean sameFormatId = knownXid.getFormatId() == xid.getFormatId(); boolean sameGtrid = Arrays.equals(knownXid.getGlobalTransactionId(), xid.getGlobalTransactionId()); boolean sameBqual = Arrays.equals(knownXid.getBranchQualifier(), xid.getBranchQualifier()); if (sameFormatId && sameGtrid && sameBqual) throw new FBXAException( "A transaction with the same XID has already been started", XAException.XAER_DUPID); } // new xid for us AbstractIscTrHandle trHandle = getGDSHelper().startTransaction(tpb.getTransactionParameterBuffer()); xidMap.put(xid, trHandle); } void notify(CELNotifier notifier, ConnectionEvent ce) { if (connectionEventListeners.size() == 0) { return; } if (connectionEventListeners.size() == 1) { ConnectionEventListener cel = (ConnectionEventListener) connectionEventListeners .get(0); notifier.notify(cel, ce); return; } // end of if () ArrayList cels = (ArrayList) connectionEventListeners.clone(); for (Iterator i = cels.iterator(); i.hasNext();) { notifier.notify((ConnectionEventListener) i.next(), ce); } // end of for () } interface CELNotifier { void notify(ConnectionEventListener cel, ConnectionEvent ce); } static final CELNotifier connectionClosedNotifier = new CELNotifier() { public void notify(ConnectionEventListener cel, ConnectionEvent ce) { cel.connectionClosed(ce); } }; static final CELNotifier connectionErrorOccurredNotifier = new CELNotifier() { public void notify(ConnectionEventListener cel, ConnectionEvent ce) { cel.connectionErrorOccurred(ce); } }; static final CELNotifier localTransactionStartedNotifier = new CELNotifier() { public void notify(ConnectionEventListener cel, ConnectionEvent ce) { cel.localTransactionStarted(ce); } }; static final CELNotifier localTransactionCommittedNotifier = new CELNotifier() { public void notify(ConnectionEventListener cel, ConnectionEvent ce) { cel.localTransactionCommitted(ce); } }; static final CELNotifier localTransactionRolledbackNotifier = new CELNotifier() { public void notify(ConnectionEventListener cel, ConnectionEvent ce) { cel.localTransactionRolledback(ce); } }; boolean matches(Subject subj, ConnectionRequestInfo cri) { if (cri == null) { return true; } if (!(cri instanceof FBConnectionRequestInfo)) return false; try { return this.cri.equals(getCombinedConnectionRequestInfo(subj, (FBConnectionRequestInfo)cri)); } catch (ResourceException re) { return false; } } /** * Get the transaction isolation level of this connection. The level is one * of the static final fields of java.sql.Connection (i.e. * TRANSACTION_READ_COMMITTED, * TRANSACTION_READ_UNCOMMITTED, * TRANSACTION_REPEATABLE_READ, * TRANSACTION_SERIALIZABLE. * * @see java.sql.Connection * @see #setTransactionIsolation(int) * @return Value representing a transaction isolation level defined in * {@link java.sql.Connection}. * @throws ResourceException * If the transaction level cannot be retrieved */ public int getTransactionIsolation() throws ResourceException { return transactionIsolation; } /** * Set the transaction level for this connection. The level is one of the * static final fields of java.sql.Connection (i.e. * TRANSACTION_READ_COMMITTED, * TRANSACTION_READ_UNCOMMITTED, * TRANSACTION_REPEATABLE_READ, * TRANSACTION_SERIALIZABLE. * * @see java.sql.Connection * @see #getTransactionIsolation() * @param isolation * Value representing a transaction isolation level defined in * {@link java.sql.Connection}. * @throws ResourceException * If the transaction level cannot be retrieved */ public void setTransactionIsolation(int isolation) throws ResourceException { transactionIsolation = isolation; tpb = mcf.getTpb(isolation); } /** * Get the managed connection factory that created this managed connection. * * @return instance of {@link ManagedConnectionFactory}. */ public ManagedConnectionFactory getManagedConnectionFactory() { return mcf; } /** * Set whether this connection is to be readonly * * @param readOnly * If true, the connection will be set read-only, * otherwise it will be writable */ public void setReadOnly(boolean readOnly) { tpb.setReadOnly(readOnly); } /** * Retrieve whether this connection is readonly. * * @return true if this connection is readonly, * false otherwise */ public boolean isReadOnly() { return tpb.isReadOnly(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy