/**
*
* 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.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
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.BrokerClient;
import org.codehaus.activemq.broker.BrokerConnector;
import org.codehaus.activemq.broker.BrokerContainer;
import org.codehaus.activemq.io.WireFormat;
import org.codehaus.activemq.message.ActiveMQMessage;
import org.codehaus.activemq.message.ActiveMQXid;
import org.codehaus.activemq.message.BrokerInfo;
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.transport.TransportChannel;
import org.codehaus.activemq.transport.TransportChannelListener;
import org.codehaus.activemq.transport.TransportServerChannel;
import org.codehaus.activemq.transport.TransportServerChannelProvider;
/**
* An implementation of the broker (the JMS server)
*
* @version $Revision: 1.3 $
*/
public class BrokerConnectorImpl implements BrokerConnector, TransportChannelListener {
private BrokerInfo brokerInfo;
private TransportServerChannel serverChannel;
private Log log;
private BrokerContainer container;
private Map clients = Collections.synchronizedMap(new HashMap());
/**
* Helper constructor for TCP protocol with the given bind address
*
* @param container
* @param bindAddress
* @throws JMSException
*/
public BrokerConnectorImpl(BrokerContainer container, String bindAddress, WireFormat wireFormat) throws JMSException {
this(container, createTransportServerChannel(wireFormat, bindAddress));
}
/**
* @param container
* @param serverChannel
*/
public BrokerConnectorImpl(BrokerContainer container, TransportServerChannel serverChannel) {
assert container != null;
this.brokerInfo = new BrokerInfo();
this.brokerInfo.setBrokerName(container.getBroker().getBrokerName());
this.brokerInfo.setClusterName(container.getBroker().getBrokerClusterName());
this.log = LogFactory.getLog(getClass().getName());
this.serverChannel = serverChannel;
this.container = container;
this.container.addConnector(this);
serverChannel.setTransportChannelListener(this);
}
/**
* @return infomation about the Broker
*/
public BrokerInfo getBrokerInfo() {
return brokerInfo;
}
/**
* Get a hint about the broker capacity for more messages
*
* @return percentage value (0-100) about how much capacity the
* broker has
*/
public int getBrokerCapacity() {
return container.getBroker().getRoundedCapacity();
}
/**
* @return Get the server channel
*/
public TransportServerChannel getServerChannel() {
return serverChannel;
}
/**
* start the Broker
*
* @throws JMSException
*/
public void start() throws JMSException {
this.serverChannel.start();
log.info("ActiveMQ connector started: " + serverChannel);
}
/**
* Stop the Broker
*
* @throws JMSException
*/
public void stop() throws JMSException {
this.container.removeConnector(this);
this.serverChannel.stop();
log.info("ActiveMQ connector stopped: " + serverChannel);
}
/**
* Register a Broker Client
*
* @param client
* @param info contains infomation about the Connection this Client represents
* @throws JMSException
* @throws javax.jms.InvalidClientIDException
* if the JMS client specifies an invalid or duplicate client ID.
* @throws JMSSecurityException if client authentication fails due to an invalid user name or password.
*/
public void registerClient(BrokerClient client, ConnectionInfo info) throws JMSException {
this.container.registerConnection(client, info);
}
/**
* Deregister a Broker Client
*
* @param client
* @param info
* @throws JMSException if some internal error occurs
*/
public void deregisterClient(BrokerClient client, ConnectionInfo info) throws JMSException {
this.container.deregisterConnection(client, info);
}
/**
* 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 {
if (info.getDestination() == null) {
throw new JMSException("No Destination specified on consumerInfo for client: " + client + " info: " + info);
}
this.container.registerMessageConsumer(client, info);
}
/**
* De-register a MessageConsumer from the Broker
*
* @param client
* @param info
* @throws JMSException
*/
public void deregisterMessageConsumer(BrokerClient client, ConsumerInfo info) throws JMSException {
this.container.deregisterMessageConsumer(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 {
this.container.registerMessageProducer(client, info);
}
/**
* De-register a MessageProducer from the Broker
*
* @param client
* @param info
* @throws JMSException
*/
public void deregisterMessageProducer(BrokerClient client, ProducerInfo info) throws JMSException {
this.container.deregisterMessageProducer(client, info);
}
/**
* Register a client-side Session (used for Monitoring)
*
* @param client
* @param info
* @throws JMSException
*/
public void registerSession(BrokerClient client, SessionInfo info) throws JMSException {
this.container.registerSession(client, info);
}
/**
* 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 {
this.container.deregisterSession(client, info);
}
/**
* Start a transaction from the Client session
*
* @param client
* @param transactionId
* @throws JMSException
*/
public void startTransaction(BrokerClient client, String transactionId) throws JMSException {
this.container.startTransaction(client, transactionId);
}
/**
* Rollback a transacton
*
* @param client
* @param transactionId
* @throws JMSException
*/
public void rollbackTransaction(BrokerClient client, String transactionId) throws JMSException {
this.container.rollbackTransaction(client, transactionId);
}
/**
* Commit a transaction
*
* @param client
* @param transactionId
* @throws JMSException
*/
public void commitTransaction(BrokerClient client, String transactionId) throws JMSException {
this.container.commitTransaction(client, transactionId);
}
/**
* Send a non-transacted message to the Broker
*
* @param client
* @param message
* @throws JMSException
*/
public void sendMessage(BrokerClient client, ActiveMQMessage message) throws JMSException {
this.container.sendMessage(client, message);
}
/**
* Acknowledge reciept of a message
*
* @param client
* @param ack
* @throws JMSException
*/
public void acknowledgeMessage(BrokerClient client, MessageAck ack) throws JMSException {
this.container.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 {
this.container.durableUnsubscribe(client, ds);
}
/**
* @param channel - client to add
*/
public void addClient(TransportChannel channel) {
try {
BrokerClient client = new BrokerClientImpl();
client.initialize(this, channel);
if (log.isDebugEnabled()) {
log.debug("Starting new client: " + client);
}
channel.setServerSide(true);
channel.start();
clients.put(channel, client);
}
catch (JMSException e) {
log.error("Failed to add client due to: " + e, e);
}
}
/**
* @param channel - client to remove
*/
public void removeClient(TransportChannel channel) {
BrokerClient client = (BrokerClient) clients.remove(channel);
if (client != null) {
if (log.isDebugEnabled()) {
log.debug("Client leaving client: " + client);
}
// we may have already been closed, if not then lets simulate a normal shutdown
client.cleanUp();
}
else {
// might have got a duplicate callback
log.warn("No such client for channel: " + channel);
}
}
/**
* @return the BrokerContainer for this Connector
*/
public BrokerContainer getBrokerContainer() {
return this.container;
}
/**
* Start an XA transaction.
*
* @see org.codehaus.activemq.broker.BrokerConnector#startTransaction(org.codehaus.activemq.broker.BrokerClient, org.codehaus.activemq.message.ActiveMQXid)
*/
public void startTransaction(BrokerClient client, ActiveMQXid xid) throws XAException {
this.container.startTransaction(client, xid);
}
/**
* Gets the prepared XA transactions.
*
* @see org.codehaus.activemq.broker.BrokerConnector#getPreparedTransactions(org.codehaus.activemq.broker.BrokerClient)
*/
public ActiveMQXid[] getPreparedTransactions(BrokerClient client) throws XAException {
return this.container.getPreparedTransactions(client);
}
/**
* Prepare an XA transaction.
*
* @see org.codehaus.activemq.broker.BrokerConnector#prepareTransaction(org.codehaus.activemq.broker.BrokerClient, org.codehaus.activemq.message.ActiveMQXid)
*/
public int prepareTransaction(BrokerClient client, ActiveMQXid xid) throws XAException {
return this.container.prepareTransaction(client, xid);
}
/**
* Rollback an XA transaction.
*
* @see org.codehaus.activemq.broker.BrokerConnector#rollbackTransaction(org.codehaus.activemq.broker.BrokerClient, org.codehaus.activemq.message.ActiveMQXid)
*/
public void rollbackTransaction(BrokerClient client, ActiveMQXid xid) throws XAException {
this.container.rollbackTransaction(client, xid);
}
/**
* Commit an XA transaction.
*
* @see org.codehaus.activemq.broker.BrokerConnector#commitTransaction(org.codehaus.activemq.broker.BrokerClient, org.codehaus.activemq.message.ActiveMQXid, boolean)
*/
public void commitTransaction(BrokerClient client, ActiveMQXid xid, boolean onePhase) throws XAException {
this.container.commitTransaction(client, xid, onePhase);
}
/**
* @see org.codehaus.activemq.broker.BrokerConnector#getResourceManagerId(org.codehaus.activemq.broker.BrokerClient)
*/
public String getResourceManagerId(BrokerClient client) {
// TODO: I think we need to return a better (more unique) RM id.
return getBrokerInfo().getBrokerName();
}
// Implementation methods
//-------------------------------------------------------------------------
/**
* Factory method ot create a transport channel
*
* @param bindAddress
* @return @throws JMSException
* @throws JMSException
*/
protected static TransportServerChannel createTransportServerChannel(WireFormat wireFormat, String bindAddress) throws JMSException {
URI url;
try {
url = new URI(bindAddress);
}
catch (URISyntaxException e) {
JMSException jmsEx = new JMSException("Badly formated bindAddress: " + e.getMessage());
jmsEx.setLinkedException(e);
throw jmsEx;
}
return TransportServerChannelProvider.create(wireFormat, url);
}
}