/**
*
* 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.activemq.transport;
import java.util.Iterator;
import java.util.Map;
import javax.jms.JMSException;
import javax.jms.Session;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.activemq.ActiveMQConnection;
import org.activemq.ActiveMQConnectionFactory;
import org.activemq.ActiveMQPrefetchPolicy;
import org.activemq.advisories.ConnectionAdvisor;
import org.activemq.advisories.ConnectionAdvisoryEvent;
import org.activemq.advisories.ConnectionAdvisoryEventListener;
import org.activemq.broker.BrokerClient;
import org.activemq.broker.BrokerContainer;
import org.activemq.broker.ConsumerInfoListener;
import org.activemq.message.ActiveMQDestination;
import org.activemq.message.BrokerInfo;
import org.activemq.message.ConsumerInfo;
import org.activemq.message.Receipt;
import org.activemq.service.MessageContainerManager;
import org.activemq.service.Service;
import org.activemq.transport.composite.CompositeTransportChannel;
import EDU.oswego.cs.dl.util.concurrent.ConcurrentHashMap;
import EDU.oswego.cs.dl.util.concurrent.PooledExecutor;
import EDU.oswego.cs.dl.util.concurrent.SynchronizedBoolean;
/**
* Represents a broker's connection with a single remote broker which bridges the two brokers to form a network. <p/>
* The NetworkChannel contains a JMS connection with the remote broker. <p/>New subscriptions on the local broker are
* multiplexed into the JMS connection so that messages published on the remote broker can be replayed onto the local
* broker.
*
* @version $Revision: 1.1.1.1 $
*/
public class NetworkChannel
implements
Service,
ConsumerInfoListener,
ConnectionAdvisoryEventListener,
TransportStatusEventListener {
private static final Log log = LogFactory.getLog(NetworkChannel.class);
protected String uri;
protected BrokerContainer brokerContainer;
protected ActiveMQConnection localConnection;
protected ActiveMQConnection remoteConnection;
protected ConcurrentHashMap topicConsumerMap;
protected ConcurrentHashMap queueConsumerMap;
protected String remoteUserName;
protected String remotePassword;
protected String remoteBrokerName;
protected String remoteClusterName;
protected int maximumRetries = 0;
protected long reconnectSleepTime = 2000L;
protected PooledExecutor threadPool;
private boolean remote = false;
private SynchronizedBoolean started = new SynchronizedBoolean(false);
private SynchronizedBoolean connected = new SynchronizedBoolean(false);
private SynchronizedBoolean stopped = new SynchronizedBoolean(false);
private ConnectionAdvisor connectionAdvisor;
private ActiveMQPrefetchPolicy localPrefetchPolicy;
private ActiveMQPrefetchPolicy remotePrefetchPolicy;
/**
* Default constructor
*/
public NetworkChannel() {
this.topicConsumerMap = new ConcurrentHashMap();
this.queueConsumerMap = new ConcurrentHashMap();
}
/**
* Default Constructor
*
* @param tp
*/
public NetworkChannel(PooledExecutor tp) {
this();
this.threadPool = tp;
}
/**
* Constructor
*
* @param connector
* @param brokerContainer
* @param uri
*/
public NetworkChannel(NetworkConnector connector, BrokerContainer brokerContainer, String uri) {
this(connector.threadPool);
this.brokerContainer = brokerContainer;
this.uri = uri;
}
/**
* Create a NetworkConnector from a TransportChannel
*
* @param connector
* @param brokerContainer
* @param channel
* @param remoteBrokerName
* @param remoteclusterName
* @throws JMSException
*/
public NetworkChannel(NetworkConnector connector, BrokerContainer brokerContainer, TransportChannel channel,
String remoteBrokerName, String remoteclusterName) throws JMSException {
this(connector.threadPool);
this.brokerContainer = brokerContainer;
this.uri = "";
this.remoteBrokerName = remoteBrokerName;
this.remoteClusterName = remoteclusterName;
ActiveMQConnectionFactory fac = new ActiveMQConnectionFactory();
fac.setJ2EEcompliant(false);
fac.setTurboBoost(true);
remoteConnection = new ActiveMQConnection(fac, remoteUserName, remotePassword, channel);
remoteConnection.setClientID("Boondocks:" + remoteClusterName + ":" + remoteBrokerName);
remoteConnection.setQuickClose(true);
remoteConnection.start();
BrokerInfo info = new BrokerInfo();
info.setBrokerName(brokerContainer.getBroker().getBrokerName());
info.setClusterName(brokerContainer.getBroker().getBrokerClusterName());
channel.asyncSend(info);
remote = true;
}
/**
* @see org.activemq.transport.TransportStatusEventListener#statusChanged(org.activemq.transport.TransportStatusEvent)
*/
public void statusChanged(TransportStatusEvent event) {
if (event != null
&& (event.getChannelStatus() == TransportStatusEvent.CONNECTED
|| event.getChannelStatus() == TransportStatusEvent.RECONNECTED)) {
connected.set(true);
}else {
connected.set(false);
}
}
private void doSetConnected() {
synchronized (connected) {
connected.set(true);
connected.notifyAll();
}
}
/**
* @return text info on this
*/
public String toString() {
return "NetworkChannel{ " + ", uri = '" + uri + "' " + ", remoteBrokerName = '" + remoteBrokerName + "' "
+ " }";
}
/**
* Start the channel
*/
public void start() {
if (started.commit(false, true)) {
try {
stopped.set(false);
threadPool.execute(new Runnable() {
public void run() {
String originalName = Thread.currentThread().getName();
try {
Thread.currentThread().setName("NetworkChannel Initiator to " + uri);
initialize();
startSubscriptions();
log.info("Started NetworkChannel to " + uri);
}
catch (JMSException jmsEx) {
log.error("Failed to start NetworkChannel: " + uri, jmsEx);
}
finally {
Thread.currentThread().setName(originalName);
}
}
});
}
catch (InterruptedException e) {
log.warn("Failed to start - interuppted", e);
}
}
}
/**
* stop the channel
*
* @throws JMSException on error
*/
public void stop() throws JMSException {
if (started.commit(true, false)) {
stopped.set(true);
topicConsumerMap.clear();
if (remoteConnection != null) {
remoteConnection.close();
remoteConnection = null;
}
if (localConnection != null) {
localConnection.close();
localConnection = null;
}
for (Iterator i = topicConsumerMap.values().iterator();i.hasNext();) {
NetworkMessageBridge consumer = (NetworkMessageBridge) i.next();
consumer.stop();
}
}
}
/**
* Listen for new Consumer events at this broker
*
* @param client
* @param info
*/
public void onConsumerInfo(final BrokerClient client, final ConsumerInfo info) {
String brokerName = client.getBrokerConnector().getBrokerInfo().getBrokerName();
if (!client.isClusteredConnection()) {
if (connected.get()) {
if (!info.hasVisited(remoteBrokerName)) {
if (info.isStarted()) {
addConsumerInfo(info);
}
else {
removeConsumerInfo(info);
}
}
}
else {
try {
threadPool.execute(new Runnable() {
public void run() {
if (!client.isClusteredConnection()) {
if (!info.hasVisited(remoteBrokerName)) {
synchronized (connected) {
while (!connected.get() && !stopped.get()) {
try {
connected.wait(500);
}
catch (InterruptedException e) {
log.debug("interuppted", e);
}
}
if (info.isStarted()) {
addConsumerInfo(info);
}
else {
removeConsumerInfo(info);
}
}
}
}
}
});
}
catch (InterruptedException e) {
log.warn("Failed to process ConsumerInfo: " + info, e);
}
}
}
}
/**
* @return the uri of the broker(s) this channel is connected to
*/
public String getUri() {
return uri;
}
/**
* set the uri of the broker(s) this channel is connected to
*
* @param uri
*/
public void setUri(String uri) {
this.uri = uri;
}
/**
* @return Returns the remotePassword.
*/
public String getRemotePassword() {
return remotePassword;
}
/**
* @param remotePassword The remotePassword to set.
*/
public void setRemotePassword(String remotePassword) {
this.remotePassword = remotePassword;
}
/**
* @return Returns the remoteUserName.
*/
public String getRemoteUserName() {
return remoteUserName;
}
/**
* @param remoteUserName The remoteUserName to set.
*/
public void setRemoteUserName(String remoteUserName) {
this.remoteUserName = remoteUserName;
}
/**
* @return Returns the brokerContainer.
*/
public BrokerContainer getBrokerContainer() {
return brokerContainer;
}
/**
* @param brokerContainer The brokerContainer to set.
*/
public void setBrokerContainer(BrokerContainer brokerContainer) {
this.brokerContainer = brokerContainer;
}
public int getMaximumRetries() {
return maximumRetries;
}
public void setMaximumRetries(int maximumRetries) {
this.maximumRetries = maximumRetries;
}
public long getReconnectSleepTime() {
return reconnectSleepTime;
}
public void setReconnectSleepTime(long reconnectSleepTime) {
this.reconnectSleepTime = reconnectSleepTime;
}
public String getRemoteBrokerName() {
return remoteBrokerName;
}
public void setRemoteBrokerName(String remoteBrokerName) {
this.remoteBrokerName = remoteBrokerName;
}
/**
* @return Returns the threadPool.
*/
protected PooledExecutor getThreadPool() {
return threadPool;
}
/**
* @param threadPool The threadPool to set.
*/
protected void setThreadPool(PooledExecutor threadPool) {
this.threadPool = threadPool;
}
private synchronized ActiveMQConnection getLocalConnection() throws JMSException {
if (localConnection == null) {
initializeLocal();
}
return localConnection;
}
private synchronized ActiveMQConnection getRemoteConnection() throws JMSException {
if (remoteConnection == null) {
initializeRemote();
}
return remoteConnection;
}
/**
* @return Returns the localPrefetchPolicy.
*/
public ActiveMQPrefetchPolicy getLocalPrefetchPolicy() {
return localPrefetchPolicy;
}
/**
* @param localPrefetchPolicy The localPrefetchPolicy to set.
*/
public void setLocalPrefetchPolicy(ActiveMQPrefetchPolicy localPrefetchPolicy) {
this.localPrefetchPolicy = localPrefetchPolicy;
}
/**
* @return Returns the remotePrefetchPolicy.
*/
public ActiveMQPrefetchPolicy getRemotePrefetchPolicy() {
return remotePrefetchPolicy;
}
/**
* @param remotePrefetchPolicy The remotePrefetchPolicy to set.
*/
public void setRemotePrefetchPolicy(ActiveMQPrefetchPolicy remotePrefetchPolicy) {
this.remotePrefetchPolicy = remotePrefetchPolicy;
}
// Implementation methods
//-------------------------------------------------------------------------
/**
* Implementation of ConnectionAdvisoryEventListener
*
* @param event
*/
public void onEvent(ConnectionAdvisoryEvent event) {
String localBrokerName = brokerContainer.getBroker().getBrokerName();
if (!event.getInfo().isClosed()) {
brokerContainer.registerRemoteClientID(event.getInfo().getClientId());
}
else {
brokerContainer.deregisterRemoteClientID(event.getInfo().getClientId());
}
}
private void addConsumerInfo(ConsumerInfo info) {
addConsumerInfo(info.getDestination(), info.getDestination().isTopic(), info.isDurableTopic());
}
private void addConsumerInfo(ActiveMQDestination destination, boolean topic, boolean durableTopic) {
Map map = topic ? topicConsumerMap : queueConsumerMap;
NetworkMessageBridge bridge = (NetworkMessageBridge) map.get(destination.getPhysicalName());
if (bridge == null) {
bridge = createBridge(map, destination, durableTopic);
}
else if (durableTopic && !bridge.isDurableTopic()) {
//upgrade our subscription
bridge.decrementReferenceCount();
upgradeBridge(bridge);
}
bridge.incrementReferenceCount();
}
private void upgradeBridge(NetworkMessageBridge bridge) {
try {
remoteConnection.stop();
bridge.upgrade();
}
catch (JMSException e) {
log.warn("Could not upgrade the NetworkMessageBridge to a durable subscription for destination: "
+ bridge.getDestination(), e);
}
try {
remoteConnection.start();
}
catch (JMSException e) {
log.error("Failed to restart the NetworkMessageBridge", e);
}
}
private NetworkMessageBridge createBridge(Map map, ActiveMQDestination destination, boolean durableTopic) {
NetworkMessageBridge bridge = new NetworkMessageBridge();
try {
bridge.setDestination(destination);
bridge.setDurableTopic(durableTopic);
bridge.setLocalBrokerName(brokerContainer.getBroker().getBrokerName());
bridge.setLocalSession(getLocalConnection().createSession(false, Session.CLIENT_ACKNOWLEDGE));
bridge.setRemoteSession(getRemoteConnection().createSession(false, Session.CLIENT_ACKNOWLEDGE));
map.put(destination.getPhysicalName(), bridge);
bridge.start();
log.info("started NetworkMessageBridge for destination: " + destination + " -- NetworkChannel: "
+ this.toString());
}
catch (JMSException jmsEx) {
log.error("Failed to start NetworkMessageBridge for destination: " + destination, jmsEx);
}
return bridge;
}
private void removeConsumerInfo(final ConsumerInfo info) {
final String physicalName = info.getDestination().getPhysicalName();
final NetworkMessageBridge bridge = (NetworkMessageBridge) topicConsumerMap.get(physicalName);
if (bridge != null) {
if (bridge.decrementReferenceCount() <= 0) {
try {
threadPool.execute(new Runnable() {
public void run() {
bridge.stop();
topicConsumerMap.remove(physicalName);
log.info("stopped MetworkMessageBridge for destination: " + info.getDestination());
}
});
}
catch (InterruptedException e) {
log.warn("got interrupted stoping NetworkBridge", e);
}
}
}
}
private void startSubscriptions() {
if (!remote) {
MessageContainerManager mcm = brokerContainer.getBroker().getPersistentTopicContainerManager();
if (mcm != null) {
Map map = mcm.getLocalDestinations();
startSubscriptions(map, true, true);
}
mcm = brokerContainer.getBroker().getTransientTopicContainerManager();
if (mcm != null) {
Map map = mcm.getLocalDestinations();
startSubscriptions(map, true, false);
}
mcm = brokerContainer.getBroker().getTransientQueueContainerManager();
if (mcm != null) {
Map map = mcm.getLocalDestinations();
startSubscriptions(map, false, false);
}
mcm = brokerContainer.getBroker().getPersistentQueueContainerManager();
if (mcm != null) {
Map map = mcm.getLocalDestinations();
startSubscriptions(map, false, false);
}
}
}
private void startSubscriptions(Map destinations, boolean topic, boolean durableTopic) {
if (destinations != null) {
for (Iterator i = destinations.values().iterator();i.hasNext();) {
ActiveMQDestination dest = (ActiveMQDestination) i.next();
addConsumerInfo(dest, topic, durableTopic);
}
}
}
protected void initialize() throws JMSException {
// force lazy construction
initializeLocal();
initializeRemote();
brokerContainer.getBroker().addConsumerInfoListener(NetworkChannel.this);
}
private synchronized void initializeRemote() throws JMSException {
if (remoteConnection == null) {
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(remoteUserName, remotePassword, uri);
//factory.setTurboBoost(true);
factory.setJ2EEcompliant(false);
factory.setQuickClose(true);
factory.setInternalConnection(true);
remoteConnection = (ActiveMQConnection) factory.createConnection();
TransportChannel transportChannel = remoteConnection.getTransportChannel();
if (transportChannel instanceof CompositeTransportChannel) {
CompositeTransportChannel composite = (CompositeTransportChannel) transportChannel;
composite.setMaximumRetries(maximumRetries);
composite.setFailureSleepTime(reconnectSleepTime);
composite.setIncrementTimeout(false);
}
transportChannel.addTransportStatusEventListener(this);
remoteConnection.setClientID(brokerContainer.getBroker().getBrokerName() + "_NetworkChannel");
remoteConnection.start();
BrokerInfo info = new BrokerInfo();
info.setBrokerName(brokerContainer.getBroker().getBrokerName());
info.setClusterName(brokerContainer.getBroker().getBrokerClusterName());
Receipt receipt = remoteConnection.syncSendRequest(info);
if (receipt != null) {
remoteBrokerName = receipt.getBrokerName();
remoteClusterName = receipt.getClusterName();
}
connectionAdvisor = new ConnectionAdvisor(remoteConnection);
connectionAdvisor.addListener(this);
connectionAdvisor.start();
if (remotePrefetchPolicy != null) {
remoteConnection.setPrefetchPolicy(remotePrefetchPolicy);
}
}
doSetConnected();
}
private void initializeLocal() throws JMSException {
String brokerName = brokerContainer.getBroker().getBrokerName();
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("vm://" + brokerName);
factory.setTurboBoost(true);
factory.setJ2EEcompliant(false);
factory.setBrokerName(brokerName);
factory.setQuickClose(true);
factory.setInternalConnection(true);
localConnection = (ActiveMQConnection) factory.createConnection();
localConnection.start();
BrokerInfo info = new BrokerInfo();
info.setBrokerName(remoteBrokerName);
info.setClusterName(remoteClusterName);
localConnection.asyncSendPacket(info);
if (localPrefetchPolicy != null) {
localConnection.setPrefetchPolicy(localPrefetchPolicy);
}
}
/*private synchronized void releaseRemote() throws JMSException {
if (remoteConnection != null) {
TransportChannel transportChannel = remoteConnection.getTransportChannel();
transportChannel.stop();
if (connectionAdvisor != null) {
connectionAdvisor.stop();
}
try {
remoteConnection.stop();
} catch (JMSException e) {
// ignore this exception, since the remote broker is most likely down
}
remoteConnection = null;
}
}*/
}