/**
*
* Copyright 2004 Protique Ltd
*
* 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.broker.impl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.activemq.broker.Broker;
import org.codehaus.activemq.broker.BrokerClient;
import org.codehaus.activemq.capacity.DelegateCapacityMonitor;
import org.codehaus.activemq.message.ActiveMQMessage;
import org.codehaus.activemq.message.ActiveMQXid;
import org.codehaus.activemq.message.ConsumerInfo;
import org.codehaus.activemq.message.MessageAck;
import org.codehaus.activemq.message.util.MemoryBoundedQueueManager;
import org.codehaus.activemq.service.MessageContainerManager;
import org.codehaus.activemq.service.Transaction;
import org.codehaus.activemq.service.TransactionManager;
import org.codehaus.activemq.service.boundedvm.TransientTopicBoundedMessageManager;
import org.codehaus.activemq.service.impl.DurableTopicMessageContainerManager;
import org.codehaus.activemq.service.impl.MessageAckTransactionTask;
import org.codehaus.activemq.service.impl.QueueMessageContainerManager;
import org.codehaus.activemq.service.impl.RedeliverMessageTransactionTask;
import org.codehaus.activemq.service.impl.SendMessageTransactionTask;
import org.codehaus.activemq.store.PersistenceAdapter;
import org.codehaus.activemq.store.PreparedTransactionStore;
import org.codehaus.activemq.store.vm.VMPersistenceAdapter;
import org.codehaus.activemq.store.vm.VMTransactionManager;
import org.codehaus.activemq.util.Callback;
import org.codehaus.activemq.util.ExceptionTemplate;
import org.codehaus.activemq.util.JMSExceptionHelper;
import javax.jms.JMSException;
import javax.transaction.xa.XAException;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* The default {@link Broker} implementation
*
* @version $Revision: 1.11 $
*/
public class DefaultBroker extends DelegateCapacityMonitor implements Broker {
private static final Log log = LogFactory.getLog(DefaultBroker.class);
protected static final String PROPERTY_STORE_DIRECTORY = "activemq.store.dir";
protected static final String PERSISTENCE_ADAPTER_PROPERTY = "activemq.persistenceAdapter";
protected static final Class[] NEWINSTANCE_PARAMETER_TYPES = {File.class};
private static final long DEFAULT_MAX_MEMORY_USAGE = 20 * 1024 * 1024; //20mb
private PersistenceAdapter persistenceAdapter;
private TransactionManager transactionManager;
private MessageContainerManager[] containerManagers;
private File tempDir;
private MemoryBoundedQueueManager memoryManager;
private PreparedTransactionStore preparedTransactionStore;
private final String brokerName;
public DefaultBroker(String brokerName) {
this.brokerName = brokerName;
memoryManager = new MemoryBoundedQueueManager("Broker Memory Manager", DEFAULT_MAX_MEMORY_USAGE);
setDelegate(memoryManager);
}
public DefaultBroker(String brokerName, PersistenceAdapter persistenceAdapter) {
this(brokerName);
this.persistenceAdapter = persistenceAdapter;
}
/**
* Start this Service
*
* @throws JMSException
*/
public void start() throws JMSException {
if (persistenceAdapter == null) {
persistenceAdapter = createPersistenceAdapter();
}
persistenceAdapter.start();
if (transactionManager == null) {
preparedTransactionStore = persistenceAdapter.createPreparedTransactionStore();
transactionManager = new VMTransactionManager(this, preparedTransactionStore);
}
transactionManager.start();
// force containers to be created
getContainerManagers();
for (int i = 0; i < containerManagers.length; i++) {
containerManagers[i].start();
}
}
/**
* stop this Service
*
* @throws JMSException
*/
public void stop() throws JMSException {
ExceptionTemplate template = new ExceptionTemplate();
for (int i = 0; i < containerManagers.length; i++) {
final MessageContainerManager containerManager = containerManagers[i];
template.run(new Callback() {
public void execute() throws Throwable {
containerManager.stop();
}
});
}
if (transactionManager != null) {
template.run(new Callback() {
public void execute() throws Throwable {
transactionManager.stop();
}
});
}
template.run(new Callback() {
public void execute() throws Throwable {
persistenceAdapter.stop();
}
});
template.throwJMSException();
}
/**
* Acknowledge consumption of a message by the Message Consumer
*
* @param client
* @param ack
* @throws JMSException
*/
public void acknowledgeMessage(BrokerClient client, MessageAck ack) throws JMSException {
for (int i = 0; i < containerManagers.length; i++) {
containerManagers[i].acknowledgeMessage(client, ack);
}
}
/**
* Acknowledge consumption of a message within a transaction
*
* @param client
* @param transactionId
* @param ack
*/
public void acknowledgeTransactedMessage(final BrokerClient client, final String transactionId, final MessageAck ack) throws JMSException {
Transaction transaction;
if (ack.isXaTransacted()) {
try {
transaction = transactionManager.getXATransaction(new ActiveMQXid(transactionId));
}
catch (XAException e) {
throw (JMSException) new JMSException(e.getMessage()).initCause(e);
}
}
else {
transaction = transactionManager.getLocalTransaction(transactionId);
}
transaction.addPostCommitTask(new MessageAckTransactionTask(client, ack));
transaction.addPostRollbackTask(new RedeliverMessageTransactionTask(client, ack));
// we need to tell the dispatcher that we can now accept another message
// even though we don't really ack the message until the commit
// this is because if we have a prefetch value of 1, we can never consume 2 messages
// in a transaction, since the ack for the first message never arrives until the commit
for (int i = 0; i < containerManagers.length; i++) {
containerManagers[i].acknowledgeTransactedMessage(client, transactionId, ack);
}
}
/**
* send a message to the broker
*
* @param client
* @param message
* @throws JMSException
*/
public void sendMessage(BrokerClient client, ActiveMQMessage message) throws JMSException {
checkValid();
if (message.getJMSMessageID() == null) {
throw new JMSException("No messageID specified for the Message");
}
for (int i = 0; i < containerManagers.length; i++) {
containerManagers[i].sendMessage(client, message);
}
}
/**
* send a message to the broker within a transaction
*
* @param client
* @param transactionId
* @param message
*/
public void sendTransactedMessage(final BrokerClient client, final String transactionId, final ActiveMQMessage message) throws JMSException {
Transaction transaction;
if (message.isXaTransacted()) {
try {
transaction = transactionManager.getXATransaction(new ActiveMQXid(transactionId));
}
catch (XAException e) {
throw (JMSException) new JMSException(e.getMessage()).initCause(e);
}
}
else {
transaction = transactionManager.getLocalTransaction(transactionId);
}
transaction.addPostCommitTask(new SendMessageTransactionTask(client, message));
}
/**
* Add an active message consumer
*
* @param client
* @param info
* @throws JMSException
*/
public void addMessageConsumer(BrokerClient client, ConsumerInfo info) throws JMSException {
validateConsumer(info);
MessageContainerManager[] array = getContainerManagers();
for (int i = 0; i < array.length; i++) {
array[i].addMessageConsumer(client, info);
}
}
/**
* remove an active message consumer
*
* @param client
* @param info
* @throws JMSException
*/
public void removeMessageConsumer(BrokerClient client, ConsumerInfo info) throws JMSException {
validateConsumer(info);
for (int i = 0; i < containerManagers.length; i++) {
containerManagers[i].removeMessageConsumer(client, info);
}
}
public void redeliverMessage(BrokerClient client, MessageAck ack) throws JMSException {
for (int i = 0; i < containerManagers.length; i++) {
containerManagers[i].redeliverMessage(client, ack);
}
}
/**
* Delete a durable subscriber
*
* @param clientId
* @param subscriberName
* @throws JMSException if the subscriber doesn't exist or is still active
*/
public void deleteSubscription(String clientId, String subscriberName) throws JMSException {
for (int i = 0; i < containerManagers.length; i++) {
containerManagers[i].deleteSubscription(clientId, subscriberName);
}
}
/**
* Start a transaction.
*
* @see org.codehaus.activemq.broker.Broker#startTransaction(org.codehaus.activemq.broker.BrokerClient, java.lang.String)
*/
public void startTransaction(BrokerClient client, String transactionId) throws JMSException {
transactionManager.createLocalTransaction(client, transactionId);
}
/**
* commit a transaction
*
* @param client
* @param transactionId
* @throws JMSException
*/
public void commitTransaction(BrokerClient client, String transactionId) throws JMSException {
try {
for (int i = 0; i < containerManagers.length; i++) {
containerManagers[i].commitTransaction(client, transactionId);
}
Transaction transaction = transactionManager.getLocalTransaction(transactionId);
transaction.commit(true);
}
catch (XAException e) {
// TODO: I think the XAException should propagate all the way to the client.
throw (JMSException) new JMSException(e.getMessage()).initCause(e);
}
}
/**
* rollback a transaction
*
* @param client
* @param transactionId
*/
public void rollbackTransaction(BrokerClient client, String transactionId) throws JMSException {
try {
for (int i = 0; i < containerManagers.length; i++) {
containerManagers[i].rollbackTransaction(client, transactionId);
}
Transaction transaction = transactionManager.getLocalTransaction(transactionId);
transaction.rollback();
}
catch (XAException e) {
// TODO: I think the XAException should propagate all the way to the client.
throw (JMSException) new JMSException(e.getMessage()).initCause(e);
}
}
/**
* Starts an XA Transaction.
*
* @see org.codehaus.activemq.broker.Broker#startTransaction(org.codehaus.activemq.broker.BrokerClient, org.codehaus.activemq.message.ActiveMQXid)
*/
public void startTransaction(BrokerClient client, ActiveMQXid xid) throws XAException {
transactionManager.createXATransaction(client, xid);
}
/**
* Prepares an XA Transaciton.
*
* @see org.codehaus.activemq.broker.Broker#prepareTransaction(org.codehaus.activemq.broker.BrokerClient, org.codehaus.activemq.message.ActiveMQXid)
*/
public int prepareTransaction(BrokerClient client, ActiveMQXid xid) throws XAException {
Transaction transaction = transactionManager.getXATransaction(xid);
return transaction.prepare();
}
/**
* Rollback an XA Transaction.
*
* @see org.codehaus.activemq.broker.Broker#rollbackTransaction(org.codehaus.activemq.broker.BrokerClient, org.codehaus.activemq.message.ActiveMQXid)
*/
public void rollbackTransaction(BrokerClient client, ActiveMQXid xid) throws XAException {
Transaction transaction = transactionManager.getXATransaction(xid);
transaction.rollback();
}
/**
* Commit an XA Transaction.
*
* @see org.codehaus.activemq.broker.Broker#commitTransaction(org.codehaus.activemq.broker.BrokerClient, org.codehaus.activemq.message.ActiveMQXid, boolean)
*/
public void commitTransaction(BrokerClient client, ActiveMQXid xid, boolean onePhase) throws XAException {
Transaction transaction = transactionManager.getXATransaction(xid);
transaction.commit(onePhase);
}
/**
* A hint to the broker that an BrokerClient has stopped
* This enables the broker to clean-up any outstanding processing
* that may be outstanding
*
* @param client
*/
public void cleanUpClient(BrokerClient client) throws JMSException {
if (transactionManager != null) {
transactionManager.cleanUpClient(client);
}
}
/**
* Gets the prepared XA transactions.
*
* @see org.codehaus.activemq.broker.Broker#getPreparedTransactions(org.codehaus.activemq.broker.BrokerClient)
*/
public ActiveMQXid[] getPreparedTransactions(BrokerClient client) throws XAException {
return transactionManager.getPreparedXATransactions();
}
// Properties
//-------------------------------------------------------------------------
/**
* Get a temp directory - used for spooling
*
* @return a File ptr to the directory
*/
public File getTempDir() {
if (tempDir == null) {
String dirName = System.getProperty("activemq.store.tempdir", "ActiveMQTemp");
tempDir = new File(dirName);
}
return tempDir;
}
public String getBrokerName() {
return brokerName;
}
public void setTempDir(File tempDir) {
this.tempDir = tempDir;
}
public MessageContainerManager[] getContainerManagers() {
if (containerManagers == null) {
containerManagers = createContainerManagers();
}
return containerManagers;
}
public void setContainerManagers(MessageContainerManager[] containerManagers) {
this.containerManagers = containerManagers;
}
public PersistenceAdapter getPersistenceAdapter() {
return persistenceAdapter;
}
public void setPersistenceAdapter(PersistenceAdapter persistenceAdapter) {
this.persistenceAdapter = persistenceAdapter;
}
public TransactionManager getTransactionManager() {
return transactionManager;
}
public void setTransactionManager(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public PreparedTransactionStore getPreparedTransactionStore() {
return preparedTransactionStore;
}
public void setPreparedTransactionStore(PreparedTransactionStore preparedTransactionStore) {
this.preparedTransactionStore = preparedTransactionStore;
}
/**
* @return Returns the maximumMemoryUsage.
*/
public long getMaximumMemoryUsage() {
return memoryManager.getValueLimit();
}
/**
* @param maximumMemoryUsage The maximumMemoryUsage to set.
*/
public void setMaximumMemoryUsage(long maximumMemoryUsage) {
this.memoryManager.setValueLimit(maximumMemoryUsage);
}
// Implementation methods
//-------------------------------------------------------------------------
/**
* Factory method to create a default persistence adapter
*
* @return
*/
protected PersistenceAdapter createPersistenceAdapter() throws JMSException {
File directory = new File(getStoreDirectory());
// lets use reflection to avoid runtime dependency on persistence libraries
PersistenceAdapter answer = null;
String property = System.getProperty(PERSISTENCE_ADAPTER_PROPERTY);
if (property != null) {
answer = tryCreatePersistenceAdapter(property, directory, false);
}
if (answer == null) {
answer = tryCreatePersistenceAdapter("org.codehaus.activemq.store.jdbm.JdbmPersistenceAdapter", directory, true);
}
if (answer == null) {
answer = tryCreatePersistenceAdapter("org.codehaus.activemq.store.bdb.BDbPersistenceAdapter", directory, true);
}
if (answer != null) {
return answer;
}
else {
log.warn("Neither JDBM or BDB on the classpath or property '" + PERSISTENCE_ADAPTER_PROPERTY
+ "' not specified so defaulting to use RAM based message persistence");
return new VMPersistenceAdapter();
}
}
protected PersistenceAdapter tryCreatePersistenceAdapter(String className, File directory, boolean ignoreErrors) throws JMSException {
Class adapterClass = loadClass(className, ignoreErrors);
if (adapterClass != null) {
try {
Method method = adapterClass.getMethod("newInstance", NEWINSTANCE_PARAMETER_TYPES);
PersistenceAdapter answer = (PersistenceAdapter) method.invoke(null, new Object[]{directory});
log.info("Using persistence adapter: " + adapterClass.getName());
return answer;
}
catch (InvocationTargetException e) {
Throwable cause = e.getTargetException();
if (cause != null) {
if (cause instanceof JMSException) {
throw (JMSException) cause;
}
else {
if (cause instanceof Exception) {
throw createInstantiateAdapterException(adapterClass, (Exception) cause);
}
}
}
if (!ignoreErrors) {
throw createInstantiateAdapterException(adapterClass, e);
}
}
catch (Throwable e) {
if (!ignoreErrors) {
throw createInstantiateAdapterException(adapterClass, e);
}
}
}
return null;
}
protected JMSException createInstantiateAdapterException(Class adapterClass, Throwable e) {
return JMSExceptionHelper.newJMSException("Could not instantiate instance of "
+ adapterClass.getName() + ". Reason: " + e, e);
}
/**
* Tries to load the given class from the current context class loader or
* class loader which loaded us or return null if the class could not be found
*/
protected Class loadClass(String name, boolean ignoreErrors) throws JMSException {
try {
return Thread.currentThread().getContextClassLoader().loadClass(name);
}
catch (ClassNotFoundException e) {
try {
return getClass().getClassLoader().loadClass(name);
}
catch (ClassNotFoundException e2) {
if (ignoreErrors) {
log.trace("Could not find class: " + name + " on the classpath");
return null;
}
else {
throw JMSExceptionHelper.newJMSException("Could not find class: " + name + " on the classpath. Reason: " + e, e);
}
}
}
}
protected String getStoreDirectory() {
return System.getProperty(PROPERTY_STORE_DIRECTORY, "ActiveMQ");
}
/**
* Factory method to create the default container managers
*
* @return
*/
protected MessageContainerManager[] createContainerManagers() {
MessageContainerManager[] answer = {
//new TransientTopicMessageContainerManager(persistenceAdapter),
new TransientTopicBoundedMessageManager(memoryManager),
new DurableTopicMessageContainerManager(persistenceAdapter),
new QueueMessageContainerManager(persistenceAdapter),
};
return answer;
}
/**
* Ensures the consumer is valid, throwing a meaningful exception if not
*
* @param info
* @throws JMSException
*/
protected void validateConsumer(ConsumerInfo info) throws JMSException {
if (info.getConsumerId() == null) {
throw new JMSException("No consumerId specified for the ConsumerInfo");
}
}
protected void checkValid() throws JMSException {
if (containerManagers == null) {
throw new JMSException("This Broker has not yet been started. Ensure start() is called before invoking action methods");
}
}
}