/**
*
* Copyright 2004 Hiram Chirino
*
* Licensed 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.codehaus.activemq.store.jdbc.adapter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.activemq.message.ActiveMQXid;
import org.codehaus.activemq.service.SubscriberEntry;
import org.codehaus.activemq.service.Transaction;
import org.codehaus.activemq.service.TransactionManager;
import org.codehaus.activemq.service.impl.XATransactionCommand;
import org.codehaus.activemq.store.jdbc.JDBCAdapter;
import org.codehaus.activemq.store.jdbc.StatementProvider;
import org.codehaus.activemq.util.LongSequenceGenerator;
import javax.jms.JMSException;
import javax.transaction.xa.XAException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
/**
* Implements all the default JDBC operations that are used
* by the JDBCPersistenceAdapter.
* <p/>
* Subclassing is encouraged to override the default
* implementation of methods to account for differences
* in JDBC Driver implementations.
* <p/>
* The JDBCAdapter inserts and extracts BLOB data using the
* getBytes()/setBytes() operations.
* <p/>
* The databases/JDBC drivers that use this adapter are:
* <ul>
* <li></li>
* </ul>
*
* @version $Revision: 1.7 $
*/
public class DefaultJDBCAdapter implements JDBCAdapter {
private static final Log log = LogFactory.getLog(DefaultJDBCAdapter.class);
final protected CachingStatementProvider statementProvider;
protected LongSequenceGenerator sequenceGenerator = new LongSequenceGenerator();
protected void setBinaryData(PreparedStatement s, int index, byte data[]) throws SQLException {
s.setBytes(index, data);
}
protected byte[] getBinaryData(ResultSet rs, int index) throws SQLException {
return rs.getBytes(index);
}
/**
* @param provider
*/
public DefaultJDBCAdapter(StatementProvider provider) {
this.statementProvider = new CachingStatementProvider(provider);
}
public DefaultJDBCAdapter() {
this(new DefaultStatementProvider());
}
public LongSequenceGenerator getSequenceGenerator() {
return sequenceGenerator;
}
public void doCreateTables(Connection c) throws SQLException {
Statement s = null;
try {
s = c.createStatement();
String[] createStatments = statementProvider.getCreateSchemaStatments();
for (int i = 0; i < createStatments.length; i++) {
// This will fail usually since the tables will be
// created allready.
try {
boolean rc = s.execute(createStatments[i]);
}
catch (SQLException e) {
log.debug("Statment failed: " + createStatments[i], e);
}
}
c.commit();
}
finally {
try {
s.close();
}
catch (Throwable e) {
}
}
}
public void initSequenceGenerator(Connection c) {
PreparedStatement s = null;
ResultSet rs = null;
try {
s = c.prepareStatement(statementProvider.getFindLastSequenceId());
rs = s.executeQuery();
if (rs.next()) {
sequenceGenerator.setLastSequenceId(rs.getLong(1));
}
}
catch (SQLException e) {
log.warn("Failed to find last sequence number: " + e, e);
}
finally {
try {
rs.close();
}
catch (Throwable e) {
}
try {
s.close();
}
catch (Throwable e) {
}
}
}
public void doAddMessage(Connection c, long seq, String messageID, String destinationName, byte[] data) throws SQLException, JMSException {
PreparedStatement s = null;
try {
s = c.prepareStatement(statementProvider.getAddMessageStatment());
s.setLong(1, seq);
s.setString(2, destinationName);
s.setString(3, messageID);
setBinaryData(s, 4, data);
if (s.executeUpdate() != 1) {
throw new JMSException("Failed to broker message: " + messageID + " in container. ");
}
}
finally {
try {
s.close();
}
catch (Throwable e) {
}
}
}
public Long getMessageSequenceId(Connection c, String messageID) throws SQLException, JMSException {
PreparedStatement s = null;
ResultSet rs = null;
try {
s = c.prepareStatement(statementProvider.getFindMessageSequenceIdStatment());
s.setString(1, messageID);
rs = s.executeQuery();
if (!rs.next()) {
return null;
}
return new Long( rs.getLong(1) );
}
finally {
try {
rs.close();
}
catch (Throwable e) {
}
try {
s.close();
}
catch (Throwable e) {
}
}
}
public byte[] doGetMessage(Connection c, long seq) throws SQLException {
PreparedStatement s = null;
ResultSet rs = null;
try {
s = c.prepareStatement(statementProvider.getFindMessageStatment());
s.setLong(1, seq);
rs = s.executeQuery();
if (!rs.next()) {
return null;
}
return getBinaryData(rs, 1);
}
finally {
try {
rs.close();
}
catch (Throwable e) {
}
try {
s.close();
}
catch (Throwable e) {
}
}
}
public void doRemoveMessage(Connection c, long seq) throws SQLException {
PreparedStatement s = null;
try {
s = c.prepareStatement(statementProvider.getRemoveMessageStatment());
s.setLong(1, seq);
if (s.executeUpdate() != 1) {
log.error("Could not delete sequenece number for: " + seq);
}
}
finally {
try {
s.close();
}
catch (Throwable e) {
}
}
}
public void doRecover(Connection c, String destinationName, MessageListResultHandler listener) throws SQLException, JMSException {
PreparedStatement s = null;
ResultSet rs = null;
try {
s = c.prepareStatement(statementProvider.getFindAllMessagesStatment());
s.setString(1, destinationName);
rs = s.executeQuery();
while (rs.next()) {
long seq = rs.getLong(1);
String msgid = rs.getString(2);
listener.onMessage(seq, msgid);
}
}
finally {
try {
rs.close();
}
catch (Throwable e) {
}
try {
s.close();
}
catch (Throwable e) {
}
}
}
public void doGetXids(Connection c, List list) throws SQLException {
PreparedStatement s = null;
ResultSet rs = null;
try {
s = c.prepareStatement(statementProvider.getFindAllXidStatment());
rs = s.executeQuery();
while (rs.next()) {
String xid = rs.getString(1);
try {
list.add(new ActiveMQXid(xid));
}
catch (JMSException e) {
log.error("Failed to recover prepared transaction due to invalid xid: " + xid, e);
}
}
}
finally {
try {
rs.close();
}
catch (Throwable e) {
}
try {
s.close();
}
catch (Throwable e) {
}
}
}
public void doRemoveXid(Connection c, ActiveMQXid xid) throws SQLException, XAException {
PreparedStatement s = null;
try {
s = c.prepareStatement(statementProvider.getRemoveMessageStatment());
s.setString(1, xid.toLocalTransactionId());
if (s.executeUpdate() != 1) {
throw new XAException("Failed to remove prepared transaction: " + xid + ".");
}
}
finally {
try {
s.close();
}
catch (Throwable e) {
}
}
}
public void doAddXid(Connection c, ActiveMQXid xid, byte[] data) throws SQLException, XAException {
PreparedStatement s = null;
try {
s = c.prepareStatement(statementProvider.getAddMessageStatment());
s.setString(1, xid.toLocalTransactionId());
setBinaryData(s, 2, data);
if (s.executeUpdate() != 1) {
throw new XAException("Failed to store prepared transaction: " + xid);
}
}
finally {
try {
s.close();
}
catch (Throwable e) {
}
}
}
public void doLoadPreparedTransactions(Connection c, TransactionManager transactionManager) throws SQLException {
PreparedStatement s = null;
ResultSet rs = null;
try {
s = c.prepareStatement(statementProvider.getFindAllTxStatment());
rs = s.executeQuery();
while (rs.next()) {
String id = rs.getString(1);
byte data[] = this.getBinaryData(rs, 2);
try {
ActiveMQXid xid = new ActiveMQXid(id);
Transaction transaction = XATransactionCommand.fromBytes(data);
transactionManager.loadTransaction(xid, transaction);
}
catch (Exception e) {
log.error("Failed to recover prepared transaction due to invalid xid: " + id, e);
}
}
}
finally {
try {
rs.close();
}
catch (Throwable e) {
}
try {
s.close();
}
catch (Throwable e) {
}
}
}
/**
* @throws JMSException
* @see org.codehaus.activemq.store.jdbc.JDBCAdapter#doSetLastAck(java.sql.Connection, java.lang.String, java.lang.String, long)
*/
public void doSetLastAck(Connection c, String destinationName, String subscriptionID, long seq) throws SQLException, JMSException {
PreparedStatement s = null;
try {
s = c.prepareStatement(statementProvider.getUpdateLastAckOfDurableSub());
s.setLong(1, seq);
s.setString(2, subscriptionID);
s.setString(3, destinationName);
if (s.executeUpdate() != 1) {
throw new JMSException("Failed to acknowlege message with sequence id: " + seq + " for client: " + subscriptionID);
}
}
finally {
try {
s.close();
}
catch (Throwable e) {
}
}
}
/**
* @throws JMSException
* @see org.codehaus.activemq.store.jdbc.JDBCAdapter#doRecoverSubscription(java.sql.Connection, java.lang.String, java.lang.String, org.codehaus.activemq.store.jdbc.JDBCAdapter.MessageListResultHandler)
*/
public void doRecoverSubscription(Connection c, String destinationName, String subscriptionID, MessageListResultHandler listener) throws SQLException, JMSException {
PreparedStatement s = null;
ResultSet rs = null;
try {
s = c.prepareStatement(statementProvider.getFindAllDurableSubMessagesStatment());
s.setString(1, destinationName);
s.setString(2, subscriptionID);
rs = s.executeQuery();
while (rs.next()) {
long seq = rs.getLong(1);
String msgid = rs.getString(2);
listener.onMessage(seq, msgid);
}
}
finally {
try {
rs.close();
}
catch (Throwable e) {
}
try {
s.close();
}
catch (Throwable e) {
}
}
}
/**
* @see org.codehaus.activemq.store.jdbc.JDBCAdapter#doSetSubscriberEntry(java.sql.Connection, java.lang.Object, org.codehaus.activemq.service.SubscriberEntry)
*/
public void doSetSubscriberEntry(Connection c, String destinationName, String sub, SubscriberEntry subscriberEntry) throws SQLException {
PreparedStatement s = null;
try {
s = c.prepareStatement(statementProvider.getUpdateDurableSubStatment());
s.setInt(1, subscriberEntry.getSubscriberID());
s.setString(2, subscriberEntry.getClientID());
s.setString(3, subscriberEntry.getConsumerName());
s.setString(4, subscriberEntry.getSelector());
s.setString(5, sub);
s.setString(6, destinationName);
// If the sub was not there then we need to create it.
if (s.executeUpdate() != 1) {
s.close();
s = c.prepareStatement(statementProvider.getCreateDurableSubStatment());
s.setInt(1, subscriberEntry.getSubscriberID());
s.setString(2, subscriberEntry.getClientID());
s.setString(3, subscriberEntry.getConsumerName());
s.setString(4, subscriberEntry.getSelector());
s.setString(5, sub);
s.setString(6, destinationName);
s.setLong(7, -1);
if (s.executeUpdate() != 1) {
log.error("Failed to store durable subscription for: " + sub);
}
}
}
finally {
try {
s.close();
}
catch (Throwable e) {
}
}
}
/**
* @see org.codehaus.activemq.store.jdbc.JDBCAdapter#doGetSubscriberEntry(java.sql.Connection, java.lang.Object)
*/
public SubscriberEntry doGetSubscriberEntry(Connection c, String destinationName, String sub) throws SQLException {
PreparedStatement s = null;
ResultSet rs = null;
try {
s = c.prepareStatement(statementProvider.getFindDurableSubStatment());
s.setString(1, sub);
s.setString(2, destinationName);
rs = s.executeQuery();
if (!rs.next()) {
return null;
}
SubscriberEntry answer = new SubscriberEntry();
answer.setSubscriberID(rs.getInt(1));
answer.setClientID(rs.getString(2));
answer.setConsumerName(rs.getString(3));
answer.setDestination(rs.getString(4));
return answer;
}
finally {
try {
rs.close();
}
catch (Throwable e) {
}
try {
s.close();
}
catch (Throwable e) {
}
}
}
}