diff options
author | Rajith Muditha Attapattu <rajith@apache.org> | 2011-05-27 15:44:23 +0000 |
---|---|---|
committer | Rajith Muditha Attapattu <rajith@apache.org> | 2011-05-27 15:44:23 +0000 |
commit | 66765100f4257159622cefe57bed50125a5ad017 (patch) | |
tree | a88ee23bb194eb91f0ebb2d9b23ff423e3ea8e37 /qpid/java/broker/src/test/java/org | |
parent | 1aeaa7b16e5ce54f10c901d75c4d40f9f88b9db6 (diff) | |
parent | 88b98b2f4152ef59a671fad55a0d08338b6b78ca (diff) | |
download | qpid-python-66765100f4257159622cefe57bed50125a5ad017.tar.gz |
Creating a branch for experimenting with some ideas for JMS client.rajith_jms_client
git-svn-id: https://svn.apache.org/repos/asf/qpid/branches/rajith_jms_client@1128369 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'qpid/java/broker/src/test/java/org')
106 files changed, 20573 insertions, 0 deletions
diff --git a/qpid/java/broker/src/test/java/org/apache/log4j/xml/QpidLog4JConfiguratorTest.java b/qpid/java/broker/src/test/java/org/apache/log4j/xml/QpidLog4JConfiguratorTest.java new file mode 100644 index 0000000000..445c7d57f2 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/log4j/xml/QpidLog4JConfiguratorTest.java @@ -0,0 +1,396 @@ +/* + * 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.log4j.xml; + + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + +import org.apache.log4j.xml.QpidLog4JConfigurator.IllegalLoggerLevelException; + +import junit.framework.TestCase; + +public class QpidLog4JConfiguratorTest extends TestCase +{ + private static final String NEWLINE = System.getProperty("line.separator"); + + private File _testConfigFile; + + private File createTempTestLog4JConfig(String loggerLevel,String rootLoggerLevel, boolean missingTagClose, boolean incorrectAttribute) + { + File tmpFile = null; + try + { + tmpFile = File.createTempFile("QpidLog4JConfiguratorTestLog4jConfig", ".tmp"); + tmpFile.deleteOnExit(); + + FileWriter fstream = new FileWriter(tmpFile); + BufferedWriter writer = new BufferedWriter(fstream); + + writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"+NEWLINE); + writer.write("<!DOCTYPE log4j:configuration SYSTEM \"log4j.dtd\">"+NEWLINE); + + writer.write("<log4j:configuration xmlns:log4j=\"http://jakarta.apache.org/log4j/\" debug=\"null\" " + + "threshold=\"null\">"+NEWLINE); + + writer.write(" <appender class=\"org.apache.log4j.ConsoleAppender\" name=\"STDOUT\">"+NEWLINE); + writer.write(" <layout class=\"org.apache.log4j.PatternLayout\">"+NEWLINE); + writer.write(" <param name=\"ConversionPattern\" value=\"%d %-5p [%t] %C{2} (%F:%L) - %m%n\"/>"+NEWLINE); + writer.write(" </layout>"+NEWLINE); + writer.write(" </appender>"+NEWLINE); + + String closeTag="/"; + if(missingTagClose) + { + closeTag=""; + } + + //Example of a 'category' with a 'priority' + writer.write(" <category additivity=\"true\" name=\"logger1\">"+NEWLINE); + writer.write(" <priority value=\"" + loggerLevel+ "\"" + closeTag + ">"+NEWLINE); + writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE); + writer.write(" </category>"+NEWLINE); + + String attributeName="value"; + if(incorrectAttribute) + { + attributeName="values"; + } + + //Example of a 'category' with a 'level' + writer.write(" <category additivity=\"true\" name=\"logger2\">"+NEWLINE); + writer.write(" <level " + attributeName + "=\"" + loggerLevel+ "\"/>"+NEWLINE); + writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE); + writer.write(" </category>"+NEWLINE); + + //Example of a 'logger' with a 'level' + writer.write(" <logger additivity=\"true\" name=\"logger3\">"+NEWLINE); + writer.write(" <level value=\"" + loggerLevel+ "\"/>"+NEWLINE); + writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE); + writer.write(" </logger>"+NEWLINE); + + //'root' logger + writer.write(" <root>"+NEWLINE); + writer.write(" <priority value=\"" + rootLoggerLevel+ "\"/>"+NEWLINE); + writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE); + writer.write(" </root>"+NEWLINE); + + writer.write("</log4j:configuration>"+NEWLINE); + + writer.flush(); + writer.close(); + } + catch (IOException e) + { + fail("Unable to create temporary test log4j configuration"); + } + + return tmpFile; + } + + + + //******* Test Methods ******* // + + public void testCheckLevelsAndStrictParser() + { + //try the valid logger levels + _testConfigFile = createTempTestLog4JConfig("all", "info", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("trace", "info", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("debug", "info", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("info", "info", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("warn", "info", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("error", "info", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("fatal", "info", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("off", "info", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("null", "info", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("inherited", "info", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + //now try an invalid logger level + _testConfigFile = createTempTestLog4JConfig("madeup", "info", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + fail("IllegalLoggerLevelException expected, invalid levels used"); + } + catch (IllegalLoggerLevelException e) + { + //expected, ignore + } + catch (IOException e) + { + fail("Incorrect Exception, expected an IllegalLoggerLevelException"); + } + + + + //now try the valid rootLogger levels + _testConfigFile = createTempTestLog4JConfig("info", "all", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("info", "trace", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("info", "debug", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("info", "info", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("info", "warn", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("info", "error", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("info", "fatal", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("info", "off", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("info", "null", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("info", "inherited", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("info", "debug", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + //now try an invalid logger level + _testConfigFile = createTempTestLog4JConfig("info", "madeup", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + fail("IllegalLoggerLevelException expected, invalid levels used"); + } + catch (IllegalLoggerLevelException e) + { + //expected, ignore + } + catch (IOException e) + { + fail("Incorrect Exception, expected an IllegalLoggerLevelException"); + } + + + + //now try invalid xml + _testConfigFile = createTempTestLog4JConfig("info", "info", true, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + fail("IOException expected, malformed XML used"); + } + catch (IllegalLoggerLevelException e) + { + fail("Incorrect Exception, expected an IOException"); + } + catch (IOException e) + { + //expected, ignore + } + + _testConfigFile = createTempTestLog4JConfig("info", "info", false, true); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + fail("IOException expected, malformed XML used"); + } + catch (IllegalLoggerLevelException e) + { + //expected, ignore + } + catch (IOException e) + { + fail("Incorrect Exception, expected an IllegalLoggerLevelException"); + } + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/AMQBrokerManagerMBeanTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/AMQBrokerManagerMBeanTest.java new file mode 100644 index 0000000000..6c135e8ba7 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/AMQBrokerManagerMBeanTest.java @@ -0,0 +1,94 @@ +/* + * + * 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.qpid.server; + +import junit.framework.TestCase; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.management.common.mbeans.ManagedBroker; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.server.util.InternalBrokerBaseCase; +import org.apache.qpid.server.virtualhost.VirtualHostImpl; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class AMQBrokerManagerMBeanTest extends InternalBrokerBaseCase +{ + private QueueRegistry _queueRegistry; + private ExchangeRegistry _exchangeRegistry; + private VirtualHost _vHost; + + public void testExchangeOperations() throws Exception + { + String exchange1 = "testExchange1_" + System.currentTimeMillis(); + String exchange2 = "testExchange2_" + System.currentTimeMillis(); + String exchange3 = "testExchange3_" + System.currentTimeMillis(); + + assertTrue(_exchangeRegistry.getExchange(new AMQShortString(exchange1)) == null); + assertTrue(_exchangeRegistry.getExchange(new AMQShortString(exchange2)) == null); + assertTrue(_exchangeRegistry.getExchange(new AMQShortString(exchange3)) == null); + + + ManagedBroker mbean = new AMQBrokerManagerMBean((VirtualHostImpl.VirtualHostMBean) _vHost.getManagedObject()); + mbean.createNewExchange(exchange1, "direct", false); + mbean.createNewExchange(exchange2, "topic", false); + mbean.createNewExchange(exchange3, "headers", false); + + assertTrue(_exchangeRegistry.getExchange(new AMQShortString(exchange1)) != null); + assertTrue(_exchangeRegistry.getExchange(new AMQShortString(exchange2)) != null); + assertTrue(_exchangeRegistry.getExchange(new AMQShortString(exchange3)) != null); + + mbean.unregisterExchange(exchange1); + mbean.unregisterExchange(exchange2); + mbean.unregisterExchange(exchange3); + + assertTrue(_exchangeRegistry.getExchange(new AMQShortString(exchange1)) == null); + assertTrue(_exchangeRegistry.getExchange(new AMQShortString(exchange2)) == null); + assertTrue(_exchangeRegistry.getExchange(new AMQShortString(exchange3)) == null); + } + + public void testQueueOperations() throws Exception + { + String queueName = "testQueue_" + System.currentTimeMillis(); + + ManagedBroker mbean = new AMQBrokerManagerMBean((VirtualHostImpl.VirtualHostMBean) _vHost.getManagedObject()); + + assertTrue(_queueRegistry.getQueue(new AMQShortString(queueName)) == null); + + mbean.createNewQueue(queueName, "test", false); + assertTrue(_queueRegistry.getQueue(new AMQShortString(queueName)) != null); + + mbean.deleteQueue(queueName); + assertTrue(_queueRegistry.getQueue(new AMQShortString(queueName)) == null); + } + + @Override + public void setUp() throws Exception + { + super.setUp(); + IApplicationRegistry appRegistry = ApplicationRegistry.getInstance(); + _vHost = appRegistry.getVirtualHostRegistry().getVirtualHost("test"); + _queueRegistry = _vHost.getQueueRegistry(); + _exchangeRegistry = _vHost.getExchangeRegistry(); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/ExtractResendAndRequeueTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/ExtractResendAndRequeueTest.java new file mode 100644 index 0000000000..d2408ba21f --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/ExtractResendAndRequeueTest.java @@ -0,0 +1,255 @@ +/* + * + * 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.qpid.server; + +import junit.framework.TestCase; +import org.apache.qpid.server.ack.UnacknowledgedMessageMapImpl; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.queue.SimpleQueueEntryList; +import org.apache.qpid.server.queue.MockAMQMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.MockAMQQueue; +import org.apache.qpid.server.message.AMQMessage; +import org.apache.qpid.server.queue.QueueEntryIterator; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.subscription.MockSubscription; +import org.apache.qpid.server.store.MemoryMessageStore; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.AMQException; + +import java.util.Map; +import java.util.LinkedHashMap; +import java.util.LinkedList; + +/** + * QPID-1385 : Race condition between added to unacked map and resending due to a rollback. + * + * In AMQChannel _unackedMap.clear() was done after the visit. This meant that the clear was not in the same + * synchronized block as as the preparation to resend. + * + * This clearing/prep for resend was done as a result of the rollback call. HOWEVER, the delivery thread was still + * in the process of sending messages to the client. It is therefore possible that a message could block on the + * _unackedMap lock waiting for the visit to compelete so that it can add the new message to the unackedMap.... + * which is then cleared by the resend/rollback thread. + * + * This problem was encountered by the testSend2ThenRollback test. + * + * To try and increase the chance of the race condition occuring this test will send multiple messages so that the + * delivery thread will be in progress while the rollback method is called. Hopefully this will cause the + * deliveryTag to be lost + */ +public class ExtractResendAndRequeueTest extends TestCase +{ + + UnacknowledgedMessageMapImpl _unacknowledgedMessageMap; + private static final int INITIAL_MSG_COUNT = 10; + private AMQQueue _queue = new MockAMQQueue(getName()); + private MessageStore _messageStore = new MemoryMessageStore(); + private LinkedList<QueueEntry> _referenceList = new LinkedList<QueueEntry>(); + + @Override + public void setUp() throws AMQException + { + _unacknowledgedMessageMap = new UnacknowledgedMessageMapImpl(100); + + long id = 0; + SimpleQueueEntryList list = new SimpleQueueEntryList(_queue); + + // Add initial messages to QueueEntryList + for (int count = 0; count < INITIAL_MSG_COUNT; count++) + { + AMQMessage msg = new MockAMQMessage(id); + + list.add(msg); + + //Increment ID; + id++; + } + + // Iterate through the QueueEntryList and add entries to unacknowledgeMessageMap and referecenList + QueueEntryIterator queueEntries = list.iterator(); + while(queueEntries.advance()) + { + QueueEntry entry = queueEntries.getNode(); + _unacknowledgedMessageMap.add(entry.getMessage().getMessageNumber(), entry); + + // Store the entry for future inspection + _referenceList.add(entry); + } + + assertEquals("Map does not contain correct setup data", INITIAL_MSG_COUNT, _unacknowledgedMessageMap.size()); + } + + /** + * Helper method to create a new subscription and aquire the given messages. + * + * @param messageList The messages to aquire + * + * @return Subscription that performed the aquire + */ + private Subscription createSubscriptionAndAquireMessages(LinkedList<QueueEntry> messageList) + { + Subscription subscription = new MockSubscription(); + + // Aquire messages in subscription + for (QueueEntry entry : messageList) + { + entry.acquire(subscription); + } + + return subscription; + } + + /** + * This is the normal consumer rollback method. + * + * An active consumer that has aquired messages expects those messasges to be reset when rollback is requested. + * + * This test validates that the msgToResend map includes all the messages and none are left behind. + * + * @throws AMQException the visit interface throws this + */ + public void testResend() throws AMQException + { + //We don't need the subscription object here. + createSubscriptionAndAquireMessages(_referenceList); + + final Map<Long, QueueEntry> msgToRequeue = new LinkedHashMap<Long, QueueEntry>(); + final Map<Long, QueueEntry> msgToResend = new LinkedHashMap<Long, QueueEntry>(); + + // requeueIfUnabletoResend doesn't matter here. + _unacknowledgedMessageMap.visit(new ExtractResendAndRequeue(_unacknowledgedMessageMap, msgToRequeue, + msgToResend, true, _messageStore)); + + assertEquals("Message count for resend not correct.", INITIAL_MSG_COUNT, msgToResend.size()); + assertEquals("Message count for requeue not correct.", 0, msgToRequeue.size()); + assertEquals("Map was not emptied", 0, _unacknowledgedMessageMap.size()); + } + + /** + * This is the normal consumer close method. + * + * When a consumer that has aquired messages expects closes the messages that it has aquired should be removed from + * the unacknowledgeMap and placed in msgToRequeue + * + * This test validates that the msgToRequeue map includes all the messages and none are left behind. + * + * @throws AMQException the visit interface throws this + */ + public void testRequeueDueToSubscriptionClosure() throws AMQException + { + Subscription subscription = createSubscriptionAndAquireMessages(_referenceList); + + // Close subscription + subscription.close(); + + final Map<Long, QueueEntry> msgToRequeue = new LinkedHashMap<Long, QueueEntry>(); + final Map<Long, QueueEntry> msgToResend = new LinkedHashMap<Long, QueueEntry>(); + + // requeueIfUnabletoResend doesn't matter here. + _unacknowledgedMessageMap.visit(new ExtractResendAndRequeue(_unacknowledgedMessageMap, msgToRequeue, + msgToResend, true, _messageStore)); + + assertEquals("Message count for resend not correct.", 0, msgToResend.size()); + assertEquals("Message count for requeue not correct.", INITIAL_MSG_COUNT, msgToRequeue.size()); + assertEquals("Map was not emptied", 0, _unacknowledgedMessageMap.size()); + } + + /** + * If the subscription is null, due to message being retrieved via a GET, And we request that messages are requeued + * requeueIfUnabletoResend(set to true) then all messages should be sent to the msgToRequeue map. + * + * @throws AMQException the visit interface throws this + */ + + public void testRequeueDueToMessageHavingNoConsumerTag() throws AMQException + { + final Map<Long, QueueEntry> msgToRequeue = new LinkedHashMap<Long, QueueEntry>(); + final Map<Long, QueueEntry> msgToResend = new LinkedHashMap<Long, QueueEntry>(); + + // requeueIfUnabletoResend = true so all messages should go to msgToRequeue + _unacknowledgedMessageMap.visit(new ExtractResendAndRequeue(_unacknowledgedMessageMap, msgToRequeue, + msgToResend, true, _messageStore)); + + assertEquals("Message count for resend not correct.", 0, msgToResend.size()); + assertEquals("Message count for requeue not correct.", INITIAL_MSG_COUNT, msgToRequeue.size()); + assertEquals("Map was not emptied", 0, _unacknowledgedMessageMap.size()); + } + + /** + * If the subscription is null, due to message being retrieved via a GET, And we request that we don't + * requeueIfUnabletoResend(set to false) then all messages should be dropped as we do not have a dead letter queue. + * + * @throws AMQException the visit interface throws this + */ + + public void testDrop() throws AMQException + { + final Map<Long, QueueEntry> msgToRequeue = new LinkedHashMap<Long, QueueEntry>(); + final Map<Long, QueueEntry> msgToResend = new LinkedHashMap<Long, QueueEntry>(); + + // requeueIfUnabletoResend = false so all messages should be dropped all maps should be empty + _unacknowledgedMessageMap.visit(new ExtractResendAndRequeue(_unacknowledgedMessageMap, msgToRequeue, + msgToResend, false, _messageStore)); + + assertEquals("Message count for resend not correct.", 0, msgToResend.size()); + assertEquals("Message count for requeue not correct.", 0, msgToRequeue.size()); + assertEquals("Map was not emptied", 0, _unacknowledgedMessageMap.size()); + + + for (QueueEntry entry : _referenceList) + { + assertTrue("Message was not discarded", entry.isDeleted()); + } + + } + + /** + * If the subscription is null, due to message being retrieved via a GET, AND the queue upon which the message was + * delivered has been deleted then it is not possible to requeue. Currently we simply discar the message but in the + * future we may wish to dead letter the message. + * + * Validate that at the end of the visit all Maps are empty and all messages are marked as deleted + * + * @throws AMQException the visit interface throws this + */ + public void testDiscard() throws AMQException + { + final Map<Long, QueueEntry> msgToRequeue = new LinkedHashMap<Long, QueueEntry>(); + final Map<Long, QueueEntry> msgToResend = new LinkedHashMap<Long, QueueEntry>(); + + _queue.delete(); + + // requeueIfUnabletoResend : value doesn't matter here as queue has been deleted + _unacknowledgedMessageMap.visit(new ExtractResendAndRequeue(_unacknowledgedMessageMap, msgToRequeue, + msgToResend, false, _messageStore)); + + assertEquals("Message count for resend not correct.", 0, msgToResend.size()); + assertEquals("Message count for requeue not correct.", 0, msgToRequeue.size()); + assertEquals("Map was not emptied", 0, _unacknowledgedMessageMap.size()); + + for (QueueEntry entry : _referenceList) + { + assertTrue("Message was not discarded", entry.isDeleted()); + } + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/RunBrokerWithCommand.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/RunBrokerWithCommand.java new file mode 100644 index 0000000000..59543874b4 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/RunBrokerWithCommand.java @@ -0,0 +1,132 @@ +/* + * 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.qpid.server; + +import org.apache.log4j.Logger; +import org.apache.log4j.Level; + +import java.io.InputStream; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.IOException; + +public class RunBrokerWithCommand +{ + public static void main(String[] args) + { + //Start the broker + try + { + String[] fudge = args.clone(); + + // Override the first value which is the command we are going to run later. + fudge[0] = "-v"; + new Main(fudge).startup(); + } + catch (Exception e) + { + System.err.println("Unable to start broker due to: " + e.getMessage()); + + e.printStackTrace(); + exit(1); + } + + Logger.getRootLogger().setLevel(Level.ERROR); + + //run command + try + { + Process task = Runtime.getRuntime().exec(args[0]); + System.err.println("Started Proccess: " + args[0]); + + InputStream inputStream = task.getInputStream(); + + InputStream errorStream = task.getErrorStream(); + + Thread out = new Thread(new Outputter("[OUT]", new BufferedReader(new InputStreamReader(inputStream)))); + Thread err = new Thread(new Outputter("[ERR]", new BufferedReader(new InputStreamReader(errorStream)))); + + out.start(); + err.start(); + + out.join(); + err.join(); + + System.err.println("Waiting for process to exit: " + args[0]); + task.waitFor(); + System.err.println("Done Proccess: " + args[0]); + + } + catch (IOException e) + { + System.err.println("Proccess had problems: " + e.getMessage()); + e.printStackTrace(System.err); + exit(1); + } + catch (InterruptedException e) + { + System.err.println("Proccess had problems: " + e.getMessage()); + e.printStackTrace(System.err); + + exit(1); + } + + + exit(0); + } + + private static void exit(int i) + { + Logger.getRootLogger().setLevel(Level.INFO); + System.exit(i); + } + + static class Outputter implements Runnable + { + + BufferedReader reader; + String prefix; + + Outputter(String s, BufferedReader r) + { + prefix = s; + reader = r; + } + + public void run() + { + String line; + try + { + while ((line = reader.readLine()) != null) + { + System.out.println(prefix + line); + } + } + catch (IOException e) + { + System.out.println("Error occured reading; " + e.getMessage()); + } + } + + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/SelectorParserTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/SelectorParserTest.java new file mode 100644 index 0000000000..a0304a7b01 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/SelectorParserTest.java @@ -0,0 +1,128 @@ +package org.apache.qpid.server; + +import junit.framework.TestCase; +import org.apache.qpid.server.filter.JMSSelectorFilter; +import org.apache.qpid.AMQException;/* + * + * 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. + * + */ + +public class SelectorParserTest extends TestCase +{ + public void testSelectorWithHyphen() + { + testPass("Cost = 2 AND \"property-with-hyphen\" = 'wibble'"); + } + + public void testLike() + { + testFail("Cost LIKE 2"); + testPass("Cost LIKE 'Hello'"); + } + + public void testStringQuoted() + { + testPass("string = 'Test'"); + } + + public void testProperty() + { + testPass("prop1 = prop2"); + } + + public void testPropertyNames() + { + testPass("$min= TRUE AND _max= FALSE AND Prop_2 = true AND prop$3 = false"); + } + + public void testProtected() + { + testFail("NULL = 0 "); + testFail("TRUE = 0 "); + testFail("FALSE = 0 "); + testFail("NOT = 0 "); + testFail("AND = 0 "); + testFail("OR = 0 "); + testFail("BETWEEN = 0 "); + testFail("LIKE = 0 "); + testFail("IN = 0 "); + testFail("IS = 0 "); + testFail("ESCAPE = 0 "); + } + + + public void testBoolean() + { + testPass("min= TRUE AND max= FALSE "); + testPass("min= true AND max= false"); + } + + public void testDouble() + { + testPass("positive=31E2 AND negative=-31.4E3"); + testPass("min=" + Double.MIN_VALUE + " AND max=" + Double.MAX_VALUE); + } + + public void testLong() + { + testPass("minLong=" + Long.MIN_VALUE + "L AND maxLong=" + Long.MAX_VALUE + "L"); + } + + public void testInt() + { + testPass("minInt=" + Integer.MIN_VALUE + " AND maxInt=" + Integer.MAX_VALUE); + } + + public void testSigned() + { + testPass("negative=-42 AND positive=+42"); + } + + public void testOctal() + { + testPass("octal=042"); + } + + + private void testPass(String selector) + { + try + { + new JMSSelectorFilter(selector); + } + catch (AMQException e) + { + fail("Selector '" + selector + "' was not parsed :" + e.getMessage()); + } + } + + private void testFail(String selector) + { + try + { + new JMSSelectorFilter(selector); + fail("Selector '" + selector + "' was parsed "); + } + catch (AMQException e) + { + //normal path + } + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/ack/AcknowledgeTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/ack/AcknowledgeTest.java new file mode 100644 index 0000000000..b3223f16c4 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/ack/AcknowledgeTest.java @@ -0,0 +1,120 @@ +/* + * + * 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.qpid.server.ack; + + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.protocol.InternalTestProtocolSession; +import org.apache.qpid.server.util.InternalBrokerBaseCase; + +import java.util.List; + +public class AcknowledgeTest extends InternalBrokerBaseCase +{ + + public void testTransactionalSingleAck() throws AMQException + { + getChannel().setLocalTransactional(); + runMessageAck(1, 1, 1, false, 0); + } + + public void testTransactionalMultiAck() throws AMQException + { + getChannel().setLocalTransactional(); + runMessageAck(10, 1, 5, true, 5); + } + + public void testTransactionalAckAll() throws AMQException + { + getChannel().setLocalTransactional(); + runMessageAck(10, 1, 0, true, 0); + } + + public void testNonTransactionalSingleAck() throws AMQException + { + runMessageAck(1, 1, 1, false, 0); + } + + public void testNonTransactionalMultiAck() throws AMQException + { + runMessageAck(10, 1, 5, true, 5); + } + + public void testNonTransactionalAckAll() throws AMQException + { + runMessageAck(10, 1, 0, true, 0); + } + + protected void runMessageAck(int sendMessageCount, long firstDeliveryTag, long acknowledgeDeliveryTag, boolean acknowldegeMultiple, int remainingUnackedMessages) throws AMQException + { + //Check store is empty + checkStoreContents(0); + + //Send required messsages to the queue + publishMessages(getSession(), getChannel(), sendMessageCount); + + if (getChannel().isTransactional()) + { + getChannel().commit(); + } + + //Ensure they are stored + checkStoreContents(sendMessageCount); + + //Check that there are no unacked messages + assertEquals("Channel should have no unacked msgs ", 0, getChannel().getUnacknowledgedMessageMap().size()); + + //Subscribe to the queue + AMQShortString subscriber = subscribe(getSession(), getChannel(), getQueue()); + + getQueue().deliverAsync(); + + //Wait for the messages to be delivered + getSession().awaitDelivery(sendMessageCount); + + //Check that they are all waiting to be acknoledged + assertEquals("Channel should have unacked msgs", sendMessageCount, getChannel().getUnacknowledgedMessageMap().size()); + + List<InternalTestProtocolSession.DeliveryPair> messages = getSession().getDelivers(getChannel().getChannelId(), subscriber, sendMessageCount); + + //Double check we received the right number of messages + assertEquals(sendMessageCount, messages.size()); + + //Check that the first message has the expected deliveryTag + assertEquals("First message does not have expected deliveryTag", firstDeliveryTag, messages.get(0).getDeliveryTag()); + + //Send required Acknowledgement + getChannel().acknowledgeMessage(acknowledgeDeliveryTag, acknowldegeMultiple); + + if (getChannel().isTransactional()) + { + getChannel().commit(); + } + + // Check Remaining Acknowledgements + assertEquals("Channel unacked msgs count incorrect", remainingUnackedMessages, getChannel().getUnacknowledgedMessageMap().size()); + + //Check store contents are also correct. + checkStoreContents(remainingUnackedMessages); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/QueueConfigurationTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/QueueConfigurationTest.java new file mode 100644 index 0000000000..d2f2ae5eea --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/QueueConfigurationTest.java @@ -0,0 +1,145 @@ +/* + * + * 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.qpid.server.configuration; + +import junit.framework.TestCase; +import org.apache.commons.configuration.CompositeConfiguration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.PropertiesConfiguration; + +public class QueueConfigurationTest extends TestCase +{ + + private VirtualHostConfiguration _emptyConf; + private PropertiesConfiguration _env; + private VirtualHostConfiguration _fullHostConf; + + public void setUp() throws Exception + { + _env = new PropertiesConfiguration(); + _emptyConf = new VirtualHostConfiguration("test", _env); + + PropertiesConfiguration fullEnv = new PropertiesConfiguration(); + fullEnv.setProperty("queues.maximumMessageAge", 1); + fullEnv.setProperty("queues.maximumQueueDepth", 1); + fullEnv.setProperty("queues.maximumMessageSize", 1); + fullEnv.setProperty("queues.maximumMessageCount", 1); + fullEnv.setProperty("queues.minimumAlertRepeatGap", 1); + + _fullHostConf = new VirtualHostConfiguration("test", fullEnv); + + } + + public void testGetMaximumMessageAge() throws ConfigurationException + { + // Check default value + QueueConfiguration qConf = new QueueConfiguration("test", _emptyConf); + assertEquals(0, qConf.getMaximumMessageAge()); + + // Check explicit value + VirtualHostConfiguration vhostConfig = overrideConfiguration("maximumMessageAge", 2); + + qConf = new QueueConfiguration("test", vhostConfig); + assertEquals(2, qConf.getMaximumMessageAge()); + + // Check inherited value + qConf = new QueueConfiguration("test", _fullHostConf); + assertEquals(1, qConf.getMaximumMessageAge()); + } + + public void testGetMaximumQueueDepth() throws ConfigurationException + { + // Check default value + QueueConfiguration qConf = new QueueConfiguration("test", _emptyConf); + assertEquals(0, qConf.getMaximumQueueDepth()); + + // Check explicit value + VirtualHostConfiguration vhostConfig = overrideConfiguration("maximumQueueDepth", 2); + qConf = new QueueConfiguration("test", vhostConfig); + assertEquals(2, qConf.getMaximumQueueDepth()); + + // Check inherited value + qConf = new QueueConfiguration("test", _fullHostConf); + assertEquals(1, qConf.getMaximumQueueDepth()); + } + + public void testGetMaximumMessageSize() throws ConfigurationException + { + // Check default value + QueueConfiguration qConf = new QueueConfiguration("test", _emptyConf); + assertEquals(0, qConf.getMaximumMessageSize()); + + // Check explicit value + VirtualHostConfiguration vhostConfig = overrideConfiguration("maximumMessageSize", 2); + qConf = new QueueConfiguration("test", vhostConfig); + assertEquals(2, qConf.getMaximumMessageSize()); + + // Check inherited value + qConf = new QueueConfiguration("test", _fullHostConf); + assertEquals(1, qConf.getMaximumMessageSize()); + } + + public void testGetMaximumMessageCount() throws ConfigurationException + { + // Check default value + QueueConfiguration qConf = new QueueConfiguration("test", _emptyConf); + assertEquals(0, qConf.getMaximumMessageCount()); + + // Check explicit value + VirtualHostConfiguration vhostConfig = overrideConfiguration("maximumMessageCount", 2); + qConf = new QueueConfiguration("test", vhostConfig); + assertEquals(2, qConf.getMaximumMessageCount()); + + // Check inherited value + qConf = new QueueConfiguration("test", _fullHostConf); + assertEquals(1, qConf.getMaximumMessageCount()); + } + + public void testGetMinimumAlertRepeatGap() throws ConfigurationException + { + // Check default value + QueueConfiguration qConf = new QueueConfiguration("test", _emptyConf); + assertEquals(0, qConf.getMinimumAlertRepeatGap()); + + // Check explicit value + VirtualHostConfiguration vhostConfig = overrideConfiguration("minimumAlertRepeatGap", 2); + qConf = new QueueConfiguration("test", vhostConfig); + assertEquals(2, qConf.getMinimumAlertRepeatGap()); + + // Check inherited value + qConf = new QueueConfiguration("test", _fullHostConf); + assertEquals(1, qConf.getMinimumAlertRepeatGap()); + } + + private VirtualHostConfiguration overrideConfiguration(String property, int value) + throws ConfigurationException + { + PropertiesConfiguration queueConfig = new PropertiesConfiguration(); + queueConfig.setProperty("queues.queue.test." + property, value); + + CompositeConfiguration config = new CompositeConfiguration(); + config.addConfiguration(_fullHostConf.getConfig()); + config.addConfiguration(queueConfig); + + return new VirtualHostConfiguration("test", config); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/ServerConfigurationTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/ServerConfigurationTest.java new file mode 100644 index 0000000000..43540c88a1 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/ServerConfigurationTest.java @@ -0,0 +1,1492 @@ +/* + * + * 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.qpid.server.configuration; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.List; +import java.util.Locale; + +import junit.framework.TestCase; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.XMLConfiguration; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.protocol.AMQProtocolEngine; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.ConfigurationFileApplicationRegistry; +import org.apache.qpid.server.util.InternalBrokerBaseCase; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; +import org.apache.qpid.transport.TestNetworkDriver; + +public class ServerConfigurationTest extends InternalBrokerBaseCase +{ + private XMLConfiguration _config = new XMLConfiguration(); + + + public void testSetJMXManagementPort() throws ConfigurationException + { + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + serverConfig.setJMXManagementPort(23); + assertEquals(23, serverConfig.getJMXManagementPort()); + } + + public void testGetJMXManagementPort() throws ConfigurationException + { + _config.setProperty("management.jmxport", 42); + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(42, serverConfig.getJMXManagementPort()); + } + + public void testGetPlatformMbeanserver() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(true, serverConfig.getPlatformMbeanserver()); + + // Check value we set + _config.setProperty("management.platform-mbeanserver", false); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(false, serverConfig.getPlatformMbeanserver()); + } + + public void testGetPluginDirectory() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(null, serverConfig.getPluginDirectory()); + + // Check value we set + _config.setProperty("plugin-directory", "/path/to/plugins"); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals("/path/to/plugins", serverConfig.getPluginDirectory()); + } + + public void testGetCacheDirectory() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(null, serverConfig.getCacheDirectory()); + + // Check value we set + _config.setProperty("cache-directory", "/path/to/cache"); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals("/path/to/cache", serverConfig.getCacheDirectory()); + } + + public void testGetPrincipalDatabaseNames() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(0, serverConfig.getPrincipalDatabaseNames().size()); + + // Check value we set + _config.setProperty("security.principal-databases.principal-database(0).name", "a"); + _config.setProperty("security.principal-databases.principal-database(1).name", "b"); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + List<String> dbs = serverConfig.getPrincipalDatabaseNames(); + assertEquals(2, dbs.size()); + assertEquals("a", dbs.get(0)); + assertEquals("b", dbs.get(1)); + } + + public void testGetPrincipalDatabaseClass() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(0, serverConfig.getPrincipalDatabaseClass().size()); + + // Check value we set + _config.setProperty("security.principal-databases.principal-database(0).class", "a"); + _config.setProperty("security.principal-databases.principal-database(1).class", "b"); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + List<String> dbs = serverConfig.getPrincipalDatabaseClass(); + assertEquals(2, dbs.size()); + assertEquals("a", dbs.get(0)); + assertEquals("b", dbs.get(1)); + } + + public void testGetPrincipalDatabaseAttributeNames() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(0, serverConfig.getPrincipalDatabaseAttributeNames(1).size()); + + // Check value we set + _config.setProperty("security.principal-databases.principal-database(0).attributes(0).attribute.name", "a"); + _config.setProperty("security.principal-databases.principal-database(0).attributes(1).attribute.name", "b"); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + List<String> dbs = serverConfig.getPrincipalDatabaseAttributeNames(0); + assertEquals(2, dbs.size()); + assertEquals("a", dbs.get(0)); + assertEquals("b", dbs.get(1)); + } + + public void testGetPrincipalDatabaseAttributeValues() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(0, serverConfig.getPrincipalDatabaseAttributeValues(1).size()); + + // Check value we set + _config.setProperty("security.principal-databases.principal-database(0).attributes(0).attribute.value", "a"); + _config.setProperty("security.principal-databases.principal-database(0).attributes(1).attribute.value", "b"); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + List<String> dbs = serverConfig.getPrincipalDatabaseAttributeValues(0); + assertEquals(2, dbs.size()); + assertEquals("a", dbs.get(0)); + assertEquals("b", dbs.get(1)); + } + + + + public void testGetFrameSize() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(65536, serverConfig.getFrameSize()); + + // Check value we set + _config.setProperty("advanced.framesize", "23"); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(23, serverConfig.getFrameSize()); + } + + public void testGetProtectIOEnabled() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(false, serverConfig.getProtectIOEnabled()); + + // Check value we set + _config.setProperty(ServerConfiguration.CONNECTOR_PROTECTIO_ENABLED, true); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(true, serverConfig.getProtectIOEnabled()); + } + + public void testGetBufferReadLimit() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(262144, serverConfig.getBufferReadLimit()); + + // Check value we set + _config.setProperty(ServerConfiguration.CONNECTOR_PROTECTIO_READ_BUFFER_LIMIT_SIZE, 23); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(23, serverConfig.getBufferReadLimit()); + } + + public void testGetBufferWriteLimit() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(262144, serverConfig.getBufferWriteLimit()); + + // Check value we set + _config.setProperty(ServerConfiguration.CONNECTOR_PROTECTIO_WRITE_BUFFER_LIMIT_SIZE, 23); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(23, serverConfig.getBufferWriteLimit()); + } + + + public void testGetStatusEnabled() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(ServerConfiguration.DEFAULT_STATUS_UPDATES.equalsIgnoreCase("on"), + serverConfig.getStatusUpdatesEnabled()); + + // Check disabling we set + _config.setProperty(ServerConfiguration.STATUS_UPDATES, "off"); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(false, serverConfig.getStatusUpdatesEnabled()); + + // Check invalid values don't cause error but result in disabled + _config.setProperty(ServerConfiguration.STATUS_UPDATES, "Yes Please"); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(false, serverConfig.getStatusUpdatesEnabled()); + + } + public void testGetSynchedClocks() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(false, serverConfig.getSynchedClocks()); + + // Check value we set + _config.setProperty("advanced.synced-clocks", true); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(true, serverConfig.getSynchedClocks()); + } + + public void testGetLocale() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + + // The Default is what ever the VMs default is + Locale defaultLocale = Locale.getDefault(); + + assertEquals(defaultLocale, serverConfig.getLocale()); + + + //Test Language only + Locale update = new Locale("es"); + _config.setProperty(ServerConfiguration.ADVANCED_LOCALE, "es"); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(update, serverConfig.getLocale()); + + //Test Language and Country + update = new Locale("es","ES"); + _config.setProperty(ServerConfiguration.ADVANCED_LOCALE, "es_ES"); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(update, serverConfig.getLocale()); + + //Test Language and Country and Variant + update = new Locale("es","ES", "Traditional_WIN"); + _config.setProperty(ServerConfiguration.ADVANCED_LOCALE, "es_ES_Traditional_WIN"); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(update, serverConfig.getLocale()); + } + + + public void testGetMsgAuth() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(false, serverConfig.getMsgAuth()); + + // Check value we set + _config.setProperty("security.msg-auth", true); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(true, serverConfig.getMsgAuth()); + } + + public void testGetJMXPrincipalDatabase() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(null, serverConfig.getJMXPrincipalDatabase()); + + // Check value we set + _config.setProperty("security.jmx.principal-database", "a"); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals("a", serverConfig.getJMXPrincipalDatabase()); + } + + public void testGetManagementKeyStorePath() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(null, serverConfig.getManagementKeyStorePath()); + + // Check value we set + _config.setProperty("management.ssl.keyStorePath", "a"); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals("a", serverConfig.getManagementKeyStorePath()); + } + + public void testGetManagementSSLEnabled() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(true, serverConfig.getManagementSSLEnabled()); + + // Check value we set + _config.setProperty("management.ssl.enabled", false); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(false, serverConfig.getManagementSSLEnabled()); + } + + public void testGetManagementKeyStorePassword() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(null, serverConfig.getManagementKeyStorePassword()); + + // Check value we set + _config.setProperty("management.ssl.keyStorePassword", "a"); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals("a", serverConfig.getManagementKeyStorePassword()); + } + + public void testGetQueueAutoRegister() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(true, serverConfig.getQueueAutoRegister()); + + // Check value we set + _config.setProperty("queue.auto_register", false); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(false, serverConfig.getQueueAutoRegister()); + } + + public void testGetManagementEnabled() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(true, serverConfig.getManagementEnabled()); + + // Check value we set + _config.setProperty("management.enabled", false); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(false, serverConfig.getManagementEnabled()); + } + + public void testSetManagementEnabled() throws ConfigurationException + { + // Check value we set + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + serverConfig.setManagementEnabled(false); + assertEquals(false, serverConfig.getManagementEnabled()); + } + + public void testGetHeartBeatDelay() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(5, serverConfig.getHeartBeatDelay()); + + // Check value we set + _config.setProperty("heartbeat.delay", 23); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(23, serverConfig.getHeartBeatDelay()); + } + + public void testGetHeartBeatTimeout() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(2.0, serverConfig.getHeartBeatTimeout()); + + // Check value we set + _config.setProperty("heartbeat.timeoutFactor", 2.3); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(2.3, serverConfig.getHeartBeatTimeout()); + } + + public void testGetMaximumMessageAge() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(0, serverConfig.getMaximumMessageAge()); + + // Check value we set + _config.setProperty("maximumMessageAge", 10L); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(10, serverConfig.getMaximumMessageAge()); + } + + public void testGetMaximumMessageCount() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(0, serverConfig.getMaximumMessageCount()); + + // Check value we set + _config.setProperty("maximumMessageCount", 10L); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(10, serverConfig.getMaximumMessageCount()); + } + + public void testGetMaximumQueueDepth() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(0, serverConfig.getMaximumQueueDepth()); + + // Check value we set + _config.setProperty("maximumQueueDepth", 10L); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(10, serverConfig.getMaximumQueueDepth()); + } + + public void testGetMaximumMessageSize() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(0, serverConfig.getMaximumMessageSize()); + + // Check value we set + _config.setProperty("maximumMessageSize", 10L); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(10, serverConfig.getMaximumMessageSize()); + } + + public void testGetMinimumAlertRepeatGap() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(0, serverConfig.getMinimumAlertRepeatGap()); + + // Check value we set + _config.setProperty("minimumAlertRepeatGap", 10L); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(10, serverConfig.getMinimumAlertRepeatGap()); + } + + public void testGetProcessors() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(4, serverConfig.getProcessors()); + + // Check value we set + _config.setProperty("connector.processors", 10); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(10, serverConfig.getProcessors()); + } + + public void testGetPort() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertNotNull(serverConfig.getPorts()); + assertEquals(1, serverConfig.getPorts().size()); + assertEquals(5672, serverConfig.getPorts().get(0)); + + + // Check value we set + _config.setProperty("connector.port", "10"); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertNotNull(serverConfig.getPorts()); + assertEquals(1, serverConfig.getPorts().size()); + assertEquals("10", serverConfig.getPorts().get(0)); + } + + public void testGetBind() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals("wildcard", serverConfig.getBind()); + + // Check value we set + _config.setProperty("connector.bind", "a"); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals("a", serverConfig.getBind()); + } + + public void testGetReceiveBufferSize() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(32767, serverConfig.getReceiveBufferSize()); + + // Check value we set + _config.setProperty("connector.socketReceiveBuffer", "23"); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(23, serverConfig.getReceiveBufferSize()); + } + + public void testGetWriteBufferSize() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(32767, serverConfig.getWriteBufferSize()); + + // Check value we set + _config.setProperty("connector.socketWriteBuffer", "23"); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(23, serverConfig.getWriteBufferSize()); + } + + public void testGetTcpNoDelay() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(true, serverConfig.getTcpNoDelay()); + + // Check value we set + _config.setProperty("connector.tcpNoDelay", false); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(false, serverConfig.getTcpNoDelay()); + } + + public void testGetEnableExecutorPool() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(false, serverConfig.getEnableExecutorPool()); + + // Check value we set + _config.setProperty("advanced.filterchain[@enableExecutorPool]", true); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(true, serverConfig.getEnableExecutorPool()); + } + + public void testGetEnableSSL() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(false, serverConfig.getEnableSSL()); + + // Check value we set + _config.setProperty("connector.ssl.enabled", true); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(true, serverConfig.getEnableSSL()); + } + + public void testGetSSLOnly() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(false, serverConfig.getSSLOnly()); + + // Check value we set + _config.setProperty("connector.ssl.sslOnly", true); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(true, serverConfig.getSSLOnly()); + } + + public void testGetSSLPort() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(8672, serverConfig.getSSLPort()); + + // Check value we set + _config.setProperty("connector.ssl.port", 23); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(23, serverConfig.getSSLPort()); + } + + public void testGetKeystorePath() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals("none", serverConfig.getKeystorePath()); + + // Check value we set + _config.setProperty("connector.ssl.keystorePath", "a"); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals("a", serverConfig.getKeystorePath()); + } + + public void testGetKeystorePassword() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals("none", serverConfig.getKeystorePassword()); + + // Check value we set + _config.setProperty("connector.ssl.keystorePassword", "a"); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals("a", serverConfig.getKeystorePassword()); + } + + public void testGetCertType() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals("SunX509", serverConfig.getCertType()); + + // Check value we set + _config.setProperty("connector.ssl.certType", "a"); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals("a", serverConfig.getCertType()); + } + + public void testGetQpidNIO() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(false, serverConfig.getQpidNIO()); + + // Check value we set + _config.setProperty("connector.qpidnio", true); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(true, serverConfig.getQpidNIO()); + } + + public void testGetUseBiasedWrites() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(false, serverConfig.getUseBiasedWrites()); + + // Check value we set + _config.setProperty("advanced.useWriteBiasedPool", true); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(true, serverConfig.getUseBiasedWrites()); + } + + public void testGetHousekeepingExpiredMessageCheckPeriod() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(30000, serverConfig.getHousekeepingCheckPeriod()); + + // Check value we set + _config.setProperty("housekeeping.expiredMessageCheckPeriod", 23L); + serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + assertEquals(23, serverConfig.getHousekeepingCheckPeriod()); + serverConfig.setHousekeepingExpiredMessageCheckPeriod(42L); + assertEquals(42, serverConfig.getHousekeepingCheckPeriod()); + } + + public void testSingleConfiguration() throws IOException, ConfigurationException + { + File fileA = File.createTempFile(getClass().getName(), null); + fileA.deleteOnExit(); + FileWriter out = new FileWriter(fileA); + out.write("<broker><connector><port>2342</port><ssl><port>4235</port></ssl></connector></broker>"); + out.close(); + ServerConfiguration conf = new ServerConfiguration(fileA); + conf.initialise(); + assertEquals(4235, conf.getSSLPort()); + } + + public void testCombinedConfiguration() throws IOException, ConfigurationException + { + File mainFile = File.createTempFile(getClass().getName(), null); + File fileA = File.createTempFile(getClass().getName(), null); + File fileB = File.createTempFile(getClass().getName(), null); + + mainFile.deleteOnExit(); + fileA.deleteOnExit(); + fileB.deleteOnExit(); + + FileWriter out = new FileWriter(mainFile); + out.write("<configuration><system/>"); + out.write("<xml fileName=\"" + fileA.getAbsolutePath() + "\"/>"); + out.write("<xml fileName=\"" + fileB.getAbsolutePath() + "\"/>"); + out.write("</configuration>"); + out.close(); + + out = new FileWriter(fileA); + out.write("<broker><connector><port>2342</port><ssl><port>4235</port></ssl></connector></broker>"); + out.close(); + + out = new FileWriter(fileB); + out.write("<broker><connector><ssl><port>2345</port></ssl><qpidnio>true</qpidnio></connector></broker>"); + out.close(); + + ServerConfiguration config = new ServerConfiguration(mainFile.getAbsoluteFile()); + config.initialise(); + assertEquals(4235, config.getSSLPort()); // From first file, not + // overriden by second + assertNotNull(config.getPorts()); + assertEquals(1, config.getPorts().size()); + assertEquals("2342", config.getPorts().get(0)); // From the first file, not + // present in the second + assertEquals(true, config.getQpidNIO()); // From the second file, not + // present in the first + } + + public void testVariableInterpolation() throws Exception + { + File mainFile = File.createTempFile(getClass().getName(), null); + + mainFile.deleteOnExit(); + + FileWriter out = new FileWriter(mainFile); + out.write("<broker>\n"); + out.write("\t<work>foo</work>\n"); + out.write("\t<management><ssl><keyStorePath>${work}</keyStorePath></ssl></management>\n"); + out.write("</broker>\n"); + out.close(); + + ServerConfiguration config = new ServerConfiguration(mainFile.getAbsoluteFile()); + config.initialise(); + assertEquals("Did not get correct interpolated value", + "foo", config.getManagementKeyStorePath()); + } + + private void writeConfigFile(File mainFile, boolean allow) throws IOException { + writeConfigFile(mainFile, allow, true, null, "test"); + } + + private void writeConfigFile(File mainFile, boolean allow, boolean includeVhosts, File vhostsFile, String name) throws IOException { + FileWriter out = new FileWriter(mainFile); + out.write("<broker>\n"); + out.write("\t<management><enabled>false</enabled></management>\n"); + out.write("\t<security>\n"); + out.write("\t\t<principal-databases>\n"); + out.write("\t\t\t<principal-database>\n"); + out.write("\t\t\t\t<name>passwordfile</name>\n"); + out.write("\t\t\t\t<class>org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase</class>\n"); + out.write("\t\t\t\t<attributes>\n"); + out.write("\t\t\t\t\t<attribute>\n"); + out.write("\t\t\t\t\t\t<name>passwordFile</name>\n"); + out.write("\t\t\t\t\t\t<value>/dev/null</value>\n"); + out.write("\t\t\t\t\t</attribute>\n"); + out.write("\t\t\t\t</attributes>\n"); + out.write("\t\t\t</principal-database>\n"); + out.write("\t\t</principal-databases>\n"); + out.write("\t\t<jmx>\n"); + out.write("\t\t\t<principal-database>passwordfile</principal-database>\n"); + out.write("\t\t</jmx>\n"); + out.write("\t\t<firewall>\n"); + out.write("\t\t\t<rule access=\""+ ((allow) ? "allow" : "deny") +"\" network=\"127.0.0.1\"/>"); + out.write("\t\t</firewall>\n"); + out.write("\t</security>\n"); + if (includeVhosts) + { + out.write("\t<virtualhosts>\n"); + out.write("\t\t<default>test</default>\n"); + out.write("\t\t<virtualhost>\n"); + out.write(String.format("\t\t\t<name>%s</name>\n", name)); + out.write(String.format("\t\t<%s> \n", name)); + out.write("\t\t\t<exchanges>\n"); + out.write("\t\t\t\t<exchange>\n"); + out.write("\t\t\t\t\t<type>topic</type>\n"); + out.write(String.format("\t\t\t\t\t<name>%s.topic</name>\n", name)); + out.write("\t\t\t\t\t<durable>true</durable>\n"); + out.write("\t\t\t\t</exchange>\n"); + out.write("\t\tt</exchanges>\n"); + out.write(String.format("\t\t</%s> \n", name)); + out.write("\t\t</virtualhost>\n"); + out.write("\t</virtualhosts>\n"); + } + if (vhostsFile != null) + { + out.write("\t<virtualhosts>"+vhostsFile.getAbsolutePath()+"</virtualhosts>\n"); + } + out.write("</broker>\n"); + out.close(); + } + + private void writeTestFishConfigFile(File mainFile) throws IOException { + FileWriter out = new FileWriter(mainFile); + out.write("<broker>\n"); + out.write("\t<management><enabled>false</enabled></management>\n"); + out.write("\t<security>\n"); + out.write("\t\t<principal-databases>\n"); + out.write("\t\t\t<principal-database>\n"); + out.write("\t\t\t\t<name>passwordfile</name>\n"); + out.write("\t\t\t\t<class>org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase</class>\n"); + out.write("\t\t\t\t<attributes>\n"); + out.write("\t\t\t\t\t<attribute>\n"); + out.write("\t\t\t\t\t\t<name>passwordFile</name>\n"); + out.write("\t\t\t\t\t\t<value>/dev/null</value>\n"); + out.write("\t\t\t\t\t</attribute>\n"); + out.write("\t\t\t\t</attributes>\n"); + out.write("\t\t\t</principal-database>\n"); + out.write("\t\t</principal-databases>\n"); + out.write("\t\t<jmx>\n"); + out.write("\t\t\t<principal-database>passwordfile</principal-database>\n"); + out.write("\t\t</jmx>\n"); + out.write("\t\t<firewall>\n"); + out.write("\t\t\t<rule access=\"allow\" network=\"127.0.0.1\"/>"); + out.write("\t\t</firewall>\n"); + out.write("\t</security>\n"); + out.write("\t<virtualhosts>\n"); + out.write("\t\t<virtualhost>\n"); + out.write("\t\t\t<name>test</name>\n"); + out.write("\t\t<test> \n"); + out.write("\t\t\t<exchanges>\n"); + out.write("\t\t\t\t<exchange>\n"); + out.write("\t\t\t\t\t<type>topic</type>\n"); + out.write("\t\t\t\t\t<name>test.topic</name>\n"); + out.write("\t\t\t\t\t<durable>true</durable>\n"); + out.write("\t\t\t\t</exchange>\n"); + out.write("\t\tt</exchanges>\n"); + out.write("\t\t</test> \n"); + out.write("\t\t</virtualhost>\n"); + out.write("\t\t<virtualhost>\n"); + out.write("\t\t\t<name>fish</name>\n"); + out.write("\t\t<fish> \n"); + out.write("\t\t\t<exchanges>\n"); + out.write("\t\t\t\t<exchange>\n"); + out.write("\t\t\t\t\t<type>topic</type>\n"); + out.write("\t\t\t\t\t<name>fish.topic</name>\n"); + out.write("\t\t\t\t\t<durable>false</durable>\n"); + out.write("\t\t\t\t</exchange>\n"); + out.write("\t\tt</exchanges>\n"); + out.write("\t\t</fish> \n"); + out.write("\t\t</virtualhost>\n"); + out.write("\t</virtualhosts>\n"); + out.write("</broker>\n"); + out.close(); + } + + private void writeVirtualHostsFile(File vhostsFile, String name) throws IOException { + FileWriter out = new FileWriter(vhostsFile); + out.write("<virtualhosts>\n"); + out.write(String.format("\t\t<default>%s</default>\n", name)); + out.write("\t<virtualhost>\n"); + out.write(String.format("\t\t<name>%s</name>\n", name)); + out.write(String.format("\t\t<%s>\n", name)); + out.write("\t\t\t<exchanges>\n"); + out.write("\t\t\t\t<exchange>\n"); + out.write("\t\t\t\t\t<type>topic</type>\n"); + out.write("\t\t\t\t\t<name>test.topic</name>\n"); + out.write("\t\t\t\t\t<durable>true</durable>\n"); + out.write("\t\t\t\t</exchange>\n"); + out.write("\t\tt</exchanges>\n"); + out.write(String.format("\t\t</%s>\n", name)); + out.write("\t</virtualhost>\n"); + out.write("</virtualhosts>\n"); + out.close(); + } + + private void writeMultiVirtualHostsFile(File vhostsFile) throws IOException { + FileWriter out = new FileWriter(vhostsFile); + out.write("<virtualhosts>\n"); + out.write("\t<virtualhost>\n"); + out.write("\t\t<name>topic</name>\n"); + out.write("\t\t<topic>\n"); + out.write("\t\t\t<exchanges>\n"); + out.write("\t\t\t\t<exchange>\n"); + out.write("\t\t\t\t\t<type>topic</type>\n"); + out.write("\t\t\t\t\t<name>test.topic</name>\n"); + out.write("\t\t\t\t\t<durable>true</durable>\n"); + out.write("\t\t\t\t</exchange>\n"); + out.write("\t\tt</exchanges>\n"); + out.write("\t\t</topic>\n"); + out.write("\t</virtualhost>\n"); + out.write("\t<virtualhost>\n"); + out.write("\t\t<name>fanout</name>\n"); + out.write("\t\t<fanout>\n"); + out.write("\t\t\t<exchanges>\n"); + out.write("\t\t\t\t<exchange>\n"); + out.write("\t\t\t\t\t<type>fanout</type>\n"); + out.write("\t\t\t\t\t<name>test.fanout</name>\n"); + out.write("\t\t\t\t\t<durable>true</durable>\n"); + out.write("\t\t\t\t</exchange>\n"); + out.write("\t\tt</exchanges>\n"); + out.write("\t\t</fanout>\n"); + out.write("\t</virtualhost>\n"); + out.write("</virtualhosts>\n"); + out.close(); + } + + private void writeMultipleVhostsConfigFile(File mainFile, File[] vhostsFileArray) throws IOException { + FileWriter out = new FileWriter(mainFile); + out.write("<broker>\n"); + out.write("\t<management><enabled>false</enabled></management>\n"); + out.write("\t<security>\n"); + out.write("\t\t<principal-databases>\n"); + out.write("\t\t\t<principal-database>\n"); + out.write("\t\t\t\t<name>passwordfile</name>\n"); + out.write("\t\t\t\t<class>org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase</class>\n"); + out.write("\t\t\t\t<attributes>\n"); + out.write("\t\t\t\t\t<attribute>\n"); + out.write("\t\t\t\t\t\t<name>passwordFile</name>\n"); + out.write("\t\t\t\t\t\t<value>/dev/null</value>\n"); + out.write("\t\t\t\t\t</attribute>\n"); + out.write("\t\t\t\t</attributes>\n"); + out.write("\t\t\t</principal-database>\n"); + out.write("\t\t</principal-databases>\n"); + out.write("\t\t<jmx>\n"); + out.write("\t\t\t<principal-database>passwordfile</principal-database>\n"); + out.write("\t\t</jmx>\n"); + out.write("\t\t<firewall>\n"); + out.write("\t\t\t<rule access=\"allow\" network=\"127.0.0.1\"/>"); + out.write("\t\t</firewall>\n"); + out.write("\t</security>\n"); + for (File vhostsFile : vhostsFileArray) + { + out.write("\t<virtualhosts>"+vhostsFile.getAbsolutePath()+"</virtualhosts>\n"); + } + out.write("</broker>\n"); + out.close(); + } + + private void writeCombinedConfigFile(File mainFile, File fileA, File fileB) throws Exception + { + FileWriter out = new FileWriter(mainFile); + out.write("<configuration><system/>"); + out.write("<xml fileName=\"" + fileA.getAbsolutePath() + "\"/>"); + out.write("<xml fileName=\"" + fileB.getAbsolutePath() + "\"/>"); + out.write("</configuration>"); + out.close(); + } + + /** + * Test that configuration loads correctly when virtual hosts are specified in the main + * configuration file only. + * <p> + * Test for QPID-2361 + */ + public void testInternalVirtualhostConfigFile() throws Exception + { + // Write out config + File mainFile = File.createTempFile(getClass().getName(), "config"); + mainFile.deleteOnExit(); + writeConfigFile(mainFile, false, true, null, "test"); + + // Load config + ApplicationRegistry reg = new ConfigurationFileApplicationRegistry(mainFile); + ApplicationRegistry.initialise(reg, 1); + + // Test config + VirtualHostRegistry virtualHostRegistry = reg.getVirtualHostRegistry(); + String defaultVirtualHost = reg.getConfiguration().getDefaultVirtualHost(); + VirtualHost virtualHost = virtualHostRegistry.getVirtualHost("test"); + Exchange exchange = virtualHost.getExchangeRegistry().getExchange(new AMQShortString("test.topic")); + + assertEquals("Incorrect default host", "test", defaultVirtualHost); + assertEquals("Incorrect virtualhost count", 1, virtualHostRegistry.getVirtualHosts().size()); + assertEquals("Incorrect virtualhost name", "test", virtualHost.getName()); + assertEquals("Incorrect exchange type", "topic", exchange.getType().getName().toString()); + } + + /** + * Test that configuration loads correctly when virtual hosts are specified in an external + * configuration file only. + * <p> + * Test for QPID-2361 + */ + public void testExternalVirtualhostXMLFile() throws Exception + { + // Write out config + File mainFile = File.createTempFile(getClass().getName(), "config"); + mainFile.deleteOnExit(); + File vhostsFile = File.createTempFile(getClass().getName(), "vhosts"); + vhostsFile.deleteOnExit(); + writeConfigFile(mainFile, false, false, vhostsFile, null); + writeVirtualHostsFile(vhostsFile, "test"); + + // Load config + ApplicationRegistry reg = new ConfigurationFileApplicationRegistry(mainFile); + ApplicationRegistry.initialise(reg, 1); + + // Test config + VirtualHostRegistry virtualHostRegistry = reg.getVirtualHostRegistry(); + String defaultVirtualHost = reg.getConfiguration().getDefaultVirtualHost(); + VirtualHost virtualHost = virtualHostRegistry.getVirtualHost("test"); + Exchange exchange = virtualHost.getExchangeRegistry().getExchange(new AMQShortString("test.topic")); + + assertEquals("Incorrect default host", "test", defaultVirtualHost); + assertEquals("Incorrect virtualhost count", 1, virtualHostRegistry.getVirtualHosts().size()); + assertEquals("Incorrect virtualhost name", "test", virtualHost.getName()); + assertEquals("Incorrect exchange type", "topic", exchange.getType().getName().toString()); + } + + /** + * Test that configuration loads correctly when virtual hosts are specified in an external + * configuration file only, with two vhosts that have different properties. + * <p> + * Test for QPID-2361 + */ + public void testExternalMultiVirtualhostXMLFile() throws Exception + { + // Write out vhosts + File vhostsFile = File.createTempFile(getClass().getName(), "vhosts-multi"); + vhostsFile.deleteOnExit(); + writeMultiVirtualHostsFile(vhostsFile); + + // Write out config + File mainFile = File.createTempFile(getClass().getName(), "config"); + mainFile.deleteOnExit(); + writeConfigFile(mainFile, false, false, vhostsFile, null); + + // Load config + ApplicationRegistry reg = new ConfigurationFileApplicationRegistry(mainFile); + ApplicationRegistry.initialise(reg, 1); + + // Test config + VirtualHostRegistry virtualHostRegistry = reg.getVirtualHostRegistry(); + + assertEquals("Incorrect virtualhost count", 2, virtualHostRegistry.getVirtualHosts().size()); + + // test topic host + VirtualHost topicVirtualHost = virtualHostRegistry.getVirtualHost("topic"); + Exchange topicExchange = topicVirtualHost.getExchangeRegistry().getExchange(new AMQShortString("test.topic")); + + assertEquals("Incorrect topic virtualhost name", "topic", topicVirtualHost.getName()); + assertEquals("Incorrect topic exchange type", "topic", topicExchange.getType().getName().toString()); + + // Test fanout host + VirtualHost fanoutVirtualHost = virtualHostRegistry.getVirtualHost("fanout"); + Exchange fanoutExchange = fanoutVirtualHost.getExchangeRegistry().getExchange(new AMQShortString("test.fanout")); + + assertEquals("Incorrect fanout virtualhost name", "fanout", fanoutVirtualHost.getName()); + assertEquals("Incorrect fanout exchange type", "fanout", fanoutExchange.getType().getName().toString()); + } + + /** + * Test that configuration does not load when virtual hosts are specified in both the main + * configuration file and an external file. Should throw a {@link ConfigurationException}. + * <p> + * Test for QPID-2361 + */ + public void testInternalAndExternalVirtualhostXMLFile() throws Exception + { + // Write out vhosts + File vhostsFile = File.createTempFile(getClass().getName(), "vhosts"); + vhostsFile.deleteOnExit(); + writeVirtualHostsFile(vhostsFile, "test"); + + // Write out config + File mainFile = File.createTempFile(getClass().getName(), "config"); + mainFile.deleteOnExit(); + writeConfigFile(mainFile, false, true, vhostsFile, "test"); + + // Load config + try + { + ApplicationRegistry reg = new ConfigurationFileApplicationRegistry(mainFile); + ApplicationRegistry.initialise(reg, 1); + fail("Different virtualhost XML configurations not allowed"); + } + catch (ConfigurationException ce) + { + assertEquals("Incorrect error message", "Only one of external or embedded virtualhosts configuration allowed.", ce.getMessage()); + } + } + + /** + * Test that configuration does not load when virtual hosts are specified in multiple external + * files. Should throw a {@link ConfigurationException}. + * <p> + * Test for QPID-2361 + */ + public void testMultipleInternalVirtualhostXMLFile() throws Exception + { + // Write out vhosts + File vhostsFileOne = File.createTempFile(getClass().getName(), "vhosts-one"); + vhostsFileOne.deleteOnExit(); + writeVirtualHostsFile(vhostsFileOne, "one"); + File vhostsFileTwo = File.createTempFile(getClass().getName(), "vhosts-two"); + vhostsFileTwo.deleteOnExit(); + writeVirtualHostsFile(vhostsFileTwo, "two"); + + // Write out config + File mainFile = File.createTempFile(getClass().getName(), "config"); + mainFile.deleteOnExit(); + writeMultipleVhostsConfigFile(mainFile, new File[] { vhostsFileOne, vhostsFileTwo }); + + // Load config + try + { + ApplicationRegistry reg = new ConfigurationFileApplicationRegistry(mainFile); + ApplicationRegistry.initialise(reg, 1); + fail("Multiple virtualhost XML configurations not allowed"); + } + catch (ConfigurationException ce) + { + assertEquals("Incorrect error message", + "Only one external virtualhosts configuration file allowed, multiple filenames found.", + ce.getMessage()); + } + } + + /** + * Test that configuration loads correctly when virtual hosts are specified in an external + * configuration file in the first of two configurations and embedded in the second. This + * will throe a {@link ConfigurationException} since the configurations have different + * types. + * <p> + * Test for QPID-2361 + */ + public void testCombinedDifferentVirtualhostConfig() throws Exception + { + // Write out vhosts config + File vhostsFile = File.createTempFile(getClass().getName(), "vhosts"); + vhostsFile.deleteOnExit(); + writeVirtualHostsFile(vhostsFile, "external"); + + // Write out combined config file + File mainFile = File.createTempFile(getClass().getName(), "main"); + File fileA = File.createTempFile(getClass().getName(), "a"); + File fileB = File.createTempFile(getClass().getName(), "b"); + mainFile.deleteOnExit(); + fileA.deleteOnExit(); + fileB.deleteOnExit(); + writeCombinedConfigFile(mainFile, fileA, fileB); + writeConfigFile(fileA, false, false, vhostsFile, null); + writeConfigFile(fileB, false); + + // Load config + try + { + ServerConfiguration config = new ServerConfiguration(mainFile.getAbsoluteFile()); + config.initialise(); + fail("Different virtualhost XML configurations not allowed"); + } + catch (ConfigurationException ce) + { + assertEquals("Incorrect error message", "Only one of external or embedded virtualhosts configuration allowed.", ce.getMessage()); + } + } + + /** + * Test that configuration loads correctly when virtual hosts are specified two overriding configurations + * each with an embedded virtualhost section. The first configuration section should be used. + * <p> + * Test for QPID-2361 + */ + public void testCombinedConfigEmbeddedVirtualhost() throws Exception + { + // Write out combined config file + File mainFile = File.createTempFile(getClass().getName(), "main"); + File fileA = File.createTempFile(getClass().getName(), "a"); + File fileB = File.createTempFile(getClass().getName(), "b"); + mainFile.deleteOnExit(); + fileA.deleteOnExit(); + fileB.deleteOnExit(); + writeCombinedConfigFile(mainFile, fileA, fileB); + writeConfigFile(fileA, false, true, null, "a"); + writeConfigFile(fileB, false, true, null, "b"); + + // Load config + ServerConfiguration config = new ServerConfiguration(mainFile.getAbsoluteFile()); + config.initialise(); + + // Test config + VirtualHostConfiguration virtualHost = config.getVirtualHostConfig("a"); + + assertEquals("Incorrect virtualhost count", 1, config.getVirtualHosts().length); + assertEquals("Incorrect virtualhost name", "a", virtualHost.getName()); + } + + /** + * Test that configuration loads correctly when virtual hosts are specified two overriding configurations + * each with an external virtualhost XML file. The first configuration file should be used. + * <p> + * Test for QPID-2361 + */ + public void testCombinedConfigExternalVirtualhost() throws Exception + { + // Write out vhosts config + File vhostsOne = File.createTempFile(getClass().getName(), "vhosts-one"); + vhostsOne.deleteOnExit(); + writeVirtualHostsFile(vhostsOne, "one"); + File vhostsTwo = File.createTempFile(getClass().getName(), "vhosts-two"); + vhostsTwo.deleteOnExit(); + writeVirtualHostsFile(vhostsTwo, "two"); + + // Write out combined config file + File mainFile = File.createTempFile(getClass().getName(), "main"); + File fileA = File.createTempFile(getClass().getName(), "a"); + File fileB = File.createTempFile(getClass().getName(), "b"); + mainFile.deleteOnExit(); + fileA.deleteOnExit(); + fileB.deleteOnExit(); + writeCombinedConfigFile(mainFile, fileA, fileB); + writeConfigFile(fileA, false, false, vhostsOne, null); + writeConfigFile(fileB, false, false, vhostsTwo, null); + + // Load config + ServerConfiguration config = new ServerConfiguration(mainFile.getAbsoluteFile()); + config.initialise(); + + // Test config + VirtualHostConfiguration virtualHost = config.getVirtualHostConfig("one"); + + assertEquals("Incorrect virtualhost count", 1, config.getVirtualHosts().length); + assertEquals("Incorrect virtualhost name", "one", virtualHost.getName()); + } + + /** + * Test that configuration loads correctly when an overriding virtualhost configuration resets + * a property of an embedded virtualhost section. The overriding configuration property value + * should be used. + * <p> + * Test for QPID-2361 + */ + public void testCombinedConfigEmbeddedVirtualhostOverride() throws Exception + { + // Write out combined config file + File mainFile = File.createTempFile(getClass().getName(), "main"); + File fileA = File.createTempFile(getClass().getName(), "override"); + File fileB = File.createTempFile(getClass().getName(), "config"); + mainFile.deleteOnExit(); + fileA.deleteOnExit(); + fileB.deleteOnExit(); + writeCombinedConfigFile(mainFile, fileA, fileB); + writeTestFishConfigFile(fileB); + + // Write out overriding virtualhosts section + FileWriter out = new FileWriter(fileA); + out.write("<broker>\n"); + out.write("<virtualhosts>\n"); + out.write("\t<virtualhost>\n"); + out.write("\t\t<test>\n"); + out.write("\t\t\t<exchanges>\n"); + out.write("\t\t\t\t<exchange>\n"); + out.write("\t\t\t\t\t<durable>false</durable>\n"); + out.write("\t\t\t\t</exchange>\n"); + out.write("\t\tt</exchanges>\n"); + out.write("\t\t</test>\n"); + out.write("\t\t<fish>\n"); + out.write("\t\t\t<exchanges>\n"); + out.write("\t\t\t\t<exchange>\n"); + out.write("\t\t\t\t\t<durable>true</durable>\n"); + out.write("\t\t\t\t</exchange>\n"); + out.write("\t\tt</exchanges>\n"); + out.write("\t\t</fish>\n"); + out.write("\t</virtualhost>\n"); + out.write("</virtualhosts>\n"); + out.write("</broker>\n"); + out.close(); + + // Load config + ServerConfiguration config = new ServerConfiguration(mainFile.getAbsoluteFile()); + config.initialise(); + + // Test config + VirtualHostConfiguration testHost = config.getVirtualHostConfig("test"); + ExchangeConfiguration testExchange = testHost.getExchangeConfiguration("test.topic"); + VirtualHostConfiguration fishHost = config.getVirtualHostConfig("fish"); + ExchangeConfiguration fishExchange = fishHost.getExchangeConfiguration("fish.topic"); + + assertEquals("Incorrect virtualhost count", 2, config.getVirtualHosts().length); + assertEquals("Incorrect virtualhost name", "test", testHost.getName()); + assertFalse("Incorrect exchange durable property", testExchange.getDurable()); + assertEquals("Incorrect virtualhost name", "fish", fishHost.getName()); + assertTrue("Incorrect exchange durable property", fishExchange.getDurable()); + } + + /** + * Test that configuration loads correctly when the virtualhost configuration is a set of overriding + * configuration files that resets a property of a virtualhost. The opmost overriding configuration + * property value should be used. + * <p> + * Test for QPID-2361 + */ + public void testCombinedVirtualhostOverride() throws Exception + { + // Write out combined config file + File mainFile = File.createTempFile(getClass().getName(), "main"); + File vhostsFile = File.createTempFile(getClass().getName(), "vhosts"); + File fileA = File.createTempFile(getClass().getName(), "vhosts-override"); + File fileB = File.createTempFile(getClass().getName(), "vhosts-base"); + mainFile.deleteOnExit(); + vhostsFile.deleteOnExit(); + fileA.deleteOnExit(); + fileB.deleteOnExit(); + writeConfigFile(mainFile, true, false, vhostsFile, null); + writeCombinedConfigFile(vhostsFile, fileA, fileB); + + // Write out overriding virtualhosts sections + FileWriter out = new FileWriter(fileA); + out.write("<virtualhosts>\n"); + out.write("\t<virtualhost>\n"); + out.write("\t\t<test>\n"); + out.write("\t\t\t<exchanges>\n"); + out.write("\t\t\t\t<exchange>\n"); + out.write("\t\t\t\t\t<durable>false</durable>\n"); + out.write("\t\t\t\t</exchange>\n"); + out.write("\t\tt</exchanges>\n"); + out.write("\t\t</test>\n"); + out.write("\t</virtualhost>\n"); + out.write("</virtualhosts>\n"); + out.close(); + writeVirtualHostsFile(fileB, "test"); + + // Load config + ServerConfiguration config = new ServerConfiguration(mainFile.getAbsoluteFile()); + config.initialise(); + + // Test config + VirtualHostConfiguration testHost = config.getVirtualHostConfig("test"); + ExchangeConfiguration testExchange = testHost.getExchangeConfiguration("test.topic"); + + assertEquals("Incorrect virtualhost count", 1, config.getVirtualHosts().length); + assertEquals("Incorrect virtualhost name", "test", testHost.getName()); + assertFalse("Incorrect exchange durable property", testExchange.getDurable()); + } + + /** + * Test that configuration loads correctly when the virtualhost configuration is a set of overriding + * configuration files that define multiple virtualhosts, one per file. Only the virtualhosts defined in + * the topmost file should be used. + * <p> + * Test for QPID-2361 + */ + public void testCombinedMultipleVirtualhosts() throws Exception + { + // Write out combined config file + File mainFile = File.createTempFile(getClass().getName(), "main"); + File vhostsFile = File.createTempFile(getClass().getName(), "vhosts"); + File fileA = File.createTempFile(getClass().getName(), "vhosts-one"); + File fileB = File.createTempFile(getClass().getName(), "vhosts-two"); + mainFile.deleteOnExit(); + vhostsFile.deleteOnExit(); + fileA.deleteOnExit(); + fileB.deleteOnExit(); + writeConfigFile(mainFile, true, false, vhostsFile, null); + writeCombinedConfigFile(vhostsFile, fileA, fileB); + + // Write both virtualhosts definitions + writeVirtualHostsFile(fileA, "test-one"); + writeVirtualHostsFile(fileB, "test-two"); + + // Load config + ServerConfiguration config = new ServerConfiguration(mainFile.getAbsoluteFile()); + config.initialise(); + + // Test config + VirtualHostConfiguration oneHost = config.getVirtualHostConfig("test-one"); + + assertEquals("Incorrect virtualhost count", 1, config.getVirtualHosts().length); + assertEquals("Incorrect virtualhost name", "test-one", oneHost.getName()); + } + + /** + * Test that a non-existant virtualhost file throws a {@link ConfigurationException}. + * <p> + * Test for QPID-2624 + */ + public void testNonExistantVirtualhosts() throws Exception + { + // Write out combined config file + File mainFile = File.createTempFile(getClass().getName(), "main"); + File vhostsFile = new File("doesnotexist"); + mainFile.deleteOnExit(); + writeConfigFile(mainFile, true, false, vhostsFile, null); + + // Load config + try + { + ServerConfiguration config = new ServerConfiguration(mainFile.getAbsoluteFile()); + config.initialise(); + } + catch (ConfigurationException ce) + { + assertEquals("Virtualhosts file does not exist", ce.getMessage()); + } + catch (Exception e) + { + fail("Should throw a ConfigurationException"); + } + } + + /* + * Tests that the old element security.jmx.access (that used to be used + * to define JMX access rights) is rejected. + */ + public void testManagementAccessRejected() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.initialise(); + + // Check value we set + _config.setProperty("security.jmx.access(0)", "jmxremote.access"); + serverConfig = new ServerConfiguration(_config); + + try + { + serverConfig.initialise(); + fail("Exception not thrown"); + } + catch (ConfigurationException ce) + { + assertEquals("Incorrect error message", + "Validation error : security/jmx/access is no longer a supported element within the configuration xml.", + ce.getMessage()); + } + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/TopicConfigurationTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/TopicConfigurationTest.java new file mode 100644 index 0000000000..7fc3b2d06a --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/TopicConfigurationTest.java @@ -0,0 +1,130 @@ +/* + * + * 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.qpid.server.configuration; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQInternalException; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.util.InternalBrokerBaseCase; + +/** + * Test of the new Topic configuration processing + */ +public class TopicConfigurationTest extends InternalBrokerBaseCase +{ + + @Override + public void configure() + { + getConfigXml().addProperty("virtualhosts.virtualhost.test.topics.topic.name", "stocks.nyse.appl"); + + getConfigXml().addProperty("virtualhosts.virtualhost.test.topics.topic(1).subscriptionName", getName()+":stockSubscription"); + + getConfigXml().addProperty("virtualhosts.virtualhost.test.topics.topic(2).name", "stocks.nyse.orcl"); + getConfigXml().addProperty("virtualhosts.virtualhost.test.topics.topic(2).subscriptionName", getName()+":stockSubscription"); + } + + /** + * Test that a TopicConfig object is created and attached to the queue when it is bound to the topic exchange. + * + + * @throws ConfigurationException + * @throws AMQSecurityException + */ + public void testTopicCreation() throws ConfigurationException, AMQSecurityException, AMQInternalException + { + Exchange topicExchange = getVirtualHost().getExchangeRegistry().getExchange(ExchangeDefaults.TOPIC_EXCHANGE_NAME); + getVirtualHost().getBindingFactory().addBinding("stocks.nyse.appl", getQueue(), topicExchange, null); + + TopicConfig config = getQueue().getConfiguration().getConfiguration(TopicConfig.class.getName()); + + assertNotNull("Queue should have topic configuration bound to it.", config); + assertEquals("Configuration name not correct", "stocks.nyse.appl", config.getName()); + } + + /** + * Test that a queue created for a subscription correctly has topic + * configuration selected based on the subscription and topic name. + * + * @throws ConfigurationException + * @throws AMQException + */ + public void testSubscriptionWithTopicCreation() throws ConfigurationException, AMQException + { + + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString(getName()+":stockSubscription"), false, new AMQShortString("testowner"), + false, false, getVirtualHost(), null); + + getVirtualHost().getQueueRegistry().registerQueue(queue); + Exchange defaultExchange = getVirtualHost().getExchangeRegistry().getDefaultExchange(); + getVirtualHost().getBindingFactory().addBinding(getName(), queue, defaultExchange, null); + + + Exchange topicExchange = getVirtualHost().getExchangeRegistry().getExchange(ExchangeDefaults.TOPIC_EXCHANGE_NAME); + getVirtualHost().getBindingFactory().addBinding("stocks.nyse.orcl", queue, topicExchange, null); + + TopicConfig config = queue.getConfiguration().getConfiguration(TopicConfig.class.getName()); + + assertNotNull("Queue should have topic configuration bound to it.", config); + assertEquals("Configuration subscription name not correct", getName() + ":stockSubscription", config.getSubscriptionName()); + assertEquals("Configuration name not correct", "stocks.nyse.orcl", config.getName()); + + } + + /** + * Test that a queue created for a subscription correctly has topic + * configuration attached here this should be the generic topic section + * with just the subscriptionName + * + * @throws ConfigurationException + * @throws AMQException + */ + public void testSubscriptionCreation() throws ConfigurationException, AMQException + { + + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString(getName()+":stockSubscription"), false, new AMQShortString("testowner"), + false, false, getVirtualHost(), null); + + getVirtualHost().getQueueRegistry().registerQueue(queue); + Exchange defaultExchange = getVirtualHost().getExchangeRegistry().getDefaultExchange(); + getVirtualHost().getBindingFactory().addBinding(getName(), queue, defaultExchange, null); + + + Exchange topicExchange = getVirtualHost().getExchangeRegistry().getExchange(ExchangeDefaults.TOPIC_EXCHANGE_NAME); + getVirtualHost().getBindingFactory().addBinding("stocks.nyse.ibm", queue, topicExchange, null); + + TopicConfig config = queue.getConfiguration().getConfiguration(TopicConfig.class.getName()); + + assertNotNull("Queue should have topic configuration bound to it.", config); + assertEquals("Configuration subscription name not correct", getName() + ":stockSubscription", config.getSubscriptionName()); + assertEquals("Configuration name not correct", "#", config.getName()); + + } + + + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/VirtualHostConfigurationTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/VirtualHostConfigurationTest.java new file mode 100644 index 0000000000..593119041d --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/VirtualHostConfigurationTest.java @@ -0,0 +1,233 @@ +/* + * 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.qpid.server.configuration; + + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.XMLConfiguration; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.queue.AMQPriorityQueue; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.store.TestableMemoryMessageStore; +import org.apache.qpid.server.util.InternalBrokerBaseCase; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class VirtualHostConfigurationTest extends InternalBrokerBaseCase +{ + + @Override + public void setUp() throws Exception + { + super.setUp(); + // Set the default configuration items + getConfigXml().clear(); + getConfigXml().addProperty("virtualhosts.virtualhost(-1).name", "test"); + getConfigXml().addProperty("virtualhosts.virtualhost(-1).test.store.class", TestableMemoryMessageStore.class.getName()); + + getConfigXml().addProperty("virtualhosts.virtualhost.name", getName()); + getConfigXml().addProperty("virtualhosts.virtualhost."+getName()+".store.class", TestableMemoryMessageStore.class.getName()); + } + + @Override + public void createBroker() + { + // Prevent auto broker startup + } + + public void testQueuePriority() throws Exception + { + // Set up queue with 5 priorities + getConfigXml().addProperty("virtualhosts.virtualhost.testQueuePriority.queues(-1).queue(-1).name(-1)", + "atest"); + getConfigXml().addProperty("virtualhosts.virtualhost.testQueuePriority.queues.queue.atest(-1).exchange", + "amq.direct"); + getConfigXml().addProperty("virtualhosts.virtualhost.testQueuePriority.queues.queue.atest.priorities", + "5"); + + // Set up queue with JMS style priorities + getConfigXml().addProperty("virtualhosts.virtualhost.testQueuePriority.queues(-1).queue(-1).name(-1)", + "ptest"); + getConfigXml().addProperty("virtualhosts.virtualhost.testQueuePriority.queues.queue.ptest(-1).exchange", + "amq.direct"); + getConfigXml().addProperty("virtualhosts.virtualhost.testQueuePriority.queues.queue.ptest.priority", + "true"); + + // Set up queue with no priorities + getConfigXml().addProperty("virtualhosts.virtualhost.testQueuePriority.queues(-1).queue(-1).name(-1)", + "ntest"); + getConfigXml().addProperty("virtualhosts.virtualhost.testQueuePriority.queues.queue.ntest(-1).exchange", + "amq.direct"); + getConfigXml().addProperty("virtualhosts.virtualhost.testQueuePriority.queues.queue.ntest.priority", + "false"); + + // Start the broker now. + super.createBroker(); + + VirtualHost vhost = + ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHost(getName()); + + // Check that atest was a priority queue with 5 priorities + AMQQueue atest = vhost.getQueueRegistry().getQueue(new AMQShortString("atest")); + assertTrue(atest instanceof AMQPriorityQueue); + assertEquals(5, ((AMQPriorityQueue) atest).getPriorities()); + + // Check that ptest was a priority queue with 10 priorities + AMQQueue ptest = vhost.getQueueRegistry().getQueue(new AMQShortString("ptest")); + assertTrue(ptest instanceof AMQPriorityQueue); + assertEquals(10, ((AMQPriorityQueue) ptest).getPriorities()); + + // Check that ntest wasn't a priority queue + AMQQueue ntest = vhost.getQueueRegistry().getQueue(new AMQShortString("ntest")); + assertFalse(ntest instanceof AMQPriorityQueue); + } + + public void testQueueAlerts() throws Exception + { + // Set up queue with 5 priorities + getConfigXml().addProperty("virtualhosts.virtualhost.testQueueAlerts.queues.exchange", "amq.topic"); + getConfigXml().addProperty("virtualhosts.virtualhost.testQueueAlerts.queues.maximumQueueDepth", "1"); + getConfigXml().addProperty("virtualhosts.virtualhost.testQueueAlerts.queues.maximumMessageSize", "2"); + getConfigXml().addProperty("virtualhosts.virtualhost.testQueueAlerts.queues.maximumMessageAge", "3"); + + getConfigXml().addProperty("virtualhosts.virtualhost.testQueueAlerts.queues(-1).queue(1).name(1)", "atest"); + getConfigXml().addProperty("virtualhosts.virtualhost.testQueueAlerts.queues.queue.atest(-1).exchange", "amq.direct"); + getConfigXml().addProperty("virtualhosts.virtualhost.testQueueAlerts.queues.queue.atest(-1).maximumQueueDepth", "4"); + getConfigXml().addProperty("virtualhosts.virtualhost.testQueueAlerts.queues.queue.atest(-1).maximumMessageSize", "5"); + getConfigXml().addProperty("virtualhosts.virtualhost.testQueueAlerts.queues.queue.atest(-1).maximumMessageAge", "6"); + + getConfigXml().addProperty("virtualhosts.virtualhost.testQueueAlerts.queues(-1).queue(-1).name(-1)", "btest"); + + // Start the broker now. + super.createBroker(); + + VirtualHost vhost = + ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHost(getName()); + + // Check specifically configured values + AMQQueue aTest = vhost.getQueueRegistry().getQueue(new AMQShortString("atest")); + assertEquals(4, aTest.getMaximumQueueDepth()); + assertEquals(5, aTest.getMaximumMessageSize()); + assertEquals(6, aTest.getMaximumMessageAge()); + + // Check default values + AMQQueue bTest = vhost.getQueueRegistry().getQueue(new AMQShortString("btest")); + assertEquals(1, bTest.getMaximumQueueDepth()); + assertEquals(2, bTest.getMaximumMessageSize()); + assertEquals(3, bTest.getMaximumMessageAge()); + } + + /** + * Test that the house keeping pool sizes is correctly processed + * + * @throws Exception + */ + public void testHouseKeepingThreadCount() throws Exception + { + int initialPoolSize = 10; + + getConfigXml().addProperty("virtualhosts.virtualhost.testHouseKeepingThreadCount.housekeeping.poolSize", + initialPoolSize); + + // Start the broker now. + super.createBroker(); + + VirtualHost vhost = + ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHost(getName()); + + assertEquals("HouseKeeping PoolSize not set correctly.", + initialPoolSize, vhost.getHouseKeepingPoolSize()); + } + + /** + * Test default house keeping tasks + * + * @throws Exception + */ + public void testDefaultHouseKeepingTasks() throws Exception + { + // Start the broker now. + super.createBroker(); + + VirtualHost vhost = + ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHost(getName()); + + assertEquals("Default houseKeeping task count incorrect.", 2, + vhost.getHouseKeepingTaskCount()); + + // Currently the two are tasks: + // ExpiredMessageTask from VirtualHost + // UpdateTask from the QMF ManagementExchange + } + + /** + * Test that we can dynamically change the thread pool size + * + * @throws Exception + */ + public void testDynamicHouseKeepingPoolSizeChange() throws Exception + { + int initialPoolSize = 10; + + getConfigXml().addProperty("virtualhosts.virtualhost.testDynamicHouseKeepingPoolSizeChange.housekeeping.poolSize", + initialPoolSize); + + // Start the broker now. + super.createBroker(); + + VirtualHost vhost = + ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHost(getName()); + + assertEquals("HouseKeeping PoolSize not set correctly.", + initialPoolSize, vhost.getHouseKeepingPoolSize()); + + vhost.setHouseKeepingPoolSize(1); + + assertEquals("HouseKeeping PoolSize not correctly change.", + 1, vhost.getHouseKeepingPoolSize()); + + } + + /** + * Tests that the old element security.authentication.name is rejected. This element + * was never supported properly as authentication is performed before the virtual host + * is considered. + */ + public void testSecurityAuthenticationNameRejected() throws Exception + { + getConfigXml().addProperty("virtualhosts.virtualhost.testSecurityAuthenticationNameRejected.security.authentication.name", + "testdb"); + + try + { + super.createBroker(); + fail("Exception not thrown"); + } + catch(ConfigurationException ce) + { + assertEquals("Incorrect error message", + "Validation error : security/authentication/name is no longer a supported element within the configuration xml." + + " It appears in virtual host definition : " + getName(), + ce.getMessage()); + } + } + + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/plugins/ConfigurationPluginTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/plugins/ConfigurationPluginTest.java new file mode 100644 index 0000000000..ee2f77f16b --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/plugins/ConfigurationPluginTest.java @@ -0,0 +1,210 @@ +/* + * + * 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.qpid.server.configuration.plugins; + +import junit.framework.TestCase; +import org.apache.commons.configuration.CompositeConfiguration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.XMLConfiguration; +import org.apache.qpid.server.util.InternalBrokerBaseCase; + +import java.util.List; + +/** + * Test that verifies that given a Configuration a ConfigurationPlugin can + * process and validate that data. + */ +public class ConfigurationPluginTest extends InternalBrokerBaseCase +{ + private static final double DOUBLE = 3.14; + private static final long POSITIVE_LONG = 1000; + private static final long NEGATIVE_LONG = -1000; + private static final int LIST_SIZE = 3; + + class ConfigPlugin extends ConfigurationPlugin + { + @Override + public String[] getElementsProcessed() + { + return new String[]{"[@property]", "name", + "positiveLong", "negativeLong", + "true", "list", "double"}; + } + + @Override + public void validateConfiguration() throws ConfigurationException + { + // no validation requried + } + + public String getName() + { + return getStringValue("name"); + } + + public String getProperty() + { + return getStringValue("[@property]"); + } + + + } + + ConfigPlugin _plugin; + + @Override + public void setUp() throws Exception + { + // Test does not directly use the AppRegistry but the configured broker + // is required for the correct ConfigurationPlugin processing + super.setUp(); + XMLConfiguration xmlconfig = new XMLConfiguration(); + xmlconfig.addProperty("base.element[@property]", "property"); + xmlconfig.addProperty("base.element.name", "name"); + // We make these strings as that is how they will be read from the file. + xmlconfig.addProperty("base.element.positiveLong", String.valueOf(POSITIVE_LONG)); + xmlconfig.addProperty("base.element.negativeLong", String.valueOf(NEGATIVE_LONG)); + xmlconfig.addProperty("base.element.boolean", String.valueOf(true)); + xmlconfig.addProperty("base.element.double", String.valueOf(DOUBLE)); + for (int i = 0; i < LIST_SIZE; i++) + { + xmlconfig.addProperty("base.element.list", i); + } + + //Use a composite configuration as this is what our broker code uses. + CompositeConfiguration composite = new CompositeConfiguration(); + composite.addConfiguration(xmlconfig); + + _plugin = new ConfigPlugin(); + + try + { + _plugin.setConfiguration("base.element", composite.subset("base.element")); + } + catch (ConfigurationException e) + { + e.printStackTrace(); + fail(e.toString()); + } + + } + + public void testHasConfiguration() + { + assertTrue("Plugin has no configuration ", _plugin.hasConfiguration()); + _plugin = new ConfigPlugin(); + assertFalse("Plugins has configuration", _plugin.hasConfiguration()); + } + + public void testValuesRetreived() + { + assertEquals("Name not correct", "name", _plugin.getName()); + assertEquals("Property not correct", "property", _plugin.getProperty()); + } + + public void testContainsPositiveLong() + { + assertTrue("positiveLong is not positive", _plugin.containsPositiveLong("positiveLong")); + assertFalse("NonExistentValue was found", _plugin.containsPositiveLong("NonExistentValue")); + + try + { + _plugin.validatePositiveLong("positiveLong"); + } + catch (ConfigurationException e) + { + fail(e.getMessage()); + } + + try + { + _plugin.validatePositiveLong("negativeLong"); + fail("negativeLong should not be positive"); + } + catch (ConfigurationException e) + { + assertEquals("negativeLong should not be reported as positive", + "ConfigPlugin: unable to configure invalid negativeLong:" + NEGATIVE_LONG, e.getMessage()); + } + + } + + public void testDouble() + { + assertEquals("Double value not returned", DOUBLE, _plugin.getDoubleValue("double")); + assertEquals("default Double value not returned", 0.0, _plugin.getDoubleValue("NonExistent")); + assertEquals("set default Double value not returned", DOUBLE, _plugin.getDoubleValue("NonExistent", DOUBLE)); + } + + public void testLong() + { + assertTrue("Long value not returned", _plugin.containsLong("positiveLong")); + assertFalse("Long value returned", _plugin.containsLong("NonExistent")); + assertEquals("Long value not returned", POSITIVE_LONG, _plugin.getLongValue("positiveLong")); + assertEquals("default Long value not returned", 0, _plugin.getLongValue("NonExistent")); + assertEquals("set default Long value not returned", NEGATIVE_LONG, _plugin.getLongValue("NonExistent", NEGATIVE_LONG)); + } + + public void testInt() + { + assertTrue("Int value not returned", _plugin.containsInt("positiveLong")); + assertFalse("Int value returned", _plugin.containsInt("NonExistent")); + assertEquals("Int value not returned", (int) POSITIVE_LONG, _plugin.getIntValue("positiveLong")); + assertEquals("default Int value not returned", 0, _plugin.getIntValue("NonExistent")); + assertEquals("set default Int value not returned", (int) NEGATIVE_LONG, _plugin.getIntValue("NonExistent", (int) NEGATIVE_LONG)); + } + + public void testString() + { + assertEquals("String value not returned", "name", _plugin.getStringValue("name")); + assertNull("Null default String value not returned", _plugin.getStringValue("NonExistent", null)); + assertNull("default String value not returned", _plugin.getStringValue("NonExistent")); + assertEquals("default String value not returned", "Default", _plugin.getStringValue("NonExistent", "Default")); + } + + public void testBoolean() + { + assertTrue("Boolean value not returned", _plugin.containsBoolean("boolean")); + assertFalse("Boolean value not returned", _plugin.containsBoolean("NonExistent")); + assertTrue("Boolean value not returned", _plugin.getBooleanValue("boolean")); + assertFalse("default String value not returned", _plugin.getBooleanValue("NonExistent")); + assertTrue("set default String value not returned", _plugin.getBooleanValue("NonExistent", true)); + } + + public void testList() + { + assertTrue("list not found in plugin", _plugin.contains("list")); + List list = _plugin.getListValue("list"); + assertNotNull("Returned list should not be null", list); + assertEquals("List should not be empty", LIST_SIZE, list.size()); + + list = _plugin.getListValue("NonExistent"); + assertNotNull("Returned list should not be null", list); + assertEquals("List is not empty", 0, list.size()); + } + + public void testContains() + { + assertTrue("list not found in plugin", _plugin.contains("list")); + assertFalse("NonExistent found in plugin", _plugin.contains("NonExistent")); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/AbstractHeadersExchangeTestBase.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/AbstractHeadersExchangeTestBase.java new file mode 100644 index 0000000000..9e831b2a8e --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/AbstractHeadersExchangeTestBase.java @@ -0,0 +1,625 @@ +/* + * + * 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.qpid.server.exchange; + +import junit.framework.TestCase; +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.FieldTableFactory; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.binding.BindingFactory; +import org.apache.qpid.server.message.AMQMessage; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.message.MessageMetaData; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.queue.IncomingMessage; +import org.apache.qpid.server.queue.MockStoredMessage; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.queue.SimpleAMQQueue; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.store.DurableConfigurationStore; +import org.apache.qpid.server.store.MemoryMessageStore; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.StoredMessage; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.util.InternalBrokerBaseCase; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; + +public class AbstractHeadersExchangeTestBase extends InternalBrokerBaseCase +{ + private static final Logger _log = Logger.getLogger(AbstractHeadersExchangeTestBase.class); + + private final HeadersExchange exchange = new HeadersExchange(); + protected final Set<TestQueue> queues = new HashSet<TestQueue>(); + + + + /** + * Not used in this test, just there to stub out the routing calls + */ + private MessageStore _store = new MemoryMessageStore(); + + + BindingFactory bindingFactory = new BindingFactory(new DurableConfigurationStore.Source() + { + + public DurableConfigurationStore getDurableConfigurationStore() + { + return _store; + } + }, + exchange); + + private int count; + + public void testDoNothing() + { + // this is here only to make junit under Eclipse happy + } + + protected TestQueue bindDefault(String... bindings) throws AMQException + { + String queueName = "Queue" + (++count); + + return bind(queueName, queueName, getHeadersMap(bindings)); + } + + protected void unbind(TestQueue queue, String... bindings) throws AMQException + { + String queueName = queue.getName(); + //TODO - check this + exchange.onUnbind(new Binding(null,queueName, queue, exchange, getHeadersMap(bindings))); + } + + protected int getCount() + { + return count; + } + + private TestQueue bind(String key, String queueName, Map<String,Object> args) throws AMQException + { + TestQueue queue = new TestQueue(new AMQShortString(queueName)); + queues.add(queue); + exchange.onBind(new Binding(null,key, queue, exchange, args)); + return queue; + } + + + protected int route(Message m) throws AMQException + { + m.getIncomingMessage().headersReceived(); + m.route(exchange); + if(m.getIncomingMessage().allContentReceived()) + { + for(BaseQueue q : m.getIncomingMessage().getDestinationQueues()) + { + q.enqueue(m); + } + } + return m.getIncomingMessage().getDestinationQueues().size(); + } + + protected void routeAndTest(Message m, TestQueue... expected) throws AMQException + { + routeAndTest(m, false, Arrays.asList(expected)); + } + + protected void routeAndTest(Message m, boolean expectReturn, TestQueue... expected) throws AMQException + { + routeAndTest(m, expectReturn, Arrays.asList(expected)); + } + + protected void routeAndTest(Message m, List<TestQueue> expected) throws AMQException + { + routeAndTest(m, false, expected); + } + + protected void routeAndTest(Message m, boolean expectReturn, List<TestQueue> expected) throws AMQException + { + int queueCount = route(m); + + for (TestQueue q : queues) + { + if (expected.contains(q)) + { + assertTrue("Expected " + m + " to be delivered to " + q, q.isInQueue(m)); + //assert m.isInQueue(q) : "Expected " + m + " to be delivered to " + q; + } + else + { + assertFalse("Did not expect " + m + " to be delivered to " + q, q.isInQueue(m)); + //assert !m.isInQueue(q) : "Did not expect " + m + " to be delivered to " + q; + } + } + + if(expectReturn) + { + assertEquals("Expected "+m+" to be returned due to manadatory flag, and lack of routing",0, queueCount); + } + + } + + static Map<String,Object> getHeadersMap(String... entries) + { + if(entries == null) + { + return null; + } + + Map<String,Object> headers = new HashMap<String,Object>(); + + for (String s : entries) + { + String[] parts = s.split("=", 2); + headers.put(parts[0], parts.length > 1 ? parts[1] : ""); + } + return headers; + } + + static FieldTable getHeaders(String... entries) + { + FieldTable headers = FieldTableFactory.newFieldTable(); + for (String s : entries) + { + String[] parts = s.split("=", 2); + headers.setObject(parts[0], parts.length > 1 ? parts[1] : ""); + } + return headers; + } + + + static final class MessagePublishInfoImpl implements MessagePublishInfo + { + private AMQShortString _exchange; + private boolean _immediate; + private boolean _mandatory; + private AMQShortString _routingKey; + + public MessagePublishInfoImpl(AMQShortString routingKey) + { + _routingKey = routingKey; + } + + public MessagePublishInfoImpl(AMQShortString exchange, boolean immediate, boolean mandatory, AMQShortString routingKey) + { + _exchange = exchange; + _immediate = immediate; + _mandatory = mandatory; + _routingKey = routingKey; + } + + public AMQShortString getExchange() + { + return _exchange; + } + + public boolean isImmediate() + { + return _immediate; + + } + + public boolean isMandatory() + { + return _mandatory; + } + + public AMQShortString getRoutingKey() + { + return _routingKey; + } + + + public void setExchange(AMQShortString exchange) + { + _exchange = exchange; + } + + public void setImmediate(boolean immediate) + { + _immediate = immediate; + } + + public void setMandatory(boolean mandatory) + { + _mandatory = mandatory; + } + + public void setRoutingKey(AMQShortString routingKey) + { + _routingKey = routingKey; + } + } + + static MessagePublishInfo getPublishRequest(final String id) + { + return new MessagePublishInfoImpl(null, false, false, new AMQShortString(id)); + } + + static ContentHeaderBody getContentHeader(FieldTable headers) + { + ContentHeaderBody header = new ContentHeaderBody(); + header.setProperties(getProperties(headers)); + return header; + } + + static BasicContentHeaderProperties getProperties(FieldTable headers) + { + BasicContentHeaderProperties properties = new BasicContentHeaderProperties(); + properties.setHeaders(headers); + return properties; + } + + static class TestQueue extends SimpleAMQQueue + { + final List<HeadersExchangeTest.Message> messages = new ArrayList<HeadersExchangeTest.Message>(); + + public String toString() + { + return getNameShortString().toString(); + } + + public TestQueue(AMQShortString name) throws AMQException + { + super(name, false, new AMQShortString("test"), true, false,ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHost("test"), Collections.EMPTY_MAP); + ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHost("test").getQueueRegistry().registerQueue(this); + } + + + + /** + * We override this method so that the default behaviour, which attempts to use a delivery manager, is + * not invoked. It is unnecessary since for this test we only care to know whether the message was + * sent to the queue; the queue processing logic is not being tested. + * @param msg + * @throws AMQException + */ + @Override + public void enqueue(ServerMessage msg, PostEnqueueAction action) throws AMQException + { + messages.add( new HeadersExchangeTest.Message((AMQMessage) msg)); + final QueueEntry queueEntry = new QueueEntry() + { + + public AMQQueue getQueue() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public AMQMessage getMessage() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getSize() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean getDeliveredToConsumer() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean expired() throws AMQException + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isAvailable() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isAcquired() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean acquire() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean acquire(Subscription sub) + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean delete() + { + return false; + } + + public boolean isDeleted() + { + return false; + } + + public boolean acquiredBySubscription() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isAcquiredBy(Subscription subscription) + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public void release() + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean releaseButRetain() + { + return false; + } + + public boolean immediateAndNotDelivered() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public void setRedelivered() + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public AMQMessageHeader getMessageHeader() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isPersistent() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isRedelivered() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public Subscription getDeliveredSubscription() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public void reject() + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void reject(Subscription subscription) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isRejectedBy(Subscription subscription) + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public void requeue(Subscription subscription) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void dequeue() + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void dispose() + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void discard() + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void routeToAlternate() + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isQueueDeleted() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public void addStateChangeListener(StateChangeListener listener) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean removeStateChangeListener(StateChangeListener listener) + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public int compareTo(final QueueEntry o) + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + }; + + if(action != null) + { + action.onEnqueue(queueEntry); + } + + } + + boolean isInQueue(Message msg) + { + return messages.contains(msg); + } + + } + + /** + * Just add some extra utility methods to AMQMessage to aid testing. + */ + static class Message extends AMQMessage + { + private static AtomicLong _messageId = new AtomicLong(); + + private class TestIncomingMessage extends IncomingMessage + { + + public TestIncomingMessage(final long messageId, + final MessagePublishInfo info, + final AMQProtocolSession publisher) + { + super(info); + } + + + public AMQMessage getUnderlyingMessage() + { + return Message.this; + } + + + public ContentHeaderBody getContentHeader() + { + try + { + return Message.this.getContentHeaderBody(); + } + catch (AMQException e) + { + throw new RuntimeException(e); + } + } + } + + private IncomingMessage _incoming; + + + Message(AMQProtocolSession protocolSession, String id, String... headers) throws AMQException + { + this(protocolSession, id, getHeaders(headers)); + } + + Message(AMQProtocolSession protocolSession, String id, FieldTable headers) throws AMQException + { + this(protocolSession, _messageId.incrementAndGet(),getPublishRequest(id), getContentHeader(headers), Collections.EMPTY_LIST); + } + + public IncomingMessage getIncomingMessage() + { + return _incoming; + } + + private Message(AMQProtocolSession protocolsession, long messageId, + MessagePublishInfo publish, + ContentHeaderBody header, + List<ContentBody> bodies) throws AMQException + { + super(new MockStoredMessage(messageId, publish, header)); + + StoredMessage<MessageMetaData> storedMessage = getStoredMessage(); + + int pos = 0; + for(ContentBody body : bodies) + { + storedMessage.addContent(pos, body.payload.duplicate().buf()); + pos += body.payload.limit(); + } + + _incoming = new TestIncomingMessage(getMessageId(),publish, protocolsession); + _incoming.setContentHeaderBody(header); + + + } + + + private Message(AMQMessage msg) throws AMQException + { + super(msg.getStoredMessage()); + } + + + + void route(Exchange exchange) throws AMQException + { + _incoming.enqueue(exchange.route(_incoming)); + } + + + public int hashCode() + { + return getKey().hashCode(); + } + + public boolean equals(Object o) + { + return o instanceof HeadersExchangeTest.Message && equals((HeadersExchangeTest.Message) o); + } + + private boolean equals(HeadersExchangeTest.Message m) + { + return getKey().equals(m.getKey()); + } + + public String toString() + { + return getKey().toString(); + } + + private Object getKey() + { + try + { + return getMessagePublishInfo().getRoutingKey(); + } + catch (AMQException e) + { + _log.error("Error getting routing key: " + e, e); + return null; + } + } + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/ExchangeMBeanTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/ExchangeMBeanTest.java new file mode 100644 index 0000000000..71e92b5294 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/ExchangeMBeanTest.java @@ -0,0 +1,195 @@ +/* + * + * 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.qpid.server.exchange; + +import junit.framework.TestCase; + +import org.apache.qpid.management.common.mbeans.ManagedExchange; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.server.management.ManagedObject; +import org.apache.qpid.server.util.InternalBrokerBaseCase; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; + +import javax.management.openmbean.TabularData; +import java.util.ArrayList; +import java.util.Collections; + +/** + * Unit test class for testing different Exchange MBean operations + */ +public class ExchangeMBeanTest extends InternalBrokerBaseCase +{ + private AMQQueue _queue; + private QueueRegistry _queueRegistry; + private VirtualHost _virtualHost; + + /** + * Test for direct exchange mbean + * @throws Exception + */ + + public void testDirectExchangeMBean() throws Exception + { + DirectExchange exchange = new DirectExchange(); + exchange.initialise(_virtualHost, ExchangeDefaults.DIRECT_EXCHANGE_NAME, false, 0, true); + ManagedObject managedObj = exchange.getManagedObject(); + ManagedExchange mbean = (ManagedExchange)managedObj; + + mbean.createNewBinding(_queue.getNameShortString().toString(), "binding1"); + mbean.createNewBinding(_queue.getNameShortString().toString(), "binding2"); + + TabularData data = mbean.bindings(); + ArrayList<Object> list = new ArrayList<Object>(data.values()); + assertTrue(list.size() == 2); + + // test general exchange properties + assertEquals(mbean.getName(), "amq.direct"); + assertEquals(mbean.getExchangeType(), "direct"); + assertTrue(mbean.getTicketNo() == 0); + assertTrue(!mbean.isDurable()); + assertTrue(mbean.isAutoDelete()); + } + + /** + * Test for "topic" exchange mbean + * @throws Exception + */ + + public void testTopicExchangeMBean() throws Exception + { + TopicExchange exchange = new TopicExchange(); + exchange.initialise(_virtualHost,ExchangeDefaults.TOPIC_EXCHANGE_NAME, false, 0, true); + ManagedObject managedObj = exchange.getManagedObject(); + ManagedExchange mbean = (ManagedExchange)managedObj; + + mbean.createNewBinding(_queue.getNameShortString().toString(), "binding1"); + mbean.createNewBinding(_queue.getNameShortString().toString(), "binding2"); + + TabularData data = mbean.bindings(); + ArrayList<Object> list = new ArrayList<Object>(data.values()); + assertTrue(list.size() == 2); + + // test general exchange properties + assertEquals(mbean.getName(), "amq.topic"); + assertEquals(mbean.getExchangeType(), "topic"); + assertTrue(mbean.getTicketNo() == 0); + assertTrue(!mbean.isDurable()); + assertTrue(mbean.isAutoDelete()); + } + + /** + * Test for "Headers" exchange mbean + * @throws Exception + */ + + public void testHeadersExchangeMBean() throws Exception + { + HeadersExchange exchange = new HeadersExchange(); + exchange.initialise(_virtualHost,ExchangeDefaults.HEADERS_EXCHANGE_NAME, false, 0, true); + ManagedObject managedObj = exchange.getManagedObject(); + ManagedExchange mbean = (ManagedExchange)managedObj; + + mbean.createNewBinding(_queue.getNameShortString().toString(), "key1=binding1,key2=binding2"); + mbean.createNewBinding(_queue.getNameShortString().toString(), "key3=binding3"); + + TabularData data = mbean.bindings(); + ArrayList<Object> list = new ArrayList<Object>(data.values()); + assertTrue(list.size() == 2); + + // test general exchange properties + assertEquals(mbean.getName(), "amq.match"); + assertEquals(mbean.getExchangeType(), "headers"); + assertTrue(mbean.getTicketNo() == 0); + assertTrue(!mbean.isDurable()); + assertTrue(mbean.isAutoDelete()); + } + + /** + * Test adding bindings and removing them from the default exchange via JMX. + * <p> + * QPID-2700 + */ + public void testDefaultBindings() throws Exception + { + int bindings = _queue.getBindingCount(); + + Exchange exchange = _queue.getVirtualHost().getExchangeRegistry().getDefaultExchange(); + ManagedExchange mbean = (ManagedExchange) ((AbstractExchange) exchange).getManagedObject(); + + mbean.createNewBinding(_queue.getName(), "robot"); + mbean.createNewBinding(_queue.getName(), "kitten"); + + assertEquals("Should have added two bindings", bindings + 2, _queue.getBindingCount()); + + mbean.removeBinding(_queue.getName(), "robot"); + + assertEquals("Should have one extra binding", bindings + 1, _queue.getBindingCount()); + + mbean.removeBinding(_queue.getName(), "kitten"); + + assertEquals("Should have original number of binding", bindings, _queue.getBindingCount()); + } + + /** + * Test adding bindings and removing them from the topic exchange via JMX. + * <p> + * QPID-2700 + */ + public void testTopicBindings() throws Exception + { + int bindings = _queue.getBindingCount(); + + Exchange exchange = _queue.getVirtualHost().getExchangeRegistry().getExchange(new AMQShortString("amq.topic")); + ManagedExchange mbean = (ManagedExchange) ((AbstractExchange) exchange).getManagedObject(); + + mbean.createNewBinding(_queue.getName(), "robot.#"); + mbean.createNewBinding(_queue.getName(), "#.kitten"); + + assertEquals("Should have added two bindings", bindings + 2, _queue.getBindingCount()); + + mbean.removeBinding(_queue.getName(), "robot.#"); + + assertEquals("Should have one extra binding", bindings + 1, _queue.getBindingCount()); + + mbean.removeBinding(_queue.getName(), "#.kitten"); + + assertEquals("Should have original number of binding", bindings, _queue.getBindingCount()); + } + + @Override + public void setUp() throws Exception + { + super.setUp(); + + IApplicationRegistry applicationRegistry = ApplicationRegistry.getInstance(); + _virtualHost = applicationRegistry.getVirtualHostRegistry().getVirtualHost("test"); + _queueRegistry = _virtualHost.getQueueRegistry(); + _queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("testQueue"), false, new AMQShortString("ExchangeMBeanTest"), false, false, + _virtualHost, null); + _queueRegistry.registerQueue(_queue); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/HeadersBindingTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/HeadersBindingTest.java new file mode 100644 index 0000000000..a7c226cbd8 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/HeadersBindingTest.java @@ -0,0 +1,317 @@ +/* + * + * 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.qpid.server.exchange; + +import java.util.Map; +import java.util.HashMap; +import java.util.Set; + +import junit.framework.TestCase; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.queue.MockAMQQueue; + +/** + */ +public class HeadersBindingTest extends TestCase +{ + + private class MockHeader implements AMQMessageHeader + { + + private final Map<String, Object> _headers = new HashMap<String, Object>(); + + public String getCorrelationId() + { + return null; + } + + public long getExpiration() + { + return 0; + } + + public String getMessageId() + { + return null; + } + + public String getMimeType() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public String getEncoding() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public byte getPriority() + { + return 0; + } + + public long getTimestamp() + { + return 0; + } + + public String getType() + { + return null; + } + + public String getReplyTo() + { + return null; + } + + public String getReplyToExchange() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public String getReplyToRoutingKey() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public Object getHeader(String name) + { + return _headers.get(name); + } + + public boolean containsHeaders(Set<String> names) + { + return _headers.keySet().containsAll(names); + } + + public boolean containsHeader(String name) + { + return _headers.containsKey(name); + } + + public void setString(String key, String value) + { + setObject(key,value); + } + + public void setObject(String key, Object value) + { + _headers.put(key,value); + } + } + + private Map<String,Object> bindHeaders = new HashMap<String,Object>(); + private MockHeader matchHeaders = new MockHeader(); + private int _count = 0; + private MockAMQQueue _queue; + + protected void setUp() + { + _count++; + _queue = new MockAMQQueue(getQueueName()); + } + + protected String getQueueName() + { + return "Queue" + _count; + } + + public void testDefault_1() + { + bindHeaders.put("A", "Value of A"); + + matchHeaders.setString("A", "Value of A"); + + Binding b = new Binding(null, getQueueName(), _queue, null, bindHeaders); + assertTrue(new HeadersBinding(b).matches(matchHeaders)); + } + + public void testDefault_2() + { + bindHeaders.put("A", "Value of A"); + + matchHeaders.setString("A", "Value of A"); + matchHeaders.setString("B", "Value of B"); + + Binding b = new Binding(null, getQueueName(), _queue, null, bindHeaders); + assertTrue(new HeadersBinding(b).matches(matchHeaders)); + } + + public void testDefault_3() + { + bindHeaders.put("A", "Value of A"); + + matchHeaders.setString("A", "Altered value of A"); + + Binding b = new Binding(null, getQueueName(), _queue, null, bindHeaders); + assertFalse(new HeadersBinding(b).matches(matchHeaders)); + } + + public void testAll_1() + { + bindHeaders.put("X-match", "all"); + bindHeaders.put("A", "Value of A"); + + matchHeaders.setString("A", "Value of A"); + + Binding b = new Binding(null, getQueueName(), _queue, null, bindHeaders); + assertTrue(new HeadersBinding(b).matches(matchHeaders)); + } + + public void testAll_2() + { + bindHeaders.put("X-match", "all"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.setString("A", "Value of A"); + + Binding b = new Binding(null, getQueueName(), _queue, null, bindHeaders); + assertFalse(new HeadersBinding(b).matches(matchHeaders)); + } + + public void testAll_3() + { + bindHeaders.put("X-match", "all"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.setString("A", "Value of A"); + matchHeaders.setString("B", "Value of B"); + + Binding b = new Binding(null, getQueueName(), _queue, null, bindHeaders); + assertTrue(new HeadersBinding(b).matches(matchHeaders)); + } + + public void testAll_4() + { + bindHeaders.put("X-match", "all"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.setString("A", "Value of A"); + matchHeaders.setString("B", "Value of B"); + matchHeaders.setString("C", "Value of C"); + + Binding b = new Binding(null, getQueueName(), _queue, null, bindHeaders); + assertTrue(new HeadersBinding(b).matches(matchHeaders)); + } + + public void testAll_5() + { + bindHeaders.put("X-match", "all"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.setString("A", "Value of A"); + matchHeaders.setString("B", "Altered value of B"); + matchHeaders.setString("C", "Value of C"); + + Binding b = new Binding(null, getQueueName(), _queue, null, bindHeaders); + assertFalse(new HeadersBinding(b).matches(matchHeaders)); + } + + public void testAny_1() + { + bindHeaders.put("X-match", "any"); + bindHeaders.put("A", "Value of A"); + + matchHeaders.setString("A", "Value of A"); + + Binding b = new Binding(null, getQueueName(), _queue, null, bindHeaders); + assertTrue(new HeadersBinding(b).matches(matchHeaders)); + } + + public void testAny_2() + { + bindHeaders.put("X-match", "any"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.setString("A", "Value of A"); + + Binding b = new Binding(null, getQueueName(), _queue, null, bindHeaders); + assertTrue(new HeadersBinding(b).matches(matchHeaders)); + } + + public void testAny_3() + { + bindHeaders.put("X-match", "any"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.setString("A", "Value of A"); + matchHeaders.setString("B", "Value of B"); + + Binding b = new Binding(null, getQueueName(), _queue, null, bindHeaders); + assertTrue(new HeadersBinding(b).matches(matchHeaders)); + } + + public void testAny_4() + { + bindHeaders.put("X-match", "any"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.setString("A", "Value of A"); + matchHeaders.setString("B", "Value of B"); + matchHeaders.setString("C", "Value of C"); + + Binding b = new Binding(null, getQueueName(), _queue, null, bindHeaders); + assertTrue(new HeadersBinding(b).matches(matchHeaders)); + } + + public void testAny_5() + { + bindHeaders.put("X-match", "any"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.setString("A", "Value of A"); + matchHeaders.setString("B", "Altered value of B"); + matchHeaders.setString("C", "Value of C"); + + Binding b = new Binding(null, getQueueName(), _queue, null, bindHeaders); + assertTrue(new HeadersBinding(b).matches(matchHeaders)); + } + + public void testAny_6() + { + bindHeaders.put("X-match", "any"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.setString("A", "Altered value of A"); + matchHeaders.setString("B", "Altered value of B"); + matchHeaders.setString("C", "Value of C"); + + Binding b = new Binding(null, getQueueName(), _queue, null, bindHeaders); + assertFalse(new HeadersBinding(b).matches(matchHeaders)); + } + + public static junit.framework.Test suite() + { + return new junit.framework.TestSuite(HeadersBindingTest.class); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/HeadersExchangeTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/HeadersExchangeTest.java new file mode 100644 index 0000000000..ac638e4e6a --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/HeadersExchangeTest.java @@ -0,0 +1,124 @@ +/* + * + * 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.qpid.server.exchange; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.protocol.InternalTestProtocolSession; +import org.apache.qpid.server.protocol.AMQProtocolSession; + +public class HeadersExchangeTest extends AbstractHeadersExchangeTestBase +{ + AMQProtocolSession _protocolSession; + + @Override + public void setUp() throws Exception + { + super.setUp(); + // Just use the first vhost. + VirtualHost + virtualHost = ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHosts().iterator().next(); + _protocolSession = new InternalTestProtocolSession(virtualHost); + } + + public void testSimple() throws AMQException + { + TestQueue q1 = bindDefault("F0000"); + TestQueue q2 = bindDefault("F0000=Aardvark"); + TestQueue q3 = bindDefault("F0001"); + TestQueue q4 = bindDefault("F0001=Bear"); + TestQueue q5 = bindDefault("F0000", "F0001"); + TestQueue q6 = bindDefault("F0000=Aardvark", "F0001=Bear"); + TestQueue q7 = bindDefault("F0000", "F0001=Bear"); + TestQueue q8 = bindDefault("F0000=Aardvark", "F0001"); + + routeAndTest(new Message(_protocolSession, "Message1", "F0000"), q1); + routeAndTest(new Message(_protocolSession, "Message2", "F0000=Aardvark"), q1, q2); + routeAndTest(new Message(_protocolSession, "Message3", "F0000=Aardvark", "F0001"), q1, q2, q3, q5, q8); + routeAndTest(new Message(_protocolSession, "Message4", "F0000", "F0001=Bear"), q1, q3, q4, q5, q7); + routeAndTest(new Message(_protocolSession, "Message5", "F0000=Aardvark", "F0001=Bear"), + q1, q2, q3, q4, q5, q6, q7, q8); + routeAndTest(new Message(_protocolSession, "Message6", "F0002")); + + Message m7 = new Message(_protocolSession, "Message7", "XXXXX"); + + MessagePublishInfoImpl pb7 = (MessagePublishInfoImpl) (m7.getMessagePublishInfo()); + pb7.setMandatory(true); + routeAndTest(m7,true); + + Message m8 = new Message(_protocolSession, "Message8", "F0000"); + MessagePublishInfoImpl pb8 = (MessagePublishInfoImpl)(m8.getMessagePublishInfo()); + pb8.setMandatory(true); + routeAndTest(m8,false,q1); + + + } + + public void testAny() throws AMQException + { + TestQueue q1 = bindDefault("F0000", "F0001", "X-match=any"); + TestQueue q2 = bindDefault("F0000=Aardvark", "F0001=Bear", "X-match=any"); + TestQueue q3 = bindDefault("F0000", "F0001=Bear", "X-match=any"); + TestQueue q4 = bindDefault("F0000=Aardvark", "F0001", "X-match=any"); + TestQueue q6 = bindDefault("F0000=Apple", "F0001", "X-match=any"); + + routeAndTest(new Message(_protocolSession, "Message1", "F0000"), q1, q3); + routeAndTest(new Message(_protocolSession, "Message2", "F0000=Aardvark"), q1, q2, q3, q4); + routeAndTest(new Message(_protocolSession, "Message3", "F0000=Aardvark", "F0001"), q1, q2, q3, q4, q6); + routeAndTest(new Message(_protocolSession, "Message4", "F0000", "F0001=Bear"), q1, q2, q3, q4, q6); + routeAndTest(new Message(_protocolSession, "Message5", "F0000=Aardvark", "F0001=Bear"), q1, q2, q3, q4, q6); + routeAndTest(new Message(_protocolSession, "Message6", "F0002")); + } + + public void testMandatory() throws AMQException + { + bindDefault("F0000"); + Message m1 = new Message(_protocolSession, "Message1", "XXXXX"); + Message m2 = new Message(_protocolSession, "Message2", "F0000"); + MessagePublishInfoImpl pb1 = (MessagePublishInfoImpl) (m1.getMessagePublishInfo()); + pb1.setMandatory(true); + MessagePublishInfoImpl pb2 = (MessagePublishInfoImpl) (m2.getMessagePublishInfo()); + pb2.setMandatory(true); + routeAndTest(m1,true); + } + + public void testOnUnbind() throws AMQException + { + TestQueue q1 = bindDefault("F0000"); + TestQueue q2 = bindDefault("F0000=Aardvark"); + TestQueue q3 = bindDefault("F0001"); + + routeAndTest(new Message(_protocolSession, "Message1", "F0000"), q1); + routeAndTest(new Message(_protocolSession, "Message2", "F0000=Aardvark"), q1, q2); + routeAndTest(new Message(_protocolSession, "Message3", "F0001"), q3); + + unbind(q1,"F0000"); + routeAndTest(new Message(_protocolSession, "Message4", "F0000")); + routeAndTest(new Message(_protocolSession, "Message5", "F0000=Aardvark"), q2); + } + + + public static junit.framework.Test suite() + { + return new junit.framework.TestSuite(HeadersExchangeTest.class); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/TopicExchangeTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/TopicExchangeTest.java new file mode 100644 index 0000000000..403a290a0f --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/TopicExchangeTest.java @@ -0,0 +1,441 @@ +/* + * 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.qpid.server.exchange; + +import junit.framework.TestCase; +import junit.framework.Assert; +import org.apache.qpid.server.queue.*; +import org.apache.qpid.server.util.InternalBrokerBaseCase; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.MemoryMessageStore; +import org.apache.qpid.server.protocol.InternalTestProtocolSession; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.message.AMQMessage; +import org.apache.qpid.server.message.MessageMetaData; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; + +public class TopicExchangeTest extends InternalBrokerBaseCase +{ + + TopicExchange _exchange; + + VirtualHost _vhost; + MessageStore _store; + + InternalTestProtocolSession _protocolSession; + + + @Override + public void setUp() throws Exception + { + super.setUp(); + _exchange = new TopicExchange(); + _vhost = ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHosts().iterator().next(); + _store = new MemoryMessageStore(); + _protocolSession = new InternalTestProtocolSession(_vhost); + } + + public void testNoRoute() throws AMQException + { + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("a*#b"), false, null, false, false, _vhost, null); + _exchange.registerQueue(new Binding(null,"a.*.#.b", queue,_exchange, null)); + + + IncomingMessage message = createMessage("a.b"); + routeMessage(message); + + Assert.assertEquals(0, queue.getMessageCount()); + } + + public void testDirectMatch() throws AMQException + { + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("ab"), false, null, false, false, _vhost, null); + _exchange.registerQueue(new Binding(null,"a.b", queue,_exchange, null)); + + + IncomingMessage message = createMessage("a.b"); + + routeMessage(message); + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message recevied", (Object) message.getMessageNumber(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageNumber()); + + queue.deleteMessageFromTop(); + Assert.assertEquals(0, queue.getMessageCount()); + + + message = createMessage("a.c"); + + int queueCount = routeMessage(message); + Assert.assertEquals("Message should not route to any queues", 0, queueCount); + + Assert.assertEquals(0, queue.getMessageCount()); + } + + + public void testStarMatch() throws AMQException + { + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("a*"), false, null, false, false, _vhost, null); + _exchange.registerQueue(new Binding(null,"a.*", queue,_exchange, null)); + + + IncomingMessage message = createMessage("a.b"); + + routeMessage(message); + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message recevied", (Object) message.getMessageNumber(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageNumber()); + + queue.deleteMessageFromTop(); + Assert.assertEquals(0, queue.getMessageCount()); + + + message = createMessage("a.c"); + + int queueCount = routeMessage(message); + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message recevied", (Object) message.getMessageNumber(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageNumber()); + + queue.deleteMessageFromTop(); + Assert.assertEquals(0, queue.getMessageCount()); + + + message = createMessage("a"); + + + queueCount = routeMessage(message); + Assert.assertEquals("Message should not route to any queues", 0, queueCount); + + Assert.assertEquals(0, queue.getMessageCount()); + } + + public void testHashMatch() throws AMQException + { + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("a#"), false, null, false, false, _vhost, null); + _exchange.registerQueue(new Binding(null,"a.#", queue,_exchange, null)); + + + IncomingMessage message = createMessage("a.b.c"); + + int queueCount = routeMessage(message); + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message recevied", (Object) message.getMessageNumber(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageNumber()); + + queue.deleteMessageFromTop(); + Assert.assertEquals(0, queue.getMessageCount()); + + + message = createMessage("a.b"); + + queueCount = routeMessage(message); + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message recevied", (Object) message.getMessageNumber(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageNumber()); + + queue.deleteMessageFromTop(); + Assert.assertEquals(0, queue.getMessageCount()); + + + message = createMessage("a.c"); + + queueCount = routeMessage(message); + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message recevied", (Object) message.getMessageNumber(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageNumber()); + + queue.deleteMessageFromTop(); + Assert.assertEquals(0, queue.getMessageCount()); + + message = createMessage("a"); + + queueCount = routeMessage(message); + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message recevied", (Object) message.getMessageNumber(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageNumber()); + + queue.deleteMessageFromTop(); + Assert.assertEquals(0, queue.getMessageCount()); + + + message = createMessage("b"); + + + queueCount = routeMessage(message); + Assert.assertEquals("Message should not route to any queues", 0, queueCount); + + Assert.assertEquals(0, queue.getMessageCount()); + } + + + public void testMidHash() throws AMQException + { + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("a"), false, null, false, false, _vhost, null); + _exchange.registerQueue(new Binding(null,"a.*.#.b", queue,_exchange, null)); + + + IncomingMessage message = createMessage("a.c.d.b"); + + routeMessage(message); + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message recevied", (Object) message.getMessageNumber(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageNumber()); + + queue.deleteMessageFromTop(); + Assert.assertEquals(0, queue.getMessageCount()); + + message = createMessage("a.c.b"); + + routeMessage(message); + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message recevied", (Object) message.getMessageNumber(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageNumber()); + + queue.deleteMessageFromTop(); + Assert.assertEquals(0, queue.getMessageCount()); + + } + + public void testMatchafterHash() throws AMQException + { + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("a#"), false, null, false, false, _vhost, null); + _exchange.registerQueue(new Binding(null,"a.*.#.b.c", queue,_exchange, null)); + + + IncomingMessage message = createMessage("a.c.b.b"); + + int queueCount = routeMessage(message); + Assert.assertEquals("Message should not route to any queues", 0, queueCount); + + Assert.assertEquals(0, queue.getMessageCount()); + + + message = createMessage("a.a.b.c"); + + routeMessage(message); + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message recevied", (Object) message.getMessageNumber(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageNumber()); + + queue.deleteMessageFromTop(); + Assert.assertEquals(0, queue.getMessageCount()); + + message = createMessage("a.b.c.b"); + + queueCount = routeMessage(message); + Assert.assertEquals("Message should not route to any queues", 0, queueCount); + + Assert.assertEquals(0, queue.getMessageCount()); + + message = createMessage("a.b.c.b.c"); + + routeMessage(message); + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message recevied", (Object) message.getMessageNumber(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageNumber()); + + queue.deleteMessageFromTop(); + Assert.assertEquals(0, queue.getMessageCount()); + + } + + + public void testHashAfterHash() throws AMQException + { + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("a#"), false, null, false, false, _vhost, null); + _exchange.registerQueue(new Binding(null,"a.*.#.b.c.#.d", queue,_exchange, null)); + + + IncomingMessage message = createMessage("a.c.b.b.c"); + + int queueCount = routeMessage(message); + Assert.assertEquals("Message should not route to any queues", 0, queueCount); + + Assert.assertEquals(0, queue.getMessageCount()); + + + message = createMessage("a.a.b.c.d"); + + routeMessage(message); + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message recevied", (Object) message.getMessageNumber(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageNumber()); + + queue.deleteMessageFromTop(); + Assert.assertEquals(0, queue.getMessageCount()); + + } + + public void testHashHash() throws AMQException + { + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("a#"), false, null, false, false, _vhost, null); + _exchange.registerQueue(new Binding(null,"a.#.*.#.d", queue,_exchange, null)); + + + IncomingMessage message = createMessage("a.c.b.b.c"); + + int queueCount = routeMessage(message); + Assert.assertEquals("Message should not route to any queues", 0, queueCount); + + Assert.assertEquals(0, queue.getMessageCount()); + + message = createMessage("a.a.b.c.d"); + + routeMessage(message); + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message recevied", (Object) message.getMessageNumber(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageNumber()); + + queue.deleteMessageFromTop(); + Assert.assertEquals(0, queue.getMessageCount()); + + } + + public void testSubMatchFails() throws AMQException + { + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("a"), false, null, false, false, _vhost, null); + _exchange.registerQueue(new Binding(null,"a.b.c.d", queue,_exchange, null)); + + + IncomingMessage message = createMessage("a.b.c"); + + int queueCount = routeMessage(message); + Assert.assertEquals("Message should not route to any queues", 0, queueCount); + + Assert.assertEquals(0, queue.getMessageCount()); + + } + + private int routeMessage(final IncomingMessage message) + throws AMQException + { + MessageMetaData mmd = message.headersReceived(); + message.setStoredMessage(_store.addMessage(mmd)); + + message.enqueue(_exchange.route(message)); + AMQMessage msg = new AMQMessage(message.getStoredMessage()); + for(BaseQueue q : message.getDestinationQueues()) + { + q.enqueue(msg); + } + return message.getDestinationQueues().size(); + } + + public void testMoreRouting() throws AMQException + { + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("a"), false, null, false, false, _vhost, null); + _exchange.registerQueue(new Binding(null,"a.b", queue,_exchange, null)); + + + IncomingMessage message = createMessage("a.b.c"); + + int queueCount = routeMessage(message); + Assert.assertEquals("Message should not route to any queues", 0, queueCount); + + Assert.assertEquals(0, queue.getMessageCount()); + + } + + public void testMoreQueue() throws AMQException + { + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("a"), false, null, false, false, _vhost, null); + _exchange.registerQueue(new Binding(null,"a.b", queue,_exchange, null)); + + + IncomingMessage message = createMessage("a"); + + int queueCount = routeMessage(message); + Assert.assertEquals("Message should not route to any queues", 0, queueCount); + + Assert.assertEquals(0, queue.getMessageCount()); + + } + + private IncomingMessage createMessage(String s) throws AMQException + { + MessagePublishInfo info = new PublishInfo(new AMQShortString(s)); + + IncomingMessage message = new IncomingMessage(info); + final ContentHeaderBody chb = new ContentHeaderBody(); + BasicContentHeaderProperties props = new BasicContentHeaderProperties(); + chb.setProperties(props); + message.setContentHeaderBody(chb); + + + return message; + } + + + class PublishInfo implements MessagePublishInfo + { + AMQShortString _routingkey; + + PublishInfo(AMQShortString routingkey) + { + _routingkey = routingkey; + } + + public AMQShortString getExchange() + { + return null; + } + + public void setExchange(AMQShortString exchange) + { + + } + + public boolean isImmediate() + { + return false; + } + + public boolean isMandatory() + { + return true; + } + + public AMQShortString getRoutingKey() + { + return _routingkey; + } + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/Log4jMessageLoggerTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/Log4jMessageLoggerTest.java new file mode 100644 index 0000000000..a845bff9ce --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/Log4jMessageLoggerTest.java @@ -0,0 +1,271 @@ +/* + * 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.qpid.server.logging; + +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; + +import junit.framework.TestCase; + +import org.apache.log4j.AppenderSkeleton; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.spi.LoggingEvent; +import org.apache.qpid.server.logging.actors.BrokerActor; + +/** Test that the Log4jMessageLogger defaults behave as expected */ +public class Log4jMessageLoggerTest extends TestCase +{ + Level _rootLevel; + Log4jTestAppender _appender; + + @Override + public void setUp() throws IOException + { + // Setup a file for logging + _appender = new Log4jTestAppender(); + + Logger root = Logger.getRootLogger(); + root.addAppender(_appender); + + _rootLevel = Logger.getRootLogger().getLevel(); + if (_rootLevel != Level.INFO) + { + root.setLevel(Level.INFO); + root.warn("Root Logger set to:" + _rootLevel + " Resetting to INFO for test."); + } + root.warn("Adding Test Appender:" + _appender); + } + + @Override + public void tearDown() + { + Logger root = Logger.getRootLogger(); + root.warn("Removing Test Appender:" + _appender); + root.warn("Resetting Root Level to : " + _rootLevel); + + Logger.getRootLogger().setLevel(_rootLevel); + + Logger.getRootLogger().removeAppender(_appender); + + //Call close on our appender. This will clear the log messages + // from Memory + _appender.close(); + } + + /** + * Verify that the Log4jMessageLogger successfully logs a message. + */ + public void testLoggedMessage() + { + Log4jMessageLogger msgLogger = new Log4jMessageLogger(); + assertTrue("Expected message logger to be enabled", msgLogger.isEnabled()); + + testLoggedMessage(msgLogger, true, getName()); + } + + /** + * Verify that for the given Log4jMessageLogger, after generating a message for the given + * log hierarchy that the outcome is as expected. + */ + private String testLoggedMessage(Log4jMessageLogger logger, boolean logExpected, String hierarchy) + { + //Create Message for test + String message = "testDefaults"; + + // Log the message + logger.rawMessage(message, hierarchy); + + if(logExpected) + { + verifyLogPresent(message); + } + else + { + verifyNoLog(message); + } + + return message; + } + + /** + * Test that specifying different log hierarchies to be used works as expected. + * <p/> + * Test this by using one hierarchy and verifying it succeeds, then disabling it and + * confirming this takes effect, and finally that using another hierarchy still succeeds. + */ + public void testMultipleHierarchyUsage() + { + String loggerName1 = getName() + ".TestLogger1"; + String loggerName2 = getName() + ".TestLogger2"; + + // Create a message logger to test + Log4jMessageLogger msgLogger = new Log4jMessageLogger(); + assertTrue("Expected message logger to be enabled", msgLogger.isEnabled()); + + //verify that using this hierarchy the message gets logged ok + String message = testLoggedMessage(msgLogger, true, loggerName1); + + //now disable that hierarchy in log4j + Logger.getLogger(loggerName1).setLevel(Level.OFF); + + //clear the previous message from the test appender + _appender.close(); + verifyNoLog(message); + + //verify that the hierarchy disabling took effect + testLoggedMessage(msgLogger, false, loggerName1); + + //now ensure that using a new hierarchy results in the message being output + testLoggedMessage(msgLogger, true, loggerName2); + } + + /** + * Test that log4j can be used to manipulate on a per-hierarchy(and thus message) basis + * whether a particular status message is enabled. + * <p/> + * Test this by using two hierarchies, setting one off and one on (info) via log4j directly, + * then confirming this gives the expected isMessageEnabled() result. Then reverse the log4j + * Levels for the Logger's and ensure the results change as expected. + */ + public void testEnablingAndDisablingMessages() + { + String loggerName1 = getName() + ".TestLogger1"; + String loggerName2 = getName() + ".TestLogger2"; + + Logger.getLogger(loggerName1).setLevel(Level.INFO); + Logger.getLogger(loggerName2).setLevel(Level.OFF); + + Log4jMessageLogger msgLogger = new Log4jMessageLogger(); + BrokerActor actor = new BrokerActor(msgLogger); + + assertTrue("Expected message logger to be enabled", msgLogger.isEnabled()); + + assertTrue("Message should be enabled", msgLogger.isMessageEnabled(actor, loggerName1)); + assertFalse("Message should be disabled", msgLogger.isMessageEnabled(actor, loggerName2)); + + Logger.getLogger(loggerName1).setLevel(Level.WARN); + Logger.getLogger(loggerName2).setLevel(Level.INFO); + + assertFalse("Message should be disabled", msgLogger.isMessageEnabled(actor, loggerName1)); + assertTrue("Message should be enabled", msgLogger.isMessageEnabled(actor, loggerName2)); + } + + /** + * Check that the Log Message reached log4j + * @param message the message to search for + */ + private void verifyLogPresent(String message) + { + List<String> results = findMessageInLog(message); + + //Validate we only got one message + assertEquals("The result set was not as expected.", 1, results.size()); + + // Validate message + String line = results.get(0); + + assertNotNull("No Message retrieved from log file", line); + assertTrue("Message not contained in log.:" + line, + line.contains(message)); + } + + /** + * Check that the given Message is not present in the log4j records. + * @param message the message to search for + */ + private void verifyNoLog(String message) + { + List<String> results = findMessageInLog(message); + + if (results.size() > 0) + { + System.err.println("Unexpected Log messages"); + + for (String msg : results) + { + System.err.println(msg); + } + } + + assertEquals("No message was expected.", 0, results.size()); + } + + /** + * Get the appenders list of events and return a list of all the messages + * that contain the given message + * + * @param message the search string + * @return The list of all logged messages that contain the search string. + */ + private List<String> findMessageInLog(String message) + { + List<LoggingEvent> log = _appender.getLog(); + + // Search Results for requested message + List<String> result = new LinkedList<String>(); + + for (LoggingEvent event : log) + { + if (String.valueOf(event.getMessage()).contains(message)) + { + result.add(String.valueOf(event.getMessage())); + } + } + + return result; + } + + + /** + * Log4j Appender that simply records all the Logging Events so we can + * verify that the above logging will make it to log4j in a unit test. + */ + private class Log4jTestAppender extends AppenderSkeleton + { + List<LoggingEvent> _log = new LinkedList<LoggingEvent>(); + + protected void append(LoggingEvent loggingEvent) + { + _log.add(loggingEvent); + } + + public void close() + { + _log.clear(); + } + + /** + * @return the list of LoggingEvents that have occured in this Appender + */ + public List<LoggingEvent> getLog() + { + return _log; + } + + public boolean requiresLayout() + { + return false; + } + } +} + diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/LogMessageTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/LogMessageTest.java new file mode 100644 index 0000000000..956bb6f8fa --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/LogMessageTest.java @@ -0,0 +1,169 @@ +/* + * + * 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.qpid.server.logging; + +import junit.framework.TestCase; +import org.apache.qpid.server.logging.messages.BrokerMessages; + +import java.util.Locale; +import java.util.ResourceBundle; + +public class LogMessageTest extends TestCase +{ + + /** + * Test that the US local has a loadable bundle. + * No longer have a specific en_US bundle so cannot verify that that version + * is loaded. Can only verify that we get a ResourceBundle loaded. + */ + public void testBundle() + { + Locale usLocal = Locale.US; + Locale.setDefault(usLocal); + ResourceBundle _messages = ResourceBundle.getBundle("org.apache.qpid.server.logging.messages.Broker_logmessages", + usLocal); + + assertNotNull("Unable to load ResourceBundle", _messages); + } + + /** + * Test that loading an undefined locale will result in loading of the + * default US locale. + */ + public void testUndefinedLocale() + { + Locale japanese = Locale.JAPANESE; + + Locale.setDefault(japanese); + try + { + ResourceBundle _messages = ResourceBundle.getBundle("org.apache.qpid.server.logging.messages.Broker_logmessages", + japanese); + + assertNotNull("Unable to load ResourceBundle", _messages); + + // If we attempt to load an undefined locale it should default to the Root locale. + assertEquals("Loaded bundle has incorrect locale.", Locale.ROOT, _messages.getLocale()); + } + catch (Throwable t) + { + fail(t.getMessage()); + } + } + + /** + * test Simultaneous log message generation. + * QPID-2137 highlighted that log message generation was not thread-safe. + * Test to ensure that simultaneous logging is possible and does not throw an exception. + * @throws InterruptedException if there is a problem joining logging threads. + */ + public void testSimultaneousLogging() throws InterruptedException + { + int LOGGERS = 10; + int LOG_COUNT = 10; + LogGenerator[] logGenerators = new LogGenerator[LOGGERS]; + Thread[] threads = new Thread[LOGGERS]; + + //Create Loggers + for (int i = 0; i < LOGGERS; i++) + { + logGenerators[i] = new LogGenerator(LOG_COUNT); + threads[i] = new Thread(logGenerators[i]); + } + + //Run Loggers + for (int i = 0; i < LOGGERS; i++) + { + threads[i].start(); + } + + //End Loggers + for (int i = 0; i < LOGGERS; i++) + { + threads[i].join(); + Exception e = logGenerators[i].getThrowException(); + // If we have an exception something went wrong. + // Check and see if it was QPID-2137 + if (e != null) + { + // Just log out if we find the usual exception causing QPID-2137 + if (e instanceof StringIndexOutOfBoundsException) + { + System.err.println("Detected QPID-2137"); + } + fail("Exception thrown during log generation:" + e); + } + } + } + + /** + * Inner class used by testSimultaneousLogging. + * + * This class creates a given number of LogMessages using the BrokerMessages package. + * CONFIG and LISTENING messages are both created per count. + * + * This class is run multiple times simultaneously so that we increase the chance of + * reproducing QPID-2137. This is reproduced when the pattern string used in the MessageFormat + * class is changed whilst formatting is taking place. + * + */ + class LogGenerator implements Runnable + { + private Exception _exception = null; + private int _count; + + /** + * @param count The number of Log Messages to generate + */ + LogGenerator(int count) + { + _count = count; + } + + public void run() + { + try + { + // try and generate _count iterations of Config & Listening messages. + for (int i = 0; i < _count; i++) + { + BrokerMessages.CONFIG("Config"); + BrokerMessages.LISTENING("TCP", 1234); + } + } + catch (Exception e) + { + // if something goes wrong recorded it for later analysis. + _exception = e; + } + } + + /** + * Return any exception that was thrown during the log generation. + * @return Exception + */ + public Exception getThrowException() + { + return _exception; + } + } + +}
\ No newline at end of file diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/UnitTestMessageLogger.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/UnitTestMessageLogger.java new file mode 100644 index 0000000000..3752dcb37e --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/UnitTestMessageLogger.java @@ -0,0 +1,72 @@ +/* + * 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.qpid.server.logging; + +import java.util.LinkedList; +import java.util.List; + +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.logging.AbstractRootMessageLogger; + +public class UnitTestMessageLogger extends AbstractRootMessageLogger +{ + List<Object> _log; + + { + _log = new LinkedList<Object>(); + } + + public UnitTestMessageLogger() + { + + } + + public UnitTestMessageLogger(ServerConfiguration config) + { + super(config); + } + + public void rawMessage(String message, String logHierarchy) + { + _log.add(message); + } + + public void rawMessage(String message, Throwable throwable, String logHierarchy) + { + _log.add(message); + + if(throwable != null) + { + _log.add(throwable); + } + } + + + public List<Object> getLogMessages() + { + return _log; + } + + public void clearLogMessages() + { + _log.clear(); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/UnitTestMessageLoggerTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/UnitTestMessageLoggerTest.java new file mode 100644 index 0000000000..e2e112be8f --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/UnitTestMessageLoggerTest.java @@ -0,0 +1,103 @@ +/* + * + * 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.qpid.server.logging; + +import junit.framework.TestCase; + +import java.util.List; + +/** + * Test: UnitTestMessageLoggerTest + * + * This test verifies that UnitTestMessageLogger adheres to its interface. + * + * Messages are logged, and Throwables recorded in an array that can be + * retrieved and cleared. + * + */ +public class UnitTestMessageLoggerTest extends TestCase +{ + private static final String TEST_MESSAGE = "Test"; + private static final String TEST_THROWABLE = "Test Throwable"; + private static final String TEST_HIERARCHY = "test.hierarchy"; + + public void testRawMessage() + { + UnitTestMessageLogger logger = new UnitTestMessageLogger(); + + assertEquals("Messages logged before test start", 0, + logger.getLogMessages().size()); + + // Log a message + logger.rawMessage(TEST_MESSAGE, TEST_HIERARCHY); + + List<Object> messages = logger.getLogMessages(); + + assertEquals("Expected to have 1 messages logged", 1, messages.size()); + + assertEquals("First message not what was logged", + TEST_MESSAGE, messages.get(0)); + } + + public void testRawMessageWithThrowable() + { + UnitTestMessageLogger logger = new UnitTestMessageLogger(); + + assertEquals("Messages logged before test start", 0, + logger.getLogMessages().size()); + + // Log a message + Throwable throwable = new Throwable(TEST_THROWABLE); + + logger.rawMessage(TEST_MESSAGE, throwable, TEST_HIERARCHY); + + List<Object> messages = logger.getLogMessages(); + + assertEquals("Expected to have 2 entries", 2, messages.size()); + + assertEquals("Message text not what was logged", + TEST_MESSAGE, messages.get(0)); + + assertEquals("Message throwable not what was logged", + TEST_THROWABLE, ((Throwable) messages.get(1)).getMessage()); + + } + + public void testClear() + { + UnitTestMessageLogger logger = new UnitTestMessageLogger(); + + assertEquals("Messages logged before test start", 0, + logger.getLogMessages().size()); + + // Log a message + logger.rawMessage(TEST_MESSAGE, null, TEST_HIERARCHY); + + assertEquals("Expected to have 1 messages logged", + 1, logger.getLogMessages().size()); + + logger.clearLogMessages(); + + assertEquals("Expected to have no messagse after a clear", + 0, logger.getLogMessages().size()); + + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/AMQPChannelActorTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/AMQPChannelActorTest.java new file mode 100644 index 0000000000..6346fff85f --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/AMQPChannelActorTest.java @@ -0,0 +1,221 @@ +/* + * + * 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.qpid.server.logging.actors; + +import java.util.List; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.AMQException; + +/** + * Test : AMQPChannelActorTest + * Validate the AMQPChannelActor class. + * + * The test creates a new AMQPActor and then logs a message using it. + * + * The test then verifies that the logged message was the only one created and + * that the message contains the required message. + */ +public class AMQPChannelActorTest extends BaseConnectionActorTestCase +{ + + @Override + public void configure() + { + // Prevent defaulting Logging to ON + } + + + @Override + public void createBroker() throws Exception + { + //prevent auto-broker startup + } + + private void startBrokerNow() throws Exception + { + super.createBroker(); + + _amqpActor = new AMQPChannelActor(getChannel(), _rootLogger); + } + + + /** + * Test that when logging on behalf of the channel + * The test sends a message then verifies that it entered the logs. + * + * The log message should be fully repalaced (no '{n}' values) and should + * contain the channel id ('/ch:1') identification. + */ + public void testChannel() throws Exception + { + getConfigXml().setProperty("status-updates", "ON"); + + startBrokerNow(); + + final String message = sendTestLogMessage(_amqpActor); + + List<Object> logs = _rawLogger.getLogMessages(); + + assertEquals("Message log size not as expected.", 1, logs.size()); + + // Verify that the logged message is present in the output + assertTrue("Message was not found in log message:" + logs.get(0), + logs.get(0).toString().contains(message)); + + // Verify that the message has the correct type + assertTrue("Message contains the [con: prefix", + logs.get(0).toString().contains("[con:")); + + + // Verify that all the values were presented to the MessageFormatter + // so we will not end up with '{n}' entries in the log. + assertFalse("Verify that the string does not contain any '{'." + logs.get(0), + logs.get(0).toString().contains("{")); + + // Verify that the logged message contains the 'ch:1' marker + assertTrue("Message was not logged as part of channel 1" + logs.get(0), + logs.get(0).toString().contains("/ch:1")); + + } + + /** + * Test that if logging is configured to be off in the configuration that + * no logging is presented + * @throws ConfigurationException + * @throws AMQException + */ + public void testChannelLoggingOFF() throws Exception, AMQException + { + getConfigXml().setProperty("status-updates", "OFF"); + + // Start the broker now. + startBrokerNow(); + + sendTestLogMessage(_amqpActor); + + List<Object> logs = _rawLogger.getLogMessages(); + + assertEquals("Message log size not as expected.", 0, logs.size()); + + } + + /** + * Test that if logging is configured to be off in the configuration that + * no logging is presented + * @throws ConfigurationException + * @throws AMQException + */ + public void testChannelLoggingOfF() throws Exception, AMQException + { + getConfigXml().setProperty("status-updates", "OfF"); + + startBrokerNow(); + + sendTestLogMessage(_amqpActor); + + List<Object> logs = _rawLogger.getLogMessages(); + + assertEquals("Message log size not as expected.", 0, logs.size()); + + } + + /** + * Test that if logging is configured to be off in the configuration that + * no logging is presented + * @throws ConfigurationException + * @throws AMQException + */ + public void testChannelLoggingOff() throws Exception, AMQException + { + getConfigXml().setProperty("status-updates", "Off"); + + startBrokerNow(); + + sendTestLogMessage(_amqpActor); + + List<Object> logs = _rawLogger.getLogMessages(); + + assertEquals("Message log size not as expected.", 0, logs.size()); + + } + + /** + * Test that if logging is configured to be off in the configuration that + * no logging is presented + * @throws ConfigurationException + * @throws AMQException + */ + public void testChannelLoggingofF() throws Exception, AMQException + { + getConfigXml().setProperty("status-updates", "ofF"); + + startBrokerNow(); + + sendTestLogMessage(_amqpActor); + + List<Object> logs = _rawLogger.getLogMessages(); + + assertEquals("Message log size not as expected.", 0, logs.size()); + + } + + /** + * Test that if logging is configured to be off in the configuration that + * no logging is presented + * @throws ConfigurationException + * @throws AMQException + */ + public void testChannelLoggingoff() throws Exception, AMQException + { + getConfigXml().setProperty("status-updates", "off"); + + startBrokerNow(); + + sendTestLogMessage(_amqpActor); + + List<Object> logs = _rawLogger.getLogMessages(); + + assertEquals("Message log size not as expected.", 0, logs.size()); + + } + + /** + * Test that if logging is configured to be off in the configuration that + * no logging is presented + * @throws ConfigurationException + * @throws AMQException + */ + public void testChannelLoggingoFf() throws Exception, AMQException + { + getConfigXml().setProperty("status-updates", "oFf"); + + startBrokerNow(); + + sendTestLogMessage(_amqpActor); + + List<Object> logs = _rawLogger.getLogMessages(); + + assertEquals("Message log size not as expected.", 0, logs.size()); + + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/AMQPConnectionActorTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/AMQPConnectionActorTest.java new file mode 100644 index 0000000000..4eda9e9da1 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/AMQPConnectionActorTest.java @@ -0,0 +1,132 @@ +/* + * + * 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.qpid.server.logging.actors; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.logging.LogMessage; +import org.apache.qpid.server.logging.LogSubject; + +import java.util.List; + +/** + * Test : AMQPConnectionActorTest + * Validate the AMQPConnectionActor class. + * + * The test creates a new AMQPActor and then logs a message using it. + * + * The test then verifies that the logged message was the only one created and + * that the message contains the required message. + */ +public class AMQPConnectionActorTest extends BaseConnectionActorTestCase +{ + @Override + public void configure() + { + // Prevent defaulting Logging to ON + } + + + @Override + public void createBroker() + { + //Prevent auto-broker startup + } + + /** + * Test the AMQPActor logging as a Connection level. + * + * The test sends a message then verifies that it entered the logs. + * + * The log message should be fully repalaced (no '{n}' values) and should + * not contain any channel identification. + */ + public void testConnection() throws Exception + { + getConfigXml().setProperty("status-updates", "ON"); + + super.createBroker(); + + final String message = sendLogMessage(); + + List<Object> logs = _rawLogger.getLogMessages(); + + assertEquals("Message log size not as expected.", 1, logs.size()); + + // Verify that the logged message is present in the output + assertTrue("Message was not found in log message", + logs.get(0).toString().contains(message)); + + // Verify that the message has the correct type + assertTrue("Message does not contain the [con: prefix", + logs.get(0).toString().contains("[con:")); + + // Verify that all the values were presented to the MessageFormatter + // so we will not end up with '{n}' entries in the log. + assertFalse("Verify that the string does not contain any '{'.", + logs.get(0).toString().contains("{")); + + // Verify that the logged message does not contains the 'ch:' marker + assertFalse("Message was logged with a channel identifier." + logs.get(0), + logs.get(0).toString().contains("/ch:")); + } + + public void testConnectionLoggingOff() throws Exception, AMQException + { + getConfigXml().setProperty("status-updates", "OFF"); + + // Start the broker now. + super.createBroker(); + + sendLogMessage(); + + List<Object> logs = _rawLogger.getLogMessages(); + + assertEquals("Message log size not as expected.", 0, logs.size()); + + } + + private String sendLogMessage() + { + final String message = "test logging"; + + _amqpActor.message(new LogSubject() + { + public String toLogString() + { + return "[AMQPActorTest]"; + } + + }, new LogMessage() + { + public String toString() + { + return message; + } + + public String getLogHierarchy() + { + return "test.hieracrchy"; + } + }); + return message; + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/BaseActorTestCase.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/BaseActorTestCase.java new file mode 100644 index 0000000000..60ecbef438 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/BaseActorTestCase.java @@ -0,0 +1,91 @@ +/* + * + * 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.qpid.server.logging.actors; + +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.logging.LogMessage; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.UnitTestMessageLogger; + +import org.apache.qpid.server.util.InternalBrokerBaseCase; + +public class BaseActorTestCase extends InternalBrokerBaseCase +{ + protected LogActor _amqpActor; + protected UnitTestMessageLogger _rawLogger; + protected RootMessageLogger _rootLogger; + + @Override + public void configure() + { + getConfiguration().getConfig().setProperty(ServerConfiguration.STATUS_UPDATES, "on"); + } + + @Override + public void createBroker() throws Exception + { + super.createBroker(); + + _rawLogger = new UnitTestMessageLogger(getConfiguration()); + _rootLogger = _rawLogger; + } + + public void tearDown() throws Exception + { + _rawLogger.clearLogMessages(); + + super.tearDown(); + } + + public String sendTestLogMessage(LogActor actor) + { + String message = "Test logging: " + getName(); + sendTestLogMessage(actor, message); + + return message; + } + + public void sendTestLogMessage(LogActor actor, final String message) + { + actor.message(new LogSubject() + { + public String toLogString() + { + return message; + } + + }, new LogMessage() + { + public String toString() + { + return message; + } + + public String getLogHierarchy() + { + return "test.hierarchy"; + } + }); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/BaseConnectionActorTestCase.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/BaseConnectionActorTestCase.java new file mode 100644 index 0000000000..956d296dce --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/BaseConnectionActorTestCase.java @@ -0,0 +1,33 @@ +/* + * + * 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.qpid.server.logging.actors; + +public class BaseConnectionActorTestCase extends BaseActorTestCase +{ + + @Override + public void createBroker() throws Exception + { + super.createBroker(); + + _amqpActor = new AMQPConnectionActor(getSession(), _rootLogger); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/CurrentActorTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/CurrentActorTest.java new file mode 100644 index 0000000000..32ad1d110d --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/CurrentActorTest.java @@ -0,0 +1,256 @@ +/* + * + * 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.qpid.server.logging.actors; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.logging.NullRootMessageLogger; + +/** + * Test : CurrentActorTest + * Summary: + * Validate ThreadLocal operation. + * + * Test creates THREADS number of threads which all then execute the same test + * together ( as close as looping Thread.start() will allow). + * + * Test: + * Test sets the CurrentActor then proceeds to retrieve the value and use it. + * + * The test also validates that it is the same LogActor that this thread set. + * + * Finally the LogActor is removed and tested to make sure that it was + * successfully removed. + * + * By having a higher number of threads than would normally be used in the + * Poolling filter we aim to catch the race condition where a ThreadLocal remove + * is called before one or more threads call get(). This way we can ensure that + * the remove does not affect more than the Thread it was called in. + */ +public class CurrentActorTest extends BaseConnectionActorTestCase +{ + //Set this to be a reasonably large number + int THREADS = 10; + + // Record any exceptions that are thrown by the threads + Exception[] _errors = new Exception[THREADS]; + + /** + * Test that CurrentActor behaves as LIFO queue. + * + * Test creates two Actors Connection and Channel and then sets the + * CurrentActor. + * + * The test validates that CurrentActor remembers the Connection actor + * after the Channel actor has been removed. + * + * And then finally validates that removing the Connection actor results + * in there being no actors set. + * + * @throws AMQException + * @throws org.apache.commons.configuration.ConfigurationException + */ + public void testLIFO() throws AMQException, ConfigurationException + { + // This test only needs the local objects created, _session etc. + // So stopping the broker and making them useless will not affect the + // test, but the extra actors the test broker adds will so by stopping + // we remove the session actor and so all is good. + stopBroker(); + + AMQPConnectionActor connectionActor = new AMQPConnectionActor(getSession(), + new NullRootMessageLogger()); + + /* + * Push the actor on to the stack: + * + * CurrentActor -> Connection + * Stack -> null + */ + CurrentActor.set(connectionActor); + + //Use the Actor to send a simple message + sendTestLogMessage(CurrentActor.get()); + + // Verify it was the same actor as we set earlier + assertEquals("Retrieved actor is not as expected ", + connectionActor, CurrentActor.get()); + + /** + * Set the actor to now be the Channel actor so testing the ability + * to push the actor on to the stack: + * + * CurrentActor -> Channel + * Stack -> Connection, null + * + */ + + AMQChannel channel = new AMQChannel(getSession(), 1, getSession().getVirtualHost().getMessageStore()); + + AMQPChannelActor channelActor = new AMQPChannelActor(channel, + new NullRootMessageLogger()); + + CurrentActor.set(channelActor); + + //Use the Actor to send a simple message + sendTestLogMessage(CurrentActor.get()); + + // Verify it was the same actor as we set earlier + assertEquals("Retrieved actor is not as expected ", + channelActor, CurrentActor.get()); + + // Remove the ChannelActor from the stack + CurrentActor.remove(); + /* + * Pop the actor on to the stack: + * + * CurrentActor -> Connection + * Stack -> null + */ + + + // Verify we now have the same connection actor as we set earlier + assertEquals("Retrieved actor is not as expected ", + connectionActor, CurrentActor.get()); + + // Verify that removing the our last actor it returns us to the test + // default that the ApplicationRegistry sets. + CurrentActor.remove(); + /* + * Pop the actor on to the stack: + * + * CurrentActor -> null + */ + + + assertEquals("CurrentActor not the Test default", TestLogActor.class ,CurrentActor.get().getClass()); + } + + /** + * Test the setting CurrentActor is done correctly as a ThreadLocal. + * + * The test starts 'THREADS' threads that all set the CurrentActor log + * a message then remove the actor. + * + * Checks are done to ensure that there is no set actor after the remove. + * + * If the ThreadLocal was not working then having concurrent actor sets + * would result in more than one actor and so the remove will not result + * in the clearing of the CurrentActor + * + */ + public void testThreadLocal() + { + + new Runnable(){ + public void run() + { + System.out.println(_errors[0]); + } + }; + + // Setup the threads + Thread[] threads = new Thread[THREADS]; + for (int count = 0; count < THREADS; count++) + { + Runnable test = new LogMessagesWithAConnectionActor(count); + threads[count] = new Thread(test); + } + + //Run the threads + for (int count = 0; count < THREADS; count++) + { + threads[count].start(); + } + + // Wait for them to finish + for (int count = 0; count < THREADS; count++) + { + try + { + threads[count].join(); + } + catch (InterruptedException e) + { + //if we are interrupted then we will exit shortly. + } + } + + // Verify that none of the tests threw an exception + for (int count = 0; count < THREADS; count++) + { + if (_errors[count] != null) + { + _errors[count].printStackTrace(); + fail("Error occured in thread:" + count); + } + } + } + + /** + * Creates a new ConnectionActor and logs the given number of messages + * before removing the actor and validating that there is no set actor. + */ + public class LogMessagesWithAConnectionActor implements Runnable + { + int count; + + LogMessagesWithAConnectionActor(int count) + { + this.count = count; + } + + public void run() + { + + // Create a new actor using retrieving the rootMessageLogger from + // the default ApplicationRegistry. + //fixme reminder that we need a better approach for broker testing. + try + { + + AMQPConnectionActor actor = new AMQPConnectionActor(getSession(), + new NullRootMessageLogger()); + + CurrentActor.set(actor); + + //Use the Actor to send a simple message + sendTestLogMessage(CurrentActor.get()); + + // Verify it was the same actor as we set earlier + assertEquals("Retrieved actor is not as expected ", + actor, CurrentActor.get()); + + // Verify that removing the actor works for this thread + CurrentActor.remove(); + + assertNull("CurrentActor should be null", CurrentActor.get()); + } + catch (Exception e) + { + _errors[count] = e; + } + + } + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/ManagementActorTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/ManagementActorTest.java new file mode 100644 index 0000000000..033ae3b4b3 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/ManagementActorTest.java @@ -0,0 +1,103 @@ +/* + * + * 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.qpid.server.logging.actors; + +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.logging.LogMessage; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.AMQException; + +import java.util.List; + +/** + * Test : AMQPManagementActorTest + * Validate the AMQPManagementActor class. + * + * The test creates a new AMQPActor and then logs a message using it. + * + * The test then verifies that the logged message was the only one created and + * that the message contains the required message. + */ +public class ManagementActorTest extends BaseActorTestCase +{ + + private static final String IP = "127.0.0.1"; + private static final String CONNECTION_ID = "1"; + private String _threadName; + + @Override + public void createBroker() throws Exception + { + super.createBroker(); + _amqpActor = new ManagementActor(_rootLogger); + + // Set the thread name to be the same as a RMI JMX Connection would use + _threadName = Thread.currentThread().getName(); + Thread.currentThread().setName("RMI TCP Connection(" + CONNECTION_ID + ")-" + IP); + } + + @Override + public void tearDown() throws Exception + { + Thread.currentThread().setName(_threadName); + super.tearDown(); + } + + /** + * Test the AMQPActor logging as a Connection level. + * + * The test sends a message then verifies that it entered the logs. + * + * The log message should be fully repalaced (no '{n}' values) and should + * not contain any channel identification. + */ + public void testConnection() + { + final String message = sendTestLogMessage(_amqpActor); + + List<Object> logs = _rawLogger.getLogMessages(); + + assertEquals("Message log size not as expected.", 1, logs.size()); + + // Verify that the logged message is present in the output + assertTrue("Message was not found in log message", + logs.get(0).toString().contains(message)); + + // Verify that all the values were presented to the MessageFormatter + // so we will not end up with '{n}' entries in the log. + assertFalse("Verify that the string does not contain any '{'.", + logs.get(0).toString().contains("{")); + + // Verify that the message has the correct type + assertTrue("Message does not contain the [mng: prefix", + logs.get(0).toString().contains("[mng:")); + + // Verify that the logged message does not contains the 'ch:' marker + assertFalse("Message was logged with a channel identifier." + logs.get(0), + logs.get(0).toString().contains("/ch:")); + + // Verify that the message has the right values + assertTrue("Message contains the [mng: prefix", + logs.get(0).toString().contains("[mng:" + CONNECTION_ID + "(" + IP + ")")); + + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/QueueActorTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/QueueActorTest.java new file mode 100644 index 0000000000..409f7c84b7 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/QueueActorTest.java @@ -0,0 +1,73 @@ +/* + * + * 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.qpid.server.logging.actors; + +import java.util.List; + +public class QueueActorTest extends BaseConnectionActorTestCase +{ + + @Override + public void createBroker() throws Exception + { + super.createBroker(); + _amqpActor = new QueueActor(getQueue(), _rootLogger); + } + + /** + * Test the QueueActor as a logger. + * + * The test logs a message then verifies that it entered the logs correctly + * + * The log message should be fully repalaced (no '{n}' values) and should + * contain the correct queue identification. + */ + public void testQueueActor() + { + final String message = sendTestLogMessage(_amqpActor); + + List<Object> logs = _rawLogger.getLogMessages(); + + assertEquals("Message log size not as expected.", 1, logs.size()); + + String log = logs.get(0).toString(); + + // Verify that the logged message is present in the output + assertTrue("Message was not found in log message", + log.contains(message)); + + // Verify that all the values were presented to the MessageFormatter + // so we will not end up with '{n}' entries in the log. + assertFalse("Verify that the string does not contain any '{':" + log, + log.contains("{")); + + // Verify that the message has the correct type + assertTrue("Message contains the [vh: prefix:" + log, + log.contains("[vh(")); + + // Verify that the logged message contains the 'qu(' marker + String expected = "qu(" + getName() + ")"; + assertTrue("Message was not logged with a queue identifer '"+expected+"' actual:" + log, + log.contains(expected)); + } + +} + diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/SubscriptionActorTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/SubscriptionActorTest.java new file mode 100644 index 0000000000..a2272cc395 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/SubscriptionActorTest.java @@ -0,0 +1,85 @@ +/* + * + * 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.qpid.server.logging.actors; + +import java.util.List; + +import org.apache.qpid.server.subscription.MockSubscription; + +/** + * Test : AMQPConnectionActorTest + * Validate the AMQPConnectionActor class. + * + * The test creates a new AMQPActor and then logs a message using it. + * + * The test then verifies that the logged message was the only one created and + * that the message contains the required message. + */ +public class SubscriptionActorTest extends BaseConnectionActorTestCase +{ + + @Override + public void createBroker() throws Exception + { + super.createBroker(); + + MockSubscription mockSubscription = new MockSubscription(); + + mockSubscription.setQueue(getQueue(), false); + + _amqpActor = new SubscriptionActor(_rootLogger, mockSubscription); + } + + /** + * Test the AMQPActor logging as a Subscription logger. + * + * The test sends a message then verifies that it entered the logs. + * + * The log message should be fully repalaced (no '{n}' values) and should + * contain subscription identification. + */ + public void testSubscription() + { + final String message = sendTestLogMessage(_amqpActor); + + List<Object> logs = _rawLogger.getLogMessages(); + + assertEquals("Message log size not as expected.", 1, logs.size()); + + // Verify that the logged message is present in the output + assertTrue("Message was not found in log message", + logs.get(0).toString().contains(message)); + + // Verify that all the values were presented to the MessageFormatter + // so we will not end up with '{n}' entries in the log. + assertFalse("Verify that the string does not contain any '{'.", + logs.get(0).toString().contains("{")); + + // Verify that the message has the correct type + assertTrue("Message contains the [sub: prefix", + logs.get(0).toString().contains("[sub:")); + + // Verify that the logged message does not contains the 'ch:' marker + assertFalse("Message was logged with a channel identifier." + logs.get(0), + logs.get(0).toString().contains("/ch:")); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/TestLogActor.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/TestLogActor.java new file mode 100644 index 0000000000..30f4e16e42 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/TestLogActor.java @@ -0,0 +1,37 @@ +/* + * + * 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.qpid.server.logging.actors; + +import org.apache.qpid.server.logging.RootMessageLogger; + +public class TestLogActor extends AbstractActor +{ + public TestLogActor(RootMessageLogger rootLogger) + { + super(rootLogger); + } + + public String getLogMessage() + { + return "[Test Actor] "; + } +} +
\ No newline at end of file diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/management/LoggingManagementMBeanTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/management/LoggingManagementMBeanTest.java new file mode 100644 index 0000000000..2d25a769aa --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/management/LoggingManagementMBeanTest.java @@ -0,0 +1,431 @@ +/* + * 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.qpid.server.logging.management; + +import static org.apache.qpid.management.common.mbeans.LoggingManagement.LOGGER_LEVEL; +import static org.apache.qpid.management.common.mbeans.LoggingManagement.LOGGER_NAME; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import javax.management.JMException; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.TabularDataSupport; + +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.qpid.management.common.mbeans.LoggingManagement; + +import junit.framework.TestCase; +import org.apache.qpid.server.util.InternalBrokerBaseCase; + +public class LoggingManagementMBeanTest extends InternalBrokerBaseCase +{ + private static final String TEST_LOGGER = "LoggingManagementMBeanTestLogger"; + private static final String TEST_LOGGER_CHILD1 = "LoggingManagementMBeanTestLogger.child1"; + private static final String TEST_LOGGER_CHILD2 = "LoggingManagementMBeanTestLogger.child2"; + + private static final String TEST_CATEGORY_PRIORITY = "LogManMBeanTest.category.priority"; + private static final String TEST_CATEGORY_LEVEL = "LogManMBeanTest.category.level"; + private static final String TEST_LOGGER_LEVEL = "LogManMBeanTest.logger.level"; + + private static final String NEWLINE = System.getProperty("line.separator"); + + private File _testConfigFile; + + @Override + public void setUp() throws Exception + { + super.setUp(); + _testConfigFile = createTempTestLog4JConfig(); + } + + @Override + public void tearDown() throws Exception + { + File oldTestConfigFile = new File(_testConfigFile.getAbsolutePath() + ".old"); + if(oldTestConfigFile.exists()) + { + oldTestConfigFile.delete(); + } + + _testConfigFile.delete(); + + super.tearDown(); + } + + private File createTempTestLog4JConfig() + { + File tmpFile = null; + try + { + tmpFile = File.createTempFile("LogManMBeanTestLog4jConfig", ".tmp"); + tmpFile.deleteOnExit(); + + FileWriter fstream = new FileWriter(tmpFile); + BufferedWriter writer = new BufferedWriter(fstream); + + writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"+NEWLINE); + writer.write("<!DOCTYPE log4j:configuration SYSTEM \"log4j.dtd\">"+NEWLINE); + + writer.write("<log4j:configuration xmlns:log4j=\"http://jakarta.apache.org/log4j/\" debug=\"null\" " + + "threshold=\"null\">"+NEWLINE); + + writer.write(" <appender class=\"org.apache.log4j.ConsoleAppender\" name=\"STDOUT\">"+NEWLINE); + writer.write(" <layout class=\"org.apache.log4j.PatternLayout\">"+NEWLINE); + writer.write(" <param name=\"ConversionPattern\" value=\"%d %-5p [%t] %C{2} (%F:%L) - %m%n\"/>"+NEWLINE); + writer.write(" </layout>"+NEWLINE); + writer.write(" </appender>"+NEWLINE); + + //Example of a 'category' with a 'priority' + writer.write(" <category additivity=\"true\" name=\"" + TEST_CATEGORY_PRIORITY +"\">"+NEWLINE); + writer.write(" <priority value=\"info\"/>"+NEWLINE); + writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE); + writer.write(" </category>"+NEWLINE); + + //Example of a 'category' with a 'level' + writer.write(" <category additivity=\"true\" name=\"" + TEST_CATEGORY_LEVEL +"\">"+NEWLINE); + writer.write(" <level value=\"warn\"/>"+NEWLINE); + writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE); + writer.write(" </category>"+NEWLINE); + + //Example of a 'logger' with a 'level' + writer.write(" <logger additivity=\"true\" name=\"" + TEST_LOGGER_LEVEL + "\">"+NEWLINE); + writer.write(" <level value=\"error\"/>"+NEWLINE); + writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE); + writer.write(" </logger>"+NEWLINE); + + //'root' logger + writer.write(" <root>"+NEWLINE); + writer.write(" <priority value=\"info\"/>"+NEWLINE); + writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE); + writer.write(" </root>"+NEWLINE); + + writer.write("</log4j:configuration>"+NEWLINE); + + writer.flush(); + writer.close(); + } + catch (IOException e) + { + fail("Unable to create temporary test log4j configuration"); + } + + return tmpFile; + } + + + + //******* Test Methods ******* // + + public void testSetRuntimeLoggerLevel() + { + LoggingManagementMBean lm = null; + try + { + lm = new LoggingManagementMBean(_testConfigFile.getAbsolutePath(), 0); + } + catch (JMException e) + { + fail("Could not create test LoggingManagementMBean"); + } + + //create a parent test logger, set its level explicitly + Logger log = Logger.getLogger(TEST_LOGGER); + log.setLevel(Level.toLevel("info")); + + //create child1 test logger, check its *effective* level is the same as the parent, "info" + Logger log1 = Logger.getLogger(TEST_LOGGER_CHILD1); + assertTrue("Test logger's level was not the expected value", + log1.getEffectiveLevel().toString().equalsIgnoreCase("info")); + + //now change its level to "warn" + assertTrue("Failed to set logger level", lm.setRuntimeLoggerLevel(TEST_LOGGER_CHILD1, "warn")); + + //check the change, see its actual level is "warn + assertTrue("Test logger's level was not the expected value", + log1.getLevel().toString().equalsIgnoreCase("warn")); + + //try an invalid level + assertFalse("Trying to set an invalid level succeded", lm.setRuntimeLoggerLevel(TEST_LOGGER_CHILD1, "made.up.level")); + } + + public void testSetRuntimeRootLoggerLevel() + { + LoggingManagementMBean lm = null; + try + { + lm = new LoggingManagementMBean(_testConfigFile.getAbsolutePath(), 0); + } + catch (JMException e) + { + fail("Could not create test LoggingManagementMBean"); + } + + Logger log = Logger.getRootLogger(); + + //get current root logger level + Level origLevel = log.getLevel(); + + //change level twice to ensure a new level is actually selected + + //set root loggers level to info + assertTrue("Failed to set root logger level", lm.setRuntimeRootLoggerLevel("debug")); + //check it is now actually info + Level currentLevel = log.getLevel(); + assertTrue("Logger level was not expected value", currentLevel.equals(Level.toLevel("debug"))); + + //try an invalid level + assertFalse("Trying to set an invalid level succeded", lm.setRuntimeRootLoggerLevel("made.up.level")); + + //set root loggers level to warn + assertTrue("Failed to set logger level", lm.setRuntimeRootLoggerLevel("info")); + //check it is now actually warn + currentLevel = log.getLevel(); + assertTrue("Logger level was not expected value", currentLevel.equals(Level.toLevel("info"))); + + //restore original level + log.setLevel(origLevel); + } + + public void testGetRuntimeRootLoggerLevel() + { + LoggingManagementMBean lm = null; + try + { + lm = new LoggingManagementMBean(_testConfigFile.getAbsolutePath(), 0); + } + catch (JMException e) + { + fail("Could not create test LoggingManagementMBean"); + } + + Logger log = Logger.getRootLogger(); + + //get current root logger level + Level origLevel = log.getLevel(); + + //change level twice to ensure a new level is actually selected + + //set root loggers level to debug + log.setLevel(Level.toLevel("debug")); + //check it is now actually debug + assertTrue("Logger level was not expected value", lm.getRuntimeRootLoggerLevel().equalsIgnoreCase("debug")); + + + //set root loggers level to warn + log.setLevel(Level.toLevel("info")); + //check it is now actually warn + assertTrue("Logger level was not expected value", lm.getRuntimeRootLoggerLevel().equalsIgnoreCase("info")); + + //restore original level + log.setLevel(origLevel); + } + + public void testViewEffectiveRuntimeLoggerLevels() + { + LoggingManagementMBean lm = null; + try + { + lm = new LoggingManagementMBean(_testConfigFile.getAbsolutePath(), 0); + } + catch (JMException e) + { + fail("Could not create test LoggingManagementMBean"); + } + + //(re)create a parent test logger, set its level explicitly + Logger log = Logger.getLogger(TEST_LOGGER); + log.setLevel(Level.toLevel("info")); + + //retrieve the current effective runtime logger level values + TabularDataSupport levels = (TabularDataSupport) lm.viewEffectiveRuntimeLoggerLevels(); + Collection<Object> records = levels.values(); + Map<String,String> list = new HashMap<String,String>(); + for (Object o : records) + { + CompositeData data = (CompositeData) o; + list.put(data.get(LOGGER_NAME).toString(), data.get(LOGGER_LEVEL).toString()); + } + + //check child2 does not exist already + assertFalse("Did not expect this logger to exist already", list.containsKey(TEST_LOGGER_CHILD2)); + + //create child2 test logger + Logger log2 = Logger.getLogger(TEST_LOGGER_CHILD2); + + //retrieve the current effective runtime logger level values + levels = (TabularDataSupport) lm.viewEffectiveRuntimeLoggerLevels(); + records = levels.values(); + list = new HashMap<String,String>(); + for (Object o : records) + { + CompositeData data = (CompositeData) o; + list.put(data.get(LOGGER_NAME).toString(), data.get(LOGGER_LEVEL).toString()); + } + + //verify the parent and child2 loggers are present in returned values + assertTrue(TEST_LOGGER + " logger was not in the returned list", list.containsKey(TEST_LOGGER)); + assertTrue(TEST_LOGGER_CHILD2 + " logger was not in the returned list", list.containsKey(TEST_LOGGER_CHILD2)); + + //check child2's effective level is the same as the parent, "info" + assertTrue("Test logger's level was not the expected value", + list.get(TEST_LOGGER_CHILD2).equalsIgnoreCase("info")); + + //now change its level explicitly to "warn" + log2.setLevel(Level.toLevel("warn")); + + //retrieve the current effective runtime logger level values + levels = (TabularDataSupport) lm.viewEffectiveRuntimeLoggerLevels(); + records = levels.values(); + list = new HashMap<String,String>(); + for (Object o : records) + { + CompositeData data = (CompositeData) o; + list.put(data.get(LOGGER_NAME).toString(), data.get(LOGGER_LEVEL).toString()); + } + + //check child2's effective level is now "warn" + assertTrue("Test logger's level was not the expected value", + list.get(TEST_LOGGER_CHILD2).equalsIgnoreCase("warn")); + } + + public void testViewAndSetConfigFileLoggerLevel() throws Exception + { + LoggingManagementMBean lm =null; + try + { + lm = new LoggingManagementMBean(_testConfigFile.getAbsolutePath(), 0); + } + catch (JMException e) + { + fail("Could not create test LoggingManagementMBean"); + } + + //retrieve the current values + TabularDataSupport levels = (TabularDataSupport) lm.viewConfigFileLoggerLevels(); + Collection<Object> records = levels.values(); + Map<String,String> list = new HashMap<String,String>(); + for (Object o : records) + { + CompositeData data = (CompositeData) o; + list.put(data.get(LOGGER_NAME).toString(), data.get(LOGGER_LEVEL).toString()); + } + + //check the 3 different types of logger definition are successfully retrieved before update + assertTrue("Wrong number of items in returned list", list.size() == 3); + assertTrue(TEST_CATEGORY_PRIORITY + " logger was not in the returned list", list.containsKey(TEST_CATEGORY_PRIORITY)); + assertTrue(TEST_CATEGORY_LEVEL + " logger was not in the returned list", list.containsKey(TEST_CATEGORY_LEVEL)); + assertTrue(TEST_LOGGER_LEVEL + " logger was not in the returned list", list.containsKey(TEST_LOGGER_LEVEL)); + + //check that their level is as expected + assertTrue(TEST_CATEGORY_PRIORITY + " logger's level was incorrect", list.get(TEST_CATEGORY_PRIORITY).equalsIgnoreCase("info")); + assertTrue(TEST_CATEGORY_LEVEL + " logger's level was incorrect", list.get(TEST_CATEGORY_LEVEL).equalsIgnoreCase("warn")); + assertTrue(TEST_LOGGER_LEVEL + " logger's level was incorrect", list.get(TEST_LOGGER_LEVEL).equalsIgnoreCase("error")); + + //increase their levels a notch to test the 3 different types of logger definition are successfully updated + //change the category+priority to warn + assertTrue("failed to set new level", lm.setConfigFileLoggerLevel(TEST_CATEGORY_PRIORITY, "warn")); + //change the category+level to error + assertTrue("failed to set new level", lm.setConfigFileLoggerLevel(TEST_CATEGORY_LEVEL, "error")); + //change the logger+level to trace + assertTrue("failed to set new level", lm.setConfigFileLoggerLevel(TEST_LOGGER_LEVEL, "trace")); + + //try an invalid level + assertFalse("Use of an invalid logger level was successfull", lm.setConfigFileLoggerLevel(TEST_LOGGER_LEVEL, "made.up.level")); + + //try an invalid logger name + assertFalse("Use of an invalid logger name was successfull", lm.setConfigFileLoggerLevel("made.up.logger.name", "info")); + + //retrieve the new values from the file and check them + levels = (TabularDataSupport) lm.viewConfigFileLoggerLevels(); + records = levels.values(); + list = new HashMap<String,String>(); + for (Object o : records) + { + CompositeData data = (CompositeData) o; + list.put(data.get(LOGGER_NAME).toString(), data.get(LOGGER_LEVEL).toString()); + } + + //check the 3 different types of logger definition are successfully retrieved after update + assertTrue("Wrong number of items in returned list", list.size() == 3); + assertTrue(TEST_CATEGORY_PRIORITY + " logger was not in the returned list", list.containsKey(TEST_CATEGORY_PRIORITY)); + assertTrue(TEST_CATEGORY_LEVEL + " logger was not in the returned list", list.containsKey(TEST_CATEGORY_LEVEL)); + assertTrue(TEST_LOGGER_LEVEL + " logger was not in the returned list", list.containsKey(TEST_LOGGER_LEVEL)); + + //check that their level is as expected after the changes + assertTrue(TEST_CATEGORY_PRIORITY + " logger's level was incorrect", list.get(TEST_CATEGORY_PRIORITY).equalsIgnoreCase("warn")); + assertTrue(TEST_CATEGORY_LEVEL + " logger's level was incorrect", list.get(TEST_CATEGORY_LEVEL).equalsIgnoreCase("error")); + assertTrue(TEST_LOGGER_LEVEL + " logger's level was incorrect", list.get(TEST_LOGGER_LEVEL).equalsIgnoreCase("trace")); + } + + public void testGetAndSetConfigFileRootLoggerLevel() throws Exception + { + LoggingManagementMBean lm =null; + try + { + lm = new LoggingManagementMBean(_testConfigFile.getAbsolutePath(), 0); + } + catch (JMException e) + { + fail("Could not create test LoggingManagementMBean"); + } + + //retrieve the current value + String level = lm.getConfigFileRootLoggerLevel(); + + //check the value was successfully retrieved before update + assertTrue("Retrieved RootLogger level was incorrect", level.equalsIgnoreCase("info")); + + //try an invalid level + assertFalse("Use of an invalid RootLogger level was successfull", lm.setConfigFileRootLoggerLevel("made.up.level")); + + //change the level to warn + assertTrue("Failed to set new RootLogger level", lm.setConfigFileRootLoggerLevel("warn")); + + //retrieve the current value + level = lm.getConfigFileRootLoggerLevel(); + + //check the value was successfully retrieved after update + assertTrue("Retrieved RootLogger level was incorrect", level.equalsIgnoreCase("warn")); + } + + public void testGetLog4jLogWatchInterval() + { + LoggingManagementMBean lm =null; + try + { + lm = new LoggingManagementMBean(_testConfigFile.getAbsolutePath(), 5000); + } + catch (JMException e) + { + fail("Could not create test LoggingManagementMBean"); + } + + assertTrue("Wrong value returned for logWatch period", lm.getLog4jLogWatchInterval() == 5000); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/AbstractTestMessages.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/AbstractTestMessages.java new file mode 100644 index 0000000000..e253881d09 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/AbstractTestMessages.java @@ -0,0 +1,109 @@ +/* + * + * 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.qpid.server.logging.messages; + +import java.util.List; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogMessage; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.UnitTestMessageLogger; +import org.apache.qpid.server.logging.actors.TestLogActor; +import org.apache.qpid.server.logging.subjects.TestBlankSubject; +import org.apache.qpid.server.util.InternalBrokerBaseCase; + +public abstract class AbstractTestMessages extends InternalBrokerBaseCase +{ + protected Configuration _config = new PropertiesConfiguration(); + protected LogMessage _logMessage = null; + protected LogActor _actor; + protected UnitTestMessageLogger _logger; + protected LogSubject _logSubject = new TestBlankSubject(); + + @Override + public void setUp() throws Exception + { + super.setUp(); + + _logger = new UnitTestMessageLogger(); + + _actor = new TestLogActor(_logger); + } + + protected List<Object> performLog() + { + if (_logMessage == null) + { + throw new NullPointerException("LogMessage has not been set"); + } + + _actor.message(_logSubject, _logMessage); + + return _logger.getLogMessages(); + } + + /** + * Validate that only a single log messasge occured and that the message + * section starts with the specified tag + * + * @param logs the logs generated during test run + * @param tag the tag to check for + * @param expected the expected log messages + * + */ + protected void validateLogMessage(List<Object> logs, String tag, String[] expected) + { + assertEquals("Log has incorrect message count", 1, logs.size()); + + //We trim() here as we don't care about extra white space at the end of the log message + // but we do care about the ability to easily check we don't have unexpected text at + // the end. + String log = String.valueOf(logs.get(0)).trim(); + + // Simple switch to print out all the logged messages + //System.err.println(log); + + int msgIndex = log.indexOf(_logSubject.toLogString())+_logSubject.toLogString().length(); + + assertTrue("Unable to locate Subject:" + log, msgIndex != -1); + + String message = log.substring(msgIndex); + + assertTrue("Message does not start with tag:" + tag + ":" + message, + message.startsWith(tag)); + + // Test that the expected items occur in order. + int index = 0; + for (String text : expected) + { + index = message.indexOf(text, index); + assertTrue("Message does not contain expected (" + text + ") text :" + message, index != -1); + index = index + text.length(); + } + + //Check there is nothing left on the log message + assertEquals("Message has more text. '" + log.substring(msgIndex + index) + "'", + log.length(), msgIndex + index); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/BindingMessagesTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/BindingMessagesTest.java new file mode 100644 index 0000000000..22de8349c6 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/BindingMessagesTest.java @@ -0,0 +1,64 @@ +/* + * + * 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.qpid.server.logging.messages; + +import java.util.List; + +/** + * Test BND Log Messages + */ +public class BindingMessagesTest extends AbstractTestMessages +{ + + public void testBindCreate_NoArgs() + { + _logMessage = BindingMessages.CREATED(null, false); + List<Object> log = performLog(); + + String[] expected = {"Create"}; + + validateLogMessage(log, "BND-1001", expected); + } + + public void testBindCreate_Args() + { + String arguments = "arguments"; + + _logMessage = BindingMessages.CREATED(arguments, true); + List<Object> log = performLog(); + + String[] expected = {"Create", ": Arguments :", arguments}; + + validateLogMessage(log, "BND-1001", expected); + } + + public void testBindDelete() + { + _logMessage = BindingMessages.DELETED(); + + List<Object> log = performLog(); + + String[] expected = {"Deleted"}; + + validateLogMessage(log, "BND-1002", expected); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/BrokerMessagesTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/BrokerMessagesTest.java new file mode 100644 index 0000000000..a3d46f5716 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/BrokerMessagesTest.java @@ -0,0 +1,116 @@ +/* + * + * 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.qpid.server.logging.messages; + +import java.util.List; + +/** + * Test BRK log Messages + */ +public class BrokerMessagesTest extends AbstractTestMessages +{ + public void testBrokerStartup() + { + String version = "Qpid 0.6"; + String build = "796936M"; + + _logMessage = BrokerMessages.STARTUP(version, build); + List<Object> log = performLog(); + + String[] expected = {"Startup :", "Version:", version, "Build:", build}; + + validateLogMessage(log, "BRK-1001", expected); + } + + public void testBrokerListening() + { + String transport = "TCP"; + Integer port = 2765; + + _logMessage = BrokerMessages.LISTENING(transport, port); + + List<Object> log = performLog(); + + String[] expected = {"Starting", "Listening on ", + transport, "port ", String.valueOf(port)}; + + validateLogMessage(log, "BRK-1002", expected); + } + + public void testBrokerShuttingDown() + { + String transport = "TCP"; + Integer port = 2765; + + _logMessage = BrokerMessages.SHUTTING_DOWN(transport, port); + + List<Object> log = performLog(); + + String[] expected = {"Shuting down", transport, "port ", String.valueOf(port)}; + + validateLogMessage(log, "BRK-1003", expected); + } + + public void testBrokerReady() + { + _logMessage = BrokerMessages.READY(); + List<Object> log = performLog(); + + String[] expected = {"Ready"}; + + validateLogMessage(log, "BRK-1004", expected); + } + + public void testBrokerStopped() + { + _logMessage = BrokerMessages.STOPPED(); + List<Object> log = performLog(); + + String[] expected = {"Stopped"}; + + validateLogMessage(log, "BRK-1005", expected); + } + + public void testBrokerConfig() + { + String path = "/file/path/to/configuration.xml"; + + _logMessage = BrokerMessages.CONFIG(path); + List<Object> log = performLog(); + + String[] expected = {"Using configuration :", path}; + + validateLogMessage(log, "BRK-1006", expected); + } + + public void testBrokerLogConfig() + { + String path = "/file/path/to/configuration.xml"; + + _logMessage = BrokerMessages.LOG_CONFIG(path); + List<Object> log = performLog(); + + String[] expected = {"Using logging configuration :", path}; + + validateLogMessage(log, "BRK-1007", expected); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/ChannelMessagesTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/ChannelMessagesTest.java new file mode 100644 index 0000000000..e94b79ba95 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/ChannelMessagesTest.java @@ -0,0 +1,64 @@ +/* + * + * 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.qpid.server.logging.messages; + +import java.util.List; + +/** + * Test CHN Log Messges + */ +public class ChannelMessagesTest extends AbstractTestMessages +{ + public void testChannelCreate() + { + _logMessage = ChannelMessages.CREATE(); + List<Object> log = performLog(); + + // We use the MessageFormat here as that is what the ChannelMessage + // will do, this makes the resulting value 12,345 + String[] expected = {"Create"}; + + validateLogMessage(log, "CHN-1001", expected); + } + + public void testChannelFlow() + { + String flow = "ON"; + + _logMessage = ChannelMessages.FLOW(flow); + List<Object> log = performLog(); + + String[] expected = {"Flow", flow}; + + validateLogMessage(log, "CHN-1002", expected); + } + + public void testChannelClose() + { + _logMessage = ChannelMessages.CLOSE(); + List<Object> log = performLog(); + + String[] expected = {"Close"}; + + validateLogMessage(log, "CHN-1003", expected); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/ConnectionMessagesTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/ConnectionMessagesTest.java new file mode 100644 index 0000000000..24fccf8446 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/ConnectionMessagesTest.java @@ -0,0 +1,90 @@ +/* + * + * 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.qpid.server.logging.messages; + +import java.util.List; + +/** + * Test CON Log Messages + */ +public class ConnectionMessagesTest extends AbstractTestMessages +{ + public void testConnectionOpen_WithClientIDProtocolVersion() + { + String clientID = "client"; + String protocolVersion = "8-0"; + + _logMessage = ConnectionMessages.OPEN(clientID, protocolVersion, true , true); + List<Object> log = performLog(); + + String[] expected = {"Open :", "Client ID", clientID, + ": Protocol Version :", protocolVersion}; + + validateLogMessage(log, "CON-1001", expected); + } + + public void testConnectionOpen_WithClientIDNoProtocolVersion() + { + String clientID = "client"; + + _logMessage = ConnectionMessages.OPEN(clientID, null,true, false); + List<Object> log = performLog(); + + String[] expected = {"Open :", "Client ID", clientID}; + + validateLogMessage(log, "CON-1001", expected); + } + + public void testConnectionOpen_WithNOClientIDProtocolVersion() + { + String protocolVersion = "8-0"; + + _logMessage = ConnectionMessages.OPEN(null, protocolVersion, false , true); + List<Object> log = performLog(); + + String[] expected = {"Open", ": Protocol Version :", protocolVersion}; + + validateLogMessage(log, "CON-1001", expected); + } + + public void testConnectionOpen_WithNoClientIDNoProtocolVersion() + { + _logMessage = ConnectionMessages.OPEN(null, null,false, false); + List<Object> log = performLog(); + + String[] expected = {"Open"}; + + validateLogMessage(log, "CON-1001", expected); + } + + + + public void testConnectionClose() + { + _logMessage = ConnectionMessages.CLOSE(); + List<Object> log = performLog(); + + String[] expected = {"Close"}; + + validateLogMessage(log, "CON-1002", expected); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/ExchangeMessagesTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/ExchangeMessagesTest.java new file mode 100644 index 0000000000..728a98e009 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/ExchangeMessagesTest.java @@ -0,0 +1,80 @@ +/* + * + * 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.qpid.server.logging.messages; + +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.registry.ApplicationRegistry; + +import java.util.List; + +/** + * Test EXH Log Messages + */ +public class ExchangeMessagesTest extends AbstractTestMessages +{ + public void testExchangeCreated_Transient() + { + // Get the Default Exchange on the Test Vhost for testing + Exchange exchange = ApplicationRegistry.getInstance(). + getVirtualHostRegistry().getVirtualHost("test"). + getExchangeRegistry().getDefaultExchange(); + + String type = exchange.getTypeShortString().toString(); + String name = exchange.getNameShortString().toString(); + + _logMessage = ExchangeMessages.CREATED(type, name, false); + List<Object> log = performLog(); + + String[] expected = {"Create :", "Type:", type, "Name:", name}; + + validateLogMessage(log, "EXH-1001", expected); + } + + public void testExchangeCreated_Persistent() + { + // Get the Default Exchange on the Test Vhost for testing + Exchange exchange = ApplicationRegistry.getInstance(). + getVirtualHostRegistry().getVirtualHost("test"). + getExchangeRegistry().getDefaultExchange(); + + String type = exchange.getTypeShortString().toString(); + String name = exchange.getNameShortString().toString(); + + _logMessage = ExchangeMessages.CREATED(type, name, true); + List<Object> log = performLog(); + + String[] expected = {"Create :", "Durable", "Type:", type, "Name:", name}; + + validateLogMessage(log, "EXH-1001", expected); + } + + + public void testExchangeDeleted() + { + _logMessage = ExchangeMessages.DELETED(); + List<Object> log = performLog(); + + String[] expected = {"Deleted"}; + + validateLogMessage(log, "EXH-1002", expected); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/ManagementConsoleMessagesTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/ManagementConsoleMessagesTest.java new file mode 100644 index 0000000000..4bfbae44ac --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/ManagementConsoleMessagesTest.java @@ -0,0 +1,107 @@ +/* + * + * 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.qpid.server.logging.messages; + +import java.util.List; + +/** + * Test MNG Log Messages + */ +public class ManagementConsoleMessagesTest extends AbstractTestMessages +{ + public void testManagementStartup() + { + _logMessage = ManagementConsoleMessages.STARTUP(); + List<Object> log = performLog(); + + String[] expected = {"Startup"}; + + validateLogMessage(log, "MNG-1001", expected); + } + + public void testManagementListening() + { + String transport = "JMX"; + Integer port = 8889; + + _logMessage = ManagementConsoleMessages.LISTENING(transport, port); + List<Object> log = performLog(); + + String[] expected = {"Starting :", transport, ": Listening on port", String.valueOf(port)}; + + validateLogMessage(log, "MNG-1002", expected); + } + + public void testManagementShuttingDown() + { + String transport = "JMX"; + Integer port = 8889; + + _logMessage = ManagementConsoleMessages.SHUTTING_DOWN(transport, port); + List<Object> log = performLog(); + + String[] expected = {"Shutting down :", transport, ": port", String.valueOf(port)}; + + validateLogMessage(log, "MNG-1003", expected); + } + + public void testManagementReady() + { + _logMessage = ManagementConsoleMessages.READY(false); + List<Object> log = performLog(); + + String[] expected = {"Ready"}; + + validateLogMessage(log, "MNG-1004", expected); + + _logger.clearLogMessages(); + + _logMessage = ManagementConsoleMessages.READY(true); + log = performLog(); + + expected = new String[]{"Ready : Using the platform JMX Agent"}; + + validateLogMessage(log, "MNG-1004", expected); + } + + public void testManagementStopped() + { + _logMessage = ManagementConsoleMessages.STOPPED(); + List<Object> log = performLog(); + + String[] expected = {"Stopped"}; + + validateLogMessage(log, "MNG-1005", expected); + } + + public void testManagementSSLKeyStore() + { + String path = "/path/to/the/keystore/files.jks"; + + _logMessage = ManagementConsoleMessages.SSL_KEYSTORE(path); + List<Object> log = performLog(); + + String[] expected = {"Using SSL Keystore :", path}; + + validateLogMessage(log, "MNG-1006", expected); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/MessageStoreMessagesTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/MessageStoreMessagesTest.java new file mode 100644 index 0000000000..cc032a0430 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/MessageStoreMessagesTest.java @@ -0,0 +1,125 @@ +/* + * + * 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.qpid.server.logging.messages; + +import java.util.List; + +/** + * Test MST Log Messages + */ +public class MessageStoreMessagesTest extends AbstractTestMessages +{ + public void testMessageStoreCreated() + { + String name = "DerbyMessageStore"; + + _logMessage = MessageStoreMessages.CREATED(name); + List<Object> log = performLog(); + + String[] expected = {"Created :", name}; + + validateLogMessage(log, "MST-1001", expected); + } + + public void testMessageStoreStoreLocation() + { + String location = "/path/to/the/message/store.files"; + + _logMessage = MessageStoreMessages.STORE_LOCATION(location); + List<Object> log = performLog(); + + String[] expected = {"Store location :", location}; + + validateLogMessage(log, "MST-1002", expected); + } + + public void testMessageStoreClosed() + { + _logMessage = MessageStoreMessages.CLOSED(); + List<Object> log = performLog(); + + String[] expected = {"Closed"}; + + validateLogMessage(log, "MST-1003", expected); + } + + public void testMessageStoreRecoveryStart() + { + _logMessage = MessageStoreMessages.RECOVERY_START(); + List<Object> log = performLog(); + + String[] expected = {"Recovery Start"}; + + validateLogMessage(log, "MST-1004", expected); + } +/* + public void testMessageStoreRecoveryStart_withQueue() + { + String queueName = "testQueue"; + + _logMessage = MessageStoreMessages.RECOVERY_START(queueName, true); + List<Object> log = performLog(); + + String[] expected = {"Recovery Start :", queueName}; + + validateLogMessage(log, "MST-1004", expected); + } + + public void testMessageStoreRecovered() + { + String queueName = "testQueue"; + Integer messasgeCount = 2000; + + _logMessage = MessageStoreMessages.MST_RECOVERED(messasgeCount, queueName); + List<Object> log = performLog(); + + // Here we use MessageFormat to ensure the messasgeCount of 2000 is + // reformated for display as '2,000' + String[] expected = {"Recovered ", + MessageFormat.format("{0,number}", messasgeCount), + "messages for queue", queueName}; + + validateLogMessage(log, "MST-1005", expected); + } + + public void testMessageStoreRecoveryComplete() + { + _logMessage = MessageStoreMessages.MST_RECOVERY_COMPLETE(null,false); + List<Object> log = performLog(); + + String[] expected = {"Recovery Complete"}; + + validateLogMessage(log, "MST-1006", expected); + } + + public void testMessageStoreRecoveryComplete_withQueue() + { + String queueName = "testQueue"; + + _logMessage = MessageStoreMessages.MST_RECOVERY_COMPLETE(queueName, true); + List<Object> log = performLog(); + + String[] expected = {"Recovery Complete :", queueName}; + + validateLogMessage(log, "MST-1006", expected); + } + */ +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/QueueMessagesTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/QueueMessagesTest.java new file mode 100644 index 0000000000..d51e6a6bb7 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/QueueMessagesTest.java @@ -0,0 +1,239 @@ +/* + * + * 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.qpid.server.logging.messages; + +import java.util.List; + +/** + * Test QUE Log Messages + */ +public class QueueMessagesTest extends AbstractTestMessages +{ + public void testQueueCreatedALL() + { + String owner = "guest"; + Integer priority = 3; + + _logMessage = QueueMessages.CREATED(owner, priority, true, true, true, true, true); + List<Object> log = performLog(); + + String[] expected = {"Create :", "Owner:", owner, "AutoDelete", + "Durable", "Transient", "Priority:", + String.valueOf(priority)}; + + validateLogMessage(log, "QUE-1001", expected); + } + + public void testQueueCreatedOwnerAutoDelete() + { + String owner = "guest"; + + _logMessage = QueueMessages.CREATED(owner, null, true, true, false, false, false); + List<Object> log = performLog(); + + String[] expected = {"Create :", "Owner:", owner, "AutoDelete"}; + + validateLogMessage(log, "QUE-1001", expected); + } + + public void testQueueCreatedOwnerPriority() + { + String owner = "guest"; + Integer priority = 3; + + _logMessage = QueueMessages.CREATED(owner, priority, true, false, false, false, true); + List<Object> log = performLog(); + + String[] expected = {"Create :", "Owner:", owner, "Priority:", + String.valueOf(priority)}; + + validateLogMessage(log, "QUE-1001", expected); + } + + public void testQueueCreatedOwnerAutoDeletePriority() + { + String owner = "guest"; + Integer priority = 3; + + _logMessage = QueueMessages.CREATED(owner, priority, true, true, false, false, true); + List<Object> log = performLog(); + + String[] expected = {"Create :", "Owner:", owner, "AutoDelete", + "Priority:", + String.valueOf(priority)}; + + validateLogMessage(log, "QUE-1001", expected); + } + + public void testQueueCreatedOwnerAutoDeleteTransient() + { + String owner = "guest"; + + _logMessage = QueueMessages.CREATED(owner, null, true, true, false, true, false); + List<Object> log = performLog(); + + String[] expected = {"Create :", "Owner:", owner, "AutoDelete", + "Transient"}; + + validateLogMessage(log, "QUE-1001", expected); + } + + public void testQueueCreatedOwnerAutoDeleteTransientPriority() + { + String owner = "guest"; + Integer priority = 3; + + _logMessage = QueueMessages.CREATED(owner, priority, true, true, false, true, true); + List<Object> log = performLog(); + + String[] expected = {"Create :", "Owner:", owner, "AutoDelete", + "Transient", "Priority:", + String.valueOf(priority)}; + + validateLogMessage(log, "QUE-1001", expected); + } + + public void testQueueCreatedOwnerAutoDeleteDurable() + { + String owner = "guest"; + + _logMessage = QueueMessages.CREATED(owner, null, true, true, true, false, false); + List<Object> log = performLog(); + + String[] expected = {"Create :", "Owner:", owner, "AutoDelete", + "Durable"}; + + validateLogMessage(log, "QUE-1001", expected); + } + + public void testQueueCreatedOwnerAutoDeleteDurablePriority() + { + String owner = "guest"; + Integer priority = 3; + + _logMessage = QueueMessages.CREATED(owner, priority, true, true, true, false, true); + List<Object> log = performLog(); + + String[] expected = {"Create :", "Owner:", owner, "AutoDelete", + "Durable", "Priority:", + String.valueOf(priority)}; + + validateLogMessage(log, "QUE-1001", expected); + } + + public void testQueueCreatedAutoDelete() + { + _logMessage = QueueMessages.CREATED(null, null, false, true, false, false, false); + List<Object> log = performLog(); + + String[] expected = {"Create :", "AutoDelete"}; + + validateLogMessage(log, "QUE-1001", expected); + } + + public void testQueueCreatedPriority() + { + Integer priority = 3; + + _logMessage = QueueMessages.CREATED(null, priority, false, false, false, false, true); + List<Object> log = performLog(); + + String[] expected = {"Create :", "Priority:", + String.valueOf(priority)}; + + validateLogMessage(log, "QUE-1001", expected); + } + + public void testQueueCreatedAutoDeletePriority() + { + Integer priority = 3; + + _logMessage = QueueMessages.CREATED(null, priority, false, true, false, false, true); + List<Object> log = performLog(); + + String[] expected = {"Create :", "AutoDelete", + "Priority:", + String.valueOf(priority)}; + + validateLogMessage(log, "QUE-1001", expected); + } + + public void testQueueCreatedAutoDeleteTransient() + { + _logMessage = QueueMessages.CREATED(null, null, false, true, false, true, false); + List<Object> log = performLog(); + + String[] expected = {"Create :", "AutoDelete", + "Transient"}; + + validateLogMessage(log, "QUE-1001", expected); + } + + public void testQueueCreatedAutoDeleteTransientPriority() + { + Integer priority = 3; + + _logMessage = QueueMessages.CREATED(null, priority, false, true, false, true, true); + List<Object> log = performLog(); + + String[] expected = {"Create :", "AutoDelete", + "Transient", "Priority:", + String.valueOf(priority)}; + + validateLogMessage(log, "QUE-1001", expected); + } + + public void testQueueCreatedAutoDeleteDurable() + { + _logMessage = QueueMessages.CREATED(null, null, false, true, true, false, false); + List<Object> log = performLog(); + + String[] expected = {"Create :", "AutoDelete", + "Durable"}; + + validateLogMessage(log, "QUE-1001", expected); + } + + public void testQueueCreatedAutoDeleteDurablePriority() + { + Integer priority = 3; + + _logMessage = QueueMessages.CREATED(null, priority, false, true, true, false, true); + List<Object> log = performLog(); + + String[] expected = {"Create :", "AutoDelete", + "Durable", "Priority:", + String.valueOf(priority)}; + + validateLogMessage(log, "QUE-1001", expected); + } + + public void testQueueDeleted() + { + _logMessage = QueueMessages.DELETED(); + List<Object> log = performLog(); + + String[] expected = {"Deleted"}; + + validateLogMessage(log, "QUE-1002", expected); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/SubscriptionMessagesTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/SubscriptionMessagesTest.java new file mode 100644 index 0000000000..b2bc351f8f --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/SubscriptionMessagesTest.java @@ -0,0 +1,86 @@ +/* + * + * 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.qpid.server.logging.messages; + +import java.util.List; + +/** + * Test SUB Log Messages + */ +public class SubscriptionMessagesTest extends AbstractTestMessages +{ + public void testSubscriptionCreateALL() + { + String arguments = "arguments"; + + _logMessage = SubscriptionMessages.CREATE(arguments, true, true); + List<Object> log = performLog(); + + String[] expected = {"Create :", "Durable", "Arguments :", arguments}; + + validateLogMessage(log, "SUB-1001", expected); + } + + public void testSubscriptionCreateDurable() + { + _logMessage = SubscriptionMessages.CREATE(null, true, false); + List<Object> log = performLog(); + + String[] expected = {"Create :", "Durable"}; + + validateLogMessage(log, "SUB-1001", expected); + } + + public void testSubscriptionCreateArguments() + { + String arguments = "arguments"; + + _logMessage = SubscriptionMessages.CREATE(arguments, false, true); + List<Object> log = performLog(); + + String[] expected = {"Create :","Arguments :", arguments}; + + validateLogMessage(log, "SUB-1001", expected); + } + + + public void testSubscriptionClose() + { + _logMessage = SubscriptionMessages.CLOSE(); + List<Object> log = performLog(); + + String[] expected = {"Close"}; + + validateLogMessage(log, "SUB-1002", expected); + } + + public void testSubscriptionState() + { + String state = "ACTIVE"; + + _logMessage = SubscriptionMessages.STATE(state); + List<Object> log = performLog(); + + String[] expected = {"State :", state}; + + validateLogMessage(log, "SUB-1003", expected); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/VirtualHostMessagesTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/VirtualHostMessagesTest.java new file mode 100644 index 0000000000..17d68ef7c3 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/VirtualHostMessagesTest.java @@ -0,0 +1,51 @@ +/* + * + * 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.qpid.server.logging.messages; + +import java.util.List; + +/** + * Test VHT Log Messages + */ +public class VirtualHostMessagesTest extends AbstractTestMessages +{ + public void testVirtualhostCreated() + { + String name = "test"; + _logMessage = VirtualHostMessages.CREATED(name); + List<Object> log = performLog(); + + String[] expected = {"Created :", name}; + + validateLogMessage(log, "VHT-1001", expected); + } + + public void testSubscriptionClosed() + { + _logMessage = VirtualHostMessages.CLOSED(); + List<Object> log = performLog(); + + String[] expected = {"Closed"}; + + validateLogMessage(log, "VHT-1002", expected); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/AbstractTestLogSubject.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/AbstractTestLogSubject.java new file mode 100644 index 0000000000..1cd8d55b0d --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/AbstractTestLogSubject.java @@ -0,0 +1,288 @@ +/* + * + * 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.qpid.server.logging.subjects; + +import junit.framework.TestCase; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogMessage; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.logging.AbstractRootMessageLogger; +import org.apache.qpid.server.logging.UnitTestMessageLogger; +import org.apache.qpid.server.logging.actors.TestLogActor; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.util.InternalBrokerBaseCase; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.protocol.InternalTestProtocolSession; +import org.apache.qpid.server.protocol.AMQProtocolSession; + +import java.util.List; + +/** + * Abstract Test for LogSubject testing + * Includes common validation code and two common tests. + * + * Each test class sets up the LogSubject and contains details of how to + * validate this class then performs a log statement with logging enabled and + * logging disabled. + * + * The resulting log file is then validated. + * + */ +public abstract class AbstractTestLogSubject extends InternalBrokerBaseCase +{ + protected Configuration _config = new PropertiesConfiguration(); + protected LogSubject _subject = null; + + @Override + public void setUp() throws Exception + { + super.setUp(); + + _config.setProperty(ServerConfiguration.STATUS_UPDATES, "ON"); + } + + + protected List<Object> performLog() throws ConfigurationException + { + if (_subject == null) + { + throw new NullPointerException("LogSubject has not been set"); + } + + ServerConfiguration serverConfig = new ServerConfiguration(_config); + UnitTestMessageLogger logger = new UnitTestMessageLogger(serverConfig); + + LogActor actor = new TestLogActor(logger); + + actor.message(_subject, new LogMessage() + { + public String toString() + { + return "<Log Message>"; + } + + public String getLogHierarchy() + { + return "test.hierarchy"; + } + }); + + return logger.getLogMessages(); + } + + /** + * Verify that the connection section has the expected items + * + * @param connectionID - The connection id (int) to check for + * @param user - the Connected username + * @param ipString - the ipString/hostname + * @param vhost - the virtualhost that the user connected to. + * @param message - the message these values should appear in. + */ + protected void verifyConnection(long connectionID, String user, String ipString, String vhost, String message) + { + // This should return us MockProtocolSessionUser@null/test + String connectionSlice = getSlice("con:" + connectionID, message); + + assertNotNull("Unable to find connection 'con:" + connectionID + "'", + connectionSlice); + + // Exract the userName + String[] userNameParts = connectionSlice.split("@"); + + assertEquals("Unable to split Username from rest of Connection:" + + connectionSlice, 2, userNameParts.length); + + assertEquals("Username not as expected", userNameParts[0], user); + + // Extract IP. + // The connection will be of the format - guest@/127.0.0.1:1/test + // and so our userNamePart will be '/127.0.0.1:1/test' + String[] ipParts = userNameParts[1].split("/"); + + // We will have three sections + assertEquals("Unable to split IP from rest of Connection:" + + userNameParts[1], 3, ipParts.length); + + // We need to skip the first '/' split will be empty so validate 1 as IP + assertEquals("IP not as expected", ipString, ipParts[1]); + + //Finally check vhost which is section 2 + assertEquals("Virtualhost name not as expected.", vhost, ipParts[2]); + } + + /** + * Verify that the RoutingKey is present in the provided message. + * + * @param message The message to check + * @param routingKey The routing key to check against + */ + protected void verifyRoutingKey(String message, AMQShortString routingKey) + { + String routingKeySlice = getSlice("rk", message); + + assertNotNull("Routing Key not found:" + message, routingKey); + + assertEquals("Routing key not correct", + routingKey.toString(), routingKeySlice); + } + + /** + * Verify that the given Queue's name exists in the provided message + * + * @param message The message to check + * @param queue The queue to check against + */ + protected void verifyQueue(String message, AMQQueue queue) + { + String queueSlice = getSlice("qu", message); + + assertNotNull("Queue not found:" + message, queueSlice); + + assertEquals("Queue name not correct", + queue.getNameShortString().toString(), queueSlice); + } + + /** + * Verify that the given exchange (name and type) are present in the + * provided message. + * + * @param message The message to check + * @param exchange the exchange to check against + */ + protected void verifyExchange(String message, Exchange exchange) + { + String exchangeSilce = getSlice("ex", message); + + assertNotNull("Exchange not found:" + message, exchangeSilce); + + String[] exchangeParts = exchangeSilce.split("/"); + + assertEquals("Exchange should be in two parts ex(type/name)", 2, + exchangeParts.length); + + assertEquals("Exchange type not correct", + exchange.getTypeShortString().toString(), exchangeParts[0]); + + assertEquals("Exchange name not correct", + exchange.getNameShortString().toString(), exchangeParts[1]); + + } + + /** + * Verify that a VirtualHost with the given name appears in the given + * message. + * + * @param message the message to search + * @param vhost the vhostName to check against + */ + static public void verifyVirtualHost(String message, VirtualHost vhost) + { + String vhostSlice = getSlice("vh", message); + + assertNotNull("Virtualhost not found:" + message, vhostSlice); + + assertEquals("Virtualhost not correct", "/" + vhost.getName(), vhostSlice); + } + + /** + * Parse the log message and return the slice according to the following: + * Given Example: + * con:1(guest@127.0.0.1/test)/ch:2/ex(amq.direct)/qu(myQueue)/rk(myQueue) + * + * Each item (except channel) is of the format <key>(<values>) + * + * So Given an ID to slice on: + * con:1 - Connection 1 + * ex - exchange + * qu - queue + * rk - routing key + * + * @param sliceID the slice to locate + * @param message the message to search in + * + * @return the slice if found otherwise null is returned + */ + static public String getSlice(String sliceID, String message) + { + int indexOfSlice = message.indexOf(sliceID + "("); + + if (indexOfSlice == -1) + { + return null; + } + + int endIndex = message.indexOf(')', indexOfSlice); + + if (endIndex == -1) + { + return null; + } + + return message.substring(indexOfSlice + 1 + sliceID.length(), + endIndex); + } + + /** + * Test that when Logging occurs a single log statement is provided + * + * @throws ConfigurationException + */ + public void testEnabled() throws ConfigurationException + { + List<Object> logs = performLog(); + + assertEquals("Log has incorrect message count", 1, logs.size()); + + validateLogStatement(String.valueOf(logs.get(0))); + } + + /** + * Call to the individiual tests to validate the message is formatted as + * expected + * + * @param message the message whos format needs validation + */ + protected abstract void validateLogStatement(String message); + + /** + * Ensure that when status-updates are off this does not perform logging + * + * @throws ConfigurationException + */ + public void testDisabled() throws ConfigurationException + { + _config.setProperty(ServerConfiguration.STATUS_UPDATES, "OFF"); + + List<Object> logs = performLog(); + + assertEquals("Log has incorrect message count", 0, logs.size()); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/BindingLogSubjectTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/BindingLogSubjectTest.java new file mode 100644 index 0000000000..e80c4c4679 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/BindingLogSubjectTest.java @@ -0,0 +1,71 @@ +/* + * + * 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.qpid.server.logging.subjects; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.MockAMQQueue; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.virtualhost.VirtualHost; + +/** + * Validate BindingLogSubjects are logged as expected + */ +public class BindingLogSubjectTest extends AbstractTestLogSubject +{ + + private AMQQueue _queue; + private AMQShortString _routingKey; + private Exchange _exchange; + private VirtualHost _testVhost; + + public void setUp() throws Exception + { + super.setUp(); + + _testVhost = ApplicationRegistry.getInstance().getVirtualHostRegistry(). + getVirtualHost("test"); + // Configure items for subjectCreation + _routingKey = new AMQShortString("RoutingKey"); + _exchange = _testVhost.getExchangeRegistry().getDefaultExchange(); + _queue = new MockAMQQueue("BindingLogSubjectTest"); + ((MockAMQQueue) _queue).setVirtualHost(_testVhost); + + _subject = new BindingLogSubject(String.valueOf(_routingKey), _exchange, _queue); + } + + /** + * Validate that the logged Subject message is as expected: + * MESSAGE [Blank][vh(/test)/ex(direct/<<default>>)/qu(BindingLogSubjectTest)/rk(RoutingKey)] <Log Message> + * @param message the message whos format needs validation + */ + @Override + protected void validateLogStatement(String message) + { + verifyVirtualHost(message, _testVhost); + verifyExchange(message, _exchange); + verifyQueue(message, _queue); + verifyRoutingKey(message, _routingKey); + } + + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/ChannelLogSubjectTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/ChannelLogSubjectTest.java new file mode 100644 index 0000000000..6bc5effa05 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/ChannelLogSubjectTest.java @@ -0,0 +1,58 @@ +/* + * + * 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.qpid.server.logging.subjects; + +import org.apache.qpid.server.AMQChannel; + +/** + * Validate ChannelLogSubjects are logged as expected + */ +public class ChannelLogSubjectTest extends ConnectionLogSubjectTest +{ + private final int _channelID = 1; + + @Override + public void setUp() throws Exception + { + super.setUp(); + + AMQChannel channel = new AMQChannel(getSession(), _channelID, getSession().getVirtualHost().getMessageStore()); + + _subject = new ChannelLogSubject(channel); + } + + /** + * MESSAGE [Blank][con:0(MockProtocolSessionUser@null/test)/ch:1] <Log Message> + * + * @param message the message whos format needs validation + */ + protected void validateLogStatement(String message) + { + // Use the ConnectionLogSubjectTest to vaildate that the connection + // section is ok + super.validateLogStatement(message); + + // Finally check that the channel identifier is correctly added + assertTrue("Channel 1 identifier not found as part of Subject", + message.contains(")/ch:1]")); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/ConnectionLogSubjectTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/ConnectionLogSubjectTest.java new file mode 100644 index 0000000000..c246fff2a8 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/ConnectionLogSubjectTest.java @@ -0,0 +1,46 @@ +/* + * + * 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.qpid.server.logging.subjects; + +/** + * Validate ConnectionLogSubjects are logged as expected + */ +public class ConnectionLogSubjectTest extends AbstractTestLogSubject +{ + + public void setUp() throws Exception + { + super.setUp(); + + _subject = new ConnectionLogSubject(getSession()); + } + + /** + * MESSAGE [Blank][con:0(MockProtocolSessionUser@null/test)] <Log Message> + * + * @param message the message whos format needs validation + */ + protected void validateLogStatement(String message) + { + verifyConnection(getSession().getSessionID(), "InternalTestProtocolSession", "127.0.0.1:1", "test", message); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/ExchangeLogSubjectTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/ExchangeLogSubjectTest.java new file mode 100644 index 0000000000..7e16516fc6 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/ExchangeLogSubjectTest.java @@ -0,0 +1,58 @@ +/* + * + * 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.qpid.server.logging.subjects; + +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.virtualhost.VirtualHost; + + +/** + * Validate ExchangeLogSubjects are logged as expected + */ +public class ExchangeLogSubjectTest extends AbstractTestLogSubject +{ + Exchange _exchange; + VirtualHost _testVhost; + + public void setUp() throws Exception + { + super.setUp(); + + _testVhost = ApplicationRegistry.getInstance().getVirtualHostRegistry(). + getVirtualHost("test"); + + _exchange = _testVhost.getExchangeRegistry().getDefaultExchange(); + _subject = new ExchangeLogSubject(_exchange,_testVhost); + } + + /** + * Validate that the logged Subject message is as expected: + * MESSAGE [Blank][vh(/test)/ex(direct/<<default>>)] <Log Message> + * @param message the message whos format needs validation + */ + @Override + protected void validateLogStatement(String message) + { + verifyVirtualHost(message, _testVhost); + verifyExchange(message, _exchange); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/MessageStoreLogSubjectTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/MessageStoreLogSubjectTest.java new file mode 100644 index 0000000000..9c868ea651 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/MessageStoreLogSubjectTest.java @@ -0,0 +1,62 @@ +/* + * + * 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.qpid.server.logging.subjects; + +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.registry.ApplicationRegistry; + +/** + * Validate MessageStoreLogSubjects are logged as expected + */ +public class MessageStoreLogSubjectTest extends AbstractTestLogSubject +{ + VirtualHost _testVhost; + + public void setUp() throws Exception + { + super.setUp(); + + _testVhost = ApplicationRegistry.getInstance().getVirtualHostRegistry(). + getVirtualHost("test"); + + _subject = new MessageStoreLogSubject(_testVhost, _testVhost.getMessageStore()); + } + + /** + * Validate that the logged Subject message is as expected: + * MESSAGE [Blank][vh(/test)/ms(MemoryMessageStore)] <Log Message> + * @param message the message whos format needs validation + */ + @Override + protected void validateLogStatement(String message) + { + verifyVirtualHost(message, _testVhost); + + String msSlice = getSlice("ms", message); + + assertNotNull("MessageStore not found:" + message, msSlice); + + assertEquals("MessageStore not correct", + _testVhost.getMessageStore().getClass().getSimpleName(), + msSlice); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/QueueLogSubjectTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/QueueLogSubjectTest.java new file mode 100644 index 0000000000..1f432be57a --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/QueueLogSubjectTest.java @@ -0,0 +1,63 @@ +/* + * + * 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.qpid.server.logging.subjects; + +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.MockAMQQueue; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.virtualhost.VirtualHost; + +/** + * Validate QueueLogSubjects are logged as expected + */ +public class QueueLogSubjectTest extends AbstractTestLogSubject +{ + + private AMQQueue _queue; + private VirtualHost _testVhost; + + @Override + public void setUp() throws Exception + { + super.setUp(); + + _testVhost = ApplicationRegistry.getInstance().getVirtualHostRegistry(). + getVirtualHost("test"); + + _queue = new MockAMQQueue("QueueLogSubjectTest"); + ((MockAMQQueue) _queue).setVirtualHost(_testVhost); + + _subject = new QueueLogSubject(_queue); + } + + /** + * Validate that the logged Subject message is as expected: + * MESSAGE [Blank][vh(/test)/qu(QueueLogSubjectTest)] <Log Message> + * + * @param message the message whos format needs validation + */ + @Override + protected void validateLogStatement(String message) + { + verifyVirtualHost(message, _testVhost); + verifyQueue(message, _queue); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/SubscriptionLogSubjectTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/SubscriptionLogSubjectTest.java new file mode 100644 index 0000000000..0c356e1838 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/SubscriptionLogSubjectTest.java @@ -0,0 +1,105 @@ +/* + * + * 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.qpid.server.logging.subjects; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.flow.LimitlessCreditManager; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.MockAMQQueue; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.subscription.SubscriptionFactory; +import org.apache.qpid.server.subscription.SubscriptionFactoryImpl; +import org.apache.qpid.server.virtualhost.VirtualHost; + +/** + * Validate SubscriptionLogSubjects are logged as expected + */ +public class SubscriptionLogSubjectTest extends AbstractTestLogSubject +{ + + private AMQQueue _queue; + private VirtualHost _testVhost; + private int _channelID = 1; + private Subscription _subscription; + + public void setUp() throws Exception + { + super.setUp(); + + _testVhost = ApplicationRegistry.getInstance().getVirtualHostRegistry(). + getVirtualHost("test"); + + _queue = new MockAMQQueue("SubscriptionLogSubjectTest"); + ((MockAMQQueue) _queue).setVirtualHost(_testVhost); + + AMQChannel channel = new AMQChannel(getSession(), _channelID, getSession().getVirtualHost().getMessageStore()); + + getSession().addChannel(channel); + + SubscriptionFactory factory = new SubscriptionFactoryImpl(); + + _subscription = factory.createSubscription(_channelID, getSession(), new AMQShortString("cTag"), + false, null, false, + new LimitlessCreditManager()); + + _subscription.setQueue(_queue, false); + + _subject = new SubscriptionLogSubject(_subscription); + } + + /** + * Validate that the logged Subject message is as expected: + * MESSAGE [Blank][sub:0(vh(/test)/qu(SubscriptionLogSubjectTest))] <Log Message> + * + * @param message the message whos format needs validation + */ + @Override + protected void validateLogStatement(String message) + { + String subscriptionSlice = getSlice("sub:" + + _subscription.getSubscriptionID(), + message); + + assertNotNull("Unable to locate subscription 'sub:" + + _subscription.getSubscriptionID() + "'"); + + + + // Pull out the qu(..) section from the subscription message + // Split it into three parts + // MESSAGE [Blank][sub:0(vh(/ + // test)/ + // qu(SubscriptionLogSubjectTest))] + // Take the last bit and drop off the extra )] + String[] parts = message.split("/"); + assertEquals("Message part count wrong", 3, parts.length); + String subscription = parts[2].substring(0, parts[2].indexOf(")") + 1); + + // Adding the ')' is a bit ugly but SubscriptionLogSubject is the only + // Subject that nests () and so the simple parser of checking for the + // next ')' falls down. + verifyVirtualHost(subscriptionSlice+ ")", _queue.getVirtualHost()); + + verifyQueue(subscription, _queue); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/TestBlankSubject.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/TestBlankSubject.java new file mode 100644 index 0000000000..89688e13b3 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/TestBlankSubject.java @@ -0,0 +1,33 @@ +/* + * + * 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.qpid.server.logging.subjects; + +/** + * Blank Subject for testing + */ +public class TestBlankSubject extends AbstractLogSubject +{ + public TestBlankSubject() + { + _logString = "[TestBlankSubject]"; + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/management/AMQUserManagementMBeanTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/management/AMQUserManagementMBeanTest.java new file mode 100644 index 0000000000..21f79e4b69 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/management/AMQUserManagementMBeanTest.java @@ -0,0 +1,191 @@ +/* + * + * 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.qpid.server.management; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.TabularData; + + +import org.apache.commons.lang.NotImplementedException; +import org.apache.qpid.management.common.mbeans.UserManagement; +import org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase; +import org.apache.qpid.server.security.auth.management.AMQUserManagementMBean; + +import org.apache.qpid.server.util.InternalBrokerBaseCase; + +/** + * + * Tests the AMQUserManagementMBean and its interaction with the PrincipalDatabase. + * + */ +public class AMQUserManagementMBeanTest extends InternalBrokerBaseCase +{ + private PlainPasswordFilePrincipalDatabase _database; + private AMQUserManagementMBean _amqumMBean; + + private File _passwordFile; + + private static final String TEST_USERNAME = "testuser"; + private static final String TEST_PASSWORD = "password"; + + @Override + public void setUp() throws Exception + { + super.setUp(); + + _database = new PlainPasswordFilePrincipalDatabase(); + _amqumMBean = new AMQUserManagementMBean(); + loadFreshTestPasswordFile(); + } + + @Override + public void tearDown() throws Exception + { + //clean up test password/access files + File _oldPasswordFile = new File(_passwordFile.getAbsolutePath() + ".old"); + _oldPasswordFile.delete(); + _passwordFile.delete(); + + super.tearDown(); + } + + public void testDeleteUser() + { + assertEquals("Unexpected number of users before test", 1,_amqumMBean.viewUsers().size()); + assertTrue("Delete should return true to flag successful delete", _amqumMBean.deleteUser(TEST_USERNAME)); + assertEquals("Unexpected number of users after test", 0,_amqumMBean.viewUsers().size()); + } + + public void testDeleteUserWhereUserDoesNotExist() + { + assertEquals("Unexpected number of users before test", 1,_amqumMBean.viewUsers().size()); + assertFalse("Delete should return false to flag unsuccessful delete", _amqumMBean.deleteUser("made.up.username")); + assertEquals("Unexpected number of users after test", 1,_amqumMBean.viewUsers().size()); + + } + + public void testCreateUser() + { + assertEquals("Unexpected number of users before test", 1,_amqumMBean.viewUsers().size()); + assertTrue("Create should return true to flag successful create", _amqumMBean.createUser("newuser", "mypass")); + assertEquals("Unexpected number of users before test", 2,_amqumMBean.viewUsers().size()); + } + + public void testCreateUserWhereUserAlreadyExists() + { + assertEquals("Unexpected number of users before test", 1,_amqumMBean.viewUsers().size()); + assertFalse("Create should return false to flag unsuccessful create", _amqumMBean.createUser(TEST_USERNAME, "mypass")); + assertEquals("Unexpected number of users before test", 1,_amqumMBean.viewUsers().size()); + } + + public void testFiveArgCreateUserWithNegativeRightsRemainsSupported() + { + assertEquals("Unexpected number of users before test", 1,_amqumMBean.viewUsers().size()); + assertTrue("Create should return true to flag successful create", _amqumMBean.createUser("newuser", "mypass".toCharArray(), false, false, false)); + assertEquals("Unexpected number of users before test", 2,_amqumMBean.viewUsers().size()); + } + + public void testSetPassword() + { + assertTrue("Set password should return true to flag successful change", _amqumMBean.setPassword(TEST_USERNAME, "newpassword")); + } + + public void testSetPasswordWhereUserDoesNotExist() + { + assertFalse("Set password should return false to flag successful change", _amqumMBean.setPassword("made.up.username", "newpassword")); + } + + public void testViewUsers() + { + TabularData userList = _amqumMBean.viewUsers(); + + assertNotNull(userList); + assertEquals("Unexpected number of users in user list", 1, userList.size()); + assertTrue(userList.containsKey(new Object[]{TEST_USERNAME})); + + // Check the deprecated read, write and admin items continue to exist but return false. + CompositeData userRec = userList.get(new Object[]{TEST_USERNAME}); + assertTrue(userRec.containsKey(UserManagement.RIGHTS_READ_ONLY)); + assertEquals(false, userRec.get(UserManagement.RIGHTS_READ_ONLY)); + assertEquals(false, userRec.get(UserManagement.RIGHTS_READ_WRITE)); + assertTrue(userRec.containsKey(UserManagement.RIGHTS_READ_WRITE)); + assertTrue(userRec.containsKey(UserManagement.RIGHTS_ADMIN)); + assertEquals(false, userRec.get(UserManagement.RIGHTS_ADMIN)); + } + + // TEST DEPRECATED METHODS + public void testFiveArgCreateUserWithPositiveRightsThrowsUnsupportedOperation() + { + try + { + _amqumMBean.createUser(TEST_USERNAME, "mypass", true, false, false); + fail("Exception not thrown"); + } + catch (UnsupportedOperationException uoe) + { + // PASS + } + } + + public void testSetRightsThrowsUnsupportedOperation() + { + try + { + _amqumMBean.setRights("", false, false, false); + fail("Exception not thrown"); + } + catch(UnsupportedOperationException nie) + { + // PASS + } + } + + // ============================ Utility methods ========================= + + private void loadFreshTestPasswordFile() + { + try + { + if(_passwordFile == null) + { + _passwordFile = File.createTempFile(this.getClass().getName(),".password"); + } + + BufferedWriter passwordWriter = new BufferedWriter(new FileWriter(_passwordFile, false)); + passwordWriter.write(TEST_USERNAME + ":" + TEST_PASSWORD); + passwordWriter.newLine(); + passwordWriter.flush(); + passwordWriter.close(); + _database.setPasswordFile(_passwordFile.toString()); + _amqumMBean.setPrincipalDatabase(_database); + } + catch (IOException e) + { + fail("Unable to create test password file: " + e.getMessage()); + } + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/plugins/MockPluginManager.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/plugins/MockPluginManager.java new file mode 100644 index 0000000000..a64ec5d3b1 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/plugins/MockPluginManager.java @@ -0,0 +1,56 @@ +/* + * 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.qpid.server.plugins; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.qpid.server.configuration.plugins.ConfigurationPluginFactory; +import org.apache.qpid.server.exchange.ExchangeType; +import org.apache.qpid.server.security.SecurityPluginFactory; + +public class MockPluginManager extends PluginManager +{ + private Map<String, SecurityPluginFactory> _securityPlugins = new HashMap<String, SecurityPluginFactory>(); + private Map<List<String>, ConfigurationPluginFactory> _configPlugins = new HashMap<List<String>, ConfigurationPluginFactory>(); + + public MockPluginManager(String pluginPath, String cachePath) throws Exception + { + super(pluginPath, cachePath); + } + + @Override + public Map<String, ExchangeType<?>> getExchanges() + { + return null; + } + + @Override + public Map<String, SecurityPluginFactory> getSecurityPlugins() + { + return _securityPlugins; + } + + @Override + public Map<List<String>, ConfigurationPluginFactory> getConfigurationPlugins() + { + return _configPlugins; + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/plugins/PluginTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/plugins/PluginTest.java new file mode 100644 index 0000000000..8c18ab85b0 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/plugins/PluginTest.java @@ -0,0 +1,56 @@ +/* + * 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.qpid.server.plugins; + +import org.apache.qpid.server.exchange.ExchangeType; +import org.apache.qpid.server.util.InternalBrokerBaseCase; + + +import java.util.Map; + +public class PluginTest extends InternalBrokerBaseCase +{ + private static final String TEST_EXCHANGE_CLASS = "org.apache.qpid.extras.exchanges.example.TestExchangeType"; + + private static final String PLUGIN_DIRECTORY = System.getProperty("example.plugin.target"); + private static final String CACHE_DIRECTORY = System.getProperty("example.cache.target"); + + @Override + public void configure() + { + getConfiguration().getConfig().addProperty("plugin-directory", PLUGIN_DIRECTORY); + getConfiguration().getConfig().addProperty("cache-directory", CACHE_DIRECTORY); + } + + public void disabled_testLoadExchanges() throws Exception + { + PluginManager manager = getRegistry().getPluginManager(); + Map<String, ExchangeType<?>> exchanges = manager.getExchanges(); + assertNotNull("No exchanges found in " + PLUGIN_DIRECTORY, exchanges); + assertEquals("Wrong number of exchanges found in " + PLUGIN_DIRECTORY, 2, exchanges.size()); + assertNotNull("Wrong exchange found in " + PLUGIN_DIRECTORY, exchanges.get(TEST_EXCHANGE_CLASS)); + } + + public void testNoExchanges() throws Exception + { + PluginManager manager = new PluginManager("/path/to/nowhere", "/tmp"); + Map<String, ExchangeType<?>> exchanges = manager.getExchanges(); + assertTrue("Exchanges found", exchanges.isEmpty()); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/AMQProtocolSessionMBeanTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/AMQProtocolSessionMBeanTest.java new file mode 100644 index 0000000000..4df051edb5 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/AMQProtocolSessionMBeanTest.java @@ -0,0 +1,146 @@ +/* + * + * 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.qpid.server.protocol; + +import junit.framework.TestCase; +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.management.common.mbeans.ManagedConnection; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.util.InternalBrokerBaseCase; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.SkeletonMessageStore; + +import javax.management.JMException; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.TabularData; + + +/** Test class to test MBean operations for AMQMinaProtocolSession. */ +public class AMQProtocolSessionMBeanTest extends InternalBrokerBaseCase +{ + /** Used for debugging. */ + private static final Logger log = Logger.getLogger(AMQProtocolSessionMBeanTest.class); + + private MessageStore _messageStore = new SkeletonMessageStore(); + private AMQProtocolEngine _protocolSession; + private AMQChannel _channel; + private AMQProtocolSessionMBean _mbean; + + public void testChannels() throws Exception + { + // check the channel count is correct + int channelCount = _mbean.channels().size(); + assertTrue(channelCount == 1); + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("testQueue_" + System.currentTimeMillis()), + false, + new AMQShortString("test"), + true, + false, _protocolSession.getVirtualHost(), null); + AMQChannel channel = new AMQChannel(_protocolSession, 2, _messageStore); + channel.setDefaultQueue(queue); + _protocolSession.addChannel(channel); + channelCount = _mbean.channels().size(); + assertTrue(channelCount == 2); + + // general properties test + _mbean.setMaximumNumberOfChannels(1000L); + assertTrue(_mbean.getMaximumNumberOfChannels() == 1000L); + + // check APIs + AMQChannel channel3 = new AMQChannel(_protocolSession, 3, _messageStore); + channel3.setLocalTransactional(); + _protocolSession.addChannel(channel3); + _mbean.rollbackTransactions(2); + _mbean.rollbackTransactions(3); + _mbean.commitTransactions(2); + _mbean.commitTransactions(3); + + // This should throw exception, because the channel does't exist + try + { + _mbean.commitTransactions(4); + fail(); + } + catch (JMException ex) + { + log.debug("expected exception is thrown :" + ex.getMessage()); + } + + // check channels() return type conveys flow control blocking status correctly + AMQChannel channel4 = new AMQChannel(_protocolSession, 4, _messageStore); + _protocolSession.addChannel(channel4); + channel4.setDefaultQueue(queue); + + final String blocking = ManagedConnection.FLOW_BLOCKED; + TabularData channels = _mbean.channels(); + CompositeData chan4result = channels.get(new Integer[]{4}); + assertNotNull(chan4result); + assertEquals("Flow should not have been blocked", false, chan4result.get(blocking)); + + channel4.block(queue); + channels = _mbean.channels(); + chan4result = channels.get(new Integer[]{4}); + assertNotNull(chan4result); + assertEquals("Flow should have been blocked", true, chan4result.get(blocking)); + + channel4.unblock(queue); + channels = _mbean.channels(); + chan4result = channels.get(new Integer[]{4}); + assertNotNull(chan4result); + assertEquals("Flow should have been unblocked", false, chan4result.get(blocking)); + + // check if closing of session works + _protocolSession.addChannel(new AMQChannel(_protocolSession, 5, _messageStore)); + _mbean.closeConnection(); + try + { + channelCount = _mbean.channels().size(); + assertTrue(channelCount == 0); + // session is now closed so adding another channel should throw an exception + _protocolSession.addChannel(new AMQChannel(_protocolSession, 6, _messageStore)); + fail(); + } + catch (AMQException ex) + { + log.debug("expected exception is thrown :" + ex.getMessage()); + } + } + + @Override + public void setUp() throws Exception + { + super.setUp(); + + VirtualHost vhost = ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHost("test"); + _protocolSession = new InternalTestProtocolSession(vhost); + + _channel = new AMQChannel(_protocolSession, 1, _messageStore); + _protocolSession.addChannel(_channel); + _mbean = (AMQProtocolSessionMBean) _protocolSession.getManagedObject(); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/InternalTestProtocolSession.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/InternalTestProtocolSession.java new file mode 100644 index 0000000000..3b6cd37ea9 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/InternalTestProtocolSession.java @@ -0,0 +1,213 @@ +/* + * + * 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.qpid.server.protocol; + +import java.security.Principal; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.output.ProtocolOutputConverter; +import org.apache.qpid.server.message.AMQMessage; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.state.AMQState; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.message.MessageContentSource; +import org.apache.qpid.transport.TestNetworkDriver; + +public class InternalTestProtocolSession extends AMQProtocolEngine implements ProtocolOutputConverter +{ + // ChannelID(LIST) -> LinkedList<Pair> + final Map<Integer, Map<AMQShortString, LinkedList<DeliveryPair>>> _channelDelivers; + private AtomicInteger _deliveryCount = new AtomicInteger(0); + + public InternalTestProtocolSession(VirtualHost virtualHost) throws AMQException + { + super(ApplicationRegistry.getInstance().getVirtualHostRegistry(), new TestNetworkDriver()); + + _channelDelivers = new HashMap<Integer, Map<AMQShortString, LinkedList<DeliveryPair>>>(); + + // Need to authenticate session for it to be representative testing. + setAuthorizedID(new Principal() + { + public String getName() + { + return "InternalTestProtocolSession"; + } + }); + + setVirtualHost(virtualHost); + } + + public ProtocolOutputConverter getProtocolOutputConverter() + { + return this; + } + + public byte getProtocolMajorVersion() + { + return (byte) 8; + } + + public void writeReturn(MessagePublishInfo messagePublishInfo, + ContentHeaderBody header, + MessageContentSource msgContent, + int channelId, + int replyCode, + AMQShortString replyText) throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public byte getProtocolMinorVersion() + { + return (byte) 0; + } + + // *** + + public List<DeliveryPair> getDelivers(int channelId, AMQShortString consumerTag, int count) + { + synchronized (_channelDelivers) + { + List<DeliveryPair> all =_channelDelivers.get(channelId).get(consumerTag); + + if (all == null) + { + return new ArrayList<DeliveryPair>(0); + } + + List<DeliveryPair> msgs = all.subList(0, count); + + List<DeliveryPair> response = new ArrayList<DeliveryPair>(msgs); + + //Remove the msgs from the receivedList. + msgs.clear(); + + return response; + } + } + + // *** ProtocolOutputConverter Implementation + public void writeReturn(AMQMessage message, int channelId, int replyCode, AMQShortString replyText) throws AMQException + { + } + + public void confirmConsumerAutoClose(int channelId, AMQShortString consumerTag) + { + } + + public void writeDeliver(QueueEntry entry, int channelId, long deliveryTag, AMQShortString consumerTag) throws AMQException + { + _deliveryCount.incrementAndGet(); + + synchronized (_channelDelivers) + { + Map<AMQShortString, LinkedList<DeliveryPair>> consumers = _channelDelivers.get(channelId); + + if (consumers == null) + { + consumers = new HashMap<AMQShortString, LinkedList<DeliveryPair>>(); + _channelDelivers.put(channelId, consumers); + } + + LinkedList<DeliveryPair> consumerDelivers = consumers.get(consumerTag); + + if (consumerDelivers == null) + { + consumerDelivers = new LinkedList<DeliveryPair>(); + consumers.put(consumerTag, consumerDelivers); + } + + consumerDelivers.add(new DeliveryPair(deliveryTag, (AMQMessage)entry.getMessage())); + } + } + + public void writeGetOk(QueueEntry message, int channelId, long deliveryTag, int queueSize) throws AMQException + { + } + + public void awaitDelivery(int msgs) + { + while (msgs > _deliveryCount.get()) + { + try + { + Thread.sleep(100); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + } + } + + public class DeliveryPair + { + private long _deliveryTag; + private AMQMessage _message; + + public DeliveryPair(long deliveryTag, AMQMessage message) + { + _deliveryTag = deliveryTag; + _message = message; + } + + public AMQMessage getMessage() + { + return _message; + } + + public long getDeliveryTag() + { + return _deliveryTag; + } + } + + public boolean isClosed() + { + return _closed; + } + + public void closeProtocolSession(boolean waitLast) + { + // Override as we don't have a real IOSession to close. + // The alternative is to fully implement the TestIOSession to return a CloseFuture from close(); + // Then the AMQMinaProtocolSession can join on the returning future without a NPE. + } + + public void closeSession(AMQSessionModel session, AMQConstant cause, String message) throws AMQException + { + super.closeSession(session, cause, message); + + //Simulate the Client responding with a CloseOK + // should really update the StateManger but we don't have access here + // changeState(AMQState.CONNECTION_CLOSED); + ((AMQChannel)session).getProtocolSession().closeSession(); + + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/MaxChannelsTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/MaxChannelsTest.java new file mode 100644 index 0000000000..f6e83e6369 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/MaxChannelsTest.java @@ -0,0 +1,79 @@ +/* + * + * 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.qpid.server.protocol; + +import junit.framework.TestCase; +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.util.InternalBrokerBaseCase; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.registry.ApplicationRegistry; + +/** Test class to test MBean operations for AMQMinaProtocolSession. */ +public class MaxChannelsTest extends InternalBrokerBaseCase +{ + private AMQProtocolEngine _session; + + public void testChannels() throws Exception + { + VirtualHost vhost = ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHost("test"); + _session = new InternalTestProtocolSession(vhost); + + // check the channel count is correct + int channelCount = _session.getChannels().size(); + assertEquals("Initial channel count wrong", 0, channelCount); + + long maxChannels = 10L; + _session.setMaximumNumberOfChannels(maxChannels); + assertEquals("Number of channels not correctly set.", new Long(maxChannels), _session.getMaximumNumberOfChannels()); + + + try + { + for (long currentChannel = 0L; currentChannel < maxChannels; currentChannel++) + { + _session.addChannel(new AMQChannel(_session, (int) currentChannel, null)); + } + } + catch (AMQException e) + { + assertEquals("Wrong exception recevied.", e.getErrorCode(), AMQConstant.NOT_ALLOWED); + } + assertEquals("Maximum number of channels not set.", new Long(maxChannels), new Long(_session.getChannels().size())); + } + + @Override + public void tearDown() throws Exception + { + try { + _session.closeSession(); + } catch (AMQException e) { + // Yikes + fail(e.getMessage()); + } + finally + { + super.tearDown(); + } + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQPriorityQueueTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQPriorityQueueTest.java new file mode 100644 index 0000000000..3961b3b355 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQPriorityQueueTest.java @@ -0,0 +1,108 @@ +package org.apache.qpid.server.queue; +/* + * + * 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. + * + */ + +import java.util.ArrayList; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.message.AMQMessage; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.FieldTable; +import junit.framework.AssertionFailedError; + +public class AMQPriorityQueueTest extends SimpleAMQQueueTest +{ + + @Override + public void setUp() throws Exception + { + _arguments = new FieldTable(); + _arguments.put(AMQQueueFactory.X_QPID_PRIORITIES, 3); + super.setUp(); + } + + public void testPriorityOrdering() throws AMQException, InterruptedException + { + + // Enqueue messages in order + _queue.enqueue(createMessage(1L, (byte) 10)); + _queue.enqueue(createMessage(2L, (byte) 4)); + _queue.enqueue(createMessage(3L, (byte) 0)); + + // Enqueue messages in reverse order + _queue.enqueue(createMessage(4L, (byte) 0)); + _queue.enqueue(createMessage(5L, (byte) 4)); + _queue.enqueue(createMessage(6L, (byte) 10)); + + // Enqueue messages out of order + _queue.enqueue(createMessage(7L, (byte) 4)); + _queue.enqueue(createMessage(8L, (byte) 10)); + _queue.enqueue(createMessage(9L, (byte) 0)); + + // Register subscriber + _queue.registerSubscription(_subscription, false); + Thread.sleep(150); + + ArrayList<QueueEntry> msgs = _subscription.getMessages(); + try + { + assertEquals(new Long(1L), msgs.get(0).getMessage().getMessageNumber()); + assertEquals(new Long(6L), msgs.get(1).getMessage().getMessageNumber()); + assertEquals(new Long(8L), msgs.get(2).getMessage().getMessageNumber()); + + assertEquals(new Long(2L), msgs.get(3).getMessage().getMessageNumber()); + assertEquals(new Long(5L), msgs.get(4).getMessage().getMessageNumber()); + assertEquals(new Long(7L), msgs.get(5).getMessage().getMessageNumber()); + + assertEquals(new Long(3L), msgs.get(6).getMessage().getMessageNumber()); + assertEquals(new Long(4L), msgs.get(7).getMessage().getMessageNumber()); + assertEquals(new Long(9L), msgs.get(8).getMessage().getMessageNumber()); + } + catch (AssertionFailedError afe) + { + // Show message order on failure. + int index = 1; + for (QueueEntry qe : msgs) + { + System.err.println(index + ":" + qe.getMessage().getMessageNumber()); + index++; + } + + throw afe; + } + + } + + protected AMQMessage createMessage(Long id, byte i) throws AMQException + { + AMQMessage msg = super.createMessage(id); + BasicContentHeaderProperties props = new BasicContentHeaderProperties(); + props.setPriority(i); + msg.getContentHeaderBody().setProperties(props); + return msg; + } + + protected AMQMessage createMessage(Long id) throws AMQException + { + return createMessage(id, (byte) 0); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueAlertTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueAlertTest.java new file mode 100644 index 0000000000..a8bddcf6bf --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueAlertTest.java @@ -0,0 +1,350 @@ +/* + * + * 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.qpid.server.queue; + +import org.apache.mina.common.ByteBuffer; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.abstraction.ContentChunk; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.message.AMQMessage; +import org.apache.qpid.server.message.MessageMetaData; +import org.apache.qpid.server.protocol.InternalTestProtocolSession; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.subscription.SubscriptionFactoryImpl; +import org.apache.qpid.server.util.InternalBrokerBaseCase; + +import javax.management.Notification; +import java.util.ArrayList; + +/** This class tests all the alerts an AMQQueue can throw based on threshold values of different parameters */ +public class AMQQueueAlertTest extends InternalBrokerBaseCase +{ + private final static long MAX_MESSAGE_COUNT = 50; + private final static long MAX_MESSAGE_AGE = 250; // 0.25 sec + private final static long MAX_MESSAGE_SIZE = 2000; // 2 KB + private final static long MAX_QUEUE_DEPTH = 10000; // 10 KB + private AMQQueueMBean _queueMBean; + private static final SubscriptionFactoryImpl SUBSCRIPTION_FACTORY = SubscriptionFactoryImpl.INSTANCE; + + /** + * Tests if the alert gets thrown when message count increases the threshold limit + * + * @throws Exception + */ + public void testMessageCountAlert() throws Exception + { + setSession(new InternalTestProtocolSession(getVirtualHost())); + AMQChannel channel = new AMQChannel(getSession(), 2, getMessageStore()); + getSession().addChannel(channel); + + setQueue(AMQQueueFactory.createAMQQueueImpl(new AMQShortString("testQueue1"), false, new AMQShortString("AMQueueAlertTest"), + false, false, + getVirtualHost(), null)); + _queueMBean = (AMQQueueMBean) getQueue().getManagedObject(); + + _queueMBean.setMaximumMessageCount(MAX_MESSAGE_COUNT); + + sendMessages(channel, MAX_MESSAGE_COUNT, 256l); + assertTrue(_queueMBean.getMessageCount() == MAX_MESSAGE_COUNT); + + Notification lastNotification = _queueMBean.getLastNotification(); + assertNotNull(lastNotification); + + String notificationMsg = lastNotification.getMessage(); + assertTrue(notificationMsg.startsWith(NotificationCheck.MESSAGE_COUNT_ALERT.name())); + } + + /** + * Tests if the Message Size alert gets thrown when message of higher than threshold limit is sent + * + * @throws Exception + */ + public void testMessageSizeAlert() throws Exception + { + setSession(new InternalTestProtocolSession(getVirtualHost())); + AMQChannel channel = new AMQChannel(getSession(), 2, getMessageStore()); + getSession().addChannel(channel); + + setQueue(AMQQueueFactory.createAMQQueueImpl(new AMQShortString("testQueue2"), false, new AMQShortString("AMQueueAlertTest"), + false, false, + getVirtualHost(), null)); + _queueMBean = (AMQQueueMBean) getQueue().getManagedObject(); + _queueMBean.setMaximumMessageCount(MAX_MESSAGE_COUNT); + _queueMBean.setMaximumMessageSize(MAX_MESSAGE_SIZE); + + sendMessages(channel, 1, MAX_MESSAGE_SIZE * 2); + assertTrue(_queueMBean.getMessageCount() == 1); + + Notification lastNotification = _queueMBean.getLastNotification(); + assertNotNull(lastNotification); + + String notificationMsg = lastNotification.getMessage(); + assertTrue(notificationMsg.startsWith(NotificationCheck.MESSAGE_SIZE_ALERT.name())); + } + + /** + * Tests if Queue Depth alert is thrown when queue depth reaches the threshold value + * + * Based on FT-402 subbmitted by client + * + * @throws Exception + */ + public void testQueueDepthAlertNoSubscriber() throws Exception + { + setSession(new InternalTestProtocolSession(getVirtualHost())); + AMQChannel channel = new AMQChannel(getSession(), 2, getMessageStore()); + getSession().addChannel(channel); + + setQueue(AMQQueueFactory.createAMQQueueImpl(new AMQShortString("testQueue3"), false, new AMQShortString("AMQueueAlertTest"), + false, false, + getVirtualHost(), null)); + _queueMBean = (AMQQueueMBean) getQueue().getManagedObject(); + _queueMBean.setMaximumMessageCount(MAX_MESSAGE_COUNT); + _queueMBean.setMaximumQueueDepth(MAX_QUEUE_DEPTH); + + while (getQueue().getQueueDepth() < MAX_QUEUE_DEPTH) + { + sendMessages(channel, 1, MAX_MESSAGE_SIZE); + } + + Notification lastNotification = _queueMBean.getLastNotification(); + assertNotNull(lastNotification); + + String notificationMsg = lastNotification.getMessage(); + assertTrue(notificationMsg.startsWith(NotificationCheck.QUEUE_DEPTH_ALERT.name())); + } + + /** + * Tests if MESSAGE AGE alert is thrown, when a message is in the queue for time higher than threshold value of + * message age + * + * Alternative test to FT-401 provided by client + * + * @throws Exception + */ + public void testMessageAgeAlert() throws Exception + { + setSession(new InternalTestProtocolSession(getVirtualHost())); + AMQChannel channel = new AMQChannel(getSession(), 2, getMessageStore()); + getSession().addChannel(channel); + + setQueue(AMQQueueFactory.createAMQQueueImpl(new AMQShortString("testQueue4"), false, new AMQShortString("AMQueueAlertTest"), + false, false, + getVirtualHost(), null)); + _queueMBean = (AMQQueueMBean) getQueue().getManagedObject(); + _queueMBean.setMaximumMessageCount(MAX_MESSAGE_COUNT); + _queueMBean.setMaximumMessageAge(MAX_MESSAGE_AGE); + + sendMessages(channel, 1, MAX_MESSAGE_SIZE); + + // Ensure message sits on queue long enough to age. + Thread.sleep(MAX_MESSAGE_AGE * 2); + + Notification lastNotification = _queueMBean.getLastNotification(); + assertNotNull("Last notification was null", lastNotification); + + String notificationMsg = lastNotification.getMessage(); + assertTrue(notificationMsg.startsWith(NotificationCheck.MESSAGE_AGE_ALERT.name())); + } + + /* + This test sends some messages to the queue with subscribers needing message to be acknowledged. + The messages will not be acknowledged and will be required twice. Why we are checking this is because + the bug reported said that the queueDepth keeps increasing when messages are requeued. + // TODO - queue depth now includes unacknowledged messages so does not go down when messages are delivered + + The QueueDepth should decrease when messages are delivered from the queue (QPID-408) + */ + public void testQueueDepthAlertWithSubscribers() throws Exception + { + AMQChannel channel = new AMQChannel(getSession(), 2, getMessageStore()); + getSession().addChannel(channel); + + // Create queue + setQueue(getNewQueue()); + Subscription subscription = + SUBSCRIPTION_FACTORY.createSubscription(channel.getChannelId(), getSession(), new AMQShortString("consumer_tag"), true, null, false, channel.getCreditManager()); + + getQueue().registerSubscription( + subscription, false); + + _queueMBean = (AMQQueueMBean) getQueue().getManagedObject(); + _queueMBean.setMaximumMessageCount(9999l); // Set a high value, because this is not being tested + _queueMBean.setMaximumQueueDepth(MAX_QUEUE_DEPTH); + + // Send messages(no of message to be little more than what can cause a Queue_Depth alert) + int messageCount = Math.round(MAX_QUEUE_DEPTH / MAX_MESSAGE_SIZE) + 10; + long totalSize = (messageCount * MAX_MESSAGE_SIZE); + sendMessages(channel, messageCount, MAX_MESSAGE_SIZE); + + // Check queueDepth. There should be no messages on the queue and as the subscriber is listening + // so there should be no Queue_Deoth alert raised + assertEquals(new Long(totalSize), new Long(_queueMBean.getQueueDepth())); + Notification lastNotification = _queueMBean.getLastNotification(); +// assertNull(lastNotification); + + // Kill the subscriber and check for the queue depth values. + // Messages are unacknowledged, so those should get requeued. All messages should be on the Queue + getQueue().unregisterSubscription(subscription); + channel.requeue(); + + assertEquals(new Long(totalSize), new Long(_queueMBean.getQueueDepth())); + + lastNotification = _queueMBean.getLastNotification(); + assertNotNull(lastNotification); + String notificationMsg = lastNotification.getMessage(); + assertTrue(notificationMsg.startsWith(NotificationCheck.QUEUE_DEPTH_ALERT.name())); + + // Connect a consumer again and check QueueDepth values. The queue should get emptied. + // Messages will get delivered but still are unacknowledged. + Subscription subscription2 = + SUBSCRIPTION_FACTORY.createSubscription(channel.getChannelId(), getSession(), new AMQShortString("consumer_tag"), true, null, false, channel.getCreditManager()); + + getQueue().registerSubscription( + subscription2, false); + + while (getQueue().getUndeliveredMessageCount()!= 0) + { + Thread.sleep(100); + } +// assertEquals(new Long(0), new Long(_queueMBean.getQueueDepth())); + + // Kill the subscriber again. Now those messages should get requeued again. Check if the queue depth + // value is correct. + getQueue().unregisterSubscription(subscription2); + channel.requeue(); + + assertEquals(new Long(totalSize), new Long(_queueMBean.getQueueDepth())); + getSession().closeSession(); + + // Check the clear queue + _queueMBean.clearQueue(); + assertEquals(new Long(0), new Long(_queueMBean.getQueueDepth())); + } + + protected IncomingMessage message(final boolean immediate, long size) throws AMQException + { + MessagePublishInfo publish = new MessagePublishInfo() + { + + public AMQShortString getExchange() + { + return null; + } + + public void setExchange(AMQShortString exchange) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isImmediate() + { + return immediate; + } + + public boolean isMandatory() + { + return false; + } + + public AMQShortString getRoutingKey() + { + return null; + } + }; + + ContentHeaderBody contentHeaderBody = new ContentHeaderBody(); + BasicContentHeaderProperties props = new BasicContentHeaderProperties(); + contentHeaderBody.setProperties(props); + contentHeaderBody.bodySize = size; // in bytes + IncomingMessage message = new IncomingMessage(publish); + message.setContentHeaderBody(contentHeaderBody); + + return message; + } + + @Override + protected void configure() + { + // Increase Alert Check period + getConfiguration().setHousekeepingExpiredMessageCheckPeriod(200); + } + + private void sendMessages(AMQChannel channel, long messageCount, final long size) throws AMQException + { + IncomingMessage[] messages = new IncomingMessage[(int) messageCount]; + MessageMetaData[] metaData = new MessageMetaData[(int) messageCount]; + for (int i = 0; i < messages.length; i++) + { + messages[i] = message(false, size); + ArrayList<AMQQueue> qs = new ArrayList<AMQQueue>(); + qs.add(getQueue()); + metaData[i] = messages[i].headersReceived(); + messages[i].setStoredMessage(getMessageStore().addMessage(metaData[i])); + + messages[i].enqueue(qs); + + } + + for (int i = 0; i < messageCount; i++) + { + messages[i].addContentBodyFrame(new ContentChunk(){ + + ByteBuffer _data = ByteBuffer.allocate((int)size); + + { + _data.limit((int)size); + } + + public int getSize() + { + return (int) size; + } + + public ByteBuffer getData() + { + return _data; + } + + public void reduceToFit() + { + + } + }); + + getQueue().enqueue(new AMQMessage(messages[i].getStoredMessage())); + + } + } + + private AMQQueue getNewQueue() throws AMQException + { + return AMQQueueFactory.createAMQQueueImpl(new AMQShortString("testQueue" + Math.random()), + false, + new AMQShortString("AMQueueAlertTest"), + false, + false, getVirtualHost(), null); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueFactoryTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueFactoryTest.java new file mode 100644 index 0000000000..27891289fb --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueFactoryTest.java @@ -0,0 +1,75 @@ +/* + * + * 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.qpid.server.queue; + +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.util.InternalBrokerBaseCase; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; + +public class AMQQueueFactoryTest extends InternalBrokerBaseCase +{ + QueueRegistry _queueRegistry; + VirtualHost _virtualHost; + int _defaultQueueCount; + + @Override + public void setUp() throws Exception + { + super.setUp(); + ApplicationRegistry registry = (ApplicationRegistry) ApplicationRegistry.getInstance(); + + _virtualHost = registry.getVirtualHostRegistry().getVirtualHost("test"); + + _queueRegistry = _virtualHost.getQueueRegistry(); + + _defaultQueueCount = _queueRegistry.getQueues().size(); + } + + @Override + public void tearDown() throws Exception + { + assertEquals("Queue was not registered in virtualhost", _defaultQueueCount + 1, _queueRegistry.getQueues().size()); + super.tearDown(); + } + + + public void testPriorityQueueRegistration() throws Exception + { + FieldTable fieldTable = new FieldTable(); + fieldTable.put(new AMQShortString(AMQQueueFactory.X_QPID_PRIORITIES), 5); + + + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("testPriorityQueue"), false, new AMQShortString("owner"), false, + false, _virtualHost, fieldTable); + + assertEquals("Queue not a priorty queue", AMQPriorityQueue.class, queue.getClass()); + } + + + public void testSimpleQueueRegistration() throws Exception + { + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("testQueue"), false, new AMQShortString("owner"), false, + false, _virtualHost, null); + assertEquals("Queue not a simple queue", SimpleAMQQueue.class, queue.getClass()); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueMBeanTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueMBeanTest.java new file mode 100644 index 0000000000..365353e734 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueMBeanTest.java @@ -0,0 +1,456 @@ +/* + * + * 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.qpid.server.queue; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.framing.abstraction.ContentChunk; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.util.InternalBrokerBaseCase; +import org.apache.qpid.server.message.AMQMessage; +import org.apache.qpid.server.message.MessageMetaData; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.subscription.SubscriptionFactory; +import org.apache.qpid.server.subscription.SubscriptionFactoryImpl; +import org.apache.qpid.server.protocol.InternalTestProtocolSession; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.store.TestableMemoryMessageStore; +import org.apache.mina.common.ByteBuffer; + +import javax.management.JMException; + +import java.util.ArrayList; + +/** + * Test class to test AMQQueueMBean attribtues and operations + */ +public class AMQQueueMBeanTest extends InternalBrokerBaseCase +{ + private static long MESSAGE_SIZE = 1000; + private AMQQueueMBean _queueMBean; + private static final SubscriptionFactoryImpl SUBSCRIPTION_FACTORY = SubscriptionFactoryImpl.INSTANCE; + + public void testMessageCountTransient() throws Exception + { + int messageCount = 10; + sendMessages(messageCount, false); + assertTrue(_queueMBean.getMessageCount() == messageCount); + assertTrue(_queueMBean.getReceivedMessageCount() == messageCount); + long queueDepth = (messageCount * MESSAGE_SIZE); + assertTrue(_queueMBean.getQueueDepth() == queueDepth); + + _queueMBean.deleteMessageFromTop(); + assertTrue(_queueMBean.getMessageCount() == (messageCount - 1)); + assertTrue(_queueMBean.getReceivedMessageCount() == messageCount); + + _queueMBean.clearQueue(); + assertEquals(0,(int)_queueMBean.getMessageCount()); + assertTrue(_queueMBean.getReceivedMessageCount() == messageCount); + + //Ensure that the data has been removed from the Store + verifyBrokerState(); + } + + public void testMessageCountPersistent() throws Exception + { + int messageCount = 10; + sendMessages(messageCount, true); + assertEquals("", messageCount, _queueMBean.getMessageCount().intValue()); + assertTrue(_queueMBean.getReceivedMessageCount() == messageCount); + long queueDepth = (messageCount * MESSAGE_SIZE); + assertTrue(_queueMBean.getQueueDepth() == queueDepth); + + _queueMBean.deleteMessageFromTop(); + assertTrue(_queueMBean.getMessageCount() == (messageCount - 1)); + assertTrue(_queueMBean.getReceivedMessageCount() == messageCount); + + _queueMBean.clearQueue(); + assertTrue(_queueMBean.getMessageCount() == 0); + assertTrue(_queueMBean.getReceivedMessageCount() == messageCount); + + //Ensure that the data has been removed from the Store + verifyBrokerState(); + } + + public void testDeleteMessages() throws Exception + { + int messageCount = 10; + sendMessages(messageCount, true); + assertEquals("", messageCount, _queueMBean.getMessageCount().intValue()); + assertTrue(_queueMBean.getReceivedMessageCount() == messageCount); + long queueDepth = (messageCount * MESSAGE_SIZE); + assertTrue(_queueMBean.getQueueDepth() == queueDepth); + + //delete first message + _queueMBean.deleteMessages(1L,1L); + assertTrue(_queueMBean.getMessageCount() == (messageCount - 1)); + assertTrue(_queueMBean.getReceivedMessageCount() == messageCount); + try + { + _queueMBean.viewMessageContent(1L); + fail("Message should no longer be on the queue"); + } + catch(Exception e) + { + + } + + //delete last message, leaving 2nd to 9th + _queueMBean.deleteMessages(10L,10L); + assertTrue(_queueMBean.getMessageCount() == (messageCount - 2)); + assertTrue(_queueMBean.getReceivedMessageCount() == messageCount); + try + { + _queueMBean.viewMessageContent(10L); + fail("Message should no longer be on the queue"); + } + catch(Exception e) + { + + } + + //delete remaining messages, leaving none + _queueMBean.deleteMessages(2L,9L); + assertTrue(_queueMBean.getMessageCount() == (0)); + assertTrue(_queueMBean.getReceivedMessageCount() == messageCount); + + //Ensure that the data has been removed from the Store + verifyBrokerState(); + } + + // todo: collect to a general testing class -duplicated from Systest/MessageReturntest + private void verifyBrokerState() + { + + TestableMemoryMessageStore store = (TestableMemoryMessageStore) getVirtualHost().getMessageStore(); + + // Unlike MessageReturnTest there is no need for a delay as there this thread does the clean up. + + assertEquals("Store should have no messages:" + store.getMessageCount(), 0, store.getMessageCount()); + } + + public void testConsumerCount() throws AMQException + { + + assertTrue(getQueue().getActiveConsumerCount() == 0); + assertTrue(_queueMBean.getActiveConsumerCount() == 0); + + + InternalTestProtocolSession protocolSession = new InternalTestProtocolSession(getVirtualHost()); + + AMQChannel channel = new AMQChannel(protocolSession, 1, getMessageStore()); + protocolSession.addChannel(channel); + + Subscription subscription = + SUBSCRIPTION_FACTORY.createSubscription(channel.getChannelId(), protocolSession, new AMQShortString("test"), false, null, false, channel.getCreditManager()); + + getQueue().registerSubscription(subscription, false); + assertEquals(1,(int)_queueMBean.getActiveConsumerCount()); + + + SubscriptionFactory subscriptionFactory = SUBSCRIPTION_FACTORY; + Subscription s1 = subscriptionFactory.createSubscription(channel.getChannelId(), + protocolSession, + new AMQShortString("S1"), + false, + null, + true, + channel.getCreditManager()); + + Subscription s2 = subscriptionFactory.createSubscription(channel.getChannelId(), + protocolSession, + new AMQShortString("S2"), + false, + null, + true, + channel.getCreditManager()); + getQueue().registerSubscription(s1,false); + getQueue().registerSubscription(s2,false); + assertTrue(_queueMBean.getActiveConsumerCount() == 3); + assertTrue(_queueMBean.getConsumerCount() == 3); + + s1.close(); + assertEquals(2, (int) _queueMBean.getActiveConsumerCount()); + assertTrue(_queueMBean.getConsumerCount() == 3); + } + + public void testGeneralProperties() throws Exception + { + long maxQueueDepth = 1000; // in bytes + _queueMBean.setMaximumMessageCount(50000l); + _queueMBean.setMaximumMessageSize(2000l); + _queueMBean.setMaximumQueueDepth(maxQueueDepth); + + assertEquals("Max MessageCount not set",50000,_queueMBean.getMaximumMessageCount().longValue()); + assertEquals("Max MessageSize not set",2000, _queueMBean.getMaximumMessageSize().longValue()); + assertEquals("Max QueueDepth not set",maxQueueDepth, _queueMBean.getMaximumQueueDepth().longValue()); + + assertEquals("Queue Name does not match", new AMQShortString(getName()), _queueMBean.getName()); + assertFalse("AutoDelete should not be set.",_queueMBean.isAutoDelete()); + assertFalse("Queue should not be durable.",_queueMBean.isDurable()); + + //set+get exclusivity using the mbean, and also verify it is actually updated in the queue + _queueMBean.setExclusive(true); + assertTrue("Exclusive property should be true.",_queueMBean.isExclusive()); + assertTrue("Exclusive property should be true.", getQueue().isExclusive()); + _queueMBean.setExclusive(false); + assertFalse("Exclusive property should be false.",_queueMBean.isExclusive()); + assertFalse("Exclusive property should be false.", getQueue().isExclusive()); + } + + public void testExceptions() throws Exception + { + try + { + _queueMBean.viewMessages(0L, 3L); + fail(); + } + catch (JMException ex) + { + + } + + try + { + _queueMBean.viewMessages(2L, 1L); + fail(); + } + catch (JMException ex) + { + + } + + try + { + _queueMBean.viewMessages(-1L, 1L); + fail(); + } + catch (JMException ex) + { + + } + + try + { + long end = Integer.MAX_VALUE; + end+=2; + _queueMBean.viewMessages(1L, end); + fail("Expected Exception due to oversized(> 2^31) message range"); + } + catch (JMException ex) + { + + } + + IncomingMessage msg = message(false, false); + getQueue().clearQueue(); + ArrayList<AMQQueue> qs = new ArrayList<AMQQueue>(); + qs.add(getQueue()); + msg.enqueue(qs); + MessageMetaData mmd = msg.headersReceived(); + msg.setStoredMessage(getMessageStore().addMessage(mmd)); + long id = msg.getMessageNumber(); + + msg.addContentBodyFrame(new ContentChunk() + { + ByteBuffer _data = ByteBuffer.allocate((int)MESSAGE_SIZE); + + { + _data.limit((int)MESSAGE_SIZE); + } + + public int getSize() + { + return (int) MESSAGE_SIZE; + } + + public ByteBuffer getData() + { + return _data; + } + + public void reduceToFit() + { + + } + }); + + AMQMessage m = new AMQMessage(msg.getStoredMessage()); + for(BaseQueue q : msg.getDestinationQueues()) + { + q.enqueue(m); + } +// _queue.process(_storeContext, new QueueEntry(_queue, msg), false); + _queueMBean.viewMessageContent(id); + try + { + _queueMBean.viewMessageContent(id + 1); + fail(); + } + catch (JMException ex) + { + + } + } + + public void testFlowControlProperties() throws Exception + { + assertTrue(_queueMBean.getCapacity() == 0); + assertTrue(_queueMBean.getFlowResumeCapacity() == 0); + assertFalse(_queueMBean.isFlowOverfull()); + + //capacity currently 0, try setting FlowResumeCapacity above this + try + { + _queueMBean.setFlowResumeCapacity(1L); + fail("Should have failed to allow setting FlowResumeCapacity above Capacity"); + } + catch (IllegalArgumentException ex) + { + //expected exception + assertTrue(_queueMBean.getFlowResumeCapacity() == 0); + } + + //add a message to the queue + sendMessages(1, true); + + //(FlowResume)Capacity currently 0, set both to 2 + _queueMBean.setCapacity(2L); + assertTrue(_queueMBean.getCapacity() == 2L); + _queueMBean.setFlowResumeCapacity(2L); + assertTrue(_queueMBean.getFlowResumeCapacity() == 2L); + + //Try setting Capacity below FlowResumeCapacity + try + { + _queueMBean.setCapacity(1L); + fail("Should have failed to allow setting Capacity below FlowResumeCapacity"); + } + catch (IllegalArgumentException ex) + { + //expected exception + assertTrue(_queueMBean.getCapacity() == 2); + } + + //create a channel and use it to exercise the capacity check mechanism + AMQChannel channel = new AMQChannel(getSession(), 1, getMessageStore()); + getQueue().checkCapacity(channel); + + assertTrue(_queueMBean.isFlowOverfull()); + assertTrue(channel.getBlocking()); + + //set FlowResumeCapacity to MESSAGE_SIZE and check queue is now underfull and channel unblocked + _queueMBean.setCapacity(MESSAGE_SIZE);//must increase capacity too + _queueMBean.setFlowResumeCapacity(MESSAGE_SIZE); + + assertFalse(_queueMBean.isFlowOverfull()); + assertFalse(channel.getBlocking()); + } + + private IncomingMessage message(final boolean immediate, boolean persistent) throws AMQException + { + MessagePublishInfo publish = new MessagePublishInfo() + { + + public AMQShortString getExchange() + { + return null; + } + + public void setExchange(AMQShortString exchange) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isImmediate() + { + return immediate; + } + + public boolean isMandatory() + { + return false; + } + + public AMQShortString getRoutingKey() + { + return null; + } + }; + + ContentHeaderBody contentHeaderBody = new ContentHeaderBody(); + contentHeaderBody.bodySize = MESSAGE_SIZE; // in bytes + contentHeaderBody.setProperties(new BasicContentHeaderProperties()); + ((BasicContentHeaderProperties) contentHeaderBody.getProperties()).setDeliveryMode((byte) (persistent ? 2 : 1)); + IncomingMessage msg = new IncomingMessage(publish); + msg.setContentHeaderBody(contentHeaderBody); + return msg; + + } + + @Override + public void setUp() throws Exception + { + super.setUp(); + + _queueMBean = new AMQQueueMBean(getQueue()); + } + + public void tearDown() + { + ApplicationRegistry.remove(); + } + + private void sendMessages(int messageCount, boolean persistent) throws AMQException + { + for (int i = 0; i < messageCount; i++) + { + IncomingMessage currentMessage = message(false, persistent); + ArrayList<AMQQueue> qs = new ArrayList<AMQQueue>(); + qs.add(getQueue()); + currentMessage.enqueue(qs); + + // route header + MessageMetaData mmd = currentMessage.headersReceived(); + currentMessage.setStoredMessage(getMessageStore().addMessage(mmd)); + + // Add the body so we have somthing to test later + currentMessage.addContentBodyFrame( + getSession().getMethodRegistry() + .getProtocolVersionMethodConverter() + .convertToContentChunk( + new ContentBody(ByteBuffer.allocate((int) MESSAGE_SIZE), + MESSAGE_SIZE))); + + AMQMessage m = new AMQMessage(currentMessage.getStoredMessage()); + for(BaseQueue q : currentMessage.getDestinationQueues()) + { + q.enqueue(m); + } + + + } + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AckTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AckTest.java new file mode 100644 index 0000000000..0f5374b3e5 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AckTest.java @@ -0,0 +1,427 @@ +/* + * + * 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.qpid.server.queue; + +import junit.framework.TestCase; +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.message.AMQMessage; +import org.apache.qpid.server.message.MessageMetaData; +import org.apache.qpid.server.txn.ServerTransaction; +import org.apache.qpid.server.txn.AutoCommitTransaction; +import org.apache.qpid.server.util.InternalBrokerBaseCase; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.protocol.InternalTestProtocolSession; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.subscription.SubscriptionFactoryImpl; +import org.apache.qpid.server.flow.LimitlessCreditManager; +import org.apache.qpid.server.flow.Pre0_10CreditManager; +import org.apache.qpid.server.ack.UnacknowledgedMessageMap; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.store.TestMemoryMessageStore; + +import java.util.ArrayList; +import java.util.Set; + +/** + * Tests that acknowledgements are handled correctly. + */ +public class AckTest extends InternalBrokerBaseCase +{ + private static final Logger _log = Logger.getLogger(AckTest.class); + + private Subscription _subscription; + + private AMQProtocolSession _protocolSession; + + private TestMemoryMessageStore _messageStore; + + private AMQChannel _channel; + + private AMQQueue _queue; + + private static final AMQShortString DEFAULT_CONSUMER_TAG = new AMQShortString("conTag"); + private VirtualHost _virtualHost; + + @Override + public void setUp() throws Exception + { + super.setUp(); + _virtualHost = ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHost("test"); + _messageStore = new TestMemoryMessageStore(); + _protocolSession = new InternalTestProtocolSession(_virtualHost); + _channel = new AMQChannel(_protocolSession,5, _messageStore /*dont need exchange registry*/); + + _protocolSession.addChannel(_channel); + + _queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("myQ"), false, new AMQShortString("guest"), true, false, + _virtualHost, null); + } + + private void publishMessages(int count) throws AMQException + { + publishMessages(count, false); + } + + private void publishMessages(int count, boolean persistent) throws AMQException + { + _queue.registerSubscription(_subscription,false); + for (int i = 1; i <= count; i++) + { + // AMQP version change: Hardwire the version to 0-8 (major=8, minor=0) + // TODO: Establish some way to determine the version for the test. + MessagePublishInfo publishBody = new MessagePublishInfo() + { + + public AMQShortString getExchange() + { + return new AMQShortString("someExchange"); + } + + public void setExchange(AMQShortString exchange) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isImmediate() + { + return false; + } + + public boolean isMandatory() + { + return false; + } + + public AMQShortString getRoutingKey() + { + return new AMQShortString("rk"); + } + }; + final IncomingMessage msg = new IncomingMessage(publishBody); + //IncomingMessage msg2 = null; + BasicContentHeaderProperties b = new BasicContentHeaderProperties(); + ContentHeaderBody cb = new ContentHeaderBody(); + cb.setProperties(b); + + if (persistent) + { + //This is DeliveryMode.PERSISTENT + b.setDeliveryMode((byte) 2); + } + + msg.setContentHeaderBody(cb); + + // we increment the reference here since we are not delivering the messaging to any queues, which is where + // the reference is normally incremented. The test is easier to construct if we have direct access to the + // subscription + ArrayList<AMQQueue> qs = new ArrayList<AMQQueue>(); + qs.add(_queue); + msg.enqueue(qs); + MessageMetaData mmd = msg.headersReceived(); + msg.setStoredMessage(_messageStore.addMessage(mmd)); + if(msg.allContentReceived()) + { + ServerTransaction txn = new AutoCommitTransaction(_messageStore); + txn.enqueue(_queue, msg, new ServerTransaction.Action() { + public void postCommit() + { + try + { + _queue.enqueue(new AMQMessage(msg.getStoredMessage())); + } + catch (AMQException e) + { + throw new RuntimeException(e); + } + } + + public void onRollback() + { + //To change body of implemented methods use File | Settings | File Templates. + } + }); + + } + // we manually send the message to the subscription + //_subscription.send(new QueueEntry(_queue,msg), _queue); + } + } + + /** + * Tests that the acknowledgements are correctly associated with a channel and + * order is preserved when acks are enabled + */ + public void testAckChannelAssociationTest() throws AMQException + { + _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, true, null, false, new LimitlessCreditManager()); + final int msgCount = 10; + publishMessages(msgCount, true); + + UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap(); + assertEquals("",msgCount,map.size()); + + Set<Long> deliveryTagSet = map.getDeliveryTags(); + int i = 1; + for (long deliveryTag : deliveryTagSet) + { + assertTrue(deliveryTag == i); + i++; + QueueEntry unackedMsg = map.get(deliveryTag); + assertTrue(unackedMsg.getQueue() == _queue); + } + + } + + /** + * Tests that in no-ack mode no messages are retained + */ + public void testNoAckMode() throws AMQException + { + // false arg means no acks expected + _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, false, null, false, new LimitlessCreditManager()); + final int msgCount = 10; + publishMessages(msgCount); + + UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == 0); + assertTrue(_messageStore.getMessageCount() == 0); + + + } + + /** + * Tests that in no-ack mode no messages are retained + */ + public void testPersistentNoAckMode() throws AMQException + { + // false arg means no acks expected + _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, false,null,false, new LimitlessCreditManager()); + final int msgCount = 10; + publishMessages(msgCount, true); + + UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == 0); + assertTrue(_messageStore.getMessageCount() == 0); + + + } + + /** + * Tests that a single acknowledgement is handled correctly (i.e multiple flag not + * set case) + */ + public void testSingleAckReceivedTest() throws AMQException + { + _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, true,null,false, new LimitlessCreditManager()); + final int msgCount = 10; + publishMessages(msgCount); + + _channel.acknowledgeMessage(5, false); + UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == msgCount - 1); + + Set<Long> deliveryTagSet = map.getDeliveryTags(); + int i = 1; + for (long deliveryTag : deliveryTagSet) + { + assertTrue(deliveryTag == i); + QueueEntry unackedMsg = map.get(deliveryTag); + assertTrue(unackedMsg.getQueue() == _queue); + // 5 is the delivery tag of the message that *should* be removed + if (++i == 5) + { + ++i; + } + } + } + + /** + * Tests that a single acknowledgement is handled correctly (i.e multiple flag not + * set case) + */ + public void testMultiAckReceivedTest() throws AMQException + { + _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, true,null,false, new LimitlessCreditManager()); + final int msgCount = 10; + publishMessages(msgCount); + + _channel.acknowledgeMessage(5, true); + UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == 5); + + Set<Long> deliveryTagSet = map.getDeliveryTags(); + int i = 1; + for (long deliveryTag : deliveryTagSet) + { + assertTrue(deliveryTag == i + 5); + QueueEntry unackedMsg = map.get(deliveryTag); + assertTrue(unackedMsg.getQueue() == _queue); + ++i; + } + } + + /** + * Tests that a multiple acknowledgement is handled correctly. When ack'ing all pending msgs. + */ + public void testMultiAckAllReceivedTest() throws AMQException + { + _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, true,null,false, new LimitlessCreditManager()); + final int msgCount = 10; + publishMessages(msgCount); + + _channel.acknowledgeMessage(0, true); + UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == 0); + + Set<Long> deliveryTagSet = map.getDeliveryTags(); + int i = 1; + for (long deliveryTag : deliveryTagSet) + { + assertTrue(deliveryTag == i + 5); + QueueEntry unackedMsg = map.get(deliveryTag); + assertTrue(unackedMsg.getQueue() == _queue); + ++i; + } + } + + /** + * A regression fixing QPID-1136 showed this up + * + * @throws Exception + */ + public void testMessageDequeueRestoresCreditTest() throws Exception + { + // Send 10 messages + Pre0_10CreditManager creditManager = new Pre0_10CreditManager(0l, 1); + + _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, + DEFAULT_CONSUMER_TAG, true, null, false, creditManager); + final int msgCount = 1; + publishMessages(msgCount); + + _queue.deliverAsync(_subscription); + + _channel.acknowledgeMessage(1, false); + + // Check credit available + assertTrue("No credit available", creditManager.hasCredit()); + + } + + +/* + public void testPrefetchHighLow() throws AMQException + { + int lowMark = 5; + int highMark = 10; + + _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, true,null,false, new LimitlessCreditManager()); + _channel.setPrefetchLowMarkCount(lowMark); + _channel.setPrefetchHighMarkCount(highMark); + + assertTrue(_channel.getPrefetchLowMarkCount() == lowMark); + assertTrue(_channel.getPrefetchHighMarkCount() == highMark); + + publishMessages(highMark); + + // at this point we should have sent out only highMark messages + // which have not bee received so will be queued up in the channel + // which should be suspended + assertTrue(_subscription.isSuspended()); + UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == highMark); + + //acknowledge messages so we are just above lowMark + _channel.acknowledgeMessage(lowMark - 1, true); + + //we should still be suspended + assertTrue(_subscription.isSuspended()); + assertTrue(map.size() == lowMark + 1); + + //acknowledge one more message + _channel.acknowledgeMessage(lowMark, true); + + //and suspension should be lifted + assertTrue(!_subscription.isSuspended()); + + //pubilsh more msgs so we are just below the limit + publishMessages(lowMark - 1); + + //we should not be suspended + assertTrue(!_subscription.isSuspended()); + + //acknowledge all messages + _channel.acknowledgeMessage(0, true); + try + { + Thread.sleep(3000); + } + catch (InterruptedException e) + { + _log.error("Error: " + e, e); + } + //map will be empty + assertTrue(map.size() == 0); + } + +*/ +/* + public void testPrefetch() throws AMQException + { + _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, true,null,false, new LimitlessCreditManager()); + _channel.setMessageCredit(5); + + assertTrue(_channel.getPrefetchCount() == 5); + + final int msgCount = 5; + publishMessages(msgCount); + + // at this point we should have sent out only 5 messages with a further 5 queued + // up in the channel which should now be suspended + assertTrue(_subscription.isSuspended()); + UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == 5); + _channel.acknowledgeMessage(5, true); + assertTrue(!_subscription.isSuspended()); + try + { + Thread.sleep(3000); + } + catch (InterruptedException e) + { + _log.error("Error: " + e, e); + } + assertTrue(map.size() == 0); + } + +*/ + public static junit.framework.Test suite() + { + return new junit.framework.TestSuite(AckTest.class); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockAMQMessage.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockAMQMessage.java new file mode 100644 index 0000000000..7000df157e --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockAMQMessage.java @@ -0,0 +1,43 @@ +/* + * + * 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.qpid.server.queue; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.message.AMQMessage; + +public class MockAMQMessage extends AMQMessage +{ + public MockAMQMessage(long messageId) + throws AMQException + { + super(new MockStoredMessage(messageId)); + } + + + + + @Override + public long getSize() + { + return 0l; + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockAMQQueue.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockAMQQueue.java new file mode 100644 index 0000000000..888a16053c --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockAMQQueue.java @@ -0,0 +1,614 @@ +/* + * + * 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.qpid.server.queue; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.configuration.*; +import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.management.ManagedObject; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.security.PrincipalHolder; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.txn.ServerTransaction; +import org.apache.qpid.AMQException; + +import java.util.List; +import java.util.Set; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CopyOnWriteArrayList; + +public class MockAMQQueue implements AMQQueue +{ + private boolean _deleted = false; + private AMQShortString _name; + private VirtualHost _virtualhost; + + private PrincipalHolder _principalHolder; + + private AMQSessionModel _exclusiveOwner; + private AMQShortString _owner; + private List<Binding> _bindings = new CopyOnWriteArrayList<Binding>(); + private boolean _autoDelete; + + public MockAMQQueue(String name) + { + _name = new AMQShortString(name); + } + + public boolean getDeleteOnNoConsumers() + { + return false; + } + + public void setDeleteOnNoConsumers(boolean b) + { + } + + public void addBinding(final Binding binding) + { + _bindings.add(binding); + } + + public void removeBinding(final Binding binding) + { + _bindings.remove(binding); + } + + public List<Binding> getBindings() + { + return _bindings; + } + + public int getBindingCount() + { + return 0; + } + + public LogSubject getLogSubject() + { + return new LogSubject() + { + public String toLogString() + { + return "[MockAMQQueue]"; + } + + }; + } + + public ConfigStore getConfigStore() + { + return getVirtualHost().getConfigStore(); + } + + public long getMessageDequeueCount() + { + return 0; + } + + public long getTotalEnqueueSize() + { + return 0; + } + + public long getTotalDequeueSize() + { + return 0; + } + + public int getBindingCountHigh() + { + return 0; + } + + public long getPersistentByteEnqueues() + { + return 0; + } + + public long getPersistentByteDequeues() + { + return 0; + } + + public long getPersistentMsgEnqueues() + { + return 0; + } + + public long getPersistentMsgDequeues() + { + return 0; + } + + public void purge(final long request) + { + + } + + public long getCreateTime() + { + return 0; + } + + public AMQShortString getNameShortString() + { + return _name; + } + + public void setNoLocal(boolean b) + { + + } + + public UUID getId() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public QueueConfigType getConfigType() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public ConfiguredObject getParent() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isDurable() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isAutoDelete() + { + return _autoDelete; + } + + public void setAutoDelete(boolean autodelete) + { + _autoDelete = autodelete; + } + + + public AMQShortString getOwner() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public void setVirtualHost(VirtualHost virtualhost) + { + _virtualhost = virtualhost; + } + + public VirtualHost getVirtualHost() + { + return _virtualhost; + } + + public String getName() + { + return _name.asString(); + } + + public void registerSubscription(Subscription subscription, boolean exclusive) throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void unregisterSubscription(Subscription subscription) throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public int getConsumerCount() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public int getActiveConsumerCount() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean hasExclusiveSubscriber() + { + return false; + } + + public boolean isUnused() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isEmpty() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public int getMessageCount() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public int getUndeliveredMessageCount() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getQueueDepth() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getReceivedMessageCount() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getOldestMessageArrivalTime() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isDeleted() + { + return _deleted; + } + + public int delete() throws AMQException + { + _deleted = true; + return getMessageCount(); + } + + public void enqueue(ServerMessage message) throws AMQException + { + } + + public void enqueue(ServerMessage message, PostEnqueueAction action) throws AMQException + { + } + + + public void requeue(QueueEntry entry) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void requeue(QueueEntryImpl storeContext, Subscription subscription) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void dequeue(QueueEntry entry, Subscription sub) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean resend(QueueEntry entry, Subscription subscription) throws AMQException + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public void addQueueDeleteTask(Task task) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void removeQueueDeleteTask(final Task task) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public List<QueueEntry> getMessagesOnTheQueue() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public List<QueueEntry> getMessagesOnTheQueue(long fromMessageId, long toMessageId) + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public List<Long> getMessagesOnTheQueue(int num) + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public List<Long> getMessagesOnTheQueue(int num, int offest) + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public QueueEntry getMessageOnTheQueue(long messageId) + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public List<QueueEntry> getMessagesRangeOnTheQueue(long fromPosition, long toPosition) + { + return null; + } + + public void moveMessagesToAnotherQueue(long fromMessageId, long toMessageId, String queueName, ServerTransaction storeContext) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void copyMessagesToAnotherQueue(long fromMessageId, long toMessageId, String queueName, ServerTransaction storeContext) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void removeMessagesFromQueue(long fromMessageId, long toMessageId) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public long getMaximumMessageSize() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public void setMaximumMessageSize(long value) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public long getMaximumMessageCount() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public void setMaximumMessageCount(long value) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public long getMaximumQueueDepth() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public void setMaximumQueueDepth(long value) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public long getMaximumMessageAge() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public void setMaximumMessageAge(long maximumMessageAge) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean getBlockOnQueueFull() + { + return false; + } + + public void setBlockOnQueueFull(boolean block) + { + } + + public long getMinimumAlertRepeatGap() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public void deleteMessageFromTop() + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public long clearQueue() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + + public void checkMessageStatus() throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public Set<NotificationCheck> getNotificationChecks() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public void flushSubscription(Subscription sub) throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void deliverAsync(Subscription sub) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void deliverAsync() + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void stop() + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isExclusive() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public Exchange getAlternateExchange() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public void setAlternateExchange(Exchange exchange) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public Map<String, Object> getArguments() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public void checkCapacity(AMQChannel channel) + { + } + + public ManagedObject getManagedObject() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public int compareTo(AMQQueue o) + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public void setMinimumAlertRepeatGap(long value) + { + + } + + public long getCapacity() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public void setCapacity(long capacity) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public long getFlowResumeCapacity() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public void setFlowResumeCapacity(long flowResumeCapacity) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void configure(ConfigurationPlugin config) + { + + } + + public ConfigurationPlugin getConfiguration() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public PrincipalHolder getPrincipalHolder() + { + return _principalHolder; + } + + public void setPrincipalHolder(PrincipalHolder principalHolder) + { + _principalHolder = principalHolder; + } + + public AMQSessionModel getExclusiveOwningSession() + { + return _exclusiveOwner; + } + + public void setExclusiveOwningSession(AMQSessionModel exclusiveOwner) + { + _exclusiveOwner = exclusiveOwner; + } + + + public String getResourceName() + { + return _name.toString(); + } + + public boolean isOverfull() + { + return false; + } + + public int getConsumerCountHigh() + { + return 0; + } + + public long getByteTxnEnqueues() + { + return 0; + } + + public long getMsgTxnEnqueues() + { + return 0; + } + + public long getByteTxnDequeues() + { + return 0; + } + + public long getMsgTxnDequeues() + { + return 0; + } + + public void decrementUnackedMsgCount() + { + + } + + public long getUnackedMessageCount() + { + return 0; + } + + public long getUnackedMessageCountHigh() + { + return 0; + } + + public void setExclusive(boolean exclusive) + { + + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockMessagePublishInfo.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockMessagePublishInfo.java new file mode 100644 index 0000000000..5a5ffaa14d --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockMessagePublishInfo.java @@ -0,0 +1,52 @@ +/* + * + * 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.qpid.server.queue; + +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.framing.AMQShortString; + +public class MockMessagePublishInfo implements MessagePublishInfo +{ + public AMQShortString getExchange() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public void setExchange(AMQShortString exchange) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isImmediate() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isMandatory() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public AMQShortString getRoutingKey() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockQueueEntry.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockQueueEntry.java new file mode 100644 index 0000000000..5bdbe2c68e --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockQueueEntry.java @@ -0,0 +1,234 @@ +/* +* + * 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.qpid.server.queue; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.message.AMQMessage; +import org.apache.qpid.server.message.ServerMessage; + +public class MockQueueEntry implements QueueEntry +{ + + private AMQMessage _message; + + public boolean acquire() + { + return false; + } + + public boolean acquire(Subscription sub) + { + return false; + } + + public boolean acquiredBySubscription() + { + return false; + } + + public boolean isAcquiredBy(Subscription subscription) + { + return false; + } + + public void addStateChangeListener(StateChangeListener listener) + { + + } + + public boolean delete() + { + return false; + } + + public void dequeue() + { + + } + + public void discard() + { + + } + + public void routeToAlternate() + { + + } + + public void dispose() + { + + } + + public boolean expired() throws AMQException + { + return false; + } + + public boolean isAvailable() + { + return false; + } + + public Subscription getDeliveredSubscription() + { + return null; + } + + public boolean getDeliveredToConsumer() + { + return false; + } + + public ServerMessage getMessage() + { + return _message; + } + + public AMQQueue getQueue() + { + return null; + } + + public long getSize() + { + return 0; + } + + public boolean immediateAndNotDelivered() + { + return false; + } + + public boolean isAcquired() + { + return false; + } + + public boolean isDeleted() + { + return false; + } + + + public boolean isQueueDeleted() + { + + return false; + } + + + public boolean isRejectedBy(Subscription subscription) + { + + return false; + } + + + public void reject() + { + + + } + + + public void reject(Subscription subscription) + { + + + } + + + public void release() + { + + + } + + public boolean releaseButRetain() + { + return false; + } + + + public boolean removeStateChangeListener(StateChangeListener listener) + { + + return false; + } + + + public void requeue() + { + + + } + + public void requeue(Subscription subscription) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + + public void setDeliveredToSubscription() + { + + + } + + + public void setRedelivered() + { + + + } + + public AMQMessageHeader getMessageHeader() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isPersistent() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isRedelivered() + { + return false; + } + + + public int compareTo(QueueEntry o) + { + + return 0; + } + + public void setMessage(AMQMessage msg) + { + _message = msg; + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockStoredMessage.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockStoredMessage.java new file mode 100755 index 0000000000..7dc491de4d --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockStoredMessage.java @@ -0,0 +1,92 @@ +/* +* +* 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.qpid.server.queue; + +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.TransactionLog; +import org.apache.qpid.server.store.StoredMessage; +import org.apache.qpid.server.message.MessageMetaData; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; + +import java.nio.ByteBuffer; + +public class MockStoredMessage implements StoredMessage<MessageMetaData> +{ + private long _messageId; + private MessageMetaData _metaData; + private final ByteBuffer _content; + + + public MockStoredMessage(long messageId) + { + this(messageId, new MockMessagePublishInfo(), new ContentHeaderBody(new BasicContentHeaderProperties(), 60)); + } + + public MockStoredMessage(long messageId, MessagePublishInfo info, ContentHeaderBody chb) + { + _messageId = messageId; + _metaData = new MessageMetaData(info, chb, 0); + _content = ByteBuffer.allocate(_metaData.getContentSize()); + + } + + public MessageMetaData getMetaData() + { + return _metaData; + } + + public long getMessageNumber() + { + return _messageId; + } + + public void addContent(int offsetInMessage, ByteBuffer src) + { + src = src.duplicate(); + ByteBuffer dst = _content.duplicate(); + dst.position(offsetInMessage); + dst.put(src); + } + + public int getContent(int offset, ByteBuffer dst) + { + ByteBuffer src = _content.duplicate(); + src.position(offset); + src = src.slice(); + if(dst.remaining() < src.limit()) + { + src.limit(dst.remaining()); + } + dst.put(src); + return src.limit(); + } + + public TransactionLog.StoreFuture flushToStore() + { + return MessageStore.IMMEDIATE_FUTURE; + } + + public void remove() + { + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/QueueEntryTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/QueueEntryTest.java new file mode 100644 index 0000000000..b67723dd25 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/QueueEntryTest.java @@ -0,0 +1,97 @@ +/* + * + * 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.qpid.server.queue; + +import org.apache.qpid.test.utils.QpidTestCase; + +/** + * + * Tests QueueEntry + * + */ +public class QueueEntryTest extends QpidTestCase +{ + private QueueEntryImpl _queueEntry1 = null; + private QueueEntryImpl _queueEntry2 = null; + private QueueEntryImpl _queueEntry3 = null; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + + int i = 0; + + SimpleQueueEntryList queueEntryList = new SimpleQueueEntryList(null); + _queueEntry1 = (QueueEntryImpl) queueEntryList.add(new MockAMQMessage(i++)); + _queueEntry2 = (QueueEntryImpl) queueEntryList.add(new MockAMQMessage(i++)); + _queueEntry3 = (QueueEntryImpl) queueEntryList.add(new MockAMQMessage(i++)); + } + + public void testCompareTo() + { + assertTrue(_queueEntry1.compareTo(_queueEntry2) < 0); + assertTrue(_queueEntry2.compareTo(_queueEntry1) > 0); + assertTrue(_queueEntry1.compareTo(_queueEntry1) == 0); + } + + /** + * Tests that the getNext() can be used to traverse the list. + */ + public void testTraverseWithNoDeletedEntries() + { + QueueEntryImpl current = _queueEntry1; + + current = current.getNext(); + assertSame("Unexpected current entry",_queueEntry2, current); + + current = current.getNext(); + assertSame("Unexpected current entry",_queueEntry3, current); + + current = current.getNext(); + assertNull(current); + + } + + /** + * Tests that the getNext() can be used to traverse the list but deleted + * entries are skipped and de-linked from the chain of entries. + */ + public void testTraverseWithDeletedEntries() + { + // Delete 2nd queue entry + _queueEntry2.delete(); + assertTrue(_queueEntry2.isDeleted()); + + + QueueEntryImpl current = _queueEntry1; + + current = current.getNext(); + assertSame("Unexpected current entry",_queueEntry3, current); + + current = current.getNext(); + assertNull(current); + + // Assert the side effects of getNext() + assertSame("Next node of entry 1 should now be entry 3", + _queueEntry3, _queueEntry1.nextNode()); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/SimpleAMQQueueTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/SimpleAMQQueueTest.java new file mode 100644 index 0000000000..abe2d1728f --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/SimpleAMQQueueTest.java @@ -0,0 +1,817 @@ +/* + * + * 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.qpid.server.queue; + +import org.apache.commons.configuration.PropertiesConfiguration; + +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQInternalException; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.exchange.DirectExchange; +import org.apache.qpid.server.message.AMQMessage; +import org.apache.qpid.server.message.MessageMetaData; +import org.apache.qpid.server.queue.BaseQueue.PostEnqueueAction; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.store.StoredMessage; +import org.apache.qpid.server.store.TestableMemoryMessageStore; +import org.apache.qpid.server.subscription.MockSubscription; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.txn.AutoCommitTransaction; +import org.apache.qpid.server.txn.ServerTransaction; +import org.apache.qpid.server.util.InternalBrokerBaseCase; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.virtualhost.VirtualHostImpl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class SimpleAMQQueueTest extends InternalBrokerBaseCase +{ + + protected SimpleAMQQueue _queue; + protected VirtualHost _virtualHost; + protected TestableMemoryMessageStore _store = new TestableMemoryMessageStore(); + protected AMQShortString _qname = new AMQShortString("qname"); + protected AMQShortString _owner = new AMQShortString("owner"); + protected AMQShortString _routingKey = new AMQShortString("routing key"); + protected DirectExchange _exchange; + protected MockSubscription _subscription = new MockSubscription(); + protected FieldTable _arguments = null; + + MessagePublishInfo info = new MessagePublishInfo() + { + + public AMQShortString getExchange() + { + return null; + } + + public void setExchange(AMQShortString exchange) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isImmediate() + { + return false; + } + + public boolean isMandatory() + { + return false; + } + + public AMQShortString getRoutingKey() + { + return null; + } + }; + + @Override + public void setUp() throws Exception + { + super.setUp(); + //Create Application Registry for test + ApplicationRegistry applicationRegistry = (ApplicationRegistry)ApplicationRegistry.getInstance(); + + PropertiesConfiguration env = new PropertiesConfiguration(); + _virtualHost = new VirtualHostImpl(new VirtualHostConfiguration(getClass().getName(), env), _store); + applicationRegistry.getVirtualHostRegistry().registerVirtualHost(_virtualHost); + + _queue = (SimpleAMQQueue) AMQQueueFactory.createAMQQueueImpl(_qname, false, _owner, false, false, _virtualHost, _arguments); + + _exchange = (DirectExchange)_virtualHost.getExchangeRegistry().getExchange(ExchangeDefaults.DIRECT_EXCHANGE_NAME); + } + + @Override + public void tearDown() throws Exception + { + _queue.stop(); + super.tearDown(); + } + + public void testCreateQueue() throws AMQException + { + _queue.stop(); + try { + _queue = (SimpleAMQQueue) AMQQueueFactory.createAMQQueueImpl(null, false, _owner, false, false, _virtualHost, _arguments ); + assertNull("Queue was created", _queue); + } + catch (IllegalArgumentException e) + { + assertTrue("Exception was not about missing name", + e.getMessage().contains("name")); + } + + try { + _queue = new SimpleAMQQueue(_qname, false, _owner, false, false,null, Collections.EMPTY_MAP); + assertNull("Queue was created", _queue); + } + catch (IllegalArgumentException e) + { + assertTrue("Exception was not about missing vhost", + e.getMessage().contains("Host")); + } + + _queue = (SimpleAMQQueue) AMQQueueFactory.createAMQQueueImpl(_qname, false, _owner, false, + false, _virtualHost, _arguments); + assertNotNull("Queue was not created", _queue); + } + + public void testGetVirtualHost() + { + assertEquals("Virtual host was wrong", _virtualHost, _queue.getVirtualHost()); + } + + public void testBinding() throws AMQSecurityException, AMQInternalException + { + _virtualHost.getBindingFactory().addBinding(String.valueOf(_routingKey), _queue, _exchange, Collections.EMPTY_MAP); + + assertTrue("Routing key was not bound", + _exchange.isBound(_routingKey)); + assertTrue("Queue was not bound to key", + _exchange.isBound(_routingKey,_queue)); + assertEquals("Exchange binding count", 1, + _queue.getBindings().size()); + assertEquals("Wrong exchange bound", String.valueOf(_routingKey), + _queue.getBindings().get(0).getBindingKey()); + assertEquals("Wrong exchange bound", _exchange, + _queue.getBindings().get(0).getExchange()); + + _virtualHost.getBindingFactory().removeBinding(String.valueOf(_routingKey), _queue, _exchange, Collections.EMPTY_MAP); + assertFalse("Routing key was still bound", + _exchange.isBound(_routingKey)); + + } + + public void testRegisterSubscriptionThenEnqueueMessage() throws AMQException + { + // Check adding a subscription adds it to the queue + _queue.registerSubscription(_subscription, false); + assertEquals("Subscription did not get queue", _queue, + _subscription.getQueue()); + assertEquals("Queue does not have consumer", 1, + _queue.getConsumerCount()); + assertEquals("Queue does not have active consumer", 1, + _queue.getActiveConsumerCount()); + + // Check sending a message ends up with the subscriber + AMQMessage messageA = createMessage(new Long(24)); + _queue.enqueue(messageA); + assertEquals(messageA, _subscription.getQueueContext().getLastSeenEntry().getMessage()); + assertNull(((QueueContext)_subscription.getQueueContext())._releasedEntry); + + // Check removing the subscription removes it's information from the queue + _queue.unregisterSubscription(_subscription); + assertTrue("Subscription still had queue", _subscription.isClosed()); + assertFalse("Queue still has consumer", 1 == _queue.getConsumerCount()); + assertFalse("Queue still has active consumer", + 1 == _queue.getActiveConsumerCount()); + + AMQMessage messageB = createMessage(new Long (25)); + _queue.enqueue(messageB); + assertNull(_subscription.getQueueContext()); + + } + + public void testEnqueueMessageThenRegisterSubscription() throws AMQException, InterruptedException + { + AMQMessage messageA = createMessage(new Long(24)); + _queue.enqueue(messageA); + _queue.registerSubscription(_subscription, false); + Thread.sleep(150); + assertEquals(messageA, _subscription.getQueueContext().getLastSeenEntry().getMessage()); + assertNull("There should be no releasedEntry after an enqueue", ((QueueContext)_subscription.getQueueContext())._releasedEntry); + } + + /** + * Tests enqueuing two messages. + */ + public void testEnqueueTwoMessagesThenRegisterSubscription() throws Exception + { + AMQMessage messageA = createMessage(new Long(24)); + AMQMessage messageB = createMessage(new Long(25)); + _queue.enqueue(messageA); + _queue.enqueue(messageB); + _queue.registerSubscription(_subscription, false); + Thread.sleep(150); + assertEquals(messageB, _subscription.getQueueContext().getLastSeenEntry().getMessage()); + assertNull("There should be no releasedEntry after enqueues", ((QueueContext)_subscription.getQueueContext())._releasedEntry); + } + + /** + * Tests that a released queue entry is resent to the subscriber. Verifies also that the + * QueueContext._releasedEntry is reset to null after the entry has been reset. + */ + public void testReleasedMessageIsResentToSubscriber() throws Exception + { + _queue.registerSubscription(_subscription, false); + + final ArrayList<QueueEntry> queueEntries = new ArrayList<QueueEntry>(); + PostEnqueueAction postEnqueueAction = new PostEnqueueAction() + { + public void onEnqueue(QueueEntry entry) + { + queueEntries.add(entry); + } + }; + + AMQMessage messageA = createMessage(new Long(24)); + AMQMessage messageB = createMessage(new Long(25)); + AMQMessage messageC = createMessage(new Long(26)); + + /* Enqueue three messages */ + + _queue.enqueue(messageA, postEnqueueAction); + _queue.enqueue(messageB, postEnqueueAction); + _queue.enqueue(messageC, postEnqueueAction); + + Thread.sleep(150); // Work done by SubFlushRunner/QueueRunner Threads + + assertEquals("Unexpected total number of messages sent to subscription", 3, _subscription.getMessages().size()); + assertFalse("Redelivery flag should not be set", queueEntries.get(0).isRedelivered()); + assertFalse("Redelivery flag should not be set", queueEntries.get(1).isRedelivered()); + assertFalse("Redelivery flag should not be set", queueEntries.get(2).isRedelivered()); + + /* Now release the first message only, causing it to be requeued */ + + queueEntries.get(0).release(); + + Thread.sleep(150); // Work done by SubFlushRunner/QueueRunner Threads + + assertEquals("Unexpected total number of messages sent to subscription", 4, _subscription.getMessages().size()); + assertTrue("Redelivery flag should now be set", queueEntries.get(0).isRedelivered()); + assertFalse("Redelivery flag should remain be unset", queueEntries.get(1).isRedelivered()); + assertFalse("Redelivery flag should remain be unset",queueEntries.get(2).isRedelivered()); + assertNull("releasedEntry should be cleared after requeue processed", ((QueueContext)_subscription.getQueueContext())._releasedEntry); + } + + /** + * Tests that a released message that becomes expired is not resent to the subscriber. + * This tests ensures that SimpleAMQQueueEntry.getNextAvailableEntry avoids expired entries. + * Verifies also that the QueueContext._releasedEntry is reset to null after the entry has been reset. + */ + public void testReleaseMessageThatBecomesExpiredIsNotRedelivered() throws Exception + { + _queue.registerSubscription(_subscription, false); + + final ArrayList<QueueEntry> queueEntries = new ArrayList<QueueEntry>(); + PostEnqueueAction postEnqueueAction = new PostEnqueueAction() + { + public void onEnqueue(QueueEntry entry) + { + queueEntries.add(entry); + } + }; + + /* Enqueue one message with expiration set for a short time in the future */ + + AMQMessage messageA = createMessage(new Long(24)); + int messageExpirationOffset = 200; + messageA.setExpiration(System.currentTimeMillis() + messageExpirationOffset); + + _queue.enqueue(messageA, postEnqueueAction); + + int subFlushWaitTime = 150; + Thread.sleep(subFlushWaitTime); // Work done by SubFlushRunner/QueueRunner Threads + + assertEquals("Unexpected total number of messages sent to subscription", 1, _subscription.getMessages().size()); + assertFalse("Redelivery flag should not be set", queueEntries.get(0).isRedelivered()); + + /* Wait a little more to be sure that message will have expired, then release the first message only, causing it to be requeued */ + Thread.sleep(messageExpirationOffset - subFlushWaitTime + 10); + queueEntries.get(0).release(); + + Thread.sleep(subFlushWaitTime); // Work done by SubFlushRunner/QueueRunner Threads + + assertTrue("Expecting the queue entry to be now expired", queueEntries.get(0).expired()); + assertEquals("Total number of messages sent should not have changed", 1, _subscription.getMessages().size()); + assertFalse("Redelivery flag should not be set", queueEntries.get(0).isRedelivered()); + assertNull("releasedEntry should be cleared after requeue processed", ((QueueContext)_subscription.getQueueContext())._releasedEntry); + + } + + /** + * Tests that if a client releases entries 'out of order' (the order + * used by QueueEntryImpl.compareTo) that messages are still resent + * successfully. Specifically this test ensures the {@see SimpleAMQQueue#requeue()} + * can correctly move the _releasedEntry to an earlier position in the QueueEntry list. + */ + public void testReleasedOutOfComparableOrderAreRedelivered() throws Exception + { + _queue.registerSubscription(_subscription, false); + + final ArrayList<QueueEntry> queueEntries = new ArrayList<QueueEntry>(); + PostEnqueueAction postEnqueueAction = new PostEnqueueAction() + { + public void onEnqueue(QueueEntry entry) + { + queueEntries.add(entry); + } + }; + + AMQMessage messageA = createMessage(new Long(24)); + AMQMessage messageB = createMessage(new Long(25)); + AMQMessage messageC = createMessage(new Long(26)); + + /* Enqueue three messages */ + + _queue.enqueue(messageA, postEnqueueAction); + _queue.enqueue(messageB, postEnqueueAction); + _queue.enqueue(messageC, postEnqueueAction); + + Thread.sleep(150); // Work done by SubFlushRunner/QueueRunner Threads + + assertEquals("Unexpected total number of messages sent to subscription", 3, _subscription.getMessages().size()); + assertFalse("Redelivery flag should not be set", queueEntries.get(0).isRedelivered()); + assertFalse("Redelivery flag should not be set", queueEntries.get(1).isRedelivered()); + assertFalse("Redelivery flag should not be set", queueEntries.get(2).isRedelivered()); + + /* Now release the third and first message only, causing it to be requeued */ + + queueEntries.get(2).release(); + queueEntries.get(0).release(); + + Thread.sleep(150); // Work done by SubFlushRunner/QueueRunner Threads + + assertEquals("Unexpected total number of messages sent to subscription", 5, _subscription.getMessages().size()); + assertTrue("Redelivery flag should now be set", queueEntries.get(0).isRedelivered()); + assertFalse("Redelivery flag should remain be unset", queueEntries.get(1).isRedelivered()); + assertTrue("Redelivery flag should now be set",queueEntries.get(2).isRedelivered()); + assertNull("releasedEntry should be cleared after requeue processed", ((QueueContext)_subscription.getQueueContext())._releasedEntry); + } + + + /** + * Tests that a release requeues an entry for a queue with multiple subscriptions. Verifies that a + * requeue resends a message to a <i>single</i> subscriber. + */ + public void testReleaseForQueueWithMultipleSubscriptions() throws Exception + { + MockSubscription subscription1 = new MockSubscription(); + MockSubscription subscription2 = new MockSubscription(); + + _queue.registerSubscription(subscription1, false); + _queue.registerSubscription(subscription2, false); + + final ArrayList<QueueEntry> queueEntries = new ArrayList<QueueEntry>(); + PostEnqueueAction postEnqueueAction = new PostEnqueueAction() + { + public void onEnqueue(QueueEntry entry) + { + queueEntries.add(entry); + } + }; + + AMQMessage messageA = createMessage(new Long(24)); + AMQMessage messageB = createMessage(new Long(25)); + + /* Enqueue two messages */ + + _queue.enqueue(messageA, postEnqueueAction); + _queue.enqueue(messageB, postEnqueueAction); + + Thread.sleep(150); // Work done by SubFlushRunner/QueueRunner Threads + + assertEquals("Unexpected total number of messages sent to both after enqueue", 2, subscription1.getMessages().size() + subscription2.getMessages().size()); + + /* Now release the first message only, causing it to be requeued */ + queueEntries.get(0).release(); + + Thread.sleep(150); // Work done by SubFlushRunner/QueueRunner Threads + + assertEquals("Unexpected total number of messages sent to both subscriptions after release", 3, subscription1.getMessages().size() + subscription2.getMessages().size()); + assertNull("releasedEntry should be cleared after requeue processed", ((QueueContext)subscription1.getQueueContext())._releasedEntry); + assertNull("releasedEntry should be cleared after requeue processed", ((QueueContext)subscription2.getQueueContext())._releasedEntry); + } + + public void testExclusiveConsumer() throws AMQException + { + // Check adding an exclusive subscription adds it to the queue + _queue.registerSubscription(_subscription, true); + assertEquals("Subscription did not get queue", _queue, + _subscription.getQueue()); + assertEquals("Queue does not have consumer", 1, + _queue.getConsumerCount()); + assertEquals("Queue does not have active consumer", 1, + _queue.getActiveConsumerCount()); + + // Check sending a message ends up with the subscriber + AMQMessage messageA = createMessage(new Long(24)); + _queue.enqueue(messageA); + assertEquals(messageA, _subscription.getQueueContext().getLastSeenEntry().getMessage()); + + // Check we cannot add a second subscriber to the queue + Subscription subB = new MockSubscription(); + Exception ex = null; + try + { + _queue.registerSubscription(subB, false); + } + catch (AMQException e) + { + ex = e; + } + assertNotNull(ex); + + // Check we cannot add an exclusive subscriber to a queue with an + // existing subscription + _queue.unregisterSubscription(_subscription); + _queue.registerSubscription(_subscription, false); + try + { + _queue.registerSubscription(subB, true); + } + catch (AMQException e) + { + ex = e; + } + assertNotNull(ex); + } + + public void testAutoDeleteQueue() throws Exception + { + _queue.stop(); + _queue = new SimpleAMQQueue(_qname, false, null, true, false, _virtualHost, Collections.EMPTY_MAP); + _queue.setDeleteOnNoConsumers(true); + _queue.registerSubscription(_subscription, false); + AMQMessage message = createMessage(new Long(25)); + _queue.enqueue(message); + _queue.unregisterSubscription(_subscription); + assertTrue("Queue was not deleted when subscription was removed", + _queue.isDeleted()); + } + + public void testResend() throws Exception + { + _queue.registerSubscription(_subscription, false); + Long id = new Long(26); + AMQMessage message = createMessage(id); + _queue.enqueue(message); + QueueEntry entry = _subscription.getQueueContext().getLastSeenEntry(); + entry.setRedelivered(); + _queue.resend(entry, _subscription); + + } + + public void testGetFirstMessageId() throws Exception + { + // Create message + Long messageId = new Long(23); + AMQMessage message = createMessage(messageId); + + // Put message on queue + _queue.enqueue(message); + // Get message id + Long testmsgid = _queue.getMessagesOnTheQueue(1).get(0); + + // Check message id + assertEquals("Message ID was wrong", messageId, testmsgid); + } + + public void testGetFirstFiveMessageIds() throws Exception + { + for (int i = 0 ; i < 5; i++) + { + // Create message + Long messageId = new Long(i); + AMQMessage message = createMessage(messageId); + // Put message on queue + _queue.enqueue(message); + } + // Get message ids + List<Long> msgids = _queue.getMessagesOnTheQueue(5); + + // Check message id + for (int i = 0; i < 5; i++) + { + Long messageId = new Long(i); + assertEquals("Message ID was wrong", messageId, msgids.get(i)); + } + } + + public void testGetLastFiveMessageIds() throws Exception + { + for (int i = 0 ; i < 10; i++) + { + // Create message + Long messageId = new Long(i); + AMQMessage message = createMessage(messageId); + // Put message on queue + _queue.enqueue(message); + } + // Get message ids + List<Long> msgids = _queue.getMessagesOnTheQueue(5, 5); + + // Check message id + for (int i = 0; i < 5; i++) + { + Long messageId = new Long(i+5); + assertEquals("Message ID was wrong", messageId, msgids.get(i)); + } + } + + public void testGetMessagesRangeOnTheQueue() throws Exception + { + for (int i = 1 ; i <= 10; i++) + { + // Create message + Long messageId = new Long(i); + AMQMessage message = createMessage(messageId); + // Put message on queue + _queue.enqueue(message); + } + + // Get non-existent 0th QueueEntry & check returned list was empty + // (the position parameters in this method are indexed from 1) + List<QueueEntry> entries = _queue.getMessagesRangeOnTheQueue(0, 0); + assertTrue(entries.size() == 0); + + // Check that when 'from' is 0 it is ignored and the range continues from 1 + entries = _queue.getMessagesRangeOnTheQueue(0, 2); + assertTrue(entries.size() == 2); + long msgID = entries.get(0).getMessage().getMessageNumber(); + assertEquals("Message ID was wrong", msgID, 1L); + msgID = entries.get(1).getMessage().getMessageNumber(); + assertEquals("Message ID was wrong", msgID, 2L); + + // Check that when 'from' is greater than 'to' the returned list is empty + entries = _queue.getMessagesRangeOnTheQueue(5, 4); + assertTrue(entries.size() == 0); + + // Get first QueueEntry & check id + entries = _queue.getMessagesRangeOnTheQueue(1, 1); + assertTrue(entries.size() == 1); + msgID = entries.get(0).getMessage().getMessageNumber(); + assertEquals("Message ID was wrong", msgID, 1L); + + // Get 5th,6th,7th entries and check id's + entries = _queue.getMessagesRangeOnTheQueue(5, 7); + assertTrue(entries.size() == 3); + msgID = entries.get(0).getMessage().getMessageNumber(); + assertEquals("Message ID was wrong", msgID, 5L); + msgID = entries.get(1).getMessage().getMessageNumber(); + assertEquals("Message ID was wrong", msgID, 6L); + msgID = entries.get(2).getMessage().getMessageNumber(); + assertEquals("Message ID was wrong", msgID, 7L); + + // Get 10th QueueEntry & check id + entries = _queue.getMessagesRangeOnTheQueue(10, 10); + assertTrue(entries.size() == 1); + msgID = entries.get(0).getMessage().getMessageNumber(); + assertEquals("Message ID was wrong", msgID, 10L); + + // Get non-existent 11th QueueEntry & check returned set was empty + entries = _queue.getMessagesRangeOnTheQueue(11, 11); + assertTrue(entries.size() == 0); + + // Get 9th,10th, and non-existent 11th entries & check result is of size 2 with correct IDs + entries = _queue.getMessagesRangeOnTheQueue(9, 11); + assertTrue(entries.size() == 2); + msgID = entries.get(0).getMessage().getMessageNumber(); + assertEquals("Message ID was wrong", msgID, 9L); + msgID = entries.get(1).getMessage().getMessageNumber(); + assertEquals("Message ID was wrong", msgID, 10L); + } + + public void testEnqueueDequeueOfPersistentMessageToNonDurableQueue() throws AMQException + { + // Create IncomingMessage and nondurable queue + final IncomingMessage msg = new IncomingMessage(info); + ContentHeaderBody contentHeaderBody = new ContentHeaderBody(); + contentHeaderBody.setProperties(new BasicContentHeaderProperties()); + ((BasicContentHeaderProperties) contentHeaderBody.getProperties()).setDeliveryMode((byte) 2); + msg.setContentHeaderBody(contentHeaderBody); + + final ArrayList<BaseQueue> qs = new ArrayList<BaseQueue>(); + + // Send persistent message + + qs.add(_queue); + MessageMetaData metaData = msg.headersReceived(); + StoredMessage handle = _store.addMessage(metaData); + msg.setStoredMessage(handle); + + + ServerTransaction txn = new AutoCommitTransaction(_store); + + txn.enqueue(qs, msg, new ServerTransaction.Action() + { + public void postCommit() + { + msg.enqueue(qs); + } + + public void onRollback() + { + } + }); + + + + // Check that it is enqueued + AMQQueue data = _store.getMessages().get(1L); + assertNull(data); + + // Dequeue message + MockQueueEntry entry = new MockQueueEntry(); + AMQMessage amqmsg = new AMQMessage(handle); + + entry.setMessage(amqmsg); + _queue.dequeue(entry,null); + + // Check that it is dequeued + data = _store.getMessages().get(1L); + assertNull(data); + } + + + /** + * processQueue() is used when asynchronously delivering messages to + * subscriptions which could not be delivered immediately during the + * enqueue() operation. + * + * A defect within the method would mean that delivery of these messages may + * not occur should the Runner stop before all messages have been processed. + * Such a defect was discovered when Selectors were used such that one and + * only one subscription can/will accept any given messages, but multiple + * subscriptions are present, and one of the earlier subscriptions receives + * more messages than the others. + * + * This test is to validate that the processQueue() method is able to + * correctly deliver all of the messages present for asynchronous delivery + * to subscriptions in such a scenario. + */ + public void testProcessQueueWithUniqueSelectors() throws Exception + { + TestSimpleQueueEntryListFactory factory = new TestSimpleQueueEntryListFactory(); + SimpleAMQQueue testQueue = new SimpleAMQQueue("testQueue", false, "testOwner",false, + false, _virtualHost, factory, null) + { + @Override + public void deliverAsync(Subscription sub) + { + // do nothing, i.e prevent deliveries by the SubFlushRunner + // when registering the new subscriptions + } + }; + + // retrieve the QueueEntryList the queue creates and insert the test + // messages, thus avoiding straight-through delivery attempts during + //enqueue() process. + QueueEntryList list = factory.getQueueEntryList(); + assertNotNull("QueueEntryList should have been created", list); + + QueueEntry msg1 = list.add(createMessage(1L)); + QueueEntry msg2 = list.add(createMessage(2L)); + QueueEntry msg3 = list.add(createMessage(3L)); + QueueEntry msg4 = list.add(createMessage(4L)); + QueueEntry msg5 = list.add(createMessage(5L)); + + // Create lists of the entries each subscription should be interested + // in.Bias over 50% of the messages to the first subscription so that + // the later subscriptions reject them and report being done before + // the first subscription as the processQueue method proceeds. + List<QueueEntry> msgListSub1 = createEntriesList(msg1, msg2, msg3); + List<QueueEntry> msgListSub2 = createEntriesList(msg4); + List<QueueEntry> msgListSub3 = createEntriesList(msg5); + + MockSubscription sub1 = new MockSubscription(msgListSub1); + MockSubscription sub2 = new MockSubscription(msgListSub2); + MockSubscription sub3 = new MockSubscription(msgListSub3); + + // register the subscriptions + testQueue.registerSubscription(sub1, false); + testQueue.registerSubscription(sub2, false); + testQueue.registerSubscription(sub3, false); + + //check that no messages have been delivered to the + //subscriptions during registration + assertEquals("No messages should have been delivered yet", 0, sub1.getMessages().size()); + assertEquals("No messages should have been delivered yet", 0, sub2.getMessages().size()); + assertEquals("No messages should have been delivered yet", 0, sub3.getMessages().size()); + + // call processQueue to deliver the messages + testQueue.processQueue(new QueueRunner(testQueue, 1) + { + @Override + public void run() + { + // we dont actually want/need this runner to do any work + // because we we are already doing it! + } + }); + + // check expected messages delivered to correct consumers + verifyRecievedMessages(msgListSub1, sub1.getMessages()); + verifyRecievedMessages(msgListSub2, sub2.getMessages()); + verifyRecievedMessages(msgListSub3, sub3.getMessages()); + } + + private List<QueueEntry> createEntriesList(QueueEntry... entries) + { + ArrayList<QueueEntry> entriesList = new ArrayList<QueueEntry>(); + for (QueueEntry entry : entries) + { + entriesList.add(entry); + } + return entriesList; + } + + private void verifyRecievedMessages(List<QueueEntry> expected, + List<QueueEntry> delivered) + { + assertEquals("Consumer did not receive the expected number of messages", + expected.size(), delivered.size()); + + for (QueueEntry msg : expected) + { + assertTrue("Consumer did not recieve msg: " + + msg.getMessage().getMessageNumber(), delivered.contains(msg)); + } + } + + public class TestMessage extends AMQMessage + { + private final long _tag; + private int _count; + + TestMessage(long tag, long messageId, MessagePublishInfo publishBody) + throws AMQException + { + this(tag, messageId, publishBody, new ContentHeaderBody(1, 1, new BasicContentHeaderProperties(), 0)); + + } + TestMessage(long tag, long messageId, MessagePublishInfo publishBody, ContentHeaderBody chb) + throws AMQException + { + super(new MockStoredMessage(messageId, publishBody, chb)); + _tag = tag; + } + + public boolean incrementReference() + { + _count++; + return true; + } + + public void decrementReference() + { + _count--; + } + + void assertCountEquals(int expected) + { + assertEquals("Wrong count for message with tag " + _tag, expected, _count); + } + } + + protected AMQMessage createMessage(Long id) throws AMQException + { + AMQMessage messageA = new TestMessage(id, id, info); + return messageA; + } + + class TestSimpleQueueEntryListFactory implements QueueEntryListFactory + { + QueueEntryList _list; + + public QueueEntryList createQueueEntryList(AMQQueue queue) + { + _list = new SimpleQueueEntryList(queue); + return _list; + } + + public QueueEntryList getQueueEntryList() + { + return _list; + } + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/SimpleAMQQueueThreadPoolTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/SimpleAMQQueueThreadPoolTest.java new file mode 100644 index 0000000000..a40dc5670f --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/SimpleAMQQueueThreadPoolTest.java @@ -0,0 +1,59 @@ +/* + * + * 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.qpid.server.queue; + +import junit.framework.TestCase; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.pool.ReferenceCountingExecutorService; +import org.apache.qpid.server.util.InternalBrokerBaseCase; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.AMQException; + +public class SimpleAMQQueueThreadPoolTest extends InternalBrokerBaseCase +{ + + public void test() throws AMQException + { + int initialCount = ReferenceCountingExecutorService.getInstance().getReferenceCount(); + VirtualHost test = ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHost("test"); + + try + { + SimpleAMQQueue queue = (SimpleAMQQueue) AMQQueueFactory.createAMQQueueImpl(new AMQShortString("test"), false, + new AMQShortString("owner"), + false, false, test, null); + + assertFalse("Creation did not start Pool.", ReferenceCountingExecutorService.getInstance().getPool().isShutdown()); + + assertEquals("References not increased", initialCount + 1, ReferenceCountingExecutorService.getInstance().getReferenceCount()); + + queue.stop(); + + assertEquals("References not decreased", initialCount , ReferenceCountingExecutorService.getInstance().getReferenceCount()); + } + finally + { + ApplicationRegistry.remove(); + } + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/SimpleQueueEntryListTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/SimpleQueueEntryListTest.java new file mode 100644 index 0000000000..320a75045a --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/SimpleQueueEntryListTest.java @@ -0,0 +1,159 @@ +/* +* +* 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.qpid.server.queue; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.qpid.server.message.AMQMessage; + +import junit.framework.TestCase; + +public class SimpleQueueEntryListTest extends TestCase +{ + private static final String SCAVENGE_PROP = "qpid.queue.scavenge_count"; + String oldScavengeValue = null; + + @Override + protected void setUp() + { + oldScavengeValue = System.setProperty(SCAVENGE_PROP, "9"); + } + + @Override + protected void tearDown() + { + if(oldScavengeValue != null) + { + System.setProperty(SCAVENGE_PROP, oldScavengeValue); + } + else + { + System.clearProperty(SCAVENGE_PROP); + } + } + + /** + * Tests the behavior of the next(QueuyEntry) method. + */ + public void testNext() throws Exception + { + SimpleQueueEntryList sqel = new SimpleQueueEntryList(null); + int i = 0; + + QueueEntry queueEntry1 = sqel.add(new MockAMQMessage(i++)); + QueueEntry queueEntry2 = sqel.add(new MockAMQMessage(i++)); + + assertSame(queueEntry2, sqel.next(queueEntry1)); + assertNull(sqel.next(queueEntry2)); + } + + public void testScavenge() throws Exception + { + SimpleQueueEntryList sqel = new SimpleQueueEntryList(null); + ConcurrentHashMap<Integer,QueueEntry> entriesMap = new ConcurrentHashMap<Integer,QueueEntry>(); + + + //Add messages to generate QueueEntry's + for(int i = 1; i <= 100 ; i++) + { + AMQMessage msg = new MockAMQMessage(i); + QueueEntry bleh = sqel.add(msg); + assertNotNull("QE should not have been null", bleh); + entriesMap.put(i,bleh); + } + + QueueEntryImpl head = ((QueueEntryImpl) sqel.getHead()); + + //We shall now delete some specific messages mid-queue that will lead to + //requiring a scavenge once the requested threshold of 9 deletes is passed + + //Delete the 2nd message only + assertTrue("Failed to delete QueueEntry", entriesMap.remove(2).delete()); + verifyDeletedButPresentBeforeScavenge(head, 2); + + //Delete messages 12 to 14 + assertTrue("Failed to delete QueueEntry", entriesMap.remove(12).delete()); + verifyDeletedButPresentBeforeScavenge(head, 12); + assertTrue("Failed to delete QueueEntry", entriesMap.remove(13).delete()); + verifyDeletedButPresentBeforeScavenge(head, 13); + assertTrue("Failed to delete QueueEntry", entriesMap.remove(14).delete()); + verifyDeletedButPresentBeforeScavenge(head, 14); + + + //Delete message 20 only + assertTrue("Failed to delete QueueEntry", entriesMap.remove(20).delete()); + verifyDeletedButPresentBeforeScavenge(head, 20); + + //Delete messages 81 to 84 + assertTrue("Failed to delete QueueEntry", entriesMap.remove(81).delete()); + verifyDeletedButPresentBeforeScavenge(head, 81); + assertTrue("Failed to delete QueueEntry", entriesMap.remove(82).delete()); + verifyDeletedButPresentBeforeScavenge(head, 82); + assertTrue("Failed to delete QueueEntry", entriesMap.remove(83).delete()); + verifyDeletedButPresentBeforeScavenge(head, 83); + assertTrue("Failed to delete QueueEntry", entriesMap.remove(84).delete()); + verifyDeletedButPresentBeforeScavenge(head, 84); + + //Delete message 99 - this is the 10th message deleted that is after the queue head + //and so will invoke the scavenge() which is set to go after 9 previous deletions + assertTrue("Failed to delete QueueEntry", entriesMap.remove(99).delete()); + + verifyAllDeletedMessagedNotPresent(head, entriesMap); + } + + private void verifyDeletedButPresentBeforeScavenge(QueueEntryImpl head, long messageId) + { + //Use the head to get the initial entry in the queue + QueueEntryImpl entry = head._next; + + for(long i = 1; i < messageId ; i++) + { + assertEquals("Expected QueueEntry was not found in the list", i, (long) entry.getMessage().getMessageNumber()); + entry = entry._next; + } + + assertTrue("Entry should have been deleted", entry.isDeleted()); + } + + private void verifyAllDeletedMessagedNotPresent(QueueEntryImpl head, Map<Integer,QueueEntry> remainingMessages) + { + //Use the head to get the initial entry in the queue + QueueEntryImpl entry = head._next; + + assertNotNull("Initial entry should not have been null", entry); + + int count = 0; + + while (entry != null) + { + assertFalse("Entry " + entry.getMessage().getMessageNumber() + " should not have been deleted", entry.isDeleted()); + assertNotNull("QueueEntry was not found in the list of remaining entries", + remainingMessages.get(entry.getMessage().getMessageNumber().intValue())); + + count++; + entry = entry._next; + } + + assertEquals("Count should have been equal",count,remainingMessages.size()); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/registry/ApplicationRegistryShutdownTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/registry/ApplicationRegistryShutdownTest.java new file mode 100644 index 0000000000..e45c8d7b96 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/registry/ApplicationRegistryShutdownTest.java @@ -0,0 +1,105 @@ +/* + * + * 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.qpid.server.registry; + +import org.apache.qpid.server.util.InternalBrokerBaseCase; + +import java.security.Security; +import java.security.Provider; +import java.util.List; +import java.util.LinkedList; + +/** + * QPID-1390 : Test to validate that the AuthenticationManger can successfully unregister any new SASL providers when + * The ApplicationRegistry is closed. + * + * This should be expanded as QPID-1399 is implemented. + */ +public class ApplicationRegistryShutdownTest extends InternalBrokerBaseCase +{ + + Provider[] _defaultProviders; + @Override + public void setUp() throws Exception + { + // Get default providers + _defaultProviders = Security.getProviders(); + + //Startup the new broker and register the new providers + super.setUp(); + } + + + /** + * QPID-1399 : Ensure that the Authentiction manager unregisters any SASL providers created during + * ApplicationRegistry initialisation. + * + */ + public void testAuthenticationMangerCleansUp() throws Exception + { + + // Get the providers after initialisation + Provider[] providersAfterInitialisation = Security.getProviders(); + + // Find the additions + List additions = new LinkedList(); + for (Provider afterInit : providersAfterInitialisation) + { + boolean found = false; + for (Provider defaultProvider : _defaultProviders) + { + if (defaultProvider == afterInit) + { + found=true; + break; + } + } + + // Record added registies + if (!found) + { + additions.add(afterInit); + } + } + + // Not using isEmpty as that is not in Java 5 + assertTrue("No new SASL mechanisms added by initialisation.", additions.size() != 0 ); + + //Close the registry which will perform the close the AuthenticationManager + getRegistry().close(); + + //Validate that the SASL plugFins have been removed. + Provider[] providersAfterClose = Security.getProviders(); + + assertTrue("No providers unregistered", providersAfterInitialisation.length > providersAfterClose.length); + + //Ensure that the additions are not still present after close(). + for (Provider afterClose : providersAfterClose) + { + assertFalse("Added provider not unregistered", additions.contains(afterClose)); + } + } + + + + + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabaseTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabaseTest.java new file mode 100644 index 0000000000..2ab15d4872 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabaseTest.java @@ -0,0 +1,466 @@ +/* + * + * 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.qpid.server.security.auth.database; + +import junit.framework.TestCase; + +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.AccountNotFoundException; + +import org.apache.commons.codec.binary.Base64; +import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.Principal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +public class Base64MD5PasswordFilePrincipalDatabaseTest extends TestCase +{ + + private static final String TEST_COMMENT = "# Test Comment"; + + private static final String USERNAME = "testUser"; + private static final String PASSWORD = "guest"; + private static final String PASSWORD_B64MD5HASHED = "CE4DQ6BIb/BVMN9scFyLtA=="; + private static char[] PASSWORD_MD5_CHARS; + private static final String PRINCIPAL_USERNAME = "testUserPrincipal"; + private static final Principal PRINCIPAL = new UsernamePrincipal(PRINCIPAL_USERNAME); + private Base64MD5PasswordFilePrincipalDatabase _database; + private File _pwdFile; + private List<File> _testPwdFiles = new ArrayList<File>(); + + static + { + try + { + Base64 b64 = new Base64(); + byte[] md5passBytes = PASSWORD_B64MD5HASHED.getBytes(Base64MD5PasswordFilePrincipalDatabase.DEFAULT_ENCODING); + byte[] decoded = b64.decode(md5passBytes); + + PASSWORD_MD5_CHARS = new char[decoded.length]; + + int index = 0; + for (byte c : decoded) + { + PASSWORD_MD5_CHARS[index++] = (char) c; + } + } + catch (UnsupportedEncodingException e) + { + fail("Unable to perform B64 decode to get the md5 char[] password"); + } + } + + + public void setUp() throws Exception + { + _database = new Base64MD5PasswordFilePrincipalDatabase(); + _pwdFile = File.createTempFile(this.getClass().getName(), "pwd"); + _pwdFile.deleteOnExit(); + _database.setPasswordFile(_pwdFile.getAbsolutePath()); + _testPwdFiles.clear(); + } + + public void tearDown() throws Exception + { + //clean up the created default password file and any backup + File oldPwdFile = new File(_pwdFile.getAbsolutePath() + ".old"); + if(oldPwdFile.exists()) + { + oldPwdFile.delete(); + } + + _pwdFile.delete(); + + //clean up any additional files and their backups + for(File f : _testPwdFiles) + { + oldPwdFile = new File(f.getAbsolutePath() + ".old"); + if(oldPwdFile.exists()) + { + oldPwdFile.delete(); + } + + f.delete(); + } + } + + private File createPasswordFile(int commentLines, int users) + { + try + { + File testFile = File.createTempFile("Base64MD5PDPDTest","tmp"); + testFile.deleteOnExit(); + + BufferedWriter writer = new BufferedWriter(new FileWriter(testFile)); + + for (int i = 0; i < commentLines; i++) + { + writer.write(TEST_COMMENT); + writer.newLine(); + } + + for (int i = 0; i < users; i++) + { + writer.write(USERNAME + i + ":Password"); + writer.newLine(); + } + + writer.flush(); + writer.close(); + + _testPwdFiles.add(testFile); + + return testFile; + + } + catch (IOException e) + { + fail("Unable to create test password file." + e.getMessage()); + } + + return null; + } + + private void loadPasswordFile(File file) + { + try + { + _database.setPasswordFile(file.toString()); + } + catch (IOException e) + { + fail("Password File was not created." + e.getMessage()); + } + } + + /** **** Test Methods ************** */ + + public void testCreatePrincipal() + { + File testFile = createPasswordFile(1, 0); + + loadPasswordFile(testFile); + + + Principal principal = new Principal() + { + public String getName() + { + return USERNAME; + } + }; + + assertTrue("New user not created.", _database.createPrincipal(principal, PASSWORD.toCharArray())); + + PasswordCallback callback = new PasswordCallback("prompt",false); + try + { + _database.setPassword(principal, callback); + } + catch (AccountNotFoundException e) + { + fail("user account did not exist"); + } + assertTrue("Password returned was incorrect.", Arrays.equals(PASSWORD_MD5_CHARS, callback.getPassword())); + + loadPasswordFile(testFile); + + try + { + _database.setPassword(principal, callback); + } + catch (AccountNotFoundException e) + { + fail("user account did not exist"); + } + assertTrue("Password returned was incorrect.", Arrays.equals(PASSWORD_MD5_CHARS, callback.getPassword())); + + assertNotNull("Created User was not saved", _database.getUser(USERNAME)); + + assertFalse("Duplicate user created.", _database.createPrincipal(principal, PASSWORD.toCharArray())); + } + + public void testCreatePrincipalIsSavedToFile() + { + + File testFile = createPasswordFile(1, 0); + + loadPasswordFile(testFile); + + final String CREATED_PASSWORD = "guest"; + final String CREATED_B64MD5HASHED_PASSWORD = "CE4DQ6BIb/BVMN9scFyLtA=="; + final String CREATED_USERNAME = "createdUser"; + + Principal principal = new Principal() + { + public String getName() + { + return CREATED_USERNAME; + } + }; + + _database.createPrincipal(principal, CREATED_PASSWORD.toCharArray()); + + try + { + BufferedReader reader = new BufferedReader(new FileReader(testFile)); + + assertTrue("File has no content", reader.ready()); + + assertEquals("Comment line has been corrupted.", TEST_COMMENT, reader.readLine()); + + assertTrue("File is missing user data.", reader.ready()); + + String userLine = reader.readLine(); + + String[] result = Pattern.compile(":").split(userLine); + + assertEquals("User line not complete '" + userLine + "'", 2, result.length); + + assertEquals("Username not correct,", CREATED_USERNAME, result[0]); + assertEquals("Password not correct,", CREATED_B64MD5HASHED_PASSWORD, result[1]); + + assertFalse("File has more content", reader.ready()); + + } + catch (IOException e) + { + fail("Unable to valdate file contents due to:" + e.getMessage()); + } + } + + + public void testDeletePrincipal() + { + File testFile = createPasswordFile(1, 1); + + loadPasswordFile(testFile); + + Principal user = _database.getUser(USERNAME + "0"); + assertNotNull("Generated user not present.", user); + + try + { + _database.deletePrincipal(user); + } + catch (AccountNotFoundException e) + { + fail("User should be present" + e.getMessage()); + } + + try + { + _database.deletePrincipal(user); + fail("User should not be present"); + } + catch (AccountNotFoundException e) + { + //pass + } + + loadPasswordFile(testFile); + + try + { + _database.deletePrincipal(user); + fail("User should not be present"); + } + catch (AccountNotFoundException e) + { + //pass + } + + assertNull("Deleted user still present.", _database.getUser(USERNAME + "0")); + } + + public void testGetUsers() + { + int USER_COUNT = 10; + File testFile = createPasswordFile(1, USER_COUNT); + + loadPasswordFile(testFile); + + Principal user = _database.getUser("MISSING_USERNAME"); + assertNull("Missing user present.", user); + + List<Principal> users = _database.getUsers(); + + assertNotNull("Users list is null.", users); + + assertEquals(USER_COUNT, users.size()); + + boolean[] verify = new boolean[USER_COUNT]; + for (int i = 0; i < USER_COUNT; i++) + { + Principal principal = users.get(i); + + assertNotNull("Generated user not present.", principal); + + String name = principal.getName(); + + int id = Integer.parseInt(name.substring(USERNAME.length())); + + assertFalse("Duplicated username retrieve", verify[id]); + verify[id] = true; + } + + for (int i = 0; i < USER_COUNT; i++) + { + assertTrue("User " + i + " missing", verify[i]); + } + } + + public void testUpdatePasswordIsSavedToFile() + { + + File testFile = createPasswordFile(1, 1); + + loadPasswordFile(testFile); + + Principal testUser = _database.getUser(USERNAME + "0"); + + assertNotNull(testUser); + + String NEW_PASSWORD = "guest"; + String NEW_PASSWORD_HASH = "CE4DQ6BIb/BVMN9scFyLtA=="; + try + { + _database.updatePassword(testUser, NEW_PASSWORD.toCharArray()); + } + catch (AccountNotFoundException e) + { + fail(e.toString()); + } + + try + { + BufferedReader reader = new BufferedReader(new FileReader(testFile)); + + assertTrue("File has no content", reader.ready()); + + assertEquals("Comment line has been corrupted.", TEST_COMMENT, reader.readLine()); + + assertTrue("File is missing user data.", reader.ready()); + + String userLine = reader.readLine(); + + String[] result = Pattern.compile(":").split(userLine); + + assertEquals("User line not complete '" + userLine + "'", 2, result.length); + + assertEquals("Username not correct,", USERNAME + "0", result[0]); + assertEquals("New Password not correct,", NEW_PASSWORD_HASH, result[1]); + + assertFalse("File has more content", reader.ready()); + + } + catch (IOException e) + { + fail("Unable to valdate file contents due to:" + e.getMessage()); + } + } + + public void testSetPasswordFileWithMissingFile() + { + try + { + _database.setPasswordFile("DoesntExist"); + } + catch (FileNotFoundException fnfe) + { + assertTrue(fnfe.getMessage(), fnfe.getMessage().startsWith("Cannot find password file")); + } + catch (IOException e) + { + fail("Password File was not created." + e.getMessage()); + } + + } + + public void testSetPasswordFileWithReadOnlyFile() + { + + File testFile = createPasswordFile(0, 0); + + testFile.setReadOnly(); + + try + { + _database.setPasswordFile(testFile.toString()); + } + catch (FileNotFoundException fnfe) + { + assertTrue(fnfe.getMessage().startsWith("Cannot read password file ")); + } + catch (IOException e) + { + fail("Password File was not created." + e.getMessage()); + } + } + + public void testCreateUserPrincipal() throws IOException + { + _database.createPrincipal(PRINCIPAL, PASSWORD.toCharArray()); + Principal newPrincipal = _database.getUser(PRINCIPAL_USERNAME); + assertNotNull(newPrincipal); + assertEquals(PRINCIPAL.getName(), newPrincipal.getName()); + } + + public void testVerifyPassword() throws IOException, AccountNotFoundException + { + testCreateUserPrincipal(); + //assertFalse(_pwdDB.verifyPassword(_username, null)); + assertFalse(_database.verifyPassword(PRINCIPAL_USERNAME, new char[]{})); + assertFalse(_database.verifyPassword(PRINCIPAL_USERNAME, (PASSWORD+"z").toCharArray())); + assertTrue(_database.verifyPassword(PRINCIPAL_USERNAME, PASSWORD.toCharArray())); + + try + { + _database.verifyPassword("made.up.username", PASSWORD.toCharArray()); + fail("Should not have been able to verify this non-existant users password."); + } + catch (AccountNotFoundException e) + { + // pass + } + } + + public void testUpdatePassword() throws IOException, AccountNotFoundException + { + testCreateUserPrincipal(); + char[] newPwd = "newpassword".toCharArray(); + _database.updatePassword(PRINCIPAL, newPwd); + assertFalse(_database.verifyPassword(PRINCIPAL_USERNAME, PASSWORD.toCharArray())); + assertTrue(_database.verifyPassword(PRINCIPAL_USERNAME, newPwd)); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/HashedUserTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/HashedUserTest.java new file mode 100644 index 0000000000..aa85cac758 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/HashedUserTest.java @@ -0,0 +1,95 @@ +/* + * + * 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.qpid.server.security.auth.database; + +import junit.framework.TestCase; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; + +import java.io.UnsupportedEncodingException; + +/* + Note User is mainly tested by Base64MD5PFPDTest this is just to catch the extra methods + */ +public class HashedUserTest extends TestCase +{ + + String USERNAME = "username"; + String PASSWORD = "password"; + String B64_ENCODED_PASSWORD = "cGFzc3dvcmQ="; + + public void testToLongArrayConstructor() + { + try + { + HashedUser user = new HashedUser(new String[]{USERNAME, PASSWORD, USERNAME}); + fail("Error expected"); + } + catch (IllegalArgumentException e) + { + assertEquals("User Data should be length 2, username, password", e.getMessage()); + } + catch (UnsupportedEncodingException e) + { + fail(e.getMessage()); + } + } + + public void testArrayConstructor() + { + try + { + HashedUser user = new HashedUser(new String[]{USERNAME, B64_ENCODED_PASSWORD}); + assertEquals("Username incorrect", USERNAME, user.getName()); + int index = 0; + + char[] hash = B64_ENCODED_PASSWORD.toCharArray(); + + try + { + for (byte c : user.getEncodedPassword()) + { + assertEquals("Password incorrect", hash[index], (char) c); + index++; + } + } + catch (Exception e) + { + fail(e.getMessage()); + } + + hash = PASSWORD.toCharArray(); + + index=0; + for (char c : user.getPassword()) + { + assertEquals("Password incorrect", hash[index], c); + index++; + } + + } + catch (UnsupportedEncodingException e) + { + fail(e.getMessage()); + } + } +} + diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabaseTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabaseTest.java new file mode 100644 index 0000000000..a3dad19bb4 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabaseTest.java @@ -0,0 +1,416 @@ +/* + * + * 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.qpid.server.security.auth.database; + +import junit.framework.TestCase; + +import javax.security.auth.login.AccountNotFoundException; + +import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.security.Principal; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +public class PlainPasswordFilePrincipalDatabaseTest extends TestCase +{ + + private static final String TEST_COMMENT = "# Test Comment"; + private static final String TEST_PASSWORD = "testPassword"; + private static final char[] TEST_PASSWORD_CHARS = TEST_PASSWORD.toCharArray(); + private static final String TEST_USERNAME = "testUser"; + + private Principal _principal = new UsernamePrincipal(TEST_USERNAME); + private PlainPasswordFilePrincipalDatabase _database; + private List<File> _testPwdFiles = new ArrayList<File>(); + + public void setUp() throws Exception + { + _database = new PlainPasswordFilePrincipalDatabase(); + _testPwdFiles.clear(); + } + + public void tearDown() throws Exception + { + //clean up any additional files and their backups + for(File f : _testPwdFiles) + { + File oldPwdFile = new File(f.getAbsolutePath() + ".old"); + if(oldPwdFile.exists()) + { + oldPwdFile.delete(); + } + + f.delete(); + } + } + + // ******* Test Methods ********** // + + public void testCreatePrincipal() + { + File testFile = createPasswordFile(1, 0); + + loadPasswordFile(testFile); + + final String CREATED_PASSWORD = "guest"; + final String CREATED_USERNAME = "createdUser"; + + Principal principal = new Principal() + { + public String getName() + { + return CREATED_USERNAME; + } + }; + + assertTrue("New user not created.", _database.createPrincipal(principal, CREATED_PASSWORD.toCharArray())); + + loadPasswordFile(testFile); + + assertNotNull("Created User was not saved", _database.getUser(CREATED_USERNAME)); + + assertFalse("Duplicate user created.", _database.createPrincipal(principal, CREATED_PASSWORD.toCharArray())); + + testFile.delete(); + } + + public void testCreatePrincipalIsSavedToFile() + { + + File testFile = createPasswordFile(1, 0); + + loadPasswordFile(testFile); + + Principal principal = new Principal() + { + public String getName() + { + return TEST_USERNAME; + } + }; + + _database.createPrincipal(principal, TEST_PASSWORD_CHARS); + + try + { + BufferedReader reader = new BufferedReader(new FileReader(testFile)); + + assertTrue("File has no content", reader.ready()); + + assertEquals("Comment line has been corrupted.", TEST_COMMENT, reader.readLine()); + + assertTrue("File is missing user data.", reader.ready()); + + String userLine = reader.readLine(); + + String[] result = Pattern.compile(":").split(userLine); + + assertEquals("User line not complete '" + userLine + "'", 2, result.length); + + assertEquals("Username not correct,", TEST_USERNAME, result[0]); + assertEquals("Password not correct,", TEST_PASSWORD, result[1]); + + assertFalse("File has more content", reader.ready()); + + } + catch (IOException e) + { + fail("Unable to valdate file contents due to:" + e.getMessage()); + } + testFile.delete(); + } + + public void testDeletePrincipal() + { + File testFile = createPasswordFile(1, 1); + + loadPasswordFile(testFile); + + Principal user = _database.getUser(TEST_USERNAME + "0"); + assertNotNull("Generated user not present.", user); + + try + { + _database.deletePrincipal(user); + } + catch (AccountNotFoundException e) + { + fail("User should be present" + e.getMessage()); + } + + try + { + _database.deletePrincipal(user); + fail("User should not be present"); + } + catch (AccountNotFoundException e) + { + //pass + } + + loadPasswordFile(testFile); + + try + { + _database.deletePrincipal(user); + fail("User should not be present"); + } + catch (AccountNotFoundException e) + { + //pass + } + + assertNull("Deleted user still present.", _database.getUser(TEST_USERNAME + "0")); + + testFile.delete(); + } + + public void testGetUsers() + { + int USER_COUNT = 10; + File testFile = createPasswordFile(1, USER_COUNT); + + loadPasswordFile(testFile); + + Principal user = _database.getUser("MISSING_USERNAME"); + assertNull("Missing user present.", user); + + List<Principal> users = _database.getUsers(); + + assertNotNull("Users list is null.", users); + + assertEquals(USER_COUNT, users.size()); + + boolean[] verify = new boolean[USER_COUNT]; + for (int i = 0; i < USER_COUNT; i++) + { + Principal principal = users.get(i); + + assertNotNull("Generated user not present.", principal); + + String name = principal.getName(); + + int id = Integer.parseInt(name.substring(TEST_USERNAME.length())); + + assertFalse("Duplicated username retrieve", verify[id]); + verify[id] = true; + } + + for (int i = 0; i < USER_COUNT; i++) + { + assertTrue("User " + i + " missing", verify[i]); + } + + testFile.delete(); + } + + public void testUpdatePasswordIsSavedToFile() + { + + File testFile = createPasswordFile(1, 1); + + loadPasswordFile(testFile); + + Principal testUser = _database.getUser(TEST_USERNAME + "0"); + + assertNotNull(testUser); + + String NEW_PASSWORD = "NewPassword"; + try + { + _database.updatePassword(testUser, NEW_PASSWORD.toCharArray()); + } + catch (AccountNotFoundException e) + { + fail(e.toString()); + } + + try + { + BufferedReader reader = new BufferedReader(new FileReader(testFile)); + + assertTrue("File has no content", reader.ready()); + + assertEquals("Comment line has been corrupted.", TEST_COMMENT, reader.readLine()); + + assertTrue("File is missing user data.", reader.ready()); + + String userLine = reader.readLine(); + + String[] result = Pattern.compile(":").split(userLine); + + assertEquals("User line not complete '" + userLine + "'", 2, result.length); + + assertEquals("Username not correct,", TEST_USERNAME + "0", result[0]); + assertEquals("New Password not correct,", NEW_PASSWORD, result[1]); + + assertFalse("File has more content", reader.ready()); + + } + catch (IOException e) + { + fail("Unable to valdate file contents due to:" + e.getMessage()); + } + testFile.delete(); + } + + public void testSetPasswordFileWithMissingFile() + { + try + { + _database.setPasswordFile("DoesntExist"); + } + catch (FileNotFoundException fnfe) + { + assertTrue(fnfe.getMessage(), fnfe.getMessage().startsWith("Cannot find password file")); + } + catch (IOException e) + { + fail("Password File was not created." + e.getMessage()); + } + + } + + public void testSetPasswordFileWithReadOnlyFile() + { + + File testFile = createPasswordFile(0, 0); + + testFile.setReadOnly(); + + try + { + _database.setPasswordFile(testFile.toString()); + } + catch (FileNotFoundException fnfe) + { + assertTrue(fnfe.getMessage().startsWith("Cannot read password file ")); + } + catch (IOException e) + { + fail("Password File was not created." + e.getMessage()); + } + + testFile.delete(); + } + + private void createUserPrincipal() throws IOException + { + File testFile = createPasswordFile(0, 0); + loadPasswordFile(testFile); + + _database.createPrincipal(_principal, TEST_PASSWORD_CHARS); + Principal newPrincipal = _database.getUser(TEST_USERNAME); + assertNotNull(newPrincipal); + assertEquals(_principal.getName(), newPrincipal.getName()); + } + + public void testVerifyPassword() throws IOException, AccountNotFoundException + { + createUserPrincipal(); + assertFalse(_database.verifyPassword(TEST_USERNAME, new char[]{})); + assertFalse(_database.verifyPassword(TEST_USERNAME, "massword".toCharArray())); + assertTrue(_database.verifyPassword(TEST_USERNAME, TEST_PASSWORD_CHARS)); + + try + { + _database.verifyPassword("made.up.username", TEST_PASSWORD_CHARS); + fail("Should not have been able to verify this non-existant users password."); + } + catch (AccountNotFoundException e) + { + // pass + } + } + + public void testUpdatePassword() throws IOException, AccountNotFoundException + { + createUserPrincipal(); + char[] newPwd = "newpassword".toCharArray(); + _database.updatePassword(_principal, newPwd); + assertFalse(_database.verifyPassword(TEST_USERNAME, TEST_PASSWORD_CHARS)); + assertTrue(_database.verifyPassword(TEST_USERNAME, newPwd)); + } + + + + // *********** Utility Methods ******** // + + private File createPasswordFile(int commentLines, int users) + { + try + { + File testFile = File.createTempFile(this.getClass().getName(),"tmp"); + testFile.deleteOnExit(); + + BufferedWriter writer = new BufferedWriter(new FileWriter(testFile)); + + for (int i = 0; i < commentLines; i++) + { + writer.write(TEST_COMMENT); + writer.newLine(); + } + + for (int i = 0; i < users; i++) + { + writer.write(TEST_USERNAME + i + ":" + TEST_PASSWORD); + writer.newLine(); + } + + writer.flush(); + writer.close(); + + _testPwdFiles.add(testFile); + + return testFile; + + } + catch (IOException e) + { + fail("Unable to create test password file." + e.getMessage()); + } + + return null; + } + + private void loadPasswordFile(File file) + { + try + { + _database.setPasswordFile(file.toString()); + } + catch (IOException e) + { + fail("Password File was not created." + e.getMessage()); + } + } + + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PlainUserTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PlainUserTest.java new file mode 100644 index 0000000000..7f0843d46e --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PlainUserTest.java @@ -0,0 +1,78 @@ +/* + * + * 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.qpid.server.security.auth.database; + +import junit.framework.TestCase; + +/* + Note PlainUser is mainly tested by PlainPFPDTest, this is just to catch the extra methods + */ +public class PlainUserTest extends TestCase +{ + + String USERNAME = "username"; + String PASSWORD = "password"; + + public void testTooLongArrayConstructor() + { + try + { + PlainUser user = new PlainUser(new String[]{USERNAME, PASSWORD, USERNAME}); + fail("Error expected"); + } + catch (IllegalArgumentException e) + { + assertEquals("User Data should be length 2, username, password", e.getMessage()); + } + } + + public void testStringArrayConstructor() + { + PlainUser user = new PlainUser(new String[]{USERNAME, PASSWORD}); + assertEquals("Username incorrect", USERNAME, user.getName()); + int index = 0; + + char[] password = PASSWORD.toCharArray(); + + try + { + for (byte c : user.getPasswordBytes()) + { + assertEquals("Password incorrect", password[index], (char) c); + index++; + } + } + catch (Exception e) + { + fail(e.getMessage()); + } + + password = PASSWORD.toCharArray(); + + index=0; + for (char c : user.getPassword()) + { + assertEquals("Password incorrect", password[index], c); + index++; + } + } +} + diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManagerTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManagerTest.java new file mode 100644 index 0000000000..f51ce0b6c6 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManagerTest.java @@ -0,0 +1,209 @@ +/* + * + * 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.qpid.server.security.auth.manager; + +import java.security.Provider; +import java.security.Security; + +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.qpid.server.security.auth.AuthenticationResult; +import org.apache.qpid.server.security.auth.AuthenticationResult.AuthenticationStatus; +import org.apache.qpid.server.util.InternalBrokerBaseCase; + +/** + * + * Tests the public methods of PrincipalDatabaseAuthenticationManager. + * + */ +public class PrincipalDatabaseAuthenticationManagerTest extends InternalBrokerBaseCase +{ + private PrincipalDatabaseAuthenticationManager _manager = null; + + /** + * @see org.apache.qpid.server.util.InternalBrokerBaseCase#tearDown() + */ + @Override + public void tearDown() throws Exception + { + super.tearDown(); + if (_manager != null) + { + _manager.close(); + } + } + + /** + * @see org.apache.qpid.server.util.InternalBrokerBaseCase#setUp() + */ + @Override + public void setUp() throws Exception + { + super.setUp(); + + _manager = new PrincipalDatabaseAuthenticationManager(); + } + + /** + * Tests that the PDAM registers SASL mechanisms correctly with the runtime. + */ + public void testRegisteredMechanisms() throws Exception + { + assertNotNull(_manager.getMechanisms()); + // relies on those mechanisms attached to PropertiesPrincipalDatabaseManager + assertEquals("PLAIN CRAM-MD5", _manager.getMechanisms()); + + Provider qpidProvider = Security.getProvider(PrincipalDatabaseAuthenticationManager.PROVIDER_NAME); + assertNotNull(qpidProvider); + } + + /** + * Tests that the SASL factory method createSaslServer correctly + * returns a non-null implementation. + */ + public void testSaslMechanismCreation() throws Exception + { + SaslServer server = _manager.createSaslServer("CRAM-MD5", "localhost"); + assertNotNull(server); + // Merely tests the creation of the mechanism. Mechanisms themselves are tested + // by their own tests. + } + + /** + * + * Tests that the authenticate method correctly interprets an + * authentication success. + * + */ + public void testAuthenticationSuccess() throws Exception + { + SaslServer testServer = createTestSaslServer(true, false); + + AuthenticationResult result = _manager.authenticate(testServer, "12345".getBytes()); + assertEquals(AuthenticationStatus.SUCCESS, result.status); + } + + /** + * + * Tests that the authenticate method correctly interprets an + * authentication not complete. + * + */ + public void testAuthenticationNotCompleted() throws Exception + { + SaslServer testServer = createTestSaslServer(false, false); + + AuthenticationResult result = _manager.authenticate(testServer, "12345".getBytes()); + assertEquals(AuthenticationStatus.CONTINUE, result.status); + } + + /** + * + * Tests that the authenticate method correctly interprets an + * authentication error. + * + */ + public void testAuthenticationError() throws Exception + { + SaslServer testServer = createTestSaslServer(false, true); + + AuthenticationResult result = _manager.authenticate(testServer, "12345".getBytes()); + assertEquals(AuthenticationStatus.ERROR, result.status); + } + + /** + * Tests the ability to de-register the provider. + */ + public void testClose() throws Exception + { + assertEquals("PLAIN CRAM-MD5", _manager.getMechanisms()); + assertNotNull(Security.getProvider(PrincipalDatabaseAuthenticationManager.PROVIDER_NAME)); + + _manager.close(); + + // Check provider has been removed. + assertNull(_manager.getMechanisms()); + assertNull(Security.getProvider(PrincipalDatabaseAuthenticationManager.PROVIDER_NAME)); + _manager = null; + } + + /** + * Test SASL implementation used to test the authenticate() method. + */ + private SaslServer createTestSaslServer(final boolean complete, final boolean throwSaslException) + { + return new SaslServer() + { + + @Override + public String getMechanismName() + { + return null; + } + + @Override + public byte[] evaluateResponse(byte[] response) throws SaslException + { + if (throwSaslException) + { + throw new SaslException("Mocked exception"); + } + return null; + } + + @Override + public boolean isComplete() + { + return complete; + } + + @Override + public String getAuthorizationID() + { + return null; + } + + @Override + public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException + { + return null; + } + + @Override + public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException + { + return null; + } + + @Override + public Object getNegotiatedProperty(String propName) + { + return null; + } + + @Override + public void dispose() throws SaslException + { + } + }; + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticatorTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticatorTest.java new file mode 100644 index 0000000000..e8c24da68d --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticatorTest.java @@ -0,0 +1,267 @@ +/* + * + * 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.qpid.server.security.auth.rmi; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Collections; + +import javax.management.remote.JMXPrincipal; +import javax.security.auth.Subject; + +import org.apache.qpid.server.security.auth.database.Base64MD5PasswordFilePrincipalDatabase; +import org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase; + +import junit.framework.TestCase; + +public class RMIPasswordAuthenticatorTest extends TestCase +{ + private final String USERNAME = "guest"; + private final String PASSWORD = "guest"; + private final String B64_MD5HASHED_PASSWORD = "CE4DQ6BIb/BVMN9scFyLtA=="; + private RMIPasswordAuthenticator _rmipa; + + private Base64MD5PasswordFilePrincipalDatabase _md5Pd; + private File _md5PwdFile; + + private PlainPasswordFilePrincipalDatabase _plainPd; + private File _plainPwdFile; + + private Subject testSubject; + + protected void setUp() throws Exception + { + _rmipa = new RMIPasswordAuthenticator(); + + _md5Pd = new Base64MD5PasswordFilePrincipalDatabase(); + _md5PwdFile = createTempPasswordFile(this.getClass().getName()+"md5pwd", USERNAME, B64_MD5HASHED_PASSWORD); + _md5Pd.setPasswordFile(_md5PwdFile.getAbsolutePath()); + + _plainPd = new PlainPasswordFilePrincipalDatabase(); + _plainPwdFile = createTempPasswordFile(this.getClass().getName()+"plainpwd", USERNAME, PASSWORD); + _plainPd.setPasswordFile(_plainPwdFile.getAbsolutePath()); + + testSubject = new Subject(true, + Collections.singleton(new JMXPrincipal(USERNAME)), + Collections.EMPTY_SET, + Collections.EMPTY_SET); + } + + private File createTempPasswordFile(String filenamePrefix, String user, String password) + { + try + { + File testFile = File.createTempFile(filenamePrefix,"tmp"); + testFile.deleteOnExit(); + + BufferedWriter writer = new BufferedWriter(new FileWriter(testFile)); + + writer.write(user + ":" + password); + writer.newLine(); + + writer.flush(); + writer.close(); + + return testFile; + } + catch (IOException e) + { + fail("Unable to create temporary test password file." + e.getMessage()); + } + + return null; + } + + + //********** Test Methods *********// + + + public void testAuthenticate() + { + String[] credentials; + Subject newSubject; + + // Test when no PD has been set + try + { + credentials = new String[]{USERNAME, PASSWORD}; + newSubject = _rmipa.authenticate(credentials); + fail("SecurityException expected due to lack of principal database"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + RMIPasswordAuthenticator.UNABLE_TO_LOOKUP, se.getMessage()); + } + + //The PrincipalDatabase's are tested primarily by their own tests, but + //minimal tests are done here to exercise their usage in this area. + + // Test correct passwords are verified with an MD5 PD + try + { + _rmipa.setPrincipalDatabase(_md5Pd); + credentials = new String[]{USERNAME, PASSWORD}; + newSubject = _rmipa.authenticate(credentials); + assertTrue("Returned subject does not equal expected value", + newSubject.equals(testSubject)); + } + catch (Exception e) + { + fail("Unexpected Exception:" + e.getMessage()); + } + + // Test incorrect passwords are not verified with an MD5 PD + try + { + credentials = new String[]{USERNAME, PASSWORD+"incorrect"}; + newSubject = _rmipa.authenticate(credentials); + fail("SecurityException expected due to incorrect password"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + RMIPasswordAuthenticator.INVALID_CREDENTIALS, se.getMessage()); + } + + // Test non-existent accounts are not verified with an MD5 PD + try + { + credentials = new String[]{USERNAME+"invalid", PASSWORD}; + newSubject = _rmipa.authenticate(credentials); + fail("SecurityException expected due to non-existant account"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + RMIPasswordAuthenticator.INVALID_CREDENTIALS, se.getMessage()); + } + + // Test correct passwords are verified with a Plain PD + try + { + _rmipa.setPrincipalDatabase(_plainPd); + credentials = new String[]{USERNAME, PASSWORD}; + newSubject = _rmipa.authenticate(credentials); + assertTrue("Returned subject does not equal expected value", + newSubject.equals(testSubject)); + } + catch (Exception e) + { + fail("Unexpected Exception"); + } + + // Test incorrect passwords are not verified with a Plain PD + try + { + credentials = new String[]{USERNAME, PASSWORD+"incorrect"}; + newSubject = _rmipa.authenticate(credentials); + fail("SecurityException expected due to incorrect password"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + RMIPasswordAuthenticator.INVALID_CREDENTIALS, se.getMessage()); + } + + // Test non-existent accounts are not verified with an Plain PD + try + { + credentials = new String[]{USERNAME+"invalid", PASSWORD}; + newSubject = _rmipa.authenticate(credentials); + fail("SecurityException expected due to non existant account"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + RMIPasswordAuthenticator.INVALID_CREDENTIALS, se.getMessage()); + } + + // Test handling of non-string credential's + try + { + Object[] objCredentials = new Object[]{USERNAME, PASSWORD}; + newSubject = _rmipa.authenticate(objCredentials); + fail("SecurityException expected due to non string[] credentials"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + RMIPasswordAuthenticator.SHOULD_BE_STRING_ARRAY, se.getMessage()); + } + + // Test handling of incorrect number of credential's + try + { + credentials = new String[]{USERNAME, PASSWORD, PASSWORD}; + newSubject = _rmipa.authenticate(credentials); + fail("SecurityException expected due to supplying wrong number of credentials"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + RMIPasswordAuthenticator.SHOULD_HAVE_2_ELEMENTS, se.getMessage()); + } + + // Test handling of null credential's + try + { + //send a null array + credentials = null; + newSubject = _rmipa.authenticate(credentials); + fail("SecurityException expected due to not supplying an array of credentials"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + RMIPasswordAuthenticator.CREDENTIALS_REQUIRED, se.getMessage()); + } + + try + { + //send a null password + credentials = new String[]{USERNAME, null}; + newSubject = _rmipa.authenticate(credentials); + fail("SecurityException expected due to sending a null password"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + RMIPasswordAuthenticator.SHOULD_BE_NON_NULL, se.getMessage()); + } + + try + { + //send a null username + credentials = new String[]{null, PASSWORD}; + newSubject = _rmipa.authenticate(credentials); + fail("SecurityException expected due to sending a null username"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + RMIPasswordAuthenticator.SHOULD_BE_NON_NULL, se.getMessage()); + } + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/CRAMMD5HexInitialiserTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/CRAMMD5HexInitialiserTest.java new file mode 100644 index 0000000000..3c5ed1d6c2 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/CRAMMD5HexInitialiserTest.java @@ -0,0 +1,137 @@ +/* + * + * 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.qpid.server.security.auth.sasl; + +import junit.framework.TestCase; +import org.apache.qpid.server.security.auth.database.PropertiesPrincipalDatabase; +import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5HexInitialiser; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Properties; + +/** + * These tests ensure that the Hex wrapping that the initialiser performs does actually operate when the handle method is called. + */ +public class CRAMMD5HexInitialiserTest extends TestCase +{ + public void testHex() + { + //Create User details for testing + String user = "testUser"; + String password = "testPassword"; + + perform(user, password); + } + + public void testHashedHex() + { + //Create User details for testing + String user = "testUser"; + String password = "testPassword"; + + //Create a hashed password that we then attempt to put through the call back mechanism. + try + { + password = new String(MessageDigest.getInstance("MD5").digest(password.getBytes())); + } + catch (NoSuchAlgorithmException e) + { + fail(e.getMessage()); + } + + perform(user, password); + } + + public void perform(String user, String password) + { + CRAMMD5HexInitialiser initialiser = new CRAMMD5HexInitialiser(); + + //Use properties to create a PrincipalDatabase + Properties users = new Properties(); + users.put(user, password); + + PropertiesPrincipalDatabase db = new PropertiesPrincipalDatabase(users); + + initialiser.initialise(db); + + //setup the callbacks + PasswordCallback passwordCallback = new PasswordCallback("password:", false); + NameCallback usernameCallback = new NameCallback("user:", user); + + Callback[] callbacks = new Callback[]{usernameCallback, passwordCallback}; + + //Check the + try + { + assertNull("The password was not null before the handle call.", passwordCallback.getPassword()); + initialiser.getCallbackHandler().handle(callbacks); + } + catch (IOException e) + { + fail(e.getMessage()); + } + catch (UnsupportedCallbackException e) + { + fail(e.getMessage()); + } + + //Hex the password we initialised with and compare it with the passwordCallback + assertArrayEquals(toHex(password.toCharArray()), passwordCallback.getPassword()); + } + + private void assertArrayEquals(char[] expected, char[] actual) + { + assertEquals("Arrays are not the same length", expected.length, actual.length); + + for (int index = 0; index < expected.length; index++) + { + assertEquals("Characters are not equal", expected[index], actual[index]); + } + } + + private char[] toHex(char[] password) + { + StringBuilder sb = new StringBuilder(); + for (char c : password) + { + //toHexString does not prepend 0 so we have to + if (((byte) c > -1) && (byte) c < 10) + { + sb.append(0); + } + + sb.append(Integer.toHexString(c & 0xFF)); + } + + //Extract the hex string as char[] + char[] hex = new char[sb.length()]; + + sb.getChars(0, sb.length(), hex, 0); + + return hex; + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/CRAMMD5HexServerTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/CRAMMD5HexServerTest.java new file mode 100644 index 0000000000..8b3f9c0622 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/CRAMMD5HexServerTest.java @@ -0,0 +1,230 @@ +/* + * + * 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.qpid.server.security.auth.sasl; + +import java.io.File; +import java.io.IOException; +import java.security.MessageDigest; +import java.security.Principal; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import javax.security.auth.login.AccountNotFoundException; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import junit.framework.TestCase; + +import org.apache.commons.codec.binary.Hex; +import org.apache.qpid.server.security.auth.database.Base64MD5PasswordFilePrincipalDatabase; +import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5HexInitialiser; +import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5HexSaslServer; +import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5HexServerFactory; + +/** + * Test for the CRAM-MD5-HEX SASL mechanism. + * + * This test case focuses on testing {@link CRAMMD5HexSaslServer} but also exercises + * collaborators {@link CRAMMD5HexInitialiser} and {@link Base64MD5PasswordFilePrincipalDatabase} + */ +public class CRAMMD5HexServerTest extends TestCase +{ + + private SaslServer _saslServer; // Class under test + private CRAMMD5HexServerFactory _saslFactory; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + + CRAMMD5HexInitialiser _initializer = new CRAMMD5HexInitialiser(); + + //Use properties to create a PrincipalDatabase + Base64MD5PasswordFilePrincipalDatabase db = createTestPrincipalDatabase(); + assertEquals("Unexpected number of test users in the db", 2, db.getUsers().size()); + + _initializer.initialise(db); + + _saslFactory = new CRAMMD5HexServerFactory(); + + _saslServer = _saslFactory.createSaslServer(CRAMMD5HexSaslServer.MECHANISM, + "AMQP", + "localhost", + _initializer.getProperties(), + _initializer.getCallbackHandler()); + assertNotNull("Unable to create saslServer with mechanism type " + CRAMMD5HexSaslServer.MECHANISM, _saslServer); + + } + + public void testSuccessfulAuth() throws Exception + { + + final byte[] serverChallenge = _saslServer.evaluateResponse(new byte[0]); + + // Generate client response + final byte[] clientResponse = generateClientResponse("knownuser", "guest", serverChallenge); + + + byte[] nextServerChallenge = _saslServer.evaluateResponse(clientResponse); + assertTrue("Exchange must be flagged as complete after successful authentication", _saslServer.isComplete()); + assertNull("Next server challenge must be null after successful authentication", nextServerChallenge); + + } + + public void testKnownUserPresentsWrongPassword() throws Exception + { + byte[] serverChallenge = _saslServer.evaluateResponse(new byte[0]); + + + final byte[] clientResponse = generateClientResponse("knownuser", "wrong!", serverChallenge); + try + { + _saslServer.evaluateResponse(clientResponse); + fail("Exception not thrown"); + } + catch (SaslException se) + { + // PASS + } + assertFalse("Exchange must not be flagged as complete after unsuccessful authentication", _saslServer.isComplete()); + } + + public void testUnknownUser() throws Exception + { + final byte[] serverChallenge = _saslServer.evaluateResponse(new byte[0]); + + + final byte[] clientResponse = generateClientResponse("unknownuser", "guest", serverChallenge); + + try + { + _saslServer.evaluateResponse(clientResponse); + fail("Exception not thrown"); + } + catch (SaslException se) + { + assertExceptionHasUnderlyingAsCause(AccountNotFoundException.class, se); + // PASS + } + assertFalse("Exchange must not be flagged as complete after unsuccessful authentication", _saslServer.isComplete()); + } + + /** + * + * Demonstrates QPID-3158. A defect meant that users with some valid password were failing to + * authenticate when using the .NET 0-8 client (uses this SASL mechanism). + * It so happens that password "guest2" was one of the affected passwords. + * + * @throws Exception + */ + public void testSuccessfulAuthReproducingQpid3158() throws Exception + { + byte[] serverChallenge = _saslServer.evaluateResponse(new byte[0]); + + // Generate client response + byte[] resp = generateClientResponse("qpid3158user", "guest2", serverChallenge); + + byte[] nextServerChallenge = _saslServer.evaluateResponse(resp); + assertTrue("Exchange must be flagged as complete after successful authentication", _saslServer.isComplete()); + assertNull("Next server challenge must be null after successful authentication", nextServerChallenge); + } + + /** + * Since we don't have a CRAM-MD5-HEX implementation client implementation in Java, this method + * provides the implementation for first principals. + * + * @param userId user id + * @param clearTextPassword clear text password + * @param serverChallenge challenge from server + * + * @return challenge response + */ + private byte[] generateClientResponse(final String userId, final String clearTextPassword, final byte[] serverChallenge) throws Exception + { + byte[] digestedPasswordBytes = MessageDigest.getInstance("MD5").digest(clearTextPassword.getBytes()); + char[] hexEncodedDigestedPassword = Hex.encodeHex(digestedPasswordBytes); + byte[] hexEncodedDigestedPasswordBytes = new String(hexEncodedDigestedPassword).getBytes(); + + + Mac hmacMd5 = Mac.getInstance("HmacMD5"); + hmacMd5.init(new SecretKeySpec(hexEncodedDigestedPasswordBytes, "HmacMD5")); + final byte[] messageAuthenticationCode = hmacMd5.doFinal(serverChallenge); + + // Build client response + String responseAsString = userId + " " + new String(Hex.encodeHex(messageAuthenticationCode)); + byte[] resp = responseAsString.getBytes(); + return resp; + } + + /** + * Creates a test principal database. + * + * @return + * @throws IOException + */ + private Base64MD5PasswordFilePrincipalDatabase createTestPrincipalDatabase() throws IOException + { + Base64MD5PasswordFilePrincipalDatabase db = new Base64MD5PasswordFilePrincipalDatabase(); + File file = File.createTempFile("passwd", "db"); + file.deleteOnExit(); + db.setPasswordFile(file.getCanonicalPath()); + db.createPrincipal( createTestPrincipal("knownuser"), "guest".toCharArray()); + db.createPrincipal( createTestPrincipal("qpid3158user"), "guest2".toCharArray()); + return db; + } + + private Principal createTestPrincipal(final String name) + { + return new Principal() + { + + @Override + public String getName() + { + return name; + } + }; + } + + private void assertExceptionHasUnderlyingAsCause(final Class<? extends Throwable> expectedUnderlying, Throwable e) + { + assertNotNull(e); + int infiniteLoopGuard = 0; // Guard against loops in the cause chain + boolean foundExpectedUnderlying = false; + while (e.getCause() != null && infiniteLoopGuard++ < 10) + { + if (expectedUnderlying.equals(e.getCause().getClass())) + { + foundExpectedUnderlying = true; + break; + } + e = e.getCause(); + } + + if (!foundExpectedUnderlying) + { + fail("Not found expected underlying exception " + expectedUnderlying + " as underlying cause of " + e.getClass()); + } + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/SaslServerTestCase.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/SaslServerTestCase.java new file mode 100644 index 0000000000..f80413d4f8 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/SaslServerTestCase.java @@ -0,0 +1,66 @@ +/* + * + * 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.qpid.server.security.auth.sasl; + +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; + +import junit.framework.TestCase; + +public abstract class SaslServerTestCase extends TestCase +{ + protected SaslServer server; + protected String username = "u"; + protected String password = "p"; + protected String notpassword = "a"; + protected PrincipalDatabase db = new TestPrincipalDatabase(); + + protected byte[] correctresponse; + protected byte[] wrongresponse; + + public void testSucessfulAuth() throws SaslException + { + byte[] resp = this.server.evaluateResponse(correctresponse); + assertNull(resp); + } + + public void testFailAuth() + { + boolean exceptionCaught = false; + try + { + byte[] resp = this.server.evaluateResponse(wrongresponse); + } + catch (SaslException e) + { + assertEquals("Authentication failed", e.getCause().getMessage()); + exceptionCaught = true; + } + if (!exceptionCaught) + { + fail("Should have thrown SaslException"); + } + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/TestPrincipalDatabase.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/TestPrincipalDatabase.java new file mode 100644 index 0000000000..8507e49e17 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/TestPrincipalDatabase.java @@ -0,0 +1,91 @@ +/* + * + * 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.qpid.server.security.auth.sasl; + +import java.io.IOException; +import java.security.Principal; +import java.util.List; +import java.util.Map; + +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.AccountNotFoundException; + +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; +import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser; + +public class TestPrincipalDatabase implements PrincipalDatabase +{ + + public boolean createPrincipal(Principal principal, char[] password) + { + // TODO Auto-generated method stub + return false; + } + + public boolean deletePrincipal(Principal principal) throws AccountNotFoundException + { + // TODO Auto-generated method stub + return false; + } + + public Map<String, AuthenticationProviderInitialiser> getMechanisms() + { + // TODO Auto-generated method stub + return null; + } + + public Principal getUser(String username) + { + // TODO Auto-generated method stub + return null; + } + + public List<Principal> getUsers() + { + // TODO Auto-generated method stub + return null; + } + + public void setPassword(Principal principal, PasswordCallback callback) throws IOException, + AccountNotFoundException + { + callback.setPassword("p".toCharArray()); + } + + public boolean updatePassword(Principal principal, char[] password) throws AccountNotFoundException + { + // TODO Auto-generated method stub + return false; + } + + public boolean verifyPassword(String principal, char[] password) throws AccountNotFoundException + { + // TODO Auto-generated method stub + return false; + } + + public void reload() throws IOException + { + // TODO Auto-generated method stub + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/amqplain/AMQPlainSaslServerTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/amqplain/AMQPlainSaslServerTest.java new file mode 100644 index 0000000000..6245064bf7 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/amqplain/AMQPlainSaslServerTest.java @@ -0,0 +1,43 @@ +/* + * + * 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.qpid.server.security.auth.sasl.amqplain; + +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.FieldTableFactory; +import org.apache.qpid.server.security.auth.sasl.SaslServerTestCase; +import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser; + +public class AMQPlainSaslServerTest extends SaslServerTestCase +{ + protected void setUp() throws Exception + { + UsernamePasswordInitialiser handler = new AmqPlainInitialiser(); + handler.initialise(db); + this.server = new AmqPlainSaslServer(handler.getCallbackHandler()); + FieldTable table = FieldTableFactory.newFieldTable(); + table.setString("LOGIN", username); + table.setString("PASSWORD", password); + correctresponse = table.getDataAsBytes(); + table.setString("PASSWORD", notpassword); + wrongresponse = table.getDataAsBytes(); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerTest.java new file mode 100644 index 0000000000..5dd51250dc --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerTest.java @@ -0,0 +1,39 @@ +/* + * + * 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.qpid.server.security.auth.sasl.plain; + +import org.apache.qpid.server.security.auth.sasl.SaslServerTestCase; +import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser; + +public class PlainSaslServerTest extends SaslServerTestCase +{ + + protected void setUp() throws Exception + { + UsernamePasswordInitialiser handler = new PlainInitialiser(); + handler.initialise(db); + this.server = new PlainSaslServer(handler.getCallbackHandler()); + correctresponse = new byte[]{0x0, (byte) username.charAt(0), 0x0, (byte) password.charAt(0)}; + wrongresponse = new byte[]{0x0,(byte) username.charAt(0), 0x0, (byte) notpassword.charAt(0)}; + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/stats/StatisticsCounterTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/stats/StatisticsCounterTest.java new file mode 100644 index 0000000000..fbaa1342c9 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/stats/StatisticsCounterTest.java @@ -0,0 +1,144 @@ +/* + * + * 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.qpid.server.stats; + +import junit.framework.TestCase; + +/** + * Unit tests for the {@link StatisticsCounter} class. + */ +public class StatisticsCounterTest extends TestCase +{ + /** + * Check that statistics counters are created correctly. + */ + public void testCreate() + { + long before = System.currentTimeMillis(); + StatisticsCounter counter = new StatisticsCounter("name", 1234L); + long after = System.currentTimeMillis(); + + assertTrue(before <= counter.getStart()); + assertTrue(after >= counter.getStart()); + assertTrue(counter.getName().startsWith("name-")); + assertEquals(1234L, counter.getPeriod()); + } + + /** + * Check that totals add up correctly. + */ + public void testTotal() + { + StatisticsCounter counter = new StatisticsCounter("test", 1000L); + long start = counter.getStart(); + for (int i = 0; i < 100; i++) + { + counter.registerEvent(i, start + i); + } + assertEquals(99 * 50, counter.getTotal()); // cf. Gauss + } + + /** + * Test totals add up correctly even when messages are delivered + * out-of-order. + */ + public void testTotalOutOfOrder() + { + StatisticsCounter counter = new StatisticsCounter("test", 1000L); + long start = counter.getStart(); + assertEquals(0, counter.getTotal()); + counter.registerEvent(10, start + 2500); + assertEquals(10, counter.getTotal()); + counter.registerEvent(20, start + 1500); + assertEquals(30, counter.getTotal()); + counter.registerEvent(10, start + 500); + assertEquals(40, counter.getTotal()); + } + + /** + * Test that the peak rate is reported correctly. + */ + public void testPeak() throws Exception + { + StatisticsCounter counter = new StatisticsCounter("test", 1000L); + long start = counter.getStart(); + assertEquals(0.0, counter.getPeak()); + Thread.sleep(500); + counter.registerEvent(1000, start + 500); + Thread.sleep(1000); + assertEquals(1000.0, counter.getPeak()); + counter.registerEvent(2000, start + 1500); + Thread.sleep(1000); + assertEquals(2000.0, counter.getPeak()); + counter.registerEvent(1000, start + 2500); + Thread.sleep(1000); + assertEquals(2000.0, counter.getPeak()); + } + + /** + * Test that peak rate is reported correctly for out-of-order messages, + * and the total is also unaffected. + */ + public void testPeakOutOfOrder() throws Exception + { + StatisticsCounter counter = new StatisticsCounter("test", 1000L); + long start = counter.getStart(); + assertEquals(0.0, counter.getPeak()); + counter.registerEvent(1000, start + 2500); + Thread.sleep(1500); + assertEquals(0.0, counter.getPeak()); + counter.registerEvent(2000, start + 1500); + Thread.sleep(1000L); + assertEquals(0.0, counter.getPeak()); + counter.registerEvent(1000, start + 500); + Thread.sleep(1500); + assertEquals(4000.0, counter.getPeak()); + Thread.sleep(2000); + assertEquals(4000.0, counter.getPeak()); + counter.registerEvent(1000, start + 500); + assertEquals(4000.0, counter.getPeak()); + Thread.sleep(2000); + counter.registerEvent(1000); + assertEquals(4000.0, counter.getPeak()); + assertEquals(6000, counter.getTotal()); + } + + /** + * Test the current rate is generated correctly. + */ + public void testRate() throws Exception + { + StatisticsCounter counter = new StatisticsCounter("test", 1000L); + assertEquals(0.0, counter.getRate()); + Thread.sleep(500); + counter.registerEvent(1000); + Thread.sleep(1000); + assertEquals(1000.0, counter.getRate()); + counter.registerEvent(2000); + Thread.sleep(1000); + assertEquals(2000.0, counter.getRate()); + counter.registerEvent(1000); + Thread.sleep(1000); + assertEquals(1000.0, counter.getRate()); + Thread.sleep(1000); + assertEquals(0.0, counter.getRate()); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/store/MessageStoreShutdownTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/MessageStoreShutdownTest.java new file mode 100644 index 0000000000..6ca88d1796 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/MessageStoreShutdownTest.java @@ -0,0 +1,81 @@ +/* + * + * 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.qpid.server.store; + +import org.apache.qpid.server.util.InternalBrokerBaseCase; +import org.apache.qpid.server.protocol.InternalTestProtocolSession; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; + +import java.util.List; + +public class MessageStoreShutdownTest extends InternalBrokerBaseCase +{ + + public void test() + { + subscribe(getSession(), getChannel(), getQueue()); + + try + { + publishMessages(getSession(), getChannel(), 1); + } + catch (AMQException e) + { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + fail(e.getMessage()); + } + + try + { + getRegistry().close(); + } + catch (Exception e) + { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + fail(e.getMessage()); + } + + assertTrue("Session should now be closed", getSession().isClosed()); + + + //Test attempting to modify the broker state after session has been closed. + + //The Message should have been removed from the unacked list. + + //Ack Messages + List<InternalTestProtocolSession.DeliveryPair> list = getSession().getDelivers(getChannel().getChannelId(), new AMQShortString("sgen_1"), 1); + + InternalTestProtocolSession.DeliveryPair pair = list.get(0); + + try + { + // The message should now be requeued and so unable to ack it. + getChannel().acknowledgeMessage(pair.getDeliveryTag(), false); + } + catch (AMQException e) + { + assertEquals("Incorrect exception thrown", "Single ack on delivery tag 1 not known for channel:1", e.getMessage()); + } + + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/store/MessageStoreTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/MessageStoreTest.java new file mode 100644 index 0000000000..62ceb68208 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/MessageStoreTest.java @@ -0,0 +1,908 @@ +/* + * + * 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.qpid.server.store; + +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.qpid.AMQException; +import org.apache.qpid.common.AMQPFilterTypes; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.framing.amqp_8_0.BasicConsumeBodyImpl; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.exchange.DirectExchange; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.exchange.ExchangeType; +import org.apache.qpid.server.exchange.TopicExchange; +import org.apache.qpid.server.message.AMQMessage; +import org.apache.qpid.server.message.MessageMetaData; +import org.apache.qpid.server.queue.AMQPriorityQueue; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.queue.ConflationQueue; +import org.apache.qpid.server.queue.IncomingMessage; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.queue.SimpleAMQQueue; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.txn.AutoCommitTransaction; +import org.apache.qpid.server.txn.ServerTransaction; +import org.apache.qpid.server.util.InternalBrokerBaseCase; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.util.FileUtils; + +/** + * This tests the MessageStores by using the available interfaces. + * + * For persistent stores, it validates that Exchanges, Queues, Bindings and + * Messages are persisted and recovered correctly. + */ +public class MessageStoreTest extends InternalBrokerBaseCase +{ + public static final int DEFAULT_PRIORTY_LEVEL = 5; + public static final String SELECTOR_VALUE = "Test = 'MST'"; + public static final String LVQ_KEY = "MST-LVQ-KEY"; + + AMQShortString nonDurableExchangeName = new AMQShortString("MST-NonDurableDirectExchange"); + AMQShortString directExchangeName = new AMQShortString("MST-DirectExchange"); + AMQShortString topicExchangeName = new AMQShortString("MST-TopicExchange"); + + AMQShortString durablePriorityTopicQueueName = new AMQShortString("MST-PriorityTopicQueue-Durable"); + AMQShortString durableTopicQueueName = new AMQShortString("MST-TopicQueue-Durable"); + AMQShortString priorityTopicQueueName = new AMQShortString("MST-PriorityTopicQueue"); + AMQShortString topicQueueName = new AMQShortString("MST-TopicQueue"); + + AMQShortString durableExclusiveQueueName = new AMQShortString("MST-Queue-Durable-Exclusive"); + AMQShortString durablePriorityQueueName = new AMQShortString("MST-PriorityQueue-Durable"); + AMQShortString durableLastValueQueueName = new AMQShortString("MST-LastValueQueue-Durable"); + AMQShortString durableQueueName = new AMQShortString("MST-Queue-Durable"); + AMQShortString priorityQueueName = new AMQShortString("MST-PriorityQueue"); + AMQShortString queueName = new AMQShortString("MST-Queue"); + + AMQShortString directRouting = new AMQShortString("MST-direct"); + AMQShortString topicRouting = new AMQShortString("MST-topic"); + + AMQShortString queueOwner = new AMQShortString("MST"); + + protected PropertiesConfiguration _config; + + public void setUp() throws Exception + { + super.setUp(); + + String storePath = System.getProperty("QPID_WORK") + "/" + getName(); + + _config = new PropertiesConfiguration(); + _config.addProperty("store.class", getTestProfileMessageStoreClassName()); + _config.addProperty("store.environment-path", storePath); + + cleanup(new File(storePath)); + + reloadVirtualHost(); + } + + protected void reloadVirtualHost() + { + VirtualHost original = getVirtualHost(); + + if (getVirtualHost() != null) + { + try + { + getVirtualHost().close(); + getVirtualHost().getApplicationRegistry(). + getVirtualHostRegistry().unregisterVirtualHost(getVirtualHost()); + } + catch (Exception e) + { + fail(e.getMessage()); + } + } + + try + { + setVirtualHost(ApplicationRegistry.getInstance().createVirtualHost(new VirtualHostConfiguration(getClass().getName(), _config))); + } + catch (Exception e) + { + e.printStackTrace(); + fail(e.getMessage()); + } + + assertTrue("Virtualhost has not changed, reload was not successful", original != getVirtualHost()); + } + + /** + * Old MessageStoreTest segment which runs against both persistent and non-persistent stores + * creating queues, exchanges and bindings and then verifying message delivery to them. + */ + public void testQueueExchangeAndBindingCreation() throws Exception + { + assertEquals("Should not be any existing queues", 0, getVirtualHost().getQueueRegistry().getQueues().size()); + + createAllQueues(); + createAllTopicQueues(); + + //Register Non-Durable DirectExchange + Exchange nonDurableExchange = createExchange(DirectExchange.TYPE, nonDurableExchangeName, false); + bindAllQueuesToExchange(nonDurableExchange, directRouting); + + //Register DirectExchange + Exchange directExchange = createExchange(DirectExchange.TYPE, directExchangeName, true); + bindAllQueuesToExchange(directExchange, directRouting); + + //Register TopicExchange + Exchange topicExchange = createExchange(TopicExchange.TYPE, topicExchangeName, true); + bindAllTopicQueuesToExchange(topicExchange, topicRouting); + + //Send Message To NonDurable direct Exchange = persistent + sendMessageOnExchange(nonDurableExchange, directRouting, true); + // and non-persistent + sendMessageOnExchange(nonDurableExchange, directRouting, false); + + //Send Message To direct Exchange = persistent + sendMessageOnExchange(directExchange, directRouting, true); + // and non-persistent + sendMessageOnExchange(directExchange, directRouting, false); + + //Send Message To topic Exchange = persistent + sendMessageOnExchange(topicExchange, topicRouting, true); + // and non-persistent + sendMessageOnExchange(topicExchange, topicRouting, false); + + //Ensure all the Queues have four messages (one transient, one persistent) x 2 exchange routings + validateMessageOnQueues(4, true); + //Ensure all the topics have two messages (one transient, one persistent) + validateMessageOnTopics(2, true); + + assertEquals("Not all queues correctly registered", + 10, getVirtualHost().getQueueRegistry().getQueues().size()); + } + + /** + * Tests message persistence by running the testQueueExchangeAndBindingCreation() method above + * before reloading the virtual host and ensuring that the persistent messages were restored. + * + * More specific testing of message persistence is left to store-specific unit testing. + */ + public void testMessagePersistence() throws Exception + { + testQueueExchangeAndBindingCreation(); + + reloadVirtualHost(); + + //Validate durable queues and subscriptions still have the persistent messages + validateMessageOnQueues(2, false); + validateMessageOnTopics(1, false); + } + + /** + * Tests message removal by running the testMessagePersistence() method above before + * clearing the queues, reloading the virtual host, and ensuring that the persistent + * messages were removed from the queues. + */ + public void testMessageRemoval() throws Exception + { + testMessagePersistence(); + + QueueRegistry queueRegistry = getVirtualHost().getQueueRegistry(); + + assertEquals("Incorrect number of queues registered after recovery", + 6, queueRegistry.getQueues().size()); + + //clear the queue + queueRegistry.getQueue(durableQueueName).clearQueue(); + + //check the messages are gone + validateMessageOnQueue(durableQueueName, 0); + + //reload and verify messages arent restored + reloadVirtualHost(); + + validateMessageOnQueue(durableQueueName, 0); + } + + /** + * Tests queue persistence by creating a selection of queues with differing properties, both + * durable and non durable, and ensuring that following the recovery process the correct queues + * are present and any property manipulations (eg queue exclusivity) are correctly recovered. + */ + public void testQueuePersistence() throws Exception + { + assertEquals("Should not be any existing queues", + 0, getVirtualHost().getQueueRegistry().getQueues().size()); + + //create durable and non durable queues/topics + createAllQueues(); + createAllTopicQueues(); + + //reload the virtual host, prompting recovery of the queues/topics + reloadVirtualHost(); + + QueueRegistry queueRegistry = getVirtualHost().getQueueRegistry(); + + assertEquals("Incorrect number of queues registered after recovery", + 6, queueRegistry.getQueues().size()); + + //Validate the non-Durable Queues were not recovered. + assertNull("Non-Durable queue still registered:" + priorityQueueName, + queueRegistry.getQueue(priorityQueueName)); + assertNull("Non-Durable queue still registered:" + queueName, + queueRegistry.getQueue(queueName)); + assertNull("Non-Durable queue still registered:" + priorityTopicQueueName, + queueRegistry.getQueue(priorityTopicQueueName)); + assertNull("Non-Durable queue still registered:" + topicQueueName, + queueRegistry.getQueue(topicQueueName)); + + //Validate normally expected properties of Queues/Topics + validateDurableQueueProperties(); + + //Update the durable exclusive queue's exclusivity and verify it is persisted and recovered correctly + setQueueExclusivity(false); + validateQueueExclusivityProperty(false); + + //Reload the Virtualhost to recover the queues again + reloadVirtualHost(); + + //verify the change was persisted and recovered correctly + validateQueueExclusivityProperty(false); + } + + /** + * Tests queue removal by creating a durable queue, verifying it recovers, and + * then removing it from the store, and ensuring that following the second reload + * process it is not recovered. + */ + public void testDurableQueueRemoval() throws Exception + { + //Register Durable Queue + createQueue(durableQueueName, false, true, false, false); + + QueueRegistry queueRegistry = getVirtualHost().getQueueRegistry(); + assertEquals("Incorrect number of queues registered before recovery", + 1, queueRegistry.getQueues().size()); + + reloadVirtualHost(); + + queueRegistry = getVirtualHost().getQueueRegistry(); + assertEquals("Incorrect number of queues registered after first recovery", + 1, queueRegistry.getQueues().size()); + + //test that removing the queue means it is not recovered next time + getVirtualHost().getDurableConfigurationStore().removeQueue(queueRegistry.getQueue(durableQueueName)); + + reloadVirtualHost(); + + queueRegistry = getVirtualHost().getQueueRegistry(); + assertEquals("Incorrect number of queues registered after second recovery", + 0, queueRegistry.getQueues().size()); + assertNull("Durable queue was not removed:" + durableQueueName, + queueRegistry.getQueue(durableQueueName)); + } + + /** + * Tests exchange persistence by creating a selection of exchanges, both durable + * and non durable, and ensuring that following the recovery process the correct + * durable exchanges are still present. + */ + public void testExchangePersistence() throws Exception + { + int origExchangeCount = getVirtualHost().getExchangeRegistry().getExchangeNames().size(); + + Map<AMQShortString, Exchange> oldExchanges = createExchanges(); + + assertEquals("Incorrect number of exchanges registered before recovery", + origExchangeCount + 3, getVirtualHost().getExchangeRegistry().getExchangeNames().size()); + + reloadVirtualHost(); + + //verify the exchanges present after recovery + validateExchanges(origExchangeCount, oldExchanges); + } + + /** + * Tests exchange removal by creating a durable exchange, verifying it recovers, and + * then removing it from the store, and ensuring that following the second reload + * process it is not recovered. + */ + public void testDurableExchangeRemoval() throws Exception + { + int origExchangeCount = getVirtualHost().getExchangeRegistry().getExchangeNames().size(); + + createExchange(DirectExchange.TYPE, directExchangeName, true); + + ExchangeRegistry exchangeRegistry = getVirtualHost().getExchangeRegistry(); + assertEquals("Incorrect number of exchanges registered before recovery", + origExchangeCount + 1, exchangeRegistry.getExchangeNames().size()); + + reloadVirtualHost(); + + exchangeRegistry = getVirtualHost().getExchangeRegistry(); + assertEquals("Incorrect number of exchanges registered after first recovery", + origExchangeCount + 1, exchangeRegistry.getExchangeNames().size()); + + //test that removing the exchange means it is not recovered next time + getVirtualHost().getDurableConfigurationStore().removeExchange(exchangeRegistry.getExchange(directExchangeName)); + + reloadVirtualHost(); + + exchangeRegistry = getVirtualHost().getExchangeRegistry(); + assertEquals("Incorrect number of exchanges registered after second recovery", + origExchangeCount, exchangeRegistry.getExchangeNames().size()); + assertNull("Durable exchange was not removed:" + directExchangeName, + exchangeRegistry.getExchange(directExchangeName)); + } + + /** + * Tests binding persistence by creating a selection of queues and exchanges, both durable + * and non durable, then adding bindings with and without selectors before reloading the + * virtual host and verifying that following the recovery process the correct durable + * bindings (those for durable queues to durable exchanges) are still present. + */ + public void testBindingPersistence() throws Exception + { + int origExchangeCount = getVirtualHost().getExchangeRegistry().getExchangeNames().size(); + + createAllQueues(); + createAllTopicQueues(); + + Map<AMQShortString, Exchange> exchanges = createExchanges(); + + Exchange nonDurableExchange = exchanges.get(nonDurableExchangeName); + Exchange directExchange = exchanges.get(directExchangeName); + Exchange topicExchange = exchanges.get(topicExchangeName); + + bindAllQueuesToExchange(nonDurableExchange, directRouting); + bindAllQueuesToExchange(directExchange, directRouting); + bindAllTopicQueuesToExchange(topicExchange, topicRouting); + + assertEquals("Incorrect number of exchanges registered before recovery", + origExchangeCount + 3, getVirtualHost().getExchangeRegistry().getExchangeNames().size()); + + reloadVirtualHost(); + + validateExchanges(origExchangeCount, exchanges); + + validateBindingProperties(); + } + + /** + * Tests binding removal by creating a durable exchange, and queue, binding them together, + * recovering to verify the persistence, then removing it from the store, and ensuring + * that following the second reload process it is not recovered. + */ + public void testDurableBindingRemoval() throws Exception + { + QueueRegistry queueRegistry = getVirtualHost().getQueueRegistry(); + + //create durable queue and exchange, bind them + Exchange exch = createExchange(DirectExchange.TYPE, directExchangeName, true); + createQueue(durableQueueName, false, true, false, false); + bindQueueToExchange(exch, directRouting, queueRegistry.getQueue(durableQueueName), false, null); + + assertEquals("Incorrect number of bindings registered before recovery", + 1, queueRegistry.getQueue(durableQueueName).getBindings().size()); + + //verify binding is actually normally recovered + reloadVirtualHost(); + + queueRegistry = getVirtualHost().getQueueRegistry(); + assertEquals("Incorrect number of bindings registered after first recovery", + 1, queueRegistry.getQueue(durableQueueName).getBindings().size()); + + ExchangeRegistry exchangeRegistry = getVirtualHost().getExchangeRegistry(); + exch = exchangeRegistry.getExchange(directExchangeName); + assertNotNull("Exchange was not recovered", exch); + + //remove the binding and verify result after recovery + unbindQueueFromExchange(exch, directRouting, queueRegistry.getQueue(durableQueueName), false, null); + + reloadVirtualHost(); + + queueRegistry = getVirtualHost().getQueueRegistry(); + assertEquals("Incorrect number of bindings registered after second recovery", + 0, queueRegistry.getQueue(durableQueueName).getBindings().size()); + } + + /** + * Validates that the durable exchanges are still present, the non durable exchange is not, + * and that the new exchanges are not the same objects as the provided list (i.e. that the + * reload actually generated new exchange objects) + */ + private void validateExchanges(int originalNumExchanges, Map<AMQShortString, Exchange> oldExchanges) + { + ExchangeRegistry registry = getVirtualHost().getExchangeRegistry(); + + assertTrue(directExchangeName + " exchange NOT reloaded", + registry.getExchangeNames().contains(directExchangeName)); + assertTrue(topicExchangeName + " exchange NOT reloaded", + registry.getExchangeNames().contains(topicExchangeName)); + assertTrue(nonDurableExchangeName + " exchange reloaded", + !registry.getExchangeNames().contains(nonDurableExchangeName)); + + //check the old exchange objects are not the same as the new exchanges + assertTrue(directExchangeName + " exchange NOT reloaded", + registry.getExchange(directExchangeName) != oldExchanges.get(directExchangeName)); + assertTrue(topicExchangeName + " exchange NOT reloaded", + registry.getExchange(topicExchangeName) != oldExchanges.get(topicExchangeName)); + + // There should only be the original exchanges + our 2 recovered durable exchanges + assertEquals("Incorrect number of exchanges available", + originalNumExchanges + 2, registry.getExchangeNames().size()); + } + + /** Validates the Durable queues and their properties are as expected following recovery */ + private void validateBindingProperties() + { + QueueRegistry queueRegistry = getVirtualHost().getQueueRegistry(); + + assertEquals("Incorrect number of (durable) queues following recovery", 6, queueRegistry.getQueues().size()); + + validateBindingProperties(queueRegistry.getQueue(durablePriorityQueueName).getBindings(), false); + validateBindingProperties(queueRegistry.getQueue(durablePriorityTopicQueueName).getBindings(), true); + validateBindingProperties(queueRegistry.getQueue(durableQueueName).getBindings(), false); + validateBindingProperties(queueRegistry.getQueue(durableTopicQueueName).getBindings(), true); + validateBindingProperties(queueRegistry.getQueue(durableExclusiveQueueName).getBindings(), false); + } + + /** + * Validate that each queue is bound only once following recovery (i.e. that bindings for non durable + * queues or to non durable exchanges are not recovered), and if a selector should be present + * that it is and contains the correct value + * + * @param bindings the set of bindings to validate + * @param useSelectors if set, check the binding has a JMS_SELECTOR argument and the correct value for it + */ + private void validateBindingProperties(List<Binding> bindings, boolean useSelectors) + { + assertEquals("Each queue should only be bound once.", 1, bindings.size()); + + Binding binding = bindings.get(0); + + if (useSelectors) + { + assertTrue("Binding does not contain a Selector argument.", + binding.getArguments().containsKey(AMQPFilterTypes.JMS_SELECTOR.getValue())); + assertEquals("The binding selector argument is incorrect", SELECTOR_VALUE, + binding.getArguments().get(AMQPFilterTypes.JMS_SELECTOR.getValue()).toString()); + } + } + + private void setQueueExclusivity(boolean exclusive) throws AMQException + { + QueueRegistry queueRegistry = getVirtualHost().getQueueRegistry(); + + AMQQueue queue = queueRegistry.getQueue(durableExclusiveQueueName); + + queue.setExclusive(exclusive); + } + + private void validateQueueExclusivityProperty(boolean expected) + { + QueueRegistry queueRegistry = getVirtualHost().getQueueRegistry(); + + AMQQueue queue = queueRegistry.getQueue(durableExclusiveQueueName); + + assertEquals("Queue exclusivity was incorrect", queue.isExclusive(), expected); + } + + + private void validateDurableQueueProperties() + { + QueueRegistry queueRegistry = getVirtualHost().getQueueRegistry(); + + validateQueueProperties(queueRegistry.getQueue(durablePriorityQueueName), true, true, false, false); + validateQueueProperties(queueRegistry.getQueue(durablePriorityTopicQueueName), true, true, false, false); + validateQueueProperties(queueRegistry.getQueue(durableQueueName), false, true, false, false); + validateQueueProperties(queueRegistry.getQueue(durableTopicQueueName), false, true, false, false); + validateQueueProperties(queueRegistry.getQueue(durableExclusiveQueueName), false, true, true, false); + validateQueueProperties(queueRegistry.getQueue(durableLastValueQueueName), false, true, true, true); + } + + private void validateQueueProperties(AMQQueue queue, boolean usePriority, boolean durable, boolean exclusive, boolean lastValueQueue) + { + if(usePriority || lastValueQueue) + { + assertNotSame("Queues cant be both Priority and LastValue based", usePriority, lastValueQueue); + } + + if (usePriority) + { + assertEquals("Queue is no longer a Priority Queue", AMQPriorityQueue.class, queue.getClass()); + assertEquals("Priority Queue does not have set priorities", + DEFAULT_PRIORTY_LEVEL, ((AMQPriorityQueue) queue).getPriorities()); + } + else if (lastValueQueue) + { + assertEquals("Queue is no longer a LastValue Queue", ConflationQueue.class, queue.getClass()); + assertEquals("LastValue Queue Key has changed", LVQ_KEY, ((ConflationQueue) queue).getConflationKey()); + } + else + { + assertEquals("Queue is not 'simple'", SimpleAMQQueue.class, queue.getClass()); + } + + assertEquals("Queue owner is not as expected", queueOwner, queue.getOwner()); + assertEquals("Queue durability is not as expected", durable, queue.isDurable()); + assertEquals("Queue exclusivity is not as expected", exclusive, queue.isExclusive()); + } + + /** + * Delete the Store Environment path + * + * @param configuration The configuration that contains the store environment path. + */ + private void cleanup(File environmentPath) + { + if (environmentPath.exists()) + { + FileUtils.delete(environmentPath, true); + } + } + + private void sendMessageOnExchange(Exchange exchange, AMQShortString routingKey, boolean deliveryMode) + { + //Set MessagePersistence + BasicContentHeaderProperties properties = new BasicContentHeaderProperties(); + properties.setDeliveryMode(deliveryMode ? Integer.valueOf(2).byteValue() : Integer.valueOf(1).byteValue()); + FieldTable headers = properties.getHeaders(); + headers.setString("Test", "MST"); + properties.setHeaders(headers); + + MessagePublishInfo messageInfo = new TestMessagePublishInfo(exchange, false, false, routingKey); + + final IncomingMessage currentMessage; + + + currentMessage = new IncomingMessage(messageInfo); + + currentMessage.setExchange(exchange); + + ContentHeaderBody headerBody = new ContentHeaderBody(); + headerBody.classId = BasicConsumeBodyImpl.CLASS_ID; + headerBody.bodySize = 0; + + headerBody.setProperties(properties); + + try + { + currentMessage.setContentHeaderBody(headerBody); + } + catch (AMQException e) + { + fail(e.getMessage()); + } + + currentMessage.setExpiration(); + + MessageMetaData mmd = currentMessage.headersReceived(); + currentMessage.setStoredMessage(getVirtualHost().getMessageStore().addMessage(mmd)); + currentMessage.getStoredMessage().flushToStore(); + currentMessage.route(); + + + // check and deliver if header says body length is zero + if (currentMessage.allContentReceived()) + { + ServerTransaction trans = new AutoCommitTransaction(getVirtualHost().getMessageStore()); + final List<? extends BaseQueue> destinationQueues = currentMessage.getDestinationQueues(); + trans.enqueue(currentMessage.getDestinationQueues(), currentMessage, new ServerTransaction.Action() { + public void postCommit() + { + try + { + AMQMessage message = new AMQMessage(currentMessage.getStoredMessage()); + + for(BaseQueue queue : destinationQueues) + { + queue.enqueue(message); + } + } + catch (AMQException e) + { + e.printStackTrace(); + } + } + + public void onRollback() + { + //To change body of implemented methods use File | Settings | File Templates. + } + }); + } + } + + private void createAllQueues() + { + //Register Durable Priority Queue + createQueue(durablePriorityQueueName, true, true, false, false); + + //Register Durable Simple Queue + createQueue(durableQueueName, false, true, false, false); + + //Register Durable Exclusive Simple Queue + createQueue(durableExclusiveQueueName, false, true, true, false); + + //Register Durable LastValue Queue + createQueue(durableLastValueQueueName, false, true, true, true); + + //Register NON-Durable Priority Queue + createQueue(priorityQueueName, true, false, false, false); + + //Register NON-Durable Simple Queue + createQueue(queueName, false, false, false, false); + } + + private void createAllTopicQueues() + { + //Register Durable Priority Queue + createQueue(durablePriorityTopicQueueName, true, true, false, false); + + //Register Durable Simple Queue + createQueue(durableTopicQueueName, false, true, false, false); + + //Register NON-Durable Priority Queue + createQueue(priorityTopicQueueName, true, false, false, false); + + //Register NON-Durable Simple Queue + createQueue(topicQueueName, false, false, false, false); + } + + private void createQueue(AMQShortString queueName, boolean usePriority, boolean durable, boolean exclusive, boolean lastValueQueue) + { + + FieldTable queueArguments = null; + + if(usePriority || lastValueQueue) + { + assertNotSame("Queues cant be both Priority and LastValue based", usePriority, lastValueQueue); + } + + if (usePriority) + { + queueArguments = new FieldTable(); + queueArguments.put(AMQQueueFactory.X_QPID_PRIORITIES, DEFAULT_PRIORTY_LEVEL); + } + + if (lastValueQueue) + { + queueArguments = new FieldTable(); + queueArguments.put(new AMQShortString(AMQQueueFactory.QPID_LAST_VALUE_QUEUE_KEY), LVQ_KEY); + } + + AMQQueue queue = null; + + //Ideally we would be able to use the QueueDeclareHandler here. + try + { + queue = AMQQueueFactory.createAMQQueueImpl(queueName, durable, queueOwner, false, exclusive, + getVirtualHost(), queueArguments); + + validateQueueProperties(queue, usePriority, durable, exclusive, lastValueQueue); + + if (queue.isDurable() && !queue.isAutoDelete()) + { + getVirtualHost().getMessageStore().createQueue(queue, queueArguments); + } + } + catch (AMQException e) + { + fail(e.getMessage()); + } + + getVirtualHost().getQueueRegistry().registerQueue(queue); + + } + + private Map<AMQShortString, Exchange> createExchanges() + { + Map<AMQShortString, Exchange> exchanges = new HashMap<AMQShortString, Exchange>(); + + //Register non-durable DirectExchange + exchanges.put(nonDurableExchangeName, createExchange(DirectExchange.TYPE, nonDurableExchangeName, false)); + + //Register durable DirectExchange and TopicExchange + exchanges.put(directExchangeName ,createExchange(DirectExchange.TYPE, directExchangeName, true)); + exchanges.put(topicExchangeName,createExchange(TopicExchange.TYPE, topicExchangeName, true)); + + return exchanges; + } + + private Exchange createExchange(ExchangeType<?> type, AMQShortString name, boolean durable) + { + Exchange exchange = null; + + try + { + exchange = type.newInstance(getVirtualHost(), name, durable, 0, false); + } + catch (AMQException e) + { + fail(e.getMessage()); + } + + try + { + getVirtualHost().getExchangeRegistry().registerExchange(exchange); + if (durable) + { + getVirtualHost().getMessageStore().createExchange(exchange); + } + } + catch (AMQException e) + { + fail(e.getMessage()); + } + return exchange; + } + + private void bindAllQueuesToExchange(Exchange exchange, AMQShortString routingKey) + { + FieldTable queueArguments = new FieldTable(); + queueArguments.put(AMQQueueFactory.X_QPID_PRIORITIES, DEFAULT_PRIORTY_LEVEL); + + QueueRegistry queueRegistry = getVirtualHost().getQueueRegistry(); + + bindQueueToExchange(exchange, routingKey, queueRegistry.getQueue(durablePriorityQueueName), false, queueArguments); + bindQueueToExchange(exchange, routingKey, queueRegistry.getQueue(durableQueueName), false, null); + bindQueueToExchange(exchange, routingKey, queueRegistry.getQueue(priorityQueueName), false, queueArguments); + bindQueueToExchange(exchange, routingKey, queueRegistry.getQueue(queueName), false, null); + bindQueueToExchange(exchange, routingKey, queueRegistry.getQueue(durableExclusiveQueueName), false, null); + } + + private void bindAllTopicQueuesToExchange(Exchange exchange, AMQShortString routingKey) + { + FieldTable queueArguments = new FieldTable(); + queueArguments.put(AMQQueueFactory.X_QPID_PRIORITIES, DEFAULT_PRIORTY_LEVEL); + + QueueRegistry queueRegistry = getVirtualHost().getQueueRegistry(); + + bindQueueToExchange(exchange, routingKey, queueRegistry.getQueue(durablePriorityTopicQueueName), true, queueArguments); + bindQueueToExchange(exchange, routingKey, queueRegistry.getQueue(durableTopicQueueName), true, null); + bindQueueToExchange(exchange, routingKey, queueRegistry.getQueue(priorityTopicQueueName), true, queueArguments); + bindQueueToExchange(exchange, routingKey, queueRegistry.getQueue(topicQueueName), true, null); + } + + + protected void bindQueueToExchange(Exchange exchange, AMQShortString routingKey, AMQQueue queue, boolean useSelector, FieldTable queueArguments) + { + FieldTable bindArguments = null; + + if (useSelector) + { + bindArguments = new FieldTable(); + bindArguments.put(AMQPFilterTypes.JMS_SELECTOR.getValue(), SELECTOR_VALUE ); + } + + try + { + getVirtualHost().getBindingFactory().addBinding(String.valueOf(routingKey), queue, exchange, FieldTable.convertToMap(bindArguments)); + } + catch (Exception e) + { + fail(e.getMessage()); + } + } + + protected void unbindQueueFromExchange(Exchange exchange, AMQShortString routingKey, AMQQueue queue, boolean useSelector, FieldTable queueArguments) + { + FieldTable bindArguments = null; + + if (useSelector) + { + bindArguments = new FieldTable(); + bindArguments.put(AMQPFilterTypes.JMS_SELECTOR.getValue(), SELECTOR_VALUE ); + } + + try + { + getVirtualHost().getBindingFactory().removeBinding(String.valueOf(routingKey), queue, exchange, FieldTable.convertToMap(bindArguments)); + } + catch (Exception e) + { + fail(e.getMessage()); + } + } + + private void validateMessageOnTopics(long messageCount, boolean allQueues) + { + validateMessageOnQueue(durablePriorityTopicQueueName, messageCount); + validateMessageOnQueue(durableTopicQueueName, messageCount); + + if (allQueues) + { + validateMessageOnQueue(priorityTopicQueueName, messageCount); + validateMessageOnQueue(topicQueueName, messageCount); + } + } + + private void validateMessageOnQueues(long messageCount, boolean allQueues) + { + validateMessageOnQueue(durablePriorityQueueName, messageCount); + validateMessageOnQueue(durableQueueName, messageCount); + + if (allQueues) + { + validateMessageOnQueue(priorityQueueName, messageCount); + validateMessageOnQueue(queueName, messageCount); + } + } + + private void validateMessageOnQueue(AMQShortString queueName, long messageCount) + { + AMQQueue queue = getVirtualHost().getQueueRegistry().getQueue(queueName); + + assertNotNull("Queue(" + queueName + ") not correctly registered:", queue); + + assertEquals("Incorrect Message count on queue:" + queueName, messageCount, queue.getMessageCount()); + } + + private class TestMessagePublishInfo implements MessagePublishInfo + { + + Exchange _exchange; + boolean _immediate; + boolean _mandatory; + AMQShortString _routingKey; + + TestMessagePublishInfo(Exchange exchange, boolean immediate, boolean mandatory, AMQShortString routingKey) + { + _exchange = exchange; + _immediate = immediate; + _mandatory = mandatory; + _routingKey = routingKey; + } + + public AMQShortString getExchange() + { + return _exchange.getNameShortString(); + } + + public void setExchange(AMQShortString exchange) + { + //no-op + } + + public boolean isImmediate() + { + return _immediate; + } + + public boolean isMandatory() + { + return _mandatory; + } + + public AMQShortString getRoutingKey() + { + return _routingKey; + } + } +}
\ No newline at end of file diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/store/ReferenceCountingTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/ReferenceCountingTest.java new file mode 100644 index 0000000000..2d41eb9899 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/ReferenceCountingTest.java @@ -0,0 +1,163 @@ +/* + * + * 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.qpid.server.store; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.server.message.AMQMessage; +import org.apache.qpid.server.message.MessageMetaData; +import org.apache.qpid.test.utils.QpidTestCase; + +/** + * Tests that reference counting works correctly with AMQMessage and the message store + */ +public class ReferenceCountingTest extends QpidTestCase +{ + private TestMemoryMessageStore _store; + + + protected void setUp() throws Exception + { + _store = new TestMemoryMessageStore(); + } + + /** + * Check that when the reference count is decremented the message removes itself from the store + */ + public void testMessageGetsRemoved() throws AMQException + { + ContentHeaderBody chb = createPersistentContentHeader(); + + MessagePublishInfo info = new MessagePublishInfo() + { + + public AMQShortString getExchange() + { + return null; + } + + public void setExchange(AMQShortString exchange) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isImmediate() + { + return false; + } + + public boolean isMandatory() + { + return false; + } + + public AMQShortString getRoutingKey() + { + return null; + } + }; + + + + MessageMetaData mmd = new MessageMetaData(info, chb, 0); + StoredMessage storedMessage = _store.addMessage(mmd); + + + AMQMessage message = new AMQMessage(storedMessage); + + message = message.takeReference(); + + // we call routing complete to set up the handle + // message.routingComplete(_store, _storeContext, new MessageHandleFactory()); + + + assertEquals(1, _store.getMessageCount()); + message.decrementReference(); + assertEquals(0, _store.getMessageCount()); + } + + private ContentHeaderBody createPersistentContentHeader() + { + ContentHeaderBody chb = new ContentHeaderBody(); + BasicContentHeaderProperties bchp = new BasicContentHeaderProperties(); + bchp.setDeliveryMode((byte)2); + chb.setProperties(bchp); + return chb; + } + + public void testMessageRemains() throws AMQException + { + + MessagePublishInfo info = new MessagePublishInfo() + { + + public AMQShortString getExchange() + { + return null; + } + + public void setExchange(AMQShortString exchange) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isImmediate() + { + return false; + } + + public boolean isMandatory() + { + return false; + } + + public AMQShortString getRoutingKey() + { + return null; + } + }; + + final ContentHeaderBody chb = createPersistentContentHeader(); + + MessageMetaData mmd = new MessageMetaData(info, chb, 0); + StoredMessage storedMessage = _store.addMessage(mmd); + + AMQMessage message = new AMQMessage(storedMessage); + + + message = message.takeReference(); + // we call routing complete to set up the handle + // message.routingComplete(_store, _storeContext, new MessageHandleFactory()); + + assertEquals(1, _store.getMessageCount()); + message = message.takeReference(); + message.decrementReference(); + assertEquals(1, _store.getMessageCount()); + } + + public static junit.framework.Test suite() + { + return new junit.framework.TestSuite(ReferenceCountingTest.class); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/store/SkeletonMessageStore.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/SkeletonMessageStore.java new file mode 100644 index 0000000000..5ff84557d8 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/SkeletonMessageStore.java @@ -0,0 +1,226 @@ +/* + * + * 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.qpid.server.store; + +import org.apache.commons.configuration.Configuration; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.abstraction.ContentChunk; +import org.apache.qpid.server.message.MessageMetaData; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.logging.LogSubject; + +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import java.nio.ByteBuffer; + +/** + * A message store that does nothing. Designed to be used in tests that do not want to use any message store + * functionality. + */ +public class SkeletonMessageStore implements MessageStore +{ + private final AtomicLong _messageId = new AtomicLong(1); + + public void configure(String base, Configuration config) throws Exception + { + } + + public void configureConfigStore(String name, + ConfigurationRecoveryHandler recoveryHandler, + Configuration config, + LogSubject logSubject) throws Exception + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void configureMessageStore(String name, + MessageStoreRecoveryHandler recoveryHandler, + Configuration config, + LogSubject logSubject) throws Exception + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void close() throws Exception + { + } + + public <M extends StorableMessageMetaData> StoredMessage<M> addMessage(M metaData) + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public void removeMessage(Long messageId) + { + } + + public void createExchange(Exchange exchange) throws AMQStoreException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void removeExchange(Exchange exchange) throws AMQStoreException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void bindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQStoreException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void unbindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQStoreException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void createQueue(AMQQueue queue) throws AMQStoreException + { + } + + public void createQueue(AMQQueue queue, FieldTable arguments) throws AMQStoreException + { + } + + + + + public List<AMQQueue> createQueues() throws AMQException + { + return null; + } + + public Long getNewMessageId() + { + return _messageId.getAndIncrement(); + } + + public void storeContentBodyChunk( + Long messageId, + int index, + ContentChunk contentBody, + boolean lastContentBody) throws AMQException + { + + } + + public void storeMessageMetaData(Long messageId, MessageMetaData messageMetaData) throws AMQException + { + + } + + public MessageMetaData getMessageMetaData(Long messageId) throws AMQException + { + return null; + } + + public ContentChunk getContentBodyChunk(Long messageId, int index) throws AMQException + { + return null; + } + + public boolean isPersistent() + { + return false; + } + + public void storeMessageHeader(Long messageNumber, ServerMessage message) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void storeContent(Long messageNumber, long offset, ByteBuffer body) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public ServerMessage getMessage(Long messageNumber) + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public void removeQueue(final AMQQueue queue) throws AMQStoreException + { + + } + + public void configureTransactionLog(String name, + TransactionLogRecoveryHandler recoveryHandler, + Configuration storeConfiguration, + LogSubject logSubject) throws Exception + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public Transaction newTransaction() + { + return new Transaction() + { + + public void enqueueMessage(TransactionLogResource queue, Long messageId) throws AMQStoreException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void dequeueMessage(TransactionLogResource queue, Long messageId) throws AMQStoreException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void commitTran() throws AMQStoreException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public StoreFuture commitTranAsync() throws AMQStoreException + { + return new StoreFuture() + { + public boolean isComplete() + { + return true; + } + + public void waitForCompletion() + { + + } + }; + } + + public void abortTran() throws AMQStoreException + { + //To change body of implemented methods use File | Settings | File Templates. + } + }; + } + + public void updateQueue(AMQQueue queue) throws AMQStoreException + { + + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestMemoryMessageStore.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestMemoryMessageStore.java new file mode 100644 index 0000000000..4dea13d391 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestMemoryMessageStore.java @@ -0,0 +1,98 @@ +/* + * + * 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.qpid.server.store; + +import org.apache.qpid.server.message.MessageMetaData; +import org.apache.qpid.framing.abstraction.ContentChunk; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.List; +import java.nio.ByteBuffer; + +/** + * Adds some extra methods to the memory message store for testing purposes. + */ +public class TestMemoryMessageStore extends MemoryMessageStore +{ + private AtomicInteger _messageCount = new AtomicInteger(0); + + + public TestMemoryMessageStore() + { + } + + @Override + public StoredMessage addMessage(StorableMessageMetaData metaData) + { + return new TestableStoredMessage(super.addMessage(metaData)); + } + + public int getMessageCount() + { + return _messageCount.get(); + } + + private class TestableStoredMessage implements StoredMessage + { + private final StoredMessage _storedMessage; + + public TestableStoredMessage(StoredMessage storedMessage) + { + _messageCount.incrementAndGet(); + _storedMessage = storedMessage; + } + + public StorableMessageMetaData getMetaData() + { + return _storedMessage.getMetaData(); + } + + public long getMessageNumber() + { + return _storedMessage.getMessageNumber(); + } + + public void addContent(int offsetInMessage, ByteBuffer src) + { + _storedMessage.addContent(offsetInMessage, src); + } + + public int getContent(int offsetInMessage, ByteBuffer dst) + { + return _storedMessage.getContent(offsetInMessage, dst); + } + + public StoreFuture flushToStore() + { + return _storedMessage.flushToStore(); + } + + public void remove() + { + _storedMessage.remove(); + _messageCount.decrementAndGet(); + } + + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestableMemoryMessageStore.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestableMemoryMessageStore.java new file mode 100644 index 0000000000..3593297a05 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestableMemoryMessageStore.java @@ -0,0 +1,157 @@ +/* + * + * 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.qpid.server.store; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.server.queue.AMQQueue; + +/** + * Adds some extra methods to the memory message store for testing purposes. + */ +public class TestableMemoryMessageStore extends MemoryMessageStore +{ + + MemoryMessageStore _mms = null; + private HashMap<Long, AMQQueue> _messages = new HashMap<Long, AMQQueue>(); + private AtomicInteger _messageCount = new AtomicInteger(0); + + public TestableMemoryMessageStore(MemoryMessageStore mms) + { + _mms = mms; + } + + public TestableMemoryMessageStore() + { + + } + + @Override + public void close() throws Exception + { + // Not required to do anything + } + + @Override + public StoredMessage addMessage(StorableMessageMetaData metaData) + { + return new TestableStoredMessage(super.addMessage(metaData)); + } + + public int getMessageCount() + { + return _messageCount.get(); + } + + private class TestableTransaction implements Transaction + { + public void enqueueMessage(TransactionLogResource queue, Long messageId) throws AMQStoreException + { + getMessages().put(messageId, (AMQQueue)queue); + } + + public void dequeueMessage(TransactionLogResource queue, Long messageId) throws AMQStoreException + { + getMessages().remove(messageId); + } + + public void commitTran() throws AMQStoreException + { + } + + public StoreFuture commitTranAsync() throws AMQStoreException + { + return new StoreFuture() + { + public boolean isComplete() + { + return true; + } + + public void waitForCompletion() + { + + } + }; + } + + public void abortTran() throws AMQStoreException + { + } + } + + + @Override + public Transaction newTransaction() + { + return new TestableTransaction(); + } + + public HashMap<Long, AMQQueue> getMessages() + { + return _messages; + } + + private class TestableStoredMessage implements StoredMessage + { + private final StoredMessage _storedMessage; + + public TestableStoredMessage(StoredMessage storedMessage) + { + _messageCount.incrementAndGet(); + _storedMessage = storedMessage; + } + + public StorableMessageMetaData getMetaData() + { + return _storedMessage.getMetaData(); + } + + public long getMessageNumber() + { + return _storedMessage.getMessageNumber(); + } + + public void addContent(int offsetInMessage, ByteBuffer src) + { + _storedMessage.addContent(offsetInMessage, src); + } + + public int getContent(int offsetInMessage, ByteBuffer dst) + { + return _storedMessage.getContent(offsetInMessage, dst); + } + + public StoreFuture flushToStore() + { + return _storedMessage.flushToStore(); + } + + public void remove() + { + _storedMessage.remove(); + _messageCount.decrementAndGet(); + } + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/subscription/MockSubscription.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/subscription/MockSubscription.java new file mode 100644 index 0000000000..6fbc627d8c --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/subscription/MockSubscription.java @@ -0,0 +1,262 @@ +package org.apache.qpid.server.subscription; + +/* +* +* 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. +* +*/ + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.queue.QueueEntry.SubscriptionAcquiredState; + +public class MockSubscription implements Subscription +{ + + private boolean _closed = false; + private AMQShortString tag = new AMQShortString("mocktag"); + private AMQQueue queue = null; + private StateListener _listener = null; + private AMQQueue.Context _queueContext = null; + private State _state = State.ACTIVE; + private ArrayList<QueueEntry> messages = new ArrayList<QueueEntry>(); + private final Lock _stateChangeLock = new ReentrantLock(); + private List<QueueEntry> _acceptEntries = null; + + private final QueueEntry.SubscriptionAcquiredState _owningState = new QueueEntry.SubscriptionAcquiredState(this); + private final QueueEntry.SubscriptionAssignedState _assignedState = new QueueEntry.SubscriptionAssignedState(this); + + + private static final AtomicLong idGenerator = new AtomicLong(0); + // Create a simple ID that increments for ever new Subscription + private final long _subscriptionID = idGenerator.getAndIncrement(); + + public MockSubscription() + { + } + + public MockSubscription(List<QueueEntry> acceptEntries) + { + _acceptEntries = acceptEntries; + } + + public void close() + { + _closed = true; + if (_listener != null) + { + _listener.stateChange(this, _state, State.CLOSED); + } + _state = State.CLOSED; + } + + public boolean filtersMessages() + { + return false; + } + + public AMQChannel getChannel() + { + return null; + } + + public AMQShortString getConsumerTag() + { + return tag; + } + + public long getSubscriptionID() + { + return _subscriptionID; + } + + public AMQQueue.Context getQueueContext() + { + return _queueContext; + } + + public SubscriptionAcquiredState getOwningState() + { + return _owningState; + } + + public QueueEntry.SubscriptionAssignedState getAssignedState() + { + return _assignedState; + } + + public LogActor getLogActor() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isTransient() + { + return false; + } + + public AMQQueue getQueue() + { + return queue; + } + + public void getSendLock() + { + _stateChangeLock.lock(); + } + + public boolean hasInterest(QueueEntry entry) + { + if(_acceptEntries != null) + { + //simulate selector behaviour, only signal + //interest in the dictated queue entries + return _acceptEntries.contains(entry); + } + + return true; + } + + public boolean isActive() + { + return true; + } + + public void confirmAutoClose() + { + + } + + public void set(String key, Object value) + { + } + + public Object get(String key) + { + return null; + } + + public boolean isAutoClose() + { + return false; + } + + public boolean isBrowser() + { + return false; + } + + public boolean isClosed() + { + return _closed; + } + + public boolean acquires() + { + return true; + } + + public boolean seesRequeues() + { + return true; + } + + public boolean isSuspended() + { + return false; + } + + public void queueDeleted(AMQQueue queue) + { + } + + public void releaseSendLock() + { + _stateChangeLock.unlock(); + } + + public void resend(QueueEntry entry) throws AMQException + { + } + + public void onDequeue(QueueEntry queueEntry) + { + } + + public void restoreCredit(QueueEntry queueEntry) + { + } + + public void send(QueueEntry entry) throws AMQException + { + if (messages.contains(entry)) + { + entry.setRedelivered(); + } + messages.add(entry); + } + + public void setQueueContext(AMQQueue.Context queueContext) + { + _queueContext = queueContext; + } + + public void setQueue(AMQQueue queue, boolean exclusive) + { + this.queue = queue; + } + + public void setNoLocal(boolean noLocal) + { + } + + public void setStateListener(StateListener listener) + { + this._listener = listener; + } + + public State getState() + { + return _state; + } + + public boolean wouldSuspend(QueueEntry msg) + { + return false; + } + + public ArrayList<QueueEntry> getMessages() + { + return messages; + } + + public boolean isSessionTransactional() + { + return false; + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/subscription/QueueBrowserUsesNoAckTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/subscription/QueueBrowserUsesNoAckTest.java new file mode 100644 index 0000000000..b315a79b33 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/subscription/QueueBrowserUsesNoAckTest.java @@ -0,0 +1,77 @@ +/* + * + * 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.qpid.server.subscription; + +import org.apache.qpid.server.util.InternalBrokerBaseCase; +import org.apache.qpid.server.protocol.InternalTestProtocolSession; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.AMQException; + +import java.util.List; + +public class QueueBrowserUsesNoAckTest extends InternalBrokerBaseCase +{ + + public void testQueueBrowserUsesNoAck() throws AMQException + { + int sendMessageCount = 2; + int prefetch = 1; + + //Check store is empty + checkStoreContents(0); + + //Send required messsages to the queue + publishMessages(getSession(), getChannel(), sendMessageCount); + + //Ensure they are stored + checkStoreContents(sendMessageCount); + + //Check that there are no unacked messages + assertEquals("Channel should have no unacked msgs ", 0, + getChannel().getUnacknowledgedMessageMap().size()); + + //Set the prefetch on the session to be less than the sent messages + getChannel().setCredit(0, prefetch); + + //browse the queue + AMQShortString browser = browse(getChannel(), getQueue()); + + getQueue().deliverAsync(); + + //Wait for messages to fill the prefetch + getSession().awaitDelivery(prefetch); + + //Get those messages + List<InternalTestProtocolSession.DeliveryPair> messages = + getSession().getDelivers(getChannel().getChannelId(), browser, + prefetch); + + //Ensure we recevied the prefetched messages + assertEquals(prefetch, messages.size()); + + //Check the process didn't suspend the subscription as this would + // indicate we are using the prefetch credit. i.e. using acks not No-Ack + assertTrue("The subscription has been suspended", + !getChannel().getSubscription(browser).getState() + .equals(Subscription.State.SUSPENDED)); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/txn/AutoCommitTransactionTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/txn/AutoCommitTransactionTest.java new file mode 100644 index 0000000000..9afed49922 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/txn/AutoCommitTransactionTest.java @@ -0,0 +1,442 @@ +/* + * + * 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.qpid.server.txn; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.MockAMQQueue; +import org.apache.qpid.server.queue.MockQueueEntry; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.store.TransactionLog; +import org.apache.qpid.server.txn.MockStoreTransaction.TransactionState; +import org.apache.qpid.test.utils.QpidTestCase; + +/** + * A unit test ensuring that AutoCommitTransaction creates a separate transaction for + * each dequeue/enqueue operation that involves enlistable messages. Verifies + * that the transaction is properly committed (or rolled-back in the case of exception), + * and that post transaction actions are correctly fired. + * + */ +public class AutoCommitTransactionTest extends QpidTestCase +{ + private ServerTransaction _transaction = null; // Class under test + + private TransactionLog _transactionLog; + private AMQQueue _queue; + private List<AMQQueue> _queues; + private Collection<QueueEntry> _queueEntries; + private ServerMessage _message; + private MockAction _action; + private MockStoreTransaction _storeTransaction; + + + @Override + protected void setUp() throws Exception + { + super.setUp(); + + _storeTransaction = createTestStoreTransaction(false); + _transactionLog = MockStoreTransaction.createTestTransactionLog(_storeTransaction); + _action = new MockAction(); + + _transaction = new AutoCommitTransaction(_transactionLog); + } + + /** + * Tests the enqueue of a non persistent message to a single non durable queue. + * Asserts that a store transaction has not been started and commit action fired. + */ + public void testEnqueueToNonDurableQueueOfNonPersistentMessage() throws Exception + { + _message = createTestMessage(false); + _queue = createTestAMQQueue(false); + + _transaction.enqueue(_queue, _message, _action); + + assertEquals("Enqueue of non-persistent message must not cause message to be enqueued", 0, _storeTransaction.getNumberOfEnqueuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + assertFalse("Rollback action must not be fired", _action.isRollbackActionFired()); + assertTrue("Post commit action must be fired", _action.isPostCommitActionFired()); + + } + + /** + * Tests the enqueue of a persistent message to a durable queue. + * Asserts that a store transaction has been committed and commit action fired. + */ + public void testEnqueueToDurableQueueOfPersistentMessage() throws Exception + { + _message = createTestMessage(true); + _queue = createTestAMQQueue(true); + + _transaction.enqueue(_queue, _message, _action); + + assertEquals("Enqueue of persistent message to durable queue must cause message to be enqueued", 1, _storeTransaction.getNumberOfEnqueuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.COMMITTED, _storeTransaction.getState()); + assertFalse("Rollback action must not be fired", _action.isRollbackActionFired()); + assertTrue("Post commit action must be fired", _action.isPostCommitActionFired()); + } + + /** + * Tests the case where the store operation throws an exception. + * Asserts that the transaction is aborted and rollback action is fired. + */ + public void testStoreEnqueueCausesException() throws Exception + { + _message = createTestMessage(true); + _queue = createTestAMQQueue(true); + + _storeTransaction = createTestStoreTransaction(true); + _transactionLog = MockStoreTransaction.createTestTransactionLog(_storeTransaction); + _transaction = new AutoCommitTransaction(_transactionLog); + + try + { + _transaction.enqueue(_queue, _message, _action); + fail("Exception not thrown"); + } + catch (RuntimeException re) + { + // PASS + } + + assertEquals("Unexpected transaction state", TransactionState.ABORTED, _storeTransaction.getState()); + assertTrue("Rollback action must be fired", _action.isRollbackActionFired()); + assertFalse("Post commit action must be fired", _action.isPostCommitActionFired()); + } + + /** + * Tests the enqueue of a non persistent message to a many non durable queues. + * Asserts that a store transaction has not been started and post commit action fired. + */ + public void testEnqueueToManyNonDurableQueuesOfNonPersistentMessage() throws Exception + { + _message = createTestMessage(false); + _queues = createTestBaseQueues(new boolean[] {false, false, false}); + + _transaction.enqueue(_queues, _message, _action); + + assertEquals("Enqueue of non-persistent message must not cause message to be enqueued", 0, _storeTransaction.getNumberOfEnqueuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + assertFalse("Rollback action must not be fired", _action.isRollbackActionFired()); + assertTrue("Post commit action must be fired", _action.isPostCommitActionFired()); + + } + + + /** + * Tests the enqueue of a persistent message to a many non durable queues. + * Asserts that a store transaction has not been started and post commit action + * fired. + */ + public void testEnqueueToManyNonDurableQueuesOfPersistentMessage() throws Exception + { + _message = createTestMessage(true); + _queues = createTestBaseQueues(new boolean[] {false, false, false}); + + _transaction.enqueue(_queues, _message, _action); + + assertEquals("Enqueue of persistent message to non-durable queues must not cause message to be enqueued", 0, _storeTransaction.getNumberOfEnqueuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + assertFalse("Rollback action must not be fired", _action.isRollbackActionFired()); + assertTrue("Post commit action must be fired", _action.isPostCommitActionFired()); + + } + + /** + * Tests the enqueue of a persistent message to many queues, some durable others not. + * Asserts that a store transaction has been committed and post commit action fired. + */ + public void testEnqueueToDurableAndNonDurableQueuesOfPersistentMessage() throws Exception + { + _message = createTestMessage(true); + _queues = createTestBaseQueues(new boolean[] {false, true, false, true}); + + _transaction.enqueue(_queues, _message, _action); + + assertEquals("Enqueue of persistent message to durable/non-durable queues must cause messages to be enqueued", 2, _storeTransaction.getNumberOfEnqueuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.COMMITTED, _storeTransaction.getState()); + assertFalse("Rollback action must not be fired", _action.isRollbackActionFired()); + assertTrue("Post commit action must be fired", _action.isPostCommitActionFired()); + } + + /** + * Tests the case where the store operation throws an exception. + * Asserts that the transaction is aborted and rollback action fired. + */ + public void testStoreEnqueuesCausesExceptions() throws Exception + { + _message = createTestMessage(true); + _queues = createTestBaseQueues(new boolean[] {true, true}); + + _storeTransaction = createTestStoreTransaction(true); + _transactionLog = MockStoreTransaction.createTestTransactionLog(_storeTransaction); + _transaction = new AutoCommitTransaction(_transactionLog); + + try + { + _transaction.enqueue(_queues, _message, _action); + fail("Exception not thrown"); + } + catch (RuntimeException re) + { + // PASS + } + + assertEquals("Unexpected transaction state", TransactionState.ABORTED, _storeTransaction.getState()); + assertTrue("Rollback action must be fired", _action.isRollbackActionFired()); + assertFalse("Post commit action must not be fired", _action.isPostCommitActionFired()); + } + + /** + * Tests the dequeue of a non persistent message from a single non durable queue. + * Asserts that a store transaction has not been started and post commit action + * fired. + */ + public void testDequeueFromNonDurableQueueOfNonPersistentMessage() throws Exception + { + _message = createTestMessage(false); + _queue = createTestAMQQueue(false); + + _transaction.dequeue(_queue, _message, _action); + + assertEquals("Dequeue of non-persistent message must not cause message to be dequeued", 0, _storeTransaction.getNumberOfDequeuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + assertFalse("Rollback action must not be fired", _action.isRollbackActionFired()); + assertTrue("Post commit action must be fired", _action.isPostCommitActionFired()); + + } + + /** + * Tests the dequeue of a persistent message from a single non durable queue. + * Asserts that a store transaction has not been started and post commit + * action fired. + */ + public void testDequeueFromDurableQueueOfPersistentMessage() throws Exception + { + _message = createTestMessage(true); + _queue = createTestAMQQueue(true); + + _transaction.dequeue(_queue, _message, _action); + + assertEquals("Dequeue of persistent message to durable queue must cause message to be dequeued",1, _storeTransaction.getNumberOfDequeuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.COMMITTED, _storeTransaction.getState()); + assertFalse("Rollback action must not be fired", _action.isRollbackActionFired()); + assertTrue("Post commit action must be fired", _action.isPostCommitActionFired()); + } + + /** + * Tests the case where the store operation throws an exception. + * Asserts that the transaction is aborted and post rollback action + * fired. + */ + public void testStoreDequeueCausesException() throws Exception + { + _message = createTestMessage(true); + _queue = createTestAMQQueue(true); + + _storeTransaction = createTestStoreTransaction(true); + _transactionLog = MockStoreTransaction.createTestTransactionLog(_storeTransaction); + _transaction = new AutoCommitTransaction(_transactionLog); + + try + { + _transaction.dequeue(_queue, _message, _action); + fail("Exception not thrown"); + } + catch (RuntimeException re) + { + // PASS + } + + assertEquals("Unexpected transaction state", TransactionState.ABORTED, _storeTransaction.getState()); + + assertTrue("Rollback action must be fired", _action.isRollbackActionFired()); + assertFalse("Post commit action must not be fired", _action.isPostCommitActionFired()); + } + + /** + * Tests the dequeue of a non persistent message from many non durable queues. + * Asserts that a store transaction has not been started and post commit action + * fired. + */ + public void testDequeueFromManyNonDurableQueuesOfNonPersistentMessage() throws Exception + { + _queueEntries = createTestQueueEntries(new boolean[] {false, false, false}, new boolean[] {false, false, false}); + + _transaction.dequeue(_queueEntries, _action); + + assertEquals("Dequeue of non-persistent messages must not cause message to be dequeued", 0, _storeTransaction.getNumberOfDequeuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + assertEquals("Rollback action must not be fired", false, _action.isRollbackActionFired()); + assertEquals("Post commit action must be fired", true, _action.isPostCommitActionFired()); + + } + + + /** + * Tests the dequeue of a persistent message from a many non durable queues. + * Asserts that a store transaction has not been started and post commit action + * fired. + */ + public void testDequeueFromManyNonDurableQueuesOfPersistentMessage() throws Exception + { + _queueEntries = createTestQueueEntries(new boolean[] {false, false, false}, new boolean[] {true, true, true}); + + _transaction.dequeue(_queueEntries, _action); + + assertEquals("Dequeue of persistent message from non-durable queues must not cause message to be enqueued", 0, _storeTransaction.getNumberOfDequeuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + assertFalse("Rollback action must not be fired", _action.isRollbackActionFired()); + assertTrue("Post commit action must be fired", _action.isPostCommitActionFired()); + } + + /** + * Tests the dequeue of a persistent message from many queues, some durable others not. + * Asserts that a store transaction has not been started and post commit action fired. + */ + public void testDequeueFromDurableAndNonDurableQueuesOfPersistentMessage() throws Exception + { + // A transaction will exist owing to the 1st and 3rd. + _queueEntries = createTestQueueEntries(new boolean[] {true, false, true, true}, new boolean[] {true, true, true, false}); + + _transaction.dequeue(_queueEntries, _action); + + assertEquals("Dequeue of persistent messages from durable/non-durable queues must cause messages to be dequeued", 2, _storeTransaction.getNumberOfDequeuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.COMMITTED, _storeTransaction.getState()); + assertFalse("Rollback action must not be fired", _action.isRollbackActionFired()); + assertTrue("Post commit action must be fired", _action.isPostCommitActionFired()); + } + + /** + * Tests the case where the store operation throws an exception. + * Asserts that the transaction is aborted and post rollback action fired. + */ + public void testStoreDequeuesCauseExceptions() throws Exception + { + // Transactions will exist owing to the 1st and 3rd queue entries in the collection + _queueEntries = createTestQueueEntries(new boolean[] {true}, new boolean[] {true}); + + _storeTransaction = createTestStoreTransaction(true); + _transactionLog = MockStoreTransaction.createTestTransactionLog(_storeTransaction); + _transaction = new AutoCommitTransaction(_transactionLog); + + try + { + _transaction.dequeue(_queueEntries, _action); + fail("Exception not thrown"); + } + catch (RuntimeException re) + { + // PASS + } + + assertEquals("Unexpected transaction state", TransactionState.ABORTED, _storeTransaction.getState()); + + assertTrue("Rollback action must be fired", _action.isRollbackActionFired()); + assertFalse("Post commit action must not be fired", _action.isPostCommitActionFired()); + } + + /** + * Tests the add of a post-commit action. Since AutoCommitTranctions + * have no long lived transactions, the post commit action is fired immediately. + */ + public void testPostCommitActionFiredImmediately() throws Exception + { + + _transaction.addPostTransactionAction(_action); + + assertTrue("Post commit action must be fired", _action.isPostCommitActionFired()); + assertFalse("Rollback action must be fired", _action.isRollbackActionFired()); + } + + private Collection<QueueEntry> createTestQueueEntries(boolean[] queueDurableFlags, boolean[] messagePersistentFlags) + { + Collection<QueueEntry> queueEntries = new ArrayList<QueueEntry>(); + + assertTrue("Boolean arrays must be the same length", queueDurableFlags.length == messagePersistentFlags.length); + + for(int i = 0; i < queueDurableFlags.length; i++) + { + final AMQQueue queue = createTestAMQQueue(queueDurableFlags[i]); + final ServerMessage message = createTestMessage(messagePersistentFlags[i]); + + queueEntries.add(new MockQueueEntry() + { + + @Override + public ServerMessage getMessage() + { + return message; + } + + @Override + public AMQQueue getQueue() + { + return queue; + } + + }); + } + + return queueEntries; + } + + private MockStoreTransaction createTestStoreTransaction(boolean throwException) + { + return new MockStoreTransaction(throwException); + } + + private List<AMQQueue> createTestBaseQueues(boolean[] durableFlags) + { + List<AMQQueue> queues = new ArrayList<AMQQueue>(); + for (boolean b: durableFlags) + { + queues.add(createTestAMQQueue(b)); + } + + return queues; + } + + private AMQQueue createTestAMQQueue(final boolean durable) + { + return new MockAMQQueue("mockQueue") + { + @Override + public boolean isDurable() + { + return durable; + } + + }; + } + + private ServerMessage createTestMessage(final boolean persistent) + { + return new MockServerMessage(persistent); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/txn/LocalTransactionTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/txn/LocalTransactionTest.java new file mode 100644 index 0000000000..e81fd8e3f1 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/txn/LocalTransactionTest.java @@ -0,0 +1,557 @@ +/* + * + * 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.qpid.server.txn; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.MockAMQQueue; +import org.apache.qpid.server.queue.MockQueueEntry; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.store.TransactionLog; +import org.apache.qpid.server.txn.MockStoreTransaction.TransactionState; +import org.apache.qpid.test.utils.QpidTestCase; + +/** + * A unit test ensuring that LocalTransactionTest creates a long-lived store transaction + * that spans many dequeue/enqueue operations of enlistable messages. Verifies + * that the long-lived transaction is properly committed and rolled back, and that + * post transaction actions are correctly fired. + * + */ +public class LocalTransactionTest extends QpidTestCase +{ + private ServerTransaction _transaction = null; // Class under test + + private AMQQueue _queue; + private List<AMQQueue> _queues; + private Collection<QueueEntry> _queueEntries; + private ServerMessage _message; + private MockAction _action1; + private MockAction _action2; + private MockStoreTransaction _storeTransaction; + private TransactionLog _transactionLog; + + + @Override + protected void setUp() throws Exception + { + super.setUp(); + + _storeTransaction = createTestStoreTransaction(false); + _transactionLog = MockStoreTransaction.createTestTransactionLog(_storeTransaction); + _action1 = new MockAction(); + _action2 = new MockAction(); + + _transaction = new LocalTransaction(_transactionLog); + + } + + + /** + * Tests the enqueue of a non persistent message to a single non durable queue. + * Asserts that a store transaction has not been started. + */ + public void testEnqueueToNonDurableQueueOfNonPersistentMessage() throws Exception + { + _message = createTestMessage(false); + _queue = createTestAMQQueue(false); + + _transaction.enqueue(_queue, _message, _action1); + + assertEquals("Enqueue of non-persistent message must not cause message to be enqueued", 0, _storeTransaction.getNumberOfEnqueuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + assertNotFired(_action1); + } + + /** + * Tests the enqueue of a persistent message to a durable queue. + * Asserts that a store transaction has been started. + */ + public void testEnqueueToDurableQueueOfPersistentMessage() throws Exception + { + _message = createTestMessage(true); + _queue = createTestAMQQueue(true); + + _transaction.enqueue(_queue, _message, _action1); + + assertEquals("Enqueue of persistent message to durable queue must cause message to be enqueued", 1, _storeTransaction.getNumberOfEnqueuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.STARTED, _storeTransaction.getState()); + assertNotFired(_action1); + } + + /** + * Tests the case where the store operation throws an exception. + * Asserts that the transaction is aborted. + */ + public void testStoreEnqueueCausesException() throws Exception + { + _message = createTestMessage(true); + _queue = createTestAMQQueue(true); + + _storeTransaction = createTestStoreTransaction(true); + _transactionLog = MockStoreTransaction.createTestTransactionLog(_storeTransaction); + _transaction = new LocalTransaction(_transactionLog); + + try + { + _transaction.enqueue(_queue, _message, _action1); + fail("Exception not thrown"); + } + catch (RuntimeException re) + { + // PASS + } + + assertTrue("Rollback action must be fired", _action1.isRollbackActionFired()); + assertEquals("Unexpected transaction state", TransactionState.ABORTED, _storeTransaction.getState()); + + assertFalse("Post commit action must not be fired", _action1.isPostCommitActionFired()); + + } + + /** + * Tests the enqueue of a non persistent message to a many non durable queues. + * Asserts that a store transaction has not been started. + */ + public void testEnqueueToManyNonDurableQueuesOfNonPersistentMessage() throws Exception + { + _message = createTestMessage(false); + _queues = createTestBaseQueues(new boolean[] {false, false, false}); + + _transaction.enqueue(_queues, _message, _action1); + + assertEquals("Enqueue of non-persistent message must not cause message to be enqueued", 0, _storeTransaction.getNumberOfEnqueuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + assertNotFired(_action1); + } + + /** + * Tests the enqueue of a persistent message to a many non durable queues. + * Asserts that a store transaction has not been started. + */ + public void testEnqueueToManyNonDurableQueuesOfPersistentMessage() throws Exception + { + _message = createTestMessage(true); + _queues = createTestBaseQueues(new boolean[] {false, false, false}); + + _transaction.enqueue(_queues, _message, _action1); + + assertEquals("Enqueue of persistent message to non-durable queues must not cause message to be enqueued", 0, _storeTransaction.getNumberOfEnqueuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + assertNotFired(_action1); + + } + + /** + * Tests the enqueue of a persistent message to many queues, some durable others not. + * Asserts that a store transaction has been started. + */ + public void testEnqueueToDurableAndNonDurableQueuesOfPersistentMessage() throws Exception + { + _message = createTestMessage(true); + _queues = createTestBaseQueues(new boolean[] {false, true, false, true}); + + _transaction.enqueue(_queues, _message, _action1); + + assertEquals("Enqueue of persistent message to durable/non-durable queues must cause messages to be enqueued", 2, _storeTransaction.getNumberOfEnqueuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.STARTED, _storeTransaction.getState()); + assertNotFired(_action1); + + } + + /** + * Tests the case where the store operation throws an exception. + * Asserts that the transaction is aborted. + */ + public void testStoreEnqueuesCausesExceptions() throws Exception + { + _message = createTestMessage(true); + _queues = createTestBaseQueues(new boolean[] {true, true}); + + _storeTransaction = createTestStoreTransaction(true); + _transactionLog = MockStoreTransaction.createTestTransactionLog(_storeTransaction); + _transaction = new LocalTransaction(_transactionLog); + + try + { + _transaction.enqueue(_queues, _message, _action1); + fail("Exception not thrown"); + } + catch (RuntimeException re) + { + // PASS + } + + assertTrue("Rollback action must be fired", _action1.isRollbackActionFired()); + assertEquals("Unexpected transaction state", TransactionState.ABORTED, _storeTransaction.getState()); + assertFalse("Post commit action must not be fired", _action1.isPostCommitActionFired()); + } + + /** + * Tests the dequeue of a non persistent message from a single non durable queue. + * Asserts that a store transaction has not been started. + */ + public void testDequeueFromNonDurableQueueOfNonPersistentMessage() throws Exception + { + _message = createTestMessage(false); + _queue = createTestAMQQueue(false); + + _transaction.dequeue(_queue, _message, _action1); + + assertEquals("Dequeue of non-persistent message must not cause message to be enqueued", 0, _storeTransaction.getNumberOfEnqueuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + assertNotFired(_action1); + + } + + /** + * Tests the dequeue of a persistent message from a single non durable queue. + * Asserts that a store transaction has not been started. + */ + public void testDequeueFromDurableQueueOfPersistentMessage() throws Exception + { + _message = createTestMessage(true); + _queue = createTestAMQQueue(true); + + _transaction.dequeue(_queue, _message, _action1); + + assertEquals("Dequeue of non-persistent message must cause message to be dequeued", 1, _storeTransaction.getNumberOfDequeuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.STARTED, _storeTransaction.getState()); + assertNotFired(_action1); + } + + /** + * Tests the case where the store operation throws an exception. + * Asserts that the transaction is aborted. + */ + public void testStoreDequeueCausesException() throws Exception + { + _message = createTestMessage(true); + _queue = createTestAMQQueue(true); + + _storeTransaction = createTestStoreTransaction(true); + _transactionLog = MockStoreTransaction.createTestTransactionLog(_storeTransaction); + _transaction = new LocalTransaction(_transactionLog); + + try + { + _transaction.dequeue(_queue, _message, _action1); + fail("Exception not thrown"); + } + catch (RuntimeException re) + { + // PASS + } + + assertTrue("Rollback action must be fired", _action1.isRollbackActionFired()); + assertEquals("Unexpected transaction state", TransactionState.ABORTED, _storeTransaction.getState()); + assertFalse("Post commit action must not be fired", _action1.isPostCommitActionFired()); + + } + + /** + * Tests the dequeue of a non persistent message from many non durable queues. + * Asserts that a store transaction has not been started. + */ + public void testDequeueFromManyNonDurableQueuesOfNonPersistentMessage() throws Exception + { + _queueEntries = createTestQueueEntries(new boolean[] {false, false, false}, new boolean[] {false, false, false}); + + _transaction.dequeue(_queueEntries, _action1); + + assertEquals("Dequeue of non-persistent messages must not cause message to be dequeued", 0, _storeTransaction.getNumberOfDequeuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + assertNotFired(_action1); + + } + + /** + * Tests the dequeue of a persistent message from a many non durable queues. + * Asserts that a store transaction has not been started. + */ + public void testDequeueFromManyNonDurableQueuesOfPersistentMessage() throws Exception + { + _queueEntries = createTestQueueEntries(new boolean[] {false, false, false}, new boolean[] {true, true, true}); + + _transaction.dequeue(_queueEntries, _action1); + + assertEquals("Dequeue of persistent message from non-durable queues must not cause message to be enqueued", 0, _storeTransaction.getNumberOfDequeuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + assertNotFired(_action1); + } + + /** + * Tests the dequeue of a persistent message from many queues, some durable others not. + * Asserts that a store transaction has not been started. + */ + public void testDequeueFromDurableAndNonDurableQueuesOfPersistentMessage() throws Exception + { + // A transaction will exist owing to the 1st and 3rd. + _queueEntries = createTestQueueEntries(new boolean[] {true, false, true, true}, new boolean[] {true, true, true, false}); + + _transaction.dequeue(_queueEntries, _action1); + + assertEquals("Dequeue of persistent messages from durable/non-durable queues must cause messages to be dequeued", 2, _storeTransaction.getNumberOfDequeuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.STARTED, _storeTransaction.getState()); + assertNotFired(_action1); + } + + /** + * Tests the case where the store operation throws an exception. + * Asserts that the transaction is aborted. + */ + public void testStoreDequeuesCauseExceptions() throws Exception + { + // Transactions will exist owing to the 1st and 3rd queue entries in the collection + _queueEntries = createTestQueueEntries(new boolean[] {true}, new boolean[] {true}); + + _storeTransaction = createTestStoreTransaction(true); + _transactionLog = MockStoreTransaction.createTestTransactionLog(_storeTransaction); + _transaction = new LocalTransaction(_transactionLog); + + try + { + _transaction.dequeue(_queueEntries, _action1); + fail("Exception not thrown"); + } + catch (RuntimeException re) + { + // PASS + } + + assertEquals("Unexpected transaction state", TransactionState.ABORTED, _storeTransaction.getState()); + assertTrue("Rollback action must be fired", _action1.isRollbackActionFired()); + assertFalse("Post commit action must not be fired", _action1.isPostCommitActionFired()); + } + + /** + * Tests the add of a post-commit action. Unlike AutoCommitTranctions, the post transaction actions + * is added to a list to be fired on commit or rollback. + */ + public void testAddingPostCommitActionNotFiredImmediately() throws Exception + { + + _transaction.addPostTransactionAction(_action1); + + assertNotFired(_action1); + } + + + /** + * Tests committing a transaction without work accepted without error and without causing store + * enqueues or dequeues. + */ + public void testCommitNoWork() throws Exception + { + + _transaction.commit(); + + assertEquals("Unexpected number of store dequeues", 0, _storeTransaction.getNumberOfDequeuedMessages()); + assertEquals("Unexpected number of store enqueues", 0, _storeTransaction.getNumberOfEnqueuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + } + + /** + * Tests rolling back a transaction without work accepted without error and without causing store + * enqueues or dequeues. + */ + public void testRollbackNoWork() throws Exception + { + + _transaction.rollback(); + + assertEquals("Unexpected number of store dequeues", 0, _storeTransaction.getNumberOfDequeuedMessages()); + assertEquals("Unexpected number of store enqueues", 0, _storeTransaction.getNumberOfEnqueuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + } + + /** + * Tests the dequeuing of a message with a commit. Test ensures that the underlying store transaction is + * correctly controlled and the post commit action is fired. + */ + public void testCommitWork() throws Exception + { + + _message = createTestMessage(true); + _queue = createTestAMQQueue(true); + + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + assertFalse("Post commit action must not be fired yet", _action1.isPostCommitActionFired()); + + _transaction.dequeue(_queue, _message, _action1); + assertEquals("Unexpected transaction state", TransactionState.STARTED, _storeTransaction.getState()); + assertFalse("Post commit action must not be fired yet", _action1.isPostCommitActionFired()); + + _transaction.commit(); + + assertEquals("Unexpected transaction state", TransactionState.COMMITTED, _storeTransaction.getState()); + assertTrue("Post commit action must be fired", _action1.isPostCommitActionFired()); + } + + /** + * Tests the dequeuing of a message with a rollback. Test ensures that the underlying store transaction is + * correctly controlled and the post rollback action is fired. + */ + public void testRollbackWork() throws Exception + { + + _message = createTestMessage(true); + _queue = createTestAMQQueue(true); + + + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + assertFalse("Rollback action must not be fired yet", _action1.isRollbackActionFired()); + + _transaction.dequeue(_queue, _message, _action1); + + assertEquals("Unexpected transaction state", TransactionState.STARTED, _storeTransaction.getState()); + assertFalse("Rollback action must not be fired yet", _action1.isRollbackActionFired()); + + _transaction.rollback(); + + assertEquals("Unexpected transaction state", TransactionState.ABORTED, _storeTransaction.getState()); + assertTrue("Rollback action must be fired", _action1.isRollbackActionFired()); + + } + + /** + * Variation of testCommitWork with an additional post transaction action. + * + */ + public void testCommitWorkWithAdditionalPostAction() throws Exception + { + + _message = createTestMessage(true); + _queue = createTestAMQQueue(true); + + _transaction.addPostTransactionAction(_action1); + _transaction.dequeue(_queue, _message, _action2); + _transaction.commit(); + + assertEquals("Unexpected transaction state", TransactionState.COMMITTED, _storeTransaction.getState()); + + assertTrue("Post commit action1 must be fired", _action1.isPostCommitActionFired()); + assertTrue("Post commit action2 must be fired", _action2.isPostCommitActionFired()); + + assertFalse("Rollback action1 must not be fired", _action1.isRollbackActionFired()); + assertFalse("Rollback action2 must not be fired", _action1.isRollbackActionFired()); + } + + /** + * Variation of testRollbackWork with an additional post transaction action. + * + */ + public void testRollbackWorkWithAdditionalPostAction() throws Exception + { + + _message = createTestMessage(true); + _queue = createTestAMQQueue(true); + + _transaction.addPostTransactionAction(_action1); + _transaction.dequeue(_queue, _message, _action2); + _transaction.rollback(); + + assertEquals("Unexpected transaction state", TransactionState.ABORTED, _storeTransaction.getState()); + + assertFalse("Post commit action1 must not be fired", _action1.isPostCommitActionFired()); + assertFalse("Post commit action2 must not be fired", _action2.isPostCommitActionFired()); + + assertTrue("Rollback action1 must be fired", _action1.isRollbackActionFired()); + assertTrue("Rollback action2 must be fired", _action1.isRollbackActionFired()); + } + + private Collection<QueueEntry> createTestQueueEntries(boolean[] queueDurableFlags, boolean[] messagePersistentFlags) + { + Collection<QueueEntry> queueEntries = new ArrayList<QueueEntry>(); + + assertTrue("Boolean arrays must be the same length", queueDurableFlags.length == messagePersistentFlags.length); + + for(int i = 0; i < queueDurableFlags.length; i++) + { + final AMQQueue queue = createTestAMQQueue(queueDurableFlags[i]); + final ServerMessage message = createTestMessage(messagePersistentFlags[i]); + + queueEntries.add(new MockQueueEntry() + { + + @Override + public ServerMessage getMessage() + { + return message; + } + + @Override + public AMQQueue getQueue() + { + return queue; + } + + }); + } + + return queueEntries; + } + + private MockStoreTransaction createTestStoreTransaction(boolean throwException) + { + return new MockStoreTransaction(throwException); + } + + private List<AMQQueue> createTestBaseQueues(boolean[] durableFlags) + { + List<AMQQueue> queues = new ArrayList<AMQQueue>(); + for (boolean b: durableFlags) + { + queues.add(createTestAMQQueue(b)); + } + + return queues; + } + + private AMQQueue createTestAMQQueue(final boolean durable) + { + return new MockAMQQueue("mockQueue") + { + @Override + public boolean isDurable() + { + return durable; + } + + }; + } + + private ServerMessage createTestMessage(final boolean persistent) + { + return new MockServerMessage(persistent); + } + + private void assertNotFired(MockAction action) + { + assertFalse("Rollback action must not be fired", action.isRollbackActionFired()); + assertFalse("Post commit action must not be fired", action.isPostCommitActionFired()); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/txn/MockAction.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/txn/MockAction.java new file mode 100644 index 0000000000..975e3e91b9 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/txn/MockAction.java @@ -0,0 +1,56 @@ +/* + * + * 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.qpid.server.txn; + +import org.apache.qpid.server.txn.ServerTransaction.Action; + +/** + * Mock implementation of a ServerTranaction Action + * allowing its state to be observed. + * + */ +class MockAction implements Action +{ + private boolean _rollbackFired = false; + private boolean _postCommitFired = false; + + @Override + public void postCommit() + { + _postCommitFired = true; + } + + @Override + public void onRollback() + { + _rollbackFired = true; + } + + public boolean isRollbackActionFired() + { + return _rollbackFired; + } + + public boolean isPostCommitActionFired() + { + return _postCommitFired; + } +}
\ No newline at end of file diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/txn/MockServerMessage.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/txn/MockServerMessage.java new file mode 100644 index 0000000000..64c62fd029 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/txn/MockServerMessage.java @@ -0,0 +1,114 @@ +/* + * + * 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.qpid.server.txn; + +import java.nio.ByteBuffer; + +import org.apache.commons.lang.NotImplementedException; +import org.apache.qpid.server.configuration.SessionConfig; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.message.MessageReference; +import org.apache.qpid.server.message.ServerMessage; + +/** + * Mock Server Message allowing its persistent flag to be controlled from test. + */ +class MockServerMessage implements ServerMessage +{ + /** + * + */ + private final boolean persistent; + + /** + * @param persistent + */ + MockServerMessage(boolean persistent) + { + this.persistent = persistent; + } + + @Override + public boolean isPersistent() + { + return persistent; + } + + @Override + public MessageReference newReference() + { + throw new NotImplementedException(); + } + + @Override + public boolean isImmediate() + { + throw new NotImplementedException(); + } + + @Override + public long getSize() + { + throw new NotImplementedException(); + } + + @Override + public SessionConfig getSessionConfig() + { + throw new NotImplementedException(); + } + + @Override + public String getRoutingKey() + { + throw new NotImplementedException(); + } + + @Override + public AMQMessageHeader getMessageHeader() + { + throw new NotImplementedException(); + } + + @Override + public long getExpiration() + { + throw new NotImplementedException(); + } + + @Override + public int getContent(ByteBuffer buf, int offset) + { + throw new NotImplementedException(); + } + + @Override + public long getArrivalTime() + { + throw new NotImplementedException(); + } + + @Override + public Long getMessageNumber() + { + return 0L; + } +}
\ No newline at end of file diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/txn/MockStoreTransaction.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/txn/MockStoreTransaction.java new file mode 100644 index 0000000000..5700bba9f8 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/txn/MockStoreTransaction.java @@ -0,0 +1,136 @@ +/* + * + * 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.qpid.server.txn; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.lang.NotImplementedException; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.store.TransactionLog; +import org.apache.qpid.server.store.TransactionLogRecoveryHandler; +import org.apache.qpid.server.store.TransactionLogResource; +import org.apache.qpid.server.store.TransactionLog.StoreFuture; +import org.apache.qpid.server.store.TransactionLog.Transaction; + +/** + * Mock implementation of a (Store) Transaction allow its state to be observed. + * Also provide a factory method to produce TestTransactionLog objects suitable + * for unit test use. + * + */ +class MockStoreTransaction implements Transaction +{ + enum TransactionState {NOT_STARTED, STARTED, COMMITTED, ABORTED}; + + private TransactionState _state = TransactionState.NOT_STARTED; + + private int _numberOfEnqueuedMessages = 0; + private int _numberOfDequeuedMessages = 0; + private boolean _throwExceptionOnQueueOp; + + public MockStoreTransaction(boolean throwExceptionOnQueueOp) + { + _throwExceptionOnQueueOp = throwExceptionOnQueueOp; + } + + public void setState(TransactionState state) + { + _state = state; + } + + public TransactionState getState() + { + return _state; + } + + @Override + public void enqueueMessage(TransactionLogResource queue, Long messageId) throws AMQStoreException + { + if (_throwExceptionOnQueueOp) + { + + throw new AMQStoreException("Mocked exception"); + } + + _numberOfEnqueuedMessages++; + } + + public int getNumberOfDequeuedMessages() + { + return _numberOfDequeuedMessages; + } + + public int getNumberOfEnqueuedMessages() + { + return _numberOfEnqueuedMessages; + } + + + @Override + public void dequeueMessage(TransactionLogResource queue, Long messageId) throws AMQStoreException + { + if (_throwExceptionOnQueueOp) + { + throw new AMQStoreException("Mocked exception"); + } + + _numberOfDequeuedMessages++; + } + + @Override + public void commitTran() throws AMQStoreException + { + _state = TransactionState.COMMITTED; + } + + @Override + public StoreFuture commitTranAsync() throws AMQStoreException + { + throw new NotImplementedException(); + } + + @Override + public void abortTran() throws AMQStoreException + { + _state = TransactionState.ABORTED; + } + + public static TransactionLog createTestTransactionLog(final MockStoreTransaction storeTransaction) + { + return new TransactionLog() + { + + @Override + public void configureTransactionLog(String name, TransactionLogRecoveryHandler recoveryHandler, + Configuration storeConfiguration, LogSubject logSubject) throws Exception + { + } + + @Override + public Transaction newTransaction() + { + storeTransaction.setState(TransactionState.STARTED); + return storeTransaction; + } + + }; + } +}
\ No newline at end of file diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/util/InternalBrokerBaseCase.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/util/InternalBrokerBaseCase.java new file mode 100644 index 0000000000..ff94942457 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/util/InternalBrokerBaseCase.java @@ -0,0 +1,365 @@ +/* + * + * 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.qpid.server.util; + +import org.apache.commons.configuration.XMLConfiguration; +import org.apache.qpid.AMQException; +import org.apache.qpid.common.AMQPFilterTypes; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.logging.SystemOutMessageLogger; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.logging.actors.TestLogActor; +import org.apache.qpid.server.protocol.InternalTestProtocolSession; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.TestableMemoryMessageStore; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.test.utils.QpidTestCase; +import org.apache.qpid.util.MockChannel; + + +public class InternalBrokerBaseCase extends QpidTestCase +{ + private IApplicationRegistry _registry; + private MessageStore _messageStore; + private MockChannel _channel; + private InternalTestProtocolSession _session; + private VirtualHost _virtualHost; + private AMQQueue _queue; + private AMQShortString QUEUE_NAME; + private ServerConfiguration _configuration; + private XMLConfiguration _configXml = new XMLConfiguration(); + private boolean _started = false; + + public void setUp() throws Exception + { + super.setUp(); + + _configXml.addProperty("virtualhosts.virtualhost.name", "test"); + _configXml.addProperty("virtualhosts.virtualhost.test.store.class", TestableMemoryMessageStore.class.getName()); + + _configXml.addProperty("virtualhosts.virtualhost(-1).name", getName()); + _configXml.addProperty("virtualhosts.virtualhost(-1)."+getName()+".store.class", TestableMemoryMessageStore.class.getName()); + + createBroker(); + } + + protected void createBroker() throws Exception + { + _started = true; + CurrentActor.set(new TestLogActor(new SystemOutMessageLogger())); + + _configuration = new ServerConfiguration(_configXml); + + configure(); + + _registry = new TestApplicationRegistry(_configuration); + ApplicationRegistry.initialise(_registry); + _registry.getVirtualHostRegistry().setDefaultVirtualHostName(getName()); + _virtualHost = _registry.getVirtualHostRegistry().getVirtualHost(getName()); + + QUEUE_NAME = new AMQShortString("test"); + // Create a queue on the test Vhost.. this will aid in diagnosing duff tests + // as the ExpiredMessage Task will log with the test Name. + _queue = AMQQueueFactory.createAMQQueueImpl(QUEUE_NAME, false, new AMQShortString("testowner"), + false, false, _virtualHost, null); + + Exchange defaultExchange = _virtualHost.getExchangeRegistry().getDefaultExchange(); + _virtualHost.getBindingFactory().addBinding(QUEUE_NAME.toString(), _queue, defaultExchange, null); + + _virtualHost = _registry.getVirtualHostRegistry().getVirtualHost("test"); + _messageStore = _virtualHost.getMessageStore(); + + _queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString(getName()), false, new AMQShortString("testowner"), + false, false, _virtualHost, null); + + _virtualHost.getQueueRegistry().registerQueue(_queue); + + defaultExchange = _virtualHost.getExchangeRegistry().getDefaultExchange(); + + _virtualHost.getBindingFactory().addBinding(getName(), _queue, defaultExchange, null); + + _session = new InternalTestProtocolSession(_virtualHost); + CurrentActor.set(_session.getLogActor()); + + _channel = new MockChannel(_session, 1, _messageStore); + + _session.addChannel(_channel); + } + + protected void configure() + { + // Allow other tests to override configuration + } + + protected void stopBroker() + { + try + { + //Remove the ProtocolSession Actor added during createBroker + CurrentActor.remove(); + } + finally + { + ApplicationRegistry.remove(); + _started = false; + } + } + + + public void tearDown() throws Exception + { + try + { + if (_started) + { + stopBroker(); + } + } + finally + { + super.tearDown(); + // Purge Any erroneously added actors + CurrentActor.removeAll(); + } + } + + protected void checkStoreContents(int messageCount) + { + assertEquals("Message header count incorrect in the MetaDataMap", messageCount, ((TestableMemoryMessageStore) _messageStore).getMessageCount()); + + //The above publish message is sufficiently small not to fit in the header so no Body is required. + //assertEquals("Message body count incorrect in the ContentBodyMap", messageCount, ((TestableMemoryMessageStore) _messageStore).getContentBodyMap().size()); + } + + protected AMQShortString subscribe(InternalTestProtocolSession session, AMQChannel channel, AMQQueue queue) + { + try + { + return channel.subscribeToQueue(null, queue, true, null, false, true); + } + catch (AMQException e) + { + e.printStackTrace(); + fail(e.getMessage()); + } + + //Keep the compiler happy + return null; + } + + protected AMQShortString browse(AMQChannel channel, AMQQueue queue) + { + try + { + FieldTable filters = new FieldTable(); + filters.put(AMQPFilterTypes.NO_CONSUME.getValue(), true); + + return channel.subscribeToQueue(null, queue, true, filters, false, true); + } + catch (AMQException e) + { + e.printStackTrace(); + fail(e.getMessage()); + } + + //Keep the compiler happy + return null; + } + + public void publishMessages(InternalTestProtocolSession session, AMQChannel channel, int messages) throws AMQException + { + MessagePublishInfo info = new MessagePublishInfo() + { + public AMQShortString getExchange() + { + return ExchangeDefaults.DEFAULT_EXCHANGE_NAME; + } + + public void setExchange(AMQShortString exchange) + { + + } + + public boolean isImmediate() + { + return false; + } + + public boolean isMandatory() + { + return false; + } + + public AMQShortString getRoutingKey() + { + return new AMQShortString(getName()); + } + }; + + for (int count = 0; count < messages; count++) + { + channel.setPublishFrame(info, _virtualHost.getExchangeRegistry().getExchange(info.getExchange())); + + //Set the body size + ContentHeaderBody _headerBody = new ContentHeaderBody(); + _headerBody.bodySize = 0; + + //Set Minimum properties + BasicContentHeaderProperties properties = new BasicContentHeaderProperties(); + + properties.setExpiration(0L); + properties.setTimestamp(System.currentTimeMillis()); + + //Make Message Persistent + properties.setDeliveryMode((byte) 2); + + _headerBody.setProperties(properties); + + channel.publishContentHeader(_headerBody); + } + + } + + public void acknowledge(AMQChannel channel, long deliveryTag) + { + try + { + channel.acknowledgeMessage(deliveryTag, false); + } + catch (AMQException e) + { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + public IApplicationRegistry getRegistry() + { + return _registry; + } + + public void setRegistry(IApplicationRegistry registry) + { + _registry = registry; + } + + public MessageStore getMessageStore() + { + return _messageStore; + } + + public void setMessageStore(MessageStore messageStore) + { + _messageStore = messageStore; + } + + public MockChannel getChannel() + { + return _channel; + } + + public void setChannel(MockChannel channel) + { + _channel = channel; + } + + public InternalTestProtocolSession getSession() + { + return _session; + } + + public void setSession(InternalTestProtocolSession session) + { + _session = session; + } + + public VirtualHost getVirtualHost() + { + return _virtualHost; + } + + public void setVirtualHost(VirtualHost virtualHost) + { + _virtualHost = virtualHost; + } + + public AMQQueue getQueue() + { + return _queue; + } + + public void setQueue(AMQQueue queue) + { + _queue = queue; + } + + public AMQShortString getQUEUE_NAME() + { + return QUEUE_NAME; + } + + public void setQUEUE_NAME(AMQShortString QUEUE_NAME) + { + this.QUEUE_NAME = QUEUE_NAME; + } + + public ServerConfiguration getConfiguration() + { + return _configuration; + } + + public void setConfiguration(ServerConfiguration configuration) + { + _configuration = configuration; + } + + public XMLConfiguration getConfigXml() + { + return _configXml; + } + + public void setConfigXml(XMLConfiguration configXml) + { + _configXml = configXml; + } + + public boolean isStarted() + { + return _started; + } + + public void setStarted(boolean started) + { + _started = started; + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/util/LoggingProxyTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/util/LoggingProxyTest.java new file mode 100644 index 0000000000..c7db51016e --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/util/LoggingProxyTest.java @@ -0,0 +1,88 @@ +/* + * + * 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.qpid.server.util; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import junit.framework.TestCase; + +public class LoggingProxyTest extends TestCase +{ + static interface IFoo { + void foo(); + void foo(int i, Collection c); + String bar(); + String bar(String s, List l); + } + + static class Foo implements IFoo { + public void foo() + { + } + + public void foo(int i, Collection c) + { + } + + public String bar() + { + return null; + } + + public String bar(String s, List l) + { + return "ha"; + } + } + + public void testSimple() { + LoggingProxy proxy = new LoggingProxy(new Foo(), 20); + IFoo foo = (IFoo)proxy.getProxy(IFoo.class); + foo.foo(); + assertEquals(2, proxy.getBufferSize()); + assertTrue(proxy.getBuffer().get(0).toString().matches(".*: foo\\(\\) entered$")); + assertTrue(proxy.getBuffer().get(1).toString().matches(".*: foo\\(\\) returned$")); + + foo.foo(3, Arrays.asList(0, 1, 2)); + assertEquals(4, proxy.getBufferSize()); + assertTrue(proxy.getBuffer().get(2).toString().matches(".*: foo\\(\\[3, \\[0, 1, 2\\]\\]\\) entered$")); + assertTrue(proxy.getBuffer().get(3).toString().matches(".*: foo\\(\\) returned$")); + + foo.bar(); + assertEquals(6, proxy.getBufferSize()); + assertTrue(proxy.getBuffer().get(4).toString().matches(".*: bar\\(\\) entered$")); + assertTrue(proxy.getBuffer().get(5).toString().matches(".*: bar\\(\\) returned null$")); + + foo.bar("hello", Arrays.asList(1, 2, 3)); + assertEquals(8, proxy.getBufferSize()); + assertTrue(proxy.getBuffer().get(6).toString().matches(".*: bar\\(\\[hello, \\[1, 2, 3\\]\\]\\) entered$")); + assertTrue(proxy.getBuffer().get(7).toString().matches(".*: bar\\(\\) returned ha$")); + + proxy.dump(); + } + + public static junit.framework.Test suite() + { + return new junit.framework.TestSuite(LoggingProxyTest.class); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/util/TestApplicationRegistry.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/util/TestApplicationRegistry.java new file mode 100644 index 0000000000..af8997cf40 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/util/TestApplicationRegistry.java @@ -0,0 +1,48 @@ +/* + * + * 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.qpid.server.util; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.security.auth.database.PropertiesPrincipalDatabaseManager; + +import java.util.Properties; + + +public class TestApplicationRegistry extends ApplicationRegistry +{ + public TestApplicationRegistry(ServerConfiguration config) throws ConfigurationException + { + super(config); + } + + protected void createDatabaseManager(ServerConfiguration configuration) throws Exception + { + Properties users = new Properties(); + users.put("guest","guest"); + users.put("admin","admin"); + _databaseManager = new PropertiesPrincipalDatabaseManager("testPasswordFile", users); + } + +} + + diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/virtualhost/plugins/SlowConsumerDetectionConfigurationTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/virtualhost/plugins/SlowConsumerDetectionConfigurationTest.java new file mode 100644 index 0000000000..cc11d68e07 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/virtualhost/plugins/SlowConsumerDetectionConfigurationTest.java @@ -0,0 +1,346 @@ +/* + * + * 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.qpid.server.virtualhost.plugins; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.XMLConfiguration; +import org.apache.qpid.server.configuration.plugins.SlowConsumerDetectionConfiguration; +import org.apache.qpid.server.util.InternalBrokerBaseCase; + +import java.util.concurrent.TimeUnit; + +/** + * Provide Unit Test coverage of the virtualhost SlowConsumer Configuration + * This is what controls how often the plugin will execute + */ +public class SlowConsumerDetectionConfigurationTest extends InternalBrokerBaseCase +{ + + /** + * Default Testing: + * + * Provide a fully complete and valid configuration specifying 'delay' and + * 'timeunit' and ensure that it is correctly processed. + * + * Ensure no exceptions are thrown and that we get the same values back that + * were put into the configuration. + */ + public void testConfigLoadingValidConfig() + { + SlowConsumerDetectionConfiguration config = new SlowConsumerDetectionConfiguration(); + + XMLConfiguration xmlconfig = new XMLConfiguration(); + + long DELAY=10; + String TIMEUNIT=TimeUnit.MICROSECONDS.toString(); + xmlconfig.addProperty("delay", String.valueOf(DELAY)); + xmlconfig.addProperty("timeunit", TIMEUNIT); + + // Create a CompositeConfiguration as this is what the broker uses + CompositeConfiguration composite = new CompositeConfiguration(); + composite.addConfiguration(xmlconfig); + + try + { + config.setConfiguration("", composite); + } + catch (ConfigurationException e) + { + e.printStackTrace(); + fail(e.getMessage()); + } + + assertEquals("Delay not correctly returned.", DELAY, config.getDelay()); + assertEquals("TimeUnit not correctly returned.", + TIMEUNIT, String.valueOf(config.getTimeUnit())); + } + + /** + * Default Testing: + * + * Test Missing TimeUnit value gets default. + * + * The TimeUnit value is optional and default to SECONDS. + * + * Test that if we do not specify a TimeUnit then we correctly get seconds. + * + * Also verify that relying on the default does not impact the setting of + * the 'delay' value. + * + */ + public void testConfigLoadingMissingTimeUnitDefaults() + { + SlowConsumerDetectionConfiguration config = new SlowConsumerDetectionConfiguration(); + + XMLConfiguration xmlconfig = new XMLConfiguration(); + + long DELAY=10; + xmlconfig.addProperty("delay", String.valueOf(DELAY)); + + // Create a CompositeConfiguration as this is what the broker uses + CompositeConfiguration composite = new CompositeConfiguration(); + composite.addConfiguration(xmlconfig); + try + { + config.setConfiguration("", composite); + } + catch (ConfigurationException e) + { + e.printStackTrace(); + fail(e.getMessage()); + } + + assertEquals("Delay not correctly returned.", DELAY, config.getDelay()); + assertEquals("Default TimeUnit incorrect", TimeUnit.SECONDS, config.getTimeUnit()); + } + + /** + * Input Testing: + * + * TimeUnit parsing requires the String value be in UpperCase. + * Ensure we can handle when the user doesn't know this. + * + * Same test as 'testConfigLoadingValidConfig' but checking that + * the timeunit field is not case sensitive. + * i.e. the toUpper is being correctly applied. + */ + public void testConfigLoadingValidConfigStrangeTimeUnit() + { + SlowConsumerDetectionConfiguration config = new SlowConsumerDetectionConfiguration(); + + XMLConfiguration xmlconfig = new XMLConfiguration(); + + long DELAY=10; + + xmlconfig.addProperty("delay", DELAY); + xmlconfig.addProperty("timeunit", "MiCrOsEcOnDs"); + + // Create a CompositeConfiguration as this is what the broker uses + CompositeConfiguration composite = new CompositeConfiguration(); + composite.addConfiguration(xmlconfig); + + try + { + config.setConfiguration("", composite); + } + catch (ConfigurationException e) + { + e.printStackTrace(); + fail(e.getMessage()); + } + + assertEquals("Delay not correctly returned.", DELAY, config.getDelay()); + assertEquals("TimeUnit not correctly returned.", + TimeUnit.MICROSECONDS.toString(), String.valueOf(config.getTimeUnit())); + + } + + /** + * Failure Testing: + * + * Test that delay must be long not a string value. + * Provide a delay as a written value not a long. 'ten'. + * + * This should throw a configuration exception which is being trapped and + * verified to be the right exception, a NumberFormatException. + * + */ + public void testConfigLoadingInValidDelayString() + { + SlowConsumerDetectionConfiguration config = new SlowConsumerDetectionConfiguration(); + + XMLConfiguration xmlconfig = new XMLConfiguration(); + + xmlconfig.addProperty("delay", "ten"); + xmlconfig.addProperty("timeunit", TimeUnit.MICROSECONDS.toString()); + + // Create a CompositeConfiguration as this is what the broker uses + CompositeConfiguration composite = new CompositeConfiguration(); + composite.addConfiguration(xmlconfig); + + try + { + config.setConfiguration("", composite); + fail("Configuration should fail to validate"); + } + catch (ConfigurationException e) + { + Throwable cause = e.getCause(); + + assertEquals("Cause not correct", NumberFormatException.class, cause.getClass()); + } + } + + /** + * Failure Testing: + * + * Test that negative delays are invalid. + * + * Delay must be a positive value as negative delay means doesn't make sense. + * + * Configuration exception with a useful message should be thrown here. + * + */ + public void testConfigLoadingInValidDelayNegative() + { + SlowConsumerDetectionConfiguration config = new SlowConsumerDetectionConfiguration(); + + XMLConfiguration xmlconfig = new XMLConfiguration(); + + xmlconfig.addProperty("delay", "-10"); + xmlconfig.addProperty("timeunit", TimeUnit.MICROSECONDS.toString()); + + // Create a CompositeConfiguration as this is what the broker uses + CompositeConfiguration composite = new CompositeConfiguration(); + composite.addConfiguration(xmlconfig); + + try + { + config.setConfiguration("", composite); + fail("Configuration should fail to validate"); + } + catch (ConfigurationException e) + { + Throwable cause = e.getCause(); + + assertNotNull("Configuration Exception must not be null.", cause); + assertEquals("Cause not correct", + ConfigurationException.class, cause.getClass()); + assertEquals("Incorrect message.", + "SlowConsumerDetectionConfiguration: 'delay' must be a Positive Long value.", + cause.getMessage()); + } + } + + /** + * Failure Testing: + * + * Test that delay cannot be 0. + * + * A zero delay means run constantly. This is not how VirtualHostTasks + * are designed to be run so we dis-allow the use of 0 delay. + * + * Same test as 'testConfigLoadingInValidDelayNegative' but with a 0 value. + * + */ + public void testConfigLoadingInValidDelayZero() + { + SlowConsumerDetectionConfiguration config = new SlowConsumerDetectionConfiguration(); + + XMLConfiguration xmlconfig = new XMLConfiguration(); + + xmlconfig.addProperty("delay", "0"); + xmlconfig.addProperty("timeunit", TimeUnit.MICROSECONDS.toString()); + + // Create a CompositeConfiguration as this is what the broker uses + CompositeConfiguration composite = new CompositeConfiguration(); + composite.addConfiguration(xmlconfig); + + try + { + config.setConfiguration("", composite); + fail("Configuration should fail to validate"); + } + catch (ConfigurationException e) + { + Throwable cause = e.getCause(); + + assertNotNull("Configuration Exception must not be null.", cause); + assertEquals("Cause not correct", + ConfigurationException.class, cause.getClass()); + assertEquals("Incorrect message.", + "SlowConsumerDetectionConfiguration: 'delay' must be a Positive Long value.", + cause.getMessage()); + } + } + + /** + * Failure Testing: + * + * Test that missing delay fails. + * If we have no delay then we do not pick a default. So a Configuration + * Exception is thrown. + * + * */ + public void testConfigLoadingInValidMissingDelay() + { + SlowConsumerDetectionConfiguration config = new SlowConsumerDetectionConfiguration(); + + XMLConfiguration xmlconfig = new XMLConfiguration(); + + xmlconfig.addProperty("timeunit", TimeUnit.SECONDS.toString()); + + // Create a CompositeConfiguration as this is what the broker uses + CompositeConfiguration composite = new CompositeConfiguration(); + composite.addConfiguration(xmlconfig); + try + { + config.setConfiguration("", composite); + fail("Configuration should fail to validate"); + } + catch (ConfigurationException e) + { + assertEquals("Incorrect message.", "SlowConsumerDetectionConfiguration: unable to configure invalid delay:null", e.getMessage()); + } + } + + /** + * Failure Testing: + * + * Test that erroneous TimeUnit fails. + * + * Valid TimeUnit values vary based on the JVM version i.e. 1.6 added HOURS/DAYS etc. + * + * We don't test the values for TimeUnit are accepted other than MILLISECONDS in the + * positive testing at the start. + * + * Here we ensure that an erroneous for TimeUnit correctly throws an exception. + * + * We test with 'foo', which will never be a TimeUnit + * + */ + public void testConfigLoadingInValidTimeUnit() + { + SlowConsumerDetectionConfiguration config = new SlowConsumerDetectionConfiguration(); + + String TIMEUNIT = "foo"; + XMLConfiguration xmlconfig = new XMLConfiguration(); + + xmlconfig.addProperty("delay", "10"); + xmlconfig.addProperty("timeunit", TIMEUNIT); + + // Create a CompositeConfiguration as this is what the broker uses + CompositeConfiguration composite = new CompositeConfiguration(); + composite.addConfiguration(xmlconfig); + try + { + config.setConfiguration("", composite); + fail("Configuration should fail to validate"); + } + catch (ConfigurationException e) + { + assertEquals("Incorrect message.", "Unable to configure Slow Consumer Detection invalid TimeUnit:" + TIMEUNIT, e.getMessage()); + } + } + + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/virtualhost/plugins/SlowConsumerDetectionPolicyConfigurationTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/virtualhost/plugins/SlowConsumerDetectionPolicyConfigurationTest.java new file mode 100644 index 0000000000..efb898e365 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/virtualhost/plugins/SlowConsumerDetectionPolicyConfigurationTest.java @@ -0,0 +1,104 @@ +/* + * + * 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.qpid.server.virtualhost.plugins; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.XMLConfiguration; +import org.apache.qpid.server.configuration.plugins.SlowConsumerDetectionPolicyConfiguration; +import org.apache.qpid.server.util.InternalBrokerBaseCase; + +/** + * Test class to ensure that the policy configuration can be processed. + */ +public class SlowConsumerDetectionPolicyConfigurationTest extends InternalBrokerBaseCase +{ + + /** + * Input Testing: + * + * Test that a given String can be set and retrieved through the configuration + * + * No validation is being performed to ensure that the policy exists. Only + * that a value can be set for the policy. + * + */ + public void testConfigLoadingValidConfig() + { + SlowConsumerDetectionPolicyConfiguration config = new SlowConsumerDetectionPolicyConfiguration(); + + XMLConfiguration xmlconfig = new XMLConfiguration(); + + String policyName = "TestPolicy"; + xmlconfig.addProperty("name", policyName); + + // Create a CompositeConfiguration as this is what the broker uses + CompositeConfiguration composite = new CompositeConfiguration(); + composite.addConfiguration(xmlconfig); + + try + { + config.setConfiguration("", composite); + } + catch (ConfigurationException e) + { + e.printStackTrace(); + fail(e.getMessage()); + } + + assertEquals("Policy name not retrieved as expected.", + policyName, config.getPolicyName()); + } + + /** + * Failure Testing: + * + * Test that providing a configuration section without the 'name' field + * causes an exception to be thrown. + * + * An empty configuration is provided and the thrown exception message + * is checked to confirm the right reason. + * + */ + public void testConfigLoadingInValidConfig() + { + SlowConsumerDetectionPolicyConfiguration config = new SlowConsumerDetectionPolicyConfiguration(); + + XMLConfiguration xmlconfig = new XMLConfiguration(); + + + // Create a CompositeConfiguration as this is what the broker uses + CompositeConfiguration composite = new CompositeConfiguration(); + composite.addConfiguration(xmlconfig); + + try + { + config.setConfiguration("", composite); + fail("Config is invalid so won't validate."); + } + catch (ConfigurationException e) + { + e.printStackTrace(); + assertEquals("Exception message not as expected.", "No Slow consumer policy defined.", e.getMessage()); + } + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/virtualhost/plugins/SlowConsumerDetectionQueueConfigurationTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/virtualhost/plugins/SlowConsumerDetectionQueueConfigurationTest.java new file mode 100644 index 0000000000..be86037dd8 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/virtualhost/plugins/SlowConsumerDetectionQueueConfigurationTest.java @@ -0,0 +1,185 @@ +/* + * 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.qpid.server.virtualhost.plugins; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.XMLConfiguration; +import org.apache.qpid.server.configuration.plugins.SlowConsumerDetectionQueueConfiguration; +import org.apache.qpid.server.util.InternalBrokerBaseCase; + +/** + * Unit test the QueueConfiguration processing. + * + * This is slightly awkward as the {@link SlowConsumerDetectionQueueConfiguration} + * requries that a policy be available. + * <p> + * So all the Valid test much catch the ensuing {@link ConfigurationException} and + * validate that the error is due to a lack of a valid policy. + */ +public class SlowConsumerDetectionQueueConfigurationTest extends InternalBrokerBaseCase +{ + /** + * Test a fully loaded configuration file. + * + * It is not an error to have all control values specified. + * <p> + * Here we need to catch the {@link ConfigurationException} that ensues due to lack + * of a policy plugin. + */ + public void testConfigLoadingValidConfig() + { + SlowConsumerDetectionQueueConfiguration config = new SlowConsumerDetectionQueueConfiguration(); + + XMLConfiguration xmlconfig = new XMLConfiguration(); + + xmlconfig.addProperty("messageAge", "60000"); + xmlconfig.addProperty("depth", "1024"); + xmlconfig.addProperty("messageCount", "10"); + + // Create a CompositeConfiguration as this is what the broker uses + CompositeConfiguration composite = new CompositeConfiguration(); + composite.addConfiguration(xmlconfig); + + try + { + config.setConfiguration("", composite); + fail("No Policies are avaialbe to load in a unit test"); + } + catch (ConfigurationException e) + { + assertTrue("Exception message incorrect, was: " + e.getMessage(), + e.getMessage().startsWith("No Slow Consumer Policy specified. Known Policies:[")); + } + } + + /** + * When we do not specify any control value then a {@link ConfigurationException} + * must be thrown to remind us. + */ + public void testConfigLoadingMissingConfig() + { + SlowConsumerDetectionQueueConfiguration config = new SlowConsumerDetectionQueueConfiguration(); + + XMLConfiguration xmlconfig = new XMLConfiguration(); + + // Create a CompositeConfiguration as this is what the broker uses + CompositeConfiguration composite = new CompositeConfiguration(); + composite.addConfiguration(xmlconfig); + + try + { + config.setConfiguration("", composite); + fail("No Policies are avaialbe to load in a unit test"); + } + catch (ConfigurationException e) + { + + assertEquals("At least one configuration property('messageAge','depth'" + + " or 'messageCount') must be specified.", e.getMessage()); + } + } + + /** + * Setting messageAge on its own is enough to have a valid configuration + * + * Here we need to catch the {@link ConfigurationException} that ensues due to lack + * of a policy plugin. + */ + public void testConfigLoadingMessageAgeOk() + { + SlowConsumerDetectionQueueConfiguration config = new SlowConsumerDetectionQueueConfiguration(); + + XMLConfiguration xmlconfig = new XMLConfiguration(); + xmlconfig.addProperty("messageAge", "60000"); + + // Create a CompositeConfiguration as this is what the broker uses + CompositeConfiguration composite = new CompositeConfiguration(); + composite.addConfiguration(xmlconfig); + + try + { + config.setConfiguration("", composite); + fail("No Policies are avaialbe to load in a unit test"); + } + catch (ConfigurationException e) + { + assertTrue("Exception message incorrect, was: " + e.getMessage(), + e.getMessage().startsWith("No Slow Consumer Policy specified. Known Policies:[")); + } + } + + /** + * Setting depth on its own is enough to have a valid configuration. + * + * Here we need to catch the {@link ConfigurationException} that ensues due to lack + * of a policy plugin. + */ + public void testConfigLoadingDepthOk() + { + SlowConsumerDetectionQueueConfiguration config = new SlowConsumerDetectionQueueConfiguration(); + + XMLConfiguration xmlconfig = new XMLConfiguration(); + xmlconfig.addProperty("depth", "1024"); + + // Create a CompositeConfiguration as this is what the broker uses + CompositeConfiguration composite = new CompositeConfiguration(); + composite.addConfiguration(xmlconfig); + + try + { + config.setConfiguration("", composite); + fail("No Policies are avaialbe to load in a unit test"); + } + catch (ConfigurationException e) + { + assertTrue("Exception message incorrect, was: " + e.getMessage(), + e.getMessage().startsWith("No Slow Consumer Policy specified. Known Policies:[")); + } + } + + /** + * Setting messageCount on its own is enough to have a valid configuration. + * + * Here we need to catch the {@link ConfigurationException} that ensues due to lack + * of a policy plugin. + */ + public void testConfigLoadingMessageCountOk() + { + SlowConsumerDetectionQueueConfiguration config = new SlowConsumerDetectionQueueConfiguration(); + + XMLConfiguration xmlconfig = new XMLConfiguration(); + xmlconfig.addProperty("messageCount", "10"); + + // Create a CompositeConfiguration as this is what the broker uses + CompositeConfiguration composite = new CompositeConfiguration(); + composite.addConfiguration(xmlconfig); + + try + { + config.setConfiguration("", composite); + fail("No Policies are avaialbe to load in a unit test"); + } + catch (ConfigurationException e) + { + assertTrue("Exception message incorrect, was: " + e.getMessage(), + e.getMessage().startsWith("No Slow Consumer Policy specified. Known Policies:[")); + } + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/virtualhost/plugins/policies/TopicDeletePolicyConfigurationTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/virtualhost/plugins/policies/TopicDeletePolicyConfigurationTest.java new file mode 100644 index 0000000000..3d3cc810df --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/virtualhost/plugins/policies/TopicDeletePolicyConfigurationTest.java @@ -0,0 +1,88 @@ +/* + * + * 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.qpid.server.virtualhost.plugins.policies; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.XMLConfiguration; +import org.apache.qpid.server.util.InternalBrokerBaseCase; + +/** + * Test to ensure TopicDelete Policy configuration can be loaded. + */ +public class TopicDeletePolicyConfigurationTest extends InternalBrokerBaseCase +{ + /** + * Test without any configuration being provided that the + * deletePersistent option is disabled. + */ + public void testNoConfigNoDeletePersistent() + { + TopicDeletePolicyConfiguration config = new TopicDeletePolicyConfiguration(); + + assertFalse("TopicDelete Configuration with no config should not delete persistent queues.", + config.deletePersistent()); + } + + /** + * Test that with the correct configuration the deletePersistent option can + * be enabled. + * + * Test creates a new Configuration object and passes in the xml snippet + * that the ConfigurationPlugin would receive during normal execution. + * This is the XML that would be matched for this plugin: + * <topicdelete> + * <delete-persistent> + * <topicdelete> + * + * So it would be subset and passed in as just: + * <delete-persistent> + * + * + * The property should therefore be enabled. + * + */ + public void testConfigDeletePersistent() + { + TopicDeletePolicyConfiguration config = new TopicDeletePolicyConfiguration(); + + XMLConfiguration xmlconfig = new XMLConfiguration(); + + xmlconfig.addProperty("delete-persistent",""); + + // Create a CompositeConfiguration as this is what the broker uses + CompositeConfiguration composite = new CompositeConfiguration(); + composite.addConfiguration(xmlconfig); + + try + { + config.setConfiguration("",composite); + } + catch (ConfigurationException e) + { + fail(e.getMessage()); + } + + assertTrue("A configured TopicDelete should delete persistent queues.", + config.deletePersistent()); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/virtualhost/plugins/policies/TopicDeletePolicyTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/virtualhost/plugins/policies/TopicDeletePolicyTest.java new file mode 100644 index 0000000000..a2e83add05 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/virtualhost/plugins/policies/TopicDeletePolicyTest.java @@ -0,0 +1,293 @@ +/* + * + * 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.qpid.server.virtualhost.plugins.policies; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.XMLConfiguration; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.exchange.DirectExchange; +import org.apache.qpid.server.exchange.TopicExchange; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.protocol.InternalTestProtocolSession; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.MockAMQQueue; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.util.InternalBrokerBaseCase; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class TopicDeletePolicyTest extends InternalBrokerBaseCase +{ + + TopicDeletePolicyConfiguration _config; + + VirtualHost _defaultVhost; + InternalTestProtocolSession _connection; + + public void setUp() throws Exception + { + super.setUp(); + + _defaultVhost = ApplicationRegistry.getInstance().getVirtualHostRegistry().getDefaultVirtualHost(); + + _connection = new InternalTestProtocolSession(_defaultVhost); + + _config = new TopicDeletePolicyConfiguration(); + + XMLConfiguration config = new XMLConfiguration(); + + _config.setConfiguration("", config); + } + + private MockAMQQueue createOwnedQueue() + { + MockAMQQueue queue = new MockAMQQueue("testQueue"); + + _defaultVhost.getQueueRegistry().registerQueue(queue); + + try + { + AMQChannel channel = new AMQChannel(_connection, 0, null); + _connection.addChannel(channel); + + queue.setExclusiveOwningSession(channel); + } + catch (AMQException e) + { + fail("Unable to create Channel:" + e.getMessage()); + } + + return queue; + } + + private void setQueueToAutoDelete(final AMQQueue queue) + { + ((MockAMQQueue) queue).setAutoDelete(true); + + queue.setDeleteOnNoConsumers(true); + final AMQProtocolSession.Task deleteQueueTask = + new AMQProtocolSession.Task() + { + public void doTask(AMQProtocolSession session) throws AMQException + { + queue.delete(); + } + }; + + ((AMQChannel) queue.getExclusiveOwningSession()).getProtocolSession().addSessionCloseTask(deleteQueueTask); + } + + /** Check that a null queue passed in does not upset the policy. */ + public void testNullQueueParameter() throws ConfigurationException + { + TopicDeletePolicy policy = new TopicDeletePolicy(); + policy.configure(_config); + + try + { + policy.performPolicy(null); + } + catch (Exception e) + { + fail("Exception should not be thrown:" + e.getMessage()); + } + + } + + /** + * Set a owning Session to null which means this is not an exclusive queue + * so the queue should not be deleted + */ + public void testNonExclusiveQueue() + { + TopicDeletePolicy policy = new TopicDeletePolicy(); + policy.configure(_config); + + MockAMQQueue queue = createOwnedQueue(); + + queue.setExclusiveOwningSession(null); + + policy.performPolicy(queue); + + assertFalse("Queue should not be deleted", queue.isDeleted()); + assertFalse("Connection should not be closed", _connection.isClosed()); + } + + /** + * Test that exclusive JMS Queues are not deleted. + * Bind the queue to the direct exchange (so it is a JMS Queue). + * + * JMS Queues are not to be processed so this should not delete the queue. + */ + public void testQueuesAreNotProcessed() + { + TopicDeletePolicy policy = new TopicDeletePolicy(); + policy.configure(_config); + + MockAMQQueue queue = createOwnedQueue(); + + queue.addBinding(new Binding(null, "bindingKey", queue, new DirectExchange(), null)); + + policy.performPolicy(queue); + + assertFalse("Queue should not be deleted", queue.isDeleted()); + assertFalse("Connection should not be closed", _connection.isClosed()); + } + + /** + * Give a non auto-delete queue is bound to the topic exchange the + * TopicDeletePolicy will close the connection and delete the queue, + */ + public void testNonAutoDeleteTopicIsNotClosed() + { + TopicDeletePolicy policy = new TopicDeletePolicy(); + policy.configure(_config); + + MockAMQQueue queue = createOwnedQueue(); + + queue.addBinding(new Binding(null, "bindingKey", queue, new TopicExchange(), null)); + + queue.setAutoDelete(false); + + policy.performPolicy(queue); + + assertFalse("Queue should not be deleted", queue.isDeleted()); + assertTrue("Connection should be closed", _connection.isClosed()); + } + + /** + * Give a auto-delete queue bound to the topic exchange the TopicDeletePolicy will + * close the connection and delete the queue + */ + public void testTopicIsClosed() + { + TopicDeletePolicy policy = new TopicDeletePolicy(); + policy.configure(_config); + + final MockAMQQueue queue = createOwnedQueue(); + + queue.addBinding(new Binding(null, "bindingKey", queue, new TopicExchange(), null)); + + setQueueToAutoDelete(queue); + + policy.performPolicy(queue); + + assertTrue("Queue should be deleted", queue.isDeleted()); + assertTrue("Connection should be closed", _connection.isClosed()); + } + + /** + * Give a queue bound to the topic exchange the TopicDeletePolicy will + * close the connection and NOT delete the queue + */ + public void testNonAutoDeleteTopicIsClosedNotDeleted() + { + TopicDeletePolicy policy = new TopicDeletePolicy(); + policy.configure(_config); + + MockAMQQueue queue = createOwnedQueue(); + + queue.addBinding(new Binding(null, "bindingKey", queue, new TopicExchange(), null)); + + policy.performPolicy(queue); + + assertFalse("Queue should not be deleted", queue.isDeleted()); + assertTrue("Connection should be closed", _connection.isClosed()); + } + + /** + * Give a queue bound to the topic exchange the TopicDeletePolicy suitably + * configured with the delete-persistent tag will close the connection + * and delete the queue + */ + public void testPersistentTopicIsClosedAndDeleted() + { + //Set the config to delete persistent queues + _config.getConfig().addProperty("delete-persistent", ""); + + TopicDeletePolicy policy = new TopicDeletePolicy(); + policy.configure(_config); + + assertTrue("Config was not updated to delete Persistent topics", + _config.deletePersistent()); + + MockAMQQueue queue = createOwnedQueue(); + + queue.addBinding(new Binding(null, "bindingKey", queue, new TopicExchange(), null)); + + policy.performPolicy(queue); + + assertTrue("Queue should be deleted", queue.isDeleted()); + assertTrue("Connection should be closed", _connection.isClosed()); + } + + /** + * Give a queue bound to the topic exchange the TopicDeletePolicy not + * configured to close a persistent queue + */ + public void testPersistentTopicIsClosedAndDeletedNullConfig() + { + TopicDeletePolicy policy = new TopicDeletePolicy(); + // Explicity say we are not configuring the policy. + policy.configure(null); + + MockAMQQueue queue = createOwnedQueue(); + + queue.addBinding(new Binding(null, "bindingKey", queue, new TopicExchange(), null)); + + policy.performPolicy(queue); + + assertFalse("Queue should not be deleted", queue.isDeleted()); + assertTrue("Connection should be closed", _connection.isClosed()); + } + + public void testNonExclusiveQueueNullConfig() + { + _config = null; + testNonExclusiveQueue(); + } + + public void testQueuesAreNotProcessedNullConfig() + { + _config = null; + testQueuesAreNotProcessed(); + } + + public void testNonAutoDeleteTopicIsNotClosedNullConfig() + { + _config = null; + testNonAutoDeleteTopicIsNotClosed(); + } + + public void testTopicIsClosedNullConfig() + { + _config = null; + testTopicIsClosed(); + } + + public void testNonAutoDeleteTopicIsClosedNotDeletedNullConfig() throws AMQException + { + _config = null; + testNonAutoDeleteTopicIsClosedNotDeleted(); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/util/MockChannel.java b/qpid/java/broker/src/test/java/org/apache/qpid/util/MockChannel.java new file mode 100644 index 0000000000..9bd1e7c5e1 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/util/MockChannel.java @@ -0,0 +1,40 @@ +/* + * + * 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.qpid.util; + +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; + +public class MockChannel extends AMQChannel +{ + public MockChannel(AMQProtocolSession session, int channelId, MessageStore messageStore) + throws AMQException + { + super(session, channelId, messageStore); + } + + + +} |