/**
* 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.store.jdbc;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Vector;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.jms.Connection;
import javax.jms.DeliveryMode;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.TopicSubscriber;
import javax.sql.DataSource;
import junit.framework.Test;
import org.apache.activemq.command.ActiveMQMessage;
import org.apache.activemq.command.ActiveMQTopic;
import org.apache.activemq.store.MessagePriorityTest;
import org.apache.activemq.store.PersistenceAdapter;
import org.apache.activemq.util.Wait;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class JDBCMessagePriorityTest extends MessagePriorityTest {
private static final Logger LOG = LoggerFactory.getLogger(JDBCMessagePriorityTest.class);
DataSource dataSource;
JDBCPersistenceAdapter jdbc;
@Override
protected PersistenceAdapter createPersistenceAdapter(boolean delete) throws Exception {
jdbc = new JDBCPersistenceAdapter();
dataSource = jdbc.getDataSource();
jdbc.deleteAllMessages();
jdbc.setCleanupPeriod(2000);
return jdbc;
}
// this cannot be a general test as kahaDB just has support for 3 priority levels
public void testDurableSubsReconnectWithFourLevels() throws Exception {
ActiveMQTopic topic = (ActiveMQTopic) sess.createTopic("TEST");
final String subName = "priorityDisconnect";
TopicSubscriber sub = sess.createDurableSubscriber(topic, subName);
sub.close();
final int MED_PRI = LOW_PRI + 1;
final int MED_HIGH_PRI = HIGH_PRI - 1;
ProducerThread lowPri = new ProducerThread(topic, MSG_NUM, LOW_PRI);
ProducerThread medPri = new ProducerThread(topic, MSG_NUM, MED_PRI);
ProducerThread medHighPri = new ProducerThread(topic, MSG_NUM, MED_HIGH_PRI);
ProducerThread highPri = new ProducerThread(topic, MSG_NUM, HIGH_PRI);
lowPri.start();
highPri.start();
medPri.start();
medHighPri.start();
lowPri.join();
highPri.join();
medPri.join();
medHighPri.join();
final int closeFrequency = MSG_NUM;
final int[] priorities = new int[]{HIGH_PRI, MED_HIGH_PRI, MED_PRI, LOW_PRI};
sub = sess.createDurableSubscriber(topic, subName);
for (int i = 0; i < MSG_NUM * 4; i++) {
Message msg = sub.receive(10000);
LOG.debug("received i=" + i + ", m=" + (msg != null ?
msg.getJMSMessageID() + ", priority: " + msg.getJMSPriority()
: null));
assertNotNull("Message " + i + " was null", msg);
assertEquals("Message " + i + " has wrong priority", priorities[i / MSG_NUM], msg.getJMSPriority());
if (i > 0 && i % closeFrequency == 0) {
LOG.info("Closing durable sub.. on: " + i);
sub.close();
sub = sess.createDurableSubscriber(topic, subName);
}
}
LOG.info("closing on done!");
sub.close();
}
public void initCombosForTestConcurrentDurableSubsReconnectWithXLevels() {
addCombinationValues("prioritizeMessages", new Object[]{Boolean.TRUE, Boolean.FALSE});
}
public void testConcurrentDurableSubsReconnectWithXLevels() throws Exception {
ActiveMQTopic topic = (ActiveMQTopic) sess.createTopic("TEST");
final String subName = "priorityDisconnect";
Connection consumerConn = factory.createConnection();
consumerConn.setClientID("priorityDisconnect");
consumerConn.start();
Session consumerSession = consumerConn.createSession(false, Session.AUTO_ACKNOWLEDGE);
TopicSubscriber sub = consumerSession.createDurableSubscriber(topic, subName);
sub.close();
final int maxPriority = 5;
final AtomicInteger[] messageCounts = new AtomicInteger[maxPriority];
final long[] messageIds = new long[maxPriority];
Vector<ProducerThread> producers = new Vector<ProducerThread>();
for (int priority = 0; priority < maxPriority; priority++) {
producers.add(new ProducerThread(topic, MSG_NUM, priority));
messageCounts[priority] = new AtomicInteger(0);
messageIds[priority] = 1l;
}
for (ProducerThread producer : producers) {
producer.start();
}
final int closeFrequency = MSG_NUM / 2;
HashMap<String, String> dups = new HashMap<String, String>();
sub = consumerSession.createDurableSubscriber(topic, subName);
for (int i = 0; i < MSG_NUM * maxPriority; i++) {
Message msg = sub.receive(10000);
LOG.debug("received i=" + i + ", m=" + (msg != null ?
msg.getJMSMessageID() + ", priority: " + msg.getJMSPriority()
: null));
assertNotNull("Message " + i + " was null, counts: " + Arrays.toString(messageCounts), msg);
assertNull("no duplicate message failed on : " + msg.getJMSMessageID(), dups.put(msg.getJMSMessageID(), subName));
messageCounts[msg.getJMSPriority()].incrementAndGet();
assertEquals("message is in order : " + msg,
messageIds[msg.getJMSPriority()],((ActiveMQMessage)msg).getMessageId().getProducerSequenceId());
messageIds[msg.getJMSPriority()]++;
if (i > 0 && i % closeFrequency == 0) {
LOG.info("Closing durable sub.. on: " + i + ", counts: " + Arrays.toString(messageCounts));
sub.close();
sub = consumerSession.createDurableSubscriber(topic, subName);
}
}
LOG.info("closing on done!");
sub.close();
consumerSession.close();
consumerConn.close();
for (ProducerThread producer : producers) {
producer.join();
}
}
public void initCombosForTestConcurrentRate() {
addCombinationValues("prefetchVal", new Object[]{new Integer(1), new Integer(500)});
}
public void testConcurrentRate() throws Exception {
ActiveMQTopic topic = (ActiveMQTopic) sess.createTopic("TEST");
final String subName = "priorityConcurrent";
Connection consumerConn = factory.createConnection();
consumerConn.setClientID("subName");
consumerConn.start();
Session consumerSession = consumerConn.createSession(false, Session.AUTO_ACKNOWLEDGE);
TopicSubscriber sub = consumerSession.createDurableSubscriber(topic, subName);
sub.close();
final int TO_SEND = 2000;
final Vector<Message> duplicates = new Vector<Message>();
final int[] dups = new int[TO_SEND * 4];
long start;
double max = 0, sum = 0;
MessageProducer messageProducer = sess.createProducer(topic);
TextMessage message = sess.createTextMessage();
for (int i = 0; i < TO_SEND; i++) {
int priority = i % 10;
message.setText(i + "-" + priority);
message.setIntProperty("seq", i);
message.setJMSPriority(priority);
if (i > 0 && i % 1000 == 0) {
LOG.info("Max send time: " + max + ". Sending message: " + message.getText());
}
start = System.currentTimeMillis();
messageProducer.send(message, DeliveryMode.PERSISTENT, message.getJMSPriority(), 0);
long duration = System.currentTimeMillis() - start;
max = Math.max(max, duration);
if (duration == max) {
LOG.info("new max: " + max + " on i=" + i + ", " + message.getText());
}
sum += duration;
}
LOG.info("Sent: " + TO_SEND + ", max send time: " + max);
double noConsumerAve = (sum * 100 / TO_SEND);
sub = consumerSession.createDurableSubscriber(topic, subName);
final AtomicInteger count = new AtomicInteger();
sub.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
try {
count.incrementAndGet();
if (count.get() % 100 == 0) {
LOG.info("onMessage: count: " + count.get() + ", " + ((TextMessage) message).getText() + ", seqNo " + message.getIntProperty("seq") + ", " + message.getJMSMessageID());
}
int seqNo = message.getIntProperty("seq");
if (dups[seqNo] == 0) {
dups[seqNo] = 1;
} else {
LOG.error("Duplicate: " + ((TextMessage) message).getText() + ", " + message.getJMSMessageID());
duplicates.add(message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
LOG.info("Activated consumer");
sum = max = 0;
for (int i = TO_SEND; i < (TO_SEND * 2); i++) {
int priority = i % 10;
message.setText(i + "-" + priority);
message.setIntProperty("seq", i);
message.setJMSPriority(priority);
if (i > 0 && i % 1000 == 0) {
LOG.info("Max send time: " + max + ". Sending message: " + message.getText());
}
start = System.currentTimeMillis();
messageProducer.send(message, DeliveryMode.PERSISTENT, message.getJMSPriority(), 0);
long duration = System.currentTimeMillis() - start;
max = Math.max(max, duration);
if (duration == max) {
LOG.info("new max: " + max + " on i=" + i + ", " + message.getText());
}
sum += duration;
}
LOG.info("Sent another: " + TO_SEND + ", max send time: " + max);
double withConsumerAve = (sum * 100 / TO_SEND);
final int reasonableMultiplier = 4; // not so reasonable, but on slow disks it can be
assertTrue("max X times as slow with consumer:" + withConsumerAve + " , noConsumerMax:" + noConsumerAve,
withConsumerAve < noConsumerAve * reasonableMultiplier);
Wait.waitFor(new Wait.Condition() {
@Override
public boolean isSatisified() throws Exception {
LOG.info("count: " + count.get());
return TO_SEND * 2 == count.get();
}
}, 60 * 1000);
assertTrue("No duplicates : " + duplicates, duplicates.isEmpty());
assertEquals("got all messages", TO_SEND * 2, count.get());
}
public void testCleanupPriorityDestination() throws Exception {
assertEquals("no messages pending", 0, messageTableCount());
ActiveMQTopic topic = (ActiveMQTopic) sess.createTopic("TEST");
final String subName = "priorityConcurrent";
Connection consumerConn = factory.createConnection();
consumerConn.setClientID("subName");
consumerConn.start();
Session consumerSession = consumerConn.createSession(false, Session.AUTO_ACKNOWLEDGE);
TopicSubscriber sub = consumerSession.createDurableSubscriber(topic, subName);
sub.close();
MessageProducer messageProducer = sess.createProducer(topic);
Message message = sess.createTextMessage();
message.setJMSPriority(2);
messageProducer.send(message, DeliveryMode.PERSISTENT, message.getJMSPriority(), 0);
message.setJMSPriority(5);
messageProducer.send(message, DeliveryMode.PERSISTENT, message.getJMSPriority(), 0);
assertEquals("two messages pending", 2, messageTableCount());
sub = consumerSession.createDurableSubscriber(topic, subName);
message = sub.receive(5000);
assertEquals("got high priority", 5, message.getJMSPriority());
waitForAck(5);
for (int i=0; i<10; i++) {
jdbc.cleanup();
}
assertEquals("one messages pending", 1, messageTableCount());
message = sub.receive(5000);
assertEquals("got high priority", 2, message.getJMSPriority());
waitForAck(2);
for (int i=0; i<10; i++) {
jdbc.cleanup();
}
assertEquals("no messages pending", 0, messageTableCount());
}
public void testCleanupNonPriorityDestination() throws Exception {
assertEquals("no messages pending", 0, messageTableCount());
ActiveMQTopic topic = (ActiveMQTopic) sess.createTopic("TEST_CLEANUP_NO_PRIORITY");
final String subName = "subName";
Connection consumerConn = factory.createConnection();
consumerConn.setClientID("subName");
consumerConn.start();
Session consumerSession = consumerConn.createSession(false, Session.AUTO_ACKNOWLEDGE);
TopicSubscriber sub = consumerSession.createDurableSubscriber(topic, subName);
sub.close();
MessageProducer messageProducer = sess.createProducer(topic);
Message message = sess.createTextMessage("ToExpire");
messageProducer.send(message, DeliveryMode.PERSISTENT, Message.DEFAULT_PRIORITY, 4000);
message = sess.createTextMessage("A");
messageProducer.send(message);
message = sess.createTextMessage("B");
messageProducer.send(message);
message = null;
assertEquals("three messages pending", 3, messageTableCount());
// let first message expire
TimeUnit.SECONDS.sleep(5);
sub = consumerSession.createDurableSubscriber(topic, subName);
message = sub.receive(5000);
assertNotNull("got message", message);
LOG.info("Got: " + message);
waitForAck(0, 1);
for (int i=0; i<10; i++) {
jdbc.cleanup();
}
assertEquals("one messages pending", 1, messageTableCount());
message = sub.receive(5000);
assertNotNull("got message two", message);
LOG.info("Got: " + message);
waitForAck(0, 2);
for (int i=0; i<10; i++) {
jdbc.cleanup();
}
assertEquals("no messages pending", 0, messageTableCount());
}
private int messageTableCount() throws Exception {
int count = -1;
java.sql.Connection c = dataSource.getConnection();
try {
PreparedStatement s = c.prepareStatement("SELECT COUNT(*) FROM ACTIVEMQ_MSGS");
ResultSet rs = s.executeQuery();
if (rs.next()) {
count = rs.getInt(1);
}
} finally {
if (c!=null) {
c.close();
}
}
return count;
}
private void waitForAck(final int priority) throws Exception {
waitForAck(priority, 0);
}
private void waitForAck(final int priority, final int minId) throws Exception {
assertTrue("got ack for " + priority, Wait.waitFor(new Wait.Condition() {
@Override
public boolean isSatisified() throws Exception {
int id = 0;
java.sql.Connection c = dataSource.getConnection();
try {
PreparedStatement s = c.prepareStatement("SELECT LAST_ACKED_ID FROM ACTIVEMQ_ACKS WHERE PRIORITY=" + priority);
ResultSet rs = s.executeQuery();
if (rs.next()) {
id = rs.getInt(1);
}
} finally {
if (c!=null) {
c.close();
}
}
return id>minId;
}
}));
}
@SuppressWarnings("unused")
private int messageTableDump() throws Exception {
int count = -1;
java.sql.Connection c = dataSource.getConnection();
try {
PreparedStatement s = c.prepareStatement("SELECT * FROM ACTIVEMQ_MSGS");
ResultSet rs = s.executeQuery();
if (rs.next()) {
count = rs.getInt(1);
}
} finally {
if (c!=null) {
c.close();
}
}
return count;
}
public static Test suite() {
return suite(JDBCMessagePriorityTest.class);
}
}