/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.activemq.usecases; import javax.jms.Connection; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageConsumer; import javax.jms.MessageProducer; import javax.jms.Session; import javax.jms.TextMessage; import java.net.URI; import org.apache.activemq.ActiveMQConnectionFactory; import org.apache.activemq.JmsMultipleBrokersTestSupport; import org.apache.activemq.broker.BrokerService; import org.apache.activemq.broker.region.policy.PolicyEntry; import org.apache.activemq.broker.region.policy.PolicyMap; import org.apache.activemq.command.ActiveMQDestination; import org.apache.activemq.command.ActiveMQQueue; import org.apache.activemq.command.ActiveMQTopic; import org.apache.activemq.network.ConditionalNetworkBridgeFilterFactory; /** * @author <a href="http://www.christianposta.com/blog">Christian Posta</a> */ public class VirtualTopicNetworkClusterReactivationTest extends JmsMultipleBrokersTestSupport { private static final String BROKER_A = "brokerA"; private static final String BROKER_B = "brokerB"; private static final String BROKER_A_TRANSPORT_URL = "tcp://localhost:61616"; private static final String BROKER_B_TRANSPORT_URL = "tcp://localhost:61617"; private static final long DEFAULT_SLEEP_MS = 1000; private ActiveMQTopic topic = new ActiveMQTopic("VirtualTopic.FOO.TEST"); private ActiveMQQueue queue = new ActiveMQQueue("Consumer.FOO.VirtualTopic.FOO.TEST"); /** * This test shows how to use pub/sub to mimic durable subscribers in a network of brokers. * * When using durable subscribers in a broker cluster, you can encounter a situation where a * subscription gets orphaned on a broker when the client disconnects and reattaches to another * broker in the cluster. Since the clientID/durableName only need to be unique within a single * broker, it's possible to have a durable sub on multiple brokers in a cluster. * * FOR EXAMPLE: * Broker A and B are networked together in both directions to form a full mesh. If durable * subscriber 'foo' subscribes to failover(A,B) and ends up on B, and a producer on A, messages * will be demand forwarded from A to B. But if the durable sub 'foo' disconnects from B, * then reconnects to failover(A,B) but this time gets connected to A, the subscription on * B will still be there are continue to receive messages (and possibly have missed messages * sent there while gone) * * We can avoid all of this mess with virtual topics as seen below: * * @throws JMSException */ public void testDurableSubReconnectFromAtoB() throws JMSException { // create consumer on broker B ActiveMQConnectionFactory bConnFactory = new ActiveMQConnectionFactory(BROKER_B_TRANSPORT_URL + "?jms.prefetchPolicy.queuePrefetch=0"); Connection bConn = bConnFactory.createConnection(); bConn.start(); Session bSession = bConn.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageConsumer bSessionConsumer = bSession.createConsumer(queue); // create producer on A ActiveMQConnectionFactory aConnFactory = new ActiveMQConnectionFactory(BROKER_A_TRANSPORT_URL); Connection aProducerConn = aConnFactory.createConnection(); aProducerConn.start(); Session aProducerSession = aProducerConn.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageProducer producer = aProducerSession.createProducer(topic); produce(producer, aProducerSession, 5); // sleep for a sec to let the messages get bridged over to broker B sleep(); // consumer on B has not consumed any messages, and for some reason goes away: bSessionConsumer.close(); bSession.close(); bConn.close(); // let the bridge catch up sleep(); // and now consumer reattaches to A and wants the messages that were sent to B Connection aConsumerConn = aConnFactory.createConnection(); aConsumerConn.start(); Session aConsumerSession = aConsumerConn.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageConsumer aSessionConsumer = aConsumerSession.createConsumer(queue); sleep(); // they should all be there consume(aSessionConsumer, 5); } private void consume(MessageConsumer durable, int numMessagesExpected) throws JMSException { for (int i = 0; i < numMessagesExpected; i++) { Message message = durable.receive(1000); assertNotNull(message); TextMessage textMessage = (TextMessage) message; System.out.println("received: " + textMessage.getText()); assertEquals("message: " + i, textMessage.getText()); } } private void produce(MessageProducer producer, Session sess, int numMessages) throws JMSException { for (int i = 0; i < numMessages; i++) { producer.send(sess.createTextMessage("message: " + i)); } } @Override protected void setUp() throws Exception { maxSetupTime = 1000; super.setAutoFail(true); super.setUp(); final String options = "?persistent=true&useJmx=false&deleteAllMessagesOnStartup=true"; BrokerService brokerServiceA = createBroker(new URI(String.format("broker:(%s)/%s%s", BROKER_A_TRANSPORT_URL, BROKER_A, options))); brokerServiceA.setDestinationPolicy(buildPolicyMap()); brokerServiceA.setDestinations(new ActiveMQDestination[]{queue}); BrokerService brokerServiceB = createBroker(new URI(String.format("broker:(%s)/%s%s", BROKER_B_TRANSPORT_URL, BROKER_B, options))); brokerServiceB.setDestinationPolicy(buildPolicyMap()); brokerServiceB.setDestinations(new ActiveMQDestination[]{queue}); // bridge brokers to each other statically (static: discovery) bridgeBrokers(BROKER_A, BROKER_B); bridgeBrokers(BROKER_B, BROKER_A); startAllBrokers(); } private PolicyMap buildPolicyMap() { PolicyMap policyMap = new PolicyMap(); PolicyEntry policyEntry = new PolicyEntry(); policyEntry.setOptimizedDispatch(true); ConditionalNetworkBridgeFilterFactory networkBridgeFilterFactory = new ConditionalNetworkBridgeFilterFactory(); networkBridgeFilterFactory.setReplayWhenNoConsumers(true); policyEntry.setNetworkBridgeFilterFactory(networkBridgeFilterFactory); policyEntry.setEnableAudit(false); policyMap.put(new ActiveMQQueue("Consumer.*.VirtualTopic.>"), policyEntry); return policyMap; } private void sleep() { try { Thread.sleep(DEFAULT_SLEEP_MS); } catch (InterruptedException igonred) { } } private void sleep(int milliSecondTime) { try { Thread.sleep(milliSecondTime); } catch (InterruptedException igonred) { } } }