001 /**
002 *
003 * Copyright 2004 Protique Ltd
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 *
017 **/
018
019 package org.activemq.transport;
020 import java.util.Iterator;
021 import java.util.Map;
022 import javax.jms.JMSException;
023 import javax.jms.Session;
024 import org.apache.commons.logging.Log;
025 import org.apache.commons.logging.LogFactory;
026 import org.activemq.ActiveMQConnection;
027 import org.activemq.ActiveMQConnectionFactory;
028 import org.activemq.ActiveMQPrefetchPolicy;
029 import org.activemq.advisories.ConnectionAdvisor;
030 import org.activemq.advisories.ConnectionAdvisoryEvent;
031 import org.activemq.advisories.ConnectionAdvisoryEventListener;
032 import org.activemq.broker.BrokerClient;
033 import org.activemq.broker.BrokerContainer;
034 import org.activemq.broker.ConsumerInfoListener;
035 import org.activemq.message.ActiveMQDestination;
036 import org.activemq.message.BrokerInfo;
037 import org.activemq.message.ConsumerInfo;
038 import org.activemq.message.Receipt;
039 import org.activemq.service.MessageContainerManager;
040 import org.activemq.service.Service;
041 import org.activemq.transport.composite.CompositeTransportChannel;
042 import EDU.oswego.cs.dl.util.concurrent.ConcurrentHashMap;
043 import EDU.oswego.cs.dl.util.concurrent.PooledExecutor;
044 import EDU.oswego.cs.dl.util.concurrent.SynchronizedBoolean;
045
046 /**
047 * Represents a broker's connection with a single remote broker which bridges the two brokers to form a network. <p/>
048 * The NetworkChannel contains a JMS connection with the remote broker. <p/>New subscriptions on the local broker are
049 * multiplexed into the JMS connection so that messages published on the remote broker can be replayed onto the local
050 * broker.
051 *
052 * @version $Revision: 1.1.1.1 $
053 */
054 public class NetworkChannel
055 implements
056 Service,
057 ConsumerInfoListener,
058 ConnectionAdvisoryEventListener,
059 TransportStatusEventListener {
060 private static final Log log = LogFactory.getLog(NetworkChannel.class);
061 protected String uri;
062 protected BrokerContainer brokerContainer;
063 protected ActiveMQConnection localConnection;
064 protected ActiveMQConnection remoteConnection;
065 protected ConcurrentHashMap topicConsumerMap;
066 protected ConcurrentHashMap queueConsumerMap;
067 protected String remoteUserName;
068 protected String remotePassword;
069 protected String remoteBrokerName;
070 protected String remoteClusterName;
071 protected int maximumRetries = 0;
072 protected long reconnectSleepTime = 2000L;
073 protected PooledExecutor threadPool;
074 private boolean remote = false;
075 private SynchronizedBoolean started = new SynchronizedBoolean(false);
076 private SynchronizedBoolean connected = new SynchronizedBoolean(false);
077 private SynchronizedBoolean stopped = new SynchronizedBoolean(false);
078 private ConnectionAdvisor connectionAdvisor;
079 private ActiveMQPrefetchPolicy localPrefetchPolicy;
080 private ActiveMQPrefetchPolicy remotePrefetchPolicy;
081
082 /**
083 * Default constructor
084 */
085 public NetworkChannel() {
086 this.topicConsumerMap = new ConcurrentHashMap();
087 this.queueConsumerMap = new ConcurrentHashMap();
088 }
089
090 /**
091 * Default Constructor
092 *
093 * @param tp
094 */
095 public NetworkChannel(PooledExecutor tp) {
096 this();
097 this.threadPool = tp;
098 }
099
100 /**
101 * Constructor
102 *
103 * @param connector
104 * @param brokerContainer
105 * @param uri
106 */
107 public NetworkChannel(NetworkConnector connector, BrokerContainer brokerContainer, String uri) {
108 this(connector.threadPool);
109 this.brokerContainer = brokerContainer;
110 this.uri = uri;
111 }
112
113 /**
114 * Create a NetworkConnector from a TransportChannel
115 *
116 * @param connector
117 * @param brokerContainer
118 * @param channel
119 * @param remoteBrokerName
120 * @param remoteclusterName
121 * @throws JMSException
122 */
123 public NetworkChannel(NetworkConnector connector, BrokerContainer brokerContainer, TransportChannel channel,
124 String remoteBrokerName, String remoteclusterName) throws JMSException {
125 this(connector.threadPool);
126 this.brokerContainer = brokerContainer;
127 this.uri = "";
128 this.remoteBrokerName = remoteBrokerName;
129 this.remoteClusterName = remoteclusterName;
130 ActiveMQConnectionFactory fac = new ActiveMQConnectionFactory();
131 fac.setJ2EEcompliant(false);
132 fac.setTurboBoost(true);
133 remoteConnection = new ActiveMQConnection(fac, remoteUserName, remotePassword, channel);
134 remoteConnection.setClientID("Boondocks:" + remoteClusterName + ":" + remoteBrokerName);
135 remoteConnection.setQuickClose(true);
136 remoteConnection.start();
137 BrokerInfo info = new BrokerInfo();
138 info.setBrokerName(brokerContainer.getBroker().getBrokerName());
139 info.setClusterName(brokerContainer.getBroker().getBrokerClusterName());
140 channel.asyncSend(info);
141 remote = true;
142 }
143
144 /**
145 * @see org.activemq.transport.TransportStatusEventListener#statusChanged(org.activemq.transport.TransportStatusEvent)
146 */
147 public void statusChanged(TransportStatusEvent event) {
148 if (event != null
149 && (event.getChannelStatus() == TransportStatusEvent.CONNECTED
150 || event.getChannelStatus() == TransportStatusEvent.RECONNECTED)) {
151 connected.set(true);
152 }else {
153 connected.set(false);
154 }
155 }
156
157 private void doSetConnected() {
158 synchronized (connected) {
159 connected.set(true);
160 connected.notifyAll();
161 }
162 }
163
164 /**
165 * @return text info on this
166 */
167 public String toString() {
168 return "NetworkChannel{ " + ", uri = '" + uri + "' " + ", remoteBrokerName = '" + remoteBrokerName + "' "
169 + " }";
170 }
171
172 /**
173 * Start the channel
174 */
175 public void start() {
176 if (started.commit(false, true)) {
177 try {
178 stopped.set(false);
179 threadPool.execute(new Runnable() {
180 public void run() {
181 String originalName = Thread.currentThread().getName();
182 try {
183 Thread.currentThread().setName("NetworkChannel Initiator to " + uri);
184 initialize();
185 startSubscriptions();
186 log.info("Started NetworkChannel to " + uri);
187 }
188 catch (JMSException jmsEx) {
189 log.error("Failed to start NetworkChannel: " + uri, jmsEx);
190 }
191 finally {
192 Thread.currentThread().setName(originalName);
193 }
194 }
195 });
196 }
197 catch (InterruptedException e) {
198 log.warn("Failed to start - interuppted", e);
199 }
200 }
201 }
202
203 /**
204 * stop the channel
205 *
206 * @throws JMSException on error
207 */
208 public void stop() throws JMSException {
209 if (started.commit(true, false)) {
210 stopped.set(true);
211 topicConsumerMap.clear();
212 if (remoteConnection != null) {
213 remoteConnection.close();
214 remoteConnection = null;
215 }
216 if (localConnection != null) {
217 localConnection.close();
218 localConnection = null;
219 }
220 for (Iterator i = topicConsumerMap.values().iterator();i.hasNext();) {
221 NetworkMessageBridge consumer = (NetworkMessageBridge) i.next();
222 consumer.stop();
223 }
224 }
225 }
226
227 /**
228 * Listen for new Consumer events at this broker
229 *
230 * @param client
231 * @param info
232 */
233 public void onConsumerInfo(final BrokerClient client, final ConsumerInfo info) {
234 String brokerName = client.getBrokerConnector().getBrokerInfo().getBrokerName();
235 if (!client.isClusteredConnection()) {
236 if (connected.get()) {
237 if (!info.hasVisited(remoteBrokerName)) {
238 if (info.isStarted()) {
239 addConsumerInfo(info);
240 }
241 else {
242 removeConsumerInfo(info);
243 }
244 }
245 }
246 else {
247 try {
248 threadPool.execute(new Runnable() {
249 public void run() {
250 if (!client.isClusteredConnection()) {
251 if (!info.hasVisited(remoteBrokerName)) {
252 synchronized (connected) {
253 while (!connected.get() && !stopped.get()) {
254 try {
255 connected.wait(500);
256 }
257 catch (InterruptedException e) {
258 log.debug("interuppted", e);
259 }
260 }
261 if (info.isStarted()) {
262 addConsumerInfo(info);
263 }
264 else {
265 removeConsumerInfo(info);
266 }
267 }
268 }
269 }
270 }
271 });
272 }
273 catch (InterruptedException e) {
274 log.warn("Failed to process ConsumerInfo: " + info, e);
275 }
276 }
277 }
278 }
279
280 /**
281 * @return the uri of the broker(s) this channel is connected to
282 */
283 public String getUri() {
284 return uri;
285 }
286
287 /**
288 * set the uri of the broker(s) this channel is connected to
289 *
290 * @param uri
291 */
292 public void setUri(String uri) {
293 this.uri = uri;
294 }
295
296 /**
297 * @return Returns the remotePassword.
298 */
299 public String getRemotePassword() {
300 return remotePassword;
301 }
302
303 /**
304 * @param remotePassword The remotePassword to set.
305 */
306 public void setRemotePassword(String remotePassword) {
307 this.remotePassword = remotePassword;
308 }
309
310 /**
311 * @return Returns the remoteUserName.
312 */
313 public String getRemoteUserName() {
314 return remoteUserName;
315 }
316
317 /**
318 * @param remoteUserName The remoteUserName to set.
319 */
320 public void setRemoteUserName(String remoteUserName) {
321 this.remoteUserName = remoteUserName;
322 }
323
324 /**
325 * @return Returns the brokerContainer.
326 */
327 public BrokerContainer getBrokerContainer() {
328 return brokerContainer;
329 }
330
331 /**
332 * @param brokerContainer The brokerContainer to set.
333 */
334 public void setBrokerContainer(BrokerContainer brokerContainer) {
335 this.brokerContainer = brokerContainer;
336 }
337
338 public int getMaximumRetries() {
339 return maximumRetries;
340 }
341
342 public void setMaximumRetries(int maximumRetries) {
343 this.maximumRetries = maximumRetries;
344 }
345
346 public long getReconnectSleepTime() {
347 return reconnectSleepTime;
348 }
349
350 public void setReconnectSleepTime(long reconnectSleepTime) {
351 this.reconnectSleepTime = reconnectSleepTime;
352 }
353
354 public String getRemoteBrokerName() {
355 return remoteBrokerName;
356 }
357
358 public void setRemoteBrokerName(String remoteBrokerName) {
359 this.remoteBrokerName = remoteBrokerName;
360 }
361
362 /**
363 * @return Returns the threadPool.
364 */
365 protected PooledExecutor getThreadPool() {
366 return threadPool;
367 }
368
369 /**
370 * @param threadPool The threadPool to set.
371 */
372 protected void setThreadPool(PooledExecutor threadPool) {
373 this.threadPool = threadPool;
374 }
375
376 private synchronized ActiveMQConnection getLocalConnection() throws JMSException {
377 if (localConnection == null) {
378 initializeLocal();
379 }
380 return localConnection;
381 }
382
383 private synchronized ActiveMQConnection getRemoteConnection() throws JMSException {
384 if (remoteConnection == null) {
385 initializeRemote();
386 }
387 return remoteConnection;
388 }
389
390 /**
391 * @return Returns the localPrefetchPolicy.
392 */
393 public ActiveMQPrefetchPolicy getLocalPrefetchPolicy() {
394 return localPrefetchPolicy;
395 }
396
397 /**
398 * @param localPrefetchPolicy The localPrefetchPolicy to set.
399 */
400 public void setLocalPrefetchPolicy(ActiveMQPrefetchPolicy localPrefetchPolicy) {
401 this.localPrefetchPolicy = localPrefetchPolicy;
402 }
403
404 /**
405 * @return Returns the remotePrefetchPolicy.
406 */
407 public ActiveMQPrefetchPolicy getRemotePrefetchPolicy() {
408 return remotePrefetchPolicy;
409 }
410
411 /**
412 * @param remotePrefetchPolicy The remotePrefetchPolicy to set.
413 */
414 public void setRemotePrefetchPolicy(ActiveMQPrefetchPolicy remotePrefetchPolicy) {
415 this.remotePrefetchPolicy = remotePrefetchPolicy;
416 }
417
418 // Implementation methods
419 //-------------------------------------------------------------------------
420 /**
421 * Implementation of ConnectionAdvisoryEventListener
422 *
423 * @param event
424 */
425 public void onEvent(ConnectionAdvisoryEvent event) {
426 String localBrokerName = brokerContainer.getBroker().getBrokerName();
427 if (!event.getInfo().isClosed()) {
428 brokerContainer.registerRemoteClientID(event.getInfo().getClientId());
429 }
430 else {
431 brokerContainer.deregisterRemoteClientID(event.getInfo().getClientId());
432 }
433 }
434
435 private void addConsumerInfo(ConsumerInfo info) {
436 addConsumerInfo(info.getDestination(), info.getDestination().isTopic(), info.isDurableTopic());
437 }
438
439 private void addConsumerInfo(ActiveMQDestination destination, boolean topic, boolean durableTopic) {
440 Map map = topic ? topicConsumerMap : queueConsumerMap;
441 NetworkMessageBridge bridge = (NetworkMessageBridge) map.get(destination.getPhysicalName());
442 if (bridge == null) {
443 bridge = createBridge(map, destination, durableTopic);
444 }
445 else if (durableTopic && !bridge.isDurableTopic()) {
446 //upgrade our subscription
447 bridge.decrementReferenceCount();
448 upgradeBridge(bridge);
449 }
450 bridge.incrementReferenceCount();
451 }
452
453 private void upgradeBridge(NetworkMessageBridge bridge) {
454 try {
455 remoteConnection.stop();
456 bridge.upgrade();
457 }
458 catch (JMSException e) {
459 log.warn("Could not upgrade the NetworkMessageBridge to a durable subscription for destination: "
460 + bridge.getDestination(), e);
461 }
462 try {
463 remoteConnection.start();
464 }
465 catch (JMSException e) {
466 log.error("Failed to restart the NetworkMessageBridge", e);
467 }
468 }
469
470 private NetworkMessageBridge createBridge(Map map, ActiveMQDestination destination, boolean durableTopic) {
471 NetworkMessageBridge bridge = new NetworkMessageBridge();
472 try {
473 bridge.setDestination(destination);
474 bridge.setDurableTopic(durableTopic);
475 bridge.setLocalBrokerName(brokerContainer.getBroker().getBrokerName());
476 bridge.setLocalSession(getLocalConnection().createSession(false, Session.CLIENT_ACKNOWLEDGE));
477 bridge.setRemoteSession(getRemoteConnection().createSession(false, Session.CLIENT_ACKNOWLEDGE));
478 map.put(destination.getPhysicalName(), bridge);
479 bridge.start();
480 log.info("started NetworkMessageBridge for destination: " + destination + " -- NetworkChannel: "
481 + this.toString());
482 }
483 catch (JMSException jmsEx) {
484 log.error("Failed to start NetworkMessageBridge for destination: " + destination, jmsEx);
485 }
486 return bridge;
487 }
488
489 private void removeConsumerInfo(final ConsumerInfo info) {
490 final String physicalName = info.getDestination().getPhysicalName();
491 final NetworkMessageBridge bridge = (NetworkMessageBridge) topicConsumerMap.get(physicalName);
492 if (bridge != null) {
493 if (bridge.decrementReferenceCount() <= 0) {
494 try {
495 threadPool.execute(new Runnable() {
496 public void run() {
497 bridge.stop();
498 topicConsumerMap.remove(physicalName);
499 log.info("stopped MetworkMessageBridge for destination: " + info.getDestination());
500 }
501 });
502 }
503 catch (InterruptedException e) {
504 log.warn("got interrupted stoping NetworkBridge", e);
505 }
506 }
507 }
508 }
509
510 private void startSubscriptions() {
511 if (!remote) {
512 MessageContainerManager mcm = brokerContainer.getBroker().getPersistentTopicContainerManager();
513 if (mcm != null) {
514 Map map = mcm.getLocalDestinations();
515 startSubscriptions(map, true, true);
516 }
517 mcm = brokerContainer.getBroker().getTransientTopicContainerManager();
518 if (mcm != null) {
519 Map map = mcm.getLocalDestinations();
520 startSubscriptions(map, true, false);
521 }
522 mcm = brokerContainer.getBroker().getTransientQueueContainerManager();
523 if (mcm != null) {
524 Map map = mcm.getLocalDestinations();
525 startSubscriptions(map, false, false);
526 }
527 mcm = brokerContainer.getBroker().getPersistentQueueContainerManager();
528 if (mcm != null) {
529 Map map = mcm.getLocalDestinations();
530 startSubscriptions(map, false, false);
531 }
532 }
533 }
534
535 private void startSubscriptions(Map destinations, boolean topic, boolean durableTopic) {
536 if (destinations != null) {
537 for (Iterator i = destinations.values().iterator();i.hasNext();) {
538 ActiveMQDestination dest = (ActiveMQDestination) i.next();
539 addConsumerInfo(dest, topic, durableTopic);
540 }
541 }
542 }
543
544 protected void initialize() throws JMSException {
545 // force lazy construction
546 initializeLocal();
547 initializeRemote();
548 brokerContainer.getBroker().addConsumerInfoListener(NetworkChannel.this);
549 }
550
551 private synchronized void initializeRemote() throws JMSException {
552 if (remoteConnection == null) {
553 ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(remoteUserName, remotePassword, uri);
554 //factory.setTurboBoost(true);
555 factory.setJ2EEcompliant(false);
556 factory.setQuickClose(true);
557 factory.setInternalConnection(true);
558 remoteConnection = (ActiveMQConnection) factory.createConnection();
559 TransportChannel transportChannel = remoteConnection.getTransportChannel();
560 if (transportChannel instanceof CompositeTransportChannel) {
561 CompositeTransportChannel composite = (CompositeTransportChannel) transportChannel;
562 composite.setMaximumRetries(maximumRetries);
563 composite.setFailureSleepTime(reconnectSleepTime);
564 composite.setIncrementTimeout(false);
565 }
566 transportChannel.addTransportStatusEventListener(this);
567 remoteConnection.setClientID(brokerContainer.getBroker().getBrokerName() + "_NetworkChannel");
568 remoteConnection.start();
569 BrokerInfo info = new BrokerInfo();
570 info.setBrokerName(brokerContainer.getBroker().getBrokerName());
571 info.setClusterName(brokerContainer.getBroker().getBrokerClusterName());
572 Receipt receipt = remoteConnection.syncSendRequest(info);
573 if (receipt != null) {
574 remoteBrokerName = receipt.getBrokerName();
575 remoteClusterName = receipt.getClusterName();
576 }
577 connectionAdvisor = new ConnectionAdvisor(remoteConnection);
578 connectionAdvisor.addListener(this);
579 connectionAdvisor.start();
580 if (remotePrefetchPolicy != null) {
581 remoteConnection.setPrefetchPolicy(remotePrefetchPolicy);
582 }
583 }
584 doSetConnected();
585 }
586
587 private void initializeLocal() throws JMSException {
588 String brokerName = brokerContainer.getBroker().getBrokerName();
589 ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("vm://" + brokerName);
590 factory.setTurboBoost(true);
591 factory.setJ2EEcompliant(false);
592 factory.setBrokerName(brokerName);
593 factory.setQuickClose(true);
594 factory.setInternalConnection(true);
595 localConnection = (ActiveMQConnection) factory.createConnection();
596 localConnection.start();
597 BrokerInfo info = new BrokerInfo();
598 info.setBrokerName(remoteBrokerName);
599 info.setClusterName(remoteClusterName);
600 localConnection.asyncSendPacket(info);
601 if (localPrefetchPolicy != null) {
602 localConnection.setPrefetchPolicy(localPrefetchPolicy);
603 }
604 }
605
606 /*private synchronized void releaseRemote() throws JMSException {
607 if (remoteConnection != null) {
608 TransportChannel transportChannel = remoteConnection.getTransportChannel();
609 transportChannel.stop();
610 if (connectionAdvisor != null) {
611 connectionAdvisor.stop();
612 }
613 try {
614 remoteConnection.stop();
615 } catch (JMSException e) {
616 // ignore this exception, since the remote broker is most likely down
617 }
618 remoteConnection = null;
619 }
620 }*/
621
622 }