/** * 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.network; 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 javax.net.ServerSocketFactory; import java.io.File; import java.io.IOException; import java.net.Socket; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; import org.apache.activemq.ActiveMQConnectionFactory; import org.apache.activemq.broker.BrokerService; import org.apache.activemq.broker.TransportConnector; import org.apache.activemq.broker.jmx.ManagementContext; import org.apache.activemq.command.Response; import org.apache.activemq.store.PersistenceAdapter; import org.apache.activemq.store.kahadb.KahaDBPersistenceAdapter; import org.apache.activemq.store.leveldb.LevelDBPersistenceAdapter; import org.apache.activemq.transport.Transport; import org.apache.activemq.transport.TransportFilter; import org.apache.activemq.transport.TransportServer; import org.apache.activemq.transport.nio.NIOTransport; import org.apache.activemq.transport.nio.NIOTransportFactory; import org.apache.activemq.transport.tcp.TcpTransportFactory; import org.apache.activemq.transport.tcp.TcpTransportServer; import org.apache.activemq.wireformat.WireFormat; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; /** * @author x22koe */ public class CheckDuplicateMessagesOnDuplexTest { private static final Logger log = LoggerFactory.getLogger(CheckDuplicateMessagesOnDuplexTest.class); private BrokerService localBroker; private BrokerService remoteBroker; private ActiveMQConnectionFactory localFactory; private ActiveMQConnectionFactory remoteFactory; private Session localSession; private MessageConsumer consumer; private Session remoteSession; private MessageProducer producer; private Connection remoteConnection; private Connection localConnection; private DebugTransportFilter debugTransportFilter; private boolean useLevelDB = false; public CheckDuplicateMessagesOnDuplexTest() { } @BeforeClass public static void setUpClass() { } @AfterClass public static void tearDownClass() { } @Before public void setUp() { } @After public void tearDown() { } @Test public void testConnectionLossBehaviorBeforeAckIsSent() throws Exception { createBrokers(); localBroker.deleteAllMessages(); remoteBroker.deleteAllMessages(); startBrokers(); openConnections(); Thread.sleep(1000); log.info("\n\n==============================================\nsend hello1\n"); // simulate network failure between REMOTE and LOCAL just before the reception response is sent back to REMOTE debugTransportFilter.closeOnResponse = true; producer.send(remoteSession.createTextMessage("hello1")); Message msg = consumer.receive(30000); assertNotNull("expected hello1", msg); assertEquals("hello1", ((TextMessage) msg).getText()); Thread.sleep(1000); log.info("\n\n------------------------------------------\nsend hello2\n"); producer.send(remoteSession.createTextMessage("hello2")); msg = consumer.receive(30000); assertNotNull("expected hello2", msg); assertEquals("hello2", ((TextMessage) msg).getText()); closeLocalConnection(); Thread.sleep(1000); log.info("\n\n------------------------------------------\nsend hello3\n"); openLocalConnection(); Thread.sleep(1000); producer.send(remoteSession.createTextMessage("hello3")); msg = consumer.receive(30000); assertNotNull("expected hello3", msg); assertEquals("hello3", ((TextMessage) msg).getText()); Thread.sleep(1000); log.info("\n\n==============================================\n\n"); closeConnections(); stopBrokers(); // restart the local broker, which should be empty Thread.sleep(1000); log.info("\n\n##############################################\n\n"); createLocalBroker(); startLocalBroker(); openLocalConnection(); // this should not return the "hello1" message msg = consumer.receive(1000); closeLocalConnection(); stopLocalBroker(); assertNull(msg); } private void createBrokers() throws Exception { createLocalBroker(); createRemoteBroker(); } private void createLocalBroker() throws Exception { localBroker = new BrokerService(); localBroker.setBrokerName("LOCAL"); localBroker.setUseJmx(true); localBroker.setSchedulePeriodForDestinationPurge(5000); ManagementContext managementContext = new ManagementContext(); managementContext.setCreateConnector(false); localBroker.setManagementContext(managementContext); PersistenceAdapter persistenceAdapter = persistenceAdapterFactory("target/local"); localBroker.setPersistenceAdapter(persistenceAdapter); List<TransportConnector> transportConnectors = new ArrayList<>(); DebugTransportFactory tf = new DebugTransportFactory(); TransportServer transport = tf.doBind(URI.create("nio://127.0.0.1:23539")); TransportConnector transportConnector = new TransportConnector(transport); transportConnector.setName("tc"); transportConnector.setAuditNetworkProducers(true); transportConnectors.add(transportConnector); localBroker.setTransportConnectors(transportConnectors); } private void createRemoteBroker() throws Exception { remoteBroker = new BrokerService(); remoteBroker.setBrokerName("REMOTE"); remoteBroker.setUseJmx(true); remoteBroker.setSchedulePeriodForDestinationPurge(5000); ManagementContext managementContext = new ManagementContext(); managementContext.setCreateConnector(false); remoteBroker.setManagementContext(managementContext); PersistenceAdapter persistenceAdapter = persistenceAdapterFactory("target/remote"); remoteBroker.setPersistenceAdapter(persistenceAdapter); List<NetworkConnector> networkConnectors = new ArrayList<>(); DiscoveryNetworkConnector networkConnector = new DiscoveryNetworkConnector(); networkConnector.setName("to local"); // set maxInactivityDuration to 0, otherwise the broker restarts while you are in the debugger networkConnector.setUri(URI.create("static://(tcp://127.0.0.1:23539?wireFormat.maxInactivityDuration=0)")); networkConnector.setDuplex(true); //networkConnector.setNetworkTTL(5); //networkConnector.setDynamicOnly(true); networkConnector.setAlwaysSyncSend(true); networkConnector.setDecreaseNetworkConsumerPriority(false); networkConnector.setPrefetchSize(1); networkConnector.setCheckDuplicateMessagesOnDuplex(true); networkConnectors.add(networkConnector); remoteBroker.setNetworkConnectors(networkConnectors); } private void startBrokers() throws Exception { startLocalBroker(); startRemoteBroker(); } private void startLocalBroker() throws Exception { localBroker.start(); localBroker.waitUntilStarted(); } private void startRemoteBroker() throws Exception { remoteBroker.start(); remoteBroker.waitUntilStarted(); } private void openConnections() throws JMSException { openLocalConnection(); openRemoteConnection(); } private void openLocalConnection() throws JMSException { localFactory = new ActiveMQConnectionFactory(localBroker.getVmConnectorURI()); //localFactory.setSendAcksAsync(false); localConnection = localFactory.createConnection(); localConnection.start(); localSession = localConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); consumer = localSession.createConsumer(localSession.createQueue("testqueue")); } private void openRemoteConnection() throws JMSException { remoteFactory = new ActiveMQConnectionFactory(remoteBroker.getVmConnectorURI()); //remoteFactory.setSendAcksAsync(false); remoteConnection = remoteFactory.createConnection(); remoteConnection.start(); remoteSession = remoteConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); producer = remoteSession.createProducer(remoteSession.createQueue("testqueue")); } private void closeConnections() throws JMSException { closeLocalConnection(); closeRemoteConnection(); } private void closeLocalConnection() throws JMSException { localConnection.close(); } private void closeRemoteConnection() throws JMSException { remoteConnection.close(); } private void stopBrokers() throws Exception { stopRemoteBroker(); stopLocalBroker(); } private void stopLocalBroker() throws Exception { localBroker.stop(); localBroker.waitUntilStopped(); } private void stopRemoteBroker() throws Exception { remoteBroker.stop(); remoteBroker.waitUntilStopped(); } private PersistenceAdapter persistenceAdapterFactory(String path) { if (useLevelDB) { return persistenceAdapterFactory_LevelDB(path); } else { return persistenceAdapterFactory_KahaDB(path); } } private PersistenceAdapter persistenceAdapterFactory_KahaDB(String path) { KahaDBPersistenceAdapter kahaDBPersistenceAdapter = new KahaDBPersistenceAdapter(); kahaDBPersistenceAdapter.setDirectory(new File(path)); kahaDBPersistenceAdapter.setIgnoreMissingJournalfiles(true); kahaDBPersistenceAdapter.setCheckForCorruptJournalFiles(true); kahaDBPersistenceAdapter.setChecksumJournalFiles(true); return kahaDBPersistenceAdapter; } private PersistenceAdapter persistenceAdapterFactory_LevelDB(String path) { LevelDBPersistenceAdapter levelDBPersistenceAdapter = new LevelDBPersistenceAdapter(); levelDBPersistenceAdapter.setDirectory(new File(path)); return levelDBPersistenceAdapter; } private class DebugTransportFactory extends NIOTransportFactory { @Override protected TcpTransportServer createTcpTransportServer(URI location, ServerSocketFactory serverSocketFactory) throws IOException, URISyntaxException { return new DebugTransportServer(this, location, serverSocketFactory); } } private class DebugTransportServer extends TcpTransportServer { public DebugTransportServer(TcpTransportFactory transportFactory, URI location, ServerSocketFactory serverSocketFactory) throws IOException, URISyntaxException { super(transportFactory, location, serverSocketFactory); } @Override protected Transport createTransport(Socket socket, WireFormat format) throws IOException { Transport transport; transport = new NIOTransport(format, socket); debugTransportFilter = new DebugTransportFilter(transport); return debugTransportFilter; } } private class DebugTransportFilter extends TransportFilter { boolean closeOnResponse = false; public DebugTransportFilter(Transport next) { super(next); } @Override public void oneway(Object command) throws IOException { if (closeOnResponse && command instanceof Response) { closeOnResponse = false; log.warn("\n\nclosing connection before response is sent\n\n"); try { ((NIOTransport) next).stop(); } catch (Exception ex) { log.error("couldn't stop niotransport", ex); } // don't send response return; } super.oneway(command); } } }