/**
*
* 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 java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.jms.InvalidClientIDException;
import javax.jms.InvalidDestinationException;
import javax.jms.JMSException;
import javax.jms.JMSSecurityException;
import javax.transaction.xa.XAException;
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.broker.BrokerContext;
import org.codehaus.activemq.capacity.CapacityMonitorEvent;
import org.codehaus.activemq.capacity.CapacityMonitorEventListener;
import org.codehaus.activemq.io.WireFormat;
import org.codehaus.activemq.io.impl.DefaultWireFormat;
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.security.SecurityAdapter;
import org.codehaus.activemq.service.RedeliveryPolicy;
import org.codehaus.activemq.service.Service;
import org.codehaus.activemq.store.PersistenceAdapter;
import org.codehaus.activemq.transport.DiscoveryAgent;
import org.codehaus.activemq.transport.NetworkConnector;
import org.codehaus.activemq.transport.TransportServerChannel;
import org.codehaus.activemq.util.IdGenerator;
import EDU.oswego.cs.dl.util.concurrent.ConcurrentHashMap;
import EDU.oswego.cs.dl.util.concurrent.CopyOnWriteArrayList;
/**
* Represents the ActiveMQ JMS Broker which typically has one or many connectors
*
* @version $Revision: 1.5 $
*/
public class BrokerContainerImpl implements BrokerContainer, CapacityMonitorEventListener {
private static final Log log = LogFactory.getLog(BrokerContainerImpl.class);
private static final boolean useLoggingForShutdownErrors = false;
private BrokerContext context;
private Broker broker;
private Map clientIds;
private Map consumerInfos;
private Map producerInfos;
private List transportConnectors;
private Thread shutdownHook;
private boolean stopped;
private List networkConnectors;
private DiscoveryAgent discoveryAgent;
private Map localDiscoveryDetails;
public BrokerContainerImpl() {
this(new IdGenerator().generateId());
}
public BrokerContainerImpl(String brokerName) {
this(brokerName, BrokerContext.getInstance());
}
public BrokerContainerImpl(String brokerName,String clusterName) {
this(brokerName, clusterName,BrokerContext.getInstance());
}
public BrokerContainerImpl(String brokerName, PersistenceAdapter persistenceAdapter) {
this(brokerName, persistenceAdapter, BrokerContext.getInstance());
}
public BrokerContainerImpl(String brokerName, BrokerContext context) {
this(new DefaultBroker(brokerName), context);
}
public BrokerContainerImpl(String brokerName, String clusterName, BrokerContext context) {
this(new DefaultBroker(brokerName,clusterName), context);
}
public BrokerContainerImpl(String brokerName, PersistenceAdapter persistenceAdapter, BrokerContext context) {
this(new DefaultBroker(brokerName, persistenceAdapter), context);
}
public BrokerContainerImpl(String brokerName,String clusterName, PersistenceAdapter persistenceAdapter, BrokerContext context) {
this(new DefaultBroker(brokerName,clusterName, persistenceAdapter), context);
}
/**
* @param broker
*/
public BrokerContainerImpl(Broker broker, BrokerContext context) {
this.broker = broker;
this.context = context;
this.clientIds = new ConcurrentHashMap();
this.consumerInfos = new ConcurrentHashMap();
this.producerInfos = new ConcurrentHashMap();
this.transportConnectors = new CopyOnWriteArrayList();
this.networkConnectors = new CopyOnWriteArrayList();
this.broker.addCapacityEventListener(this);
// lets register ourselves with the context
context.registerContainer(broker.getBrokerName(), this);
}
/**
* start the Container
*
* @throws JMSException
*/
public void start() throws JMSException {
log.info("ActiveMQ JMS Message Broker (" + broker.getBrokerName() + ") is starting");
log.info("For help or more information please see: www.protique.com");
broker.start();
addShutdownHook();
// TODO we might not need to copy the collections, 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(networkConnectors).iterator(); iter.hasNext();) {
Service connector = (Service) iter.next();
connector.start();
}
for (Iterator iter = new ArrayList(transportConnectors).iterator(); iter.hasNext();) {
Service connector = (Service) iter.next();
connector.start();
}
if (discoveryAgent != null) {
discoveryAgent.start();
localDiscoveryDetails = createLocalDiscoveryDetails();
discoveryAgent.registerService(getLocalBrokerName(), localDiscoveryDetails);
}
log.info("ActiveMQ JMS Message Broker (" + broker.getBrokerName() + ") has started");
}
/**
* Stop the Container
*
* @throws JMSException
*/
public synchronized void stop() throws JMSException {
if (!stopped) {
stopped = true;
log.info("ActiveMQ Message Broker (" + broker.getBrokerName() + ") is shutting down");
context.deregisterContainer(broker.getBrokerName(), this);
try {
Runtime.getRuntime().removeShutdownHook(shutdownHook);
}
catch (Exception e) {
log.debug("Caught exception, must be shutting down: " + e);
}
JMSException firstException = null;
for (Iterator iter = new ArrayList(transportConnectors).iterator(); iter.hasNext();) {
Service connector = (Service) iter.next();
try {
connector.stop();
}
catch (JMSException e) {
if (firstException == null) {
firstException = e;
}
log.warn("Could not close transport connector: " + connector + " due to: " + e, e);
}
}
transportConnectors.clear();
for (Iterator iter = new ArrayList(networkConnectors).iterator(); iter.hasNext();) {
Service connector = (Service) iter.next();
try {
connector.stop();
}
catch (JMSException e) {
if (firstException == null) {
firstException = e;
}
log.warn("Could not close network connector: " + connector + " due to: " + e, e);
}
}
networkConnectors.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 (" + broker.getBrokerName() + ") stopped");
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 JMSException {
String clientId = info.getClientId();
if (clientIds.containsKey(clientId)) {
throw new InvalidClientIDException("Duplicate clientId: " + info);
}
getBroker().addClient(client, 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().removeClient(client, info);
}
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();
checkTempDestinationExistance(dest);
getBroker().addMessageProducer(client, info);
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 {
getBroker().removeMessageProducer(client, info);
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 {
ActiveMQDestination dest = message.getJMSActiveMQDestination();
checkTempDestinationExistance(dest);
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 {
ActiveMQDestination dest = message.getJMSActiveMQDestination();
checkTempDestinationExistance(dest);
getBroker().sendMessage(client, message);
}
/**
* @param dest
* @throws InvalidDestinationException
*/
private void checkTempDestinationExistance(ActiveMQDestination dest) throws InvalidDestinationException {
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");
}
}
}
/**
* 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);
}
/**
* 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());
}
}
// Properties
//-------------------------------------------------------------------------
public List getTransportConnectors() {
return transportConnectors;
}
public void setTransportConnectors(List transportConnectors) {
this.transportConnectors = transportConnectors;
}
public void addConnector(BrokerConnector connector) {
transportConnectors.add(connector);
context.registerConnector(connector.getServerChannel().getUrl(), connector);
}
public void removeConnector(BrokerConnector connector) {
transportConnectors.remove(connector);
context.deregisterConnector(connector.getServerChannel().getUrl());
}
public void addConnector(String bindAddress) throws JMSException {
addConnector(bindAddress, new DefaultWireFormat());
}
public void addConnector(String bindAddress, WireFormat wireFormat) throws JMSException {
addConnector(new BrokerConnectorImpl(this, bindAddress, wireFormat));
}
public void addConnector(TransportServerChannel transportConnector) {
addConnector(new BrokerConnectorImpl(this, transportConnector));
}
public List getNetworkConnectors() {
return networkConnectors;
}
public void setNetworkConnectors(List networkConnectors) {
this.networkConnectors = networkConnectors;
}
public NetworkConnector addNetworkConnector(String uri) {
NetworkConnector connector = addNetworkConnector();
connector.addNetworkChannel(uri);
return connector;
}
public NetworkConnector addNetworkConnector() {
NetworkConnector connector = new NetworkConnector(this);
addNetworkConnector(connector);
return connector;
}
public void addNetworkConnector(NetworkConnector connector) {
networkConnectors.add(connector);
}
public void removeNetworkConnector(NetworkConnector connector) {
networkConnectors.remove(connector);
}
public Broker getBroker() {
return broker;
}
public PersistenceAdapter getPersistenceAdapter() {
return broker != null ? broker.getPersistenceAdapter() : null;
}
public void setPersistenceAdapter(PersistenceAdapter persistenceAdapter) {
checkBrokerSet();
broker.setPersistenceAdapter(persistenceAdapter);
}
public DiscoveryAgent getDiscoveryAgent() {
return discoveryAgent;
}
public void setDiscoveryAgent(DiscoveryAgent discoveryAgent) {
this.discoveryAgent = discoveryAgent;
}
public SecurityAdapter getSecurityAdapter() {
return broker != null ? broker.getSecurityAdapter() : null;
}
public void setSecurityAdapter(SecurityAdapter securityAdapter) {
checkBrokerSet();
broker.setSecurityAdapter(securityAdapter);
}
public RedeliveryPolicy getRedeliveryPolicy() {
return broker != null ? broker.getRedeliveryPolicy() : null;
}
public void setRedeliveryPolicy(RedeliveryPolicy redeliveryPolicy) {
checkBrokerSet();
broker.setRedeliveryPolicy(redeliveryPolicy);
}
// Implementation methods
//-------------------------------------------------------------------------
protected void checkBrokerSet() {
if (broker == null) {
throw new IllegalStateException("Cannot set this property as we don't have a broker yet");
}
}
protected Map createLocalDiscoveryDetails() {
Map map = new HashMap();
map.put("brokerName", getLocalBrokerName());
map.put("connectURL", getLocalConnectionURL());
return map;
}
protected String getLocalBrokerName() {
return getBroker().getBrokerName();
}
protected String getLocalConnectionURL() {
StringBuffer buffer = new StringBuffer("reliable:");
List list = getTransportConnectors();
boolean first = true;
for (Iterator iter = list.iterator(); iter.hasNext();) {
BrokerConnector brokerConnector = (BrokerConnector) iter.next();
TransportServerChannel connector = brokerConnector.getServerChannel();
String url = connector.getUrl();
if (first) {
first = false;
}
else {
buffer.append(",");
}
buffer.append(url);
}
return buffer.toString();
}
protected void addShutdownHook() {
shutdownHook = new Thread("ActiveMQ ShutdownHook") {
public void run() {
containerShutdown();
}
};
Runtime.getRuntime().addShutdownHook(shutdownHook);
}
/**
* 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) {
if (useLoggingForShutdownErrors) {
log.error("Failed to shut down: " + e + ". Reason: " + linkedException, linkedException);
}
else {
System.err.println("Failed to shut down: " + e + ". Reason: " + linkedException);
}
}
else {
if (useLoggingForShutdownErrors) {
log.error("Failed to shut down: " + e);
}
else {
System.err.println("Failed to shut down: " + e);
}
}
if (!useLoggingForShutdownErrors) {
e.printStackTrace(System.err);
}
}
catch (Exception e) {
if (useLoggingForShutdownErrors) {
log.error("Failed to shut down: " + e, e);
}
else {
System.err.println("Failed to shut down: " + e);
e.printStackTrace(System.err);
}
}
}
}