/**
*
* 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.activemq.store.jdbc;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import javax.jms.JMSException;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.activemq.io.WireFormat;
import org.activemq.io.impl.StatelessDefaultWireFormat;
import org.activemq.store.MessageStore;
import org.activemq.store.PersistenceAdapter;
import org.activemq.store.TopicMessageStore;
import org.activemq.store.TransactionStore;
import org.activemq.store.jdbc.adapter.DefaultJDBCAdapter;
import org.activemq.store.vm.VMTransactionStore;
import org.activemq.util.FactoryFinder;
import org.activemq.util.JMSExceptionHelper;
import EDU.oswego.cs.dl.util.concurrent.ClockDaemon;
import EDU.oswego.cs.dl.util.concurrent.ThreadFactory;
/**
* A {@link PersistenceAdapter} implementation using JDBC for
* persistence storage.
*
* This persistence adapter will correctly remember prepared XA transactions,
* but it will not keep track of local transaction commits so that operations
* performed against the Message store are done as a single uow.
*
* @version $Revision: 1.1 $
*/
public class JDBCPersistenceAdapter implements PersistenceAdapter {
private static final Log log = LogFactory.getLog(JDBCPersistenceAdapter.class);
private static FactoryFinder factoryFinder = new FactoryFinder("META-INF/services/org/activemq/store/jdbc/");
private WireFormat wireFormat = new StatelessDefaultWireFormat();
private DataSource dataSource;
private JDBCAdapter adapter;
private String adapterClass;
private VMTransactionStore transactionStore;
private boolean dropTablesOnStartup=false;
private ClockDaemon clockDaemon;
private Object clockTicket;
public JDBCPersistenceAdapter() {
}
public JDBCPersistenceAdapter(DataSource ds, WireFormat wireFormat) {
this.dataSource = ds;
this.wireFormat = wireFormat;
}
public Map getInitialDestinations() {
return null;
}
public MessageStore createQueueMessageStore(String destinationName) throws JMSException {
if (adapter == null) {
throw new IllegalStateException("Not started");
}
MessageStore store = new JDBCMessageStore(this, adapter, wireFormat.copy(), destinationName);
if( transactionStore!=null ) {
store = transactionStore.proxy(store);
}
return store;
}
public TopicMessageStore createTopicMessageStore(String destinationName) throws JMSException {
if (adapter == null) {
throw new IllegalStateException("Not started");
}
TopicMessageStore store = new JDBCTopicMessageStore(this, adapter, wireFormat.copy(), destinationName);
if( transactionStore!=null ) {
store = transactionStore.proxy(store);
}
return store;
}
public TransactionStore createTransactionStore() throws JMSException {
if (adapter == null) {
throw new IllegalStateException("Not started");
}
if( this.transactionStore == null ) {
this.transactionStore = new VMTransactionStore();
}
return this.transactionStore;
}
public void beginTransaction() throws JMSException {
try {
Connection c = dataSource.getConnection();
c.setAutoCommit(false);
TransactionContext.pushConnection(c);
}
catch (SQLException e) {
throw JMSExceptionHelper.newJMSException("Failed to create transaction: " + e, e);
}
}
public void commitTransaction() throws JMSException {
Connection c = TransactionContext.popConnection();
if (c == null) {
log.warn("Commit while no transaction in progress");
}
else {
try {
c.commit();
}
catch (SQLException e) {
throw JMSExceptionHelper.newJMSException("Failed to commit transaction: " + c + ": " + e, e);
}
finally {
try {
c.close();
}
catch (Throwable e) {
}
}
}
}
public void rollbackTransaction() {
Connection c = TransactionContext.popConnection();
try {
c.rollback();
}
catch (SQLException e) {
log.warn("Cannot rollback transaction due to: " + e, e);
}
finally {
try {
c.close();
}
catch (Throwable e) {
}
}
}
public void start() throws JMSException {
beginTransaction();
Connection c = null;
try {
// Load the right adapter for the database
adapter = null;
try {
c = getConnection();
}
catch (SQLException e) {
throw JMSExceptionHelper.newJMSException("Could not get a database connection: "+e,e);
}
// If the adapter class is not specified.. try to dectect they right type by getting
// info from the database.
if( adapterClass == null ) {
try {
// Make the filename file system safe.
String dirverName = c.getMetaData().getDriverName();
dirverName = dirverName.replaceAll("[^a-zA-Z0-9\\-]", "_").toLowerCase();
try {
adapter = (DefaultJDBCAdapter) factoryFinder.newInstance(dirverName);
log.info("Database driver recognized: [" + dirverName + "]");
}
catch (Throwable e) {
log.warn("Database driver NOT recognized: [" + dirverName + "]. Will use default JDBC implementation.");
}
}
catch (SQLException e) {
log.warn("JDBC error occured while trying to detect database type. Will use default JDBC implementation: "+e.getMessage());
log.debug("Reason: " + e, e);
}
} else {
try {
Class clazz = JDBCPersistenceAdapter.class.getClassLoader().loadClass(adapterClass);
adapter = (DefaultJDBCAdapter)clazz.newInstance();
}
catch (Throwable e) {
log.warn("Invalid JDBC adapter class class (" + adapterClass + "). Will use default JDBC implementation.");
log.debug("Reason: " + e, e);
}
}
// Use the default JDBC adapter if the
// Database type is not recognized.
if (adapter == null) {
adapter = new DefaultJDBCAdapter();
}
if( dropTablesOnStartup ) {
try {
adapter.doDropTables(c);
}
catch (SQLException e) {
log.warn("Cannot drop tables due to: " + e, e);
}
}
try {
adapter.doCreateTables(c);
}
catch (SQLException e) {
log.warn("Cannot create tables due to: " + e, e);
}
adapter.initSequenceGenerator(c);
}
finally {
commitTransaction();
}
// Cleanup the db periodically.
clockTicket = getClockDaemon().executePeriodically(1000 * 60 * 5, new Runnable() {
public void run() {
cleanup();
}
}, false);
cleanup();
}
protected void cleanup() {
Connection c = null;
try {
log.debug("Cleaning up old messages.");
c = getConnection();
adapter.doDeleteOldMessages(c);
} catch (JMSException e) {
log.warn("Old message cleanup failed due to: " + e, e);
} catch (SQLException e) {
log.warn("Old message cleanup failed due to: " + e, e);
} finally {
returnConnection(c);
log.debug("Cleanup done.");
}
}
public void setClockDaemon(ClockDaemon clockDaemon) {
this.clockDaemon = clockDaemon;
}
public ClockDaemon getClockDaemon() {
if (clockDaemon == null) {
clockDaemon = new ClockDaemon();
clockDaemon.setThreadFactory(new ThreadFactory() {
public Thread newThread(Runnable runnable) {
Thread thread = new Thread(runnable, "Cleanup Timmer");
thread.setDaemon(true);
return thread;
}
});
}
return clockDaemon;
}
public synchronized void stop() throws JMSException {
if (clockTicket != null) {
// Stop the periodical cleanup.
ClockDaemon.cancel(clockTicket);
clockTicket=null;
clockDaemon.shutDown();
}
}
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public WireFormat getWireFormat() {
return wireFormat;
}
public void setWireFormat(WireFormat wireFormat) {
this.wireFormat = wireFormat;
}
public Connection getConnection() throws SQLException {
Connection answer = TransactionContext.peekConnection();
if (answer == null) {
answer = dataSource.getConnection();
answer.setAutoCommit(true);
}
return answer;
}
public void returnConnection(Connection connection) {
if (connection == null) {
return;
}
Connection peek = TransactionContext.peekConnection();
if (peek != connection) {
try {
connection.close();
}
catch (SQLException e) {
}
}
}
/**
* @return Returns the adapterClass.
*/
public String getAdapterClass() {
return adapterClass;
}
/**
* @param adapterClass The adapterClass to set.
*/
public void setAdapterClass(String adapterClass) {
this.adapterClass = adapterClass;
}
/**
* @return Returns the dropTablesOnStartup.
*/
public boolean getDropTablesOnStartup() {
return dropTablesOnStartup;
}
/**
* @param dropTablesOnStartup The dropTablesOnStartup to set.
*/
public void setDropTablesOnStartup(boolean dropTablesOnStartup) {
this.dropTablesOnStartup = dropTablesOnStartup;
}
}