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

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

There is a newer version: 2.2.7
Show newest version
/*
 * $Id: FBManagedConnectionFactory.java 57608 2013-01-19 14:14:44Z mrotteveel $
 * 
 * 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.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.util.*;

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

import org.firebirdsql.gds.*;
import org.firebirdsql.gds.impl.*;
import org.firebirdsql.jdbc.*;

/**
 * FBManagedConnectionFactory implements the jca ManagedConnectionFactory
 * interface and also many of the internal functions of ManagedConnection. This
 * nonstandard behavior is required due to firebird requiring all work done in a
 * transaction to be done over one connection. To support xa semantics, the
 * correct db handle must be located whenever a ManagedConnection is associated
 * with an xid.
 * 
 * WARNING: this adapter will probably not work properly in an environment where
 * ManagedConnectionFactory is serialized and deserialized, and the deserialized
 * copy is expected to function as anything other than a key.
 * 
 * @author David Jencks 
 */

public class FBManagedConnectionFactory implements ManagedConnectionFactory,
        Serializable, FirebirdConnectionProperties {
    
    private static final long serialVersionUID = 7500832904323015501L;

    /**
     * The mcfInstances weak hash map is used in deserialization
     * to find the correct instance of a mcf after deserializing.
     */
    private final static Map mcfInstances = Collections.synchronizedMap(new WeakHashMap());

    // /**
    // * @todo Claudio suggests this should be 1024*64 -1, we should find out I
    // * thought this was the largest value I could make work, but I didn't
    // * write down my experiments.
    // */
    // public final static int MAX_BLOB_BUFFER_LENGTH = 1024 * 32 - 1;
    //
    // public final static int MIN_BLOB_BUFFER_LENGTH = 1024;


    private ConnectionManager defaultCm;
    private int hashCode;
    private GDSType gdsType;

    // Maps supplied XID to internal transaction handle.
    // a concurrent reader map would be better
    private transient final Map xidMap = Collections.synchronizedMap(new HashMap());

    private transient final Object startLock = new Object();
    private transient boolean started = false;

    private FBConnectionProperties connectionProperties;

    /**
     * Create a new pure-Java FBManagedConnectionFactory.
     */
    public FBManagedConnectionFactory() {
        this(GDSFactory.getDefaultGDSType(), new FBConnectionProperties());
    }

    /**
     * Create a new FBManagedConnectionFactory based around the given GDSType.
     * 
     * @param gdsType
     *            The GDS implementation to use
     */
    public FBManagedConnectionFactory(GDSType gdsType) {
        this(gdsType, new FBConnectionProperties());
    }
    
    public FBManagedConnectionFactory(GDSType gdsType, FBConnectionProperties connectionProperties) {
        this.defaultCm = new FBStandAloneConnectionManager();
        this.connectionProperties = connectionProperties;
        setType(gdsType.toString());
    }

    public GDS getGDS() {
        return GDSFactory.getGDSForType(getGDSType());
    }

    /**
     * Get the GDS implementation type around which this factory is based.
     * 
     * @return The GDS implementation type
     */
    public GDSType getGDSType() {
        if (gdsType != null)
            return gdsType;
        
        gdsType = GDSType.getType(getType());
        
        return gdsType;
    }
    
    /**
     * @deprecated use {@link #getBlobBufferSize()}
     */
    public int getBlobBufferLength() {
        return getBlobBufferSize();
    }
    
    /**
     * @deprecated use {@link #setBlobBufferSize(int)}
     */
    public void setBlobBufferLength(int value) {
        setBlobBufferSize(value);
    }
    
    /**
     * @deprecated use {@link #getDefaultTransactionIsolation()}
     */
    public Integer getTransactionIsolation() {
        return Integer.valueOf(getDefaultTransactionIsolation());
    }
    
    /**
     * @deprecated use {@link #setDefaultTransactionIsolation(int)}
     */
    public void setTransactionIsolation(Integer value) {
        if (value != null)
            setDefaultTransactionIsolation(value.intValue());
    }
    
    /**
     * @deprecated use {@link #getDefaultIsolation()}
     */
    public String getTransactionIsolationName() {
        return getDefaultIsolation();
    }

    /**
     * @deprecated use {@link #setDefaultIsolation(String)} 
     */
    public void setTransactionIsolationName(String name) {
        setDefaultIsolation(name);
    }
    
    /**
     * @deprecated use {@link #getCharSet()} instead.
     */
    public String getLocalEncoding() {
        return getCharSet();
    }
    
    /**
     * @deprecated use {@link #setCharSet(String)} instead.
     */
    public void setLocalEncoding(String localEncoding) {
        setCharSet(localEncoding);
    }
    
    public int getBlobBufferSize() {
        return connectionProperties.getBlobBufferSize();
    }

    public int getBuffersNumber() {
        return connectionProperties.getBuffersNumber();
    }

    public String getCharSet() {
        return connectionProperties.getCharSet();
    }

    public String getDatabase() {
        return connectionProperties.getDatabase();
    }

    public DatabaseParameterBuffer getDatabaseParameterBuffer() throws SQLException {
        return connectionProperties.getDatabaseParameterBuffer();
    }

    public String getDefaultIsolation() {
        return connectionProperties.getDefaultIsolation();
    }

    public int getDefaultTransactionIsolation() {
        return connectionProperties.getDefaultTransactionIsolation();
    }

    public String getEncoding() {
        return connectionProperties.getEncoding();
    }

    public String getNonStandardProperty(String key) {
        return connectionProperties.getNonStandardProperty(key);
    }

    public String getPassword() {
        return connectionProperties.getPassword();
    }

    public String getRoleName() {
        return connectionProperties.getRoleName();
    }

    public int getSocketBufferSize() {
        return connectionProperties.getSocketBufferSize();
    }

    public String getSqlDialect() {
        return connectionProperties.getSqlDialect();
    }

    public String getTpbMapping() {
        return connectionProperties.getTpbMapping();
    }

    public TransactionParameterBuffer getTransactionParameters(int isolation) {
        return connectionProperties.getTransactionParameters(isolation);
    }

    public String getType() {
        return connectionProperties.getType();
    }

    public String getUserName() {
        return connectionProperties.getUserName();
    }

    public String getUseTranslation() {
        return connectionProperties.getUseTranslation();
    }

    public boolean isTimestampUsesLocalTimezone() {
        return connectionProperties.isTimestampUsesLocalTimezone();
    }

    public boolean isUseStandardUdf() {
        return connectionProperties.isUseStandardUdf();
    }

    public boolean isUseStreamBlobs() {
        return connectionProperties.isUseStreamBlobs();
    }

    public void setBlobBufferSize(int bufferSize) {
        connectionProperties.setBlobBufferSize(bufferSize);
    }

    public void setBuffersNumber(int buffersNumber) {
        connectionProperties.setBuffersNumber(buffersNumber);
    }

    public void setCharSet(String charSet) {
        connectionProperties.setCharSet(charSet);
    }

    public void setDatabase(String database) {
        connectionProperties.setDatabase(database);
    }

    public void setDefaultIsolation(String isolation) {
        connectionProperties.setDefaultIsolation(isolation);
    }

    public void setDefaultTransactionIsolation(int defaultIsolationLevel) {
        connectionProperties.setDefaultTransactionIsolation(defaultIsolationLevel);
    }

    public void setEncoding(String encoding) {
        connectionProperties.setEncoding(encoding);
    }

    public void setNonStandardProperty(String key, String value) {
        connectionProperties.setNonStandardProperty(key, value);        
    }

    public void setNonStandardProperty(String propertyMapping) {
        connectionProperties.setNonStandardProperty(propertyMapping);
    }

    public void setPassword(String password) {
        connectionProperties.setPassword(password);
    }

    public void setRoleName(String roleName) {
        connectionProperties.setRoleName(roleName);        
    }

    public void setSocketBufferSize(int socketBufferSize) {
        connectionProperties.setSocketBufferSize(socketBufferSize);        
    }

    public void setSqlDialect(String sqlDialect) {
        connectionProperties.setSqlDialect(sqlDialect);
    }

    public void setTimestampUsesLocalTimezone(boolean timestampUsesLocalTimezone) {
        connectionProperties.setTimestampUsesLocalTimezone(timestampUsesLocalTimezone);        
    }

    public void setTpbMapping(String tpbMapping) {
        connectionProperties.setTpbMapping(tpbMapping);        
    }

    public void setTransactionParameters(int isolation, TransactionParameterBuffer tpb) {
        connectionProperties.setTransactionParameters(isolation, tpb);        
    }

    public void setType(String type) {
        if (gdsType != null)
            throw new java.lang.IllegalStateException(
                    "Cannot change GDS type at runtime.");
        
        connectionProperties.setType(type);
    }

    public void setUserName(String userName) {
        connectionProperties.setUserName(userName);        
    }

    public void setUseStandardUdf(boolean useStandardUdf) {
        connectionProperties.setUseStandardUdf(useStandardUdf);        
    }

    public void setUseStreamBlobs(boolean useStreamBlobs) {
        connectionProperties.setUseStreamBlobs(useStreamBlobs);        
    }

    public void setUseTranslation(String translationPath) {
        connectionProperties.setUseTranslation(translationPath);        
    }

    public boolean isDefaultResultSetHoldable() {
        return connectionProperties.isDefaultResultSetHoldable();
    }

    public void setDefaultResultSetHoldable(boolean isHoldable) {
        connectionProperties.setDefaultResultSetHoldable(isHoldable);
    }

    public void setDefaultConnectionManager(ConnectionManager defaultCm) {
        this.defaultCm = defaultCm;
    }
    
    public int getSoTimeout() {
        return connectionProperties.getSoTimeout();
    }

    public void setSoTimeout(int soTimeout) {
        connectionProperties.setSoTimeout(soTimeout);
    }
    
    public int getConnectTimeout() {
        return connectionProperties.getConnectTimeout();
    }
    
    public void setConnectTimeout(int connectTimeout) {
        connectionProperties.setConnectTimeout(connectTimeout);
    }

    public int hashCode() {
        if (hashCode != 0) 
            return hashCode;

        int result = 17;
        result = 37 * result + connectionProperties.hashCode();
        if (result == 0)
            result = 17;
        
        if (gdsType != null)
            hashCode = result;
        
        return result;
    }

    public boolean equals(Object other) {
        if (other == this) return true;

        if (!(other instanceof FBManagedConnectionFactory)) return false;

        FBManagedConnectionFactory that = (FBManagedConnectionFactory) other;

        return this.connectionProperties.equals(that.connectionProperties);
    }
    
    public FBConnectionRequestInfo getDefaultConnectionRequestInfo() throws ResourceException {
        try {
            return new FBConnectionRequestInfo(getDatabaseParameterBuffer().deepCopy());
        } catch(SQLException ex) {
            throw new FBResourceException(ex);
        }
    }
    
    public FBTpb getDefaultTpb() throws ResourceException {
        int defaultTransactionIsolation = 
            connectionProperties.getMapper().getDefaultTransactionIsolation();

        return getTpb(defaultTransactionIsolation);
    }

    public FBTpb getTpb(int defaultTransactionIsolation) throws FBResourceException {
        return new FBTpb(connectionProperties.getMapper().getMapping(
                defaultTransactionIsolation));
    }

    /**
     * The createConnectionFactory method creates a DataSource
     * using the supplied ConnectionManager.
     * 
     * @param cxManager
     *            a ConnectionManager value
     * @return a java.lang.Object value
     * @exception ResourceException
     *                if an error occurs
     */
    public Object createConnectionFactory(ConnectionManager cxManager)
            throws ResourceException {
        start();
        return new FBDataSource(this, cxManager);
    }

    /**
     * The createConnectionFactory method creates a DataSource
     * with a default stand alone ConnectionManager. Ours can implement pooling.
     * 
     * @return a new javax.sql.DataSource based around this
     *         connection factory
     * @exception ResourceException
     *                if an error occurs
     */
    public Object createConnectionFactory() throws ResourceException {
        start();
        return new FBDataSource(this, defaultCm);
    }

    /**
     * Creates a new physical connection to the underlying EIS resource manager,
     * ManagedConnectionFactory uses the security information (passed as
     * Subject) and additional ConnectionRequestInfo (which is specific to
     * ResourceAdapter and opaque to application server) to create this new
     * connection.
     * 
     * @param subject
     *            Caller's security information
     * @param cri
     *            Additional resource adapter specific connection request
     *            information
     * @return ManagedConnection instance
     * @throws ResourceException
     *             generic exception
     * @throws SecurityException
     *             security related error
     * @throws ResourceAllocationException
     *             failed to allocate system resources for connection request
     * @throws ResourceAdapterInternalException
     *             resource adapter related error condition
     * @throws EISSystemException
     *             internal error condition in EIS instance
     */
    public ManagedConnection createManagedConnection(Subject subject,
            ConnectionRequestInfo cri) throws ResourceException {
        start();
        return new FBManagedConnection(subject, cri, this);
    }

    /**
     * Returns a matched connection from the candidate set of connections.
     * ManagedConnectionFactory uses the security info (as in
     * Subject) and information provided through
     * ConnectionRequestInfo and additional Resource Adapter
     * specific criteria to do matching. Note that criteria used for matching is
     * specific to a resource adapter and is not prescribed by the
     * Connector specification.
     * 

* This method returns a ManagedConnection instance that is * the best match for handling the connection allocation request. * * @param connectionSet * candidate connection set * @param subject * caller's security information * @param cxRequestInfo * additional resource adapter specific connection request * information * @return ManagedConnection if resource adapter finds an acceptable match * otherwise null * @throws ResourceException - * generic exception * @throws SecurityException - * security related error * @throws ResourceAdapterInternalException - * resource adapter related error condition * @throws NotSupportedException - * if operation is not supported */ public ManagedConnection matchManagedConnections(Set connectionSet, javax.security.auth.Subject subject, ConnectionRequestInfo cxRequestInfo) throws ResourceException { Iterator i = connectionSet.iterator(); while (i.hasNext()) { FBManagedConnection mc = (FBManagedConnection) i.next(); if (mc.matches(subject, (FBConnectionRequestInfo) cxRequestInfo)) return mc; } return null; } /** * Set the log writer for this ManagedConnectionFactory * instance. The log writer is a character output stream to which all * logging and tracing messages for this * ManagedConnectionFactory instance will be printed. * ApplicationServer manages the association of output stream with the * ManagedConnectionFactory. When a * ManagedConnectionFactory object is created the log writer * is initially null, in other words, logging is disabled. * Once a log writer is associated with a * ManagedConnectionFactory, logging and tracing for * ManagedConnectionFactory instance is enabled. *

* The ManagedConnection instances created by * ManagedConnectionFactory "inherits" the log writer, which * can be overridden by ApplicationServer using * {@link ManagedConnection#setLogWriter}to set * ManagedConnection specific logging and tracing. * * @param out * an out stream for error logging and tracing * @throws ResourceException * generic exception * @throws ResourceAdapterInternalException * resource adapter related error condition */ public void setLogWriter(PrintWriter out) throws ResourceException { // ignore - we're using log4j } /** * Get the log writer for this ManagedConnectionFactory * instance. The log writer is a character output stream to which all * logging and tracing messages for this * ManagedConnectionFactory instance will be printed. * ApplicationServer manages the association of output stream with the * ManagedConnectionFactory. When a * ManagedConnectionFactory object is created the log writer * is initially null, in other words, logging is disabled. * * @return PrintWriter instance * @throws ResourceException * generic exception */ public PrintWriter getLogWriter() { return null;// we're using log4j } // Serialization support private Object readResolve() throws ObjectStreamException { FBManagedConnectionFactory mcf = (FBManagedConnectionFactory) mcfInstances.get(this); if (mcf != null) return mcf; mcf = new FBManagedConnectionFactory(getGDSType(), (FBConnectionProperties)this.connectionProperties.clone()); return mcf; } /** * The canonicalize method is used in FBDriver to reuse * previous fbmcf instances if they have been create. It should really be * package access level * * @return a FBManagedConnectionFactory value */ public FBManagedConnectionFactory canonicalize() { FBManagedConnectionFactory mcf = (FBManagedConnectionFactory) mcfInstances.get(this); if (mcf != null) return mcf; return this; } private void start() { synchronized (startLock) { if (!started) { mcfInstances.put(this, this); started = true; } // end of if () } } void notifyStart(FBManagedConnection mc, Xid xid) throws GDSException { xidMap.put(xid, mc); } void notifyEnd(FBManagedConnection mc, Xid xid) throws XAException { // empty } int notifyPrepare(FBManagedConnection mc, Xid xid) throws GDSException, XAException { FBManagedConnection targetMc = (FBManagedConnection) xidMap.get(xid); if (targetMc == null) throw new FBXAException("Commit called with unknown transaction", XAException.XAER_NOTA); return targetMc.internalPrepare(xid); } void notifyCommit(FBManagedConnection mc, Xid xid, boolean onePhase) throws GDSException, XAException { FBManagedConnection targetMc = (FBManagedConnection) xidMap.get(xid); if (targetMc == null) tryCompleteInLimboTransaction(getGDS(), xid, true); else targetMc.internalCommit(xid, onePhase); xidMap.remove(xid); } void notifyRollback(FBManagedConnection mc, Xid xid) throws GDSException, XAException { FBManagedConnection targetMc = (FBManagedConnection) xidMap.get(xid); if (targetMc == null) tryCompleteInLimboTransaction(getGDS(), xid, false); else targetMc.internalRollback(xid); xidMap.remove(xid); } public void forget(FBManagedConnection mc, Xid xid) throws GDSException { xidMap.remove(xid); } public void recover(FBManagedConnection mc, Xid xid) throws GDSException { } /** * Try to complete the "in limbo" transaction. This method tries to * reconnect an "in limbo" transaction and complete it either by commit or * rollback. If no "in limbo" transaction can be found, or error happens * during completion, an exception is thrown. * * @param gdsHelper * instance of {@link GDSHelper} that will be used to reconnect * transaction. * @param xid * Xid of the transaction to reconnect. * @param commit * true if "in limbo" transaction should be * committed, otherwise false. * * @throws XAException * if "in limbo" transaction cannot be completed. */ private void tryCompleteInLimboTransaction(GDS gds, Xid xid, boolean commit) throws XAException { try { FBManagedConnection tempMc = null; FirebirdLocalTransaction tempLocalTx = null; try { tempMc = new FBManagedConnection(null, null, this); tempLocalTx = (FirebirdLocalTransaction) tempMc.getLocalTransaction(); tempLocalTx.begin(); long fbTransactionId = 0; boolean found = false; FBXid[] inLimboIds = (FBXid[]) tempMc.recover(XAResource.TMSTARTRSCAN); for (int i = 0; i < inLimboIds.length; i++) { if (inLimboIds[i].equals(xid)) { found = true; fbTransactionId = inLimboIds[i].getFirebirdTransactionId(); } } if (!found) throw new FBXAException((commit ? "Commit" : "Rollback") + " called with unknown transaction.", XAException.XAER_NOTA); IscDbHandle dbHandle = tempMc.getGDSHelper().getCurrentDbHandle(); IscTrHandle trHandle = gds.createIscTrHandle(); gds.iscReconnectTransaction(trHandle, dbHandle, fbTransactionId); // complete transaction by commit or rollback if (commit) gds.iscCommitTransaction(trHandle); else gds.iscRollbackTransaction(trHandle); // remove heuristic data from rdb$transactions try { String query = "delete from rdb$transactions where rdb$transaction_id = " + fbTransactionId; GDSHelper gdsHelper = new GDSHelper(gds, getDatabaseParameterBuffer(), dbHandle, null); AbstractIscTrHandle trHandle2 = (AbstractIscTrHandle)gds.createIscTrHandle(); gds.iscStartTransaction(trHandle2, gdsHelper.getCurrentDbHandle(), getDefaultTpb().getTransactionParameterBuffer()); AbstractIscStmtHandle stmtHandle2 = (AbstractIscStmtHandle)gds.createIscStmtHandle(); gds.iscDsqlAllocateStatement(gdsHelper.getCurrentDbHandle(), stmtHandle2); stmtHandle2 = (AbstractIscStmtHandle)gds.createIscStmtHandle(); gds.iscDsqlAllocateStatement(gdsHelper.getCurrentDbHandle(), stmtHandle2); GDSHelper gdsHelper2 = new GDSHelper(gds, gdsHelper.getDatabaseParameterBuffer(), gdsHelper.getCurrentDbHandle(), null); gdsHelper2.setCurrentTrHandle(trHandle2); gdsHelper2.prepareStatement(stmtHandle2, query, false); gdsHelper2.executeStatement(stmtHandle2, false); gdsHelper2.closeStatement(stmtHandle2, true); gds.iscCommitTransaction(trHandle2); } catch (SQLException sqle) { throw new FBXAException("unable to remove in limbo transaction from rdb$transactions where rdb$transaction_id = " + fbTransactionId, XAException.XAER_RMERR); } } catch (GDSException ex) { /* * if ex.getIntParam() is 335544353 (transaction is not in limbo) and next ex.getIntParam() is 335544468 (transaction {0} is {1}) * => detected heuristic */ int errorCode = XAException.XAER_RMERR; int intParam = ex.getIntParam(); int nextIntParam = ex.getNext().getIntParam(); if (intParam == ISCConstants.isc_no_recon && nextIntParam == ISCConstants.isc_tra_state) { String param = ex.getNext().getNext().getNext().getParam(); if ("committed".equals(param)) errorCode = XAException.XA_HEURCOM; else if ("rolled back".equals(param)) errorCode = XAException.XA_HEURRB; } throw new FBXAException("unable to complete in limbo transaction", errorCode, ex); } finally { try { if (tempLocalTx != null && tempLocalTx.inTransaction()) tempLocalTx.commit(); } finally { if (tempMc != null) tempMc.destroy(); } } } catch (ResourceException ex) { throw new FBXAException(XAException.XAER_RMERR, ex); } } AbstractConnection newConnection(FBManagedConnection mc) throws ResourceException { Class connectionClass = GDSFactory.getConnectionClass(getGDSType()); if (!AbstractConnection.class.isAssignableFrom(connectionClass)) throw new IllegalArgumentException("Specified connection class" + " does not extend " + AbstractConnection.class.getName() + " class"); try { Constructor constructor = connectionClass .getConstructor(new Class[] { FBManagedConnection.class}); return (AbstractConnection) constructor .newInstance(new Object[] { mc}); } catch (NoSuchMethodException ex) { throw new FBResourceException( "Cannot instantiate connection class " + connectionClass.getName() + ", no constructor accepting " + FBManagedConnection.class + " class as single parameter was found."); } catch (InvocationTargetException ex) { if (ex.getTargetException() instanceof RuntimeException) throw (RuntimeException) ex.getTargetException(); if (ex.getTargetException() instanceof Error) throw (Error) ex.getTargetException(); throw new FBResourceException((Exception) ex.getTargetException()); } catch (IllegalAccessException ex) { throw new FBResourceException("Constructor for class " + connectionClass.getName() + " is not accessible."); } catch (InstantiationException ex) { throw new FBResourceException("Cannot instantiate class" + connectionClass.getName()); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy