org.firebirdsql.jdbc.FBTpbMapper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jaybird Show documentation
Show all versions of jaybird Show documentation
JDBC Driver for the Firebird RDBMS
/*
* 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.jdbc;
import org.firebirdsql.gds.ISCConstants;
import org.firebirdsql.gds.ParameterBufferHelper;
import org.firebirdsql.gds.TransactionParameterBuffer;
import org.firebirdsql.gds.impl.TransactionParameterBufferImpl;
import org.firebirdsql.jca.FBResourceException;
import java.io.Serializable;
import java.sql.Connection;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* This class is provides mapping capabilities between standard JDBC
* transaction isolation level and Firebird Transaction Parameters Block (TPB).
*
* @author Roman Rokytskyy
*/
public class FBTpbMapper implements Serializable, Cloneable {
private static final long serialVersionUID = 1690658870275668176L;
public static FBTpbMapper getDefaultMapper() {
return new FBTpbMapper();
}
/**
* Dirty reads, non-repeatable reads and phantom reads are prevented. This
* level includes the prohibitions in TRANSACTION_REPEATABLE_READ and
* further prohibits the situation where one transaction reads all rows that
* satisfy a WHERE condition, a second transaction inserts a row that
* satisfies that WHERE condition, and the first transaction rereads for the
* same condition, retrieving the additional "phantom" row in the second
* read.
*/
public static final String TRANSACTION_SERIALIZABLE = "TRANSACTION_SERIALIZABLE";
/**
* Dirty reads and non-repeatable reads are prevented; phantom reads can
* occur. This level prohibits a transaction from reading a row with
* uncommitted changes in it, and it also prohibits the situation where one
* transaction reads a row, a second transaction alters the row, and the
* first transaction rereads the row, getting different values the second
* time (a "non-repeatable read").
*/
public static final String TRANSACTION_REPEATABLE_READ = "TRANSACTION_REPEATABLE_READ";
/**
* Dirty reads are prevented; non-repeatable reads and phantom reads can
* occur. This level only prohibits a transaction from reading a row with
* uncommitted changes in it.
*/
public static final String TRANSACTION_READ_COMMITTED = "TRANSACTION_READ_COMMITTED";
private static final List ISOLATION_LEVEL_NAMES = Collections.unmodifiableList(Arrays.asList(
TRANSACTION_SERIALIZABLE, TRANSACTION_REPEATABLE_READ, TRANSACTION_READ_COMMITTED));
// read uncommitted actually not supported
/**
* Dirty reads, non-repeatable reads and phantom reads can occur. This level
* allows a row changed by one transaction to be read by another transaction
* before any changes in that row have been committed (a "dirty read"). If
* any of the changes are rolled back, the second transaction will have
* retrieved an invalid row. This level is not actually supported
*/
public static final String TRANSACTION_READ_UNCOMMITTED = "TRANSACTION_READ_UNCOMMITTED";
/**
* Indicates that transactions are not supported. This level is not
* supported
*/
public static final String TRANSACTION_NONE = "TRANSACTION_NONE";
/**
* Convert transaction isolation level into string.
*
* @param isolationLevel
* transaction isolation level as integer constant.
* @return corresponding string representation.
*/
public static String getTransactionIsolationName(int isolationLevel) {
switch (isolationLevel) {
case Connection.TRANSACTION_NONE:
return TRANSACTION_NONE;
case Connection.TRANSACTION_READ_UNCOMMITTED:
return TRANSACTION_READ_UNCOMMITTED;
case Connection.TRANSACTION_READ_COMMITTED:
return TRANSACTION_READ_COMMITTED;
case Connection.TRANSACTION_REPEATABLE_READ:
return TRANSACTION_REPEATABLE_READ;
case Connection.TRANSACTION_SERIALIZABLE:
return TRANSACTION_SERIALIZABLE;
default:
throw new IllegalArgumentException("Incorrect transaction isolation level.");
}
}
/**
* Convert transaction isolation level name into a corresponding constant.
*
* @param isolationName
* name of the transaction isolation.
* @return corresponding constant.
*/
public static int getTransactionIsolationLevel(String isolationName) {
switch (isolationName) {
case TRANSACTION_NONE:
return Connection.TRANSACTION_NONE;
case TRANSACTION_READ_UNCOMMITTED:
return Connection.TRANSACTION_READ_UNCOMMITTED;
case TRANSACTION_READ_COMMITTED:
return Connection.TRANSACTION_READ_COMMITTED;
case TRANSACTION_REPEATABLE_READ:
return Connection.TRANSACTION_REPEATABLE_READ;
case TRANSACTION_SERIALIZABLE:
return Connection.TRANSACTION_SERIALIZABLE;
default:
throw new IllegalArgumentException("Invalid isolation name.");
}
}
// ConcurrentHashMap because changes can - potentially - be made concurrently
private Map mapping = new ConcurrentHashMap<>();
private int defaultIsolationLevel = Connection.TRANSACTION_READ_COMMITTED;
/**
* Create instance of this class with the default mapping of JDBC
* transaction isolation levels to Firebird TPB.
*/
public FBTpbMapper() {
// TODO instance creation should be delegated to FbDatabase or another factory
// TODO Should use isc_tpb_mapping.properties
TransactionParameterBuffer serializableTpb = new TransactionParameterBufferImpl();
serializableTpb.addArgument(ISCConstants.isc_tpb_write);
serializableTpb.addArgument(ISCConstants.isc_tpb_wait);
serializableTpb.addArgument(ISCConstants.isc_tpb_consistency);
TransactionParameterBuffer repeatableReadTpb = new TransactionParameterBufferImpl();
repeatableReadTpb.addArgument(ISCConstants.isc_tpb_write);
repeatableReadTpb.addArgument(ISCConstants.isc_tpb_wait);
repeatableReadTpb.addArgument(ISCConstants.isc_tpb_concurrency);
TransactionParameterBuffer readCommittedTpb = new TransactionParameterBufferImpl();
readCommittedTpb.addArgument(ISCConstants.isc_tpb_write);
readCommittedTpb.addArgument(ISCConstants.isc_tpb_wait);
readCommittedTpb.addArgument(ISCConstants.isc_tpb_read_committed);
readCommittedTpb.addArgument(ISCConstants.isc_tpb_rec_version);
mapping.put(Connection.TRANSACTION_SERIALIZABLE, serializableTpb);
mapping.put(Connection.TRANSACTION_REPEATABLE_READ, repeatableReadTpb);
mapping.put(Connection.TRANSACTION_READ_COMMITTED, readCommittedTpb);
}
/**
* Create instance of this class for the specified string mapping.
*
* @param stringMapping mapping of JDBC transaction isolation to Firebird
* mapping. Keys and values of this map must be strings. Keys can have
* following values:
*
* "TRANSACTION_SERIALIZABLE"
* "TRANSACTION_REPEATABLE_READ"
* "TRANSACTION_READ_COMMITTED"
* "TRANSACTION_READ_UNCOMMITTED"
*
* Values are specified as comma-separated list of following keywords:
*
* "isc_tpb_consistency"
* "isc_tpb_concurrency"
* "isc_tpb_read_committed"
* "isc_tpb_rec_version"
* "isc_tpb_no_rec_version"
* "isc_tpb_wait"
* "isc_tpb_nowait"
* "isc_tpb_read"
* "isc_tpb_write"
* "isc_tpb_lock_read"
* "isc_tpb_lock_write"
* "isc_tpb_shared"
* "isc_tpb_protected"
*
* It is also allowed to strip "isc_tpb_" prefix from above shown constans.
* Meaning of these constants and possible combinations you can find in a
* documentation.
*
* @throws FBResourceException if mapping contains incorrect values.
*/
public FBTpbMapper(Map stringMapping) throws FBResourceException {
this();
processMapping(stringMapping);
}
/**
* Process specified string mapping. This method updates default mapping
* with values specified in a stringMapping
.
*
* @param stringMapping
* mapping to process.
* @throws FBResourceException
* if mapping contains incorrect values.
*/
private void processMapping(Map stringMapping) throws FBResourceException {
for (Map.Entry entry : stringMapping.entrySet()) {
String jdbcTxIsolation = entry.getKey();
Integer isolationLevel;
try {
isolationLevel = getTransactionIsolationLevel(jdbcTxIsolation);
} catch (IllegalArgumentException ex) {
throw new FBResourceException("Transaction isolation " + jdbcTxIsolation + " is not supported.");
}
TransactionParameterBuffer tpb = processMapping(entry.getValue());
mapping.put(isolationLevel, tpb);
}
}
/**
* Create instance of this class and load mapping from the specified
* resource.
*
* @param mappingResource
* name of the resource to load.
* @param cl
* class loader that should be used to load specified resource.
* @throws FBResourceException
* if resource cannot be loaded or contains
* incorrect values.
*/
public FBTpbMapper(String mappingResource, ClassLoader cl) throws FBResourceException {
try {
ResourceBundle res = ResourceBundle.getBundle(mappingResource, Locale.getDefault(), cl);
Map mapping = new HashMap<>();
Enumeration en = res.getKeys();
while (en.hasMoreElements()) {
String key = en.nextElement();
String value = res.getString(key);
mapping.put(key, value);
}
processMapping(mapping);
} catch (MissingResourceException mrex) {
throw new FBResourceException("Cannot load TPB mapping." + mrex.getMessage());
}
}
/**
* This method extracts TPB mapping information from the connection
* parameters and adds it to the connectionProperties. Two formats are supported:
*
* info
contains "tpb_mapping"
parameter
* pointing to a resource bundle with mapping information;
* info
contains separate mappings for each of following
* transaction isolation levels: "TRANSACTION_SERIALIZABLE"
,
* "TRANSACTION_REPEATABLE_READ"
and
* "TRANSACTION_READ_COMMITTED"
.
*
*
* @param connectionProperties
* FirebirdConnectionProperties to set transaction state
* @param info
* connection parameters passed into a driver.
* @throws FBResourceException
* if specified mapping is incorrect.
*/
public static void processMapping(FirebirdConnectionProperties connectionProperties, Properties info)
throws FBResourceException {
for (String isolationName : ISOLATION_LEVEL_NAMES) {
String property = info.getProperty(isolationName);
if (property == null) continue;
connectionProperties.setTransactionParameters(
getTransactionIsolationLevel(isolationName),
processMapping(property));
}
}
/**
* Process comma-separated list of keywords and convert them into TPB
* values.
*
* @param mapping
* comma-separated list of keywords.
* @return set containing values corresponding to the specified keywords.
* @throws FBResourceException
* if mapping contains keyword that is not
* a TPB parameter.
*/
public static TransactionParameterBuffer processMapping(String mapping) throws FBResourceException {
// TODO instance creation should be delegated to FbDatabase
TransactionParameterBuffer result = new TransactionParameterBufferImpl();
StringTokenizer st = new StringTokenizer(mapping, ",");
while (st.hasMoreTokens()) {
String token = st.nextToken();
Integer argValue = null;
if (token.contains("=")) {
String[] parts = token.split("=");
try {
argValue = Integer.valueOf(parts[1]);
} catch (NumberFormatException ex) {
throw new FBResourceException(parts[1] + " is not valid integer value");
}
token = parts[0];
}
Integer value = ParameterBufferHelper.getTpbParam(token);
if (value == null) {
throw new FBResourceException("Keyword " + token + " unknown. Please check your mapping.");
}
if (argValue == null) {
result.addArgument(value);
} else {
result.addArgument(value, argValue);
}
}
return result;
}
/**
* Get mapping for the specified transaction isolation level.
*
* @param transactionIsolation
* transaction isolation level.
* @return set with TPB parameters.
* @throws IllegalArgumentException
* if specified transaction isolation level is unknown.
*/
public TransactionParameterBuffer getMapping(int transactionIsolation) {
switch (transactionIsolation) {
case Connection.TRANSACTION_SERIALIZABLE:
case Connection.TRANSACTION_REPEATABLE_READ:
case Connection.TRANSACTION_READ_COMMITTED:
return mapping.get(transactionIsolation).deepCopy();
case Connection.TRANSACTION_READ_UNCOMMITTED:
// promote transaction
return mapping.get(Connection.TRANSACTION_READ_COMMITTED).deepCopy();
case Connection.TRANSACTION_NONE:
default:
throw new IllegalArgumentException(
"Transaction isolation level " + transactionIsolation + " is not supported.");
}
}
/**
* Set mapping for the specified transaction isolation.
*
* @param transactionIsolation
* transaction isolation level.
* @param tpb
* TPB parameters.
* @throws IllegalArgumentException
* if incorrect isolation level is specified.
*/
public void setMapping(int transactionIsolation, TransactionParameterBuffer tpb) {
switch (transactionIsolation) {
case Connection.TRANSACTION_SERIALIZABLE:
case Connection.TRANSACTION_REPEATABLE_READ:
case Connection.TRANSACTION_READ_COMMITTED:
mapping.put(transactionIsolation, tpb.deepCopy());
break;
case Connection.TRANSACTION_READ_UNCOMMITTED:
case Connection.TRANSACTION_NONE:
default:
throw new IllegalArgumentException(
"Transaction isolation level " + transactionIsolation + " is not supported.");
}
}
/**
* Get default mapping. Default mapping represents a TPB mapping for the
* default transaction isolation level (read committed).
*
* @return mapping for the default transaction isolation level.
*/
public TransactionParameterBuffer getDefaultMapping() {
return mapping.get(defaultIsolationLevel);
}
public int getDefaultTransactionIsolation() {
return defaultIsolationLevel;
}
public void setDefaultTransactionIsolation(int isolationLevel) {
// TODO Check if valid isolation level
this.defaultIsolationLevel = isolationLevel;
}
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof FBTpbMapper)) {
return false;
}
FBTpbMapper that = (FBTpbMapper) obj;
boolean result = this.mapping.equals(that.mapping);
result &= (this.defaultIsolationLevel == that.defaultIsolationLevel);
return result;
}
public int hashCode() {
// TODO both these values are mutable, so potentially unstable hashcode
return Objects.hash(mapping, defaultIsolationLevel);
}
public Object clone() {
try {
FBTpbMapper clone = (FBTpbMapper) super.clone();
ConcurrentHashMap newMapping = new ConcurrentHashMap<>();
for (Map.Entry entry : mapping.entrySet()) {
newMapping.put(entry.getKey(), entry.getValue().deepCopy());
}
clone.mapping = newMapping;
return clone;
} catch (CloneNotSupportedException ex) {
throw new Error("Assertion failure: clone not supported"); // Can't happen
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy