/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.qpid.server.store.derby;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.configuration.Configuration;
import org.apache.log4j.Logger;
import org.apache.qpid.AMQException;
import org.apache.qpid.AMQStoreException;
import org.apache.qpid.framing.AMQShortString;
import org.apache.qpid.framing.FieldTable;
import org.apache.qpid.server.binding.Binding;
import org.apache.qpid.server.exchange.Exchange;
import org.apache.qpid.server.message.EnqueableMessage;
import org.apache.qpid.server.queue.AMQQueue;
import org.apache.qpid.server.store.ConfigurationRecoveryHandler;
import org.apache.qpid.server.store.ConfiguredObjectHelper;
import org.apache.qpid.server.store.ConfiguredObjectRecord;
import org.apache.qpid.server.store.Event;
import org.apache.qpid.server.store.EventListener;
import org.apache.qpid.server.store.EventManager;
import org.apache.qpid.server.store.MessageMetaDataType;
import org.apache.qpid.server.store.MessageStore;
import org.apache.qpid.server.store.MessageStoreConstants;
import org.apache.qpid.server.store.MessageStoreRecoveryHandler;
import org.apache.qpid.server.store.State;
import org.apache.qpid.server.store.StateManager;
import org.apache.qpid.server.store.StorableMessageMetaData;
import org.apache.qpid.server.store.StoreFuture;
import org.apache.qpid.server.store.StoredMemoryMessage;
import org.apache.qpid.server.store.StoredMessage;
import org.apache.qpid.server.store.Transaction;
import org.apache.qpid.server.store.TransactionLogRecoveryHandler;
import org.apache.qpid.server.store.TransactionLogResource;
import org.apache.qpid.server.store.ConfigurationRecoveryHandler.BindingRecoveryHandler;
import org.apache.qpid.server.store.ConfigurationRecoveryHandler.ExchangeRecoveryHandler;
import org.apache.qpid.server.store.ConfigurationRecoveryHandler.QueueRecoveryHandler;
/**
* An implementation of a {@link MessageStore} that uses Apache Derby as the persistence
* mechanism.
*
* TODO extract the SQL statements into a generic JDBC store
*/
public class DerbyMessageStore implements MessageStore
{
private static final Logger _logger = Logger.getLogger(DerbyMessageStore.class);
private static final String SQL_DRIVER_NAME = "org.apache.derby.jdbc.EmbeddedDriver";
private static final String DB_VERSION_TABLE_NAME = "QPID_DB_VERSION";
private static final String QUEUE_ENTRY_TABLE_NAME = "QPID_QUEUE_ENTRIES";
private static final String META_DATA_TABLE_NAME = "QPID_MESSAGE_METADATA";
private static final String MESSAGE_CONTENT_TABLE_NAME = "QPID_MESSAGE_CONTENT";
private static final String LINKS_TABLE_NAME = "QPID_LINKS";
private static final String BRIDGES_TABLE_NAME = "QPID_BRIDGES";
private static final String XID_TABLE_NAME = "QPID_XIDS";
private static final String XID_ACTIONS_TABLE_NAME = "QPID_XID_ACTIONS";
private static final String CONFIGURED_OBJECTS_TABLE_NAME = "QPID_CONFIGURED_OBJECTS";
private static final int DB_VERSION = 6;
private static Class<Driver> DRIVER_CLASS;
public static final String MEMORY_STORE_LOCATION = ":memory:";
private final AtomicLong _messageId = new AtomicLong(0);
private AtomicBoolean _closed = new AtomicBoolean(false);
private String _connectionURL;
private static final String TABLE_EXISTANCE_QUERY = "SELECT 1 FROM SYS.SYSTABLES WHERE TABLENAME = ?";
private static final String CREATE_DB_VERSION_TABLE = "CREATE TABLE "+DB_VERSION_TABLE_NAME+" ( version int not null )";
private static final String INSERT_INTO_DB_VERSION = "INSERT INTO "+DB_VERSION_TABLE_NAME+" ( version ) VALUES ( ? )";
private static final String CREATE_QUEUE_ENTRY_TABLE = "CREATE TABLE "+QUEUE_ENTRY_TABLE_NAME+" ( queue_id varchar(36) not null, message_id bigint not null, PRIMARY KEY (queue_id, message_id) )";
private static final String INSERT_INTO_QUEUE_ENTRY = "INSERT INTO " + QUEUE_ENTRY_TABLE_NAME + " (queue_id, message_id) values (?,?)";
private static final String DELETE_FROM_QUEUE_ENTRY = "DELETE FROM " + QUEUE_ENTRY_TABLE_NAME + " WHERE queue_id = ? AND message_id =?";
private static final String SELECT_FROM_QUEUE_ENTRY = "SELECT queue_id, message_id FROM " + QUEUE_ENTRY_TABLE_NAME + " ORDER BY queue_id, message_id";
private static final String CREATE_META_DATA_TABLE = "CREATE TABLE " + META_DATA_TABLE_NAME
+ " ( message_id bigint not null, meta_data blob, PRIMARY KEY ( message_id ) )";
private static final String CREATE_MESSAGE_CONTENT_TABLE = "CREATE TABLE " + MESSAGE_CONTENT_TABLE_NAME
+ " ( message_id bigint not null, content blob , PRIMARY KEY (message_id) )";
private static final String INSERT_INTO_MESSAGE_CONTENT = "INSERT INTO " + MESSAGE_CONTENT_TABLE_NAME
+ "( message_id, content ) values (?, ?)";
private static final String SELECT_FROM_MESSAGE_CONTENT = "SELECT content FROM " + MESSAGE_CONTENT_TABLE_NAME
+ " WHERE message_id = ?";
private static final String DELETE_FROM_MESSAGE_CONTENT = "DELETE FROM " + MESSAGE_CONTENT_TABLE_NAME
+ " WHERE message_id = ?";
private static final String INSERT_INTO_META_DATA = "INSERT INTO " + META_DATA_TABLE_NAME + "( message_id , meta_data ) values (?, ?)";;
private static final String SELECT_FROM_META_DATA =
"SELECT meta_data FROM " + META_DATA_TABLE_NAME + " WHERE message_id = ?";
private static final String DELETE_FROM_META_DATA = "DELETE FROM " + META_DATA_TABLE_NAME + " WHERE message_id = ?";
private static final String SELECT_ALL_FROM_META_DATA = "SELECT message_id, meta_data FROM " + META_DATA_TABLE_NAME;
private static final String CREATE_LINKS_TABLE =
"CREATE TABLE "+LINKS_TABLE_NAME+" ( id_lsb bigint not null,"
+ " id_msb bigint not null,"
+ " create_time bigint not null,"
+ " arguments blob, PRIMARY KEY ( id_lsb, id_msb ))";
private static final String SELECT_FROM_LINKS =
"SELECT create_time, arguments FROM " + LINKS_TABLE_NAME + " WHERE id_lsb = ? and id_msb";
private static final String DELETE_FROM_LINKS = "DELETE FROM " + LINKS_TABLE_NAME
+ " WHERE id_lsb = ? and id_msb = ?";
private static final String SELECT_ALL_FROM_LINKS = "SELECT id_lsb, id_msb, create_time, "
+ "arguments FROM " + LINKS_TABLE_NAME;
private static final String FIND_LINK = "SELECT id_lsb, id_msb FROM " + LINKS_TABLE_NAME + " WHERE id_lsb = ? and"
+ " id_msb = ?";
private static final String INSERT_INTO_LINKS = "INSERT INTO " + LINKS_TABLE_NAME + "( id_lsb, "
+ "id_msb, create_time, arguments ) values (?, ?, ?, ?)";
private static final String CREATE_BRIDGES_TABLE =
"CREATE TABLE "+BRIDGES_TABLE_NAME+" ( id_lsb bigint not null,"
+ " id_msb bigint not null,"
+ " create_time bigint not null,"
+ " link_id_lsb bigint not null,"
+ " link_id_msb bigint not null,"
+ " arguments blob, PRIMARY KEY ( id_lsb, id_msb ))";
private static final String SELECT_FROM_BRIDGES =
"SELECT create_time, link_id_lsb, link_id_msb, arguments FROM "
+ BRIDGES_TABLE_NAME + " WHERE id_lsb = ? and id_msb = ?";
private static final String DELETE_FROM_BRIDGES = "DELETE FROM " + BRIDGES_TABLE_NAME
+ " WHERE id_lsb = ? and id_msb = ?";
private static final String SELECT_ALL_FROM_BRIDGES = "SELECT id_lsb, id_msb, "
+ " create_time,"
+ " link_id_lsb, link_id_msb, "
+ "arguments FROM " + BRIDGES_TABLE_NAME
+ " WHERE link_id_lsb = ? and link_id_msb = ?";
private static final String FIND_BRIDGE = "SELECT id_lsb, id_msb FROM " + BRIDGES_TABLE_NAME +
" WHERE id_lsb = ? and id_msb = ?";
private static final String INSERT_INTO_BRIDGES = "INSERT INTO " + BRIDGES_TABLE_NAME + "( id_lsb, id_msb, "
+ "create_time, "
+ "link_id_lsb, link_id_msb, "
+ "arguments )"
+ " values (?, ?, ?, ?, ?, ?)";
private static final String CREATE_XIDS_TABLE =
"CREATE TABLE "+XID_TABLE_NAME+" ( format bigint not null,"
+ " global_id varchar(64) for bit data, branch_id varchar(64) for bit data, PRIMARY KEY ( format, " +
"global_id, branch_id ))";
private static final String INSERT_INTO_XIDS =
"INSERT INTO "+XID_TABLE_NAME+" ( format, global_id, branch_id ) values (?, ?, ?)";
private static final String DELETE_FROM_XIDS = "DELETE FROM " + XID_TABLE_NAME
+ " WHERE format = ? and global_id = ? and branch_id = ?";
private static final String SELECT_ALL_FROM_XIDS = "SELECT format, global_id, branch_id FROM " + XID_TABLE_NAME;
private static final String CREATE_XID_ACTIONS_TABLE =
"CREATE TABLE "+XID_ACTIONS_TABLE_NAME+" ( format bigint not null,"
+ " global_id varchar(64) for bit data not null, branch_id varchar(64) for bit data not null, " +
"action_type char not null, queue_id varchar(36) not null, message_id bigint not null" +
", PRIMARY KEY ( " +
"format, global_id, branch_id, action_type, queue_id, message_id))";
private static final String INSERT_INTO_XID_ACTIONS =
"INSERT INTO "+XID_ACTIONS_TABLE_NAME+" ( format, global_id, branch_id, action_type, " +
"queue_id, message_id ) values (?,?,?,?,?,?) ";
private static final String DELETE_FROM_XID_ACTIONS = "DELETE FROM " + XID_ACTIONS_TABLE_NAME
+ " WHERE format = ? and global_id = ? and branch_id = ?";
private static final String SELECT_ALL_FROM_XID_ACTIONS =
"SELECT action_type, queue_id, message_id FROM " + XID_ACTIONS_TABLE_NAME +
" WHERE format = ? and global_id = ? and branch_id = ?";
private static final String CREATE_CONFIGURED_OBJECTS_TABLE = "CREATE TABLE " + CONFIGURED_OBJECTS_TABLE_NAME
+ " ( id VARCHAR(36) not null, object_type varchar(255), attributes blob, PRIMARY KEY (id))";
private static final String INSERT_INTO_CONFIGURED_OBJECTS = "INSERT INTO " + CONFIGURED_OBJECTS_TABLE_NAME
+ " ( id, object_type, attributes) VALUES (?,?,?)";
private static final String UPDATE_CONFIGURED_OBJECTS = "UPDATE " + CONFIGURED_OBJECTS_TABLE_NAME
+ " set object_type =?, attributes = ? where id = ?";
private static final String DELETE_FROM_CONFIGURED_OBJECTS = "DELETE FROM " + CONFIGURED_OBJECTS_TABLE_NAME
+ " where id = ?";
private static final String FIND_CONFIGURED_OBJECT = "SELECT object_type, attributes FROM " + CONFIGURED_OBJECTS_TABLE_NAME
+ " where id = ?";
private static final String SELECT_FROM_CONFIGURED_OBJECTS = "SELECT id, object_type, attributes FROM " + CONFIGURED_OBJECTS_TABLE_NAME;
private final Charset UTF8_CHARSET = Charset.forName("UTF-8");
private static final String DERBY_SINGLE_DB_SHUTDOWN_CODE = "08006";
private static final String DERBY_STORE_TYPE = "DERBY";
private final StateManager _stateManager;
private final EventManager _eventManager = new EventManager();
private long _totalStoreSize;
private boolean _limitBusted;
private long _persistentSizeLowThreshold;
private long _persistentSizeHighThreshold;
private MessageStoreRecoveryHandler _messageRecoveryHandler;
private TransactionLogRecoveryHandler _tlogRecoveryHandler;
private ConfigurationRecoveryHandler _configRecoveryHandler;
private String _storeLocation;
public DerbyMessageStore()
{
_stateManager = new StateManager(_eventManager);
}
private ConfiguredObjectHelper _configuredObjectHelper = new ConfiguredObjectHelper();
@Override
public void configureConfigStore(String name,
ConfigurationRecoveryHandler configRecoveryHandler,
Configuration storeConfiguration) throws Exception
{
_stateManager.attainState(State.INITIALISING);
_configRecoveryHandler = configRecoveryHandler;
commonConfiguration(name, storeConfiguration);
}
@Override
public void configureMessageStore(String name,
MessageStoreRecoveryHandler recoveryHandler,
TransactionLogRecoveryHandler tlogRecoveryHandler,
Configuration storeConfiguration) throws Exception
{
_tlogRecoveryHandler = tlogRecoveryHandler;
_messageRecoveryHandler = recoveryHandler;
_stateManager.attainState(State.INITIALISED);
}
@Override
public void activate() throws Exception
{
_stateManager.attainState(State.ACTIVATING);
// this recovers durable exchanges, queues, and bindings
recoverConfiguration(_configRecoveryHandler);
recoverMessages(_messageRecoveryHandler);
TransactionLogRecoveryHandler.DtxRecordRecoveryHandler dtxrh = recoverQueueEntries(_tlogRecoveryHandler);
recoverXids(dtxrh);
_stateManager.attainState(State.ACTIVE);
}
private void commonConfiguration(String name, Configuration storeConfiguration)
throws ClassNotFoundException, SQLException
{
initialiseDriver();
//Update to pick up QPID_WORK and use that as the default location not just derbyDB
final String databasePath = storeConfiguration.getString(MessageStoreConstants.ENVIRONMENT_PATH_PROPERTY, System.getProperty("QPID_WORK")
+ File.separator + "derbyDB");
if(!MEMORY_STORE_LOCATION.equals(databasePath))
{
File environmentPath = new File(databasePath);
if (!environmentPath.exists())
{
if (!environmentPath.mkdirs())
{
throw new IllegalArgumentException("Environment path " + environmentPath + " could not be read or created. "
+ "Ensure the path is correct and that the permissions are correct.");
}
}
}
_storeLocation = databasePath;
_persistentSizeHighThreshold = storeConfiguration.getLong(MessageStoreConstants.OVERFULL_SIZE_PROPERTY, -1l);
_persistentSizeLowThreshold = storeConfiguration.getLong(MessageStoreConstants.UNDERFULL_SIZE_PROPERTY, _persistentSizeHighThreshold);
if(_persistentSizeLowThreshold > _persistentSizeHighThreshold || _persistentSizeLowThreshold < 0l)
{
_persistentSizeLowThreshold = _persistentSizeHighThreshold;
}
createOrOpenDatabase(name, databasePath);
Connection conn = newAutoCommitConnection();;
try
{
_totalStoreSize = getSizeOnDisk(conn);
}
finally
{
conn.close();
}
}
private static synchronized void initialiseDriver() throws ClassNotFoundException
{
if(DRIVER_CLASS == null)
{
DRIVER_CLASS = (Class<Driver>) Class.forName(SQL_DRIVER_NAME);
}
}
private void createOrOpenDatabase(String name, final String environmentPath) throws SQLException
{
//FIXME this the _vhost name should not be added here, but derby wont use an empty directory as was possibly just created.
_connectionURL = "jdbc:derby" + (environmentPath.equals(MEMORY_STORE_LOCATION) ? environmentPath : ":" + environmentPath + "/") + name + ";create=true";
Connection conn = newAutoCommitConnection();
createVersionTable(conn);
createConfiguredObjectsTable(conn);
createQueueEntryTable(conn);
createMetaDataTable(conn);
createMessageContentTable(conn);
createLinkTable(conn);
createBridgeTable(conn);
createXidTable(conn);
createXidActionTable(conn);
conn.close();
}
private void createVersionTable(final Connection conn) throws SQLException
{
if(!tableExists(DB_VERSION_TABLE_NAME, conn))
{
Statement stmt = conn.createStatement();
try
{
stmt.execute(CREATE_DB_VERSION_TABLE);
}
finally
{
stmt.close();
}
PreparedStatement pstmt = conn.prepareStatement(INSERT_INTO_DB_VERSION);
try
{
pstmt.setInt(1, DB_VERSION);
pstmt.execute();
}
finally
{
pstmt.close();
}
}
}
private void createConfiguredObjectsTable(final Connection conn) throws SQLException
{
if(!tableExists(CONFIGURED_OBJECTS_TABLE_NAME, conn))
{
Statement stmt = conn.createStatement();
try
{
stmt.execute(CREATE_CONFIGURED_OBJECTS_TABLE);
}
finally
{
stmt.close();
}
}
}
private void createQueueEntryTable(final Connection conn) throws SQLException
{
if(!tableExists(QUEUE_ENTRY_TABLE_NAME, conn))
{
Statement stmt = conn.createStatement();
try
{
stmt.execute(CREATE_QUEUE_ENTRY_TABLE);
}
finally
{
stmt.close();
}
}
}
private void createMetaDataTable(final Connection conn) throws SQLException
{
if(!tableExists(META_DATA_TABLE_NAME, conn))
{
Statement stmt = conn.createStatement();
try
{
stmt.execute(CREATE_META_DATA_TABLE);
}
finally
{
stmt.close();
}
}
}
private void createMessageContentTable(final Connection conn) throws SQLException
{
if(!tableExists(MESSAGE_CONTENT_TABLE_NAME, conn))
{
Statement stmt = conn.createStatement();
try
{
stmt.execute(CREATE_MESSAGE_CONTENT_TABLE);
}
finally
{
stmt.close();
}
}
}
private void createLinkTable(final Connection conn) throws SQLException
{
if(!tableExists(LINKS_TABLE_NAME, conn))
{
Statement stmt = conn.createStatement();
try
{
stmt.execute(CREATE_LINKS_TABLE);
}
finally
{
stmt.close();
}
}
}
private void createBridgeTable(final Connection conn) throws SQLException
{
if(!tableExists(BRIDGES_TABLE_NAME, conn))
{
Statement stmt = conn.createStatement();
try
{
stmt.execute(CREATE_BRIDGES_TABLE);
}
finally
{
stmt.close();
}
}
}
private void createXidTable(final Connection conn) throws SQLException
{
if(!tableExists(XID_TABLE_NAME, conn))
{
Statement stmt = conn.createStatement();
try
{
stmt.execute(CREATE_XIDS_TABLE);
}
finally
{
stmt.close();
}
}
}
private void createXidActionTable(final Connection conn) throws SQLException
{
if(!tableExists(XID_ACTIONS_TABLE_NAME, conn))
{
Statement stmt = conn.createStatement();
try
{
stmt.execute(CREATE_XID_ACTIONS_TABLE);
}
finally
{
stmt.close();
}
}
}
private boolean tableExists(final String tableName, final Connection conn) throws SQLException
{
PreparedStatement stmt = conn.prepareStatement(TABLE_EXISTANCE_QUERY);
try
{
stmt.setString(1, tableName);
ResultSet rs = stmt.executeQuery();
try
{
return rs.next();
}
finally
{
rs.close();
}
}
finally
{
stmt.close();
}
}
private void recoverConfiguration(ConfigurationRecoveryHandler recoveryHandler) throws AMQException
{
try
{
List<ConfiguredObjectRecord> configuredObjects = loadConfiguredObjects();
ExchangeRecoveryHandler erh = recoveryHandler.begin(this);
_configuredObjectHelper.recoverExchanges(erh, configuredObjects);
QueueRecoveryHandler qrh = erh.completeExchangeRecovery();
_configuredObjectHelper.recoverQueues(qrh, configuredObjects);
BindingRecoveryHandler brh = qrh.completeQueueRecovery();
_configuredObjectHelper.recoverBindings(brh, configuredObjects);
brh.completeBindingRecovery();
}
catch (SQLException e)
{
throw new AMQStoreException("Error recovering persistent state: " + e.getMessage(), e);
}
}
@Override
public void close() throws Exception
{
_closed.getAndSet(true);
_stateManager.attainState(State.CLOSING);
try
{
Connection conn = DriverManager.getConnection(_connectionURL + ";shutdown=true");
// Shouldn't reach this point - shutdown=true should throw SQLException
conn.close();
_logger.error("Unable to shut down the store");
}
catch (SQLException e)
{
if (e.getSQLState().equalsIgnoreCase(DERBY_SINGLE_DB_SHUTDOWN_CODE))
{
//expected and represents a clean shutdown of this database only, do nothing.
}
else
{
_logger.error("Exception whilst shutting down the store: " + e);
}
}
_stateManager.attainState(State.CLOSED);
}
@Override
public StoredMessage addMessage(StorableMessageMetaData metaData)
{
if(metaData.isPersistent())
{
return new StoredDerbyMessage(_messageId.incrementAndGet(), metaData);
}
else
{
return new StoredMemoryMessage(_messageId.incrementAndGet(), metaData);
}
}
public StoredMessage getMessage(long messageNumber)
{
return null;
}
public void removeMessage(long messageId)
{
try
{
Connection conn = newConnection();
try
{
PreparedStatement stmt = conn.prepareStatement(DELETE_FROM_META_DATA);
try
{
stmt.setLong(1,messageId);
int results = stmt.executeUpdate();
stmt.close();
if (results == 0)
{
_logger.warn("Message metadata not found for message id " + messageId);
}
if (_logger.isDebugEnabled())
{
_logger.debug("Deleted metadata for message " + messageId);
}
stmt = conn.prepareStatement(DELETE_FROM_MESSAGE_CONTENT);
stmt.setLong(1,messageId);
results = stmt.executeUpdate();
}
finally
{
stmt.close();
}
conn.commit();
}
catch(SQLException e)
{
try
{
conn.rollback();
}
catch(SQLException t)
{
// ignore - we are re-throwing underlying exception
}
throw e;
}
finally
{
conn.close();
}
}
catch (SQLException e)
{
throw new RuntimeException("Error removing message with id " + messageId + " from database: " + e.getMessage(), e);
}
}
@Override
public void createExchange(Exchange exchange) throws AMQStoreException
{
if (_stateManager.isInState(State.ACTIVE))
{
ConfiguredObjectRecord configuredObject = _configuredObjectHelper.createExchangeConfiguredObject(exchange);
insertConfiguredObject(configuredObject);
}
}
@Override
public void removeExchange(Exchange exchange) throws AMQStoreException
{
int results = removeConfiguredObject(exchange.getId());
if (results == 0)
{
throw new AMQStoreException("Exchange " + exchange.getName() + " with id " + exchange.getId() + " not found");
}
}
@Override
public void bindQueue(Binding binding)
throws AMQStoreException
{
if (_stateManager.isInState(State.ACTIVE))
{
ConfiguredObjectRecord configuredObject = _configuredObjectHelper.createBindingConfiguredObject(binding);
insertConfiguredObject(configuredObject);
}
}
@Override
public void unbindQueue(Binding binding)
throws AMQStoreException
{
int results = removeConfiguredObject(binding.getId());
if (results == 0)
{
throw new AMQStoreException("Binding " + binding + " not found");
}
}
@Override
public void createQueue(AMQQueue queue) throws AMQStoreException
{
createQueue(queue, null);
}
@Override
public void createQueue(AMQQueue queue, FieldTable arguments) throws AMQStoreException
{
_logger.debug("public void createQueue(AMQQueue queue = " + queue + "): called");
if (_stateManager.isInState(State.ACTIVE))
{
ConfiguredObjectRecord queueConfiguredObject = _configuredObjectHelper.createQueueConfiguredObject(queue, arguments);
insertConfiguredObject(queueConfiguredObject);
}
}
/**
* Updates the specified queue in the persistent store, IF it is already present. If the queue
* is not present in the store, it will not be added.
*
* NOTE: Currently only updates the exclusivity.
*
* @param queue The queue to update the entry for.
* @throws AMQStoreException If the operation fails for any reason.
*/
@Override
public void updateQueue(final AMQQueue queue) throws AMQStoreException
{
if (_stateManager.isInState(State.ACTIVE))
{
ConfiguredObjectRecord queueConfiguredObject = loadConfiguredObject(queue.getId());
if (queueConfiguredObject != null)
{
ConfiguredObjectRecord newQueueRecord = _configuredObjectHelper.updateQueueConfiguredObject(queue, queueConfiguredObject);
updateConfiguredObject(newQueueRecord);
}
}
}
/**
* Convenience method to create a new Connection configured for TRANSACTION_READ_COMMITED
* isolation and with auto-commit transactions enabled.
*/
private Connection newAutoCommitConnection() throws SQLException
{
final Connection connection = newConnection();
try
{
connection.setAutoCommit(true);
}
catch (SQLException sqlEx)
{
try
{
connection.close();
}
finally
{
throw sqlEx;
}
}
return connection;
}
/**
* Convenience method to create a new Connection configured for TRANSACTION_READ_COMMITED
* isolation and with auto-commit transactions disabled.
*/
private Connection newConnection() throws SQLException
{
final Connection connection = DriverManager.getConnection(_connectionURL);
try
{
connection.setAutoCommit(false);
connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
}
catch (SQLException sqlEx)
{
try
{
connection.close();
}
finally
{
throw sqlEx;
}
}
return connection;
}
@Override
public void removeQueue(final AMQQueue queue) throws AMQStoreException
{
AMQShortString name = queue.getNameShortString();
_logger.debug("public void removeQueue(AMQShortString name = " + name + "): called");
int results = removeConfiguredObject(queue.getId());
if (results == 0)
{
throw new AMQStoreException("Queue " + name + " with id " + queue.getId() + " not found");
}
}
private byte[] convertStringMapToBytes(final Map<String, String> arguments) throws AMQStoreException
{
byte[] argumentBytes;
if(arguments == null)
{
argumentBytes = new byte[0];
}
else
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
try
{
dos.writeInt(arguments.size());
for(Map.Entry<String,String> arg : arguments.entrySet())
{
dos.writeUTF(arg.getKey());
dos.writeUTF(arg.getValue());
}
}
catch (IOException e)
{
// This should never happen
throw new AMQStoreException(e.getMessage(), e);
}
argumentBytes = bos.toByteArray();
}
return argumentBytes;
}
@Override
public Transaction newTransaction()
{
return new DerbyTransaction();
}
public void enqueueMessage(ConnectionWrapper connWrapper, final TransactionLogResource queue, Long messageId) throws AMQStoreException
{
Connection conn = connWrapper.getConnection();
try
{
if (_logger.isDebugEnabled())
{
_logger.debug("Enqueuing message " + messageId + " on queue " + (queue instanceof AMQQueue ? ((AMQQueue)queue).getName() : "" ) + queue.getId()+ "[Connection" + conn + "]");
}
PreparedStatement stmt = conn.prepareStatement(INSERT_INTO_QUEUE_ENTRY);
try
{
stmt.setString(1, queue.getId().toString());
stmt.setLong(2,messageId);
stmt.executeUpdate();
}
finally
{
stmt.close();
}
}
catch (SQLException e)
{
_logger.error("Failed to enqueue: " + e.getMessage(), e);
throw new AMQStoreException("Error writing enqueued message with id " + messageId + " for queue " + (queue instanceof AMQQueue ? ((AMQQueue)queue).getName() : "" ) + " with id " + queue.getId()
+ " to database", e);
}
}
public void dequeueMessage(ConnectionWrapper connWrapper, final TransactionLogResource queue, Long messageId) throws AMQStoreException
{
Connection conn = connWrapper.getConnection();
try
{
PreparedStatement stmt = conn.prepareStatement(DELETE_FROM_QUEUE_ENTRY);
try
{
stmt.setString(1, queue.getId().toString());
stmt.setLong(2,messageId);
int results = stmt.executeUpdate();
if(results != 1)
{
throw new AMQStoreException("Unable to find message with id " + messageId + " on queue " + (queue instanceof AMQQueue ? ((AMQQueue)queue).getName() : "" )
+ " with id " + queue.getId());
}
if (_logger.isDebugEnabled())
{
_logger.debug("Dequeuing message " + messageId + " on queue " + (queue instanceof AMQQueue ? ((AMQQueue)queue).getName() : "" )
+ " with id " + queue.getId());
}
}
finally
{
stmt.close();
}
}
catch (SQLException e)
{
_logger.error("Failed to dequeue: " + e.getMessage(), e);
throw new AMQStoreException("Error deleting enqueued message with id " + messageId + " for queue " + (queue instanceof AMQQueue ? ((AMQQueue)queue).getName() : "" )
+ " with id " + queue.getId() + " from database", e);
}
}
private void removeXid(ConnectionWrapper connWrapper, long format, byte[] globalId, byte[] branchId)
throws AMQStoreException
{
Connection conn = connWrapper.getConnection();
try
{
PreparedStatement stmt = conn.prepareStatement(DELETE_FROM_XIDS);
try
{
stmt.setLong(1,format);
stmt.setBytes(2,globalId);
stmt.setBytes(3,branchId);
int results = stmt.executeUpdate();
if(results != 1)
{
throw new AMQStoreException("Unable to find message with xid");
}
}
finally
{
stmt.close();
}
stmt = conn.prepareStatement(DELETE_FROM_XID_ACTIONS);
try
{
stmt.setLong(1,format);
stmt.setBytes(2,globalId);
stmt.setBytes(3,branchId);
int results = stmt.executeUpdate();
}
finally
{
stmt.close();
}
}
catch (SQLException e)
{
_logger.error("Failed to dequeue: " + e.getMessage(), e);
throw new AMQStoreException("Error deleting enqueued message with xid", e);
}
}
private void recordXid(ConnectionWrapper connWrapper, long format, byte[] globalId, byte[] branchId,
Transaction.Record[] enqueues, Transaction.Record[] dequeues) throws AMQStoreException
{
Connection conn = connWrapper.getConnection();
try
{
PreparedStatement stmt = conn.prepareStatement(INSERT_INTO_XIDS);
try
{
stmt.setLong(1,format);
stmt.setBytes(2, globalId);
stmt.setBytes(3, branchId);
stmt.executeUpdate();
}
finally
{
stmt.close();
}
stmt = conn.prepareStatement(INSERT_INTO_XID_ACTIONS);
try
{
stmt.setLong(1,format);
stmt.setBytes(2, globalId);
stmt.setBytes(3, branchId);
if(enqueues != null)
{
stmt.setString(4, "E");
for(Transaction.Record record : enqueues)
{
stmt.setString(5, record.getQueue().getId().toString());
stmt.setLong(6, record.getMessage().getMessageNumber());
stmt.executeUpdate();
}
}
if(dequeues != null)
{
stmt.setString(4, "D");
for(Transaction.Record record : dequeues)
{
stmt.setString(5, record.getQueue().getId().toString());
stmt.setLong(6, record.getMessage().getMessageNumber());
stmt.executeUpdate();
}
}
}
finally
{
stmt.close();
}
}
catch (SQLException e)
{
_logger.error("Failed to enqueue: " + e.getMessage(), e);
throw new AMQStoreException("Error writing xid ", e);
}
}
private static final class ConnectionWrapper
{
private final Connection _connection;
public ConnectionWrapper(Connection conn)
{
_connection = conn;
}
public Connection getConnection()
{
return _connection;
}
}
public void commitTran(ConnectionWrapper connWrapper) throws AMQStoreException
{
try
{
Connection conn = connWrapper.getConnection();
conn.commit();
if (_logger.isDebugEnabled())
{
_logger.debug("commit tran completed");
}
conn.close();
}
catch (SQLException e)
{
throw new AMQStoreException("Error commit tx: " + e.getMessage(), e);
}
finally
{
}
}
public StoreFuture commitTranAsync(ConnectionWrapper connWrapper) throws AMQStoreException
{
commitTran(connWrapper);
return StoreFuture.IMMEDIATE_FUTURE;
}
public void abortTran(ConnectionWrapper connWrapper) throws AMQStoreException
{
if (connWrapper == null)
{
throw new AMQStoreException("Fatal internal error: transactional context is empty at abortTran");
}
if (_logger.isDebugEnabled())
{
_logger.debug("abort tran called: " + connWrapper.getConnection());
}
try
{
Connection conn = connWrapper.getConnection();
conn.rollback();
conn.close();
}
catch (SQLException e)
{
throw new AMQStoreException("Error aborting transaction: " + e.getMessage(), e);
}
}
public Long getNewMessageId()
{
return _messageId.incrementAndGet();
}
private void storeMetaData(Connection conn, long messageId, StorableMessageMetaData metaData)
throws SQLException
{
if(_logger.isDebugEnabled())
{
_logger.debug("Adding metadata for message " +messageId);
}
PreparedStatement stmt = conn.prepareStatement(INSERT_INTO_META_DATA);
try
{
stmt.setLong(1,messageId);
final int bodySize = 1 + metaData.getStorableSize();
byte[] underlying = new byte[bodySize];
underlying[0] = (byte) metaData.getType().ordinal();
java.nio.ByteBuffer buf = java.nio.ByteBuffer.wrap(underlying);
buf.position(1);
buf = buf.slice();
metaData.writeToBuffer(0, buf);
ByteArrayInputStream bis = new ByteArrayInputStream(underlying);
try
{
stmt.setBinaryStream(2,bis,underlying.length);
int result = stmt.executeUpdate();
if(result == 0)
{
throw new RuntimeException("Unable to add meta data for message " +messageId);
}
}
finally
{
try
{
bis.close();
}
catch (IOException e)
{
throw new SQLException(e);
}
}
}
finally
{
stmt.close();
}
}
private void recoverMessages(MessageStoreRecoveryHandler recoveryHandler) throws SQLException
{
Connection conn = newAutoCommitConnection();
try
{
MessageStoreRecoveryHandler.StoredMessageRecoveryHandler messageHandler = recoveryHandler.begin();
Statement stmt = conn.createStatement();
try
{
ResultSet rs = stmt.executeQuery(SELECT_ALL_FROM_META_DATA);
try
{
long maxId = 0;
while(rs.next())
{
long messageId = rs.getLong(1);
Blob dataAsBlob = rs.getBlob(2);
if(messageId > maxId)
{
maxId = messageId;
}
byte[] dataAsBytes = dataAsBlob.getBytes(1,(int) dataAsBlob.length());
java.nio.ByteBuffer buf = java.nio.ByteBuffer.wrap(dataAsBytes);
buf.position(1);
buf = buf.slice();
MessageMetaDataType type = MessageMetaDataType.values()[dataAsBytes[0]];
StorableMessageMetaData metaData = type.getFactory().createMetaData(buf);
StoredDerbyMessage message = new StoredDerbyMessage(messageId, metaData, true);
messageHandler.message(message);
}
_messageId.set(maxId);
messageHandler.completeMessageRecovery();
}
finally
{
rs.close();
}
}
finally
{
stmt.close();
}
}
finally
{
conn.close();
}
}
private TransactionLogRecoveryHandler.DtxRecordRecoveryHandler recoverQueueEntries(TransactionLogRecoveryHandler recoveryHandler) throws SQLException
{
Connection conn = newAutoCommitConnection();
try
{
TransactionLogRecoveryHandler.QueueEntryRecoveryHandler queueEntryHandler = recoveryHandler.begin(this);
Statement stmt = conn.createStatement();
try
{
ResultSet rs = stmt.executeQuery(SELECT_FROM_QUEUE_ENTRY);
try
{
while(rs.next())
{
String id = rs.getString(1);
long messageId = rs.getLong(2);
queueEntryHandler.queueEntry(UUID.fromString(id), messageId);
}
}
finally
{
rs.close();
}
}
finally
{
stmt.close();
}
return queueEntryHandler.completeQueueEntryRecovery();
}
finally
{
conn.close();
}
}
private static final class Xid
{
private final long _format;
private final byte[] _globalId;
private final byte[] _branchId;
public Xid(long format, byte[] globalId, byte[] branchId)
{
_format = format;
_globalId = globalId;
_branchId = branchId;
}
public long getFormat()
{
return _format;
}
public byte[] getGlobalId()
{
return _globalId;
}
public byte[] getBranchId()
{
return _branchId;
}
}
private static class RecordImpl implements Transaction.Record, TransactionLogResource, EnqueableMessage
{
private long _messageNumber;
private UUID _queueId;
public RecordImpl(UUID queueId, long messageNumber)
{
_messageNumber = messageNumber;
_queueId = queueId;
}
@Override
public TransactionLogResource getQueue()
{
return this;
}
@Override
public EnqueableMessage getMessage()
{
return this;
}
@Override
public long getMessageNumber()
{
return _messageNumber;
}
@Override
public boolean isPersistent()
{
return true;
}
@Override
public StoredMessage getStoredMessage()
{
throw new UnsupportedOperationException();
}
@Override
public UUID getId()
{
return _queueId;
}
}
private void recoverXids(TransactionLogRecoveryHandler.DtxRecordRecoveryHandler dtxrh) throws SQLException
{
Connection conn = newAutoCommitConnection();
try
{
List<Xid> xids = new ArrayList<Xid>();
Statement stmt = conn.createStatement();
try
{
ResultSet rs = stmt.executeQuery(SELECT_ALL_FROM_XIDS);
try
{
while(rs.next())
{
long format = rs.getLong(1);
byte[] globalId = rs.getBytes(2);
byte[] branchId = rs.getBytes(3);
xids.add(new Xid(format, globalId, branchId));
}
}
finally
{
rs.close();
}
}
finally
{
stmt.close();
}
for(Xid xid : xids)
{
List<RecordImpl> enqueues = new ArrayList<RecordImpl>();
List<RecordImpl> dequeues = new ArrayList<RecordImpl>();
PreparedStatement pstmt = conn.prepareStatement(SELECT_ALL_FROM_XID_ACTIONS);
try
{
pstmt.setLong(1, xid.getFormat());
pstmt.setBytes(2, xid.getGlobalId());
pstmt.setBytes(3, xid.getBranchId());
ResultSet rs = pstmt.executeQuery();
try
{
while(rs.next())
{
String actionType = rs.getString(1);
UUID queueId = UUID.fromString(rs.getString(2));
long messageId = rs.getLong(3);
RecordImpl record = new RecordImpl(queueId, messageId);
List<RecordImpl> records = "E".equals(actionType) ? enqueues : dequeues;
records.add(record);
}
}
finally
{
rs.close();
}
}
finally
{
pstmt.close();
}
dtxrh.dtxRecord(xid.getFormat(), xid.getGlobalId(), xid.getBranchId(),
enqueues.toArray(new RecordImpl[enqueues.size()]),
dequeues.toArray(new RecordImpl[dequeues.size()]));
}
dtxrh.completeDtxRecordRecovery();
}
finally
{
conn.close();
}
}
StorableMessageMetaData getMetaData(long messageId) throws SQLException
{
Connection conn = newAutoCommitConnection();
try
{
PreparedStatement stmt = conn.prepareStatement(SELECT_FROM_META_DATA);
try
{
stmt.setLong(1,messageId);
ResultSet rs = stmt.executeQuery();
try
{
if(rs.next())
{
Blob dataAsBlob = rs.getBlob(1);
byte[] dataAsBytes = dataAsBlob.getBytes(1,(int) dataAsBlob.length());
java.nio.ByteBuffer buf = java.nio.ByteBuffer.wrap(dataAsBytes);
buf.position(1);
buf = buf.slice();
MessageMetaDataType type = MessageMetaDataType.values()[dataAsBytes[0]];
StorableMessageMetaData metaData = type.getFactory().createMetaData(buf);
return metaData;
}
else
{
throw new RuntimeException("Meta data not found for message with id " + messageId);
}
}
finally
{
rs.close();
}
}
finally
{
stmt.close();
}
}
finally
{
conn.close();
}
}
private void addContent(Connection conn, long messageId, ByteBuffer src)
{
if(_logger.isDebugEnabled())
{
_logger.debug("Adding content for message " +messageId);
}
PreparedStatement stmt = null;
try
{
src = src.slice();
byte[] chunkData = new byte[src.limit()];
src.duplicate().get(chunkData);
stmt = conn.prepareStatement(INSERT_INTO_MESSAGE_CONTENT);
stmt.setLong(1,messageId);
ByteArrayInputStream bis = new ByteArrayInputStream(chunkData);
stmt.setBinaryStream(2, bis, chunkData.length);
stmt.executeUpdate();
}
catch (SQLException e)
{
closeConnection(conn);
throw new RuntimeException("Error adding content for message " + messageId + ": " + e.getMessage(), e);
}
finally
{
closePreparedStatement(stmt);
}
}
public int getContent(long messageId, int offset, ByteBuffer dst)
{
Connection conn = null;
PreparedStatement stmt = null;
try
{
conn = newAutoCommitConnection();
stmt = conn.prepareStatement(SELECT_FROM_MESSAGE_CONTENT);
stmt.setLong(1,messageId);
ResultSet rs = stmt.executeQuery();
int written = 0;
if (rs.next())
{
Blob dataAsBlob = rs.getBlob(1);
final int size = (int) dataAsBlob.length();
byte[] dataAsBytes = dataAsBlob.getBytes(1, size);
if (offset > size)
{
throw new RuntimeException("Offset " + offset + " is greater than message size " + size
+ " for message id " + messageId + "!");
}
written = size - offset;
if(written > dst.remaining())
{
written = dst.remaining();
}
dst.put(dataAsBytes, offset, written);
}
return written;
}
catch (SQLException e)
{
throw new RuntimeException("Error retrieving content from offset " + offset + " for message " + messageId + ": " + e.getMessage(), e);
}
finally
{
closePreparedStatement(stmt);
closeConnection(conn);
}
}
@Override
public boolean isPersistent()
{
return true;
}
private class DerbyTransaction implements Transaction
{
private final ConnectionWrapper _connWrapper;
private int _storeSizeIncrease;
private DerbyTransaction()
{
try
{
_connWrapper = new ConnectionWrapper(newConnection());
}
catch (SQLException e)
{
throw new RuntimeException(e);
}
}
@Override
public void enqueueMessage(TransactionLogResource queue, EnqueableMessage message) throws AMQStoreException
{
final StoredMessage storedMessage = message.getStoredMessage();
if(storedMessage instanceof StoredDerbyMessage)
{
try
{
((StoredDerbyMessage) storedMessage).store(_connWrapper.getConnection());
}
catch (SQLException e)
{
throw new AMQStoreException("Exception on enqueuing message " + _messageId, e);
}
}
_storeSizeIncrease += storedMessage.getMetaData().getContentSize();
DerbyMessageStore.this.enqueueMessage(_connWrapper, queue, message.getMessageNumber());
}
@Override
public void dequeueMessage(TransactionLogResource queue, EnqueableMessage message) throws AMQStoreException
{
DerbyMessageStore.this.dequeueMessage(_connWrapper, queue, message.getMessageNumber());
}
@Override
public void commitTran() throws AMQStoreException
{
DerbyMessageStore.this.commitTran(_connWrapper);
storedSizeChange(_storeSizeIncrease);
}
@Override
public StoreFuture commitTranAsync() throws AMQStoreException
{
final StoreFuture storeFuture = DerbyMessageStore.this.commitTranAsync(_connWrapper);
storedSizeChange(_storeSizeIncrease);
return storeFuture;
}
@Override
public void abortTran() throws AMQStoreException
{
DerbyMessageStore.this.abortTran(_connWrapper);
}
@Override
public void removeXid(long format, byte[] globalId, byte[] branchId) throws AMQStoreException
{
DerbyMessageStore.this.removeXid(_connWrapper, format, globalId, branchId);
}
@Override
public void recordXid(long format, byte[] globalId, byte[] branchId, Record[] enqueues, Record[] dequeues)
throws AMQStoreException
{
DerbyMessageStore.this.recordXid(_connWrapper, format, globalId, branchId, enqueues, dequeues);
}
}
private class StoredDerbyMessage implements StoredMessage
{
private final long _messageId;
private final boolean _isRecovered;
private StorableMessageMetaData _metaData;
private volatile SoftReference<StorableMessageMetaData> _metaDataRef;
private byte[] _data;
private volatile SoftReference<byte[]> _dataRef;
StoredDerbyMessage(long messageId, StorableMessageMetaData metaData)
{
this(messageId, metaData, false);
}
StoredDerbyMessage(long messageId,
StorableMessageMetaData metaData, boolean isRecovered)
{
_messageId = messageId;
_isRecovered = isRecovered;
if(!_isRecovered)
{
_metaData = metaData;
}
_metaDataRef = new SoftReference<StorableMessageMetaData>(metaData);
}
@Override
public StorableMessageMetaData getMetaData()
{
StorableMessageMetaData metaData = _metaData == null ? _metaDataRef.get() : _metaData;
if(metaData == null)
{
try
{
metaData = DerbyMessageStore.this.getMetaData(_messageId);
}
catch (SQLException e)
{
throw new RuntimeException(e);
}
_metaDataRef = new SoftReference<StorableMessageMetaData>(metaData);
}
return metaData;
}
@Override
public long getMessageNumber()
{
return _messageId;
}
@Override
public void addContent(int offsetInMessage, java.nio.ByteBuffer src)
{
src = src.slice();
if(_data == null)
{
_data = new byte[src.remaining()];
_dataRef = new SoftReference<byte[]>(_data);
src.duplicate().get(_data);
}
else
{
byte[] oldData = _data;
_data = new byte[oldData.length + src.remaining()];
_dataRef = new SoftReference<byte[]>(_data);
System.arraycopy(oldData,0,_data,0,oldData.length);
src.duplicate().get(_data, oldData.length, src.remaining());
}
}
@Override
public int getContent(int offsetInMessage, java.nio.ByteBuffer dst)
{
byte[] data = _dataRef == null ? null : _dataRef.get();
if(data != null)
{
int length = Math.min(dst.remaining(), data.length - offsetInMessage);
dst.put(data, offsetInMessage, length);
return length;
}
else
{
return DerbyMessageStore.this.getContent(_messageId, offsetInMessage, dst);
}
}
@Override
public ByteBuffer getContent(int offsetInMessage, int size)
{
ByteBuffer buf = ByteBuffer.allocate(size);
getContent(offsetInMessage, buf);
buf.position(0);
return buf;
}
@Override
public synchronized StoreFuture flushToStore()
{
Connection conn = null;
try
{
if(!stored())
{
conn = newConnection();
store(conn);
conn.commit();
storedSizeChange(getMetaData().getContentSize());
}
}
catch (SQLException e)
{
if(_logger.isDebugEnabled())
{
_logger.debug("Error when trying to flush message " + _messageId + " to store: " + e);
}
throw new RuntimeException(e);
}
finally
{
closeConnection(conn);
}
return StoreFuture.IMMEDIATE_FUTURE;
}
@Override
public void remove()
{
int delta = getMetaData().getContentSize();
DerbyMessageStore.this.removeMessage(_messageId);
storedSizeChange(-delta);
}
private synchronized void store(final Connection conn) throws SQLException
{
if (!stored())
{
try
{
storeMetaData(conn, _messageId, _metaData);
DerbyMessageStore.this.addContent(conn, _messageId,
_data == null ? ByteBuffer.allocate(0) : ByteBuffer.wrap(_data));
}
finally
{
_metaData = null;
_data = null;
}
if(_logger.isDebugEnabled())
{
_logger.debug("Storing message " + _messageId + " to store");
}
}
}
private boolean stored()
{
return _metaData == null || _isRecovered;
}
}
private void closeConnection(final Connection conn)
{
if(conn != null)
{
try
{
conn.close();
}
catch (SQLException e)
{
_logger.error("Problem closing connection", e);
}
}
}
private void closePreparedStatement(final PreparedStatement stmt)
{
if (stmt != null)
{
try
{
stmt.close();
}
catch(SQLException e)
{
_logger.error("Problem closing prepared statement", e);
}
}
}
@Override
public void addEventListener(EventListener eventListener, Event... events)
{
_eventManager.addEventListener(eventListener, events);
}
@Override
public String getStoreLocation()
{
return _storeLocation;
}
private void insertConfiguredObject(ConfiguredObjectRecord configuredObject) throws AMQStoreException
{
if (_stateManager.isInState(State.ACTIVE))
{
try
{
Connection conn = newAutoCommitConnection();
try
{
PreparedStatement stmt = conn.prepareStatement(FIND_CONFIGURED_OBJECT);
try
{
stmt.setString(1, configuredObject.getId().toString());
ResultSet rs = stmt.executeQuery();
try
{
// If we don't have any data in the result set then we can add this configured object
if (!rs.next())
{
PreparedStatement insertStmt = conn.prepareStatement(INSERT_INTO_CONFIGURED_OBJECTS);
try
{
insertStmt.setString(1, configuredObject.getId().toString());
insertStmt.setString(2, configuredObject.getType());
if(configuredObject.getAttributes() == null)
{
insertStmt.setNull(3, Types.BLOB);
}
else
{
byte[] attributesAsBytes = configuredObject.getAttributes().getBytes(UTF8_CHARSET);
ByteArrayInputStream bis = new ByteArrayInputStream(attributesAsBytes);
insertStmt.setBinaryStream(3, bis, attributesAsBytes.length);
}
insertStmt.execute();
}
finally
{
insertStmt.close();
}
}
}
finally
{
rs.close();
}
}
finally
{
stmt.close();
}
}
finally
{
conn.close();
}
}
catch (SQLException e)
{
throw new AMQStoreException("Error inserting of configured object " + configuredObject + " into database: " + e.getMessage(), e);
}
}
}
private int removeConfiguredObject(UUID id) throws AMQStoreException
{
int results = 0;
try
{
Connection conn = newAutoCommitConnection();
try
{
PreparedStatement stmt = conn.prepareStatement(DELETE_FROM_CONFIGURED_OBJECTS);
try
{
stmt.setString(1, id.toString());
results = stmt.executeUpdate();
}
finally
{
stmt.close();
}
}
finally
{
conn.close();
}
}
catch (SQLException e)
{
throw new AMQStoreException("Error deleting of configured object with id " + id + " from database: " + e.getMessage(), e);
}
return results;
}
private void updateConfiguredObject(final ConfiguredObjectRecord configuredObject) throws AMQStoreException
{
if (_stateManager.isInState(State.ACTIVE))
{
try
{
Connection conn = newAutoCommitConnection();
try
{
PreparedStatement stmt = conn.prepareStatement(FIND_CONFIGURED_OBJECT);
try
{
stmt.setString(1, configuredObject.getId().toString());
ResultSet rs = stmt.executeQuery();
try
{
if (rs.next())
{
PreparedStatement stmt2 = conn.prepareStatement(UPDATE_CONFIGURED_OBJECTS);
try
{
stmt2.setString(1, configuredObject.getType());
if (configuredObject.getAttributes() != null)
{
byte[] attributesAsBytes = configuredObject.getAttributes().getBytes(UTF8_CHARSET);
ByteArrayInputStream bis = new ByteArrayInputStream(attributesAsBytes);
stmt2.setBinaryStream(2, bis, attributesAsBytes.length);
}
else
{
stmt2.setNull(2, Types.BLOB);
}
stmt2.setString(3, configuredObject.getId().toString());
stmt2.execute();
}
finally
{
stmt2.close();
}
}
}
finally
{
rs.close();
}
}
finally
{
stmt.close();
}
}
finally
{
conn.close();
}
}
catch (SQLException e)
{
throw new AMQStoreException("Error updating configured object " + configuredObject + " in database: " + e.getMessage(), e);
}
}
}
private ConfiguredObjectRecord loadConfiguredObject(final UUID id) throws AMQStoreException
{
ConfiguredObjectRecord result = null;
try
{
Connection conn = newAutoCommitConnection();
try
{
PreparedStatement stmt = conn.prepareStatement(FIND_CONFIGURED_OBJECT);
try
{
stmt.setString(1, id.toString());
ResultSet rs = stmt.executeQuery();
try
{
if (rs.next())
{
String type = rs.getString(1);
Blob blob = rs.getBlob(2);
String attributes = null;
if (blob != null)
{
attributes = blobToString(blob);
}
result = new ConfiguredObjectRecord(id, type, attributes);
}
}
finally
{
rs.close();
}
}
finally
{
stmt.close();
}
}
finally
{
conn.close();
}
}
catch (SQLException e)
{
throw new AMQStoreException("Error loading of configured object with id " + id + " from database: "
+ e.getMessage(), e);
}
return result;
}
private String blobToString(Blob blob) throws SQLException
{
byte[] bytes = blob.getBytes(1, (int)blob.length());
return new String(bytes, UTF8_CHARSET);
}
private List<ConfiguredObjectRecord> loadConfiguredObjects() throws SQLException
{
ArrayList<ConfiguredObjectRecord> results = new ArrayList<ConfiguredObjectRecord>();
Connection conn = newAutoCommitConnection();
try
{
PreparedStatement stmt = conn.prepareStatement(SELECT_FROM_CONFIGURED_OBJECTS);
try
{
ResultSet rs = stmt.executeQuery();
try
{
while (rs.next())
{
String id = rs.getString(1);
String objectType = rs.getString(2);
String attributes = blobToString(rs.getBlob(3));
results.add(new ConfiguredObjectRecord(UUID.fromString(id), objectType, attributes));
}
}
finally
{
rs.close();
}
}
finally
{
stmt.close();
}
}
finally
{
conn.close();
}
return results;
}
private synchronized void storedSizeChange(final int delta)
{
if(getPersistentSizeHighThreshold() > 0)
{
synchronized(this)
{
// the delta supplied is an approximation of a store size change. we don;t want to check the statistic every
// time, so we do so only when there's been enough change that it is worth looking again. We do this by
// assuming the total size will change by less than twice the amount of the message data change.
long newSize = _totalStoreSize += 3*delta;
Connection conn = null;
try
{
if(!_limitBusted && newSize > getPersistentSizeHighThreshold())
{
conn = newAutoCommitConnection();
_totalStoreSize = getSizeOnDisk(conn);
if(_totalStoreSize > getPersistentSizeHighThreshold())
{
_limitBusted = true;
_eventManager.notifyEvent(Event.PERSISTENT_MESSAGE_SIZE_OVERFULL);
}
}
else if(_limitBusted && newSize < getPersistentSizeLowThreshold())
{
long oldSize = _totalStoreSize;
conn = newAutoCommitConnection();
_totalStoreSize = getSizeOnDisk(conn);
if(oldSize <= _totalStoreSize)
{
reduceSizeOnDisk(conn);
_totalStoreSize = getSizeOnDisk(conn);
}
if(_totalStoreSize < getPersistentSizeLowThreshold())
{
_limitBusted = false;
_eventManager.notifyEvent(Event.PERSISTENT_MESSAGE_SIZE_UNDERFULL);
}
}
}
catch (SQLException e)
{
closeConnection(conn);
throw new RuntimeException("Exception will processing store size change", e);
}
}
}
}
private void reduceSizeOnDisk(Connection conn)
{
CallableStatement cs = null;
PreparedStatement stmt = null;
try
{
String tableQuery =
"SELECT S.SCHEMANAME, T.TABLENAME FROM SYS.SYSSCHEMAS S, SYS.SYSTABLES T WHERE S.SCHEMAID = T.SCHEMAID AND T.TABLETYPE='T'";
stmt = conn.prepareStatement(tableQuery);
ResultSet rs = null;
List<String> schemas = new ArrayList<String>();
List<String> tables = new ArrayList<String>();
try
{
rs = stmt.executeQuery();
while(rs.next())
{
schemas.add(rs.getString(1));
tables.add(rs.getString(2));
}
}
finally
{
if(rs != null)
{
rs.close();
}
}
cs = conn.prepareCall
("CALL SYSCS_UTIL.SYSCS_COMPRESS_TABLE(?, ?, ?)");
for(int i = 0; i < schemas.size(); i++)
{
cs.setString(1, schemas.get(i));
cs.setString(2, tables.get(i));
cs.setShort(3, (short) 0);
cs.execute();
}
}
catch (SQLException e)
{
closeConnection(conn);
throw new RuntimeException("Error reducing on disk size", e);
}
finally
{
closePreparedStatement(stmt);
closePreparedStatement(cs);
}
}
private long getSizeOnDisk(Connection conn)
{
PreparedStatement stmt = null;
try
{
String sizeQuery = "SELECT SUM(T2.NUMALLOCATEDPAGES * T2.PAGESIZE) TOTALSIZE" +
" FROM " +
" SYS.SYSTABLES systabs," +
" TABLE (SYSCS_DIAG.SPACE_TABLE(systabs.tablename)) AS T2" +
" WHERE systabs.tabletype = 'T'";
stmt = conn.prepareStatement(sizeQuery);
ResultSet rs = null;
long size = 0l;
try
{
rs = stmt.executeQuery();
while(rs.next())
{
size = rs.getLong(1);
}
}
finally
{
if(rs != null)
{
rs.close();
}
}
return size;
}
catch (SQLException e)
{
closeConnection(conn);
throw new RuntimeException("Error establishing on disk size", e);
}
finally
{
closePreparedStatement(stmt);
}
}
private long getPersistentSizeLowThreshold()
{
return _persistentSizeLowThreshold;
}
private long getPersistentSizeHighThreshold()
{
return _persistentSizeHighThreshold;
}
@Override
public String getStoreType()
{
return DERBY_STORE_TYPE;
}
}