org.firebirdsql.jca.FBManagedConnectionFactory Maven / Gradle / Ivy
Show all versions of jaybird-jdk17 Show documentation
/*
* $Id$
*
* Firebird Open Source JavaEE 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 source control history command.
*
* All rights reserved.
*/
package org.firebirdsql.jca;
import java.io.*;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
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, FirebirdConnectionProperties,
Serializable {
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.
*
* It is also used to return a canonical instance to {@link org.firebirdsql.jdbc.FBDriver}.
*
*/
private static final Map> mcfInstances =
new ConcurrentHashMap>();
private static final ReferenceQueue mcfReferenceQueue =
new ReferenceQueue();
// /**
// * @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);
}
@Override
public boolean isUseFirebirdAutocommit() {
return connectionProperties.isUseFirebirdAutocommit();
}
@Override
public void setUseFirebirdAutocommit(boolean useFirebirdAutocommit) {
connectionProperties.setUseFirebirdAutocommit(useFirebirdAutocommit);
}
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 = internalCanonicalize();
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() {
final FBManagedConnectionFactory mcf = internalCanonicalize();
if (mcf != null) return mcf;
start();
return this;
}
private FBManagedConnectionFactory internalCanonicalize() {
final SoftReference factoryReference = mcfInstances.get(getCacheKey());
return factoryReference != null ? factoryReference.get() : null;
}
/**
* Starts this MCF and adds this instance to {@code #mcfInstances} cache.
*
* This implementation (together with {@link #canonicalize()} has a race condition with regard to the
* instance cache. As this is a relatively harmless one, we leave it as is.
*
*/
private void start() {
synchronized (startLock) {
if (started) return;
mcfInstances.put(getCacheKey(), new SoftReference(this, mcfReferenceQueue));
started = true;
}
cleanMcfInstances();
}
/**
* Removes cleared references from the {@link #mcfInstances} cache.
*/
private void cleanMcfInstances() {
Reference extends FBManagedConnectionFactory> reference;
while ((reference = mcfReferenceQueue.poll()) != null) {
mcfInstances.values().remove(reference);
}
}
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 gds
* instance of {@link GDS} 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;
if (tempMc.getGDSHelper().compareToVersion(2, 0) < 0) {
// Find Xid by scanning
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();
}
}
} else {
// Find Xid by intelligent scan
FBXid foundXid = (FBXid) tempMc.findSingleXid(xid);
if (foundXid != null && foundXid.equals(xid)) {
found = true;
fbTransactionId = foundXid.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);
if (tempMc.getGDSHelper().compareToVersion(3, 0) < 0) {
// remove heuristic data from rdb$transactions (only possible in versions before Firebird 3)
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());
gdsHelper.setCurrentTrHandle(trHandle2);
AbstractIscStmtHandle stmtHandle2 = (AbstractIscStmtHandle) gds.createIscStmtHandle();
gds.iscDsqlAllocateStatement(gdsHelper.getCurrentDbHandle(), stmtHandle2);
gdsHelper.prepareStatement(stmtHandle2, query, false);
gdsHelper.executeStatement(stmtHandle2, false);
gdsHelper.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());
}
}
public final FBConnectionProperties getCacheKey() {
return (FBConnectionProperties) connectionProperties.clone();
}
}