/* * 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.artemis.tests.extras.jms.bridge; import javax.jms.BytesMessage; import javax.jms.Connection; import javax.jms.ConnectionFactory; import javax.jms.DeliveryMode; import javax.jms.Destination; import javax.jms.Message; import javax.jms.MessageConsumer; import javax.jms.MessageProducer; import javax.jms.Queue; import javax.jms.Session; import javax.jms.TextMessage; import javax.jms.Topic; import javax.jms.XAConnectionFactory; import javax.transaction.TransactionManager; import java.util.HashMap; import java.util.HashSet; import java.util.Set; import com.arjuna.ats.arjuna.coordinator.TransactionReaper; import com.arjuna.ats.arjuna.coordinator.TxControl; import com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionManagerImple; import org.apache.activemq.artemis.api.core.TransportConfiguration; import org.apache.activemq.artemis.api.core.management.AddressControl; import org.apache.activemq.artemis.api.core.management.QueueControl; import org.apache.activemq.artemis.api.core.management.ResourceNames; import org.apache.activemq.artemis.api.jms.ActiveMQJMSClient; import org.apache.activemq.artemis.api.jms.JMSFactoryType; import org.apache.activemq.artemis.core.config.Configuration; import org.apache.activemq.artemis.core.registry.JndiBindingRegistry; import org.apache.activemq.artemis.core.remoting.impl.invm.TransportConstants; import org.apache.activemq.artemis.core.server.ActiveMQServer; import org.apache.activemq.artemis.core.server.ActiveMQServers; import org.apache.activemq.artemis.core.server.management.ManagementService; import org.apache.activemq.artemis.jms.bridge.ConnectionFactoryFactory; import org.apache.activemq.artemis.jms.bridge.DestinationFactory; import org.apache.activemq.artemis.jms.bridge.QualityOfServiceMode; import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory; import org.apache.activemq.artemis.jms.client.ActiveMQJMSConnectionFactory; import org.apache.activemq.artemis.jms.client.ActiveMQMessage; import org.apache.activemq.artemis.jms.client.ActiveMQXAConnectionFactory; import org.apache.activemq.artemis.jms.server.JMSServerManager; import org.apache.activemq.artemis.jms.server.impl.JMSServerManagerImpl; import org.apache.activemq.artemis.tests.integration.IntegrationTestLogger; import org.apache.activemq.artemis.tests.unit.util.InVMNamingContext; import org.apache.activemq.artemis.tests.util.ActiveMQTestBase; import org.junit.After; import org.junit.Assert; import org.junit.Before; public abstract class BridgeTestBase extends ActiveMQTestBase { private static final IntegrationTestLogger log = IntegrationTestLogger.LOGGER; protected ConnectionFactoryFactory cff0, cff1; protected ConnectionFactoryFactory cff0xa, cff1xa; protected ConnectionFactory cf0, cf1; protected XAConnectionFactory cf0xa, cf1xa; protected DestinationFactory sourceQueueFactory; protected DestinationFactory targetQueueFactory; protected DestinationFactory localTargetQueueFactory; protected DestinationFactory sourceTopicFactory; protected Queue sourceQueue, targetQueue, localTargetQueue; protected Topic sourceTopic; protected ActiveMQServer server0; protected JMSServerManager jmsServer0; protected ActiveMQServer server1; protected JMSServerManager jmsServer1; private InVMNamingContext context0; protected InVMNamingContext context1; protected HashMap<String, Object> params1; protected ConnectionFactoryFactory cff0LowProducerWindow; @Override @Before public void setUp() throws Exception { super.setUp(); // Start the servers Configuration conf0 = createBasicConfig().setJournalDirectory(getJournalDir(0, false)).setBindingsDirectory(getBindingsDir(0, false)).addAcceptorConfiguration(new TransportConfiguration(INVM_ACCEPTOR_FACTORY)); server0 = addServer(ActiveMQServers.newActiveMQServer(conf0, false)); context0 = new InVMNamingContext(); jmsServer0 = new JMSServerManagerImpl(server0); jmsServer0.setRegistry(new JndiBindingRegistry(context0)); jmsServer0.start(); params1 = new HashMap<>(); params1.put(TransportConstants.SERVER_ID_PROP_NAME, 1); Configuration conf1 = createBasicConfig().setJournalDirectory(getJournalDir(1, false)).setBindingsDirectory(getBindingsDir(1, false)).addAcceptorConfiguration(new TransportConfiguration(INVM_ACCEPTOR_FACTORY, params1)); server1 = addServer(ActiveMQServers.newActiveMQServer(conf1, false)); context1 = new InVMNamingContext(); jmsServer1 = new JMSServerManagerImpl(server1); jmsServer1.setRegistry(new JndiBindingRegistry(context1)); jmsServer1.start(); createQueue("sourceQueue", 0); jmsServer0.createTopic(false, "sourceTopic", "/topic/sourceTopic"); createQueue("localTargetQueue", 0); createQueue("targetQueue", 1); setUpAdministeredObjects(); TxControl.enable(); // We need a local transaction and recovery manager // We must start this after the remote servers have been created or it won't // have deleted the database and the recovery manager may attempt to recover transactions } protected void createQueue(final String queueName, final int index) throws Exception { JMSServerManager server = jmsServer0; if (index == 1) { server = jmsServer1; } assertTrue("queue '/queue/" + queueName + "' created", server.createQueue(false, queueName, null, true, "/queue/" + queueName)); } @Override @After public void tearDown() throws Exception { checkEmpty(sourceQueue, 0); checkEmpty(localTargetQueue, 0); checkEmpty(targetQueue, 1); // Check no subscriptions left lying around checkNoSubscriptions(sourceTopic, 0); if (cff0 instanceof ActiveMQConnectionFactory) { ((ActiveMQConnectionFactory) cff0).close(); } if (cff1 instanceof ActiveMQConnectionFactory) { ((ActiveMQConnectionFactory) cff1).close(); } stopComponent(jmsServer0); stopComponent(jmsServer1); cff0 = cff1 = null; cff0xa = cff1xa = null; cf0 = cf1 = null; cf0xa = cf1xa = null; sourceQueueFactory = targetQueueFactory = localTargetQueueFactory = sourceTopicFactory = null; sourceQueue = targetQueue = localTargetQueue = null; sourceTopic = null; server0 = null; jmsServer0 = null; server1 = null; jmsServer1 = null; if (context0 != null) context0.close(); context0 = null; if (context1 != null) context1.close(); context1 = null; // Shutting down Arjuna threads TxControl.disable(true); TransactionReaper.terminate(false); super.tearDown(); } protected void setUpAdministeredObjects() throws Exception { cff0LowProducerWindow = new ConnectionFactoryFactory() { @Override public ConnectionFactory createConnectionFactory() throws Exception { ActiveMQConnectionFactory cf = ActiveMQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.CF, new TransportConfiguration(INVM_CONNECTOR_FACTORY)); // Note! We disable automatic reconnection on the session factory. The bridge needs to do the reconnection cf.setReconnectAttempts(0); cf.setBlockOnNonDurableSend(true); cf.setBlockOnDurableSend(true); cf.setCacheLargeMessagesClient(true); cf.setProducerWindowSize(100); return cf; } }; cff0 = new ConnectionFactoryFactory() { @Override public ConnectionFactory createConnectionFactory() throws Exception { ActiveMQConnectionFactory cf = ActiveMQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.CF, new TransportConfiguration(INVM_CONNECTOR_FACTORY)); // Note! We disable automatic reconnection on the session factory. The bridge needs to do the reconnection cf.setReconnectAttempts(0); cf.setBlockOnNonDurableSend(true); cf.setBlockOnDurableSend(true); cf.setCacheLargeMessagesClient(true); return cf; } }; cff0xa = new ConnectionFactoryFactory() { @Override public Object createConnectionFactory() throws Exception { ActiveMQXAConnectionFactory cf = (ActiveMQXAConnectionFactory) ActiveMQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.XA_CF, new TransportConfiguration(INVM_CONNECTOR_FACTORY)); // Note! We disable automatic reconnection on the session factory. The bridge needs to do the reconnection cf.setReconnectAttempts(0); cf.setBlockOnNonDurableSend(true); cf.setBlockOnDurableSend(true); cf.setCacheLargeMessagesClient(true); return cf; } }; cf0 = (ConnectionFactory) cff0.createConnectionFactory(); cf0xa = (XAConnectionFactory) cff0xa.createConnectionFactory(); cff1 = new ConnectionFactoryFactory() { @Override public ConnectionFactory createConnectionFactory() throws Exception { ActiveMQJMSConnectionFactory cf = (ActiveMQJMSConnectionFactory) ActiveMQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.CF, new TransportConfiguration(INVM_CONNECTOR_FACTORY, params1)); // Note! We disable automatic reconnection on the session factory. The bridge needs to do the reconnection cf.setReconnectAttempts(0); cf.setBlockOnNonDurableSend(true); cf.setBlockOnDurableSend(true); cf.setCacheLargeMessagesClient(true); return cf; } }; cff1xa = new ConnectionFactoryFactory() { @Override public XAConnectionFactory createConnectionFactory() throws Exception { ActiveMQXAConnectionFactory cf = (ActiveMQXAConnectionFactory) ActiveMQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.XA_CF, new TransportConfiguration(INVM_CONNECTOR_FACTORY, params1)); // Note! We disable automatic reconnection on the session factory. The bridge needs to do the reconnection cf.setReconnectAttempts(0); cf.setBlockOnNonDurableSend(true); cf.setBlockOnDurableSend(true); cf.setCacheLargeMessagesClient(true); return cf; } }; cf1 = (ConnectionFactory) cff1.createConnectionFactory(); cf1xa = (XAConnectionFactory) cff1xa.createConnectionFactory(); sourceQueueFactory = new DestinationFactory() { @Override public Destination createDestination() throws Exception { return (Destination) context0.lookup("/queue/sourceQueue"); } }; sourceQueue = (Queue) sourceQueueFactory.createDestination(); targetQueueFactory = new DestinationFactory() { @Override public Destination createDestination() throws Exception { return (Destination) context1.lookup("/queue/targetQueue"); } }; targetQueue = (Queue) targetQueueFactory.createDestination(); sourceTopicFactory = new DestinationFactory() { @Override public Destination createDestination() throws Exception { return (Destination) context0.lookup("/topic/sourceTopic"); } }; sourceTopic = (Topic) sourceTopicFactory.createDestination(); localTargetQueueFactory = new DestinationFactory() { @Override public Destination createDestination() throws Exception { return (Destination) context0.lookup("/queue/localTargetQueue"); } }; localTargetQueue = (Queue) localTargetQueueFactory.createDestination(); } protected void sendMessages(final ConnectionFactory cf, final Destination dest, final int start, final int numMessages, final boolean persistent, final boolean largeMessage) throws Exception { Connection conn = null; try { conn = cf.createConnection(); Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageProducer prod = sess.createProducer(dest); prod.setDeliveryMode(persistent ? DeliveryMode.PERSISTENT : DeliveryMode.NON_PERSISTENT); for (int i = start; i < start + numMessages; i++) { if (largeMessage) { BytesMessage msg = sess.createBytesMessage(); ((ActiveMQMessage) msg).setInputStream(ActiveMQTestBase.createFakeLargeStream(1024L * 1024L)); msg.setStringProperty("msg", "message" + i); prod.send(msg); } else { TextMessage tm = sess.createTextMessage("message" + i); prod.send(tm); } } } finally { if (conn != null) { conn.close(); } } } protected void checkMessagesReceived(final ConnectionFactory cf, final Destination dest, final QualityOfServiceMode qosMode, final int numMessages, final boolean longWaitForFirst, final boolean largeMessage) throws Exception { Connection conn = null; try { conn = cf.createConnection(); conn.start(); Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageConsumer cons = sess.createConsumer(dest); // Consume the messages Set<String> msgs = new HashSet<>(); int count = 0; // We always wait longer for the first one - it may take some time to arrive especially if we are // waiting for recovery to kick in while (true) { Message tm = cons.receive(count == 0 ? (longWaitForFirst ? 60000 : 10000) : 5000); if (tm == null) { break; } // log.info("Got message " + tm.getText()); if (largeMessage) { BytesMessage bmsg = (BytesMessage) tm; msgs.add(tm.getStringProperty("msg")); byte[] buffRead = new byte[1024]; for (int i = 0; i < 1024; i++) { Assert.assertEquals(1024, bmsg.readBytes(buffRead)); } } else { msgs.add(((TextMessage) tm).getText()); } count++; } if (qosMode == QualityOfServiceMode.ONCE_AND_ONLY_ONCE || qosMode == QualityOfServiceMode.DUPLICATES_OK) { // All the messages should be received for (int i = 0; i < numMessages; i++) { Assert.assertTrue("quality=" + qosMode + ", #=" + i + ", message=" + msgs, msgs.contains("message" + i)); } // Should be no more if (qosMode == QualityOfServiceMode.ONCE_AND_ONLY_ONCE) { Assert.assertEquals(numMessages, msgs.size()); } } else if (qosMode == QualityOfServiceMode.AT_MOST_ONCE) { // No *guarantee* that any messages will be received // but you still might get some depending on how/where the crash occurred } BridgeTestBase.log.trace("Check complete"); } finally { if (conn != null) { conn.close(); } } } protected void checkAllMessageReceivedInOrder(final ConnectionFactory cf, final Destination dest, final int start, final int numMessages, final boolean largeMessage) throws Exception { Connection conn = null; try { conn = cf.createConnection(); conn.start(); Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageConsumer cons = sess.createConsumer(dest); // Consume the messages for (int i = 0; i < numMessages; i++) { Message tm = cons.receive(30000); Assert.assertNotNull(tm); if (largeMessage) { BytesMessage bmsg = (BytesMessage) tm; Assert.assertEquals("message" + (i + start), tm.getStringProperty("msg")); byte[] buffRead = new byte[1024]; for (int j = 0; j < 1024; j++) { Assert.assertEquals(1024, bmsg.readBytes(buffRead)); } } else { Assert.assertEquals("message" + (i + start), ((TextMessage) tm).getText()); } } } finally { if (conn != null) { conn.close(); } } } public boolean checkEmpty(final Queue queue, final int index) throws Exception { ManagementService managementService = server0.getManagementService(); if (index == 1) { managementService = server1.getManagementService(); } QueueControl queueControl = (QueueControl) managementService.getResource(ResourceNames.QUEUE + queue.getQueueName()); //server may be closed if (queueControl != null) { queueControl.flushExecutor(); Long messageCount = queueControl.getMessageCount(); if (messageCount > 0) { queueControl.removeMessages(null); } } return true; } protected void checkNoSubscriptions(final Topic topic, final int index) throws Exception { ManagementService managementService = server0.getManagementService(); if (index == 1) { managementService = server1.getManagementService(); } AddressControl topicControl = (AddressControl) managementService.getResource(ResourceNames.ADDRESS + topic.getTopicName()); if (topicControl != null) { Assert.assertEquals(0, topicControl.getQueueNames().length); } } protected void removeAllMessages(final String queueName, final int index) throws Exception { ManagementService managementService = server0.getManagementService(); if (index == 1) { managementService = server1.getManagementService(); } QueueControl queueControl = (QueueControl) managementService.getResource("queue." + queueName); queueControl.removeMessages(null); } protected TransactionManager newTransactionManager() { return new TransactionManagerImple(); } // Inner classes ------------------------------------------------------------------- }