blob: f901d3d7ebff9210f1a519f8ab0972fb5bdf8165 [file] [log] [blame]
/**
* 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.bugs;
import static org.junit.Assert.assertTrue;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.jms.Connection;
import javax.jms.Destination;
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 org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.broker.BrokerService;
import org.apache.activemq.command.ActiveMQDestination;
import org.apache.activemq.network.DiscoveryNetworkConnector;
import org.apache.activemq.network.NetworkConnector;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AMQ3274Test {
private static final transient Logger LOG = LoggerFactory.getLogger(AMQ3274Test.class);
protected static int Next_broker_num = 0;
protected EmbeddedTcpBroker broker1;
protected EmbeddedTcpBroker broker2;
protected int nextEchoId = 0;
protected boolean testError = false;
protected int echoResponseFill = 0; // Number of "filler" response messages per request
public AMQ3274Test() throws Exception {
broker1 = new EmbeddedTcpBroker();
broker2 = new EmbeddedTcpBroker();
broker1.coreConnectTo(broker2, true);
broker2.coreConnectTo(broker1, true);
}
public void logMessage(String msg) {
System.out.println(msg);
System.out.flush();
}
public void testMessages(Session sess, MessageProducer req_prod, Destination resp_dest, int num_msg) throws Exception {
MessageConsumer resp_cons;
TextMessage msg;
MessageClient cons_client;
int cur;
int tot_expected;
resp_cons = sess.createConsumer(resp_dest);
cons_client = new MessageClient(resp_cons, num_msg);
cons_client.start();
cur = 0;
while ((cur < num_msg) && (!testError)) {
msg = sess.createTextMessage("MSG AAAA " + cur);
msg.setIntProperty("SEQ", 100 + cur);
msg.setStringProperty("TEST", "TOPO");
msg.setJMSReplyTo(resp_dest);
if (cur == (num_msg - 1))
msg.setBooleanProperty("end-of-response", true);
req_prod.send(msg);
cur++;
}
cons_client.waitShutdown(5000);
if (cons_client.shutdown()) {
LOG.debug("Consumer client shutdown complete");
} else {
LOG.debug("Consumer client shutdown incomplete!!!");
}
tot_expected = num_msg * (echoResponseFill + 1);
if (cons_client.getNumMsgReceived() == tot_expected) {
LOG.info("Have " + tot_expected + " messages, as-expected");
} else {
LOG.error("Have " + cons_client.getNumMsgReceived() + " messages; expected " + tot_expected);
testError = true;
}
resp_cons.close();
}
/**
* Test one destination between the given "producer broker" and
* "consumer broker" specified.
*/
public void testOneDest(Connection conn, Session sess, Destination cons_dest, String prod_broker_url, String cons_broker_url, int num_msg) throws Exception {
int echo_id;
EchoService echo_svc;
String echo_queue_name;
Destination prod_dest;
MessageProducer msg_prod;
synchronized (this) {
echo_id = this.nextEchoId;
this.nextEchoId++;
}
echo_queue_name = "echo.queue." + echo_id;
LOG.trace("destroying the echo queue in case an old one exists");
removeQueue(conn, echo_queue_name);
echo_svc = new EchoService(echo_queue_name, prod_broker_url);
echo_svc.start();
LOG.trace("Creating echo queue and producer");
prod_dest = sess.createQueue(echo_queue_name);
msg_prod = sess.createProducer(prod_dest);
testMessages(sess, msg_prod, cons_dest, num_msg);
echo_svc.shutdown();
msg_prod.close();
}
/**
* TEST TEMPORARY TOPICS
*/
public void testTempTopic(String prod_broker_url, String cons_broker_url) throws Exception {
Connection conn;
Session sess;
Destination cons_dest;
int num_msg;
num_msg = 5;
LOG.info("TESTING TEMP TOPICS " + prod_broker_url + " -> " + cons_broker_url + " (" + num_msg + " messages)");
conn = createConnection(cons_broker_url);
conn.start();
sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
LOG.trace("Creating destination");
cons_dest = sess.createTemporaryTopic();
testOneDest(conn, sess, cons_dest, prod_broker_url, cons_broker_url, num_msg);
sess.close();
conn.close();
}
/**
* TEST TOPICS
*/
public void testTopic(String prod_broker_url, String cons_broker_url) throws Exception {
int num_msg;
Connection conn;
Session sess;
String topic_name;
Destination cons_dest;
num_msg = 5;
LOG.info("TESTING TOPICS " + prod_broker_url + " -> " + cons_broker_url + " (" + num_msg + " messages)");
conn = createConnection(cons_broker_url);
conn.start();
sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
topic_name = "topotest2.perm.topic";
LOG.trace("Removing existing Topic");
removeTopic(conn, topic_name);
LOG.trace("Creating Topic, " + topic_name);
cons_dest = sess.createTopic(topic_name);
testOneDest(conn, sess, cons_dest, prod_broker_url, cons_broker_url, num_msg);
removeTopic(conn, topic_name);
sess.close();
conn.close();
}
/**
* TEST TEMPORARY QUEUES
*/
public void testTempQueue(String prod_broker_url, String cons_broker_url) throws Exception {
int num_msg;
Connection conn;
Session sess;
Destination cons_dest;
num_msg = 5;
LOG.info("TESTING TEMP QUEUES " + prod_broker_url + " -> " + cons_broker_url + " (" + num_msg + " messages)");
conn = createConnection(cons_broker_url);
conn.start();
sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
LOG.trace("Creating destination");
cons_dest = sess.createTemporaryQueue();
testOneDest(conn, sess, cons_dest, prod_broker_url, cons_broker_url, num_msg);
sess.close();
conn.close();
}
/**
* TEST QUEUES
*/
public void testQueue(String prod_broker_url, String cons_broker_url) throws Exception {
int num_msg;
Connection conn;
Session sess;
String queue_name;
Destination cons_dest;
num_msg = 5;
LOG.info("TESTING QUEUES " + prod_broker_url + " -> " + cons_broker_url + " (" + num_msg + " messages)");
conn = createConnection(cons_broker_url);
conn.start();
sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
queue_name = "topotest2.perm.queue";
LOG.trace("Removing existing Queue");
removeQueue(conn, queue_name);
LOG.trace("Creating Queue, " + queue_name);
cons_dest = sess.createQueue(queue_name);
testOneDest(conn, sess, cons_dest, prod_broker_url, cons_broker_url, num_msg);
removeQueue(conn, queue_name);
sess.close();
conn.close();
}
@Test
public void run() throws Exception {
Thread start1;
Thread start2;
testError = false;
// Use threads to avoid startup deadlock since the first broker started waits until
// it knows the name of the remote broker before finishing its startup, which means
// the remote must already be running.
start1 = new Thread() {
public void run() {
try {
broker1.start();
} catch (Exception ex) {
LOG.error(null, ex);
}
}
};
start2 = new Thread() {
public void run() {
try {
broker2.start();
} catch (Exception ex) {
LOG.error(null, ex);
}
}
};
start1.start();
start2.start();
start1.join();
start2.join();
if (!testError) {
this.testTempTopic(broker1.getConnectionUrl(), broker2.getConnectionUrl());
}
if (!testError) {
this.testTempQueue(broker1.getConnectionUrl(), broker2.getConnectionUrl());
}
if (!testError) {
this.testTopic(broker1.getConnectionUrl(), broker2.getConnectionUrl());
}
if (!testError) {
this.testQueue(broker1.getConnectionUrl(), broker2.getConnectionUrl());
}
Thread.sleep(100);
shutdown();
assertTrue(!testError);
}
public void shutdown() throws Exception {
broker1.stop();
broker2.stop();
}
/**
* @param args
* the command line arguments
*/
public static void main(String[] args) {
AMQ3274Test main_obj;
try {
main_obj = new AMQ3274Test();
main_obj.run();
} catch (Exception ex) {
ex.printStackTrace();
LOG.error(null, ex);
System.exit(0);
}
}
protected Connection createConnection(String url) throws Exception {
return org.apache.activemq.ActiveMQConnection.makeConnection(url);
}
protected static void removeQueue(Connection conn, String dest_name) throws java.lang.Exception {
org.apache.activemq.command.ActiveMQDestination dest;
if (conn instanceof org.apache.activemq.ActiveMQConnection) {
dest = org.apache.activemq.command.ActiveMQDestination.createDestination(dest_name,
(byte) org.apache.activemq.command.ActiveMQDestination.QUEUE_TYPE);
((org.apache.activemq.ActiveMQConnection) conn).destroyDestination(dest);
}
}
protected static void removeTopic(Connection conn, String dest_name) throws java.lang.Exception {
org.apache.activemq.command.ActiveMQDestination dest;
if (conn instanceof org.apache.activemq.ActiveMQConnection) {
dest = org.apache.activemq.command.ActiveMQDestination.createDestination(dest_name,
(byte) org.apache.activemq.command.ActiveMQDestination.TOPIC_TYPE);
((org.apache.activemq.ActiveMQConnection) conn).destroyDestination(dest);
}
}
@SuppressWarnings("rawtypes")
public static String fmtMsgInfo(Message msg) throws Exception {
StringBuilder msg_desc;
String prop;
Enumeration prop_enum;
msg_desc = new StringBuilder();
msg_desc = new StringBuilder();
if (msg instanceof TextMessage) {
msg_desc.append(((TextMessage) msg).getText());
} else {
msg_desc.append("[");
msg_desc.append(msg.getClass().getName());
msg_desc.append("]");
}
prop_enum = msg.getPropertyNames();
while (prop_enum.hasMoreElements()) {
prop = (String) prop_enum.nextElement();
msg_desc.append("; ");
msg_desc.append(prop);
msg_desc.append("=");
msg_desc.append(msg.getStringProperty(prop));
}
return msg_desc.toString();
}
// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////// INTERNAL CLASSES
// /////////////////////////////////////////////////
// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
protected class EmbeddedTcpBroker {
protected BrokerService brokerSvc;
protected int brokerNum;
protected String brokerName;
protected String brokerId;
protected int port;
protected String tcpUrl;
public EmbeddedTcpBroker() throws Exception {
brokerSvc = new BrokerService();
synchronized (this.getClass()) {
brokerNum = Next_broker_num;
Next_broker_num++;
}
brokerName = "broker" + brokerNum;
brokerId = "b" + brokerNum;
brokerSvc.setBrokerName(brokerName);
brokerSvc.setBrokerId(brokerId);
brokerSvc.setPersistent(false);
brokerSvc.setUseJmx(false);
tcpUrl = brokerSvc.addConnector("tcp://localhost:0").getPublishableConnectString();
}
public Connection createConnection() throws URISyntaxException, JMSException {
Connection result;
result = org.apache.activemq.ActiveMQConnection.makeConnection(this.tcpUrl);
return result;
}
public String getConnectionUrl() {
return this.tcpUrl;
}
/**
* Create network connections to the given broker using the
* network-connector configuration of CORE brokers (e.g.
* core1.bus.dev1.coresys.tmcs)
*
* @param other
* @param duplex_f
*/
public void coreConnectTo(EmbeddedTcpBroker other, boolean duplex_f) throws Exception {
this.makeConnectionTo(other, duplex_f, true);
this.makeConnectionTo(other, duplex_f, false);
}
public void start() throws Exception {
brokerSvc.start();
}
public void stop() throws Exception {
brokerSvc.stop();
}
/**
* Make one connection to the other embedded broker, of the specified
* type (queue or topic) using the standard CORE broker networking.
*
* @param other
* @param duplex_f
* @param queue_f
* @throws Exception
*/
protected void makeConnectionTo(EmbeddedTcpBroker other, boolean duplex_f, boolean queue_f) throws Exception {
NetworkConnector nw_conn;
String prefix;
ActiveMQDestination excl_dest;
ArrayList<ActiveMQDestination> excludes;
nw_conn = new DiscoveryNetworkConnector(new URI("static:(" + other.tcpUrl + ")"));
nw_conn.setDuplex(duplex_f);
if (queue_f)
nw_conn.setConduitSubscriptions(false);
else
nw_conn.setConduitSubscriptions(true);
nw_conn.setNetworkTTL(5);
nw_conn.setSuppressDuplicateQueueSubscriptions(true);
nw_conn.setDecreaseNetworkConsumerPriority(true);
nw_conn.setBridgeTempDestinations(true);
if (queue_f) {
prefix = "queue";
excl_dest = ActiveMQDestination.createDestination(">", ActiveMQDestination.TOPIC_TYPE);
} else {
prefix = "topic";
excl_dest = ActiveMQDestination.createDestination(">", ActiveMQDestination.QUEUE_TYPE);
}
excludes = new ArrayList<ActiveMQDestination>();
excludes.add(excl_dest);
nw_conn.setExcludedDestinations(excludes);
if (duplex_f)
nw_conn.setName(this.brokerId + "<-" + prefix + "->" + other.brokerId);
else
nw_conn.setName(this.brokerId + "-" + prefix + "->" + other.brokerId);
brokerSvc.addNetworkConnector(nw_conn);
}
}
protected class MessageClient extends java.lang.Thread {
protected MessageConsumer msgCons;
protected boolean shutdownInd;
protected int expectedCount;
protected int lastSeq = 0;
protected int msgCount = 0;
protected boolean haveFirstSeq;
protected CountDownLatch shutdownLatch;
public MessageClient(MessageConsumer cons, int num_to_expect) {
msgCons = cons;
expectedCount = (num_to_expect * (echoResponseFill + 1));
shutdownLatch = new CountDownLatch(1);
}
public void run() {
CountDownLatch latch;
try {
synchronized (this) {
latch = shutdownLatch;
}
shutdownInd = false;
processMessages();
latch.countDown();
} catch (Exception exc) {
LOG.error("message client error", exc);
}
}
public void waitShutdown(long timeout) {
CountDownLatch latch;
try {
synchronized (this) {
latch = shutdownLatch;
}
if (latch != null)
latch.await(timeout, TimeUnit.MILLISECONDS);
else
LOG.info("echo client shutdown: client does not appear to be active");
} catch (InterruptedException int_exc) {
LOG.warn("wait for message client shutdown interrupted", int_exc);
}
}
public boolean shutdown() {
boolean down_ind;
if (!shutdownInd) {
shutdownInd = true;
}
waitShutdown(200);
synchronized (this) {
if ((shutdownLatch == null) || (shutdownLatch.getCount() == 0))
down_ind = true;
else
down_ind = false;
}
return down_ind;
}
public int getNumMsgReceived() {
return msgCount;
}
protected void processMessages() throws Exception {
Message in_msg;
haveFirstSeq = false;
while ((!shutdownInd) && (!testError)) {
in_msg = msgCons.receive(100);
if (in_msg != null) {
msgCount++;
checkMessage(in_msg);
}
}
}
protected void checkMessage(Message in_msg) throws Exception {
int seq;
LOG.debug("received message " + fmtMsgInfo(in_msg));
if (in_msg.propertyExists("SEQ")) {
seq = in_msg.getIntProperty("SEQ");
if ((haveFirstSeq) && (seq != (lastSeq + 1))) {
LOG.error("***ERROR*** incorrect sequence number; expected " + Integer.toString(lastSeq + 1) + " but have " + Integer.toString(seq));
testError = true;
}
lastSeq = seq;
if (msgCount > expectedCount) {
LOG.warn("*** have more messages than expected; have " + msgCount + "; expect " + expectedCount);
testError = true;
}
}
if (in_msg.propertyExists("end-of-response")) {
LOG.trace("received end-of-response message");
shutdownInd = true;
}
}
}
protected class EchoService extends java.lang.Thread {
protected String destName;
protected Connection jmsConn;
protected Session sess;
protected MessageConsumer msg_cons;
protected boolean Shutdown_ind;
protected Destination req_dest;
protected Destination resp_dest;
protected MessageProducer msg_prod;
protected CountDownLatch waitShutdown;
public EchoService(String dest, Connection broker_conn) throws Exception {
destName = dest;
jmsConn = broker_conn;
Shutdown_ind = false;
sess = jmsConn.createSession(false, Session.AUTO_ACKNOWLEDGE);
req_dest = sess.createQueue(destName);
msg_cons = sess.createConsumer(req_dest);
jmsConn.start();
waitShutdown = new CountDownLatch(1);
}
public EchoService(String dest, String broker_url) throws Exception {
this(dest, ActiveMQConnection.makeConnection(broker_url));
}
public void run() {
Message req;
try {
LOG.info("STARTING ECHO SERVICE");
while (!Shutdown_ind) {
req = msg_cons.receive(100);
if (req != null) {
if (LOG.isDebugEnabled())
LOG.debug("ECHO request message " + req.toString());
resp_dest = req.getJMSReplyTo();
if (resp_dest != null) {
msg_prod = sess.createProducer(resp_dest);
msg_prod.send(req);
msg_prod.close();
msg_prod = null;
} else {
LOG.warn("invalid request: no reply-to destination given");
}
}
}
} catch (Exception ex) {
LOG.error(null, ex);
} finally {
LOG.info("shutting down test echo service");
try {
jmsConn.stop();
} catch (javax.jms.JMSException jms_exc) {
LOG.warn("error on shutting down JMS connection", jms_exc);
}
synchronized (this) {
waitShutdown.countDown();
}
}
}
/**
* Shut down the service, waiting up to 3 seconds for the service to
* terminate.
*/
public void shutdown() {
CountDownLatch wait_l;
synchronized (this) {
wait_l = waitShutdown;
}
Shutdown_ind = true;
try {
if (wait_l != null) {
if (wait_l.await(3000, TimeUnit.MILLISECONDS)) {
LOG.info("echo service shutdown complete");
} else {
LOG.warn("timeout waiting for echo service shutdown");
}
} else {
LOG.info("echo service shutdown: service does not appear to be active");
}
} catch (InterruptedException int_exc) {
LOG.warn("interrupted while waiting for echo service shutdown");
}
}
}
}