/**
*
* 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 EDU.oswego.cs.dl.util.concurrent.ConcurrentHashMap;
import EDU.oswego.cs.dl.util.concurrent.CopyOnWriteArrayList;
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.broker.BrokerConnector;
import org.codehaus.activemq.broker.BrokerContainer;
import org.codehaus.activemq.capacity.CapacityMonitorEvent;
import org.codehaus.activemq.capacity.CapacityMonitorEventListener;
import org.codehaus.activemq.message.ActiveMQDestination;
import org.codehaus.activemq.message.ActiveMQMessage;
import org.codehaus.activemq.message.ActiveMQXid;
import org.codehaus.activemq.message.ConnectionInfo;
import org.codehaus.activemq.message.ConsumerInfo;
import org.codehaus.activemq.message.DurableUnsubscribe;
import org.codehaus.activemq.message.MessageAck;
import org.codehaus.activemq.message.ProducerInfo;
import org.codehaus.activemq.message.SessionInfo;
import org.codehaus.activemq.service.Service;
import org.codehaus.activemq.store.PersistenceAdapter;
import javax.jms.InvalidClientIDException;
import javax.jms.InvalidDestinationException;
import javax.jms.JMSException;
import javax.jms.JMSSecurityException;
import javax.transaction.xa.XAException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* Represents the ActiveMQ JMS Broker which typically has one or many connectors
*
* @version $Revision: 1.19 $
*/
public class BrokerContainerImpl implements BrokerContainer, CapacityMonitorEventListener {
private static final Log log = LogFactory.getLog(BrokerContainerImpl.class);
private Broker broker;
private Map clientIds;
private Map consumerInfos;
private Map producerInfos;
private List connectors;
private Thread shutdownHook;
private boolean stopped;
/**
* Default Constructor
*
* @param brokerName
*/
public BrokerContainerImpl(String brokerName) {
this(new DefaultBroker(brokerName));
}
public BrokerContainerImpl(String brokerName, PersistenceAdapter persistenceAdapter) {
this(new DefaultBroker(brokerName, persistenceAdapter));
}
/**
* @param broker
*/
public BrokerContainerImpl(Broker broker) {
this.broker = broker;
this.clientIds = new ConcurrentHashMap();
this.consumerInfos = new ConcurrentHashMap();
this.producerInfos = new ConcurrentHashMap();
this.connectors = new CopyOnWriteArrayList();
this.broker.addCapacityEventListener(this);
}
public List getConnectors() {
return connectors;
}
public void setConnectors(List connectors) {
this.connectors = connectors;
}
/**
* @return the Broker for the Container
*/
public Broker getBroker() {
return broker;
}
public PersistenceAdapter getPersistenceAdapter() {
return broker != null ? broker.getPersistenceAdapter() : null;
}
public void setPersistenceAdapter(PersistenceAdapter persistenceAdapter) {
if (broker == null) {
throw new IllegalStateException("Cannot set this property as we don't have a broker yet");
}
broker.setPersistenceAdapter(persistenceAdapter);
}
/**
* start the Container
*
* @throws JMSException
*/
public void start() throws JMSException {
log.info("ActiveMQ JMS Message Broker is starting");
log.info("For help or more information please see: http://activemq.codehaus.org/");
broker.start();
addShutdownHook();
log.info("ActiveMQ JMS Message Broker has started");
}
protected void addShutdownHook() {
shutdownHook = new Thread() {
public void run() {
containerShutdown();
}
};
Runtime.getRuntime().addShutdownHook(shutdownHook);
}
/**
* Stop the Container
*
* @throws JMSException
*/
public synchronized void stop() throws JMSException {
if (!stopped) {
log.info("ActiveMQ Message Broker is shutting down");
try {
Runtime.getRuntime().removeShutdownHook(shutdownHook);
}
catch (Exception e) {
// Ignore, must be shutting down
}
JMSException firstException = null;
// lets close all the connectors - copying the collection first
// TODO we might not need to copy the collection, as maybe the List might not
// throw concurrent modification exception? Couldn't tell from the docs
// but I don't think it does
for (Iterator iter = new ArrayList(connectors).iterator(); iter.hasNext();) {
Service connector = (Service) iter.next();
try {
connector.stop();
}
catch (JMSException e) {
if (firstException == null) {
firstException = e;
}
log.warn("Could not close connector: " + connector + " due to: " + e, e);
}
}
connectors.clear();
// lets close all the channels
// note that this Map implementation does not throw concurrent modification exception
for (Iterator iter = clientIds.values().iterator(); iter.hasNext();) {
// should remove clients from parent container?
BrokerClient client = (BrokerClient) iter.next();
try {
client.stop();
}
catch (JMSException e) {
if (firstException == null) {
firstException = e;
}
log.warn("Could not close client: " + client + " due to: " + e, e);
}
}
clientIds.clear();
broker.removeCapacityEventListener(this);
broker.stop();
log.info("ActiveMQ JMS Message Broker stopped");
stopped = true;
if (firstException != null) {
throw firstException;
}
}
}
/**
* registers a new Connection
*
* @param client
* @param info infomation about the client-side Connection
* @throws InvalidClientIDException if the ClientID of the Connection is a duplicate
*/
public void registerConnection(BrokerClient client, ConnectionInfo info) throws InvalidClientIDException {
String clientId = info.getClientId();
if (clientIds.containsKey(clientId)) {
throw new InvalidClientIDException("Duplicate clientId: " + info);
}
log.info("Adding new client: " + clientId + " on transport: " + client.getChannel());
clientIds.put(clientId, client);
}
/**
* un-registers a Connection
*
* @param client
* @param info infomation about the client-side Connection
* @throws JMSException
*/
public void deregisterConnection(BrokerClient client, ConnectionInfo info) throws JMSException {
String clientId = client.getClientID();
if (clientId != null) {
Object answer = clientIds.remove(clientId);
if (answer != null) {
log.info("Removing client: " + clientId + " on transport: " + client.getChannel());
getBroker().cleanUpClient(client);
}
else {
log.warn("Got duplicate deregisterConnection for client: " + clientId);
}
}
else {
log.warn("No clientID available for client: " + client);
}
}
/**
* Registers a MessageConsumer
*
* @param client
* @param info
* @throws JMSException
* @throws JMSSecurityException if client authentication fails for the Destination the Consumer applies for
*/
public void registerMessageConsumer(BrokerClient client, ConsumerInfo info) throws JMSException {
consumerInfos.put(info, client);
getBroker().addMessageConsumer(client, info);
}
/**
* De-register a MessageConsumer from the Broker
*
* @param client
* @param info
* @throws JMSException
*/
public void deregisterMessageConsumer(BrokerClient client, ConsumerInfo info) throws JMSException {
consumerInfos.remove(info);
getBroker().removeMessageConsumer(client, info);
}
/**
* Registers a MessageProducer
*
* @param client
* @param info
* @throws JMSException
* @throws JMSSecurityException if client authentication fails for the Destination the Consumer applies for
*/
public void registerMessageProducer(BrokerClient client, ProducerInfo info) throws JMSException {
ActiveMQDestination dest = info.getDestination();
if (dest != null && dest.isTemporary()) {
//check to see if the client that is the target for the temporary destination still exists
String clientId = ActiveMQDestination.getClientId(dest);
if (clientId == null) {
throw new InvalidDestinationException("Destination " + dest.getPhysicalName()
+ " is a temporary destination with null clientId");
}
if (!clientIds.containsKey(clientId)) {
throw new InvalidDestinationException("Destination " + dest.getPhysicalName()
+ " is no longer valid because the client " + clientId + " no longer exists");
}
}
producerInfos.put(info, client);
}
/**
* De-register a MessageProducer from the Broker
*
* @param client
* @param info
* @throws JMSException
*/
public void deregisterMessageProducer(BrokerClient client, ProducerInfo info) throws JMSException {
producerInfos.remove(info);
}
/**
* Register a client-side Session (used for Monitoring)
*
* @param client
* @param info
* @throws JMSException
*/
public void registerSession(BrokerClient client, SessionInfo info) throws JMSException {
}
/**
* De-register a client-side Session from the Broker (used for monitoring)
*
* @param client
* @param info
* @throws JMSException
*/
public void deregisterSession(BrokerClient client, SessionInfo info) throws JMSException {
}
/**
* Start a transaction from the Client session
*
* @param client
* @param transactionId
* @throws JMSException
*/
public void startTransaction(BrokerClient client, String transactionId) throws JMSException {
getBroker().startTransaction(client, transactionId);
}
/**
* Rollback a transacton
*
* @param client
* @param transactionId
* @throws JMSException
*/
public void rollbackTransaction(BrokerClient client, String transactionId) throws JMSException {
getBroker().rollbackTransaction(client, transactionId);
}
/**
* Commit a transaction
*
* @param client
* @param transactionId
* @throws JMSException
*/
public void commitTransaction(BrokerClient client, String transactionId) throws JMSException {
getBroker().commitTransaction(client, transactionId);
}
/**
* send message with a transaction context
*
* @param client
* @param transactionId
* @param message
* @throws JMSException
*/
public void sendTransactedMessage(BrokerClient client, String transactionId, ActiveMQMessage message)
throws JMSException {
getBroker().sendTransactedMessage(client, transactionId, message);
}
/**
* Acknowledge receipt of a message within a transaction context
*
* @param client
* @param transactionId
* @param ack
* @throws JMSException
*/
public void acknowledgeTransactedMessage(BrokerClient client, String transactionId, MessageAck ack)
throws JMSException {
getBroker().acknowledgeTransactedMessage(client, transactionId, ack);
}
/**
* Send a non-transacted message to the Broker
*
* @param client
* @param message
* @throws JMSException
*/
public void sendMessage(BrokerClient client, ActiveMQMessage message) throws JMSException {
getBroker().sendMessage(client, message);
}
/**
* Acknowledge reciept of a message
*
* @param client
* @param ack
* @throws JMSException
*/
public void acknowledgeMessage(BrokerClient client, MessageAck ack) throws JMSException {
getBroker().acknowledgeMessage(client, ack);
}
/**
* Command to delete a durable topic subscription
*
* @param client
* @param ds
* @throws JMSException
*/
public void durableUnsubscribe(BrokerClient client, DurableUnsubscribe ds) throws JMSException {
getBroker().deleteSubscription(ds.getClientId(), ds.getSubscriberName());
}
/**
* Start an XA transaction.
*
* @param client
* @param xid
*/
public void startTransaction(BrokerClient client, ActiveMQXid xid) throws XAException {
getBroker().startTransaction(client, xid);
}
/**
* Gets the prepared XA transactions.
*
* @param client
* @return
*/
public ActiveMQXid[] getPreparedTransactions(BrokerClient client) throws XAException {
return getBroker().getPreparedTransactions(client);
}
/**
* Prepare an XA transaction.
*
* @param client
* @param xid
*/
public int prepareTransaction(BrokerClient client, ActiveMQXid xid) throws XAException {
return getBroker().prepareTransaction(client, xid);
}
/**
* Rollback an XA transaction.
*
* @param client
* @param xid
*/
public void rollbackTransaction(BrokerClient client, ActiveMQXid xid) throws XAException {
getBroker().rollbackTransaction(client, xid);
}
/**
* Commit an XA transaction.
*
* @param client
* @param xid
* @param onePhase
*/
public void commitTransaction(BrokerClient client, ActiveMQXid xid, boolean onePhase) throws XAException {
getBroker().commitTransaction(client, xid, onePhase);
}
public void addConnector(BrokerConnector connector) {
connectors.add(connector);
}
public void removeConnector(BrokerConnector connector) {
connectors.remove(connector);
}
/**
* Update any message producers about our capacity to handle messages
*
* @param event
*/
public void capacityChanged(CapacityMonitorEvent event) {
//only send to producers
for (Iterator i = producerInfos.values().iterator(); i.hasNext();) {
BrokerClient client = (BrokerClient) i.next();
client.updateBrokerCapacity(event.getCapacity());
}
}
/**
* Causes a clean shutdown of the container when the VM is being shut down
*/
protected void containerShutdown() {
try {
stop();
}
catch (JMSException e) {
Exception linkedException = e.getLinkedException();
if (linkedException != null) {
log.error("Failed to shut down: " + e + ". Reason: " + linkedException, linkedException);
}
else {
log.error("Failed to shut down: " + e, e);
}
}
catch (Exception e) {
log.error("Failed to shut down: " + e, e);
}
}
}