/*------------------------------------------------------------------------------
Name: JdbcManagerCommonTable.java
Project: xmlBlaster.org
Copyright: xmlBlaster.org, see xmlBlaster-LICENSE file
------------------------------------------------------------------------------*/
package org.xmlBlaster.util.queue.jdbc;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Properties;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.xmlBlaster.util.Global;
import org.xmlBlaster.util.ThreadLister;
import org.xmlBlaster.util.XmlBlasterException;
import org.xmlBlaster.util.def.ErrorCode;
import org.xmlBlaster.util.plugin.PluginInfo;
import org.xmlBlaster.util.queue.I_Entry;
import org.xmlBlaster.util.queue.I_EntryFactory;
import org.xmlBlaster.util.queue.I_EntryFilter;
import org.xmlBlaster.util.queue.I_Queue;
import org.xmlBlaster.util.queue.I_Storage;
import org.xmlBlaster.util.queue.I_StorageProblemListener;
import org.xmlBlaster.util.queue.I_StorageProblemNotifier;
import org.xmlBlaster.util.queue.QueuePluginManager;
import org.xmlBlaster.util.queue.ReturnDataHolder;
import org.xmlBlaster.util.queue.StorageId;
/**
* Delegate class which takes care of SQL specific stuff for the JdbcQueuePlugin
* class.
* <p>
* One instance of this is created by Global for each StorageId prefix,
* so one instance for 'history', one for 'cb' etc.
* </p>
*
* The tables needed for each JdbcManagerCommonTable instance are the following:<br />
* - entriesTableName (defaults to 'ENTRIES')<br />
* The names of such tables are constituted by the tableNamePrefix (which defaults to 'XB') plus the
* entriesTableName
*
* @see <a href="http://www.xmlBlaster.org/xmlBlaster/doc/requirements/queue.jdbc.commontable.html">The queue.jdbc.commontable requirement</a>
* @author <a href="mailto:xmlBlaster@marcelruff.info">Marcel Ruff</a>
* @author <a href='mailto:michele@laghi.eu'>Michele Laghi</a>
*
*/
public class CommonTableDatabaseAccessor implements I_StorageProblemListener, I_StorageProblemNotifier {
private static final String ME = "JdbcManagerCommonTable";
private final Global glob;
private static Logger log = Logger.getLogger(CommonTableDatabaseAccessor.class.getName());
private final JdbcConnectionPool pool;
private final I_EntryFactory factory;
private final WeakHashMap listener;
private final static String DUMMY_VALUE = "A";
private final String tableNamePrefix;
private final String colNamePrefix;
// the names to be used
private String stringTxt = null;
private String longintTxt = null;
private String intTxt = null;
private String blobTxt = null;
private String booleanTxt = null;
private int maxStatementLength = 0;
private boolean isConnected = true;
private static boolean first = true;
private String entriesTableName = null;
private String blobVarName;
private String byteSizeColName;
private String dataIdColName;
private String keyAttr;
private boolean isPostgres;
private boolean isOracle;
private boolean isDB2;
private boolean isFirebird;
private boolean isMicrosoftSQLServer;
private boolean isHSQLDatabaseEngine;
private boolean isMySql;
private boolean isLdbc;
private boolean isSQLite;
// private final String managerName;
private final I_Storage storage;
private PreparedStatement pingPrepared = null;
/**
* Counts the queues using this manager.
*/
private int queueCounter = 0;
/**
* tells wether the used database supports batch updates or not.
*/
private boolean supportsBatch = true;
/** forces the desactivation of batch mode when adding entries */
private boolean enableBatchMode = true;
/** Column index into XB_ENTRIES table */
final static int DATA_ID = 1;
final static int QUEUE_NAME = 2;
final static int PRIO = 3;
final static int TYPE_NAME = 4;
final static int PERSISTENT = 5;
final static int SIZE_IN_BYTES = 6;
final static int BLOB = 7;
private int maxNumStatements;
private int maxSelectLimit;
/**
* @param storage TODO
* @param JdbcConnectionPool the pool to be used for the connections to
* the database. IMPORTANT: The pool must have been previously
* initialized.
*/
public CommonTableDatabaseAccessor(JdbcConnectionPool pool, I_EntryFactory factory, String managerName, I_Storage storage)
throws XmlBlasterException {
// this.managerName = managerName;
this.pool = pool;
this.glob = this.pool.getGlobal();
this.storage = storage;
if (log.isLoggable(Level.FINER)) log.finer("Constructor called");
this.factory = factory;
if (!this.pool.isInitialized())
throw new XmlBlasterException(glob, ErrorCode.INTERNAL_UNKNOWN, ME, "constructor: the Connection pool is not properly initialized");
// get the necessary metadata
Connection conn = null;
boolean success = true;
try {
conn = this.pool.getConnection();
DatabaseMetaData dbmd = conn.getMetaData();
this.maxStatementLength = dbmd.getMaxStatementLength();
if (this.maxStatementLength < 1) {
this.maxStatementLength = glob.getProperty().get("queue.persistent.maxStatementLength", 2048);
if (first) {
log.info("The maximum SQL statement length is not defined in JDBC meta data, we set it to " + this.maxStatementLength);
first = false;
}
}
if (!dbmd.supportsTransactions()) {
String dbName = dbmd.getDatabaseProductName();
log.severe("the database '" + dbName + "' does not support transactions, unpredicted results may happen");
}
if (!dbmd.supportsBatchUpdates()) {
String dbName = dbmd.getDatabaseProductName();
this.supportsBatch = false;
log.fine("the database '" + dbName + "' does not support batch mode. No problem I will work whitout it");
}
// zero means not limit (to be sure we also check negative Values
boolean logWarn = false;
int defaultMaxNumStatements = dbmd.getMaxStatements();
if (defaultMaxNumStatements < 1) {
defaultMaxNumStatements = 50;
logWarn = true;
}
// -queue.persistent.maxNumStatements 50
this.maxNumStatements = this.pool.getProp("maxNumStatements", defaultMaxNumStatements);
log.info("The maximum Number of statements for this database instance are '" + this.maxNumStatements + "'");
if (logWarn && this.pool.getProp("maxNumStatements",-1)==-1)
log.warning("The maxStatements returned fromt the database metadata is '" + defaultMaxNumStatements + "', will set the default to 50 unless you explicitly set '-maxNumStatements <num>'");
// -queue.persistent.maxSelectLimit -1 (off)
this.maxSelectLimit = this.pool.getProp("maxSelectLimit", -1);
if (this.maxSelectLimit > 0)
log.info("The maximum results returned by a select is set to '" + this.maxSelectLimit + "' (MSSQLerver only)");
log.info("Using DB " + dbmd.getDatabaseProductName() + " " + dbmd.getDatabaseProductVersion() + " " + dbmd.getDriverName());
}
catch (XmlBlasterException ex) {
success = false;
throw ex;
}
catch (Throwable ex) {
success = false;
throw new XmlBlasterException(glob, ErrorCode.RESOURCE_CONFIGURATION, ME, "constructor: failed to get the metadata", ex);
}
finally {
if (conn != null)
this.pool.releaseConnection(conn, success);
}
Hashtable names = pool.getMapping();
this.listener = new WeakHashMap();
this.stringTxt = (String)names.get("string");
if (this.stringTxt == null) this.stringTxt = "text";
this.longintTxt = (String)names.get("longint");
if (this.longintTxt == null) this.longintTxt = "bigint";
this.intTxt = (String)names.get("int");
if (this.intTxt == null) this.intTxt = "integer";
this.booleanTxt = (String)names.get("boolean");
if (this.booleanTxt == null) this.booleanTxt = "char(1)";
this.blobTxt = (String)names.get("blob");
if (this.blobTxt == null) this.blobTxt = "bytea";
this.blobVarName = (String)names.get("blobVarName");
if (this.blobVarName == null) this.blobVarName = "blob";
this.keyAttr = (String)names.get("keyAttr");
if (this.keyAttr == null) this.keyAttr = ""; // could be "not null" for MySQL
this.tableNamePrefix = this.pool.getTableNamePrefix();
this.colNamePrefix = this.pool.getColNamePrefix();
// this.queueIncrement = this.pool.getTableAllocationIncrement(); // 2
this.entriesTableName = this.tableNamePrefix +
pool.getPluginProperties().getProperty("entriesTableName", "ENTRIES");
this.entriesTableName = this.entriesTableName.toUpperCase();
// byteSize and dataId are reserved in MS-SQLServer, prefixing other column names are not yet coded
this.byteSizeColName = this.colNamePrefix + "byteSize";
this.dataIdColName = this.colNamePrefix + "dataId";
this.enableBatchMode = this.pool.isBatchModeEnabled();
this.isPostgres = this.pool.getUrl().startsWith("jdbc:postgresql:");
this.isOracle = this.pool.getUrl().startsWith("jdbc:oracle:");
this.isDB2 = this.pool.getUrl().startsWith("jdbc:db2:");
this.isFirebird = this.pool.getUrl().startsWith("jdbc:firebirdsql:");
this.isMicrosoftSQLServer = this.pool.getUrl().startsWith("jdbc:microsoft:sqlserver:") // 2000
|| this.pool.getUrl().startsWith("jdbc:sqlserver:"); // 2005
this.isHSQLDatabaseEngine = this.pool.getUrl().startsWith("jdbc:hsqldb:");
this.isMySql = this.pool.getUrl().startsWith("jdbc:mysql:");
this.isLdbc = this.pool.getUrl().startsWith("jdbc:ldbc:");
this.isSQLite = this.pool.getUrl().startsWith("jdbc:sqlite:");
this.pool.registerManager(this);
}
/**
* pings with a newly created jdbc connection to check if the DB is up and running. It returns
* 'true' if the database is available, false otherwise. The ping is done by invocation
* <p>
* We don't use a connection from the pool for this check
* as it could be stale even if the database is alive.
*/
public boolean pingDatabase() {
Connection conn = null;
PreparedStatement st = null;
boolean success = true;
try {
conn = this.pool.createNewConnection(false);
if (isPostgres() || isMicrosoftSQLServer()) {
st = conn.prepareStatement("select 1");
}
else {
st = conn.prepareStatement("select min("+this.dataIdColName+") from " + this.entriesTableName);
}
st.executeQuery();
}
catch (Throwable ex) {
success = false;
log.warning("ping failed due to problems with backend database. Reason:" + ex.getMessage());
}
finally {
try {
if (st != null)
st.close();
}
catch (Throwable e) {
log.severe("ping: releaseConnection failed: " + e.getMessage());
}
try {
if (conn != null)
conn.close();
}
catch (Throwable e) {
log.severe("ping: releaseConnection failed: " + e.getMessage());
}
}
return success;
}
/**
* Adds (registers) a listener for connection/disconnection events.
*/
public boolean registerStorageProblemListener(I_StorageProblemListener entry) {
synchronized (this.listener) {
this.listener.put(entry, DUMMY_VALUE); // use DUMMY_VALUE to support check in unregisterListener()
}
return true;
}
/**
* Adds (registers) a listener for connection/disconnection events.
* @return boolean true if the entry was successfully removed.
*/
public boolean unRegisterStorageProblemListener(I_StorageProblemListener entry) {
if (entry == null) return false;
synchronized (this.listener) {
return this.listener.remove(entry) != null;
}
}
/**
* @see I_StorageProblemListener#storageUnavailable(int)
*/
public void storageUnavailable(int oldStatus) {
if (log.isLoggable(Level.FINER)) log.finer("storageUnavailable (old status '" + oldStatus + "')");
this.isConnected = false;
I_StorageProblemListener[] listenerArr = getStorageProblemListenerArr();
for(int i=0; i<listenerArr.length; i++) {
if (this.isConnected == true) break;
I_StorageProblemListener singleListener = listenerArr[i];
singleListener.storageUnavailable(oldStatus);
}
}
/**
* @see I_StorageProblemListener#storageAvailable(int)
*/
public void storageAvailable(int oldStatus) {
if (log.isLoggable(Level.FINER)) log.finer("storageAvailable (old status '" + oldStatus + "')");
this.isConnected = true;
//change this once this class implements I_StorageProblemNotifier
if (oldStatus == I_StorageProblemListener.UNDEF) return;
I_StorageProblemListener[] listenerArr = getStorageProblemListenerArr();
for(int i=0; i<listenerArr.length; i++) {
if (this.isConnected == false) break;
I_StorageProblemListener singleListener = listenerArr[i];
singleListener.storageAvailable(oldStatus);
}
}
/**
* @return A current snapshot of the connection listeners where we can work on (unsynchronized) and remove
* listeners without danger
*/
public I_StorageProblemListener[] getStorageProblemListenerArr() {
synchronized (this.listener) {
return (I_StorageProblemListener[])this.listener.keySet().toArray(new I_StorageProblemListener[this.listener.size()]);
}
}
/**
* @see #checkIfDBLoss(Connection, String, Throwable, String)
*/
protected final boolean checkIfDBLoss(Connection conn, String location, Throwable ex) {
return checkIfDBLoss(conn, location, ex, null);
}
/**
* Handles the SQLException.
* If it is a communication exception like the
* connection has been broken it will inform the connection pool.
* @param location where the exception occured.
* @param ex the exception which has to be handled.
* @param trace additional information to put in the logetLogId(tableName, storageId, "getEntries") logging trace.
* @return boolean true if it was a communication exception
*
*/
protected final boolean checkIfDBLoss(Connection conn, String location, Throwable ex, String trace) {
boolean ret = !pingDatabase();
if (ret) {
log.severe(location + ": the connection to the DB has been lost. Going in polling modus");
this.pool.setDatabaseLost();
}
return ret;
}
/**
* Gets the names of all the tables used by XmlBlaster.
* This information is retrieved via the database's metadata.
* @param conn the connection on which to retrieve the metadata.
* @return HashSet the set containing all the existing tablenames.
*/
synchronized private HashSet getXbTableNames(Connection conn) throws SQLException {
String[] types = { "TABLE" };
ResultSet rs = conn.getMetaData().getTables(null, null, null, types);
HashSet ret = new HashSet();
while (rs.next()) { // retrieve the result set ...
String table = rs.getString(3).toUpperCase();
// if (table.startsWith(this.tablePrefix))
// we currently add everything since I don't know what's better: speed here or when searching
if (log.isLoggable(Level.FINE)) log.fine("getXbTableNames found table '" + table + "': adding to the set of found tables");
ret.add(table);
}
return ret;
}
/**
* Checks if all necessary tables exist.
* If a table does not exist and 'createTables' true, then the
* table is created.
* @return boolean 'true' if the tables are all there after the invocation to this method, 'false' if at
* least one of the required tables is missing after the invocation to this method.
*/
public final boolean tablesCheckAndSetup(boolean createTables)
throws XmlBlasterException {
if (log.isLoggable(Level.FINER)) log.finer("tablesCheckAndSetup");
if (!this.isConnected)
throw new XmlBlasterException(glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME, "tablesCheckAndSetup: the DB disconnected. Handling is currently not possible");
boolean entriesTableExists = false;
Connection conn = null;
boolean success = true;
String req = "retrieving metadata";
try {
conn = this.pool.getConnection();
conn.setAutoCommit(false);
HashSet set = getXbTableNames(conn);
if (set.contains(this.entriesTableName.toUpperCase())) entriesTableExists = true;
if (log.isLoggable(Level.FINE)) log.fine("entries table exists : " + entriesTableExists);
if (!createTables) return entriesTableExists;
if (!entriesTableExists) {
log.info("adding table '" + this.entriesTableName + "' as the 'entries' table");
req = "CREATE TABLE " + this.entriesTableName.toUpperCase() +
" (" + this.dataIdColName + " " + this.longintTxt + " " + this.keyAttr +
//", nodeId " + this.stringTxt + " " + this.keyAttr +
", queueName " + this.stringTxt + " " + this.keyAttr +
", prio " + this.intTxt +
", flag " + this.stringTxt +
", durable " + this.booleanTxt +
", " + this.byteSizeColName + " " + this.longintTxt +
", " + this.blobVarName + " " + this.blobTxt +
", PRIMARY KEY (" + this.dataIdColName + ", queueName)";
if (this.pool.isCascadeDeleteSuppported()) req += " ON DELETE CASCADE)";
else req += ")";
if (log.isLoggable(Level.FINE))
log.fine("Request: '" + req + "'");
update(req, conn);
}
if (!conn.getAutoCommit())
conn.commit();
return true;
}
catch (XmlBlasterException ex) {
success = false;
try {
if (!conn.getAutoCommit())
conn.rollback();
}
catch (Throwable e) {
log.severe("tablesCheckAndSetup: exception occured when rolling back: " + e.toString());
}
throw ex;
}
catch (Throwable ex) {
success = false;
try {
if (conn != null && !conn.getAutoCommit())
conn.rollback();
}
catch (Throwable e) {
log.severe("tablesCheckAndSetup: exception occured when rolling back: " + e.toString());
}
if (checkIfDBLoss(conn, getLogId(null, "tablesCheckAndSetup"), ex, "SQL request giving problems: " + req))
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME + ".tablesCheckAndSetup", "", ex);
else throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNKNOWN, ME + ".tablesCheckAndSetup", "", ex);
}
finally {
if (conn != null) {
try {
conn.setAutoCommit(true);
}
catch (Throwable e) {
log.severe("tablesCheckAndSetup: exception occured when setting back autocommit flag, reason: " + e.toString());
}
this.pool.releaseConnection(conn, success);
}
}
}
/**
*
* modifies a row in the specified queue table
* @param queueName The name of the queue on which to perform the operation
* @param entry the object to be stored.
* @param oldEntry the old one
* @return true on success
*
* @throws XmlBlasterException if an error occurred when trying to get a connection or an SQLException
* occurred.
*/
public long modifyEntry(String queueName, I_Entry entry, I_Entry oldEntry)
throws XmlBlasterException {
if (log.isLoggable(Level.FINER)) log.finer("Entering");
if (!this.isConnected) {
if (log.isLoggable(Level.FINE)) log.fine("For entry '" + entry.getUniqueId() + "' currently not possible. No connection to the DB");
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME + ".modifyEntry", " the connection to the DB is unavailable already before trying to add an entry");
}
Connection conn = null;
boolean success = true;
PreparedStatement preStatement = null;
Statement exStatement = null;
long dataId = entry.getUniqueId();
int prio = entry.getPriority();
byte[] blob = this.factory.toBlob(entry);
String typeName = entry.getEmbeddedType();
boolean persistent = entry.isPersistent();
long sizeInBytes = entry.getSizeInBytes();
if (log.isLoggable(Level.FINEST))
log.finest("modification dataId: " + dataId + ", prio: " + prio + ", typeName: " + typeName + ", byteSize in bytes: " + sizeInBytes);
try {
conn = this.pool.getConnection();
String req = "UPDATE " + this.entriesTableName + " SET prio = ? , flag = ? , durable = ? , " +
this.byteSizeColName + " = ? , " + this.blobVarName + " = ? WHERE " + this.dataIdColName +
" = ? AND queueName = ?";
if (log.isLoggable(Level.FINE)) log.fine(req);
preStatement = conn.prepareStatement(req);
preStatement.setQueryTimeout(this.pool.getQueryTimeout());
preStatement.setInt(1, prio);
preStatement.setString(2, typeName);
if (persistent == true) preStatement.setString(3, "T");
else preStatement.setString(3, "F");
preStatement.setLong(4, sizeInBytes);
ByteArrayInputStream blob_stream = new ByteArrayInputStream(blob);
preStatement.setBinaryStream(5, blob_stream, blob.length); //(int)sizeInBytes);
// preStatement.setBytes(5, blob);
preStatement.setLong(6, dataId);
preStatement.setString(7, queueName);
if (log.isLoggable(Level.FINE)) log.fine(preStatement.toString());
int num = preStatement.executeUpdate();
if (log.isLoggable(Level.FINE)) log.fine("Modified " + num + " entries, entryId='" + entry.getUniqueId() + "'");
if (oldEntry == null)
return entry.getSizeInBytes();
return entry.getSizeInBytes() - oldEntry.getSizeInBytes();
}
catch (Throwable ex) {
success = false;
if (log.isLoggable(Level.FINE)) {
if (ex instanceof SQLException) {
log.fine("modifyEntry: sql exception, the sql state: '" + ((SQLException)ex).getSQLState() );
log.fine("modifyEntry: sql exception, the error code: '" + ((SQLException)ex).getErrorCode() );
}
else log.fine("modifyEntry: exception, the error reason: '" + ex.toString());
}
try {
preStatement.close();
preStatement = null;
}
catch (Throwable ex1) {
log.severe("modifyEntry: Exception when closing the statement: " + ex1.toString());
}
// if (!conn.getAutoCommit()) conn.rollback(); // DANGER !!!!!!! NOT SAFE YET
log.warning("Could not update entry '" +
entry.getClass().getName() + "'-'" + entry.getLogId() + "-" + entry.getUniqueId() + "': " + ex.toString());
if (checkIfDBLoss(conn, getLogId(queueName, "modifyEntry"), ex)) {
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME + ".modifyEntry", "", ex);
}
else {
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNKNOWN, ME + ".modifyEntry", "", ex);
}
}
finally {
try {
if (exStatement != null)
exStatement.close();
}
catch (Throwable ex) {
log.warning("modifyEntry: throwable when closing the connection: " + ex.toString());
}
try {
if (preStatement != null)
preStatement.close();
}
catch (Throwable ex) {
success = false;
log.warning("modifyEntry: throwable when closing the connection: " + ex.toString());
}
if (conn != null)
this.pool.releaseConnection(conn, success);
}
}
private final boolean testException = false;
private volatile short countTest = 0;
/**
*
* Adds a row to the specified queue table
* @param queueName The name of the queue on which to perform the operation
* @param entry the object to be stored.
*
* @return true on success false if the entry was already in the table.
*
* @throws SQLException if an error other than double entry occured while adding the row
* @throws XmlBlasterException if an error occured when trying to get a connection
*/
private final boolean addSingleEntry(String queueName, I_Entry entry, Connection conn)
throws XmlBlasterException {
if (log.isLoggable(Level.FINER)) log.finer("Entering");
if (!this.isConnected) {
if (log.isLoggable(Level.FINE)) log.fine("For entry '" + entry.getUniqueId() + "' currently not possible. No connection to the DB");
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME + ".addSingleEntry", " the connection to the DB is unavailable already before trying to add an entry");
}
PreparedStatement preStatement = null;
// PreparedStatement exStatement = null;
Statement exStatement = null;
boolean ret = false;
long dataId = entry.getUniqueId();
int prio = entry.getPriority();
byte[] blob = this.factory.toBlob(entry);
String typeName = entry.getEmbeddedType();
boolean persistent = entry.isPersistent();
long sizeInBytes = entry.getSizeInBytes();
if (log.isLoggable(Level.FINEST))
log.finest("addition. dataId: " + dataId + ", prio: " + prio + ", typeName: " + typeName + ", byteSize in bytes: " + sizeInBytes);
try {
String req = "INSERT INTO " + this.entriesTableName + " VALUES ( ?, ?, ?, ?, ?, ?, ?)";
if (log.isLoggable(Level.FINE)) log.fine(req);
preStatement = conn.prepareStatement(req);
preStatement.setQueryTimeout(this.pool.getQueryTimeout());
preStatement.setLong(DATA_ID, dataId);
preStatement.setString(QUEUE_NAME, queueName);
preStatement.setInt(PRIO, prio);
preStatement.setString(TYPE_NAME, typeName);
if (persistent == true) preStatement.setString(PERSISTENT, "T");
else preStatement.setString(PERSISTENT, "F");
preStatement.setLong(SIZE_IN_BYTES, sizeInBytes);
ByteArrayInputStream blob_stream = new ByteArrayInputStream(blob);
preStatement.setBinaryStream(BLOB, blob_stream, blob.length); //(int)sizeInBytes);
// preStatement.setBytes(BLOB, blob);
if (log.isLoggable(Level.FINE)) log.fine(preStatement.toString());
if (testException) {
countTest++;
if (countTest % 10 == 0)
throw new java.sql.SQLException("Connection reset by peer: socket write error");
//com.microsoft.sqlserver.jdbc.XmlBlasterSqlServerHelper.throwTestException();
}
int num = preStatement.executeUpdate();
if (log.isLoggable(Level.FINE)) log.fine("Added " + num + " entries, entryId='" + entry.getUniqueId() + "'");
ret = true;
}
catch (Throwable ex) {
String originalExceptionStack = Global.getStackTraceAsString(ex);
String originalExceptionReason = ex.getMessage();
if (log.isLoggable(Level.FINE)) {
if (ex instanceof SQLException) {
log.fine("addEntry: sql exception, the sql state: '" + ((SQLException)ex).getSQLState() );
log.fine("addEntry: sql exception, the error code: '" + ((SQLException)ex).getErrorCode() );
}
}
try {
if (preStatement != null) {
preStatement.close();
preStatement = null;
}
}
catch (Throwable ex1) {
log.severe("exception when closing statement: " + ex1.toString());
ex1.printStackTrace();
}
// if (!conn.getAutoCommit()) conn.rollback(); // DANGER !!!!!!! NOT SAFE YET
log.severe("Could not insert entry '" +
entry.getClass().getName() + "'-'" + entry.getLogId() + "-" + entry.getUniqueId() + "': " + ex.toString());
if (checkIfDBLoss(conn, getLogId(queueName, "addEntry"), ex)) {
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME + ".addEntry", "", ex);
}
// check if the exception was due to an existing entry. If yes, no exception will be thrown
try {
String req = "SELECT count(*) from " + this.entriesTableName + " where (" + this.dataIdColName + "='" + dataId + "')";
if (log.isLoggable(Level.FINE)) log.fine("addEntry: checking if entry already in db: request='" + req + "'");
exStatement = conn.createStatement();
// exStatement.setQueryTimeout(this.pool.getQueryTimeout());
ResultSet rs = exStatement.executeQuery(req);
rs.next();
int size = rs.getInt(1);
if (size < 1) throw ex;
}
catch (Throwable ex1) {
String secondException = Global.getStackTraceAsString(ex1);
log.warning("The exception '" + ex1.getMessage() + "' occured at " + secondException + "'. The original exception was '" + originalExceptionReason + "' at '" + originalExceptionStack);
if (log.isLoggable(Level.FINE)) log.fine("addEntry: checking if entry already in db: exception in select: '" + ex.toString() + "'");
if (checkIfDBLoss(conn, getLogId(queueName, "addEntry"), ex1))
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME + ".addEntry", "", ex1);
else throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNKNOWN, ME + ".addEntry", "", ex1);
}
ret = false;
}
finally {
try {
if (exStatement != null) exStatement.close();
}
catch (Throwable ex) {
log.warning("addEntry: throwable when closing the connection: " + ex.toString());
}
try {
if (preStatement != null) preStatement.close();
}
catch (Throwable ex) {
log.warning("addEntry: throwable when closing the connection: " + ex.toString());
}
}
return ret;
}
/**
* Adds a row to the specified queue table
* @param queueName The name of the queue on which to perform the operation
* @param entry the object to be stored.
*
* @return true on success
*
* @throws SQLException if an error occured while adding the row
* @throws XmlBlasterException if an error occured when trying to get a connection
*/
public final boolean addEntry(String queueName, I_Entry entry)
throws XmlBlasterException {
Connection conn = null;
boolean success = true;
try {
conn = this.pool.getConnection();
return addSingleEntry(queueName, entry, conn);
}
catch (XmlBlasterException ex) {
success = false;
throw ex;
}
finally {
if (conn != null)
this.pool.releaseConnection(conn, success);
}
}
private int[] addEntriesSingleMode(Connection conn, String queueName, I_Entry[] entries)
throws XmlBlasterException {
// due to a problem in Postgres (implicit abortion of transaction by exceptions)
// we can't do everything in the same transaction. That's why we simulate a single
// transaction by deleting the processed entries in case of a failure other than
// double entries exception.
int i = 0;
int[] ret = new int[entries.length];
try {
if (log.isLoggable(Level.FINE)) log.fine("addEntriesSingleMode adding each entry in single mode since an exception occured when using 'batch mode'");
for (i=0; i < entries.length; i++) {
if (addSingleEntry(queueName, entries[i], conn)) ret[i] = 1;
else ret[i] = 0;
if (log.isLoggable(Level.FINE)) log.fine("addEntriesSingleMode adding entry '" + i + "' in single mode succeeded");
}
if (!conn.getAutoCommit()) conn.commit();
return ret;
}
catch (XmlBlasterException ex1) {
// conn.rollback();
try {
for (int ii=0; ii < i; ii++) {
if (ret[ii] > 0) deleteEntry(queueName, entries[ii].getUniqueId()); // this could be collected and done in one shot
}
}
catch (Throwable ex2) {
log.severe("addEntriesSingleMode exception occured when rolling back (this could generate inconsistencies in the data) : " + ex2.toString());
}
throw ex1;
}
catch (Throwable ex1) {
// conn.rollback();
try {
for (int ii=0; ii < i; ii++) {
if (ret[ii] > 0) deleteEntry(queueName, entries[ii].getUniqueId()); // this could be collected and done in one shot
}
}
catch (Throwable ex2) {
log.severe("addEntriesSingleMode exception occured when rolling back (this could generate inconsistencies in the data) : " + ex2.toString());
}
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNKNOWN, ME + ".addEntriesSingleMode", "", ex1);
}
}
/**
*
* Adds several rows to the specified queue table in batch mode to improve performance
* @param queueName The name of the queue on which to perform the operation
* @param entries the entries to store
* @return array of boolean telling which entries where stored and which not.
*
* @throws SQLException if an error occured while adding the row
* @throws XmlBlasterException if an error occured when trying to get a connection
*/
public int[] addEntries(String queueName, I_Entry[] entries)
throws XmlBlasterException {
if (log.isLoggable(Level.FINER)) log.finer("Entering");
if (!this.isConnected) {
if (log.isLoggable(Level.FINE)) log.fine(" for '" + entries.length + "' currently not possible. No connection to the DB");
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME + ".addEntries", " the connection to the DB is unavailable already before trying to add entries");
}
PreparedStatement preStatement = null;
Connection conn = null;
boolean success = true;
try {
conn = this.pool.getConnection();
if (!this.supportsBatch || !this.enableBatchMode)
return addEntriesSingleMode(conn, queueName, entries);
if (conn.getAutoCommit()) conn.setAutoCommit(false);
String req = "INSERT INTO " + this.entriesTableName + " VALUES ( ?, ?, ?, ?, ?, ?, ?)";
if (log.isLoggable(Level.FINE)) log.fine(req);
preStatement = conn.prepareStatement(req);
preStatement.setQueryTimeout(this.pool.getQueryTimeout());
for (int i=0; i < entries.length; i++) {
I_Entry entry = entries[i];
long dataId = entry.getUniqueId();
int prio = entry.getPriority();
byte[] blob = this.factory.toBlob(entry);
String typeName = entry.getEmbeddedType();
boolean persistent = entry.isPersistent();
long sizeInBytes = entry.getSizeInBytes();
preStatement.setLong(DATA_ID, dataId);
preStatement.setString(QUEUE_NAME, queueName);
preStatement.setInt(PRIO, prio);
preStatement.setString(TYPE_NAME, typeName);
if (persistent == true) preStatement.setString(PERSISTENT, "T");
else preStatement.setString(PERSISTENT, "F");
preStatement.setLong(SIZE_IN_BYTES, sizeInBytes);
ByteArrayInputStream blob_stream = new ByteArrayInputStream(blob);
preStatement.setBinaryStream(BLOB, blob_stream, blob.length); //(int)sizeInBytes);
//preStatement.setBytes(7, blob);
if (log.isLoggable(Level.FINE)) log.fine(preStatement.toString());
preStatement.addBatch();
}
int[] ret = preStatement.executeBatch();
if (!conn.getAutoCommit()) conn.commit();
return ret;
}
catch (Throwable ex) {
success = false;
try {
if (!conn.getAutoCommit()) {
conn.rollback(); // rollback the original request ...
conn.setAutoCommit(true); // since if an exeption occurs it infects future queries within the same transaction
}
}
catch (Throwable ex1) {
log.severe("error occured when trying to rollback after exception: reason: " + ex1.toString() + " original reason:" + ex.toString());
ex.printStackTrace(); // original stack trace
}
if (log.isLoggable(Level.FINE)) log.fine("Could not insert entries: " + ex.toString());
// if ((!this.supportsBatch || !this.enableBatchMode) ||
// checkIfDBLoss(conn, getLogId(queueName, "addEntries"), ex))
if (checkIfDBLoss(conn, getLogId(queueName, "addEntries"), ex))
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME + ".addEntries", "", ex);
else { // check if the exception was due to an already existing entry by re
return addEntriesSingleMode(conn, queueName, entries);
}
}
finally {
try {
if (preStatement != null)
preStatement.close();
}
catch (Throwable ex) {
success = false;
log.warning("addEntries: throwable when closing the statement: " + ex.toString());
}
try {
if (conn != null) {
if (!conn.getAutoCommit())
conn.setAutoCommit(true);
}
}
catch (Throwable ex) {
success = false;
log.warning("addEntries: throwable when closing the connection: " + ex.toString());
}
if (conn != null)
this.pool.releaseConnection(conn, success);
}
}
/**
* Cleans up the specified queue. It deletes all queue entries in the 'entries' table.
* @return the number of queues deleted (not the number of entries).
*/
public final int cleanUp(String queueName) throws XmlBlasterException {
if (log.isLoggable(Level.FINER)) log.finer("Entering");
if (!this.isConnected) {
if (log.isLoggable(Level.FINE))
log.fine("Currently not possible. No connection to the DB");
return 0;
}
try {
String req = "delete from " + this.entriesTableName + " where queueName='" + queueName + "'";
if (log.isLoggable(Level.FINE))
log.fine(" request is '" + req + "'");
int num = update(req);
return (num > 0) ? 1 : 0;
}
catch (XmlBlasterException ex) {
throw ex;
}
catch (Throwable ex) {
Connection conn = null;
boolean success = true;
try {
conn = this.pool.getConnection();
if (checkIfDBLoss(conn, getLogId(queueName, "deleteEntries"), ex))
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME + ".deleteEntries", "", ex);
}
catch (XmlBlasterException e) {
success = false;
throw e;
}
finally {
if (conn != null)
this.pool.releaseConnection(conn, success);
}
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNKNOWN, ME + ".deleteEntries", "", ex);
}
}
/**
* Wipes out the entire DB.
* i.e. it deletes the three tables from the DB. IMPORTANT:
* @param doSetupNewTables if set to 'true' it recreates the necessary empty tables.
*/
public int wipeOutDB(boolean doSetupNewTables) throws XmlBlasterException {
// retrieve all tables to delete
if (log.isLoggable(Level.FINER)) log.finer("wipeOutDB");
int count = 0;
Connection conn = null;
boolean success = true;
try {
try {
String req = "DROP TABLE " + this.entriesTableName;
if (log.isLoggable(Level.FINER)) log.finer("wipeOutDB " + req + " will be invoked on the DB");
conn = this.pool.getConnection();
if (conn.getAutoCommit()) conn.setAutoCommit(false);
this.update(req, conn);
count++;
}
catch (SQLException ex) {
success = false;
if (checkIfDBLoss(conn, getLogId(null, "wipeOutDB"), ex))
throw new XmlBlasterException(glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, getLogId(null, "wipeOutDB"), "SQLException when wiping out DB", ex);
else {
log.warning("Exception occurred when trying to drop the table '" + this.entriesTableName + "', it probably is already dropped. Reason: " + ex.toString());
}
}
if (!conn.getAutoCommit())
conn.commit();
}
catch (Throwable ex) {
success = false;
try {
if (conn != null)
conn.rollback();
}
catch (Throwable ex1) {
success = false;
log.severe("wipeOutDB: exception occurred when rolling back: " + ex1.toString());
}
if (ex instanceof XmlBlasterException) throw (XmlBlasterException)ex;
throw new XmlBlasterException(glob, ErrorCode.RESOURCE_DB_UNKNOWN, getLogId(null, "wipeOutDB"), "wipeOutDB: exception ", ex);
}
finally {
try {
if (conn != null) {
if (!conn.getAutoCommit())
conn.setAutoCommit(true);
}
}
catch (Throwable ex) {
success = false;
throw new XmlBlasterException(glob, ErrorCode.RESOURCE_DB_UNKNOWN, getLogId(null, "wipeOutDB"), "wipeOutDB: exception when closing the query", ex);
}
finally {
if (conn != null)
this.pool.releaseConnection(conn, success);
}
}
try {
if (doSetupNewTables) setUp();
}
catch (Throwable ex) {
log.severe("SQLException occured when cleaning up the table. Reason " + ex.toString());
}
return count;
}
/**
* Dumps the metadata to the log object. The log will write out this
* information as an info. This means you don't need to have the switch
* 'dump' set to true to see this information.
*/
public void dumpMetaData() {
this.pool.dumpMetaData();
}
/**
* returns the amount of bytes currently in the specified queue
* @param tableName the name of the table in which to count
* @return the current amount of bytes used in the table.
* @deprecated Use {@link #getNumOfAll(String)}
*/
public long getNumOfBytes(String queueName)
throws XmlBlasterException {
if (!this.isConnected)
throw new XmlBlasterException(glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, getLogId(queueName, "getNumOfBytes"), "The DB is disconnected. Handling queue '" + queueName + "' is currently not possible");
Connection conn = null;
boolean success = true;
PreparedStatement st = null;
try {
String req = "SELECT sum(" + this.byteSizeColName + ") from " + this.entriesTableName + " where queueName='" + queueName + "'";
conn = this.pool.getConnection();
st = conn.prepareStatement(req);
st.setQueryTimeout(this.pool.getQueryTimeout());
ResultSet rs = st.executeQuery();
rs.next();
return rs.getLong(1);
}
catch (XmlBlasterException ex) {
success = false;
throw ex;
}
catch (Throwable ex) {
success = false;
try {
if (st != null) st.close();
st = null;
}
catch (Throwable ex1) {
log.warning(".getNumOfBytes: exception when closing statement: " + ex1.toString());
}
if (checkIfDBLoss(conn, getLogId(queueName, "getNumOfBytes"), ex))
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME + ".getNumOfBytes", "", ex);
else throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNKNOWN, ME + ".getNumOfBytes", "", ex);
}
finally {
try {
if (st != null)
st.close();
}
catch (Throwable ex) {
success = false;
log.warning(".getNumOfBytes: exception when closing statement: " + ex.toString());
}
if (conn != null)
this.pool.releaseConnection(conn, success);
}
}
/**
* Sets up a table for the queue specified by this queue name.
* If one already exists (i.e. if it recovers from a crash) its associated
* table name is returned.
*/
public final void setUp() throws XmlBlasterException {
if (log.isLoggable(Level.FINER)) log.finer("setUp");
if (log.isLoggable(Level.FINE)) log.fine("Initializing the first time the pool");
tablesCheckAndSetup(this.pool.isDbAdmin());
synchronized(CommonTableDatabaseAccessor.class) {
// Should be only done on startup for each database instance
// To have a real JVM singleton a static bool is not enough (ClassLoader)
// so we use System.properties
String key="xmlBlaster.init." + pool.getUrl()+":"+pool.getUserName()+":"+pool.getTableNamePrefix();
if (System.getProperty(key) == null) {
deleteAllTransient(null);
System.setProperty(key, "true");
}
}
}
/*
* String persistentAsChar = rs.getString(PERSISTENT);
*/
private boolean isPersistent(String persistentAsChar) {
if (persistentAsChar != null)
persistentAsChar = persistentAsChar.trim();
boolean persistent = false;
if ("T".equalsIgnoreCase(persistentAsChar)) persistent = true;
return persistent;
}
private final ArrayList processResultSet(ResultSet rs, StorageId storageIdDefault,
int numOfEntries, long numOfBytes, boolean onlyId,
I_EntryFilter entryFilter)
throws SQLException, XmlBlasterException {
if (log.isLoggable(Level.FINER)) log.finer("Entering");
ArrayList entries = new ArrayList();
int count = 0;
long amount = 0L;
while ( (rs.next()) && ((count < numOfEntries) || (numOfEntries < 0)) &&
((amount < numOfBytes) || (numOfBytes < 0))) {
if(onlyId) {
entries.add(new Long(rs.getLong(1)));
}
else {
long dataId = rs.getLong(DATA_ID); // preStatement.setLong(1, dataId);
String queueName = rs.getString(QUEUE_NAME); // preStatement.setString(3, queueName);
int prio = rs.getInt(PRIO); // preStatement.setInt(4, prio);
String typeName = rs.getString(TYPE_NAME); // preStatement.setString(5, typeName);
if (typeName != null)
typeName = typeName.trim();
//this only to make ORACLE happy since it does not support BOOLEAN
boolean persistent = isPersistent(rs.getString(PERSISTENT));
long sizeInBytes = rs.getLong(SIZE_IN_BYTES);
InputStream is = rs.getBinaryStream(BLOB);
// byte[] blob = rs.getBytes(7); // preStatement.setObject(5, blob);
StorageId storageId = (storageIdDefault != null) ? storageIdDefault : StorageId.valueOf(this.glob,
queueName);
if (is == null) {
String txt = "dataId='" + dataId + "' prio='" + prio + "' typeName='" + typeName + "' persistent='" + persistent + "' sizeInBytes='" + sizeInBytes + "'";
log.warning("The stream for the blob of data: " + txt + " is null");
}
if ( (numOfBytes < 0) || (sizeInBytes+amount < numOfBytes) || (count == 0)) {
if (log.isLoggable(Level.FINEST))
log.finest("processResultSet: dataId: " + dataId + ", prio: " + prio + ", typeName: " + typeName + " persistent: " + persistent);
// entries.add(this.factory.createEntry(prio, dataId, typeName, persistent, sizeInBytes, blob, storageId));
I_Entry entry = this.factory.createEntry(prio, dataId, typeName, persistent, sizeInBytes, is, storageId);
if (entryFilter != null)
entry = entryFilter.intercept(entry, this.storage);
if (entry != null) {
entries.add(entry);
amount += sizeInBytes;
}
}
}
count++;
}
return entries;
}
/**
* It accepts result sets with (long dataId, long size)
* @param numOfBytes as input is the maximum number of bytes to process. As
* output it stores the number of bytes processed.
* @param numOfEntries the maximum number of entries to process
*
*/
private final ReturnDataHolder processResultSetForDeleting(ResultSet rs, int numOfEntries, long numOfBytes,
List<Long> retL)
throws SQLException, XmlBlasterException {
if (log.isLoggable(Level.FINER)) log.finer("processResultSetForDeleting invoked");
ReturnDataHolder ret = new ReturnDataHolder();
long currentAmount = 0L;
while ( (rs.next()) && ((ret.countEntries < numOfEntries) || (numOfEntries < 0)) &&
((ret.countBytes < numOfBytes) || (numOfBytes < 0))) {
currentAmount = rs.getLong(2);
if ( (numOfBytes < 0) || (ret.countBytes+currentAmount < numOfBytes) || (ret.countEntries == 0)) {
retL.add(new Long(rs.getLong(1)));
ret.countBytes += currentAmount;
ret.countEntries++;
}
}
return ret;
}
/**
* Returns the pool associated to this object.
* @return JdbcConnectionPool the pool managing the connections to the DB
*/
public JdbcConnectionPool getPool() {
return this.pool;
}
/**
* A generic invocation for a simple update on the database. It will use
* a normal statement (not a prepared statement),
* @param request the request string
* @param fetchSize the number of entries to fetch, i.e. to update
*/
private final int update(String request)
throws SQLException, XmlBlasterException {
if (log.isLoggable(Level.FINER)) log.finer("Entering request=" + request);
Connection conn = null;
boolean success = false;
Statement statement = null;
int ret = 0;
try {
conn = pool.getConnection();
statement = conn.createStatement();
statement.setQueryTimeout(this.pool.getQueryTimeout());
ret = statement.executeUpdate(request);
if (log.isLoggable(Level.FINE)) log.fine("Executed statement '" + request + "', number of changed entries=" + ret);
success = true;
}
finally {
try {
if (statement !=null)
statement.close();
}
catch (Throwable ex) {
success = false;
log.warning("update: throwable when closing statement: " + ex.toString());
}
if (conn != null)
this.pool.releaseConnection(conn, success);
}
return ret;
}
/**
* This version makes an update but does no connection management, i.e. a
* connection must be established outside this method and passed in the
* argument list. The closing and cleaning up of the connection must also
* be handled outside.
*
* @param request the string specifying which request to make
* @param conn the connection to use for this request
*/
private final int update(String request, Connection conn)
throws SQLException {
if (log.isLoggable(Level.FINER)) log.finer("Request=" + request + " and connection " + conn);
Statement statement = null;
int ret = 0;
try {
statement = conn.createStatement();
statement.setQueryTimeout(this.pool.getQueryTimeout());
ret = statement.executeUpdate(request);
if (log.isLoggable(Level.FINE)) log.fine("Executed statement '" + request + "', number of changed entries=" + ret);
}
finally {
try {
if (statement !=null) statement.close();
}
catch (Throwable ex) {
log.warning("update: throwable when closing statement: " + ex.toString());
}
}
return ret;
}
/**
* deletes all transient messages
*/
public int deleteAllTransient(String queueName) throws XmlBlasterException {
try {
if (log.isLoggable(Level.FINER))
log.finer("deleteAllTransient");
if (!this.isConnected) {
if (log.isLoggable(Level.FINE)) log.fine("Currently not possible. No connection to the DB");
return 0;
}
String req = "";
if (queueName == null)
req = "delete from " + this.entriesTableName + " where durable='F'";
else
req = "delete from " + this.entriesTableName + " where queueName='" + queueName + "' AND durable='F'";
return update(req);
}
catch (XmlBlasterException ex) {
throw ex;
}
catch (Throwable ex) {
Connection conn = null;
boolean success = false;
try {
conn = this.pool.getConnection();
if (checkIfDBLoss(conn, getLogId(queueName, "deleteAllTransient"), ex))
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME + ".deleteAllTransient", "", ex);
success = true;
}
finally {
if (conn != null)
this.pool.releaseConnection(conn, success);
}
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNKNOWN, ME + ".deleteAllTransient", "", ex);
}
}
/**
* The prefix is the initial part of the SQL update/query. Note that this
* method can be used both for SELECT statements as for updates such as
* DELETE or UPDATE.
* An example of prefix:
* "delete from tableName where dataId in(";
*/
private final ArrayList whereInStatement(String reqPrefix, long[] uniqueIds) {
if (log.isLoggable(Level.FINER)) log.finer("whereInStatement");
final String reqPostfix = ")";
boolean isFirst = true;
int initialLength = reqPrefix.length() + reqPostfix.length() + 2;
StringBuffer buf = new StringBuffer();
int length = initialLength;
int currentLength = 0;
ArrayList ret = new ArrayList();
int count = 0;
for (int i=0; i<uniqueIds.length; i++) {
String req = null;
String entryId = Long.toString(uniqueIds[i]);
currentLength = entryId.length();
length += currentLength;
if ((length > this.maxStatementLength) || (i == (uniqueIds.length-1)) || count >= this.maxNumStatements) { // then make the update
if (i == (uniqueIds.length-1)) {
if (!isFirst) buf.append(",");
count++;
buf.append(entryId);
}
req = reqPrefix + buf.toString() + reqPostfix;
if (count > 0)
ret.add(req);
length = initialLength + currentLength;
buf = new StringBuffer();
count = 0;
isFirst = true;
}
else
count++;
if (!isFirst) {
buf.append(",");
length++;
}
else
isFirst = false;
count++;
buf.append(entryId);
}
return ret;
}
/**
* Helper method to find out if still to retrieve entries in getAndDeleteLowest or not.
*/
private final boolean isInsideRange(int numEntries, int maxNumEntries, long numBytes, long maxNumBytes) {
if (maxNumEntries < 0) {
if (maxNumBytes <0L) return true;
return numBytes < maxNumBytes;
}
// then maxNumEntries >= 0
if (maxNumBytes <0L) return numEntries < maxNumEntries;
// then the less restrictive of both is used (since none is negative)
return numEntries < maxNumEntries || numBytes < maxNumBytes;
}
/**
* Under the same transaction it gets and deletes all the entries which fit
* into the constrains specified in the argument list.
* The entries are really deleted only if doDelete is true, otherwise they are left untouched on the queue
* @see org.xmlBlaster.util.queue.I_Queue#takeLowest(int, long, org.xmlBlaster.util.queue.I_QueueEntry, boolean)
*/
public ReturnDataHolder getAndDeleteLowest(StorageId storageId, int numOfEntries, long numOfBytes,
int maxPriority, long minUniqueId, boolean leaveOne, boolean doDelete) throws XmlBlasterException {
String queueName = storageId.getStrippedId();
if (log.isLoggable(Level.FINER)) log.finer("Entering");
ReturnDataHolder ret = new ReturnDataHolder();
if (!this.isConnected) {
if (log.isLoggable(Level.FINE))
log.fine("Currently not possible. No connection to the DB");
return ret;
}
PreparedQuery query = null;
boolean success = true;
try {
String req = "select * from " + this.entriesTableName + " WHERE queueName='" + queueName + "' ORDER BY prio ASC, " + this.dataIdColName + " DESC";
query = new PreparedQuery(pool, req, false, -1);
// process the result set. Give only back what asked for (and only delete that)
ResultSet rs = query.rs;
boolean doContinue = true;
boolean stillEntriesInQueue = false;
while ( (stillEntriesInQueue=rs.next()) && doContinue) {
long dataId = rs.getLong(DATA_ID); // preStatement.setLong(1, dataId);
/*String queueName =*/ rs.getString(QUEUE_NAME); // preStatement.setString(3, queueName);
int prio = rs.getInt(PRIO); // preStatement.setInt(4, prio);
String typeName = rs.getString(TYPE_NAME); // preStatement.setString(5, typeName);
//boolean persistent = rs.getBoolean(PERSISTENT); // preStatement.setBoolean(4, persistent);
//this only to make ORACLE happy since it does not support BOOLEAN
boolean persistent = isPersistent(rs.getString(PERSISTENT));
long sizeInBytes = rs.getLong(SIZE_IN_BYTES);
if (!isInsideRange((int)ret.countEntries, numOfEntries, ret.countBytes, numOfBytes)) break;
// byte[] blob = rs.getBytes(7); // preStatement.setObject(5, blob);
InputStream is = rs.getBinaryStream(BLOB);
// check if allowed or already outside the range ...
if ((prio<maxPriority) || ((prio==maxPriority)&&(dataId>minUniqueId)) ) {
if (log.isLoggable(Level.FINEST)) log.finest("dataId: " + dataId + ", prio: " + prio + ", typeName: " + typeName + " persistent: " + persistent);
// ret.list.add(this.factory.createEntry(prio, dataId, typeName, persistent, sizeInBytes, blob, storageId));
ret.list.add(this.factory.createEntry(prio, dataId, typeName, persistent, sizeInBytes, is, storageId));
ret.countBytes += sizeInBytes;
if (persistent)
ret.countPersistentBytes += sizeInBytes;
}
else doContinue = false;
ret.countEntries++;
if (persistent)
ret.countPersistentEntries++;
}
// prepare for deleting (we don't use deleteEntries since we want
// to use the same transaction (and the same connection)
if (leaveOne) {
// leave at least one entry
if (stillEntriesInQueue) stillEntriesInQueue = rs.next();
if ((!stillEntriesInQueue) && (ret.list.size()>0)) {
ret.countEntries--;
I_Entry entryToDelete = ret.list.remove(ret.list.size() - 1);
ret.countBytes -= entryToDelete.getSizeInBytes();
boolean persistent = entryToDelete.isPersistent();
if (persistent) {
ret.countPersistentEntries--;
ret.countPersistentBytes -= entryToDelete.getSizeInBytes();
}
if (log.isLoggable(Level.FINE)) log.fine("takeLowest size to delete: " + entryToDelete.getSizeInBytes());
}
}
if (doDelete) {
//first strip the unique ids:
long[] uniqueIds = new long[ret.list.size()];
for (int i=0; i < uniqueIds.length; i++)
uniqueIds[i] = (ret.list.get(i)).getUniqueId();
String reqPrefix = "delete from " + this.entriesTableName + " where queueName='" + queueName + "' AND " + this.dataIdColName + " in(";
ArrayList reqList = this.whereInStatement(reqPrefix, uniqueIds);
for (int i=0; i < reqList.size(); i++) {
req = (String)reqList.get(i);
if (log.isLoggable(Level.FINE))
log.fine("'delete from " + req + "'");
update(req, query.conn);
}
}
if (!query.conn.getAutoCommit()) query.conn.commit();
return ret;
}
catch (XmlBlasterException ex) {
success = false;
throw ex;
}
catch (Throwable ex) {
success = false;
try {
if (query != null && query.rs != null) {
query.rs.close();
query.rs = null;
}
}
catch (Throwable ex1) {
log.severe("exception occured when closing query: " + ex1.toString());
}
try {
if (query != null && query.conn != null) query.conn.rollback();
}
catch (Throwable ex1) {
log.severe("could not rollback: " + ex.toString());
ex1.printStackTrace();
}
Connection tmpConn = null;
if (query != null) {
tmpConn = query.conn;
query.closeStatement();
}
if (checkIfDBLoss(tmpConn, getLogId(queueName, "getAndDeleteLowest"), ex))
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME + ".getAndDeleteLowest", "", ex);
else throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNKNOWN, ME + ".getAndDeleteLowest", "", ex);
}
finally {
try {
if (query != null)
query.close(success);
}
catch (Throwable ex1) {
log.severe("exception when closing query: " + ex1.toString());
ex1.printStackTrace();
}
}
}
/**
* Deletes the entries specified by the entries array. Since all entries
* are deleted at the same time, in case of an exception the result
* is uncertain (it depends on what the used database will do). In case the
* statement would be too long for the DB, the request is split into several
* statements which are invoked separately.
*
* @param tableName the name of the table on which to delete the entries
* @param uniqueIds the array containing all the uniqueId for the entries to delete.
*/
public boolean[] deleteEntries(String queueName, long[] uniqueIds) throws XmlBlasterException {
/*
// JdbcManagerCommonTableDelegate.java specific handling
////// !!!!!!!!!!!!!! TEST ONLY on erase(): java javaclients.HelloWorldPublish -persistent true
if (true)
//throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNKNOWN, ME + ".TESTING ONLY!!!", "");
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME + ".TESTING ONLY!!!", "");
*/
if (this.maxNumStatements > 0 && this.maxNumStatements < uniqueIds.length) {
int rest = uniqueIds.length;
int offset = 0;
boolean[] ret = new boolean[rest];
while (rest > 0) {
int nmax = this.maxNumStatements;
if (rest < nmax)
nmax = rest;
long[] ids = new long[nmax];
for (int i=0; i < ids.length; i++)
ids[i] = uniqueIds[i+offset];
boolean[] tmpRet = deleteEntriesNoSplit(queueName, ids);
for (int i=0; i < tmpRet.length; i++) {
ret[offset + i] = tmpRet[i];
}
offset+= tmpRet.length;
rest -= ids.length;
}
return ret;
}
else
return deleteEntriesNoSplit(queueName, uniqueIds);
}
private boolean[] deleteEntriesNoSplit(String queueName, long[] uniqueIds)
throws XmlBlasterException {
if (log.isLoggable(Level.FINER)) log.finer("Entering");
if (!this.isConnected) {
if (log.isLoggable(Level.FINE))
log.fine("Currently not possible. No connection to the DB");
return new boolean[uniqueIds.length];
}
Connection conn = null;
boolean success = true;
try {
int count = 0;
String reqPrefix = "delete from " + this.entriesTableName + " where queueName='" + queueName + "' AND " + this.dataIdColName + " in(";
ArrayList reqList = whereInStatement(reqPrefix, uniqueIds);
conn = pool.getConnection();
if (conn.getAutoCommit()) conn.setAutoCommit(false);
for (int i=0; i < reqList.size(); i++) {
String req = (String)reqList.get(i);
if (log.isLoggable(Level.FINE))
log.fine("' " + req + "'");
count += update(req, conn);
}
if (count != uniqueIds.length)
conn.rollback(); // not all entries have been deleted:
// will be handled individually below (to know which is deleted and which not)
else {
if (!conn.getAutoCommit()) conn.commit();
boolean[] ret = new boolean[uniqueIds.length];
for (int i=0; i < ret.length; i++) ret[i] = true;
return ret;
}
}
catch (XmlBlasterException ex) {
success = false;
throw ex;
}
catch (Throwable ex) {
success = false;
if (checkIfDBLoss(conn, getLogId(queueName, "deleteEntries"), ex))
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME + ".deleteEntries", "", ex);
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNKNOWN, ME + ".deleteEntries", "", ex);
}
finally {
if (conn != null) {
try {
if (!conn.getAutoCommit())
conn.setAutoCommit(true);
}
catch (Throwable ex) {
success = false;
log.severe("error when setting autocommit to 'true'. reason: " + ex.toString());
ex.printStackTrace();
}
this.pool.releaseConnection(conn, success);
}
}
// handle individually because of the above rollback
boolean[] ret = new boolean[uniqueIds.length];
for (int i=0; i < uniqueIds.length; i++) {
ret[i] = deleteEntry(queueName, uniqueIds[i]) == 1;
}
return ret;
}
/**
* Deletes the entry specified
*
* @param tableName the name of the table on which to delete the entries
* @param uniqueIds the array containing all the uniqueId for the entries to delete.
*/
/*
public int[] deleteEntriesBatch(String queueName, long[] uniqueIds)
throws XmlBlasterException {
if (log.isLoggable(Level.FINER)) log.call(getLogId(queueName, "deleteEntriesBatch"), "Entering");
if (!this.isConnected) {
if (log.isLoggable(Level.FINE))
log.trace(getLogId(queueName, "deleteEntry"), "Currently not possible. No connection to the DB");
int[] ret = new int[uniqueIds.length];
for (int i=0; i < ret.length; i++) ret[i] = 0;
return ret;
}
Connection conn = null;
PreparedStatement st = null;
try {
conn = this.pool.getConnection();
if (conn.getAutoCommit()) conn.setAutoCommit(false);
String req = "delete from " + this.entriesTableName + " where queueName=? AND dataId=?";
st = conn.prepareStatement(req);
for (int i=0; i < uniqueIds.length; i++) {
st.setString(1, queueName);
st.setLong(2, uniqueIds[i]);
st.addBatch();
}
int[] ret = st.executeBatch();
if (!conn.getAutoCommit()) conn.commit();
return ret;
}
catch (BatchUpdateException ex) {
try {
if (!conn.getAutoCommit()) conn.commit();
}
catch (Throwable ex1) {
log.error(ME, "could not commit correclty: " + ex1.toString());
ex1.printStackTrace();
}
return ex.getUpdateCounts();
}
catch (XmlBlasterException ex) {
throw ex;
}
catch (Throwable ex) {
try {
if (!conn.getAutoCommit()) conn.commit();
}
catch (Throwable ex1) {
log.error(ME, "could not rollback correclty: " + ex1.toString());
ex1.printStackTrace();
}
if (checkIfDBLoss(conn, getLogId(queueName, "deleteEntry"), ex))
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME + ".deleteEntry", "", ex);
else throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNKNOWN, ME + ".deleteEntry", "", ex);
}
finally {
try {
if (st != null) st.close();
}
catch (Throwable ex) {
log.warn(ME, "deleteEntry: throwable when closing the connection: " + ex.toString());
}
if (conn != null) {
try {
if (!conn.getAutoCommit()) conn.setAutoCommit(true);
}
catch (Throwable ex1) {
log.error(ME, "could not rollback correclty: " + ex1.toString());
ex1.printStackTrace();
}
this.pool.releaseConnection(conn);
}
}
}
*/
/**
* Deletes the entry specified
*
* @param tableName the name of the table on which to delete the entries
* @param uniqueIds the array containing all the uniqueId for the entries to delete.
*/
public int deleteEntry(String queueName, long uniqueId)
throws XmlBlasterException {
if (log.isLoggable(Level.FINER)) log.finer("Entering");
if (!this.isConnected) {
if (log.isLoggable(Level.FINE))
log.fine("Currently not possible. No connection to the DB");
return 0;
}
Connection conn = null;
boolean success = true;
String req = "delete from " + this.entriesTableName + " where queueName='"+queueName+"' AND " + this.dataIdColName + "="+uniqueId;
try {
conn = this.pool.getConnection();
return update(req, conn);
}
catch (XmlBlasterException ex) {
success = false;
throw ex;
}
catch (Throwable ex) {
success = false;
if (checkIfDBLoss(conn, getLogId(queueName, "deleteEntry"), ex))
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME + ".deleteEntry", req, ex);
else throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNKNOWN, ME + ".deleteEntry", req, ex);
}
finally {
if (conn != null)
this.pool.releaseConnection(conn, success);
}
}
/**
* Deletes number of entries specified by the argument list. If less than
* that amount of elements is given, then all entries will be deleted.
* If you specify -1 as the number of entries, then all entries will be
* deleted.
*
* @param tableName the name of the table on which to act.
* @param numOfEntries the number of entries to be deleted.
* @param amount the maximum amount of bytes to remove. Note that if no entries
* fit into this size, the first entry is taken anyway (to avoid deadlocks)
*/
public ReturnDataHolder deleteFirstEntries(String queueName, long numOfEntries, long amount)
throws XmlBlasterException {
if (log.isLoggable(Level.FINER)) log.finer("Entering");
ReturnDataHolder ret = new ReturnDataHolder();
if (!this.isConnected) {
if (log.isLoggable(Level.FINE))
log.fine("Currently not possible. No connection to the DB");
return ret;
}
if (numOfEntries >= Integer.MAX_VALUE)
throw new XmlBlasterException(glob, ErrorCode.INTERNAL_ILLEGALARGUMENT, getLogId(queueName, "deleteFirstEntries"),
"The number of entries=" + numOfEntries + " to be deleted is too big for this system");
PreparedQuery query = null;
boolean success = true;
try {
String req = "select " + this.dataIdColName + "," + this.byteSizeColName + " from " + this.entriesTableName + " WHERE queueName='" + queueName + "' ORDER BY prio DESC, " + this.dataIdColName + " ASC";
query = new PreparedQuery(pool, req, false, -1);
// I only want the uniqueId (dataId)
List<Long> retL = new ArrayList<Long>();
ret = processResultSetForDeleting(query.rs, (int) numOfEntries, amount, retL);
int nmax = retL.size();
if (nmax < 1) return ret;
long[] uniqueIds = new long[nmax];
for (int i = 0; i < nmax; i++)
uniqueIds[i] = (retL.get(i)).longValue();
ArrayList reqList = whereInStatement("delete from " + this.entriesTableName + " where queueName='" + queueName + "' AND " + this.dataIdColName + " in(", uniqueIds);
ret.countEntries = 0L;
// everything in the same transaction (just in case)
for (int i=0; i < reqList.size(); i++) {
req = (String)reqList.get(i);
if (log.isLoggable(Level.FINE)) log.fine("'" + req + "'");
ret.countEntries += update(req, query.conn);
}
return ret;
}
catch (XmlBlasterException ex) {
success = false;
throw ex;
}
catch (Throwable ex) {
success = false;
if (query != null)
query.closeStatement();
if (checkIfDBLoss(query != null ? query.conn : null, getLogId(queueName, "deleteFirstEntries"), ex))
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME + ".deleteFirstEntries", "", ex);
else throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNKNOWN, ME + ".deleteFirstEntries", "", ex);
}
finally {
try {
if (query != null)
query.close(success);
}
catch (Throwable ex1) {
log.severe("exception when closing query: " + ex1.toString());
ex1.printStackTrace();
}
}
}
/**
* gets the first numOfEntries of the queue which have the priority in the
* range specified by prioMin and prioMax (inclusive).
* If there are not so many entries in the queue, all elements in the queue
* are returned.
*
* @param storageId the storageId of the queue/storage from which to retrieve the information.
* @param numOfEntries the maximum number of elements to retrieve. If negative there is no constriction.
* @param numOfBytes the maximum number of bytes to retrieve. If negative, there is no constriction.
* @param minPrio the minimum priority to retreive (inclusive).
* @param maxPrio the maximum priority to retrieve (inclusive).
*
*/
public ArrayList getEntriesByPriority(StorageId storageId, int numOfEntries,
long numOfBytes, int minPrio, int maxPrio, I_EntryFilter entryFilter)
throws XmlBlasterException {
String queueName = storageId.getStrippedId();
if (log.isLoggable(Level.FINER)) log.finer("Entering");
if (!this.isConnected) {
if (log.isLoggable(Level.FINE)) log.fine("Currently not possible. No connection to the DB");
return new ArrayList();
}
String req = "SELECT * from " + this.entriesTableName + " where queueName='" + queueName + "' AND prio >= " + minPrio + " and prio <= " + maxPrio +
" ORDER BY prio DESC, " + this.dataIdColName + " ASC";
if (log.isLoggable(Level.FINE)) log.fine("Request: '" + req + "'");
PreparedQuery query =null;
boolean success = true;
try {
query = new PreparedQuery(pool, req, numOfEntries);
ArrayList ret = processResultSet(query.rs, storageId, numOfEntries, numOfBytes, false, entryFilter);
if (log.isLoggable(Level.FINE)) log.fine("Found " + ret.size() + " entries");
return ret;
}
catch (XmlBlasterException ex) {
success = false;
throw ex;
}
catch (Throwable ex) {
success = false;
if (query != null)
query.closeStatement();
if (checkIfDBLoss(query != null ? query.conn : null, getLogId(queueName, "getEntriesByPriority"), ex))
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME + ".getEntriesByPriority", "", ex);
else throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNKNOWN, ME + ".getEntriesByPriority", "", ex);
}
finally {
try {
if (query != null)
query.close(success);
}
catch (Throwable ex1) {
log.severe("exception when closing query: " + ex1.toString());
ex1.printStackTrace();
}
}
}
/**
* gets the first numOfEntries of the queue which have the same priority.
* If there are not so many entries in the queue, all elements in the queue
* are returned.
*
* @param numOfEntries the maximum number of elements to retrieve
*
*/
public ArrayList getEntriesBySamePriority( StorageId storageId, int numOfEntries, long numOfBytes)
throws XmlBlasterException {
String queueName = storageId.getStrippedId();
// 65 ms (for 10000 msg)
if (log.isLoggable(Level.FINER)) log.finer("Entering");
if (!this.isConnected) {
if (log.isLoggable(Level.FINE))
log.fine("Currently not possible. No connection to the DB");
return new ArrayList();
}
String req = null;
PreparedQuery query = null;
boolean success = true;
try {
if (!this.pool.isNestedBracketsSuppported()) {
int prio = 0;
req = "SELECT max(prio) from " + this.entriesTableName + " where queueName='" + queueName + "'";
query = new PreparedQuery(pool, req, numOfEntries);
query.rs.next();
prio = query.rs.getInt(1);
if (log.isLoggable(Level.FINE)) log.fine("Max prio " + new Integer(prio).toString());
query.close(success);
query = null;
req = "SELECT * from " + this.entriesTableName + " where queueName='" + queueName +
"' and prio=" + prio + " ORDER BY " + this.dataIdColName + " ASC";
}
else {
req = "SELECT * from " + this.entriesTableName + " where queueName='" + queueName +
"' and prio=(select max(prio) from " + this.entriesTableName + " where queueName='" + queueName +
"') ORDER BY " + this.dataIdColName + " ASC";
}
if (log.isLoggable(Level.FINE)) log.fine("Request: '" + req + "'");
query = new PreparedQuery(pool, req, numOfEntries);
I_EntryFilter entryFilter = null;
ArrayList ret = processResultSet(query.rs, storageId, numOfEntries, numOfBytes, false, entryFilter);
if (log.isLoggable(Level.FINE)) log.fine("Found " + ret.size() + " entries");
return ret;
}
catch (XmlBlasterException ex) {
success = false;
throw ex;
}
catch (Throwable ex) {
success = false;
if (query != null)
query.closeStatement();
if (checkIfDBLoss(query != null ? query.conn : null, getLogId(queueName, "getEntriesBySamePriority"), ex))
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME + ".getEntriesBySamePriority", "", ex);
else throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNKNOWN, ME + ".getEntriesBySamePriority", "", ex);
}
finally {
try {
if (query != null)
query.close(success);
}
catch (Throwable ex1) {
log.severe("exception when closing query: " + ex1.toString());
ex1.printStackTrace();
}
}
}
private int countDumps = 0;
/**
* gets the first numOfEntries of the queue.
* If there are not so many entries in the queue, all elements in the queue
* are returned.
* <p>
* Is public for testsuite only.
* @param numOfEntries Access num entries, if -1 access all entries currently found
* @param numOfBytes is the maximum size in bytes of the array to return, -1 is unlimited .
*/
public ArrayList getEntries(StorageId storageId, int numOfEntries, long numOfBytes, I_EntryFilter entryFilter)
throws XmlBlasterException {
String queueName = storageId.getStrippedId();
if (log.isLoggable(Level.FINER)) log.finer("Entering");
if (!this.isConnected) {
if (log.isLoggable(Level.FINE)) log.fine("Currently not possible. No connection to the DB");
return new ArrayList();
}
// "SELECT * from " + this.entriesTableName + " where queueName='" + queueName
// + "' ORDER BY prio DESC, " + this.dataIdColName + " ASC";
String req = null;
if (numOfEntries == 1) {
//SELECT * from xb_entries where dataId=(select min(dataId) from XB_ENTRIES where queueName='topicStore_heron');
String sub = "select min("+this.dataIdColName+") from " + this.entriesTableName + " where queueName='"+queueName+"'";
req = "SELECT * from " + this.entriesTableName + " where " + this.dataIdColName + "=("+sub+")";
}
else if (numOfEntries == 0) {
req = "select max("+this.dataIdColName+") from " + this.entriesTableName; // do a ping
}
else if (numOfEntries == -1/* && numOfBytes == -1*/) {
req = "SELECT * from " + this.entriesTableName + " where queueName='" + queueName + "' ORDER BY prio DESC, " + this.dataIdColName + " ASC";
}
else {
// numOfEntries > 1, see http://www.swissql.com/sqlone-console-review.pdf
// see statement.setFetchSize(1); -> PreparedQuery(pool, req, numOfEntries);
// SQLServer: select top 1 queuename from xb_entries where queueName like '%'
// Postgres; SELECT * from xb_entries limit 1
if (isPostgres() || isMySql()) {
req = "SELECT * from " + this.entriesTableName + " where queueName='"
+ queueName + "' ORDER BY prio DESC, " + this.dataIdColName + " ASC limit " + numOfEntries;
}
else if (isOracle()) {
req = "SELECT * from " + this.entriesTableName + " where queueName='" + queueName
+ "' AND rownum <=" + numOfEntries + " ORDER BY prio DESC, " + this.dataIdColName + " ASC";
}
else if (isDB2()) {
req = "SELECT * from " + this.entriesTableName + " where queueName='" + queueName
+ "' ORDER BY prio DESC, " + this.dataIdColName + " ASC FETCH FIRST " + numOfEntries + " ROWS ONLY";
}
else if (isMicrosoftSQLServer()) {
if (true) {
if (numOfEntries > 10000 && numOfEntries != 100000) {
synchronized (this) {
if (countDumps < 20) {
log.info("numOfEntries=" + numOfEntries + "\r\n" + ThreadLister.getAllStackTraces());
countDumps++;
}
}
}
}
int selectLimit = numOfEntries;
if (maxSelectLimit > 0 && numOfEntries > maxSelectLimit) {
selectLimit = maxSelectLimit;
}
req = "SELECT TOP " + selectLimit + " * from " + this.entriesTableName + " where queueName='" + queueName
+ "' ORDER BY prio DESC, " + this.dataIdColName + " ASC";
}
else if (isHSQLDatabaseEngine()) {
req = "SELECT TOP " + numOfEntries + " * from " + this.entriesTableName + " where queueName='" + queueName
+ "' ORDER BY prio DESC, " + this.dataIdColName + " ASC";
}
else {
req = "SELECT * from " + this.entriesTableName + " where queueName='" + queueName
+ "' ORDER BY prio DESC, " + this.dataIdColName + " ASC";
}
}
if (log.isLoggable(Level.FINE))
log.fine("Request: '" + req + "' wanted limits: numOfEntries="+numOfEntries+" numOfBytes="+numOfBytes);
PreparedQuery query = null;
boolean success = true;
try {
query = new PreparedQuery(pool, req, numOfEntries);
ArrayList ret = processResultSet(query.rs, storageId, numOfEntries, numOfBytes, false, entryFilter);
if (log.isLoggable(Level.FINE)) log.fine("Found " + ret.size() + " entries. Wanted limits: numOfEntries="+numOfEntries+" numOfBytes="+numOfBytes);
//log.info(req + " Found " + ret.size() + " entries. Wanted limits: numOfEntries="+numOfEntries+" numOfBytes="+numOfBytes);
return ret;
}
catch (XmlBlasterException ex) {
success = false;
throw ex;
}
catch (Throwable ex) {
success = false;
ex.printStackTrace();
if (query != null)
query.closeStatement();
if (checkIfDBLoss(query != null ? query.conn : null, getLogId(queueName, "getEntries"), ex))
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME + ".getEntries", "", ex);
else throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNKNOWN, ME + ".getEntries", "", ex);
}
finally {
try {
if (query != null)
query.close(success);
}
catch (Throwable ex1) {
log.severe("exception when closing query: " + ex1.toString());
ex1.printStackTrace();
}
}
}
/**
* Raw access to the table.
* If there are not so many entries in the queue, all elements in the queue
* are returned.
*
* @param numOfEntries the maximum number of elements to retrieve
*
*/
public ArrayList getEntriesLike(String queueNamePattern, String flag,
int numOfEntries, long numOfBytes,
I_EntryFilter entryFilter)
throws XmlBlasterException {
if (log.isLoggable(Level.FINER)) log.finer("Entering");
StorageId storageId = null;
if (!this.isConnected) {
if (log.isLoggable(Level.FINE)) log.fine("Currently not possible. No connection to the DB");
return new ArrayList();
}
String req = null;
if (queueNamePattern != null)
req = "SELECT * from " + this.entriesTableName + " where queueName like'" + queueNamePattern + "'";
else
req = "SELECT * from " + this.entriesTableName;
if (flag != null) req += " and flag='" + flag + "'";
req += " ORDER BY prio DESC, " + this.dataIdColName + " ASC";
if (log.isLoggable(Level.FINE)) log.fine("Request: '" + req + "' wanted limits: numOfEntries="+numOfEntries+" numOfBytes="+numOfBytes);
PreparedQuery query = null;
try {
query = new PreparedQuery(pool, req, numOfEntries);
ArrayList ret = processResultSet(query.rs, storageId, numOfEntries, numOfBytes, false, entryFilter);
if (log.isLoggable(Level.FINE)) log.fine("Found " + ret.size() + " entries. Wanted limits: numOfEntries="+numOfEntries+" numOfBytes="+numOfBytes);
return ret;
}
catch (SQLException ex) {
if (query != null) query.closeStatement();
if (checkIfDBLoss(query != null ? query.conn : null, getLogId(queueNamePattern, "getEntriesLike"), ex))
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME + ".getEntriesLike", "", ex);
else throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNKNOWN, ME + ".getEntriesLike", "", ex);
}
finally {
try {
if (query != null) query.close(true);
}
catch (Throwable ex1) {
log.severe("exception when closing query: " + ex1.toString());
ex1.printStackTrace();
}
}
}
/**
* gets the first numOfEntries of the queue until the limitEntry is reached.
*
* @param numOfEntries the maximum number of elements to retrieve
*/
public ArrayList getEntriesWithLimit(StorageId storageId, I_Entry limitEntry)
throws XmlBlasterException {
String queueName = storageId.getStrippedId();
if (log.isLoggable(Level.FINER)) log.finer("Entering");
if (!this.isConnected) {
if (log.isLoggable(Level.FINE)) log.fine("Currently not possible. No connection to the DB");
return new ArrayList();
}
int limitPrio = limitEntry.getPriority();
long limitId = limitEntry.getUniqueId();
String req = "SELECT * from " + this.entriesTableName + " WHERE queueName='" + queueName + "' AND (prio > " + limitPrio + " OR (prio = " + limitPrio + " AND " + this.dataIdColName + " < " + limitId + ") ) ORDER BY prio DESC, " + this.dataIdColName + " ASC";
if (log.isLoggable(Level.FINE)) log.fine("Request: '" + req + "'");
PreparedQuery query = null;
boolean success = true;
try {
query = new PreparedQuery(pool, req, -1);
I_EntryFilter entryFilter = null;
ArrayList ret = processResultSet(query.rs, storageId, -1, -1L, false, entryFilter);
if (log.isLoggable(Level.FINE)) log.fine("Found " + ret.size() + " entries");
return ret;
}
catch (XmlBlasterException ex) {
success = false;
throw ex;
}
catch (Throwable ex) {
success = false;
if (query != null) query.closeStatement();
if (checkIfDBLoss(query != null ? query.conn : null, getLogId(queueName, "getEntriesWithLimit"), ex))
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME + ".getEntriesWithLimit", "", ex);
else throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNKNOWN, ME + ".getEntriesWithLimit", "", ex);
}
finally {
try {
if (query != null)
query.close(success);
}
catch (Throwable ex1) {
log.severe("exception when closing query: " + ex1.toString());
ex1.printStackTrace();
}
}
}
/**
* deletes the first numOfEntries of the queue until the limitEntry is reached.
* @param numOfEntries the maximum number of elements to retrieve
*/
public long removeEntriesWithLimit(StorageId storageId, I_Entry limitEntry, boolean inclusive)
throws XmlBlasterException {
try {
String queueName = storageId.getStrippedId();
if (log.isLoggable(Level.FINER)) log.finer("Entering");
if (!this.isConnected) {
if (log.isLoggable(Level.FINE)) log.fine("Currently not possible. No connection to the DB");
return 0L;
}
int limitPrio = limitEntry.getPriority();
long limitId = limitEntry.getUniqueId();
String req = null;
if (inclusive)
req = "DELETE from " + this.entriesTableName + " WHERE queueName='" + queueName + "' AND (prio > " + limitPrio + " OR (prio = " + limitPrio + " AND " + this.dataIdColName + " <= " + limitId + ") )";
else
req = "DELETE from " + this.entriesTableName + " WHERE queueName='" + queueName + "' AND (prio > " + limitPrio + " OR (prio = " + limitPrio + " AND " + this.dataIdColName + " < " + limitId + ") )";
if (log.isLoggable(Level.FINE)) log.fine("Request: '" + req + "'");
int ret = update(req);
if (log.isLoggable(Level.FINE)) log.fine("removeEntriesWithLimit the result of the request '" + req + "' is : '" + ret + "'");
return ret;
}
catch (SQLException ex) {
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNKNOWN, ME + ".removeEntriesWithLimit", "", ex);
}
}
/**
* gets all the entries which have the dataid specified in the argument list.
* If the list is empty or null, an empty ArrayList object is returned.
*/
public ArrayList getEntries(StorageId storageId, long[] dataids)
throws XmlBlasterException {
String queueName = storageId.getStrippedId();
if (log.isLoggable(Level.FINER)) log.finer("Entering");
if (!this.isConnected) {
if (log.isLoggable(Level.FINE)) log.fine("Currently not possible. No connection to the DB");
return new ArrayList();
}
String req = null;
if ((dataids == null) || (dataids.length < 1)) return new ArrayList();
req = "SELECT * FROM " + this.entriesTableName + " WHERE queueName='" + queueName + "' AND " + this.dataIdColName + " in (";
ArrayList requests = this.whereInStatement(req, dataids);
PreparedQuery query = null;
boolean success = true;
try {
req = (String)requests.get(0);
if (log.isLoggable(Level.FINE)) log.fine("Request: '" + req + "'");
query = new PreparedQuery(pool, req, -1);
ArrayList ret = processResultSet(query.rs, storageId, -1, -1L, false, null);
for (int i=1; i < requests.size(); i++) {
req = (String)requests.get(i);
if (log.isLoggable(Level.FINE)) log.fine("Request: '" + req + "'");
query.inTransactionRequest(req /*, -1 */);
ret.addAll(processResultSet(query.rs, storageId, -1, -1L, false, null));
}
if (log.isLoggable(Level.FINE)) log.fine("Found " + ret.size() + " entries");
return ret;
}
catch (XmlBlasterException ex) {
success = false;
throw ex;
}
catch (Throwable ex) {
success = false;
if (query != null) query.closeStatement();
if (checkIfDBLoss(query != null ? query.conn : null, getLogId(queueName, "getEntries"), ex))
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME + ".getEntries", "", ex);
else throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNKNOWN, ME + ".getEntries", "", ex);
}
finally {
try {
if (query != null)
query.close(success);
}
catch (Throwable ex1) {
log.severe("exception when closing query: " + ex1.toString());
ex1.printStackTrace();
}
}
}
/**
* Gets the real number of entries.
* That is it really makes a call to the DB to find out
* how big the size is.
* @return never null
*/
public final EntryCount getNumOfAll(String queueName)
throws XmlBlasterException {
if (!this.isConnected) {
if (log.isLoggable(Level.FINE)) log.fine("Currently not possible. No connection to the DB");
return new EntryCount();
}
String req = "select durable, count(" + this.byteSizeColName + "), sum(" + this.byteSizeColName + ") from " + this.entriesTableName +
" WHERE queueName='" + queueName + "' group by durable";
if (log.isLoggable(Level.FINE)) log.fine("Request: '" + req + "'");
PreparedQuery query = null;
boolean success = true;
try {
query = new PreparedQuery(pool, req, true, -1);
boolean ok = query.rs.next();
EntryCount entryCount = new EntryCount();
if (ok) {
long transientOfEntries = 0;
long transientOfBytes = 0;
boolean persistent = isPersistent(query.rs.getString(1));
if (persistent) {
entryCount.numOfPersistentEntries = query.rs.getLong(2);
entryCount.numOfPersistentBytes = query.rs.getLong(3);
}
else {
transientOfEntries = query.rs.getLong(2);
transientOfBytes = query.rs.getLong(3);
}
if (query.rs.next()) {
persistent = isPersistent(query.rs.getString(1));
if (persistent) {
entryCount.numOfPersistentEntries = query.rs.getLong(2);
entryCount.numOfPersistentBytes = query.rs.getLong(3);
}
else {
transientOfEntries = query.rs.getLong(2);
transientOfBytes = query.rs.getLong(3);
}
}
entryCount.numOfEntries = transientOfEntries + entryCount.numOfPersistentEntries;
entryCount.numOfBytes = transientOfBytes + entryCount.numOfPersistentBytes;
}
if (log.isLoggable(Level.FINE)) log.fine("Num=" + entryCount.toString());
return entryCount;
}
catch (XmlBlasterException ex) {
success = false;
throw ex;
}
catch (Throwable ex) {
success = false;
if (query != null) query.closeStatement();
if (checkIfDBLoss(query != null ? query.conn : null, getLogId(queueName, "getNumOfAll"), ex))
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME + ".getNumOfAll", "", ex);
else throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNKNOWN, ME + ".getNumOfAll", "", ex);
}
finally {
try {
if (query != null)
query.close(success);
}
catch (Throwable ex1) {
log.severe("getNumOfAll exception when closing query: " + ex1.toString());
ex1.printStackTrace();
}
}
}
/**
* Gets the real number of entries.
* That is it really makes a call to the DB to find out
* how big the size is.
* @deprecated Use {@link #getNumOfAll(String)}
*/
public final long getNumOfEntries(String queueName)
throws XmlBlasterException {
if (!this.isConnected) {
if (log.isLoggable(Level.FINE)) log.fine("Currently not possible. No connection to the DB");
return 0L;
}
String req = "select count(*) from " + this.entriesTableName + " WHERE queueName='" + queueName + "'";
if (log.isLoggable(Level.FINE)) log.fine("Request: '" + req + "'");
PreparedQuery query = null;
boolean success = true;
try {
query = new PreparedQuery(pool, req, true, -1);
query.rs.next();
long ret = query.rs.getLong(1);
if (log.isLoggable(Level.FINE)) log.fine("Num=" + ret);
return ret;
}
catch (XmlBlasterException ex) {
success = false;
throw ex;
}
catch (Throwable ex) {
success = false;
if (query != null) query.closeStatement();
if (checkIfDBLoss(query != null ? query.conn : null, getLogId(queueName, "getNumOfEntries"), ex))
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME + ".getNumOfEntries", "", ex);
else throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNKNOWN, ME + ".getNumOfEntries", "", ex);
}
finally {
try {
if (query != null)
query.close(success);
}
catch (Throwable ex1) {
log.severe("exception when closing query: " + ex1.toString());
ex1.printStackTrace();
}
}
}
/**
* Gets the real number of persistent entries.
* That is it really makes a call to the DB to find out
* how big the size is.
* @deprecated Use {@link #getNumOfAll(String)}
*/
public final long getNumOfPersistents(String queueName)
throws XmlBlasterException {
if (!this.isConnected) {
if (log.isLoggable(Level.FINE)) log.fine("Currently not possible. No connection to the DB");
return 0L;
}
String req = "select count(*) from " + this.entriesTableName + " where queueName='" + queueName + "' AND durable='T'";
if (log.isLoggable(Level.FINE)) log.fine("Request: '" + req + "'");
PreparedQuery query = null;
boolean success = true;
try {
query = new PreparedQuery(pool, req, true, -1);
query.rs.next();
long ret = query.rs.getLong(1);
return ret;
}
catch (XmlBlasterException ex) {
success = false;
throw ex;
}
catch (Throwable ex) {
success = false;
if (query != null)
query.closeStatement();
if (checkIfDBLoss(query != null ? query.conn : null, getLogId(queueName, "getNumOfPersistents"), ex))
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME + ".getNumOfPersistents", "", ex);
else throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNKNOWN, ME + ".getNumOfPersistents", "", ex);
}
finally {
try {
if (query != null)
query.close(success);
}
catch (Throwable ex1) {
log.severe("exception when closing query: " + ex1.toString());
ex1.printStackTrace();
}
}
}
/**
* gets the real size of persistent entries, that is it really makes a call to the DB to find out
* how big the size is.
* <br />
* @deprecated Use {@link #getNumOfAll(String)}
* Replaced all four selects with one:
* "select count(*), sum(byteSize), durable from XB_ENTRIES WHERE queueName='history_xmlBlaster_192_168_1_4_3412Hello' GROUP BY durable;"
*/
public final long getSizeOfPersistents(String queueName)
throws XmlBlasterException {
if (log.isLoggable(Level.FINER)) log.finer("Entering");
if (!this.isConnected) {
if (log.isLoggable(Level.FINE)) log.fine("Currently not possible. No connection to the DB");
return 0L;
}
String req = "select sum(" + this.byteSizeColName + ") from " + this.entriesTableName + " where queueName='" + queueName + "' AND durable='T'";
if (log.isLoggable(Level.FINE)) log.fine("Request: '" + req + "'");
PreparedQuery query = null;
boolean success = true;
try {
query = new PreparedQuery(pool, req, true, -1);
query.rs.next();
long ret = query.rs.getLong(1);
return ret;
}
catch (XmlBlasterException ex) {
success = false;
throw ex;
}
catch (Throwable ex) {
success = false;
if (query != null) query.closeStatement();
if (checkIfDBLoss(query != null ? query.conn : null, getLogId(queueName, "getSizeOfPersistents"), ex))
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME + ".getSizeOfPersistents", "", ex);
else throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNKNOWN, ME + ".getSizeOfPersistents", "", ex);
}
finally {
try {
if (query != null)
query.close(success);
}
catch (Throwable ex1) {
log.severe("exception when closing query: " + ex1.toString());
ex1.printStackTrace();
}
}
}
/**
* @param queueName or null
* @return A nice location string for logging (instead of plain ME)
*/
private final String getLogId(String queueName, String methodName) {
StringBuffer sb = new StringBuffer(200);
sb.append(ME);
if (this.tableNamePrefix != null) {
sb.append("-").append(this.tableNamePrefix);
}
if (queueName != null) {
sb.append("-").append(queueName);
}
if (methodName != null) {
sb.append("-").append(methodName).append("()");
}
return sb.toString();
}
/**
* The Properties to use as a default are these from the QueuePlugin with the
* configuration name specified by defaultConfName (default is 'JDBC'). You can overwrite these
* properties entirely or partially with 'properties'.
* @param confType the name of the configuration to use as default. If you pass null, then
* 'JDBC' will be taken.
* @param confVersion the version to use as a default. If you pass null, then '1.0' will be taken.
* @param properties the properties to use to overwrite the default properties. If you pass null, no
* properties will be overwritten, and the default will be used.
*/
public static CommonTableDatabaseAccessor createInstance(Global glob, I_EntryFactory factory, String confType, String confVersion, Properties properties)
throws XmlBlasterException {
if (confType == null) confType = "JDBC";
if (confVersion == null) confVersion = "1.0";
QueuePluginManager pluginManager = new QueuePluginManager(glob);
PluginInfo pluginInfo = new PluginInfo(glob, pluginManager, confType, confVersion);
// clone the properties (to make sure they only belong to us) ...
java.util.Properties
ownProperties = (java.util.Properties)pluginInfo.getParameters().clone();
//overwrite our onw properties ...
if (properties != null) {
java.util.Enumeration enumer = properties.keys();
while (enumer.hasMoreElements()) {
String key =(String)enumer.nextElement();
ownProperties.put(key, properties.getProperty(key));
}
}
JdbcConnectionPool pool = new JdbcConnectionPool();
try {
pool.initialize(glob, pluginInfo.getParameters());
}
catch (ClassNotFoundException ex) {
log.severe("wipOutDB class not found: " + ex.getMessage());
throw new XmlBlasterException(glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME,
"JdbcConnectionPool class not found", ex);
}
catch (SQLException ex) {
throw new XmlBlasterException(glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME, "JdbcConnectionPool SQL exception",
ex);
}
// determine which jdbc manager class to use
String queueClassName = pluginInfo.getClassName();
if ("org.xmlBlaster.util.queue.jdbc.JdbcQueuePlugin".equals(queueClassName)) {
throw new XmlBlasterException(glob, ErrorCode.INTERNAL_NOTIMPLEMENTED, ME, "org.xmlBlaster.util.queue.jdbc.JdbcQueuePlugin is not supported anymore");
}
else if ("org.xmlBlaster.util.queue.jdbc.JdbcQueueCommonTablePlugin".equals(queueClassName)) {
// then it is a JdbcManagerCommontTable
// then it is a JdbcManager
// AWARE: Used in Main.java as well
boolean useJdbcManagerDelegate = glob.get("xmlBlaster/useJdbcManagerDelegate", true, properties, pluginInfo); // pluginConfig
CommonTableDatabaseAccessor manager = (useJdbcManagerDelegate) ?
new CommonTableDatabaseAccessorDelegate(pool, factory, "cleaner", null) :
new CommonTableDatabaseAccessor(pool, factory, "cleaner", null);
pool.registerStorageProblemListener(manager);
manager.setUp();
return manager;
}
else {
throw new XmlBlasterException(glob, ErrorCode.INTERNAL_NOTIMPLEMENTED, ME, "Plugin '" + queueClassName
+ "' is not implemented");
}
}
/**
* wipes out the db. The Properties to use as a default are these from the QueuePlugin with the
* configuration name specified by defaultConfName (default is 'JDBC'). You can overwrite these
* properties entirely or partially with 'properties'.
* @param confType the name of the configuration to use as default. If you pass null, then
* 'JDBC' will be taken.
* @param confVersion the version to use as a default. If you pass null, then '1.0' will be taken.
* @param properties the properties to use to overwrite the default properties. If you pass null, no
* properties will be overwritten, and the default will be used.
* @param setupNewTables tells the manager to recreate empty tables if set to 'true'. Note that this flag only
* has effect if the JdbcManagerCommonTable is used.
*/
public static void wipeOutDB(Global glob, String confType, String confVersion, java.util.Properties properties, boolean setupNewTables)
throws XmlBlasterException {
CommonTableDatabaseAccessor manager = createInstance(glob, glob.getEntryFactory(), confType, confVersion,
properties);
manager.wipeOutDB(setupNewTables);
}
/**
* This main method can be used to delete all tables on the db which start
* with a certain prefix. It is useful to cleanup the entire DB.
*
* </pre>
*/
public static void main(String[] args) {
Global glob = Global.instance();
glob.init(args);
String type = glob.getProperty().get("wipeout.pluginType", (String)null);
String version = glob.getProperty().get("wipeout.pluginVersion", (String)null);
if ((type == null) || (version == null)) {
System.out.println("usage: java org.xmlBlaster.util.queue.jdbc.JdbcManagerCommonTable -wipeout.pluginType JDBC -wipeout.pluginVersion 1.0");
System.exit(1);
}
try {
CommonTableDatabaseAccessor.wipeOutDB(glob, type, version, null, false);
}
catch (Exception ex) {
System.err.println("Main" + ex.toString());
}
}
synchronized public void shutdown() {
// if (this.pool != null) this.pool.shutdown();
if (log.isLoggable(Level.FINER)) log.finer("shutdown");
if (this.pool != null) {
this.pool.unregisterManager(this);
}
}
synchronized public void registerQueue(I_Queue queue) {
if (log.isLoggable(Level.FINER))
log.finer("registerQueue, number of queues registered (before registering this one): '" + this.queueCounter + "'");
if (queue == null) return;
this.queueCounter++;
}
synchronized public void unregisterQueue(I_Queue queue) {
if (log.isLoggable(Level.FINER))
log.finer("unregisterQueue, number of queues registered (still including this one): '" + this.queueCounter + "'");
if (queue == null) return;
this.queueCounter--;
if (this.queueCounter == 0) {
shutdown();
}
}
/**
* @return the isDb2
*/
public boolean isDB2() {
return this.isDB2;
}
/**
* @return the isHsqlDb
*/
public boolean isHSQLDatabaseEngine() {
return this.isHSQLDatabaseEngine;
}
/**
* @return the isMsSqlServer
*/
public boolean isMicrosoftSQLServer() {
return this.isMicrosoftSQLServer;
}
/**
* @return the isMySql
*/
public boolean isMySql() {
return this.isMySql;
}
/**
* @return the isOracle
*/
public boolean isOracle() {
return this.isOracle;
}
/**
* @return the isPostgres
*/
public boolean isPostgres() {
return this.isPostgres;
}
/**
* @return the firebird
*/
public boolean isFirebird() {
return this.isFirebird;
}
/**
* @return the isLdbc
*/
public boolean isLdbc() {
return this.isLdbc;
}
/**
* @return the isSQLite
*/
public boolean isSQLite() {
return this.isSQLite;
}
}