From 5b060554268c763cb883a102b04be21741551161 Mon Sep 17 00:00:00 2001
From: Aidan Skinner
Date: Mon, 28 Jan 2008 16:48:00 +0000
Subject: Merged revisions 608477,609961,610475,610479,610806,611146 via
svnmerge from https://svn.apache.org/repos/asf/incubator/qpid/branches/M2
........
r608477 | rgodfrey | 2008-01-03 13:23:04 +0000 (Thu, 03 Jan 2008) | 1 line
QPID-499 : Added per-virtual host timed tasks to inspect queues (with no consumers) for expired messages
........
r609961 | ritchiem | 2008-01-08 12:59:01 +0000 (Tue, 08 Jan 2008) | 2 lines
QPID-499 : Patch to update the queue size statistics when the Active TTL process runs
Removed old single commented out code line from AMQSession.
........
r610475 | ritchiem | 2008-01-09 17:32:43 +0000 (Wed, 09 Jan 2008) | 1 line
Qpid-723 Added exec to qpid.start
........
r610479 | ritchiem | 2008-01-09 17:39:54 +0000 (Wed, 09 Jan 2008) | 1 line
Qpid-690 : Provide configurable delay between re-connecion attempts.
........
r610806 | ritchiem | 2008-01-10 14:41:37 +0000 (Thu, 10 Jan 2008) | 1 line
QPID-690 : Relaxed the timings on failover as Thread.sleep is accurate to 10ms so may finish the sleep 10ms early. Resulting in erratic failures as 9.9s < 10s.
........
r611146 | ritchiem | 2008-01-11 11:33:31 +0000 (Fri, 11 Jan 2008) | 1 line
Patch by Aidan Skinner to make third constructor public. This is done so that the BDBMessageStore tests can still run with the addition of the VirtualHost reaper thread.
........
git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/branches/M2.1@615943 13f79535-47bb-0310-9956-ffa450edef68
---
java/broker/etc/config.xml | 4 +
.../org/apache/qpid/server/queue/AMQQueue.java | 24 +-
.../queue/ConcurrentSelectorDeliveryManager.java | 24 +
.../apache/qpid/server/queue/DeliveryManager.java | 2 +
.../apache/qpid/server/queue/SubscriptionSet.java | 14 +-
.../qpid/server/virtualhost/VirtualHost.java | 563 +++++++++++----------
.../java/org/apache/qpid/client/AMQSession.java | 65 +++
.../qpid/client/transport/TransportConnection.java | 5 +-
.../ConcurrentLinkedMessageQueueAtomicSize.java | 8 +
.../qpid/server/failover/FailoverMethodTest.java | 66 ++-
.../apache/qpid/server/queue/TimeToLiveTest.java | 135 +++--
11 files changed, 575 insertions(+), 335 deletions(-)
(limited to 'java')
diff --git a/java/broker/etc/config.xml b/java/broker/etc/config.xml
index b3b6a2877f..6c2d84c5d8 100644
--- a/java/broker/etc/config.xml
+++ b/java/broker/etc/config.xml
@@ -100,6 +100,10 @@
org.apache.qpid.server.store.MemoryMessageStore
+
+ 20000
+
+
diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueue.java b/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueue.java
index 53c36d9718..e1c1de29bd 100644
--- a/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueue.java
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueue.java
@@ -26,6 +26,7 @@ import org.apache.qpid.configuration.Configured;
import org.apache.qpid.framing.AMQShortString;
import org.apache.qpid.framing.FieldTable;
import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.registry.ApplicationRegistry;
import org.apache.qpid.server.exchange.Exchange;
import org.apache.qpid.server.management.Managable;
import org.apache.qpid.server.management.ManagedObject;
@@ -37,6 +38,8 @@ import org.apache.qpid.server.virtualhost.VirtualHost;
import javax.management.JMException;
import java.text.MessageFormat;
import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -154,10 +157,7 @@ public class AMQQueue implements Managable, Comparable
/** total messages received by the queue since startup. */
public AtomicLong _totalMessagesReceived = new AtomicLong();
- public int compareTo(Object o)
- {
- return _name.compareTo(((AMQQueue) o).getName());
- }
+
public AMQQueue(AMQShortString name, boolean durable, AMQShortString owner, boolean autoDelete, VirtualHost virtualHost)
throws AMQException
@@ -950,4 +950,20 @@ public class AMQQueue implements Managable, Comparable
return new QueueEntry(this, amqMessage);
}
+ public int compareTo(Object o)
+ {
+ return _name.compareTo(((AMQQueue) o).getName());
+ }
+
+
+ public void removeExpiredIfNoSubscribers() throws AMQException
+ {
+ synchronized(_subscribers.getChangeLock())
+ {
+ if(_subscribers.isEmpty())
+ {
+ _deliveryMgr.removeExpired();
+ }
+ }
+ }
}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/ConcurrentSelectorDeliveryManager.java b/java/broker/src/main/java/org/apache/qpid/server/queue/ConcurrentSelectorDeliveryManager.java
index 3cf2cb9b12..a7cef1b983 100644
--- a/java/broker/src/main/java/org/apache/qpid/server/queue/ConcurrentSelectorDeliveryManager.java
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/ConcurrentSelectorDeliveryManager.java
@@ -212,6 +212,30 @@ public class ConcurrentSelectorDeliveryManager implements DeliveryManager
}
}
+ /**
+ * NOTE : This method should only be called when there are no active subscribers
+ */
+ public void removeExpired() throws AMQException
+ {
+ _lock.lock();
+
+
+ for(Iterator iter = _messages.iterator(); iter.hasNext();)
+ {
+ QueueEntry entry = iter.next();
+ if(entry.expired())
+ {
+ // fixme: Currently we have to update the total byte size here for the data in the queue
+ _totalMessageSize.addAndGet(-entry.getSize());
+ _queue.dequeue(_reapingStoreContext,entry);
+ iter.remove();
+ }
+ }
+
+
+ _lock.unlock();
+ }
+
/** @return the state of the async processor. */
public boolean isProcessingAsync()
{
diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/DeliveryManager.java b/java/broker/src/main/java/org/apache/qpid/server/queue/DeliveryManager.java
index f7f35a9319..1568f58e2e 100644
--- a/java/broker/src/main/java/org/apache/qpid/server/queue/DeliveryManager.java
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/DeliveryManager.java
@@ -97,4 +97,6 @@ interface DeliveryManager
long getOldestMessageArrival();
void subscriberHasPendingResend(boolean hasContent, Subscription subscription, QueueEntry msg);
+
+ void removeExpired() throws AMQException;
}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionSet.java b/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionSet.java
index b73b8d7e07..b2f8cae8ff 100644
--- a/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionSet.java
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionSet.java
@@ -37,7 +37,8 @@ class SubscriptionSet implements WeightedSubscriptionManager
/** Used to control the round robin delivery of content */
private int _currentSubscriber;
- private final Object _subscriptionsChange = new Object();
+
+ private final Object _changeLock = new Object();
/** Accessor for unit tests. */
@@ -48,7 +49,7 @@ class SubscriptionSet implements WeightedSubscriptionManager
public void addSubscriber(Subscription subscription)
{
- synchronized (_subscriptionsChange)
+ synchronized (_changeLock)
{
_subscriptions.add(subscription);
}
@@ -66,7 +67,7 @@ class SubscriptionSet implements WeightedSubscriptionManager
// TODO: possibly need O(1) operation here.
Subscription sub = null;
- synchronized (_subscriptionsChange)
+ synchronized (_changeLock)
{
int subIndex = _subscriptions.indexOf(subscription);
@@ -226,4 +227,11 @@ class SubscriptionSet implements WeightedSubscriptionManager
{
return _subscriptions.size();
}
+
+
+ public Object getChangeLock()
+ {
+ return _changeLock;
+ }
+
}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHost.java b/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHost.java
index 8d6a26fdbc..9addf83e01 100644
--- a/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHost.java
+++ b/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHost.java
@@ -1,260 +1,303 @@
-/*
- *
- * 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;
-
-import javax.management.NotCompliantMBeanException;
-
-import org.apache.commons.configuration.Configuration;
-import org.apache.log4j.Logger;
-import org.apache.qpid.server.AMQBrokerManagerMBean;
-import org.apache.qpid.server.security.access.AccessManager;
-import org.apache.qpid.server.security.access.AccessManagerImpl;
-import org.apache.qpid.server.security.access.Accessable;
-import org.apache.qpid.server.security.auth.manager.PrincipalDatabaseAuthenticationManager;
-import org.apache.qpid.server.security.auth.manager.AuthenticationManager;
-import org.apache.qpid.server.configuration.Configurator;
-import org.apache.qpid.server.exchange.DefaultExchangeFactory;
-import org.apache.qpid.server.exchange.DefaultExchangeRegistry;
-import org.apache.qpid.server.exchange.ExchangeFactory;
-import org.apache.qpid.server.exchange.ExchangeRegistry;
-import org.apache.qpid.server.management.AMQManagedObject;
-import org.apache.qpid.server.management.ManagedObject;
-import org.apache.qpid.server.queue.DefaultQueueRegistry;
-import org.apache.qpid.server.queue.QueueRegistry;
-import org.apache.qpid.server.registry.ApplicationRegistry;
-import org.apache.qpid.server.store.MessageStore;
-
-public class VirtualHost implements Accessable
-{
- private static final Logger _logger = Logger.getLogger(VirtualHost.class);
-
-
- private final String _name;
-
- private QueueRegistry _queueRegistry;
-
- private ExchangeRegistry _exchangeRegistry;
-
- private ExchangeFactory _exchangeFactory;
-
- private MessageStore _messageStore;
-
- protected VirtualHostMBean _virtualHostMBean;
-
- private AMQBrokerManagerMBean _brokerMBean;
-
- private AuthenticationManager _authenticationManager;
-
- private AccessManager _accessManager;
-
-
- public void setAccessableName(String name)
- {
- _logger.warn("Setting Accessable Name for VirualHost is not allowed. ("
- + name + ") ignored remains :" + getAccessableName());
- }
-
- public String getAccessableName()
- {
- return _name;
- }
-
-
- /**
- * Abstract MBean class. This has some of the methods implemented from management intrerface for exchanges. Any
- * implementaion of an Exchange MBean should extend this class.
- */
- public class VirtualHostMBean extends AMQManagedObject implements ManagedVirtualHost
- {
- public VirtualHostMBean() throws NotCompliantMBeanException
- {
- super(ManagedVirtualHost.class, "VirtualHost");
- }
-
- public String getObjectInstanceName()
- {
- return _name.toString();
- }
-
- public String getName()
- {
- return _name.toString();
- }
-
- public VirtualHost getVirtualHost()
- {
- return VirtualHost.this;
- }
-
-
- } // End of MBean class
-
- /**
- * Used for testing only
- * @param name
- * @param store
- * @throws Exception
- */
- public VirtualHost(String name, MessageStore store) throws Exception
- {
- this(name, null, store);
- }
-
- /**
- * Normal Constructor
- * @param name
- * @param hostConfig
- * @throws Exception
- */
- public VirtualHost(String name, Configuration hostConfig) throws Exception
- {
- this(name, hostConfig, null);
- }
-
- private VirtualHost(String name, Configuration hostConfig, MessageStore store) throws Exception
- {
- _name = name;
-
- _virtualHostMBean = new VirtualHostMBean();
- // This isn't needed to be registered
- //_virtualHostMBean.register();
-
- _queueRegistry = new DefaultQueueRegistry(this);
- _exchangeFactory = new DefaultExchangeFactory(this);
- _exchangeFactory.initialise(hostConfig);
- _exchangeRegistry = new DefaultExchangeRegistry(this);
-
- if (store != null)
- {
- _messageStore = store;
- }
- else
- {
- if (hostConfig == null)
- {
- throw new IllegalAccessException("HostConfig and MessageStore cannot be null");
- }
- initialiseMessageStore(hostConfig);
- }
-
- _exchangeRegistry.initialise();
-
- _authenticationManager = new PrincipalDatabaseAuthenticationManager(name, hostConfig);
-
- _accessManager = new AccessManagerImpl(name, hostConfig);
-
- _brokerMBean = new AMQBrokerManagerMBean(_virtualHostMBean);
- _brokerMBean.register();
- }
-
- private void initialiseMessageStore(Configuration config) throws Exception
- {
- String messageStoreClass = config.getString("store.class");
-
- Class clazz = Class.forName(messageStoreClass);
- Object o = clazz.newInstance();
-
- if (!(o instanceof MessageStore))
- {
- throw new ClassCastException("Message store class must implement " + MessageStore.class + ". Class " + clazz +
- " does not.");
- }
- _messageStore = (MessageStore) o;
- _messageStore.configure(this, "store", config);
- }
-
-
- public T getConfiguredObject(Class instanceType, Configuration config)
- {
- T instance;
- try
- {
- instance = instanceType.newInstance();
- }
- catch (Exception e)
- {
- _logger.error("Unable to instantiate configuration class " + instanceType + " - ensure it has a public default constructor");
- throw new IllegalArgumentException("Unable to instantiate configuration class " + instanceType + " - ensure it has a public default constructor", e);
- }
- Configurator.configure(instance);
-
- return instance;
- }
-
-
- public String getName()
- {
- return _name;
- }
-
- public QueueRegistry getQueueRegistry()
- {
- return _queueRegistry;
- }
-
- public ExchangeRegistry getExchangeRegistry()
- {
- return _exchangeRegistry;
- }
-
- public ExchangeFactory getExchangeFactory()
- {
- return _exchangeFactory;
- }
-
- public ApplicationRegistry getApplicationRegistry()
- {
- throw new UnsupportedOperationException();
- }
-
- public MessageStore getMessageStore()
- {
- return _messageStore;
- }
-
- public AuthenticationManager getAuthenticationManager()
- {
- return _authenticationManager;
- }
-
- public AccessManager getAccessManager()
- {
- return _accessManager;
- }
-
- public void close() throws Exception
- {
- if (_messageStore != null)
- {
- _messageStore.close();
- }
- }
-
- public ManagedObject getBrokerMBean()
- {
- return _brokerMBean;
- }
-
- public ManagedObject getManagedObject()
- {
- return _virtualHostMBean;
- }
-}
+/*
+ *
+ * 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;
+
+import javax.management.NotCompliantMBeanException;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.log4j.Logger;
+import org.apache.qpid.server.AMQBrokerManagerMBean;
+import org.apache.qpid.server.security.access.AccessManager;
+import org.apache.qpid.server.security.access.AccessManagerImpl;
+import org.apache.qpid.server.security.access.Accessable;
+import org.apache.qpid.server.security.auth.manager.PrincipalDatabaseAuthenticationManager;
+import org.apache.qpid.server.security.auth.manager.AuthenticationManager;
+import org.apache.qpid.server.configuration.Configurator;
+import org.apache.qpid.server.exchange.DefaultExchangeFactory;
+import org.apache.qpid.server.exchange.DefaultExchangeRegistry;
+import org.apache.qpid.server.exchange.ExchangeFactory;
+import org.apache.qpid.server.exchange.ExchangeRegistry;
+import org.apache.qpid.server.management.AMQManagedObject;
+import org.apache.qpid.server.management.ManagedObject;
+import org.apache.qpid.server.queue.DefaultQueueRegistry;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.store.MessageStore;
+import org.apache.qpid.AMQException;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+public class VirtualHost implements Accessable
+{
+ private static final Logger _logger = Logger.getLogger(VirtualHost.class);
+
+
+ private final String _name;
+
+ private QueueRegistry _queueRegistry;
+
+ private ExchangeRegistry _exchangeRegistry;
+
+ private ExchangeFactory _exchangeFactory;
+
+ private MessageStore _messageStore;
+
+ protected VirtualHostMBean _virtualHostMBean;
+
+ private AMQBrokerManagerMBean _brokerMBean;
+
+ private AuthenticationManager _authenticationManager;
+
+ private AccessManager _accessManager;
+
+ private final Timer _houseKeepingTimer = new Timer("Queue-housekeeping", true);
+
+ private static final long DEFAULT_HOUSEKEEPING_PERIOD = 30000L;
+
+ public void setAccessableName(String name)
+ {
+ _logger.warn("Setting Accessable Name for VirualHost is not allowed. ("
+ + name + ") ignored remains :" + getAccessableName());
+ }
+
+ public String getAccessableName()
+ {
+ return _name;
+ }
+
+
+ /**
+ * Abstract MBean class. This has some of the methods implemented from management intrerface for exchanges. Any
+ * implementaion of an Exchange MBean should extend this class.
+ */
+ public class VirtualHostMBean extends AMQManagedObject implements ManagedVirtualHost
+ {
+ public VirtualHostMBean() throws NotCompliantMBeanException
+ {
+ super(ManagedVirtualHost.class, "VirtualHost");
+ }
+
+ public String getObjectInstanceName()
+ {
+ return _name.toString();
+ }
+
+ public String getName()
+ {
+ return _name.toString();
+ }
+
+ public VirtualHost getVirtualHost()
+ {
+ return VirtualHost.this;
+ }
+
+
+ } // End of MBean class
+
+ /**
+ * Used for testing only
+ * @param name
+ * @param store
+ * @throws Exception
+ */
+ public VirtualHost(String name, MessageStore store) throws Exception
+ {
+ this(name, null, store);
+ }
+
+ /**
+ * Normal Constructor
+ * @param name
+ * @param hostConfig
+ * @throws Exception
+ */
+ public VirtualHost(String name, Configuration hostConfig) throws Exception
+ {
+ this(name, hostConfig, null);
+ }
+
+ public VirtualHost(String name, Configuration hostConfig, MessageStore store) throws Exception
+ {
+ _name = name;
+
+ _virtualHostMBean = new VirtualHostMBean();
+ // This isn't needed to be registered
+ //_virtualHostMBean.register();
+
+ _queueRegistry = new DefaultQueueRegistry(this);
+ _exchangeFactory = new DefaultExchangeFactory(this);
+ _exchangeFactory.initialise(hostConfig);
+ _exchangeRegistry = new DefaultExchangeRegistry(this);
+
+ if (store != null)
+ {
+ _messageStore = store;
+ }
+ else
+ {
+ if (hostConfig == null)
+ {
+ throw new IllegalAccessException("HostConfig and MessageStore cannot be null");
+ }
+ initialiseMessageStore(hostConfig);
+ }
+
+ _exchangeRegistry.initialise();
+
+ _authenticationManager = new PrincipalDatabaseAuthenticationManager(name, hostConfig);
+
+ _accessManager = new AccessManagerImpl(name, hostConfig);
+
+ _brokerMBean = new AMQBrokerManagerMBean(_virtualHostMBean);
+ _brokerMBean.register();
+ initialiseHouseKeeping(hostConfig);
+ }
+
+ private void initialiseHouseKeeping(final Configuration hostConfig)
+ {
+
+ long period = hostConfig.getLong("housekeeping.expiredMessageCheckPeriod", DEFAULT_HOUSEKEEPING_PERIOD);
+
+ /* add a timer task to iterate over queues, cleaning expired messages from queues with no consumers */
+ if(period != 0L)
+ {
+ class RemoveExpiredMessagesTask extends TimerTask
+ {
+ public void run()
+ {
+ for(AMQQueue q : _queueRegistry.getQueues())
+ {
+
+ try
+ {
+ q.removeExpiredIfNoSubscribers();
+ }
+ catch (AMQException e)
+ {
+ _logger.error("Exception in housekeeping for queue: " + q.getName().toString(),e);
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+
+ _houseKeepingTimer.scheduleAtFixedRate(new RemoveExpiredMessagesTask(),
+ period/2,
+ period);
+ }
+ }
+
+ private void initialiseMessageStore(Configuration config) throws Exception
+ {
+ String messageStoreClass = config.getString("store.class");
+
+ Class clazz = Class.forName(messageStoreClass);
+ Object o = clazz.newInstance();
+
+ if (!(o instanceof MessageStore))
+ {
+ throw new ClassCastException("Message store class must implement " + MessageStore.class + ". Class " + clazz +
+ " does not.");
+ }
+ _messageStore = (MessageStore) o;
+ _messageStore.configure(this, "store", config);
+ }
+
+
+ public T getConfiguredObject(Class instanceType, Configuration config)
+ {
+ T instance;
+ try
+ {
+ instance = instanceType.newInstance();
+ }
+ catch (Exception e)
+ {
+ _logger.error("Unable to instantiate configuration class " + instanceType + " - ensure it has a public default constructor");
+ throw new IllegalArgumentException("Unable to instantiate configuration class " + instanceType + " - ensure it has a public default constructor", e);
+ }
+ Configurator.configure(instance);
+
+ return instance;
+ }
+
+
+ public String getName()
+ {
+ return _name;
+ }
+
+ public QueueRegistry getQueueRegistry()
+ {
+ return _queueRegistry;
+ }
+
+ public ExchangeRegistry getExchangeRegistry()
+ {
+ return _exchangeRegistry;
+ }
+
+ public ExchangeFactory getExchangeFactory()
+ {
+ return _exchangeFactory;
+ }
+
+ public ApplicationRegistry getApplicationRegistry()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ public MessageStore getMessageStore()
+ {
+ return _messageStore;
+ }
+
+ public AuthenticationManager getAuthenticationManager()
+ {
+ return _authenticationManager;
+ }
+
+ public AccessManager getAccessManager()
+ {
+ return _accessManager;
+ }
+
+ public void close() throws Exception
+ {
+ if (_messageStore != null)
+ {
+ _messageStore.close();
+ }
+ }
+
+ public ManagedObject getBrokerMBean()
+ {
+ return _brokerMBean;
+ }
+
+ public ManagedObject getManagedObject()
+ {
+ return _virtualHostMBean;
+ }
+}
diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQSession.java b/java/client/src/main/java/org/apache/qpid/client/AMQSession.java
index 42f07f97f9..deaa435d8c 100644
--- a/java/client/src/main/java/org/apache/qpid/client/AMQSession.java
+++ b/java/client/src/main/java/org/apache/qpid/client/AMQSession.java
@@ -39,6 +39,7 @@ import org.apache.qpid.client.message.MessageFactoryRegistry;
import org.apache.qpid.client.message.UnprocessedMessage;
import org.apache.qpid.client.protocol.AMQProtocolHandler;
import org.apache.qpid.client.util.FlowControllingBlockingQueue;
+import org.apache.qpid.client.state.listener.SpecificMethodFrameListener;
import org.apache.qpid.common.AMQPFilterTypes;
import org.apache.qpid.framing.*;
import org.apache.qpid.framing.amqp_0_9.MethodRegistry_0_9;
@@ -2148,6 +2149,70 @@ public class AMQSession extends Closeable implements Session, QueueSession, Topi
declareExchange(amqd.getExchangeName(), amqd.getExchangeClass(), protocolHandler, nowait);
}
+
+ /**
+ * Returns the number of messages currently queued for the given destination.
+ *
+ * Note that this operation automatically retries in the event of fail-over.
+ *
+ * @param amqd The destination to be checked
+ *
+ * @return the number of queued messages.
+ *
+ * @throws AMQException If the queue cannot be declared for any reason.
+ */
+ public long getQueueDepth(final AMQDestination amqd)
+ throws AMQException
+ {
+
+ class QueueDeclareOkHandler extends SpecificMethodFrameListener
+ {
+
+ private long _messageCount;
+ private long _consumerCount;
+
+ public QueueDeclareOkHandler()
+ {
+ super(getChannelId(), QueueDeclareOkBody.class);
+ }
+
+ public boolean processMethod(int channelId, AMQMethodBody frame) //throws AMQException
+ {
+ boolean matches = super.processMethod(channelId, frame);
+ QueueDeclareOkBody declareOk = (QueueDeclareOkBody) frame;
+ _messageCount = declareOk.getMessageCount();
+ _consumerCount = declareOk.getConsumerCount();
+ return matches;
+ }
+
+ }
+
+ return new FailoverNoopSupport(
+ new FailoverProtectedOperation()
+ {
+ public Long execute() throws AMQException, FailoverException
+ {
+
+ AMQFrame queueDeclare =
+ getMethodRegistry().createQueueDeclareBody(getTicket(),
+ amqd.getAMQQueueName(),
+ true,
+ amqd.isDurable(),
+ amqd.isExclusive(),
+ amqd.isAutoDelete(),
+ false,
+ null).generateFrame(_channelId);
+ QueueDeclareOkHandler okHandler = new QueueDeclareOkHandler();
+ getProtocolHandler().writeCommandFrameAndWaitForReply(queueDeclare, okHandler);
+
+ return okHandler._messageCount;
+ }
+ }, _connection).execute();
+
+ }
+
+
+
/**
* Declares the named exchange and type of exchange.
*
diff --git a/java/client/src/main/java/org/apache/qpid/client/transport/TransportConnection.java b/java/client/src/main/java/org/apache/qpid/client/transport/TransportConnection.java
index e8a220f5e9..c1116ca01e 100644
--- a/java/client/src/main/java/org/apache/qpid/client/transport/TransportConnection.java
+++ b/java/client/src/main/java/org/apache/qpid/client/transport/TransportConnection.java
@@ -283,7 +283,10 @@ public class TransportConnection
public static void killAllVMBrokers()
{
_logger.info("Killing all VM Brokers");
- _acceptor.unbindAll();
+ if (_acceptor != null)
+ {
+ _acceptor.unbindAll();
+ }
synchronized (_inVmPipeAddress)
{
_inVmPipeAddress.clear();
diff --git a/java/common/src/main/java/org/apache/qpid/util/ConcurrentLinkedMessageQueueAtomicSize.java b/java/common/src/main/java/org/apache/qpid/util/ConcurrentLinkedMessageQueueAtomicSize.java
index 461cf9591d..633cf4fe3a 100644
--- a/java/common/src/main/java/org/apache/qpid/util/ConcurrentLinkedMessageQueueAtomicSize.java
+++ b/java/common/src/main/java/org/apache/qpid/util/ConcurrentLinkedMessageQueueAtomicSize.java
@@ -215,6 +215,14 @@ public class ConcurrentLinkedMessageQueueAtomicSize extends ConcurrentLinkedQ
public void remove()
{
last.remove();
+ if(last == _mainIterator)
+ {
+ _size.decrementAndGet();
+ }
+ else
+ {
+ _messageHeadSize.decrementAndGet();
+ }
}
};
}
diff --git a/java/systests/src/main/java/org/apache/qpid/server/failover/FailoverMethodTest.java b/java/systests/src/main/java/org/apache/qpid/server/failover/FailoverMethodTest.java
index d3f79f84b6..2c293527d9 100644
--- a/java/systests/src/main/java/org/apache/qpid/server/failover/FailoverMethodTest.java
+++ b/java/systests/src/main/java/org/apache/qpid/server/failover/FailoverMethodTest.java
@@ -1,19 +1,4 @@
-package org.apache.qpid.server.failover;
-
-import junit.framework.TestCase;
-import org.apache.qpid.AMQDisconnectedException;
-import org.apache.qpid.AMQException;
-import org.apache.qpid.client.AMQConnection;
-import org.apache.qpid.client.AMQConnectionURL;
-import org.apache.qpid.client.transport.TransportConnection;
-import org.apache.qpid.client.vmbroker.AMQVMBrokerCreationException;
-import org.apache.qpid.url.URLSyntaxException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.jms.ExceptionListener;
-import javax.jms.JMSException;
-import java.util.concurrent.CountDownLatch;/*
+/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
@@ -33,26 +18,51 @@ import java.util.concurrent.CountDownLatch;/*
* under the License.
*
*/
+package org.apache.qpid.server.failover;
+
+import junit.framework.TestCase;
+import org.apache.qpid.AMQDisconnectedException;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQConnectionURL;
+import org.apache.qpid.client.transport.TransportConnection;
+import org.apache.qpid.client.vmbroker.AMQVMBrokerCreationException;
+import org.apache.qpid.url.URLSyntaxException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.ExceptionListener;
+import javax.jms.JMSException;
+import java.util.concurrent.CountDownLatch;
public class FailoverMethodTest extends TestCase implements ExceptionListener
{
- private static final Logger _logger = LoggerFactory.getLogger(FailoverMethodTest.class);
private CountDownLatch _failoverComplete = new CountDownLatch(1);
public void setUp() throws AMQVMBrokerCreationException
{
+ TransportConnection.createVMBroker(1);
}
public void tearDown() throws AMQVMBrokerCreationException
{
+ TransportConnection.killAllVMBrokers();
}
- public void testFailoverRoundRobinDelay() throws URLSyntaxException, AMQVMBrokerCreationException, InterruptedException, JMSException
+ /**
+ * Test that the round robin method has the correct delays.
+ * The first connection to vm://:1 will work but the localhost connection should fail but the duration it takes
+ * to report the failure is what is being tested.
+ *
+ * @throws URLSyntaxException
+ * @throws InterruptedException
+ * @throws JMSException
+ */
+ public void testFailoverRoundRobinDelay() throws URLSyntaxException, InterruptedException, JMSException
{
String connectionString = "amqp://guest:guest@/test?brokerlist='vm://:1;tcp://localhost:5670?connectdelay='2000',retries='3''";
AMQConnectionURL url = new AMQConnectionURL(connectionString);
- TransportConnection.createVMBroker(1);
try
{
@@ -64,9 +74,15 @@ public class FailoverMethodTest extends TestCase implements ExceptionListener
TransportConnection.killAllVMBrokers();
_failoverComplete.await();
+
long end = System.currentTimeMillis();
- assertTrue("Failover took at over 10seconds", (end - start) > 6000);
+ //Failover should take less that 10 seconds.
+ // This is calculated by vm://:1 two retries left after initial connection (4s)
+ // localhost get three retries so (6s) so 10s in total for connection dropping
+ assertTrue("Failover took less than 9.5 seconds:"+(end - start), (end - start) > 9500);
+ // The sleep method is not 100% accurate under windows so with 5 sleeps and a 10ms accuracy then there is
+ // the potential for the tests to finish in 500ms sooner than the predicted 10s.
}
catch (AMQException e)
@@ -80,7 +96,6 @@ public class FailoverMethodTest extends TestCase implements ExceptionListener
String connectionString = "amqp://guest:guest@/test?brokerlist='vm://:1?connectdelay='2000',retries='3''";
AMQConnectionURL url = new AMQConnectionURL(connectionString);
- TransportConnection.createVMBroker(1);
try
{
@@ -92,9 +107,16 @@ public class FailoverMethodTest extends TestCase implements ExceptionListener
TransportConnection.killAllVMBrokers();
_failoverComplete.await();
+
long end = System.currentTimeMillis();
- assertTrue("Failover took at over 10seconds", (end - start) > 6000);
+ //Failover should take less that 10 seconds.
+ // This is calculated by vm://:1 two retries left after initial connection
+ // so 4s in total for connection dropping
+
+ assertTrue("Failover took less than 3.7 seconds", (end - start) > 3700);
+ // The sleep method is not 100% accurate under windows so with 3 sleeps and a 10ms accuracy then there is
+ // the potential for the tests to finish in 300ms sooner than the predicted 4s.
}
catch (AMQException e)
diff --git a/java/systests/src/main/java/org/apache/qpid/server/queue/TimeToLiveTest.java b/java/systests/src/main/java/org/apache/qpid/server/queue/TimeToLiveTest.java
index 06956ba52f..3afecb47d3 100644
--- a/java/systests/src/main/java/org/apache/qpid/server/queue/TimeToLiveTest.java
+++ b/java/systests/src/main/java/org/apache/qpid/server/queue/TimeToLiveTest.java
@@ -25,7 +25,11 @@ import junit.framework.TestCase;
import junit.framework.Assert;
import org.apache.qpid.client.transport.TransportConnection;
import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQDestination;
import org.apache.qpid.jndi.PropertiesFileInitialContextFactory;
+import org.apache.qpid.url.URLSyntaxException;
+import org.apache.qpid.AMQException;
import org.apache.log4j.Logger;
import javax.jms.JMSException;
@@ -38,6 +42,7 @@ import javax.jms.Connection;
import javax.jms.Message;
import javax.naming.spi.InitialContextFactory;
import javax.naming.Context;
+import javax.naming.NamingException;
import java.util.Hashtable;
@@ -53,21 +58,37 @@ public class TimeToLiveTest extends TestCase
private final long TIME_TO_LIVE = 1000L;
- Context _context;
-
- private Connection _clientConnection, _producerConnection;
-
- private MessageConsumer _consumer;
- MessageProducer _producer;
- Session _clientSession, _producerSession;
private static final int MSG_COUNT = 50;
+ private static final long SERVER_TTL_TIMEOUT = 60000L;
protected void setUp() throws Exception
{
- if (BROKER.startsWith("vm://"))
+ super.setUp();
+
+ if (usingInVMBroker())
{
TransportConnection.createVMBroker(1);
}
+
+
+ }
+
+ private boolean usingInVMBroker()
+ {
+ return BROKER.startsWith("vm://");
+ }
+
+ protected void tearDown() throws Exception
+ {
+ if (usingInVMBroker())
+ {
+ TransportConnection.killAllVMBrokers();
+ }
+ super.tearDown();
+ }
+
+ public void testPassiveTTL() throws JMSException, NamingException
+ {
InitialContextFactory factory = new PropertiesFileInitialContextFactory();
Hashtable env = new Hashtable();
@@ -75,56 +96,40 @@ public class TimeToLiveTest extends TestCase
env.put("connectionfactory.connection", "amqp://guest:guest@TTL_TEST_ID" + VHOST + "?brokerlist='" + BROKER + "'");
env.put("queue.queue", QUEUE);
- _context = factory.getInitialContext(env);
+ Context context = factory.getInitialContext(env);
- Queue queue = (Queue) _context.lookup("queue");
+ Queue queue = (Queue) context.lookup("queue");
//Create Client 1
- _clientConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection();
+ Connection clientConnection = ((ConnectionFactory) context.lookup("connection")).createConnection();
- _clientSession = _clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ Session clientSession = clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
- _consumer = _clientSession.createConsumer(queue);
+ MessageConsumer consumer = clientSession.createConsumer(queue);
//Create Producer
- _producerConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection();
+ Connection producerConnection = ((ConnectionFactory) context.lookup("connection")).createConnection();
- _producerConnection.start();
+ producerConnection.start();
- _producerSession = _producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ Session producerSession = producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
- _producer = _producerSession.createProducer(queue);
- }
+ MessageProducer producer = producerSession.createProducer(queue);
- protected void tearDown() throws Exception
- {
- _clientConnection.close();
-
- _producerConnection.close();
- super.tearDown();
-
- if (BROKER.startsWith("vm://"))
- {
- TransportConnection.killAllVMBrokers();
- }
- }
-
- public void test() throws JMSException
- {
//Set TTL
int msg = 0;
- _producer.send(nextMessage(String.valueOf(msg), true));
+ producer.send(nextMessage(String.valueOf(msg), true, producerSession, producer));
- _producer.setTimeToLive(TIME_TO_LIVE);
+ producer.setTimeToLive(TIME_TO_LIVE);
for (; msg < MSG_COUNT - 2; msg++)
{
- _producer.send(nextMessage(String.valueOf(msg), false));
+ producer.send(nextMessage(String.valueOf(msg), false, producerSession, producer));
}
//Reset TTL
- _producer.setTimeToLive(0L);
- _producer.send(nextMessage(String.valueOf(msg), false));
+ producer.setTimeToLive(0L);
+ producer.send(nextMessage(String.valueOf(msg), false, producerSession, producer));
try
{
@@ -136,31 +141,71 @@ public class TimeToLiveTest extends TestCase
}
- _clientConnection.start();
+ clientConnection.start();
//Receive Message 0
- Message received = _consumer.receive(100);
+ Message received = consumer.receive(100);
Assert.assertNotNull("First message not received", received);
Assert.assertTrue("First message doesn't have first set.", received.getBooleanProperty("first"));
Assert.assertEquals("First message has incorrect TTL.", 0L, received.getLongProperty("TTL"));
- received = _consumer.receive(100);
+ received = consumer.receive(100);
Assert.assertNotNull("Final message not received", received);
Assert.assertFalse("Final message has first set.", received.getBooleanProperty("first"));
Assert.assertEquals("Final message has incorrect TTL.", 0L, received.getLongProperty("TTL"));
- received = _consumer.receive(100);
+ received = consumer.receive(100);
Assert.assertNull("More messages received", received);
+
+ clientConnection.close();
+
+ producerConnection.close();
}
- private Message nextMessage(String msg, boolean first) throws JMSException
+ private Message nextMessage(String msg, boolean first, Session producerSession, MessageProducer producer) throws JMSException
{
- Message send = _producerSession.createTextMessage("Message " + msg);
+ Message send = producerSession.createTextMessage("Message " + msg);
send.setBooleanProperty("first", first);
- send.setLongProperty("TTL", _producer.getTimeToLive());
+ send.setLongProperty("TTL", producer.getTimeToLive());
return send;
}
+ /**
+ * Tests the expired messages get actively deleted even on queues which have no consumers
+ */
+ public void testActiveTTL() throws URLSyntaxException, AMQException, JMSException, InterruptedException
+ {
+ Connection producerConnection = new AMQConnection(BROKER,"guest","guest","activeTTLtest","test");
+ AMQSession producerSession = (AMQSession) producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ Queue queue = producerSession.createTemporaryQueue();
+ producerSession.declareAndBind((AMQDestination) queue);
+ MessageProducer producer = producerSession.createProducer(queue);
+ producer.setTimeToLive(1000L);
+
+ // send Messages
+ for(int i = 0; i < MSG_COUNT; i++)
+ {
+ producer.send(producerSession.createTextMessage("Message: "+i));
+ }
+ long failureTime = System.currentTimeMillis() + 2*SERVER_TTL_TIMEOUT;
+
+ // check Queue depth for up to TIMEOUT seconds
+ long messageCount;
+
+ do
+ {
+ Thread.sleep(100);
+ messageCount = producerSession.getQueueDepth((AMQDestination) queue);
+ }
+ while(messageCount > 0L && System.currentTimeMillis() < failureTime);
+
+ assertEquals("Messages not automatically expired: ", 0L, messageCount);
+
+ producer.close();
+ producerSession.close();
+ producerConnection.close();
+ }
+
}
--
cgit v1.2.1
From 438015a3fe213ba2f6a82455065004412c36024e Mon Sep 17 00:00:00 2001
From: Aidan Skinner
Date: Wed, 30 Jan 2008 16:35:30 +0000
Subject: QPID-766: shorten delay
git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/branches/M2.1@616808 13f79535-47bb-0310-9956-ffa450edef68
---
.../main/java/org/apache/qpid/server/failover/FailoverMethodTest.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'java')
diff --git a/java/systests/src/main/java/org/apache/qpid/server/failover/FailoverMethodTest.java b/java/systests/src/main/java/org/apache/qpid/server/failover/FailoverMethodTest.java
index 2c293527d9..1f2d11b045 100644
--- a/java/systests/src/main/java/org/apache/qpid/server/failover/FailoverMethodTest.java
+++ b/java/systests/src/main/java/org/apache/qpid/server/failover/FailoverMethodTest.java
@@ -80,7 +80,7 @@ public class FailoverMethodTest extends TestCase implements ExceptionListener
//Failover should take less that 10 seconds.
// This is calculated by vm://:1 two retries left after initial connection (4s)
// localhost get three retries so (6s) so 10s in total for connection dropping
- assertTrue("Failover took less than 9.5 seconds:"+(end - start), (end - start) > 9500);
+ assertTrue("Failover took less than 9.5 seconds:"+(end - start), (end - start) > 6000);
// The sleep method is not 100% accurate under windows so with 5 sleeps and a 10ms accuracy then there is
// the potential for the tests to finish in 500ms sooner than the predicted 10s.
--
cgit v1.2.1
From e5593ea5ed57ddeab99474c45592baed1127b8fb Mon Sep 17 00:00:00 2001
From: Aidan Skinner
Date: Wed, 30 Jan 2008 16:37:06 +0000
Subject: QPID-766: shorten delay
git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/branches/M2.1@616809 13f79535-47bb-0310-9956-ffa450edef68
---
.../main/java/org/apache/qpid/server/failover/FailoverMethodTest.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
(limited to 'java')
diff --git a/java/systests/src/main/java/org/apache/qpid/server/failover/FailoverMethodTest.java b/java/systests/src/main/java/org/apache/qpid/server/failover/FailoverMethodTest.java
index 1f2d11b045..9b2a3a6e28 100644
--- a/java/systests/src/main/java/org/apache/qpid/server/failover/FailoverMethodTest.java
+++ b/java/systests/src/main/java/org/apache/qpid/server/failover/FailoverMethodTest.java
@@ -78,9 +78,9 @@ public class FailoverMethodTest extends TestCase implements ExceptionListener
long end = System.currentTimeMillis();
//Failover should take less that 10 seconds.
- // This is calculated by vm://:1 two retries left after initial connection (4s)
+ // This is calculated by vm://:1 two retries left after initial connection
// localhost get three retries so (6s) so 10s in total for connection dropping
- assertTrue("Failover took less than 9.5 seconds:"+(end - start), (end - start) > 6000);
+ assertTrue("Failover took less than 6 seconds:"+(end - start), (end - start) > 6000);
// The sleep method is not 100% accurate under windows so with 5 sleeps and a 10ms accuracy then there is
// the potential for the tests to finish in 500ms sooner than the predicted 10s.
--
cgit v1.2.1
From cebb9bb85aa61402762e2567be0d6a714b65f8d2 Mon Sep 17 00:00:00 2001
From: Aidan Skinner
Date: Fri, 8 Feb 2008 12:52:54 +0000
Subject: QPID-588: change instances of trace() and isTraceEnabled to debug
equivalent to support older versions of log4j
git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/branches/M2.1@619868 13f79535-47bb-0310-9956-ffa450edef68
---
.../java/org/apache/qpid/server/AMQChannel.java | 8 +++----
.../server/handler/BasicConsumeMethodHandler.java | 4 ++--
.../server/handler/BasicRejectMethodHandler.java | 4 ++--
.../queue/ConcurrentSelectorDeliveryManager.java | 24 ++++++++++----------
.../apache/qpid/server/queue/SubscriptionImpl.java | 8 +++----
.../java/org/apache/qpid/client/AMQSession.java | 8 +++----
.../apache/qpid/client/BasicMessageConsumer.java | 24 ++++++++++----------
.../qpid/test/unit/basic/PropertyValueTest.java | 2 +-
.../java/org/apache/qpid/framing/FieldTable.java | 26 +++++++++++-----------
.../qpid/sustained/SustainedClientTestCase.java | 4 ++--
.../apache/qpid/requestreply/PingPongBouncer.java | 6 ++---
11 files changed, 59 insertions(+), 59 deletions(-)
(limited to 'java')
diff --git a/java/broker/src/main/java/org/apache/qpid/server/AMQChannel.java b/java/broker/src/main/java/org/apache/qpid/server/AMQChannel.java
index 4696ec4453..1bf0cd027a 100644
--- a/java/broker/src/main/java/org/apache/qpid/server/AMQChannel.java
+++ b/java/broker/src/main/java/org/apache/qpid/server/AMQChannel.java
@@ -215,9 +215,9 @@ public class AMQChannel
}
else
{
- if (_log.isTraceEnabled())
+ if (_log.isDebugEnabled())
{
- _log.trace(debugIdentity() + "Content header received on channel " + _channelId);
+ _log.debug(debugIdentity() + "Content header received on channel " + _channelId);
}
if (ENABLE_JMSXUserID)
@@ -252,9 +252,9 @@ public class AMQChannel
throw new AMQException("Received content body without previously receiving a JmsPublishBody");
}
- if (_log.isTraceEnabled())
+ if (_log.isDebugEnabled())
{
- _log.trace(debugIdentity() + "Content body received on channel " + _channelId);
+ _log.debug(debugIdentity() + "Content body received on channel " + _channelId);
}
try
diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/BasicConsumeMethodHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicConsumeMethodHandler.java
index bb48539f50..ee46b16397 100644
--- a/java/broker/src/main/java/org/apache/qpid/server/handler/BasicConsumeMethodHandler.java
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicConsumeMethodHandler.java
@@ -77,9 +77,9 @@ public class BasicConsumeMethodHandler implements StateAwareMethodListener currentQueue = _messages.iterator();
@@ -536,9 +536,9 @@ public class ConcurrentSelectorDeliveryManager implements DeliveryManager
//else the clean up is not required as the message has already been taken for this queue therefore
// it was the responsibility of the code that took the message to ensure the _totalMessageSize was updated.
- if (_log.isTraceEnabled())
+ if (_log.isDebugEnabled())
{
- _log.trace("Removed taken message:" + message.debugIdentity());
+ _log.debug("Removed taken message:" + message.debugIdentity());
}
// try the next message
@@ -634,9 +634,9 @@ public class ConcurrentSelectorDeliveryManager implements DeliveryManager
Queue messageQueue = sub.getNextQueue(_messages);
- if (_log.isTraceEnabled())
+ if (_log.isDebugEnabled())
{
- _log.trace(debugIdentity() + "Async sendNextMessage for sub (" + System.identityHashCode(sub) +
+ _log.debug(debugIdentity() + "Async sendNextMessage for sub (" + System.identityHashCode(sub) +
") from queue (" + System.identityHashCode(messageQueue) +
") AMQQueue (" + System.identityHashCode(queue) + ")");
}
@@ -662,9 +662,9 @@ public class ConcurrentSelectorDeliveryManager implements DeliveryManager
// message will be null if we have no messages in the messageQueue.
if (entry == null)
{
- if (_log.isTraceEnabled())
+ if (_log.isDebugEnabled())
{
- _log.trace(debugIdentity() + "No messages for Subscriber(" + System.identityHashCode(sub) + ") from queue; (" + System.identityHashCode(messageQueue) + ")");
+ _log.debug(debugIdentity() + "No messages for Subscriber(" + System.identityHashCode(sub) + ") from queue; (" + System.identityHashCode(messageQueue) + ")");
}
return;
}
@@ -704,9 +704,9 @@ public class ConcurrentSelectorDeliveryManager implements DeliveryManager
if (messageQueue == sub.getResendQueue())
{
- if (_log.isTraceEnabled())
+ if (_log.isDebugEnabled())
{
- _log.trace(debugIdentity() + "All messages sent from resendQueue for " + sub);
+ _log.debug(debugIdentity() + "All messages sent from resendQueue for " + sub);
}
if (messageQueue.isEmpty())
{
@@ -918,9 +918,9 @@ public class ConcurrentSelectorDeliveryManager implements DeliveryManager
{
if (!s.isSuspended())
{
- if (_log.isTraceEnabled())
+ if (_log.isDebugEnabled())
{
- _log.trace(debugIdentity() + "Delivering Message:" + entry.getMessage().debugIdentity() + " to(" +
+ _log.debug(debugIdentity() + "Delivering Message:" + entry.getMessage().debugIdentity() + " to(" +
System.identityHashCode(s) + ") :" + s);
}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionImpl.java b/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionImpl.java
index e631481cc8..7f0accf052 100644
--- a/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionImpl.java
+++ b/java/broker/src/main/java/org/apache/qpid/server/queue/SubscriptionImpl.java
@@ -416,9 +416,9 @@ public class SubscriptionImpl implements Subscription
}
- if (_logger.isTraceEnabled())
+ if (_logger.isDebugEnabled())
{
- _logger.trace("(" + debugIdentity() + ") checking filters for message (" + entry.debugIdentity());
+ _logger.debug("(" + debugIdentity() + ") checking filters for message (" + entry.debugIdentity());
}
return checkFilters(entry);
@@ -563,9 +563,9 @@ public class SubscriptionImpl implements Subscription
{
QueueEntry resent = _resendQueue.poll();
- if (_logger.isTraceEnabled())
+ if (_logger.isDebugEnabled())
{
- _logger.trace("Removed for resending:" + resent.debugIdentity());
+ _logger.debug("Removed for resending:" + resent.debugIdentity());
}
resent.release();
diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQSession.java b/java/client/src/main/java/org/apache/qpid/client/AMQSession.java
index deaa435d8c..64895fe16c 100644
--- a/java/client/src/main/java/org/apache/qpid/client/AMQSession.java
+++ b/java/client/src/main/java/org/apache/qpid/client/AMQSession.java
@@ -1394,9 +1394,9 @@ public class AMQSession extends Closeable implements Session, QueueSession, Topi
public void rejectMessage(UnprocessedMessage message, boolean requeue)
{
- if (_logger.isTraceEnabled())
+ if (_logger.isDebugEnabled())
{
- _logger.trace("Rejecting Unacked message:" + message.getDeliverBody().getDeliveryTag());
+ _logger.debug("Rejecting Unacked message:" + message.getDeliverBody().getDeliveryTag());
}
rejectMessage(message.getDeliverBody().getDeliveryTag(), requeue);
@@ -1404,9 +1404,9 @@ public class AMQSession extends Closeable implements Session, QueueSession, Topi
public void rejectMessage(AbstractJMSMessage message, boolean requeue)
{
- if (_logger.isTraceEnabled())
+ if (_logger.isDebugEnabled())
{
- _logger.trace("Rejecting Abstract message:" + message.getDeliveryTag());
+ _logger.debug("Rejecting Abstract message:" + message.getDeliveryTag());
}
rejectMessage(message.getDeliveryTag(), requeue);
diff --git a/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer.java b/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer.java
index 610e0109b1..488d22c4bd 100644
--- a/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer.java
+++ b/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer.java
@@ -551,12 +551,12 @@ public class BasicMessageConsumer extends Closeable implements MessageConsumer
{
if (!_closed.getAndSet(true))
{
- if (_logger.isTraceEnabled())
+ if (_logger.isDebugEnabled())
{
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
if (_closedStack != null)
{
- _logger.trace(_consumerTag + " previously:" + _closedStack.toString());
+ _logger.debug(_consumerTag + " previously:" + _closedStack.toString());
}
else
{
@@ -621,14 +621,14 @@ public class BasicMessageConsumer extends Closeable implements MessageConsumer
{
_closed.set(true);
- if (_logger.isTraceEnabled())
+ if (_logger.isDebugEnabled())
{
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
if (_closedStack != null)
{
- _logger.trace(_consumerTag + " markClosed():"
+ _logger.debug(_consumerTag + " markClosed():"
+ Arrays.asList(stackTrace).subList(3, stackTrace.length - 1));
- _logger.trace(_consumerTag + " previously:" + _closedStack.toString());
+ _logger.debug(_consumerTag + " previously:" + _closedStack.toString());
}
else
{
@@ -845,14 +845,14 @@ public class BasicMessageConsumer extends Closeable implements MessageConsumer
// synchronized (_closed)
{
_closed.set(true);
- if (_logger.isTraceEnabled())
+ if (_logger.isDebugEnabled())
{
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
if (_closedStack != null)
{
- _logger.trace(_consumerTag + " notifyError():"
+ _logger.debug(_consumerTag + " notifyError():"
+ Arrays.asList(stackTrace).subList(3, stackTrace.length - 1));
- _logger.trace(_consumerTag + " previously" + _closedStack.toString());
+ _logger.debug(_consumerTag + " previously" + _closedStack.toString());
}
else
{
@@ -982,9 +982,9 @@ public class BasicMessageConsumer extends Closeable implements MessageConsumer
if (tag != null)
{
- if (_logger.isTraceEnabled())
+ if (_logger.isDebugEnabled())
{
- _logger.trace("Rejecting tag from _receivedDTs:" + tag);
+ _logger.debug("Rejecting tag from _receivedDTs:" + tag);
}
_session.rejectMessage(tag, true);
@@ -1025,9 +1025,9 @@ public class BasicMessageConsumer extends Closeable implements MessageConsumer
{
_session.rejectMessage(((AbstractJMSMessage) o), true);
- if (_logger.isTraceEnabled())
+ if (_logger.isDebugEnabled())
{
- _logger.trace("Rejected message:" + ((AbstractJMSMessage) o).getDeliveryTag());
+ _logger.debug("Rejected message:" + ((AbstractJMSMessage) o).getDeliveryTag());
}
iterator.remove();
diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/basic/PropertyValueTest.java b/java/client/src/test/java/org/apache/qpid/test/unit/basic/PropertyValueTest.java
index d2a7ba301b..8cceba6ffd 100644
--- a/java/client/src/test/java/org/apache/qpid/test/unit/basic/PropertyValueTest.java
+++ b/java/client/src/test/java/org/apache/qpid/test/unit/basic/PropertyValueTest.java
@@ -173,7 +173,7 @@ public class PropertyValueTest extends TestCase implements MessageListener
m.setJMSReplyTo(q);
m.setStringProperty("TempQueue", q.toString());
- _logger.trace("Message:" + m);
+ _logger.debug("Message:" + m);
Assert.assertEquals("Check temp queue has been set correctly", m.getJMSReplyTo().toString(),
m.getStringProperty("TempQueue"));
diff --git a/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java b/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java
index 46b10b5963..55968ffe19 100644
--- a/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java
+++ b/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java
@@ -706,14 +706,14 @@ public class FieldTable
public void writeToBuffer(ByteBuffer buffer)
{
- final boolean trace = _logger.isTraceEnabled();
+ final boolean trace = _logger.isDebugEnabled();
if (trace)
{
- _logger.trace("FieldTable::writeToBuffer: Writing encoded length of " + getEncodedSize() + "...");
+ _logger.debug("FieldTable::writeToBuffer: Writing encoded length of " + getEncodedSize() + "...");
if (_properties != null)
{
- _logger.trace(_properties.toString());
+ _logger.debug(_properties.toString());
}
}
@@ -918,11 +918,11 @@ public class FieldTable
final Map.Entry me = it.next();
try
{
- if (_logger.isTraceEnabled())
+ if (_logger.isDebugEnabled())
{
- _logger.trace("Writing Property:" + me.getKey() + " Type:" + me.getValue().getType() + " Value:"
+ _logger.debug("Writing Property:" + me.getKey() + " Type:" + me.getValue().getType() + " Value:"
+ me.getValue().getValue());
- _logger.trace("Buffer Position:" + buffer.position() + " Remaining:" + buffer.remaining());
+ _logger.debug("Buffer Position:" + buffer.position() + " Remaining:" + buffer.remaining());
}
// Write the actual parameter name
@@ -931,12 +931,12 @@ public class FieldTable
}
catch (Exception e)
{
- if (_logger.isTraceEnabled())
+ if (_logger.isDebugEnabled())
{
- _logger.trace("Exception thrown:" + e);
- _logger.trace("Writing Property:" + me.getKey() + " Type:" + me.getValue().getType() + " Value:"
+ _logger.debug("Exception thrown:" + e);
+ _logger.debug("Writing Property:" + me.getKey() + " Type:" + me.getValue().getType() + " Value:"
+ me.getValue().getValue());
- _logger.trace("Buffer Position:" + buffer.position() + " Remaining:" + buffer.remaining());
+ _logger.debug("Buffer Position:" + buffer.position() + " Remaining:" + buffer.remaining());
}
throw new RuntimeException(e);
@@ -948,7 +948,7 @@ public class FieldTable
private void setFromBuffer(ByteBuffer buffer, long length) throws AMQFrameDecodingException
{
- final boolean trace = _logger.isTraceEnabled();
+ final boolean trace = _logger.isDebugEnabled();
if (length > 0)
{
@@ -964,7 +964,7 @@ public class FieldTable
if (trace)
{
- _logger.trace("FieldTable::PropFieldTable(buffer," + length + "): Read type '" + value.getType()
+ _logger.debug("FieldTable::PropFieldTable(buffer," + length + "): Read type '" + value.getType()
+ "', key '" + key + "', value '" + value.getValue() + "'");
}
@@ -979,7 +979,7 @@ public class FieldTable
if (trace)
{
- _logger.trace("FieldTable::FieldTable(buffer," + length + "): Done.");
+ _logger.debug("FieldTable::FieldTable(buffer," + length + "): Done.");
}
}
diff --git a/java/integrationtests/src/main/java/org/apache/qpid/sustained/SustainedClientTestCase.java b/java/integrationtests/src/main/java/org/apache/qpid/sustained/SustainedClientTestCase.java
index 2764f2c3aa..5ed502287d 100644
--- a/java/integrationtests/src/main/java/org/apache/qpid/sustained/SustainedClientTestCase.java
+++ b/java/integrationtests/src/main/java/org/apache/qpid/sustained/SustainedClientTestCase.java
@@ -335,9 +335,9 @@ public class SustainedClientTestCase extends TestCase3BasicPubSub implements Exc
public void onMessage(Message message)
{
- if (log.isTraceEnabled())
+ if (log.isDebugEnabled())
{
- log.trace("Message " + _received + "received in listener");
+ log.debug("Message " + _received + "received in listener");
}
if (message instanceof TextMessage)
diff --git a/java/perftests/src/main/java/org/apache/qpid/requestreply/PingPongBouncer.java b/java/perftests/src/main/java/org/apache/qpid/requestreply/PingPongBouncer.java
index 82b36bf233..0712557383 100644
--- a/java/perftests/src/main/java/org/apache/qpid/requestreply/PingPongBouncer.java
+++ b/java/perftests/src/main/java/org/apache/qpid/requestreply/PingPongBouncer.java
@@ -340,7 +340,7 @@ public class PingPongBouncer implements MessageListener
{
if (_failBeforeCommit)
{
- _logger.trace("Failing Before Commit");
+ _logger.debug("Failing Before Commit");
doFailover();
}
@@ -348,11 +348,11 @@ public class PingPongBouncer implements MessageListener
if (_failAfterCommit)
{
- _logger.trace("Failing After Commit");
+ _logger.debug("Failing After Commit");
doFailover();
}
- _logger.trace("Session Commited.");
+ _logger.debug("Session Commited.");
}
catch (JMSException e)
{
--
cgit v1.2.1
From d203e3c2b52e59b8b5faed91b57be4603840b9a7 Mon Sep 17 00:00:00 2001
From: Martin Ritchie
Date: Tue, 12 Feb 2008 11:29:19 +0000
Subject: QPID-784 : Added ability to provide existing Socket to Qpid Client
Libraries to use as for connection. AMQBrokerDetails.java, BrokerDetails.java
And ConnectionURLTest.java augmented to allow new transport type 'socket' New
ExistingSocketConnector, which utises a given Socket() rather than creating
its own from a SocketChannel. This code was taken from the Mina library
v1.0.0. Changes to AMQConnection.java, SocketTransportConnection.java were
required to allow the new Socket object to be passed through to the
ExistingSocketConnector. The TransportConnection.java was updated to return
an ExistingSocketConnector when the 'socket' transport is used.
AMQConnection.makeBrokerConnection was changed when the 'socket' transport is
being used. This allows the set Socket to be passed down to the
ExistingSocketConnector for the transport to be run over.
git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/branches/M2.1@620767 13f79535-47bb-0310-9956-ffa450edef68
---
.../transport/ExistingSocketConnectorDemo.java | 161 +++++++
.../socket/nio/ExistingSocketConnector.java | 478 +++++++++++++++++++++
.../org/apache/qpid/client/AMQBrokerDetails.java | 3 +-
.../java/org/apache/qpid/client/AMQConnection.java | 56 ++-
.../transport/SocketTransportConnection.java | 36 +-
.../qpid/client/transport/TransportConnection.java | 17 +-
.../java/org/apache/qpid/jms/BrokerDetails.java | 1 +
.../client/connectionurl/ConnectionURLTest.java | 17 +
8 files changed, 757 insertions(+), 12 deletions(-)
create mode 100644 java/client/example/src/main/java/org/apache/qpid/example/transport/ExistingSocketConnectorDemo.java
create mode 100644 java/client/src/main/java/org/apache/mina/transport/socket/nio/ExistingSocketConnector.java
(limited to 'java')
diff --git a/java/client/example/src/main/java/org/apache/qpid/example/transport/ExistingSocketConnectorDemo.java b/java/client/example/src/main/java/org/apache/qpid/example/transport/ExistingSocketConnectorDemo.java
new file mode 100644
index 0000000000..0979c9c6b8
--- /dev/null
+++ b/java/client/example/src/main/java/org/apache/qpid/example/transport/ExistingSocketConnectorDemo.java
@@ -0,0 +1,161 @@
+/*
+ *
+ * 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.example.transport;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.jms.ConnectionListener;
+import org.apache.qpid.url.URLSyntaxException;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.nio.channels.SocketChannel;
+
+/**
+ * This is a simple application that demonstrates how you can use the Qpid AMQP interfaces to use existing sockets as
+ * the transport for the Client API.
+ *
+ * The Demo here runs twice:
+ * 1. Just to show a simple publish and receive.
+ * 2. To demonstrate how to use existing sockets and utilise the underlying client failover mechnaism.
+ */
+public class ExistingSocketConnectorDemo implements ConnectionListener
+{
+ private static boolean DEMO_FAILOVER = false;
+
+ public static void main(String[] args) throws IOException, URLSyntaxException, AMQException, JMSException
+ {
+ System.out.println("Testing socket connection to localhost:5672.");
+
+ new ExistingSocketConnectorDemo();
+
+ System.out.println("Testing socket connection failover between localhost:5672 and localhost:5673.");
+
+ DEMO_FAILOVER = true;
+
+ new ExistingSocketConnectorDemo();
+ }
+
+ Connection _connection;
+ MessageProducer _producer;
+ Session _session;
+
+
+ /** Here we can see the broker we are connecting to is set to be 'socket:///' signifying we will provide the socket. */
+ public static final String CONNECTION = "amqp://guest:guest@id/test?brokerlist='socket:///'";
+
+ public ExistingSocketConnectorDemo() throws IOException, URLSyntaxException, AMQException, JMSException
+ {
+
+ Socket socket = SocketChannel.open().socket();
+ socket.connect(new InetSocketAddress("localhost", 5672));
+
+ _connection = new AMQConnection(CONNECTION, socket);
+
+ _session = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ MessageConsumer consumer = _session.createConsumer(_session.createQueue("Queue"));
+
+ _producer = _session.createProducer(_session.createQueue("Queue"));
+
+ _connection.start();
+
+ if (!DEMO_FAILOVER)
+ {
+ _producer.send(_session.createTextMessage("Simple Test"));
+ }
+ else
+ {
+ // Using the Qpid interfaces we can set a listener that allows us to demonstrate failover
+ ((AMQConnection) _connection).setConnectionListener(this);
+
+ System.out.println("Testing failover: Please ensure second broker running on localhost:5673 and shutdown broker on 5672.");
+ }
+
+ //We do a blocking receive here so that we can demonstrate failover.
+ Message message = consumer.receive();
+
+ System.out.println("Recevied :" + message);
+
+ _connection.close();
+ }
+
+ // ConnectionListener Interface
+
+ public void bytesSent(long count)
+ {
+ //not used in this example
+ }
+ public void bytesReceived(long count)
+ {
+ //not used in this example
+ }
+
+ public boolean preFailover(boolean redirect)
+ {
+ /**
+ * This method is called before the underlying client library starts to reconnect. This gives us the opportunity
+ * to set a new socket for the failover to occur on.
+ */
+ try
+ {
+ Socket socket = SocketChannel.open().socket();
+
+ socket.connect(new InetSocketAddress("localhost", 5673));
+
+ // This is the new method to pass in an open socket for the connection to use.
+ ((AMQConnection) _connection).setOpenSocket(socket);
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ return false;
+ }
+ return true;
+ }
+
+ public boolean preResubscribe()
+ {
+ //not used in this example - but must return true to allow the resubscription of existing clients.
+ return true;
+ }
+
+ public void failoverComplete()
+ {
+ // Now that failover has completed we can send a message that the receiving thread will pick up
+ try
+ {
+ _producer.send(_session.createTextMessage("Simple Failover Test"));
+ }
+ catch (JMSException e)
+ {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/java/client/src/main/java/org/apache/mina/transport/socket/nio/ExistingSocketConnector.java b/java/client/src/main/java/org/apache/mina/transport/socket/nio/ExistingSocketConnector.java
new file mode 100644
index 0000000000..ab3bc28d83
--- /dev/null
+++ b/java/client/src/main/java/org/apache/mina/transport/socket/nio/ExistingSocketConnector.java
@@ -0,0 +1,478 @@
+/*
+ * 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.mina.transport.socket.nio;
+
+import edu.emory.mathcs.backport.java.util.concurrent.Executor;
+import org.apache.mina.common.ConnectFuture;
+import org.apache.mina.common.ExceptionMonitor;
+import org.apache.mina.common.IoConnector;
+import org.apache.mina.common.IoConnectorConfig;
+import org.apache.mina.common.IoHandler;
+import org.apache.mina.common.IoServiceConfig;
+import org.apache.mina.common.support.BaseIoConnector;
+import org.apache.mina.common.support.DefaultConnectFuture;
+import org.apache.mina.util.NamePreservingRunnable;
+import org.apache.mina.util.NewThreadExecutor;
+import org.apache.mina.util.Queue;
+
+import java.io.IOException;
+import java.net.ConnectException;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.SocketChannel;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * {@link IoConnector} for socket transport (TCP/IP).
+ *
+ * @author The Apache Directory Project (mina-dev@directory.apache.org)
+ * @version $Rev$, $Date$
+ */
+public class ExistingSocketConnector extends BaseIoConnector
+{
+ /** @noinspection StaticNonFinalField */
+ private static volatile int nextId = 0;
+
+ private final Object lock = new Object();
+ private final int id = nextId++;
+ private final String threadName = "SocketConnector-" + id;
+ private SocketConnectorConfig defaultConfig = new SocketConnectorConfig();
+ private final Queue connectQueue = new Queue();
+ private final SocketIoProcessor[] ioProcessors;
+ private final int processorCount;
+ private final Executor executor;
+
+ /** @noinspection FieldAccessedSynchronizedAndUnsynchronized */
+ private Selector selector;
+ private Worker worker;
+ private int processorDistributor = 0;
+ private int workerTimeout = 60; // 1 min.
+ private Socket _openSocket = null;
+
+ /** Create a connector with a single processing thread using a NewThreadExecutor */
+ public ExistingSocketConnector()
+ {
+ this(1, new NewThreadExecutor());
+ }
+
+ /**
+ * Create a connector with the desired number of processing threads
+ *
+ * @param processorCount Number of processing threads
+ * @param executor Executor to use for launching threads
+ */
+ public ExistingSocketConnector(int processorCount, Executor executor)
+ {
+ if (processorCount < 1)
+ {
+ throw new IllegalArgumentException("Must have at least one processor");
+ }
+
+ this.executor = executor;
+ this.processorCount = processorCount;
+ ioProcessors = new SocketIoProcessor[processorCount];
+
+ for (int i = 0; i < processorCount; i++)
+ {
+ ioProcessors[i] = new SocketIoProcessor("SocketConnectorIoProcessor-" + id + "." + i, executor);
+ }
+ }
+
+ /**
+ * How many seconds to keep the connection thread alive between connection requests
+ *
+ * @return Number of seconds to keep connection thread alive
+ */
+ public int getWorkerTimeout()
+ {
+ return workerTimeout;
+ }
+
+ /**
+ * Set how many seconds the connection worker thread should remain alive once idle before terminating itself.
+ *
+ * @param workerTimeout Number of seconds to keep thread alive. Must be >=0
+ */
+ public void setWorkerTimeout(int workerTimeout)
+ {
+ if (workerTimeout < 0)
+ {
+ throw new IllegalArgumentException("Must be >= 0");
+ }
+ this.workerTimeout = workerTimeout;
+ }
+
+ public ConnectFuture connect(SocketAddress address, IoHandler handler, IoServiceConfig config)
+ {
+ return connect(address, null, handler, config);
+ }
+
+ public ConnectFuture connect(SocketAddress address, SocketAddress localAddress,
+ IoHandler handler, IoServiceConfig config)
+ {
+ /** Changes here from the Mina OpenSocketConnector.
+ * Ignoreing all address as they are not needed */
+
+ if (handler == null)
+ {
+ throw new NullPointerException("handler");
+ }
+
+
+ if (config == null)
+ {
+ config = getDefaultConfig();
+ }
+
+ if (_openSocket == null)
+ {
+ throw new IllegalArgumentException("Specifed Socket not active");
+ }
+
+ boolean success = false;
+
+ try
+ {
+ DefaultConnectFuture future = new DefaultConnectFuture();
+ newSession(_openSocket, handler, config, future);
+ success = true;
+ return future;
+ }
+ catch (IOException e)
+ {
+ return DefaultConnectFuture.newFailedFuture(e);
+ }
+ finally
+ {
+ if (!success && _openSocket != null)
+ {
+ try
+ {
+ _openSocket.close();
+ }
+ catch (IOException e)
+ {
+ ExceptionMonitor.getInstance().exceptionCaught(e);
+ }
+ }
+ }
+ }
+
+ public IoServiceConfig getDefaultConfig()
+ {
+ return defaultConfig;
+ }
+
+ /**
+ * Sets the config this connector will use by default.
+ *
+ * @param defaultConfig the default config.
+ *
+ * @throws NullPointerException if the specified value is null.
+ */
+ public void setDefaultConfig(SocketConnectorConfig defaultConfig)
+ {
+ if (defaultConfig == null)
+ {
+ throw new NullPointerException("defaultConfig");
+ }
+ this.defaultConfig = defaultConfig;
+ }
+
+ private synchronized void startupWorker() throws IOException
+ {
+ if (worker == null)
+ {
+ selector = Selector.open();
+ worker = new Worker();
+ executor.execute(new NamePreservingRunnable(worker));
+ }
+ }
+
+ private void registerNew()
+ {
+ if (connectQueue.isEmpty())
+ {
+ return;
+ }
+
+ for (; ;)
+ {
+ ConnectionRequest req;
+ synchronized (connectQueue)
+ {
+ req = (ConnectionRequest) connectQueue.pop();
+ }
+
+ if (req == null)
+ {
+ break;
+ }
+
+ SocketChannel ch = req.channel;
+ try
+ {
+ ch.register(selector, SelectionKey.OP_CONNECT, req);
+ }
+ catch (IOException e)
+ {
+ req.setException(e);
+ }
+ }
+ }
+
+ private void processSessions(Set keys)
+ {
+ Iterator it = keys.iterator();
+
+ while (it.hasNext())
+ {
+ SelectionKey key = (SelectionKey) it.next();
+
+ if (!key.isConnectable())
+ {
+ continue;
+ }
+
+ SocketChannel ch = (SocketChannel) key.channel();
+ ConnectionRequest entry = (ConnectionRequest) key.attachment();
+
+ boolean success = false;
+ try
+ {
+ ch.finishConnect();
+ newSession(ch, entry.handler, entry.config, entry);
+ success = true;
+ }
+ catch (Throwable e)
+ {
+ entry.setException(e);
+ }
+ finally
+ {
+ key.cancel();
+ if (!success)
+ {
+ try
+ {
+ ch.close();
+ }
+ catch (IOException e)
+ {
+ ExceptionMonitor.getInstance().exceptionCaught(e);
+ }
+ }
+ }
+ }
+
+ keys.clear();
+ }
+
+ private void processTimedOutSessions(Set keys)
+ {
+ long currentTime = System.currentTimeMillis();
+ Iterator it = keys.iterator();
+
+ while (it.hasNext())
+ {
+ SelectionKey key = (SelectionKey) it.next();
+
+ if (!key.isValid())
+ {
+ continue;
+ }
+
+ ConnectionRequest entry = (ConnectionRequest) key.attachment();
+
+ if (currentTime >= entry.deadline)
+ {
+ entry.setException(new ConnectException());
+ try
+ {
+ key.channel().close();
+ }
+ catch (IOException e)
+ {
+ ExceptionMonitor.getInstance().exceptionCaught(e);
+ }
+ finally
+ {
+ key.cancel();
+ }
+ }
+ }
+ }
+
+ private void newSession(Socket socket, IoHandler handler, IoServiceConfig config, ConnectFuture connectFuture)
+ throws IOException
+ {
+ SocketSessionImpl session = new SocketSessionImpl(this,
+ nextProcessor(),
+ getListeners(),
+ config,
+ socket.getChannel(),
+ handler,
+ socket.getRemoteSocketAddress());
+
+ newSession(session, config, connectFuture);
+ }
+
+ private void newSession(SocketChannel ch, IoHandler handler, IoServiceConfig config, ConnectFuture connectFuture)
+ throws IOException
+
+ {
+ SocketSessionImpl session = new SocketSessionImpl(this,
+ nextProcessor(),
+ getListeners(),
+ config,
+ ch,
+ handler,
+ ch.socket().getRemoteSocketAddress());
+
+ newSession(session, config, connectFuture);
+ }
+
+ private void newSession(SocketSessionImpl session, IoServiceConfig config, ConnectFuture connectFuture)
+ throws IOException
+ {
+ try
+ {
+ getFilterChainBuilder().buildFilterChain(session.getFilterChain());
+ config.getFilterChainBuilder().buildFilterChain(session.getFilterChain());
+ config.getThreadModel().buildFilterChain(session.getFilterChain());
+ }
+ catch (Throwable e)
+ {
+ throw (IOException) new IOException("Failed to create a session.").initCause(e);
+ }
+ session.getIoProcessor().addNew(session);
+ connectFuture.setSession(session);
+ }
+
+ private SocketIoProcessor nextProcessor()
+ {
+ return ioProcessors[processorDistributor++ % processorCount];
+ }
+
+ public void setOpenSocket(Socket openSocket)
+ {
+ _openSocket = openSocket;
+ }
+
+ private class Worker implements Runnable
+ {
+ private long lastActive = System.currentTimeMillis();
+
+ public void run()
+ {
+ Thread.currentThread().setName(ExistingSocketConnector.this.threadName);
+
+ for (; ;)
+ {
+ try
+ {
+ int nKeys = selector.select(1000);
+
+ registerNew();
+
+ if (nKeys > 0)
+ {
+ processSessions(selector.selectedKeys());
+ }
+
+ processTimedOutSessions(selector.keys());
+
+ if (selector.keys().isEmpty())
+ {
+ if (System.currentTimeMillis() - lastActive > workerTimeout * 1000L)
+ {
+ synchronized (lock)
+ {
+ if (selector.keys().isEmpty() &&
+ connectQueue.isEmpty())
+ {
+ worker = null;
+ try
+ {
+ selector.close();
+ }
+ catch (IOException e)
+ {
+ ExceptionMonitor.getInstance().exceptionCaught(e);
+ }
+ finally
+ {
+ selector = null;
+ }
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ lastActive = System.currentTimeMillis();
+ }
+ }
+ catch (IOException e)
+ {
+ ExceptionMonitor.getInstance().exceptionCaught(e);
+
+ try
+ {
+ Thread.sleep(1000);
+ }
+ catch (InterruptedException e1)
+ {
+ ExceptionMonitor.getInstance().exceptionCaught(e1);
+ }
+ }
+ }
+ }
+ }
+
+ private class ConnectionRequest extends DefaultConnectFuture
+ {
+ private final SocketChannel channel;
+ private final long deadline;
+ private final IoHandler handler;
+ private final IoServiceConfig config;
+
+ private ConnectionRequest(SocketChannel channel, IoHandler handler, IoServiceConfig config)
+ {
+ this.channel = channel;
+ long timeout;
+ if (config instanceof IoConnectorConfig)
+ {
+ timeout = ((IoConnectorConfig) config).getConnectTimeoutMillis();
+ }
+ else
+ {
+ timeout = ((IoConnectorConfig) getDefaultConfig()).getConnectTimeoutMillis();
+ }
+ this.deadline = System.currentTimeMillis() + timeout;
+ this.handler = handler;
+ this.config = config;
+ }
+ }
+}
diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQBrokerDetails.java b/java/client/src/main/java/org/apache/qpid/client/AMQBrokerDetails.java
index c04380ba8c..89ce4b2c72 100644
--- a/java/client/src/main/java/org/apache/qpid/client/AMQBrokerDetails.java
+++ b/java/client/src/main/java/org/apache/qpid/client/AMQBrokerDetails.java
@@ -58,7 +58,8 @@ public class AMQBrokerDetails implements BrokerDetails
{
//todo this list of valid transports should be enumerated somewhere
if ((!(transport.equalsIgnoreCase("vm") ||
- transport.equalsIgnoreCase("tcp"))))
+ transport.equalsIgnoreCase("tcp") ||
+ transport.equalsIgnoreCase("socket"))))
{
if (transport.equalsIgnoreCase("localhost"))
{
diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQConnection.java b/java/client/src/main/java/org/apache/qpid/client/AMQConnection.java
index 39b3b80e74..acbe495550 100644
--- a/java/client/src/main/java/org/apache/qpid/client/AMQConnection.java
+++ b/java/client/src/main/java/org/apache/qpid/client/AMQConnection.java
@@ -30,6 +30,8 @@ import org.apache.qpid.client.failover.FailoverRetrySupport;
import org.apache.qpid.client.protocol.AMQProtocolHandler;
import org.apache.qpid.client.state.AMQState;
import org.apache.qpid.client.state.AMQStateManager;
+import org.apache.qpid.client.transport.ITransportConnection;
+import org.apache.qpid.client.transport.SocketTransportConnection;
import org.apache.qpid.client.transport.TransportConnection;
import org.apache.qpid.exchange.ExchangeDefaults;
import org.apache.qpid.framing.*;
@@ -62,6 +64,7 @@ import javax.naming.Referenceable;
import javax.naming.StringRefAddr;
import java.io.IOException;
import java.net.ConnectException;
+import java.net.Socket;
import java.nio.channels.UnresolvedAddressException;
import java.text.MessageFormat;
import java.util.*;
@@ -157,6 +160,9 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect
private static final long DEFAULT_TIMEOUT = 1000 * 30;
private ProtocolVersion _protocolVersion;
+ /** The active socket that is to be used as a value for connection */
+ private Socket _openSocket;
+
/**
* @param broker brokerdetails
* @param username username
@@ -173,7 +179,7 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect
this(new AMQConnectionURL(
ConnectionURL.AMQ_PROTOCOL + "://" + username + ":" + password + "@"
+ ((clientName == null) ? "" : clientName) + "/" + virtualHost + "?brokerlist='"
- + AMQBrokerDetails.checkTransport(broker) + "'"), null);
+ + AMQBrokerDetails.checkTransport(broker) + "'"), null, null);
}
/**
@@ -192,7 +198,7 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect
this(new AMQConnectionURL(
ConnectionURL.AMQ_PROTOCOL + "://" + username + ":" + password + "@"
+ ((clientName == null) ? "" : clientName) + "/" + virtualHost + "?brokerlist='"
- + AMQBrokerDetails.checkTransport(broker) + "'"), sslConfig);
+ + AMQBrokerDetails.checkTransport(broker) + "'"), sslConfig, null);
}
public AMQConnection(String host, int port, String username, String password, String clientName, String virtualHost)
@@ -217,26 +223,38 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect
+ "'" + "," + ConnectionURL.OPTIONS_SSL + "='true'")
: (ConnectionURL.AMQ_PROTOCOL + "://" + username + ":" + password + "@"
+ ((clientName == null) ? "" : clientName) + virtualHost + "?brokerlist='tcp://" + host + ":" + port
- + "'" + "," + ConnectionURL.OPTIONS_SSL + "='false'")), sslConfig);
+ + "'" + "," + ConnectionURL.OPTIONS_SSL + "='false'")), sslConfig, null);
+ }
+
+ public AMQConnection(String connection, Socket socket) throws AMQException, URLSyntaxException
+ {
+ this(new AMQConnectionURL(connection), null, socket);
}
public AMQConnection(String connection) throws AMQException, URLSyntaxException
{
- this(new AMQConnectionURL(connection), null);
+ this(new AMQConnectionURL(connection), null, null);
}
public AMQConnection(String connection, SSLConfiguration sslConfig) throws AMQException, URLSyntaxException
{
- this(new AMQConnectionURL(connection), sslConfig);
+ this(new AMQConnectionURL(connection), sslConfig, null);
}
public AMQConnection(ConnectionURL connectionURL, SSLConfiguration sslConfig) throws AMQException
+ {
+ this(connectionURL, sslConfig, null);
+ }
+
+ public AMQConnection(ConnectionURL connectionURL, SSLConfiguration sslConfig, Socket socket) throws AMQException
{
if (_logger.isInfoEnabled())
{
_logger.info("Connection:" + connectionURL);
}
+ _openSocket = socket;
+
_sslConfiguration = sslConfig;
if (connectionURL == null)
{
@@ -395,9 +413,26 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect
EnumSet.of(AMQState.CONNECTION_OPEN, AMQState.CONNECTION_CLOSED);
try
{
- TransportConnection.getInstance(brokerDetail).connect(_protocolHandler, brokerDetail);
- // this blocks until the connection has been set up or when an error
- // has prevented the connection being set up
+
+ ITransportConnection connection = TransportConnection.getInstance(brokerDetail);
+
+ if (brokerDetail.getTransport().equals(BrokerDetails.SOCKET))
+ {
+ if (_openSocket != null)
+ {
+ ((SocketTransportConnection) connection).setOpenSocket(_openSocket);
+ }
+ else
+ {
+ throw new IllegalArgumentException("Active Socket must be provided for broker " +
+ "with 'socket' transport:" + brokerDetail);
+ }
+
+ }
+
+ connection.connect(_protocolHandler, brokerDetail);
+ // this blocks until the connection has been set up or when an error
+ // has prevented the connection being set up
//_protocolHandler.attainState(AMQState.CONNECTION_OPEN);
AMQState state = _protocolHandler.attainState(openOrClosedStates);
@@ -1292,6 +1327,11 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect
return _sessions.get(channelId);
}
+ public void setOpenSocket(Socket socket)
+ {
+ _openSocket = socket;
+ }
+
public ProtocolVersion getProtocolVersion()
{
return _protocolVersion;
diff --git a/java/client/src/main/java/org/apache/qpid/client/transport/SocketTransportConnection.java b/java/client/src/main/java/org/apache/qpid/client/transport/SocketTransportConnection.java
index 5482e48699..e9d6242e77 100644
--- a/java/client/src/main/java/org/apache/qpid/client/transport/SocketTransportConnection.java
+++ b/java/client/src/main/java/org/apache/qpid/client/transport/SocketTransportConnection.java
@@ -24,6 +24,7 @@ import org.apache.mina.common.ByteBuffer;
import org.apache.mina.common.ConnectFuture;
import org.apache.mina.common.IoConnector;
import org.apache.mina.common.SimpleByteBufferAllocator;
+import org.apache.mina.transport.socket.nio.ExistingSocketConnector;
import org.apache.mina.transport.socket.nio.SocketConnectorConfig;
import org.apache.mina.transport.socket.nio.SocketSessionConfig;
@@ -36,6 +37,7 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.InetSocketAddress;
+import java.net.Socket;
public class SocketTransportConnection implements ITransportConnection
{
@@ -44,6 +46,8 @@ public class SocketTransportConnection implements ITransportConnection
private SocketConnectorFactory _socketConnectorFactory;
+ private Socket _openSocket;
+
static interface SocketConnectorFactory
{
IoConnector newSocketConnector();
@@ -54,6 +58,11 @@ public class SocketTransportConnection implements ITransportConnection
_socketConnectorFactory = socketConnectorFactory;
}
+ public void setOpenSocket(Socket openSocket)
+ {
+ _openSocket = openSocket;
+ }
+
public void connect(AMQProtocolHandler protocolHandler, BrokerDetails brokerDetail) throws IOException
{
ByteBuffer.setUseDirectBuffers(Boolean.getBoolean("amqj.enableDirectBuffers"));
@@ -83,8 +92,31 @@ public class SocketTransportConnection implements ITransportConnection
_logger.info("send-buffer-size = " + scfg.getSendBufferSize());
scfg.setReceiveBufferSize(Integer.getInteger("amqj.receiveBufferSize", DEFAULT_BUFFER_SIZE));
_logger.info("recv-buffer-size = " + scfg.getReceiveBufferSize());
- final InetSocketAddress address = new InetSocketAddress(brokerDetail.getHost(), brokerDetail.getPort());
- _logger.info("Attempting connection to " + address);
+
+ final InetSocketAddress address;
+
+ if (brokerDetail.getTransport().equals(BrokerDetails.SOCKET))
+ {
+ address = null;
+
+ if (_openSocket != null)
+ {
+ _logger.info("Using existing Socket:" + _openSocket);
+ ((ExistingSocketConnector) ioConnector).setOpenSocket(_openSocket);
+ }
+ else
+ {
+ throw new IllegalArgumentException("Active Socket must be provided for broker " +
+ "with 'socket' transport:" + brokerDetail);
+ }
+ }
+ else
+ {
+ address = new InetSocketAddress(brokerDetail.getHost(), brokerDetail.getPort());
+ _logger.info("Attempting connection to " + address);
+ }
+
+
ConnectFuture future = ioConnector.connect(address, protocolHandler);
// wait for connection to complete
diff --git a/java/client/src/main/java/org/apache/qpid/client/transport/TransportConnection.java b/java/client/src/main/java/org/apache/qpid/client/transport/TransportConnection.java
index c1116ca01e..94361fccfc 100644
--- a/java/client/src/main/java/org/apache/qpid/client/transport/TransportConnection.java
+++ b/java/client/src/main/java/org/apache/qpid/client/transport/TransportConnection.java
@@ -23,6 +23,7 @@ package org.apache.qpid.client.transport;
import org.apache.mina.common.IoConnector;
import org.apache.mina.common.IoHandlerAdapter;
import org.apache.mina.common.IoServiceConfig;
+import org.apache.mina.transport.socket.nio.ExistingSocketConnector;
import org.apache.mina.transport.socket.nio.MultiThreadSocketConnector;
import org.apache.mina.transport.socket.nio.SocketConnector;
import org.apache.mina.transport.vmpipe.VmPipeAcceptor;
@@ -54,6 +55,7 @@ public class TransportConnection
private static final int TCP = 0;
private static final int VM = 1;
+ private static final int SOCKET = 2;
private static Logger _logger = LoggerFactory.getLogger(TransportConnection.class);
@@ -87,7 +89,15 @@ public class TransportConnection
switch (transport)
{
-
+ case SOCKET:
+ _instance = new SocketTransportConnection(new SocketTransportConnection.SocketConnectorFactory()
+ {
+ public IoConnector newSocketConnector()
+ {
+ return new ExistingSocketConnector();
+ }
+ });
+ break;
case TCP:
_instance = new SocketTransportConnection(new SocketTransportConnection.SocketConnectorFactory()
{
@@ -127,6 +137,11 @@ public class TransportConnection
private static int getTransport(String transport)
{
+ if (transport.equals(BrokerDetails.SOCKET))
+ {
+ return SOCKET;
+ }
+
if (transport.equals(BrokerDetails.TCP))
{
return TCP;
diff --git a/java/client/src/main/java/org/apache/qpid/jms/BrokerDetails.java b/java/client/src/main/java/org/apache/qpid/jms/BrokerDetails.java
index 603b0834a3..8b353a7264 100644
--- a/java/client/src/main/java/org/apache/qpid/jms/BrokerDetails.java
+++ b/java/client/src/main/java/org/apache/qpid/jms/BrokerDetails.java
@@ -34,6 +34,7 @@ public interface BrokerDetails
public static final String OPTIONS_CONNECT_DELAY = "connectdelay";
public static final int DEFAULT_PORT = 5672;
+ public static final String SOCKET = "socket";
public static final String TCP = "tcp";
public static final String VM = "vm";
diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/client/connectionurl/ConnectionURLTest.java b/java/client/src/test/java/org/apache/qpid/test/unit/client/connectionurl/ConnectionURLTest.java
index 6c872a0e10..978ce34d59 100644
--- a/java/client/src/test/java/org/apache/qpid/test/unit/client/connectionurl/ConnectionURLTest.java
+++ b/java/client/src/test/java/org/apache/qpid/test/unit/client/connectionurl/ConnectionURLTest.java
@@ -510,6 +510,23 @@ public class ConnectionURLTest extends TestCase
}
+ public void testSocketProtocol() throws URLSyntaxException
+ {
+ String url = "amqp://guest:guest@id/test" + "?brokerlist='socket:///'";
+
+ try
+ {
+ AMQConnectionURL curl = new AMQConnectionURL(url);
+ assertNotNull(curl);
+ assertEquals(1, curl.getBrokerCount());
+ assertNotNull(curl.getBrokerDetails(0));
+ assertEquals("socket", curl.getBrokerDetails(0).getTransport());
+ }
+ catch (URLSyntaxException e)
+ {
+ fail(e.getMessage());
+ }
+ }
public static junit.framework.Test suite()
{
--
cgit v1.2.1
From 1df04df23ddda53cda350ddaeec692cb2f7cbed8 Mon Sep 17 00:00:00 2001
From: Robert Godfrey
Date: Tue, 12 Feb 2008 16:44:59 +0000
Subject: QPID-787 : Allow for quoting of identifiers in selectors
git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/branches/M2.1@620858 13f79535-47bb-0310-9956-ffa450edef68
---
java/broker/src/main/grammar/SelectorParser.jj | 17 +++++++++++++++++
.../org/apache/qpid/test/unit/basic/SelectorTest.java | 3 ++-
2 files changed, 19 insertions(+), 1 deletion(-)
(limited to 'java')
diff --git a/java/broker/src/main/grammar/SelectorParser.jj b/java/broker/src/main/grammar/SelectorParser.jj
index adec1b348d..f6a843e080 100644
--- a/java/broker/src/main/grammar/SelectorParser.jj
+++ b/java/broker/src/main/grammar/SelectorParser.jj
@@ -172,6 +172,7 @@ TOKEN [IGNORE_CASE] :
TOKEN [IGNORE_CASE] :
{
< ID : ["a"-"z", "_", "$"] (["a"-"z","0"-"9","_", "$"])* >
+ | < QUOTED_ID : "\"" ( ("\"\"") | ~["\""] )* "\"" >
}
// ----------------------------------------------------------------------------
@@ -589,6 +590,7 @@ String stringLitteral() :
PropertyExpression variable() :
{
Token t;
+ StringBuffer rc = new StringBuffer();
PropertyExpression left=null;
}
{
@@ -597,6 +599,21 @@ PropertyExpression variable() :
{
left = new PropertyExpression(t.image);
}
+ |
+ t =
+ {
+ // Decode the sting value.
+ String image = t.image;
+ for( int i=1; i < image.length()-1; i++ ) {
+ char c = image.charAt(i);
+ if( c == '"' )
+ i++;
+ rc.append(c);
+ }
+ return new PropertyExpression(rc.toString());
+ }
+
+
)
{
return left;
diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/basic/SelectorTest.java b/java/client/src/test/java/org/apache/qpid/test/unit/basic/SelectorTest.java
index 40c712c1c9..e0bfc5d498 100644
--- a/java/client/src/test/java/org/apache/qpid/test/unit/basic/SelectorTest.java
+++ b/java/client/src/test/java/org/apache/qpid/test/unit/basic/SelectorTest.java
@@ -72,7 +72,7 @@ public class SelectorTest extends TestCase implements MessageListener
connection.start();
String selector = null;
- // selector = "Cost = 2 AND JMSDeliveryMode=" + DeliveryMode.NON_PERSISTENT;
+ selector = "Cost = 2 AND \"property-with-hyphen\" = 'wibble'";
// selector = "JMSType = Special AND Cost = 2 AND AMQMessageID > 0 AND JMSDeliveryMode=" + DeliveryMode.NON_PERSISTENT;
_session = (AMQSession) connection.createSession(false, AMQSession.NO_ACKNOWLEDGE);
@@ -87,6 +87,7 @@ public class SelectorTest extends TestCase implements MessageListener
Message msg = _session.createTextMessage("Message");
msg.setJMSPriority(1);
msg.setIntProperty("Cost", 2);
+ msg.setStringProperty("property-with-hyphen","wibble");
msg.setJMSType("Special");
_logger.info("Sending Message:" + msg);
--
cgit v1.2.1
From 031b02ced99d04d103c02575913ea238e48221ac Mon Sep 17 00:00:00 2001
From: Martin Ritchie
Date: Tue, 12 Feb 2008 17:36:07 +0000
Subject: QPID-784 : Added ability to provide existing Socket to Qpid Client
Libraries to use as for connection. Modified based on review by Robert
Godfrey due to Thread safety around SocketTransportConnection.java and
TransportConnection.java. Now use a safe Map to store all registered sockets
in the TransportConnection.java these are then removed as used or on request.
git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/branches/M2.1@620876 13f79535-47bb-0310-9956-ffa450edef68
---
.../transport/ExistingSocketConnectorDemo.java | 16 +++++--
.../org/apache/qpid/client/AMQBrokerDetails.java | 14 ++++--
.../java/org/apache/qpid/client/AMQConnection.java | 50 +++-------------------
.../transport/SocketTransportConnection.java | 20 ++++-----
.../qpid/client/transport/TransportConnection.java | 17 +++++++-
5 files changed, 54 insertions(+), 63 deletions(-)
(limited to 'java')
diff --git a/java/client/example/src/main/java/org/apache/qpid/example/transport/ExistingSocketConnectorDemo.java b/java/client/example/src/main/java/org/apache/qpid/example/transport/ExistingSocketConnectorDemo.java
index 0979c9c6b8..d7eb138523 100644
--- a/java/client/example/src/main/java/org/apache/qpid/example/transport/ExistingSocketConnectorDemo.java
+++ b/java/client/example/src/main/java/org/apache/qpid/example/transport/ExistingSocketConnectorDemo.java
@@ -23,6 +23,7 @@ package org.apache.qpid.example.transport;
import org.apache.qpid.AMQException;
import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.transport.TransportConnection;
import org.apache.qpid.jms.ConnectionListener;
import org.apache.qpid.url.URLSyntaxException;
@@ -36,6 +37,7 @@ import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.channels.SocketChannel;
+import java.util.UUID;
/**
* This is a simple application that demonstrates how you can use the Qpid AMQP interfaces to use existing sockets as
@@ -66,9 +68,14 @@ public class ExistingSocketConnectorDemo implements ConnectionListener
MessageProducer _producer;
Session _session;
+ String Socket1_ID = UUID.randomUUID().toString();
+ String Socket2_ID = UUID.randomUUID().toString();
+
+
/** Here we can see the broker we are connecting to is set to be 'socket:///' signifying we will provide the socket. */
- public static final String CONNECTION = "amqp://guest:guest@id/test?brokerlist='socket:///'";
+ public final String CONNECTION = "amqp://guest:guest@id/test?brokerlist='socket://" + Socket1_ID + ";socket://" + Socket2_ID + "'";
+
public ExistingSocketConnectorDemo() throws IOException, URLSyntaxException, AMQException, JMSException
{
@@ -76,7 +83,10 @@ public class ExistingSocketConnectorDemo implements ConnectionListener
Socket socket = SocketChannel.open().socket();
socket.connect(new InetSocketAddress("localhost", 5672));
- _connection = new AMQConnection(CONNECTION, socket);
+ TransportConnection.registerOpenSocket(Socket1_ID, socket);
+
+
+ _connection = new AMQConnection(CONNECTION);
_session = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
@@ -130,7 +140,7 @@ public class ExistingSocketConnectorDemo implements ConnectionListener
socket.connect(new InetSocketAddress("localhost", 5673));
// This is the new method to pass in an open socket for the connection to use.
- ((AMQConnection) _connection).setOpenSocket(socket);
+ TransportConnection.registerOpenSocket(Socket2_ID, socket);
}
catch (IOException e)
{
diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQBrokerDetails.java b/java/client/src/main/java/org/apache/qpid/client/AMQBrokerDetails.java
index 89ce4b2c72..572ea48f85 100644
--- a/java/client/src/main/java/org/apache/qpid/client/AMQBrokerDetails.java
+++ b/java/client/src/main/java/org/apache/qpid/client/AMQBrokerDetails.java
@@ -157,7 +157,10 @@ public class AMQBrokerDetails implements BrokerDetails
}
else
{
- setPort(port);
+ if (!_transport.equalsIgnoreCase(SOCKET))
+ {
+ setPort(port);
+ }
}
String queryString = connection.getQuery();
@@ -264,13 +267,16 @@ public class AMQBrokerDetails implements BrokerDetails
sb.append(_transport);
sb.append("://");
- if (!(_transport.equalsIgnoreCase("vm")))
+ if (!(_transport.equalsIgnoreCase(VM)))
{
sb.append(_host);
}
- sb.append(':');
- sb.append(_port);
+ if (!(_transport.equalsIgnoreCase(SOCKET)))
+ {
+ sb.append(':');
+ sb.append(_port);
+ }
sb.append(printOptionsURL());
diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQConnection.java b/java/client/src/main/java/org/apache/qpid/client/AMQConnection.java
index acbe495550..c9928a084e 100644
--- a/java/client/src/main/java/org/apache/qpid/client/AMQConnection.java
+++ b/java/client/src/main/java/org/apache/qpid/client/AMQConnection.java
@@ -30,8 +30,6 @@ import org.apache.qpid.client.failover.FailoverRetrySupport;
import org.apache.qpid.client.protocol.AMQProtocolHandler;
import org.apache.qpid.client.state.AMQState;
import org.apache.qpid.client.state.AMQStateManager;
-import org.apache.qpid.client.transport.ITransportConnection;
-import org.apache.qpid.client.transport.SocketTransportConnection;
import org.apache.qpid.client.transport.TransportConnection;
import org.apache.qpid.exchange.ExchangeDefaults;
import org.apache.qpid.framing.*;
@@ -64,7 +62,6 @@ import javax.naming.Referenceable;
import javax.naming.StringRefAddr;
import java.io.IOException;
import java.net.ConnectException;
-import java.net.Socket;
import java.nio.channels.UnresolvedAddressException;
import java.text.MessageFormat;
import java.util.*;
@@ -160,8 +157,6 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect
private static final long DEFAULT_TIMEOUT = 1000 * 30;
private ProtocolVersion _protocolVersion;
- /** The active socket that is to be used as a value for connection */
- private Socket _openSocket;
/**
* @param broker brokerdetails
@@ -179,7 +174,7 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect
this(new AMQConnectionURL(
ConnectionURL.AMQ_PROTOCOL + "://" + username + ":" + password + "@"
+ ((clientName == null) ? "" : clientName) + "/" + virtualHost + "?brokerlist='"
- + AMQBrokerDetails.checkTransport(broker) + "'"), null, null);
+ + AMQBrokerDetails.checkTransport(broker) + "'"), null);
}
/**
@@ -198,7 +193,7 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect
this(new AMQConnectionURL(
ConnectionURL.AMQ_PROTOCOL + "://" + username + ":" + password + "@"
+ ((clientName == null) ? "" : clientName) + "/" + virtualHost + "?brokerlist='"
- + AMQBrokerDetails.checkTransport(broker) + "'"), sslConfig, null);
+ + AMQBrokerDetails.checkTransport(broker) + "'"), sslConfig);
}
public AMQConnection(String host, int port, String username, String password, String clientName, String virtualHost)
@@ -223,38 +218,26 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect
+ "'" + "," + ConnectionURL.OPTIONS_SSL + "='true'")
: (ConnectionURL.AMQ_PROTOCOL + "://" + username + ":" + password + "@"
+ ((clientName == null) ? "" : clientName) + virtualHost + "?brokerlist='tcp://" + host + ":" + port
- + "'" + "," + ConnectionURL.OPTIONS_SSL + "='false'")), sslConfig, null);
- }
-
- public AMQConnection(String connection, Socket socket) throws AMQException, URLSyntaxException
- {
- this(new AMQConnectionURL(connection), null, socket);
+ + "'" + "," + ConnectionURL.OPTIONS_SSL + "='false'")), sslConfig);
}
public AMQConnection(String connection) throws AMQException, URLSyntaxException
{
- this(new AMQConnectionURL(connection), null, null);
+ this(new AMQConnectionURL(connection), null);
}
public AMQConnection(String connection, SSLConfiguration sslConfig) throws AMQException, URLSyntaxException
{
- this(new AMQConnectionURL(connection), sslConfig, null);
+ this(new AMQConnectionURL(connection), sslConfig);
}
public AMQConnection(ConnectionURL connectionURL, SSLConfiguration sslConfig) throws AMQException
- {
- this(connectionURL, sslConfig, null);
- }
-
- public AMQConnection(ConnectionURL connectionURL, SSLConfiguration sslConfig, Socket socket) throws AMQException
{
if (_logger.isInfoEnabled())
{
_logger.info("Connection:" + connectionURL);
}
- _openSocket = socket;
-
_sslConfiguration = sslConfig;
if (connectionURL == null)
{
@@ -414,23 +397,7 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect
try
{
- ITransportConnection connection = TransportConnection.getInstance(brokerDetail);
-
- if (brokerDetail.getTransport().equals(BrokerDetails.SOCKET))
- {
- if (_openSocket != null)
- {
- ((SocketTransportConnection) connection).setOpenSocket(_openSocket);
- }
- else
- {
- throw new IllegalArgumentException("Active Socket must be provided for broker " +
- "with 'socket' transport:" + brokerDetail);
- }
-
- }
-
- connection.connect(_protocolHandler, brokerDetail);
+ TransportConnection.getInstance(brokerDetail).connect(_protocolHandler, brokerDetail);
// this blocks until the connection has been set up or when an error
// has prevented the connection being set up
@@ -1327,11 +1294,6 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect
return _sessions.get(channelId);
}
- public void setOpenSocket(Socket socket)
- {
- _openSocket = socket;
- }
-
public ProtocolVersion getProtocolVersion()
{
return _protocolVersion;
diff --git a/java/client/src/main/java/org/apache/qpid/client/transport/SocketTransportConnection.java b/java/client/src/main/java/org/apache/qpid/client/transport/SocketTransportConnection.java
index e9d6242e77..b2f7ae8395 100644
--- a/java/client/src/main/java/org/apache/qpid/client/transport/SocketTransportConnection.java
+++ b/java/client/src/main/java/org/apache/qpid/client/transport/SocketTransportConnection.java
@@ -38,6 +38,8 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
public class SocketTransportConnection implements ITransportConnection
{
@@ -46,8 +48,6 @@ public class SocketTransportConnection implements ITransportConnection
private SocketConnectorFactory _socketConnectorFactory;
- private Socket _openSocket;
-
static interface SocketConnectorFactory
{
IoConnector newSocketConnector();
@@ -58,11 +58,6 @@ public class SocketTransportConnection implements ITransportConnection
_socketConnectorFactory = socketConnectorFactory;
}
- public void setOpenSocket(Socket openSocket)
- {
- _openSocket = openSocket;
- }
-
public void connect(AMQProtocolHandler protocolHandler, BrokerDetails brokerDetail) throws IOException
{
ByteBuffer.setUseDirectBuffers(Boolean.getBoolean("amqj.enableDirectBuffers"));
@@ -99,15 +94,18 @@ public class SocketTransportConnection implements ITransportConnection
{
address = null;
- if (_openSocket != null)
+ Socket socket = TransportConnection.removeOpenSocket(brokerDetail.getHost());
+
+ if (socket != null)
{
- _logger.info("Using existing Socket:" + _openSocket);
- ((ExistingSocketConnector) ioConnector).setOpenSocket(_openSocket);
+ _logger.info("Using existing Socket:" + socket);
+
+ ((ExistingSocketConnector) ioConnector).setOpenSocket(socket);
}
else
{
throw new IllegalArgumentException("Active Socket must be provided for broker " +
- "with 'socket' transport:" + brokerDetail);
+ "with 'socket://' transport:" + brokerDetail);
}
}
else
diff --git a/java/client/src/main/java/org/apache/qpid/client/transport/TransportConnection.java b/java/client/src/main/java/org/apache/qpid/client/transport/TransportConnection.java
index 94361fccfc..0ea04e5bc3 100644
--- a/java/client/src/main/java/org/apache/qpid/client/transport/TransportConnection.java
+++ b/java/client/src/main/java/org/apache/qpid/client/transport/TransportConnection.java
@@ -37,6 +37,9 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.net.Socket;
+
/**
* The TransportConnection is a helper class responsible for connecting to an AMQ server. It sets up the underlying
@@ -61,6 +64,18 @@ public class TransportConnection
private static final String DEFAULT_QPID_SERVER = "org.apache.qpid.server.protocol.AMQPFastProtocolHandler";
+ private static Map _openSocketRegister = new ConcurrentHashMap();
+
+ public static void registerOpenSocket(String socketID, Socket openSocket)
+ {
+ _openSocketRegister.put(socketID, openSocket);
+ }
+
+ public static Socket removeOpenSocket(String socketID)
+ {
+ return _openSocketRegister.remove(socketID);
+ }
+
public static ITransportConnection getInstance(BrokerDetails details) throws AMQTransportConnectionException
{
int transport = getTransport(details.getTransport());
@@ -305,7 +320,7 @@ public class TransportConnection
synchronized (_inVmPipeAddress)
{
_inVmPipeAddress.clear();
- }
+ }
_acceptor = null;
_currentInstance = -1;
_currentVMPort = -1;
--
cgit v1.2.1
From 855aa3ce41f07c28071cebde64df12349bdcec51 Mon Sep 17 00:00:00 2001
From: Martin Ritchie
Date: Wed, 13 Feb 2008 11:00:13 +0000
Subject: QPID-788 : Changed MAXIMUM_STATE_WAIT_TIME to pickup value via
-Damqj.MaximumStateWait=90000 or default to 30000.
git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/branches/M2.1@627354 13f79535-47bb-0310-9956-ffa450edef68
---
.../src/main/java/org/apache/qpid/client/state/AMQStateManager.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'java')
diff --git a/java/client/src/main/java/org/apache/qpid/client/state/AMQStateManager.java b/java/client/src/main/java/org/apache/qpid/client/state/AMQStateManager.java
index b6baefe1b0..e62fce0f60 100644
--- a/java/client/src/main/java/org/apache/qpid/client/state/AMQStateManager.java
+++ b/java/client/src/main/java/org/apache/qpid/client/state/AMQStateManager.java
@@ -54,7 +54,7 @@ public class AMQStateManager implements AMQMethodListener
private final CopyOnWriteArraySet _stateListeners = new CopyOnWriteArraySet();
private final Object _stateLock = new Object();
- private static final long MAXIMUM_STATE_WAIT_TIME = 30000L;
+ private static final long MAXIMUM_STATE_WAIT_TIME = Long.parseLong(System.getProperty("amqj.MaximumStateWait", "30000"));
public AMQStateManager()
{
--
cgit v1.2.1
From 62ef6db190b842c39a7101a0d108c28554171b1b Mon Sep 17 00:00:00 2001
From: Robert Godfrey
Date: Wed, 13 Feb 2008 14:01:26 +0000
Subject: QPID-789 : FieldTable putDataInBuffer method not thread safe
git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/branches/M2.1@627416 13f79535-47bb-0310-9956-ffa450edef68
---
java/common/src/main/java/org/apache/qpid/framing/FieldTable.java | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
(limited to 'java')
diff --git a/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java b/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java
index 55968ffe19..7f851ab264 100644
--- a/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java
+++ b/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java
@@ -898,13 +898,15 @@ public class FieldTable
if (_encodedForm != null)
{
- if (_encodedForm.position() != 0)
+ ByteBuffer encodedForm = _encodedForm.duplicate();
+
+ if (encodedForm.position() != 0)
{
- _encodedForm.flip();
+ encodedForm.flip();
}
// _encodedForm.limit((int)getEncodedSize());
- buffer.put(_encodedForm);
+ buffer.put(encodedForm);
}
else if (_properties != null)
{
--
cgit v1.2.1
From 91dfa2865cb9998a379e099ff58e830b4b1ba8a4 Mon Sep 17 00:00:00 2001
From: Robert Godfrey
Date: Wed, 13 Feb 2008 18:10:53 +0000
Subject: QPID-790 : Performance Improvements
git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/branches/M2.1@627552 13f79535-47bb-0310-9956-ffa450edef68
---
.../java/org/apache/qpid/server/AMQChannel.java | 6 ++-
.../qpid/server/exchange/DestWildExchange.java | 2 +-
.../server/handler/BasicPublishMethodHandler.java | 3 +-
.../amqp0_8/ProtocolOutputConverterImpl.java | 18 +++----
.../amqp0_9/ProtocolOutputConverterImpl.java | 63 +++++++++++++---------
.../org/apache/qpid/server/queue/AMQMessage.java | 17 +++++-
.../qpid/server/exchange/DestWildExchangeTest.java | 5 ++
.../qpid/server/queue/AMQQueueAlertTest.java | 5 ++
.../qpid/server/queue/AMQQueueMBeanTest.java | 5 ++
.../apache/qpid/framing/CompositeAMQDataBlock.java | 21 ++++----
.../org/apache/qpid/framing/DeferredDataBlock.java | 50 +++++++++++++++++
.../qpid/framing/SmallCompositeAMQDataBlock.java | 22 ++++----
.../framing/abstraction/MessagePublishInfo.java | 2 +
.../qpid/framing/amqp_0_9/MethodConverter_0_9.java | 9 +++-
.../qpid/framing/amqp_8_0/MethodConverter_8_0.java | 7 ++-
.../java/org/apache/qpid/server/ack/TxAckTest.java | 5 ++
.../java/org/apache/qpid/server/queue/AckTest.java | 5 ++
.../qpid/server/queue/MessageTestHelper.java | 5 ++
.../qpid/server/store/TestReferenceCounting.java | 5 ++
19 files changed, 192 insertions(+), 63 deletions(-)
create mode 100644 java/common/src/main/java/org/apache/qpid/framing/DeferredDataBlock.java
(limited to 'java')
diff --git a/java/broker/src/main/java/org/apache/qpid/server/AMQChannel.java b/java/broker/src/main/java/org/apache/qpid/server/AMQChannel.java
index 1bf0cd027a..10184a79e5 100644
--- a/java/broker/src/main/java/org/apache/qpid/server/AMQChannel.java
+++ b/java/broker/src/main/java/org/apache/qpid/server/AMQChannel.java
@@ -35,6 +35,7 @@ import org.apache.qpid.server.ack.UnacknowledgedMessageMap;
import org.apache.qpid.server.ack.UnacknowledgedMessageMapImpl;
import org.apache.qpid.server.exchange.MessageRouter;
import org.apache.qpid.server.exchange.NoRouteException;
+import org.apache.qpid.server.exchange.Exchange;
import org.apache.qpid.server.protocol.AMQProtocolSession;
import org.apache.qpid.server.queue.*;
import org.apache.qpid.server.store.MessageStore;
@@ -199,11 +200,12 @@ public class AMQChannel
_prefetch_HighWaterMark = prefetchCount;
}
- public void setPublishFrame(MessagePublishInfo info, AMQProtocolSession publisher) throws AMQException
+ public void setPublishFrame(MessagePublishInfo info, AMQProtocolSession publisher, final Exchange e) throws AMQException
{
_currentMessage = new AMQMessage(_messageStore.getNewMessageId(), info, _txnContext);
_currentMessage.setPublisher(publisher);
+ _currentMessage.setExchange(e);
}
public void publishContentHeader(ContentHeaderBody contentHeaderBody, AMQProtocolSession protocolSession)
@@ -285,7 +287,7 @@ public class AMQChannel
{
try
{
- _exchanges.routeContent(_currentMessage);
+ _currentMessage.route();
}
catch (NoRouteException e)
{
diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/DestWildExchange.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/DestWildExchange.java
index 75be86a387..19172b98f3 100644
--- a/java/broker/src/main/java/org/apache/qpid/server/exchange/DestWildExchange.java
+++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/DestWildExchange.java
@@ -239,7 +239,7 @@ public class DestWildExchange extends AbstractExchange
{
MessagePublishInfo info = payload.getMessagePublishInfo();
- final AMQShortString routingKey = normalize(info.getRoutingKey());
+ final AMQShortString routingKey = info.getRoutingKey();
List queues = getMatchedQueues(routingKey);
// if we have no registered queues we have nothing to do
diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/BasicPublishMethodHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicPublishMethodHandler.java
index 66afc61751..687ec33ba0 100644
--- a/java/broker/src/main/java/org/apache/qpid/server/handler/BasicPublishMethodHandler.java
+++ b/java/broker/src/main/java/org/apache/qpid/server/handler/BasicPublishMethodHandler.java
@@ -91,7 +91,8 @@ public class BasicPublishMethodHandler implements StateAwareMethodListener 0;
}
+ public void setExchange(final Exchange exchange)
+ {
+ _exchange = exchange;
+ }
+
+ public void route() throws AMQException
+ {
+ _exchange.route(this);
+ }
+
/**
* Used to iterate through all the body frames associated with this message. Will not keep all the data in memory
* therefore is memory-efficient.
diff --git a/java/broker/src/test/java/org/apache/qpid/server/exchange/DestWildExchangeTest.java b/java/broker/src/test/java/org/apache/qpid/server/exchange/DestWildExchangeTest.java
index 8e5879a51e..7e2d56b460 100644
--- a/java/broker/src/test/java/org/apache/qpid/server/exchange/DestWildExchangeTest.java
+++ b/java/broker/src/test/java/org/apache/qpid/server/exchange/DestWildExchangeTest.java
@@ -589,6 +589,11 @@ public class DestWildExchangeTest extends TestCase
return null;
}
+ public void setExchange(AMQShortString exchange)
+ {
+
+ }
+
public boolean isImmediate()
{
return false;
diff --git a/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueAlertTest.java b/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueAlertTest.java
index 81b0ae2213..fbd9e65480 100644
--- a/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueAlertTest.java
+++ b/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueAlertTest.java
@@ -242,6 +242,11 @@ public class AMQQueueAlertTest extends TestCase
return null;
}
+ public void setExchange(AMQShortString exchange)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
public boolean isImmediate()
{
return immediate;
diff --git a/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueMBeanTest.java b/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueMBeanTest.java
index d86c90bdae..e72e1bf1f0 100644
--- a/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueMBeanTest.java
+++ b/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueMBeanTest.java
@@ -234,6 +234,11 @@ public class AMQQueueMBeanTest extends TestCase
return null;
}
+ public void setExchange(AMQShortString exchange)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
public boolean isImmediate()
{
return immediate;
diff --git a/java/common/src/main/java/org/apache/qpid/framing/CompositeAMQDataBlock.java b/java/common/src/main/java/org/apache/qpid/framing/CompositeAMQDataBlock.java
index 5ec62ede93..7b6699b783 100644
--- a/java/common/src/main/java/org/apache/qpid/framing/CompositeAMQDataBlock.java
+++ b/java/common/src/main/java/org/apache/qpid/framing/CompositeAMQDataBlock.java
@@ -24,7 +24,7 @@ import org.apache.mina.common.ByteBuffer;
public class CompositeAMQDataBlock extends AMQDataBlock implements EncodableAMQDataBlock
{
- private ByteBuffer _encodedBlock;
+ private AMQDataBlock _firstFrame;
private AMQDataBlock[] _blocks;
@@ -39,10 +39,10 @@ public class CompositeAMQDataBlock extends AMQDataBlock implements EncodableAMQD
* @param encodedBlock already-encoded data
* @param blocks some blocks to be encoded.
*/
- public CompositeAMQDataBlock(ByteBuffer encodedBlock, AMQDataBlock[] blocks)
+ public CompositeAMQDataBlock(AMQDataBlock encodedBlock, AMQDataBlock[] blocks)
{
this(blocks);
- _encodedBlock = encodedBlock;
+ _firstFrame = encodedBlock;
}
public AMQDataBlock[] getBlocks()
@@ -50,9 +50,9 @@ public class CompositeAMQDataBlock extends AMQDataBlock implements EncodableAMQD
return _blocks;
}
- public ByteBuffer getEncodedBlock()
+ public AMQDataBlock getFirstFrame()
{
- return _encodedBlock;
+ return _firstFrame;
}
public long getSize()
@@ -62,19 +62,18 @@ public class CompositeAMQDataBlock extends AMQDataBlock implements EncodableAMQD
{
frameSize += _blocks[i].getSize();
}
- if (_encodedBlock != null)
+ if (_firstFrame != null)
{
- _encodedBlock.rewind();
- frameSize += _encodedBlock.remaining();
+ frameSize += _firstFrame.getSize();
}
return frameSize;
}
public void writePayload(ByteBuffer buffer)
{
- if (_encodedBlock != null)
+ if (_firstFrame != null)
{
- buffer.put(_encodedBlock);
+ _firstFrame.writePayload(buffer);
}
for (int i = 0; i < _blocks.length; i++)
{
@@ -91,7 +90,7 @@ public class CompositeAMQDataBlock extends AMQDataBlock implements EncodableAMQD
else
{
StringBuilder buf = new StringBuilder(this.getClass().getName());
- buf.append("{encodedBlock=").append(_encodedBlock);
+ buf.append("{encodedBlock=").append(_firstFrame);
for (int i = 0 ; i < _blocks.length; i++)
{
buf.append(" ").append(i).append("=[").append(_blocks[i].toString()).append("]");
diff --git a/java/common/src/main/java/org/apache/qpid/framing/DeferredDataBlock.java b/java/common/src/main/java/org/apache/qpid/framing/DeferredDataBlock.java
new file mode 100644
index 0000000000..f6795ff200
--- /dev/null
+++ b/java/common/src/main/java/org/apache/qpid/framing/DeferredDataBlock.java
@@ -0,0 +1,50 @@
+package org.apache.qpid.framing;
+
+import org.apache.mina.common.ByteBuffer;
+
+/*
+*
+* 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 abstract class DeferredDataBlock extends AMQDataBlock
+{
+ private AMQDataBlock _underlyingDataBlock;
+
+
+ public long getSize()
+ {
+ if(_underlyingDataBlock == null)
+ {
+ _underlyingDataBlock = createAMQDataBlock();
+ }
+ return _underlyingDataBlock.getSize();
+ }
+
+ public void writePayload(ByteBuffer buffer)
+ {
+ if(_underlyingDataBlock == null)
+ {
+ _underlyingDataBlock = createAMQDataBlock();
+ }
+ _underlyingDataBlock.writePayload(buffer);
+ }
+
+ abstract protected AMQDataBlock createAMQDataBlock();
+
+}
diff --git a/java/common/src/main/java/org/apache/qpid/framing/SmallCompositeAMQDataBlock.java b/java/common/src/main/java/org/apache/qpid/framing/SmallCompositeAMQDataBlock.java
index 26c048e34a..f8cf3f3011 100644
--- a/java/common/src/main/java/org/apache/qpid/framing/SmallCompositeAMQDataBlock.java
+++ b/java/common/src/main/java/org/apache/qpid/framing/SmallCompositeAMQDataBlock.java
@@ -25,7 +25,7 @@ import org.apache.mina.common.ByteBuffer;
public class SmallCompositeAMQDataBlock extends AMQDataBlock implements EncodableAMQDataBlock
{
- private ByteBuffer _encodedBlock;
+ private AMQDataBlock _firstFrame;
private AMQDataBlock _block;
@@ -40,10 +40,10 @@ public class SmallCompositeAMQDataBlock extends AMQDataBlock implements Encodabl
* @param encodedBlock already-encoded data
* @param block a block to be encoded.
*/
- public SmallCompositeAMQDataBlock(ByteBuffer encodedBlock, AMQDataBlock block)
+ public SmallCompositeAMQDataBlock(AMQDataBlock encodedBlock, AMQDataBlock block)
{
this(block);
- _encodedBlock = encodedBlock;
+ _firstFrame = encodedBlock;
}
public AMQDataBlock getBlock()
@@ -51,28 +51,28 @@ public class SmallCompositeAMQDataBlock extends AMQDataBlock implements Encodabl
return _block;
}
- public ByteBuffer getEncodedBlock()
+ public AMQDataBlock getFirstFrame()
{
- return _encodedBlock;
+ return _firstFrame;
}
public long getSize()
{
long frameSize = _block.getSize();
- if (_encodedBlock != null)
+ if (_firstFrame != null)
{
- _encodedBlock.rewind();
- frameSize += _encodedBlock.remaining();
+
+ frameSize += _firstFrame.getSize();
}
return frameSize;
}
public void writePayload(ByteBuffer buffer)
{
- if (_encodedBlock != null)
+ if (_firstFrame != null)
{
- buffer.put(_encodedBlock);
+ _firstFrame.writePayload(buffer);
}
_block.writePayload(buffer);
@@ -87,7 +87,7 @@ public class SmallCompositeAMQDataBlock extends AMQDataBlock implements Encodabl
else
{
StringBuilder buf = new StringBuilder(this.getClass().getName());
- buf.append("{encodedBlock=").append(_encodedBlock);
+ buf.append("{encodedBlock=").append(_firstFrame);
buf.append(" _block=[").append(_block.toString()).append("]");
diff --git a/java/common/src/main/java/org/apache/qpid/framing/abstraction/MessagePublishInfo.java b/java/common/src/main/java/org/apache/qpid/framing/abstraction/MessagePublishInfo.java
index 706499c1b0..49c28bb06b 100644
--- a/java/common/src/main/java/org/apache/qpid/framing/abstraction/MessagePublishInfo.java
+++ b/java/common/src/main/java/org/apache/qpid/framing/abstraction/MessagePublishInfo.java
@@ -27,6 +27,8 @@ public interface MessagePublishInfo
public AMQShortString getExchange();
+ public void setExchange(AMQShortString exchange);
+
public boolean isImmediate();
public boolean isMandatory();
diff --git a/java/common/src/main/java/org/apache/qpid/framing/amqp_0_9/MethodConverter_0_9.java b/java/common/src/main/java/org/apache/qpid/framing/amqp_0_9/MethodConverter_0_9.java
index de0007c132..d8b6b25b92 100644
--- a/java/common/src/main/java/org/apache/qpid/framing/amqp_0_9/MethodConverter_0_9.java
+++ b/java/common/src/main/java/org/apache/qpid/framing/amqp_0_9/MethodConverter_0_9.java
@@ -67,7 +67,7 @@ public class MethodConverter_0_9 extends AbstractMethodConverter implements Prot
final AMQShortString exchange = publishBody.getExchange();
final AMQShortString routingKey = publishBody.getRoutingKey();
- return new MethodConverter_0_9.MessagePublishInfoImpl(exchange == null ? null : exchange.intern(),
+ return new MethodConverter_0_9.MessagePublishInfoImpl(exchange,
publishBody.getImmediate(),
publishBody.getMandatory(),
routingKey == null ? null : routingKey.intern());
@@ -87,7 +87,7 @@ public class MethodConverter_0_9 extends AbstractMethodConverter implements Prot
private static class MessagePublishInfoImpl implements MessagePublishInfo
{
- private final AMQShortString _exchange;
+ private AMQShortString _exchange;
private final boolean _immediate;
private final boolean _mandatory;
private final AMQShortString _routingKey;
@@ -108,6 +108,11 @@ public class MethodConverter_0_9 extends AbstractMethodConverter implements Prot
return _exchange;
}
+ public void setExchange(AMQShortString exchange)
+ {
+ _exchange = exchange;
+ }
+
public boolean isImmediate()
{
return _immediate;
diff --git a/java/common/src/main/java/org/apache/qpid/framing/amqp_8_0/MethodConverter_8_0.java b/java/common/src/main/java/org/apache/qpid/framing/amqp_8_0/MethodConverter_8_0.java
index 7a13af8a43..b1be49a350 100644
--- a/java/common/src/main/java/org/apache/qpid/framing/amqp_8_0/MethodConverter_8_0.java
+++ b/java/common/src/main/java/org/apache/qpid/framing/amqp_8_0/MethodConverter_8_0.java
@@ -107,7 +107,7 @@ public class MethodConverter_8_0 extends AbstractMethodConverter implements Prot
private static class MessagePublishInfoImpl implements MessagePublishInfo
{
- private final AMQShortString _exchange;
+ private AMQShortString _exchange;
private final boolean _immediate;
private final boolean _mandatory;
private final AMQShortString _routingKey;
@@ -128,6 +128,11 @@ public class MethodConverter_8_0 extends AbstractMethodConverter implements Prot
return _exchange;
}
+ public void setExchange(AMQShortString exchange)
+ {
+ _exchange = exchange;
+ }
+
public boolean isImmediate()
{
return _immediate;
diff --git a/java/systests/src/main/java/org/apache/qpid/server/ack/TxAckTest.java b/java/systests/src/main/java/org/apache/qpid/server/ack/TxAckTest.java
index 10189a8017..42d9cccb4f 100644
--- a/java/systests/src/main/java/org/apache/qpid/server/ack/TxAckTest.java
+++ b/java/systests/src/main/java/org/apache/qpid/server/ack/TxAckTest.java
@@ -117,6 +117,11 @@ public class TxAckTest extends TestCase
return null;
}
+ public void setExchange(AMQShortString exchange)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
public boolean isImmediate()
{
return false;
diff --git a/java/systests/src/main/java/org/apache/qpid/server/queue/AckTest.java b/java/systests/src/main/java/org/apache/qpid/server/queue/AckTest.java
index 790607e268..96be579d2a 100644
--- a/java/systests/src/main/java/org/apache/qpid/server/queue/AckTest.java
+++ b/java/systests/src/main/java/org/apache/qpid/server/queue/AckTest.java
@@ -107,6 +107,11 @@ public class AckTest extends TestCase
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;
diff --git a/java/systests/src/main/java/org/apache/qpid/server/queue/MessageTestHelper.java b/java/systests/src/main/java/org/apache/qpid/server/queue/MessageTestHelper.java
index 812aec6a5d..521bedeccd 100644
--- a/java/systests/src/main/java/org/apache/qpid/server/queue/MessageTestHelper.java
+++ b/java/systests/src/main/java/org/apache/qpid/server/queue/MessageTestHelper.java
@@ -70,6 +70,11 @@ class MessageTestHelper extends TestCase
return null;
}
+ public void setExchange(AMQShortString exchange)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
public boolean isImmediate()
{
return immediate;
diff --git a/java/systests/src/main/java/org/apache/qpid/server/store/TestReferenceCounting.java b/java/systests/src/main/java/org/apache/qpid/server/store/TestReferenceCounting.java
index ab6d9742e4..28cc89353b 100644
--- a/java/systests/src/main/java/org/apache/qpid/server/store/TestReferenceCounting.java
+++ b/java/systests/src/main/java/org/apache/qpid/server/store/TestReferenceCounting.java
@@ -61,6 +61,11 @@ public class TestReferenceCounting extends TestCase
return null;
}
+ public void setExchange(AMQShortString exchange)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
public boolean isImmediate()
{
return false;
--
cgit v1.2.1
From e1f14213a9b4d1159aee92521891fda274912fdb Mon Sep 17 00:00:00 2001
From: Rupert Smith
Date: Thu, 14 Feb 2008 16:01:15 +0000
Subject: QPID-9 : Nested field tables implemented. Also wrote a test that
encodes/decodes one to check it works.
git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/branches/M2.1@627789 13f79535-47bb-0310-9956-ffa450edef68
---
.../main/java/org/apache/qpid/framing/AMQType.java | 262 ++++++++++++++-------
.../org/apache/qpid/framing/AMQTypedValue.java | 32 ++-
.../java/org/apache/qpid/framing/FieldTable.java | 62 ++++-
.../qpid/framing/PropertyFieldTableTest.java | 82 +++++--
4 files changed, 331 insertions(+), 107 deletions(-)
(limited to 'java')
diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQType.java b/java/common/src/main/java/org/apache/qpid/framing/AMQType.java
index 6dda91a488..2c356d072c 100644
--- a/java/common/src/main/java/org/apache/qpid/framing/AMQType.java
+++ b/java/common/src/main/java/org/apache/qpid/framing/AMQType.java
@@ -23,12 +23,24 @@ package org.apache.qpid.framing;
import org.apache.mina.common.ByteBuffer;
import java.math.BigDecimal;
-import java.math.BigInteger;
+/**
+ * AMQType is a type that represents the different possible AMQP field table types. It provides operations for each
+ * of the types to perform tasks such as calculating the size of an instance of the type, converting types between AMQP
+ * and Java native types, and reading and writing instances of AMQP types in binary formats to and from byte buffers.
+ *
+ *
CRC Card
+ *
Responsibilities
Collaborations
+ *
Get the equivalent one byte identifier for a type.
+ *
Calculate the size of an instance of an AMQP parameter type.
{@link EncodingUtils}
+ *
Convert an instance of an AMQP parameter into a compatable Java object tagged with its AMQP type.
+ *
{@link AMQTypedValue}
+ *
Write an instance of an AMQP parameter type to a byte buffer.
{@link EncodingUtils}
+ *
Read an instance of an AMQP parameter from a byte buffer.
{@link EncodingUtils}
+ *
+ */
public enum AMQType
{
- //AMQP FieldTable Wire Types
-
LONG_STRING('S')
{
public int getEncodingSize(Object value)
@@ -36,7 +48,6 @@ public enum AMQType
return EncodingUtils.encodedLongStringLength((String) value);
}
-
public String toNativeValue(Object value)
{
if (value != null)
@@ -58,12 +69,10 @@ public enum AMQType
{
return EncodingUtils.readLongString(buffer);
}
-
},
INTEGER('i')
{
-
public int getEncodingSize(Object value)
{
return EncodingUtils.unsignedIntegerLength();
@@ -89,12 +98,11 @@ public enum AMQType
}
else if ((value instanceof String) || (value == null))
{
- return Long.valueOf((String)value);
+ return Long.valueOf((String) value);
}
else
{
- throw new NumberFormatException("Cannot convert: " + value + "(" +
- value.getClass().getName() + ") to int.");
+ throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName() + ") to int.");
}
}
@@ -111,22 +119,21 @@ public enum AMQType
DECIMAL('D')
{
-
public int getEncodingSize(Object value)
{
- return EncodingUtils.encodedByteLength()+ EncodingUtils.encodedIntegerLength();
+ return EncodingUtils.encodedByteLength() + EncodingUtils.encodedIntegerLength();
}
public Object toNativeValue(Object value)
{
- if(value instanceof BigDecimal)
+ if (value instanceof BigDecimal)
{
return (BigDecimal) value;
}
else
{
- throw new NumberFormatException("Cannot convert: " + value + "(" +
- value.getClass().getName() + ") to BigDecimal.");
+ throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName()
+ + ") to BigDecimal.");
}
}
@@ -150,7 +157,8 @@ public enum AMQType
int unscaled = EncodingUtils.readInteger(buffer);
BigDecimal bd = new BigDecimal(unscaled);
- return bd.setScale(places);
+
+ return bd.setScale(places);
}
},
@@ -163,14 +171,14 @@ public enum AMQType
public Object toNativeValue(Object value)
{
- if(value instanceof Long)
+ if (value instanceof Long)
{
return (Long) value;
}
else
{
- throw new NumberFormatException("Cannot convert: " + value + "(" +
- value.getClass().getName() + ") to timestamp.");
+ throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName()
+ + ") to timestamp.");
}
}
@@ -179,37 +187,97 @@ public enum AMQType
EncodingUtils.writeLong(buffer, (Long) value);
}
-
public Object readValueFromBuffer(ByteBuffer buffer)
{
return EncodingUtils.readLong(buffer);
}
},
+ /**
+ * Implements the field table type. The native value of a field table type will be an instance of
+ * {@link FieldTable}, which itself may contain name/value pairs encoded as {@link AMQTypedValue}s.
+ */
FIELD_TABLE('F')
{
+ /**
+ * Calculates the size of an instance of the type in bytes.
+ *
+ * @param value An instance of the type.
+ *
+ * @return The size of the instance of the type in bytes.
+ */
public int getEncodingSize(Object value)
{
- // TODO : fixme
- throw new UnsupportedOperationException();
+ // Ensure that the value is a FieldTable.
+ if (!(value instanceof FieldTable))
+ {
+ throw new IllegalArgumentException("Value is not a FieldTable.");
+ }
+
+ FieldTable ftValue = (FieldTable) value;
+
+ // Loop over all name/value pairs adding up size of each. FieldTable itself keeps track of its encoded
+ // size as entries are added, so no need to loop over all explicitly.
+ // EncodingUtils calculation of the encoded field table lenth, will include 4 bytes for its 'size' field.
+ return EncodingUtils.encodedFieldTableLength(ftValue);
}
+ /**
+ * Converts an instance of the type to an equivalent Java native representation.
+ *
+ * @param value An instance of the type.
+ *
+ * @return An equivalent Java native representation.
+ */
public Object toNativeValue(Object value)
{
- // TODO : fixme
- throw new UnsupportedOperationException();
+ // Ensure that the value is a FieldTable.
+ if (!(value instanceof FieldTable))
+ {
+ throw new IllegalArgumentException("Value is not a FieldTable.");
+ }
+
+ return (FieldTable) value;
}
+ /**
+ * Writes an instance of the type to a specified byte buffer.
+ *
+ * @param value An instance of the type.
+ * @param buffer The byte buffer to write it to.
+ */
public void writeValueImpl(Object value, ByteBuffer buffer)
{
- // TODO : fixme
- throw new UnsupportedOperationException();
+ // Ensure that the value is a FieldTable.
+ if (!(value instanceof FieldTable))
+ {
+ throw new IllegalArgumentException("Value is not a FieldTable.");
+ }
+
+ FieldTable ftValue = (FieldTable) value;
+
+ // Loop over all name/values writing out into buffer.
+ ftValue.writeToBuffer(buffer);
}
+ /**
+ * Reads an instance of the type from a specified byte buffer.
+ *
+ * @param buffer The byte buffer to write it to.
+ *
+ * @return An instance of the type.
+ */
public Object readValueFromBuffer(ByteBuffer buffer)
{
- // TODO : fixme
- throw new UnsupportedOperationException();
+ try
+ {
+ // Read size of field table then all name/value pairs.
+ return EncodingUtils.readFieldTable(buffer);
+ }
+ catch (AMQFrameDecodingException e)
+ {
+ throw new IllegalArgumentException("Unable to read field table from buffer.", e);
+ }
}
},
@@ -220,7 +288,6 @@ public enum AMQType
return 0;
}
-
public Object toNativeValue(Object value)
{
if (value == null)
@@ -229,14 +296,13 @@ public enum AMQType
}
else
{
- throw new NumberFormatException("Cannot convert: " + value + "(" +
- value.getClass().getName() + ") to null String.");
+ throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName()
+ + ") to null String.");
}
}
public void writeValueImpl(Object value, ByteBuffer buffer)
- {
- }
+ { }
public Object readValueFromBuffer(ByteBuffer buffer)
{
@@ -244,8 +310,6 @@ public enum AMQType
}
},
- // Extended types
-
BINARY('x')
{
public int getEncodingSize(Object value)
@@ -253,21 +317,19 @@ public enum AMQType
return EncodingUtils.encodedLongstrLength((byte[]) value);
}
-
public Object toNativeValue(Object value)
{
- if((value instanceof byte[]) || (value == null))
+ if ((value instanceof byte[]) || (value == null))
{
return value;
}
else
{
- throw new IllegalArgumentException("Value: " + value + " (" + value.getClass().getName() +
- ") cannot be converted to byte[]");
+ throw new IllegalArgumentException("Value: " + value + " (" + value.getClass().getName()
+ + ") cannot be converted to byte[]");
}
}
-
public void writeValueImpl(Object value, ByteBuffer buffer)
{
EncodingUtils.writeLongstr(buffer, (byte[]) value);
@@ -277,7 +339,6 @@ public enum AMQType
{
return EncodingUtils.readLongstr(buffer);
}
-
},
ASCII_STRING('c')
@@ -287,7 +348,6 @@ public enum AMQType
return EncodingUtils.encodedLongStringLength((String) value);
}
-
public String toNativeValue(Object value)
{
if (value != null)
@@ -309,7 +369,6 @@ public enum AMQType
{
return EncodingUtils.readLongString(buffer);
}
-
},
WIDE_STRING('C')
@@ -320,7 +379,6 @@ public enum AMQType
return EncodingUtils.encodedLongStringLength((String) value);
}
-
public String toNativeValue(Object value)
{
if (value != null)
@@ -351,7 +409,6 @@ public enum AMQType
return EncodingUtils.encodedBooleanLength();
}
-
public Object toNativeValue(Object value)
{
if (value instanceof Boolean)
@@ -360,12 +417,12 @@ public enum AMQType
}
else if ((value instanceof String) || (value == null))
{
- return Boolean.valueOf((String)value);
+ return Boolean.valueOf((String) value);
}
else
{
- throw new NumberFormatException("Cannot convert: " + value + "(" +
- value.getClass().getName() + ") to boolean.");
+ throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName()
+ + ") to boolean.");
}
}
@@ -374,7 +431,6 @@ public enum AMQType
EncodingUtils.writeBoolean(buffer, (Boolean) value);
}
-
public Object readValueFromBuffer(ByteBuffer buffer)
{
return EncodingUtils.readBoolean(buffer);
@@ -388,7 +444,6 @@ public enum AMQType
return EncodingUtils.encodedCharLength();
}
-
public Character toNativeValue(Object value)
{
if (value instanceof Character)
@@ -401,8 +456,8 @@ public enum AMQType
}
else
{
- throw new NumberFormatException("Cannot convert: " + value + "(" +
- value.getClass().getName() + ") to char.");
+ throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName()
+ + ") to char.");
}
}
@@ -415,7 +470,6 @@ public enum AMQType
{
return EncodingUtils.readChar(buffer);
}
-
},
BYTE('b')
@@ -425,7 +479,6 @@ public enum AMQType
return EncodingUtils.encodedByteLength();
}
-
public Byte toNativeValue(Object value)
{
if (value instanceof Byte)
@@ -434,12 +487,12 @@ public enum AMQType
}
else if ((value instanceof String) || (value == null))
{
- return Byte.valueOf((String)value);
+ return Byte.valueOf((String) value);
}
else
{
- throw new NumberFormatException("Cannot convert: " + value + "(" +
- value.getClass().getName() + ") to byte.");
+ throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName()
+ + ") to byte.");
}
}
@@ -456,13 +509,11 @@ public enum AMQType
SHORT('s')
{
-
public int getEncodingSize(Object value)
{
return EncodingUtils.encodedShortLength();
}
-
public Short toNativeValue(Object value)
{
if (value instanceof Short)
@@ -475,16 +526,13 @@ public enum AMQType
}
else if ((value instanceof String) || (value == null))
{
- return Short.valueOf((String)value);
+ return Short.valueOf((String) value);
}
-
else
{
- throw new NumberFormatException("Cannot convert: " + value + "(" +
- value.getClass().getName() + ") to short.");
+ throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName()
+ + ") to short.");
}
-
-
}
public void writeValueImpl(Object value, ByteBuffer buffer)
@@ -521,12 +569,11 @@ public enum AMQType
}
else if ((value instanceof String) || (value == null))
{
- return Integer.valueOf((String)value);
+ return Integer.valueOf((String) value);
}
else
{
- throw new NumberFormatException("Cannot convert: " + value + "(" +
- value.getClass().getName() + ") to int.");
+ throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName() + ") to int.");
}
}
@@ -543,7 +590,6 @@ public enum AMQType
LONG('l')
{
-
public int getEncodingSize(Object value)
{
return EncodingUtils.encodedLongLength();
@@ -551,7 +597,7 @@ public enum AMQType
public Object toNativeValue(Object value)
{
- if(value instanceof Long)
+ if (value instanceof Long)
{
return (Long) value;
}
@@ -569,12 +615,12 @@ public enum AMQType
}
else if ((value instanceof String) || (value == null))
{
- return Long.valueOf((String)value);
+ return Long.valueOf((String) value);
}
else
{
- throw new NumberFormatException("Cannot convert: " + value + "(" +
- value.getClass().getName() + ") to long.");
+ throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName()
+ + ") to long.");
}
}
@@ -583,7 +629,6 @@ public enum AMQType
EncodingUtils.writeLong(buffer, (Long) value);
}
-
public Object readValueFromBuffer(ByteBuffer buffer)
{
return EncodingUtils.readLong(buffer);
@@ -597,7 +642,6 @@ public enum AMQType
return EncodingUtils.encodedFloatLength();
}
-
public Float toNativeValue(Object value)
{
if (value instanceof Float)
@@ -606,12 +650,12 @@ public enum AMQType
}
else if ((value instanceof String) || (value == null))
{
- return Float.valueOf((String)value);
+ return Float.valueOf((String) value);
}
else
{
- throw new NumberFormatException("Cannot convert: " + value + "(" +
- value.getClass().getName() + ") to float.");
+ throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName()
+ + ") to float.");
}
}
@@ -628,13 +672,11 @@ public enum AMQType
DOUBLE('d')
{
-
public int getEncodingSize(Object value)
{
return EncodingUtils.encodedDoubleLength();
}
-
public Double toNativeValue(Object value)
{
if (value instanceof Double)
@@ -647,12 +689,12 @@ public enum AMQType
}
else if ((value instanceof String) || (value == null))
{
- return Double.valueOf((String)value);
+ return Double.valueOf((String) value);
}
else
{
- throw new NumberFormatException("Cannot convert: " + value + "(" +
- value.getClass().getName() + ") to double.");
+ throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName()
+ + ") to double.");
}
}
@@ -667,35 +709,87 @@ public enum AMQType
}
};
+ /** Holds the defined one byte identifier for the type. */
private final byte _identifier;
+ /**
+ * Creates an instance of an AMQP type from its defined one byte identifier.
+ *
+ * @param identifier The one byte identifier for the type.
+ */
AMQType(char identifier)
{
_identifier = (byte) identifier;
}
+ /**
+ * Extracts the byte identifier for the typ.
+ *
+ * @return The byte identifier for the typ.
+ */
public final byte identifier()
{
return _identifier;
}
-
+ /**
+ * Calculates the size of an instance of the type in bytes.
+ *
+ * @param value An instance of the type.
+ *
+ * @return The size of the instance of the type in bytes.
+ */
public abstract int getEncodingSize(Object value);
+ /**
+ * Converts an instance of the type to an equivalent Java native representation.
+ *
+ * @param value An instance of the type.
+ *
+ * @return An equivalent Java native representation.
+ */
public abstract Object toNativeValue(Object value);
+ /**
+ * Converts an instance of the type to an equivalent Java native representation, packaged as an
+ * {@link AMQTypedValue} tagged with its AMQP type.
+ *
+ * @param value An instance of the type.
+ *
+ * @return An equivalent Java native representation, tagged with its AMQP type.
+ */
public AMQTypedValue asTypedValue(Object value)
{
return new AMQTypedValue(this, toNativeValue(value));
}
+ /**
+ * Writes an instance of the type to a specified byte buffer, preceded by its one byte identifier. As the type and
+ * value are both written, this provides a fully encoded description of a parameters type and value.
+ *
+ * @param value An instance of the type.
+ * @param buffer The byte buffer to write it to.
+ */
public void writeToBuffer(Object value, ByteBuffer buffer)
{
- buffer.put((byte)identifier());
+ buffer.put(identifier());
writeValueImpl(value, buffer);
}
+ /**
+ * Writes an instance of the type to a specified byte buffer.
+ *
+ * @param value An instance of the type.
+ * @param buffer The byte buffer to write it to.
+ */
abstract void writeValueImpl(Object value, ByteBuffer buffer);
+ /**
+ * Reads an instance of the type from a specified byte buffer.
+ *
+ * @param buffer The byte buffer to write it to.
+ *
+ * @return An instance of the type.
+ */
abstract Object readValueFromBuffer(ByteBuffer buffer);
}
diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQTypedValue.java b/java/common/src/main/java/org/apache/qpid/framing/AMQTypedValue.java
index 7193580884..e5b1fad9a8 100644
--- a/java/common/src/main/java/org/apache/qpid/framing/AMQTypedValue.java
+++ b/java/common/src/main/java/org/apache/qpid/framing/AMQTypedValue.java
@@ -18,23 +18,40 @@
* under the License.
*
*/
-
package org.apache.qpid.framing;
import org.apache.mina.common.ByteBuffer;
+/**
+ * AMQTypedValue combines together a native Java Object value, and an {@link AMQType}, as a fully typed AMQP parameter
+ * value. It provides the ability to read and write fully typed parameters to and from byte buffers. It also provides
+ * the ability to create such parameters from Java native value and a type tag or to extract the native value and type
+ * from one.
+ *
+ *
CRC Card
+ *
Responsibilities
Collaborations
+ *
Create a fully typed AMQP value from a native type and a type tag.
{@link AMQType}
+ *
Create a fully typed AMQP value from a binary representation in a byte buffer.
{@link AMQType}
+ *
Write a fully typed AMQP value to a binary representation in a byte buffer.
{@link AMQType}
+ *
Extract the type from a fully typed AMQP value.
+ *
Extract the value from a fully typed AMQP value.
+ *
+ */
public class AMQTypedValue
{
+ /** The type of the value. */
private final AMQType _type;
- private final Object _value;
+ /** The Java native representation of the AMQP typed value. */
+ private final Object _value;
public AMQTypedValue(AMQType type, Object value)
{
- if(type == null)
+ if (type == null)
{
throw new NullPointerException("Cannot create a typed value with null type");
}
+
_type = type;
_value = type.toNativeValue(value);
}
@@ -42,10 +59,9 @@ public class AMQTypedValue
private AMQTypedValue(AMQType type, ByteBuffer buffer)
{
_type = type;
- _value = type.readValueFromBuffer( buffer );
+ _value = type.readValueFromBuffer(buffer);
}
-
public AMQType getType()
{
return _type;
@@ -56,10 +72,9 @@ public class AMQTypedValue
return _value;
}
-
public void writeToBuffer(ByteBuffer buffer)
{
- _type.writeToBuffer(_value,buffer);
+ _type.writeToBuffer(_value, buffer);
}
public int getEncodingSize()
@@ -70,11 +85,12 @@ public class AMQTypedValue
public static AMQTypedValue readFromBuffer(ByteBuffer buffer)
{
AMQType type = AMQTypeMap.getType(buffer.get());
+
return new AMQTypedValue(type, buffer);
}
public String toString()
{
- return "["+getType()+": "+getValue()+"]";
+ return "[" + getType() + ": " + getValue() + "]";
}
}
diff --git a/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java b/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java
index 7f851ab264..ee6762181d 100644
--- a/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java
+++ b/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java
@@ -18,7 +18,6 @@
* under the License.
*
*/
-
package org.apache.qpid.framing;
import org.apache.mina.common.ByteBuffer;
@@ -360,6 +359,41 @@ public class FieldTable
}
}
+ /**
+ * Extracts a value from the field table that is itself a FieldTable associated with the specified parameter name.
+ *
+ * @param string The name of the parameter to get the associated FieldTable value for.
+ *
+ * @return The associated FieldTable value, or null if the associated value is not of FieldTable type or
+ * not present in the field table at all.
+ */
+ public FieldTable getFieldTable(String string)
+ {
+ return getFieldTable(new AMQShortString(string));
+ }
+
+ /**
+ * Extracts a value from the field table that is itself a FieldTable associated with the specified parameter name.
+ *
+ * @param string The name of the parameter to get the associated FieldTable value for.
+ *
+ * @return The associated FieldTable value, or null if the associated value is not of FieldTable type or
+ * not present in the field table at all.
+ */
+ public FieldTable getFieldTable(AMQShortString string)
+ {
+ AMQTypedValue value = getProperty(string);
+
+ if ((value != null) && (value.getType() == AMQType.FIELD_TABLE))
+ {
+ return (FieldTable) value.getValue();
+ }
+ else
+ {
+ return null;
+ }
+ }
+
public Object getObject(String string)
{
return getObject(new AMQShortString(string));
@@ -568,6 +602,32 @@ public class FieldTable
return setProperty(string, AMQType.VOID.asTypedValue(null));
}
+ /**
+ * Associates a nested field table with the specified parameter name.
+ *
+ * @param string The name of the parameter to store in the table.
+ * @param ftValue The field table value to associate with the parameter name.
+ *
+ * @return The stored value.
+ */
+ public Object setFieldTable(String string, FieldTable ftValue)
+ {
+ return setFieldTable(new AMQShortString(string), ftValue);
+ }
+
+ /**
+ * Associates a nested field table with the specified parameter name.
+ *
+ * @param string The name of the parameter to store in the table.
+ * @param ftValue The field table value to associate with the parameter name.
+ *
+ * @return The stored value.
+ */
+ public Object setFieldTable(AMQShortString string, FieldTable ftValue)
+ {
+ return setProperty(string, AMQType.FIELD_TABLE.asTypedValue(ftValue));
+ }
+
public Object setObject(AMQShortString string, Object object)
{
if (object instanceof Boolean)
diff --git a/java/common/src/test/java/org/apache/qpid/framing/PropertyFieldTableTest.java b/java/common/src/test/java/org/apache/qpid/framing/PropertyFieldTableTest.java
index e63b0df770..007da7423e 100644
--- a/java/common/src/test/java/org/apache/qpid/framing/PropertyFieldTableTest.java
+++ b/java/common/src/test/java/org/apache/qpid/framing/PropertyFieldTableTest.java
@@ -1,21 +1,21 @@
/*
- * 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
+ * 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
*
- * 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.
+ * 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.framing;
@@ -439,6 +439,60 @@ public class PropertyFieldTableTest extends TestCase
Assert.assertEquals("Hello", table1.getString("value"));
}
+ /** Check that a nested field table parameter correctly encodes and decodes to a byte buffer. */
+ public void testNestedFieldTable()
+ {
+ byte[] testBytes = new byte[] { 0, 1, 2, 3, 4, 5 };
+
+ FieldTable outerTable = new FieldTable();
+ FieldTable innerTable = new FieldTable();
+
+ // Put some stuff in the inner table.
+ innerTable.setBoolean("bool", true);
+ innerTable.setByte("byte", Byte.MAX_VALUE);
+ innerTable.setBytes("bytes", testBytes);
+ innerTable.setChar("char", 'c');
+ innerTable.setDouble("double", Double.MAX_VALUE);
+ innerTable.setFloat("float", Float.MAX_VALUE);
+ innerTable.setInteger("int", Integer.MAX_VALUE);
+ innerTable.setLong("long", Long.MAX_VALUE);
+ innerTable.setShort("short", Short.MAX_VALUE);
+ innerTable.setString("string", "hello");
+ innerTable.setString("null-string", null);
+
+ // Put the inner table in the outer one.
+ outerTable.setFieldTable("innerTable", innerTable);
+
+ // Write the outer table into the buffer.
+ final ByteBuffer buffer = ByteBuffer.allocate((int) outerTable.getEncodedSize() + 4);
+ outerTable.writeToBuffer(buffer);
+ buffer.flip();
+
+ // Extract the table back from the buffer again.
+ try
+ {
+ FieldTable extractedOuterTable = EncodingUtils.readFieldTable(buffer);
+
+ FieldTable extractedTable = extractedOuterTable.getFieldTable("innerTable");
+
+ Assert.assertEquals((Boolean) true, extractedTable.getBoolean("bool"));
+ Assert.assertEquals((Byte) Byte.MAX_VALUE, extractedTable.getByte("byte"));
+ assertBytesEqual(testBytes, extractedTable.getBytes("bytes"));
+ Assert.assertEquals((Character) 'c', extractedTable.getCharacter("char"));
+ Assert.assertEquals(Double.MAX_VALUE, extractedTable.getDouble("double"));
+ Assert.assertEquals(Float.MAX_VALUE, extractedTable.getFloat("float"));
+ Assert.assertEquals((Integer) Integer.MAX_VALUE, extractedTable.getInteger("int"));
+ Assert.assertEquals((Long) Long.MAX_VALUE, extractedTable.getLong("long"));
+ Assert.assertEquals((Short) Short.MAX_VALUE, extractedTable.getShort("short"));
+ Assert.assertEquals("hello", extractedTable.getString("string"));
+ Assert.assertEquals(null, extractedTable.getString("null-string"));
+ }
+ catch (AMQFrameDecodingException e)
+ {
+ fail("Failed to decode field table with nested inner table.");
+ }
+ }
+
public void testValues()
{
FieldTable table = new FieldTable();
--
cgit v1.2.1
From d97e876f0ec2bd5251e73f2cdaba6a9809a6ebcc Mon Sep 17 00:00:00 2001
From: Robert Godfrey
Date: Thu, 14 Feb 2008 16:24:11 +0000
Subject: QPID-790 : Performance Improvements
git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/branches/M2.1@627794 13f79535-47bb-0310-9956-ffa450edef68
---
.../java/org/apache/qpid/server/store/TestReferenceCounting.java | 5 +++++
1 file changed, 5 insertions(+)
(limited to 'java')
diff --git a/java/systests/src/main/java/org/apache/qpid/server/store/TestReferenceCounting.java b/java/systests/src/main/java/org/apache/qpid/server/store/TestReferenceCounting.java
index 28cc89353b..374c69fa00 100644
--- a/java/systests/src/main/java/org/apache/qpid/server/store/TestReferenceCounting.java
+++ b/java/systests/src/main/java/org/apache/qpid/server/store/TestReferenceCounting.java
@@ -114,6 +114,11 @@ public class TestReferenceCounting extends TestCase
return null;
}
+ public void setExchange(AMQShortString exchange)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
public boolean isImmediate()
{
return false;
--
cgit v1.2.1
From 45072deb936db16404f3746eeb9dcb74e4ecd3df Mon Sep 17 00:00:00 2001
From: Aidan Skinner
Date: Tue, 19 Feb 2008 16:53:57 +0000
Subject: Qpid-594: make AMQConnection listen for exceptions that are thrown
asynchronously in it's constructor and do something appropriate with them
git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/branches/M2.1@629158 13f79535-47bb-0310-9956-ffa450edef68
---
.../java/org/apache/qpid/client/AMQConnection.java | 41 +++++++++-
.../qpid/client/protocol/AMQProtocolHandler.java | 1 +
.../unit/client/connection/ConnectionTest.java | 16 ++++
.../apache/qpid/AMQConnectionFailureException.java | 87 ++++++++++++----------
4 files changed, 102 insertions(+), 43 deletions(-)
(limited to 'java')
diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQConnection.java b/java/client/src/main/java/org/apache/qpid/client/AMQConnection.java
index c9928a084e..79d92f7705 100644
--- a/java/client/src/main/java/org/apache/qpid/client/AMQConnection.java
+++ b/java/client/src/main/java/org/apache/qpid/client/AMQConnection.java
@@ -39,6 +39,7 @@ import org.apache.qpid.jms.Connection;
import org.apache.qpid.jms.ConnectionListener;
import org.apache.qpid.jms.ConnectionURL;
import org.apache.qpid.jms.FailoverPolicy;
+import org.apache.qpid.protocol.AMQConstant;
import org.apache.qpid.url.URLSyntaxException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -233,6 +234,26 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect
public AMQConnection(ConnectionURL connectionURL, SSLConfiguration sslConfig) throws AMQException
{
+ final ArrayList exceptions = new ArrayList();
+
+ class Listener implements ExceptionListener
+ {
+ public void onException(JMSException e)
+ {
+ exceptions.add(e);
+ }
+ }
+
+ try
+ {
+ setExceptionListener(new Listener());
+ }
+ catch (JMSException e)
+ {
+ // Shouldn't happen
+ throw new AMQException(null, null, e);
+ }
+
if (_logger.isInfoEnabled())
{
_logger.info("Connection:" + connectionURL);
@@ -289,8 +310,6 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect
try
{
makeBrokerConnection(_failoverPolicy.getNextBrokerDetails());
- lastException = null;
- _connected = true;
}
catch (Exception e)
{
@@ -318,7 +337,23 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect
{
String message = null;
- if (lastException != null)
+ if (exceptions.size() > 0)
+ {
+ JMSException e = exceptions.get(exceptions.size() - 1);
+ int code = -1;
+ try
+ {
+ code = new Integer(e.getErrorCode()).intValue();
+ }
+ catch (NumberFormatException nfe)
+ {
+ // Ignore this, we have some error codes and messages swapped around
+ }
+
+ throw new AMQConnectionFailureException(AMQConstant.getConstant(code),
+ e.getMessage(), e);
+ }
+ else if (lastException != null)
{
if (lastException.getCause() != null)
{
diff --git a/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolHandler.java b/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolHandler.java
index 8a1e78d2e0..f70c1faa84 100644
--- a/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolHandler.java
+++ b/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolHandler.java
@@ -361,6 +361,7 @@ public class AMQProtocolHandler extends IoHandlerAdapter
// this will attemp failover
sessionClosed(session);
+ _connection.exceptionReceived(cause);
}
else
{
diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/client/connection/ConnectionTest.java b/java/client/src/test/java/org/apache/qpid/test/unit/client/connection/ConnectionTest.java
index 56394fee27..7103397ad4 100644
--- a/java/client/src/test/java/org/apache/qpid/test/unit/client/connection/ConnectionTest.java
+++ b/java/client/src/test/java/org/apache/qpid/test/unit/client/connection/ConnectionTest.java
@@ -165,6 +165,22 @@ public class ConnectionTest extends TestCase
}
}
+ public void testUnresolvedVirtualHostFailure() throws Exception
+ {
+ try
+ {
+ new AMQConnection("amqp://guest:guest@clientid/rubbishhost?brokerlist='" + _broker + "?retries='0''");
+ fail("Connection should not be established");
+ }
+ catch (AMQException amqe)
+ {
+ if (!(amqe instanceof AMQConnectionFailureException))
+ {
+ fail("Correct exception not thrown. Excpected 'AMQConnectionFailureException' got: " + amqe);
+ }
+ }
+ }
+
public void testClientIdCannotBeChanged() throws Exception
{
Connection connection = new AMQConnection(_broker, "guest", "guest",
diff --git a/java/common/src/main/java/org/apache/qpid/AMQConnectionFailureException.java b/java/common/src/main/java/org/apache/qpid/AMQConnectionFailureException.java
index f78307d16f..eddd225d28 100644
--- a/java/common/src/main/java/org/apache/qpid/AMQConnectionFailureException.java
+++ b/java/common/src/main/java/org/apache/qpid/AMQConnectionFailureException.java
@@ -1,40 +1,47 @@
-/*
- *
- * 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;
-
-/**
- * AMQConnectionFailureException indicates that a connection to a broker could not be formed.
- *
- *
CRC Card
- *
Responsibilities
Collaborations
- *
Represents failure to connect to a broker.
- *
- *
- * @todo Not an AMQP exception as no status code.
- */
-public class AMQConnectionFailureException extends AMQException
-{
- public AMQConnectionFailureException(String message)
- {
- super(message);
- }
-}
+/*
+ *
+ * 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;
+
+import org.apache.qpid.protocol.AMQConstant;
+
+/**
+ * AMQConnectionFailureException indicates that a connection to a broker could not be formed.
+ *
+ *
+ *
+ * @author Rupert Smith
+ *
+ * @noinspection CustomClassloader
+ */
+public class IsolatedClassLoader extends URLClassLoader
+{
+
+ private static final URL[] EMPTY_URL_ARRAY = new URL[0];
+ private ClassLoader parent = ClassLoader.getSystemClassLoader();
+
+ private Set urls = new HashSet();
+
+ private boolean childDelegation = true;
+
+ public IsolatedClassLoader()
+ {
+ super(EMPTY_URL_ARRAY, null);
+ }
+
+ public IsolatedClassLoader(ClassLoader parent, boolean childDelegation)
+ {
+ super(EMPTY_URL_ARRAY, parent);
+
+ this.childDelegation = childDelegation;
+ }
+
+ public IsolatedClassLoader(ClassLoader parent)
+ {
+ super(EMPTY_URL_ARRAY, parent);
+ }
+
+ public void addURL(URL url)
+ {
+ // avoid duplicates
+ if (!urls.contains(url))
+ {
+ super.addURL(url);
+ urls.add(url);
+ }
+ }
+
+ public synchronized Class loadClass(String name) throws ClassNotFoundException
+ {
+ Class c;
+
+ if (childDelegation)
+ {
+ c = findLoadedClass(name);
+
+ ClassNotFoundException ex = null;
+
+ if (c == null)
+ {
+ try
+ {
+ c = findClass(name);
+ }
+ catch (ClassNotFoundException e)
+ {
+ ex = e;
+
+ if (parent != null)
+ {
+ c = parent.loadClass(name);
+ }
+ }
+ }
+
+ if (c == null)
+ {
+ throw ex;
+ }
+ }
+ else
+ {
+ c = super.loadClass(name);
+ }
+
+ return c;
+ }
+}
diff --git a/java/junit-toolkit-maven-plugin/src/main/org/apache/qpid/junit/maven/TKTestRunnerMojo.java b/java/junit-toolkit-maven-plugin/src/main/org/apache/qpid/junit/maven/TKTestRunnerMojo.java
new file mode 100644
index 0000000000..442529b609
--- /dev/null
+++ b/java/junit-toolkit-maven-plugin/src/main/org/apache/qpid/junit/maven/TKTestRunnerMojo.java
@@ -0,0 +1,274 @@
+/*
+ *
+ * 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.junit.maven;
+
+import org.apache.maven.plugin.AbstractMojo;
+
+import java.io.File;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.*;
+
+/**
+ * TKTestRunnerMojo is a JUnit test runner plugin for Maven 2. It is intended to be compatible with the surefire
+ * plugin (though not all features of that are yet implemented), with some extended capabilities.
+ *
+ * This plugin adds the ability to use different JUnit test runners, and to pass arbitrary options to them.
+ *
+ *
CRC Card
+ *
Responsibilities
Collaborations
+ *
+ *
+ * @author Rupert Smith
+ *
+ * @goal tktest
+ * @phase test
+ * @requiresDependencyResolution test
+ */
+public class TKTestRunnerMojo extends AbstractMojo
+{
+ private static final BitSet UNRESERVED = new BitSet(256);
+
+ /**
+ * Set this to 'true' to bypass unit tests entirely. Its use is NOT RECOMMENDED, but quite convenient on occasion.
+ *
+ * @parameter expression="${maven.test.skip}"
+ */
+ private boolean skip;
+
+ /**
+ * The TKTest runner command lines. There are passed directly to the TKTestRunner main method.
+ *
+ * @parameter
+ */
+ private Map commands = new LinkedHashMap();
+
+ /**
+ * The base directory of the project being tested. This can be obtained in your unit test by
+ * System.getProperty("basedir").
+ *
+ * @parameter expression="${basedir}"
+ * @required
+ */
+ private File basedir;
+
+ /**
+ * The directory containing generated classes of the project being tested.
+ *
+ * @parameter expression="${project.build.outputDirectory}"
+ * @required
+ */
+ private File classesDirectory;
+
+ /**
+ * The directory containing generated test classes of the project being tested.
+ *
+ * @parameter expression="${project.build.testOutputDirectory}"
+ * @required
+ */
+ private File testClassesDirectory;
+
+ /**
+ * The classpath elements of the project being tested.
+ *
+ * @parameter expression="${project.testClasspathElements}"
+ * @required
+ * @readonly
+ */
+ private List classpathElements;
+
+ /**
+ * List of System properties to pass to the tests.
+ *
+ * @parameter
+ */
+ private Properties systemProperties;
+
+ /**
+ * Map of of plugin artifacts.
+ *
+ * @parameter expression="${plugin.artifactMap}"
+ * @required
+ * @readonly
+ */
+ private Map pluginArtifactMap;
+
+ /**
+ * Map of of project artifacts.
+ *
+ * @parameter expression="${project.artifactMap}"
+ * @required
+ * @readonly
+ */
+ private Map projectArtifactMap;
+
+ /**
+ * Option to specify the forking mode. Can be "never" (default), "once" or "always".
+ * "none" and "pertest" are also accepted for backwards compatibility.
+ *
+ * @parameter expression="${forkMode}" default-value="once"
+ */
+ private String forkMode;
+
+ /**
+ * Option to specify the jvm (or path to the java executable) to use with
+ * the forking options. For the default we will assume that java is in the path.
+ *
+ * @parameter expression="${jvm}"
+ * default-value="java"
+ */
+ private String jvm;
+
+ /**
+ * The test runner to use.
+ *
+ * @parameter
+ */
+ private String testrunner;
+
+ /**
+ * The additional properties to append to the test runner invocation command line.
+ *
+ * @parameter
+ */
+ private Properties testrunnerproperties;
+
+ /**
+ * The options to pass to all test runner invocation command lines.
+ *
+ * @parameter
+ */
+ private String[] testrunneroptions;
+
+ /**
+ * Implementation of the tktest goal.
+ */
+ public void execute()
+ {
+ // Skip these tests if test skipping is turned on.
+ if (skip)
+ {
+ getLog().info("Skipping Tests.");
+
+ return;
+ }
+
+ // Log out the classpath if debugging is on.
+ if (getLog().isDebugEnabled())
+ {
+ getLog().info("Test Classpath :");
+
+ for (Object classpathElement1 : classpathElements)
+ {
+ String classpathElement = (String) classpathElement1;
+ getLog().info(" " + classpathElement);
+ }
+ }
+
+ try
+ {
+ // Create a class loader to load the test runner with. This also gets set as the context loader for this
+ // thread, so that all subsequent class loading activity by the test runner or the test code, has the
+ // test classes available to it. The system loader is set up for the maven build, which is why a seperate
+ // loader needs to be created; in order to inject the test dependencies into it.
+ ClassLoader runnerClassLoader = createClassLoader(classpathElements, ClassLoader.getSystemClassLoader(), true);
+ Thread.currentThread().setContextClassLoader(runnerClassLoader);
+
+ // Load the test runner implementation that will be used to run the tests.
+ if ((testrunner == null) || "".equals(testrunner))
+ {
+ testrunner = "org.apache.qpid.junit.extensions.TKTestRunner";
+ }
+
+ Class testRunnerClass = Class.forName(testrunner, false, runnerClassLoader);
+ Method run = testRunnerClass.getMethod("main", String[].class);
+
+ // Concatenate all of the options to pass on the command line to the test runner.
+ String preOptions = "";
+
+ for (String option : testrunneroptions)
+ {
+ preOptions += option + " ";
+ }
+
+ // Concatenate all of the additional properties as name=value pairs on the command line.
+ String nvPairs = "";
+
+ if (testrunnerproperties != null)
+ {
+ for (Object objKey : testrunnerproperties.keySet())
+ {
+ String key = (String) objKey;
+ String value = testrunnerproperties.getProperty(key);
+
+ nvPairs = key + "=" + value + " ";
+ }
+ }
+
+ // Pass each of the test runner command lines in turn to the toolkit test runner.
+ // The command line is made up of the options, the command specific command line, then the trailing
+ // name=value pairs.
+ for (String testName : commands.keySet())
+ {
+ String commandLine = preOptions + " " + commands.get(testName) + " " + nvPairs;
+ getLog().info("commandLine = " + commandLine);
+
+ // Tokenize the command line on white space, into an array of string components.
+ String[] tokenizedCommandLine = commandLine.split("\\s+");
+
+ // Run the tests.
+ run.invoke(testRunnerClass, new Object[] { tokenizedCommandLine });
+ }
+ }
+ catch (Exception e)
+ {
+ getLog().error("There was an exception: " + e.getMessage(), e);
+ }
+ }
+
+ private static ClassLoader createClassLoader(List classPathUrls, ClassLoader parent, boolean childDelegation)
+ throws MalformedURLException
+ {
+ List urls = new ArrayList();
+
+ for (Iterator i = classPathUrls.iterator(); i.hasNext();)
+ {
+ String url = (String) i.next();
+
+ if (url != null)
+ {
+ File f = new File(url);
+ urls.add(f.toURL());
+ }
+ }
+
+ IsolatedClassLoader classLoader = new IsolatedClassLoader(parent, childDelegation);
+
+ for (Iterator iter = urls.iterator(); iter.hasNext();)
+ {
+ URL url = (URL) iter.next();
+ classLoader.addURL(url);
+ }
+
+ return classLoader;
+ }
+}
diff --git a/java/junit-toolkit-maven-plugin/src/main/org/apache/qpid/junit/maven/TKTestScriptGenMojo.java b/java/junit-toolkit-maven-plugin/src/main/org/apache/qpid/junit/maven/TKTestScriptGenMojo.java
new file mode 100644
index 0000000000..be665da018
--- /dev/null
+++ b/java/junit-toolkit-maven-plugin/src/main/org/apache/qpid/junit/maven/TKTestScriptGenMojo.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2007 Rupert Smith.
+ *
+ * Licensed 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.junit.maven;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Properties;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+
+/**
+ *
CRC Card
+ *
Responsibilities
Collaborations
+ *
+ *
+ * @author Rupert Smith
+ * @goal tkscriptgen
+ * @phase test
+ * @execute phase="test"
+ * @requiresDependencyResolution test
+ */
+public class TKTestScriptGenMojo extends AbstractMojo
+{
+ private static final String _scriptLanguage = "#!/bin/bash\n\n";
+
+ private static final String _javaOptArgParser =
+ "# Parse arguements taking all - prefixed args as JAVA_OPTS\n" + "for arg in \"$@\"; do\n"
+ + " if [[ $arg == -java:* ]]; then\n" + " JAVA_OPTS=\"${JAVA_OPTS}-`echo $arg|cut -d ':' -f 2` \"\n"
+ + " else\n" + " ARGS=\"${ARGS}$arg \"\n" + " fi\n" + "done\n\n";
+
+ /**
+ * Where to write out the scripts.
+ *
+ * @parameter
+ */
+ private String scriptOutDirectory;
+
+ /**
+ * The all-in-one test jar location.
+ *
+ * @parameter
+ */
+ private String testJar;
+
+ /**
+ * The system properties to pass to java runtime.
+ *
+ * @parameter
+ */
+ private Properties systemproperties;
+
+ /**
+ * The TKTest runner command lines. There are passed directly to the TKTestRunner main method.
+ *
+ * @parameter
+ */
+ private Map commands = new LinkedHashMap();
+
+ /**
+ * Implementation of the tkscriptgen goal.
+ *
+ * @throws MojoExecutionException
+ */
+ public void execute() throws MojoExecutionException
+ {
+ // Turn each of the test runner command lines into a script.
+ for (String testName : commands.keySet())
+ {
+ String testOptions = commands.get(testName);
+ String commandLine = "java ";
+
+ String logdir = null;
+
+ for (Object key : systemproperties.keySet())
+ {
+ String keyString = (String) key;
+ String value = systemproperties.getProperty(keyString);
+
+ if (keyString.equals("logdir"))
+ {
+ logdir = value;
+ }
+ else
+ {
+ if (keyString.startsWith("-X"))
+ {
+ commandLine += keyString + value + " ";
+ }
+ else
+ {
+ commandLine += "-D" + keyString + "=" + value + " ";
+ }
+ }
+ }
+
+ commandLine +=
+ "${JAVA_OPTS} -cp " + testJar + " org.apache.qpid.junit.extensions.TKTestRunner " + testOptions
+ + " ${ARGS}";
+
+ getLog().info("Generating Script for test: " + testName);
+ getLog().debug(commandLine);
+
+ String fileName = scriptOutDirectory + "/" + testName + ".sh";
+
+ try
+ {
+ File scriptFile = new File(fileName);
+ Writer scriptWriter = new FileWriter(scriptFile);
+ scriptWriter.write(_scriptLanguage);
+ scriptWriter.write(_javaOptArgParser);
+ if (logdir != null)
+ {
+ scriptWriter.write("mkdir -p " + logdir + "\n");
+ }
+
+ scriptWriter.write(commandLine);
+ scriptWriter.flush();
+ scriptWriter.close();
+ }
+ catch (IOException e)
+ {
+ getLog().error("Failed to write: " + fileName);
+ }
+ }
+ }
+}
diff --git a/java/junit-toolkit/pom.xml b/java/junit-toolkit/pom.xml
new file mode 100644
index 0000000000..d5f8c24734
--- /dev/null
+++ b/java/junit-toolkit/pom.xml
@@ -0,0 +1,57 @@
+
+ 4.0.0
+
+ org.apache.qpid
+ junit-toolkit
+ junit-toolkit
+ 1.0-incubating-M2.1-SNAPSHOT
+
+ jar
+
+
+ org.apache.qpid
+ qpid
+ 1.0-incubating-M2.1-SNAPSHOT
+ ../pom.xml
+
+
+
+ ..
+
+
+
+
+
+
+ junit
+ junit
+ 3.8.1
+
+
+
+ log4j
+ log4j
+ 1.2.12
+
+
+
+
+
+
+
+ junit
+ junit
+
+
+
+ log4j
+ log4j
+
+
+
+
+ src/main
+ src/unittests
+
+
+
\ No newline at end of file
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/DefaultThreadFactory.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/DefaultThreadFactory.java
new file mode 100644
index 0000000000..6c88d019c4
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/DefaultThreadFactory.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.junit.concurrency;
+
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * Implements a default thread factory.
+ *
+ *
CRC Card
+ *
Responsibilities
Collaborations
+ *
Create default threads with no specialization.
+ *
+ *
+ * @author Rupert Smith
+ */
+public class DefaultThreadFactory implements ThreadFactory
+{
+ /**
+ * Constructs a new Thread.
+ *
+ * @param r A runnable to be executed by new thread instance.
+ *
+ * @return The constructed thread.
+ */
+ public Thread newThread(Runnable r)
+ {
+ return new Thread(r);
+ }
+}
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/PossibleDeadlockException.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/PossibleDeadlockException.java
new file mode 100644
index 0000000000..0bb07a4557
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/PossibleDeadlockException.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.junit.concurrency;
+
+/**
+ * PossibleDeadlockException is used to signal that two test threads being executed by a {@link ThreadTestCoordinator}
+ * may be in a state of deadlock because they are mutually blocking each other or one is waiting on the other and the
+ * other has been blocked elsewhere for longer than a specified timeout.
+ *
+ *
CRC Card
+ *
Responsibilities
Collaborations
+ *
Signal a possible state of deadlock between coordinated test threads.
+ *
+ *
+ * @author Rupert Smith
+ */
+public class PossibleDeadlockException extends RuntimeException
+{
+ /**
+ * Create a new possible deadlock execption.
+ *
+ * @param message The exception message.
+ */
+ public PossibleDeadlockException(String message)
+ {
+ super(message);
+ }
+}
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/TestRunnable.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/TestRunnable.java
new file mode 100644
index 0000000000..5bf0c430cd
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/TestRunnable.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.junit.concurrency;
+
+/**
+ * TestRunnable is an extension of java.util.Runnable that adds some features to make it easier to coordinate the
+ * activities of threads in such a way as to expose bugs in multi threaded code.
+ *
+ * Sometimes several threads will run in a particular order so that a bug is not revealed. Other times the ordering
+ * of the threads will expose a bug. Such bugs can be hard to replicate as the exact execution ordering of threads is not
+ * usually controlled. This class adds some methods that allow threads to synchronize other threads, either allowing them
+ * to run, or waiting for them to allow this thread to run. It also provides convenience methods to gather error messages
+ * and exceptions from threads, which will often be reported in unit testing code.
+ *
+ * Coordination between threads is handled by the {@link ThreadTestCoordinator}. It is called through the convenience
+ * methods {@link #allow} and {@link #waitFor}. Threads to be coordinated must be set up with the coordinator and assigned
+ * integer ids. It is then possible to call the coordinator with an array of thread ids requesting that those threads
+ * be allowed to continue, or to wait until one of them allows this thread to continue. The otherwise non-deterministic
+ * execution order of threads can be controlled into a carefully determined sequence using these methods in order
+ * to reproduce race conditions, dead locks, live locks, dirty reads, phantom reads, non repeatable reads and so on.
+ *
+ * When waiting for another thread to give a signal to continue it is sometimes the case that the other thread has
+ * become blocked by the code under test. For example in testing for a dirty read (for example in database code),
+ * thread 1 lets thread 2 perform a write but not commit it, then thread 2 lets thread 1 run and attempt to perform a
+ * dirty read on its uncommitted write. Transaction synchronization code being tested against the possibility of a dirty
+ * write may make use of snapshots in which case both threads should be able to read and write without blocking. It may
+ * make use of explicit keys in which case thread 2 may become blocked on its write attempt because thread 1 holds a
+ * read lock and it must wait until thread 1 completes its transaction before it can acquire this lock. The
+ * {@link #waitFor} method accepts a boolean parameter to indicate that threads being blocked (other than on the
+ * coordinator) can be interpreted the same as if the thread explicitly allows the thread calling waitFor to continue.
+ * Using this technique a dirty read test could be written that works against either the snapshot or the locking
+ * implementation, allowing both approaches to pass the test yet arranging for multiple threads to run against the
+ * implementation in such a way that a potential dirty read bug is exposed.
+ *
+ *
CRC Card
+ *
Responsibilities
Collaborations
+ *
Wait for another thread to allow this one to continue.
+ *
Allow another thread to continue.
+ *
Accumulate error messages.
+ *
Record exceptions from thread run.
+ *
Maintain link to thread coordinator.
+ *
Explicitly mark a thread with an integer id.
+ *
Maintian a flag to indicate whether or not this thread is waiting on the coordinator.
+ *
+ *
+ * @todo The allow then waitFor operations are very often used as a pair. So create a method allowAndWait that combines
+ * them into a single method call.
+ *
+ * @author Rupert Smith
+ */
+public abstract class TestRunnable implements Runnable
+{
+ /** Holds a reference to the thread coordinator. */
+ private ThreadTestCoordinator coordinator;
+
+ /** Holds the explicit integer id of this thread. */
+ private int id;
+
+ /** Used to indicate that this thread is waiting on the coordinator and not elsewhere. */
+ private boolean waitingOnCoordinator = false;
+
+ /** Used to accumulate error messsages. */
+ private String errorMessage = "";
+
+ /** Holds the Java thread object that this is running under. */
+ private Thread thisThread;
+
+ /** Used to hold any exceptions resulting from the run method. */
+ private Exception runException = null;
+
+ /**
+ * Implementations override this to perform coordinated thread sequencing.
+ *
+ * @throws Exception Any exception raised by the implementation will be caught by the default {@link #run()}
+ * implementation for later querying by the {@link #getException()} method.
+ */
+ public abstract void runWithExceptions() throws Exception;
+
+ /**
+ * Provides a default implementation of the run method that allows exceptions to be thrown and keeps a record
+ * of those exceptions. Defers to the {@link #runWithExceptions()} method to provide the thread body implementation
+ * and catches any exceptions thrown by it.
+ */
+ public void run()
+ {
+ try
+ {
+ runWithExceptions();
+ }
+ catch (Exception e)
+ {
+ this.runException = e;
+ }
+ }
+
+ /**
+ * Attempt to consume an allow event from one of the specified threads and blocks until such an event occurrs.
+ *
+ * @param threads The set of threads that can allow this one to continue.
+ * @param otherWaitIsAllow If set to true if the threads being waited on are blocked other than on
+ * the coordinator itself then this is to be interpreted as allowing this thread to
+ * continue.
+ *
+ * @return If the otherWaitIsAllow flag is set, then true is returned when the thread being waited on is found
+ * to be blocked outside of the thread test coordinator. false under all other conditions.
+ */
+ protected boolean waitFor(int[] threads, boolean otherWaitIsAllow)
+ {
+ return coordinator.consumeAllowEvent(threads, otherWaitIsAllow, id, this);
+ }
+
+ /**
+ * Produces allow events on each of the specified threads.
+ *
+ * @param threads The set of threads that are to be allowed to continue.
+ */
+ protected void allow(int[] threads)
+ {
+ coordinator.produceAllowEvents(threads, id, this);
+ }
+
+ /**
+ * Keeps the error message for later reporting by the coordinator.
+ *
+ * @param message The error message to keep.
+ */
+ protected void addErrorMessage(String message)
+ {
+ errorMessage += message;
+ }
+
+ /**
+ * Sets the coordinator for this thread.
+ *
+ * @param coordinator The coordinator for this thread.
+ */
+ void setCoordinator(ThreadTestCoordinator coordinator)
+ {
+ this.coordinator = coordinator;
+ }
+
+ /**
+ * Reports whether or not this thread is waiting on the coordinator.
+ *
+ * @return If this thread is waiting on the coordinator.
+ */
+ boolean isWaitingOnCoordinator()
+ {
+ return waitingOnCoordinator;
+ }
+
+ /**
+ * Sets the value of the waiting on coordinator flag.
+ *
+ * @param waiting The value of the waiting on coordinator flag.
+ */
+ void setWaitingOnCoordinator(boolean waiting)
+ {
+ waitingOnCoordinator = waiting;
+ }
+
+ /**
+ * Sets up the explicit int id for this thread.
+ *
+ * @param id The integer id.
+ */
+ void setId(int id)
+ {
+ this.id = id;
+ }
+
+ /**
+ * Reports any accumulated error messages.
+ *
+ * @return Any accumulated error messages.
+ */
+ String getErrorMessage()
+ {
+ return errorMessage;
+ }
+
+ /**
+ * Reports any exception thrown by the {@link #runWithExceptions} method.
+ *
+ * @return Any exception thrown by the {@link #runWithExceptions} method.
+ */
+ Exception getException()
+ {
+ return runException;
+ }
+
+ /**
+ * Sets the Java thread under which this runs.
+ *
+ * @param thread The Java thread under which this runs.
+ */
+ void setThread(Thread thread)
+ {
+ thisThread = thread;
+ }
+
+ /**
+ * Gets the Java thread under which this runs.
+ *
+ * @return The Java thread under which this runs.
+ */
+ Thread getThread()
+ {
+ return thisThread;
+ }
+
+ /**
+ * Provides a string summary of this test threads status.
+ *
+ * @return Summarizes this threads status.
+ */
+ public String toString()
+ {
+ return "id = " + id + ", waitingOnCoordinator = " + waitingOnCoordinator;
+ }
+}
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestCoordinator.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestCoordinator.java
new file mode 100644
index 0000000000..0be0fe37dc
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestCoordinator.java
@@ -0,0 +1,485 @@
+/*
+ *
+ * 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.junit.concurrency;
+
+import org.apache.log4j.Logger;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * ThreadTestCoordinator provides an array of binary latches that allows threads to wait for other threads or to send
+ * them a signal that allows them to continue running or to wait for another thread to signal them. The binary latch
+ * array is always a square array, allowing one latch from and to every thread. Upon accepting an allow signal from one
+ * sender the latches for all senders for a are cleared. This class is always used in conjunction with
+ * {@link TestRunnable} for writing concurrent test code that coordinates multi-threaded activity in order to reproduce
+ * concurrency bugs.
+ *
+ *
CRC Card
+ *
Responsibilities
Collaborations
+ *
Accept test threads to coordinate.
+ *
Allow test threads to send 'allow to continue' signals.
+ *
Allow test threads to wait on this coordinator for 'allow to continue' signals.
+ *
Report error messages from test threads.
+ *
Report exceptions from test threads.
+ *
Provide method to wait until all test threads have completed.
+ *
+ *
+ * @todo This code was hacked together as a bit of an experiment, because I wasn't sure if this idea would work. It has
+ * proved extremely usefull. Some documentation for this needs to be written to explain it better.
+ *
+ * @todo Consider how deadlock detection will be handled. If all threads are blocking on the coordinator, waiting for
+ * each other, they are deadlocked and there is something wrong with the test code that put them in that
+ * situation. If they are all blocked elsewhere, they may be deadlocked, or could just be waiting on some
+ * external event. A timeout should be used. Timeout is already implemented, just need to sanity check how
+ * this is working and document it.
+ *
+ * @todo Consider how livelock detection could be implemented? LockFree data structures might cause live locks. I
+ * guess a longish timeout is the only thing that can be done for that.
+ *
+ * @todo Only course grained synchronous at the method class level can be obtained. This is because test code can
+ * only insert synchronization points between method calls it makes. So this code will not be usefull for
+ * checking sequences of events within methods, unless the code under test is explicitly instrumented for it.
+ * It might be possible to instrument code by using labels, and then use the debugger/profiler interface to
+ * put breakpoints on the labels and use them as synchronization points. Not perfect, but at the unused labels
+ * can be left in the code, without altering its behaviour.
+ *
+ * @author Rupert Smith
+ */
+public class ThreadTestCoordinator
+{
+ /** Used for logging. */
+ private static final Logger log = Logger.getLogger(ThreadTestCoordinator.class);
+
+ /** Keeps track of the test threads by their ids. */
+ private TestRunnable[] testThreads; // = new TestRunnable[2];
+
+ /** An explicit thread monitor for the coordinator. Threads wait on the coordinator whilst waiting for events. */
+ private final Object coordinatorLock = new Object();
+
+ /** A set of monitors for each test thread. */
+ private Object[] locks;
+
+ /** The binary latch array, this is always a square array allowing one event from and to every thread. */
+ private boolean[][] allowEvents;
+
+ /** Keeps track of the number of threads being coordinated. */
+ private int threadCount = 0;
+
+ /** Accumulates any exceptions resulting from the threads run methods. */
+ private Collection exceptions = new ArrayList();
+
+ /**
+ * Holds the deadlock timeout after which threads are given a runtime exception to signal that a potential
+ * deadlock may be happening.
+ */
+ private long deadlockTimeout = 1000 * 1000000;
+
+ /** Holds the factory to create test thread with. */
+ private ThreadFactory threadFactory;
+
+ /**
+ * Creates a new test thread coordinator. The number of threads to run must be specified here.
+ *
+ * @param numThreads The number of threads to run.
+ */
+ public ThreadTestCoordinator(int numThreads)
+ {
+ this.threadCount = numThreads;
+
+ // Create an array big enough to hold all the test threads.
+ testThreads = new TestRunnable[threadCount];
+
+ // Use the default thread factory, as none specified.
+ threadFactory = new DefaultThreadFactory();
+ }
+
+ /**
+ * Creates a new test thread coordinator with a specific thread factory. The number of threads to run must be
+ * specified here.
+ *
+ * @param numThreads The number of threads to run.
+ * @param threadFactory The factory to use to create the test threads.
+ */
+ public ThreadTestCoordinator(int numThreads, ThreadFactory threadFactory)
+ {
+ this.threadCount = numThreads;
+
+ // Create an array big enough to hold all the test threads.
+ testThreads = new TestRunnable[threadCount];
+
+ // Use the specified thread factory.
+ this.threadFactory = threadFactory;
+ }
+
+ /**
+ * Adds a thread to this coordinator and assigns an id to it. The ids must be numbered sequentially from 0 and
+ * it is up to the caller to do this.
+ *
+ * @param runnable The test thread.
+ * @param id The explicit id to assign to the test thread.
+ */
+ public void addTestThread(TestRunnable runnable, int id)
+ {
+ testThreads[id] = runnable;
+ runnable.setCoordinator(this);
+ runnable.setId(id);
+ }
+
+ /**
+ * Starts all the coordinated threads running.
+ */
+ public void run()
+ {
+ // Create the monitors for each thread.
+ locks = new Object[threadCount];
+
+ // Create an appropriately sized event queue to allow one event from and to each thread.
+ allowEvents = new boolean[threadCount][threadCount];
+
+ // Initialize the monitors and clear the event queues.
+ for (int i = 0; i < locks.length; i++)
+ {
+ locks[i] = new Object();
+
+ for (int j = 0; j < locks.length; j++)
+ {
+ allowEvents[i][j] = false;
+ }
+ }
+
+ // Start all the threads running.
+ for (TestRunnable nextRunnable : testThreads)
+ {
+ // Create a Java thread for the test thread.
+ Thread newThread = threadFactory.newThread(nextRunnable);
+ nextRunnable.setThread(newThread);
+
+ // Start it running.
+ newThread.start();
+ }
+ }
+
+ /**
+ * Waits until all the test threads have completed and returns any accumulated error messages from them. Any
+ * exceptions thrown by their run methods are also kept at this point.
+ *
+ * @return The accumulated error messages from all the threads concatenated together.
+ */
+ public String joinAndRetrieveMessages()
+ {
+ // Create an empty error message.
+ String errorMessage = "";
+
+ // Join all the test threads.
+ for (TestRunnable r : testThreads)
+ {
+ Thread t = r.getThread();
+
+ try
+ {
+ t.join();
+ }
+ catch (InterruptedException e)
+ { }
+
+ // Add any accumulated error messages to the return value.
+ errorMessage += r.getErrorMessage();
+
+ // Keep any exceptions resulting from the threads run method.
+ Exception e = r.getException();
+
+ if (e != null)
+ {
+ exceptions.add(e);
+ }
+ }
+
+ return errorMessage;
+ }
+
+ /**
+ * Reports any accumulated exceptions from the test threads run methods. This method must be called after
+ * {@link #joinAndRetrieveMessages}.
+ *
+ * @return Any accumulated exceptions from the test threads run methods. This method must be called after
+ */
+ public Collection getExceptions()
+ {
+ return exceptions;
+ }
+
+ /**
+ * Sets a timeout to break out of potential deadlocks. If all threads are waiting for other threads to send
+ * them continue events for longer than this timeout then the threads are all terminated.
+ *
+ * @param millis The minimum time to allow to pass before breaking out of any potential deadlocks.
+ *
+ * @todo This has not been implemented yet. If a potential deadlock happens then the joinAndRetrieveMessages
+ * method should throw a PotentialDeadlockException.
+ */
+ public void setDeadlockTimeout(long millis)
+ {
+ deadlockTimeout = millis * 1000000;
+ }
+
+ /**
+ * Creates a set of 'allow to continue' events on the event queues of the specified threads.
+ *
+ * @param threads The set of threads to allow to continue.
+ * @param callerId The explicit id of the calling test thread.
+ * @param caller The calling test thread.
+ */
+ void produceAllowEvents(int[] threads, int callerId, TestRunnable caller)
+ {
+ // Generate some debugging messages. Very usefull to know how thread synchronization is progressing.
+ String message = "Thread " + callerId + " is allowing threads [ ";
+
+ for (int j = 0; j < threads.length; j++)
+ {
+ message += threads[j] + ((j < (threads.length - 1)) ? ", " : "");
+ }
+
+ message += " ] to continue.";
+ log.debug(message);
+
+ // For each allow event, synchronize on the threads lock then set the event flag to true.
+ for (int id : threads)
+ {
+ // Set the waiting on coordinator flag to true in case the coordinator tries to test this thread for
+ // being blocked at this time.
+ caller.setWaitingOnCoordinator(true);
+
+ synchronized (locks[id])
+ {
+ // Release the wating on coordinator flag now that this thread is running again.
+ caller.setWaitingOnCoordinator(false);
+
+ // Send the allow to continue event to the receiving thread.
+ allowEvents[id][callerId] = true;
+ }
+ }
+
+ // Wake up any threads waiting on the coordinator lock to recheck their event queues.
+ // Set the waiting on coordinator flag to true in case the coordinator tries to test this thread for
+ // being blocked at this time.
+ caller.setWaitingOnCoordinator(true);
+
+ synchronized (coordinatorLock)
+ {
+ // Release the wating on coordinator flag now that this thread is running again.
+ caller.setWaitingOnCoordinator(false);
+ coordinatorLock.notifyAll();
+ }
+ }
+
+ /**
+ * Consumes an 'allow to continue' from one of the specified threads or waits until one is available or in some
+ * cases if one of the specified threads is blocked elsewhere to accept that as an 'allow to continue' event.
+ *
+ * @param threads The set of threads to accept an allow to continue event from.
+ * @param otherWaitIsAllow Whether or not to accept threads being blocked elsewhere as permission to continue.
+ * @param callerId The explicit id of the calling test thread.
+ * @param caller The calling test thread.
+ *
+ * @return If the otherWaitIsAllow flag is set, then true is returned when the thread being waited on is found
+ * to be blocked outside of the thread test coordinator. false under all other conditions.
+ */
+ boolean consumeAllowEvent(int[] threads, boolean otherWaitIsAllow, int callerId, TestRunnable caller)
+ {
+ // Generate some debugging messages. Very usefull to know how thread synchronization is progressing.
+ String message = "Thread " + callerId + " is requesting threads [ ";
+
+ // Record the time at which this method was called. Will be used for breaking out of potential deadlocks.
+ long startTime = System.nanoTime();
+
+ for (int j = 0; j < threads.length; j++)
+ {
+ message += threads[j] + ((j < (threads.length - 1)) ? ", " : "");
+ }
+
+ message += " ] to allow it to continue.";
+ log.debug(message);
+
+ // Loop until an allow to continue event is received.
+ while (true)
+ {
+ // Look at all the allowing thread to see if one has created an event for consumption.
+ for (int allowerId : threads)
+ {
+ // Get the threads lock for the event to consume.
+ // Set the waiting on coordinator flag to true in case the coordinator tries to test this thread for
+ // being blocked at this time.
+ caller.setWaitingOnCoordinator(true);
+
+ synchronized (locks[callerId])
+ {
+ // Release the wating on coordinator flag now that this thread is running again.
+ caller.setWaitingOnCoordinator(false);
+
+ // Check if there is an event on the queue from the allowing thread to this one.
+ if (allowEvents[callerId][allowerId])
+ {
+ log.debug("Found an allow event, thread " + allowerId + ", is allowing thread " + callerId
+ + ", to continue.");
+
+ // Consume all the allow events for this thread.
+ /*for (int i = 0; i < allowEvents[callerId].length; i++)
+ {
+ allowEvents[callerId][i] = false;
+ }*/
+
+ // Consume just the event from the allower to the consumer, leaving other pending allow events alone.
+ allowEvents[callerId][allowerId] = false;
+
+ return false;
+ }
+ }
+ }
+
+ // If waiting elsewhere is to be interpreted as an 'allow to continue' event, then look at the thread status
+ // for the threads being waited on to see if any are blocked on other resources.
+ if (otherWaitIsAllow)
+ {
+ log.debug("Other wait is to be interpreted as an allow event.");
+
+ // Look at all the potential allower threads.
+ for (int allowerId : threads)
+ {
+ // Get the Java thread state for the allowing thread.
+ Thread threadToTest = testThreads[allowerId].getThread();
+ Thread.State state = threadToTest.getState();
+
+ // Check if the thread is blocked and so a potential candidate for releasing this one.
+ if ((state == Thread.State.BLOCKED) || (state == Thread.State.WAITING)
+ || (state == Thread.State.TIMED_WAITING))
+ {
+ log.debug("Found an allower thread, id = " + allowerId + ", that is blocked or wating.");
+
+ // Check that the allower thread is not waiting on the coordinator lock or any of the
+ // individual thread locks. It must be waiting or blocked on another monitor.
+ TestRunnable allowingRunnable = testThreads[allowerId];
+ boolean isWaitingOnCoordinator = allowingRunnable.isWaitingOnCoordinator();
+
+ if (!isWaitingOnCoordinator)
+ {
+ log.debug("The allower thread, id = " + allowerId
+ + ", is blocked or waiting other than on the coordinator.");
+
+ // Get the threads lock for the event to consume.
+ caller.setWaitingOnCoordinator(true);
+
+ synchronized (locks[callerId])
+ {
+ caller.setWaitingOnCoordinator(false);
+
+ // Consume all the allow events for this thread.
+ for (int i = 0; i < allowEvents[callerId].length; i++)
+ {
+ allowEvents[callerId][i] = false;
+ }
+
+ return true;
+ }
+ }
+ else
+ {
+ log.debug("The waiting allower thread, " + allowerId
+ + ", is waiting on the coordinator so does not allow thread " + callerId + " to continue.");
+ }
+ }
+ }
+ }
+
+ // Keep waiting until an 'allow to continue' event can be consumed.
+ try
+ {
+ // Set the waiting on coordinator flag to true in case the coordinator tries to test this thread for
+ // being blocked at this time.
+ caller.setWaitingOnCoordinator(true);
+
+ synchronized (coordinatorLock)
+ {
+ // Release the wating on coordinator flag now that this thread is running again.
+ caller.setWaitingOnCoordinator(false);
+
+ log.debug("Thread " + callerId + " is waiting on coordinator lock for more allow events.");
+
+ // Set the waiting on coordinator flag to true in case the coordinator tries to test this thread for
+ // being blocked at this time.
+ caller.setWaitingOnCoordinator(true);
+ coordinatorLock.wait(10);
+ }
+ }
+ catch (InterruptedException e)
+ { }
+
+ // Release the waiting on coordinator flag now that this thread is running again.
+ caller.setWaitingOnCoordinator(false);
+
+ // Check if this thread has been waiting for longer than the deadlock timeout and raise a possible
+ // deadlock exception if so.
+ long waitTime = System.nanoTime() - startTime;
+ log.debug("Thread " + callerId + " has been waiting for " + (waitTime / 1000000) + " milliseconds.");
+
+ if (waitTime > deadlockTimeout)
+ {
+ // Throw a possible deadlock exception.
+ throw new PossibleDeadlockException("Possible deadlock due to timeout with state:\n" + this);
+ }
+
+ log.debug("Thread " + callerId + " has woken up, was waiting for more allow events to become available.");
+ }
+ }
+
+ /**
+ * Pretty prints the state of the thread test coordinator, for debugging purposes.
+ *
+ * @return Pretty printed state of the thread test coordinator.
+ */
+ public String toString()
+ {
+ String result = "[";
+
+ for (int i = 0; i < allowEvents.length; i++)
+ {
+ for (int j = 0; j < allowEvents[i].length; j++)
+ {
+ result += allowEvents[i][j];
+
+ result += (j < (allowEvents[i].length - 1)) ? ", " : "";
+ }
+
+ result += (i < (allowEvents.length - 1)) ? ",\n " : "";
+ }
+
+ result += "]";
+
+ for (int i = 0; i < testThreads.length; i++)
+ {
+ result += "thread[" + i + "] = " + testThreads[i].toString();
+ }
+
+ return result;
+ }
+
+}
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestExample.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestExample.java
new file mode 100644
index 0000000000..ef177a4255
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/ThreadTestExample.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.junit.concurrency;
+
+import org.apache.log4j.Logger;
+
+/**
+ * An example to illustrate the use of the {@link ThreadTestCoordinator} and {@link TestRunnable}s.
+ *
+ *
CRC Card
+ *
Responsibilities
Collaborations
+ *
Demo multi-threaded testing.
+ *
+ *
+ * @author Rupert Smith
+ */
+public class ThreadTestExample
+{
+ /** Used for logging. */
+ private static final Logger log = Logger.getLogger(ThreadTestExample.class);
+
+ /** Test thread 1. */
+ TestRunnable testThread1 =
+ new TestRunnable()
+ {
+ public void runWithExceptions() throws Exception
+ {
+ log.debug("public void run(): called");
+ log.info("in testThread0, block 1");
+
+ // Wait for t2 to allow t1 to continue.
+ allow(new int[] { 1 });
+ waitFor(new int[] { 1 }, false);
+
+ log.info("in testThread0, block 2");
+
+ // Wait for t2 to allow t1 to continue. T2 is allowed to be blocked elsewhere than giving explicit
+ // permission to allow t1 to continue.
+ allow(new int[] { 1 });
+ waitFor(new int[] { 1 }, true);
+
+ log.info("in testThread0, block 3");
+
+ // Release thread 2 from waiting on the shared lock.
+ synchronized (sharedLock)
+ {
+ sharedLock.notifyAll();
+ }
+
+ allow(new int[] { 1 });
+ }
+ };
+
+ /** A shared lock between the test threads. */
+ final Object sharedLock = new Object();
+
+ /** Test thread 2. */
+ TestRunnable testThread2 =
+ new TestRunnable()
+ {
+ public void runWithExceptions() throws Exception
+ {
+ log.debug("public void run(): called");
+ log.info("in testThread1, block 1");
+
+ // Wait for t1 to allow t2 to continue.
+ allow(new int[] { 0 });
+ waitFor(new int[] { 0 }, false);
+
+ log.info("in testThread1, block 2");
+
+ // Wait on another resource. T1 should accept this as permission to continue.
+ try
+ {
+ synchronized (sharedLock)
+ {
+ log.debug("in testThread1, waiting on shared lock.");
+ sharedLock.wait();
+ }
+ }
+ catch (InterruptedException e)
+ {
+ // Bail-out with a runtime if this happens.
+ throw new RuntimeException("Interrupted whilst waiting for shared lock.", e);
+ }
+
+ log.info("in testThread1, finished waiting on shared lock.");
+
+ // allow(new int[] { 0 });
+
+ // Wait for t1 to allow t2 to continue.
+ waitFor(new int[] { 0 }, false);
+
+ log.info("in testThread1, block 3");
+
+ allow(new int[] { 0 });
+ }
+ };
+
+ /**
+ * Executes the test threads with coordination.
+ *
+ * @param args Ignored.
+ */
+ public void main(String[] args)
+ {
+ ThreadTestCoordinator tt = new ThreadTestCoordinator(2);
+
+ tt.addTestThread(testThread1, 0);
+ tt.addTestThread(testThread2, 1);
+ tt.setDeadlockTimeout(500);
+ tt.run();
+
+ String errorMessage = tt.joinAndRetrieveMessages();
+
+ // Print any error messages or exceptions.
+ log.info(errorMessage);
+
+ if (!tt.getExceptions().isEmpty())
+ {
+ for (Exception e : tt.getExceptions())
+ {
+ log.warn("Exception thrown during test thread: ", e);
+ }
+ }
+ }
+}
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/package.html b/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/package.html
new file mode 100644
index 0000000000..4264367690
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/concurrency/package.html
@@ -0,0 +1,7 @@
+
+
+Contains code to assist in testing concurrency issues using coordinated threads to present code under test with
+oportunities to expose concurrency bugs. Some example concurrency bugs that may be tested using these techniques are
+race conditions, dead locks, live locks, dirty reads, phantom reads, non repeatable reads and so on.
+
+
\ No newline at end of file
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/AsymptoticTestCase.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/AsymptoticTestCase.java
new file mode 100644
index 0000000000..03e465695e
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/AsymptoticTestCase.java
@@ -0,0 +1,303 @@
+/*
+ *
+ * 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.junit.extensions;
+
+import junit.framework.TestCase;
+
+import org.apache.log4j.Logger;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+/**
+ * AsymptoticTestCase is an extension of TestCase for writing unit tests to analyze asymptotic time and space behaviour.
+ *
+ *
ParameterizedTestCases allow tests to be defined which have test methods that take a single int argument. Normal
+ * JUnit test methods do not take any arguments. This int argument can be interpreted in any way by the test but it is
+ * intended to denote the 'size' of the test to be run. For example, when testing the performance of a data structure
+ * for different numbers of data elements held in the data structure the int parameter should be interpreted as the
+ * number of elements. Test timings for different numbers of elements can then be captured and the asymptotic behaviour
+ * of the data structure with respect to time analyzed. Any non-parameterized tests defined in extensions of this class
+ * will also be run.
+ *
+ *
TestCases derived from this class may also define tear down methods to clean up their memory usage. This is
+ * intended to be used in conjunction with memory listeners that report the amount of memory a test uses. The idea is
+ * to write a test that allocates memory in the main test method in such a way that it leaves that memory still
+ * allocated at the end of the test. The amount of memory used can then be measured before calling the tear down method
+ * to clean it up. In the data structure example above, a test will allocate as many elements as are requested by the
+ * int parameter and deallocate them in the tear down method. In this way memory readings for different numbers of
+ * elements can be captured and the asymptotic behaviour of the data structure with respect to space analyzed.
+ *
+ *
CRC Card
+ *
Responsibilities
Collaborations
+ *
Store the current int parameter value.
{@link TKTestResult} and see {@link AsymptoticTestDecorator} too.
+ *
Invoke parameterized test methods.
+ *
+ *
+ * @todo If possible try to move the code that invokes the test and setup/teardown methods into {@link TKTestResult} or
+ * {@link AsymptoticTestDecorator} rather than this class. This would mean that tests don't have to extend this
+ * class to do time and space performance analysis, these methods could be added to any JUnit TestCase class
+ * instead. This would be an improvement because existing unit tests wouldn't have to extend a different class to
+ * work with this extension, and also tests that extend other junit extension classes could have parameterized
+ * and tear down methods too.
+ *
+ * @author Rupert Smith
+ */
+public class AsymptoticTestCase extends TestCase implements InstrumentedTest
+{
+ /** Used for logging. */
+ private static final Logger log = Logger.getLogger(AsymptoticTestCase.class);
+
+ /** The name of the test case. */
+ private String testCaseName;
+
+ /** Thread local for holding measurements on a per thread basis. */
+ ThreadLocal threadLocalMeasurement =
+ new ThreadLocal()
+ {
+ /**
+ * Sets up a default set test measurements (zeroed, apart from the size param which defaults to 1).
+ *
+ * @return A set of default test measurements.
+ */
+ protected synchronized TestMeasurements initialValue()
+ {
+ return new TestMeasurements();
+ }
+ };
+
+ /**
+ * Constructs a test case with the given name.
+ *
+ * @param name The name of the test.
+ */
+ public AsymptoticTestCase(String name)
+ {
+ super(name);
+
+ log.debug("public AsymptoticTestCase(String " + name + "): called");
+ testCaseName = name;
+ }
+
+ /**
+ * Gets the current value of the integer parameter to be passed to the parameterized test.
+ *
+ * @return The current value of the integer parameter.
+ */
+ public int getN()
+ {
+ log.debug("public int getN(): called");
+ int n = threadLocalMeasurement.get().n;
+
+ log.debug("return: " + n);
+
+ return n;
+ }
+
+ /**
+ * Sets the current value of the integer parameter to be passed to the parameterized test.
+ *
+ * @param n The new current value of the integer parameter.
+ */
+ public void setN(int n)
+ {
+ log.debug("public void setN(int " + n + "): called");
+ threadLocalMeasurement.get().n = n;
+ }
+
+ /**
+ * Reports how long the test took to run.
+ *
+ * @return The time in milliseconds that the test took to run.
+ */
+ public long getTestTime()
+ {
+ log.debug("public long getTestTime(): called");
+ long startTime = threadLocalMeasurement.get().startTime;
+ long endTime = threadLocalMeasurement.get().endTime;
+ long testTime = endTime - startTime;
+
+ log.debug("return: " + testTime);
+
+ return testTime;
+ }
+
+ /**
+ * Reports the memory usage at the start of the test.
+ *
+ * @return The memory usage at the start of the test.
+ */
+ public long getTestStartMemory()
+ {
+ // log.debug("public long getTestStartMemory(): called");
+ long startMem = threadLocalMeasurement.get().startMem;
+
+ // log.debug("return: " + startMem);
+
+ return startMem;
+ }
+
+ /**
+ * Reports the memory usage at the end of the test.
+ *
+ * @return The memory usage at the end of the test.
+ */
+ public long getTestEndMemory()
+ {
+ // log.debug("public long getTestEndMemory(): called");
+ long endMem = threadLocalMeasurement.get().endMem;
+
+ // log.debug("return: " + endMem);
+ return endMem;
+ }
+
+ /**
+ * Resets the instrumentation values to zero, and nulls any references to held measurements so that the memory
+ * can be reclaimed.
+ */
+ public void reset()
+ {
+ log.debug("public void reset(): called");
+ threadLocalMeasurement.remove();
+ }
+
+ /**
+ * Runs the test method for this test case.
+ *
+ * @throws Throwable Any Throwables from the test methods invoked are allowed to fall through.
+ */
+ protected void runTest() throws Throwable
+ {
+ log.debug("protected void runTest(): called");
+
+ // Check that a test name has been set. This is used to define which method to run.
+ assertNotNull(testCaseName);
+ log.debug("testCaseName = " + testCaseName);
+
+ // Try to get the method with matching name.
+ Method runMethod = null;
+ boolean isParameterized = false;
+
+ // Check if a parameterized test method is available.
+ try
+ {
+ // Use getMethod to get all public inherited methods. getDeclaredMethods returns all
+ // methods of this class but excludes the inherited ones.
+ runMethod = getClass().getMethod(testCaseName, int.class);
+ isParameterized = true;
+ }
+ catch (NoSuchMethodException e)
+ {
+ // log.debug("Parameterized method \"" + testCaseName + "\" not found.");
+ // Set run method to null (it already will be but...) to indicate that no parameterized method
+ // version could be found.
+ runMethod = null;
+ }
+
+ // If no parameterized method is available, try and get the unparameterized method.
+ if (runMethod == null)
+ {
+ try
+ {
+ runMethod = getClass().getMethod(testCaseName);
+ isParameterized = false;
+
+ }
+ catch (NoSuchMethodException e)
+ {
+ fail("Method \"" + testCaseName + "\" not found.");
+ }
+ }
+
+ // Check that the method is publicly accessable.
+ if (!Modifier.isPublic(runMethod.getModifiers()))
+ {
+ fail("Method \"" + testCaseName + "\" should be public.");
+ }
+
+ // Try to execute the method, passing it the current int parameter value. Allow any invocation exceptions or
+ // resulting exceptions from the method to fall through.
+ try
+ {
+ Integer paramN = getN();
+ log.debug("paramN = " + paramN);
+
+ // Calculate parameters for parameterized tests so new does not get called during memory measurement.
+ Object[] params = new Object[] { paramN };
+
+ // Take the test start memory and start time.
+ threadLocalMeasurement.get().startMem = 0; // SizeOf.getUsedMemory();
+
+ threadLocalMeasurement.get().startTime = System.nanoTime();
+
+ if (isParameterized)
+ {
+ runMethod.invoke(this, params);
+ }
+ else
+ {
+ runMethod.invoke(this);
+ }
+ }
+ catch (InvocationTargetException e)
+ {
+ e.fillInStackTrace();
+ throw e.getTargetException();
+ }
+ catch (IllegalAccessException e)
+ {
+ e.fillInStackTrace();
+ throw e;
+ }
+ finally
+ {
+ // Take the test end memory and end time and calculate how long it took to run.
+ long endTime = System.nanoTime();
+ threadLocalMeasurement.get().endTime = endTime;
+ log.debug("startTime = " + threadLocalMeasurement.get().startTime + ", endTime = " + endTime + ", testTime = "
+ + getTestTime());
+
+ threadLocalMeasurement.get().endMem = 0; // SizeOf.getUsedMemory();
+ }
+ }
+
+ /**
+ * The test parameters, encapsulated as a unit for attaching on a per thread basis.
+ */
+ private static class TestMeasurements
+ {
+ /** Holds the current value of the integer parameter to run tests on. */
+ public int n = 1;
+
+ /** Holds the test start memory. */
+ public long startTime = 0;
+
+ /** Holds the test end memory. */
+ public long endTime = 0;
+
+ /** Holds the test start memory. */
+ public long startMem = 0;
+
+ /** Holds the test end memory. */
+ public long endMem = 0;
+ }
+}
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/AsymptoticTestDecorator.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/AsymptoticTestDecorator.java
new file mode 100644
index 0000000000..e99f904331
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/AsymptoticTestDecorator.java
@@ -0,0 +1,170 @@
+/*
+ *
+ * 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.junit.extensions;
+
+import junit.framework.TestResult;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.junit.extensions.util.MathUtils;
+
+/**
+ * A Decorator that runs a test repeatedly on an increasing int parameter, or for a fixed number of repeats. If both
+ * a set of integer parameters and a repeat count are specified, then each test is run for the repeat count at each
+ * integer parameter.
+ *
+ *
CRC Card
+ *
Responsibilities
Collaborations
+ *
Repeat a test for each of a set of integer parameters.
{@link TKTestResult}
+ *
Repeat a test multiple times.
+ *
+ *
+ *
+ * @author Rupert Smith
+ */
+public class AsymptoticTestDecorator extends WrappedSuiteTestDecorator
+{
+ /** Used for logging. */
+ private static final Logger log = Logger.getLogger(AsymptoticTestDecorator.class);
+
+ /** The int size parameters to run the test with. */
+ private int[] params;
+
+ /** The number of times the whole test should be repeated. */
+ private int repeat;
+
+ /**
+ * Creates an asymptotic test decorator that wraps a test with repeats and a set of integer 'size' paramters
+ * to call the test with.
+ *
+ * @param test The test to wrap.
+ * @param params The integer 'size' parameters.
+ * @param repeat The number of times to repeat the test.
+ */
+ public AsymptoticTestDecorator(WrappedSuiteTestDecorator test, int[] params, int repeat)
+ {
+ super(test);
+
+ log.debug("public AsymptoticTestDecorator(Test \"" + test + "\", int[] "
+ + ((params == null) ? null : MathUtils.printArray(params)) + ", int " + repeat + "): called");
+
+ this.params = params;
+ this.repeat = repeat;
+ }
+
+ /**
+ * Creates a new AsymptoticTestDecorator object.
+ *
+ * @param test The test to decorate.
+ * @param start The starting asymptotic integer parameter value.
+ * @param end The ending asymptotic integer parameter value.
+ * @param step The increment size to move from the start to end values by.
+ * @param repeat The number of times to repeat the test at each step of the cycle.
+ */
+ public AsymptoticTestDecorator(WrappedSuiteTestDecorator test, int start, int end, int step, int repeat)
+ {
+ super(test);
+
+ if (start < 0)
+ {
+ throw new IllegalArgumentException("Start must be >= 0");
+ }
+
+ if (end < start)
+ {
+ throw new IllegalArgumentException("End must be >= start");
+ }
+
+ if (step < 1)
+ {
+ throw new IllegalArgumentException("Step must be >= 1");
+ }
+
+ if (repeat < 1)
+ {
+ throw new IllegalArgumentException("Repeat must be >= 1");
+ }
+
+ // Generate the sequence.
+ params = new int[((end - start) / step) + 1];
+ int i = 0;
+ for (int n = start; n <= end; n += step)
+ {
+ params[i++] = n;
+ }
+
+ this.repeat = repeat;
+ }
+
+ /**
+ * Runs the test repeatedly for each value of the int parameter specified and for the correct number of test
+ * repeats.
+ *
+ * @param result The test result object that the tests will indicate their results to. This is also used
+ * to pass the int parameter from this class to the decorated test class.
+ */
+ public void run(TestResult result)
+ {
+ log.debug("public void run(TestResult result): called");
+
+ if (!(result instanceof TKTestResult))
+ {
+ throw new IllegalArgumentException("AsymptoticTestDecorator only works with TKTestResult");
+ }
+
+ // Cast the test result into a TKTestResult to place the current parameter into.
+ TKTestResult tkResult = (TKTestResult) result;
+
+ log.debug("params = " + ((params == null) ? null : MathUtils.printArray(params)));
+ log.debug("repeat = " + repeat);
+
+ for (int n : params)
+ {
+ for (int j = 0; j < repeat; j++)
+ {
+ log.debug("n = " + n);
+
+ // Set the integer parameter in the TKTestResult to be passed to the tests.
+ tkResult.setN(n);
+
+ if (tkResult.shouldStop())
+ {
+ log.debug("tkResult.shouldStop = " + true);
+
+ break;
+ }
+
+ log.debug("Calling super#run");
+ super.run(tkResult);
+ }
+ }
+ }
+
+ /**
+ * Prints out the name of this test with the string "(parameterized)" appended onto it for debugging purposes.
+ *
+ * @return The name of this test with the string "(parameterized)" appended onto it.
+ */
+ public String toString()
+ {
+ return super.toString() + "(parameterized)";
+ }
+}
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/BaseThrottle.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/BaseThrottle.java
new file mode 100644
index 0000000000..17d1fb7692
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/BaseThrottle.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2007 Rupert Smith.
+ *
+ * Licensed 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.junit.extensions;
+
+/**
+ * Provides a base implementation of the non-waiting throttle checking method, using the system nano timer.
+ *
+ *
CRC Card
+ *
Responsibilities
Collaborations
+ *
Check against a throttle speed without waiting.
+ *
+ *
+ * @author Rupert Smith
+ */
+public abstract class BaseThrottle implements Throttle
+{
+ /** Holds the length of a single cycle in nano seconds. */
+ protected long cycleTimeNanos;
+
+ /** Holds the time of the last succesfull call to the check method. */
+ private long lastCheckTimeNanos;
+
+ /** Flag used to detect the first call to the {@link #checkThrottle()} method. */
+ boolean firstCheckCall = true;
+
+ /**
+ * Flag used to detect the first call to the {@link #throttle()} method. Zero or negative start time cannot be
+ * relied on to detect this as System.nanoTime can return zero or negative values.
+ */
+ boolean firstCall = true;
+
+ /**
+ * Specifies the throttling rate in operations per second. This must be called with with a value, the inverse
+ * of which is a measurement in nano seconds, such that the number of nano seconds do not overflow a long integer.
+ * The value must also be larger than zero.
+ *
+ * @param hertz The throttling rate in cycles per second.
+ */
+ public void setRate(float hertz)
+ {
+ // Check that the argument is above zero.
+ if (hertz <= 0.0f)
+ {
+ throw new IllegalArgumentException("The throttle rate must be above zero.");
+ }
+
+ // Calculate the cycle time.
+ cycleTimeNanos = (long)(1000000000f / hertz);
+
+ // Reset the first pass flag.
+ firstCall = false;
+ firstCheckCall = false;
+ }
+
+ /**
+ * Checks but does not enforce the throttle rate. When this method is called, it checks if a length of time greater
+ * than that equal to the inverse of the throttling rate has passed since it was last called and returned true
+ *
+ * @return true if a length of time greater than that equal to the inverse of the throttling rate has
+ * passed since this method was last called and returned true, false otherwise. The very
+ * first time this method is called on a throttle, it returns true as the base case to the above
+ * self-referential definition.
+ */
+ public boolean checkThrottle()
+ {
+ long now = System.nanoTime();
+
+ if ((now > (cycleTimeNanos + lastCheckTimeNanos)) || firstCheckCall)
+ {
+ firstCheckCall = false;
+ lastCheckTimeNanos = now;
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+}
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/BatchedThrottle.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/BatchedThrottle.java
new file mode 100644
index 0000000000..1d00fcf3b6
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/BatchedThrottle.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.junit.extensions;
+
+/**
+ * BatchedThrottle is a {@link SleepThrottle} that uses batching to achieve much higher throttling rates than a sleep
+ * throttle can. Sleep throttle has difficulties once the rate gets above a few hundred hertz, because the JVM cannot
+ * generate timed pauses that are that short. BatchedThrottle gets around this by only inserting pauses once every so
+ * many calls to the {@link #throttle()} method, and using a sleep throttle run at a lower rate. The rate for the sleep
+ * throttle is chosen so that it remains under 100hz. The final throttling rate of this throttle is equal to the batch
+ * size times the rate of the underlying sleep throttle.
+ *
+ * The batching calculation involves taking the log to the base 100 of the desired rate and rounding this to
+ * an integer. The batch size is always an exact power of 100 because of the rounding. The rate for an underlying
+ * sleep throttle is then chosen appropriately.
+ *
+ * In practice, the accuracy of a BacthedThrottle skews off but can sometimes even be reasonable up to ten thousand
+ * hertz compared with 100 Hz for a {@link SleepThrottle}.
+ *
+ *
CRC Card
+ *
Responsibilities
Collaborations
+ *
Accept throttling rate in operations per second.
+ *
Inject short pauses, occasionaly, to fill out processing cycles to a specified rate.
+ *
Check against a throttle speed without waiting.
+ *
+ *
+ * @todo Should always round the log base 100 down to the nearest integer?
+ *
+ * @author Rupert Smith
+ */
+public class BatchedThrottle extends BaseThrottle
+{
+ /** Holds the batch size. */
+ int batchSize;
+
+ /** The call count within the current batch. */
+ long callCount;
+
+ /** Holds a sleep throttle configured to run at the batched rate. */
+ private Throttle batchRateThrottle = new SleepThrottle();
+
+ /**
+ * Specifies the throttling rate in operations per second.
+ *
+ * @param hertz The throttling rate in cycles per second.
+ */
+ public void setRate(float hertz)
+ {
+ // Pass the rate unaltered down to the base implementation, for the check method.
+ super.setRate(hertz);
+
+ // Log base 10 over 2 is used here to get a feel for what power of 100 the total rate is.
+ // As the total rate goes up the powers of 100 the batch size goes up by powers of 100 to keep the
+ // throttle rate in the range 1 to 100.
+ int x = (int) (Math.log10(hertz) / 2);
+ batchSize = (int) Math.pow(100, x);
+ float throttleRate = hertz / batchSize;
+
+ // Reset the call count.
+ callCount = 0;
+
+ // Set the sleep throttle wrapped implementation at a rate within its abilities.
+ batchRateThrottle.setRate(throttleRate);
+ }
+
+ /**
+ * Throttle calls to this method to the rate specified by the {@link #setRate(float)} method.
+ */
+ public void throttle()
+ {
+ if ((callCount++ % batchSize) == 0)
+ {
+ batchRateThrottle.throttle();
+ }
+ }
+}
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/DurationTestDecorator.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/DurationTestDecorator.java
new file mode 100644
index 0000000000..187fb21e9a
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/DurationTestDecorator.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2007 Rupert Smith.
+ *
+ * Licensed 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.junit.extensions;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+import junit.framework.Test;
+import junit.framework.TestResult;
+
+import org.apache.log4j.Logger;
+
+/**
+ * A test decorator that runs a test repeatedly until a specified length of time has passed.
+ *
+ *
CRC Card
+ *
Responsibilities
Collaborations
+ *
Repeatedly run a test for a fixed length of time.
+ *
+ *
+ * @todo The count of the number of tests run is an important number to keep. Also num passed/error/failed is also
+ * important to record. What to do with these numbers? They are already logged to the test listeners.
+ *
+ * @todo The duration test runner wraps on top of size, repeat or thread wrappers, need a way for it to tell
+ * TKTestResult when the duration is up, so that it can terminate any repeats in progress. It should end
+ * as soon as possible once the test method exits.
+ *
+ * @author Rupert Smith
+ */
+public class DurationTestDecorator extends WrappedSuiteTestDecorator implements ShutdownHookable
+{
+ /** Used for logging. */
+ private static final Logger log = Logger.getLogger(DurationTestDecorator.class);
+
+ /** The test to run. */
+ private Test test;
+
+ /** The length of time to run the test for. */
+ private long duration;
+
+ /** Flag set by the shutdown hook. This decorator will not start any new tests when this is set. */
+ private boolean shutdown = false;
+
+ /**
+ * Creates an active test with default multiplier (1).
+ *
+ * @param test The target test.
+ */
+ public DurationTestDecorator(WrappedSuiteTestDecorator test)
+ {
+ super(test);
+ this.test = test;
+ }
+
+ /**
+ * Creates active test with default multiplier (1).
+ *
+ * @param test The target test.
+ * @param duration The duration in milliseconds.
+ */
+ public DurationTestDecorator(WrappedSuiteTestDecorator test, long duration)
+ {
+ super(test);
+
+ // log.debug("public DurationTestDecorator(Test \"" + test + "\", long " + duration + "): called");
+
+ this.test = test;
+ this.duration = duration;
+ }
+
+ /**
+ * Runs the test repeatedly for the fixed duration.
+ *
+ * @param testResult The the results object to monitor the test results with.
+ */
+ public void run(TestResult testResult)
+ {
+ log.debug("public void run(TestResult testResult): called");
+
+ // Cast the test result to expose it as a TKTestResult if the test is running under the TKTestRunner.
+ TKTestResult tkTestResult = null;
+
+ if (testResult instanceof TKTestResult)
+ {
+ tkTestResult = (TKTestResult)testResult;
+ }
+
+ // Work out when the test should end.
+ long now = System.nanoTime();
+ long end = (duration * 1000000) + now;
+
+ // If running under the TKTestRunner, set up a timer to notify the test framework when the test reaches its
+ // completion time.
+ Timer durationTimer = null;
+
+ if (tkTestResult != null)
+ {
+ log.debug("Creating duration timer.");
+
+ durationTimer = new Timer();
+ durationTimer.schedule(new DurationTimerTask((TKTestResult)testResult), duration);
+ }
+
+ // Run the test until the duration times out or the shutdown flag is set. The test method may not exit until
+ // interrupted in some cases, in which case the timer will do the interrupting.
+ while ((now < end) && !shutdown)
+ {
+ test.run(testResult);
+
+ now = System.nanoTime();
+ }
+
+ // Clean up any timer that was used.
+ if (durationTimer != null)
+ {
+ log.debug("Cancelling duration timer.");
+
+ durationTimer.cancel();
+ }
+ }
+
+ /**
+ * Supplies the shutdown hook. This shutdown hook does not call {@link TKTestResult#shutdownNow()} because the
+ * {@link ScaledTestDecorator} already takes care of that.
+ *
+ * @return The shut down hook.
+ */
+ public Thread getShutdownHook()
+ {
+ return new Thread(new Runnable()
+ {
+ public void run()
+ {
+ // log.debug("DurationTestDecorator::ShutdownHook: called");
+
+ // Set the shutdown flag so that no new tests are started.
+ shutdown = true;
+ }
+ });
+ }
+
+ /**
+ * DurationTimerTask is a timer task that is configured, upon expiry of its timer, to invoke
+ * {@link TKTestResult#shutdownNow()}, for the test result object on which it is set. It also sets
+ * the {@link DurationTestDecorator#shutdown} flag to indicate that no new tests should be run.
+ *
+ * The test loop implemented by DurationTestDecorator checks that the duration has not expired, on each
+ * test case that it runs. However, it is possible to write test cases that never return until explicitly
+ * interrupted by the test framework. This timer task exists to notify the test framework
+ */
+ private class DurationTimerTask extends TimerTask
+ {
+ /** Used for debugging purposes. */
+ private final Logger log = Logger.getLogger(DurationTimerTask.class);
+
+ /** Holds the test result for the test to which a duration limit is being applied. */
+ TKTestResult testResult;
+
+ /**
+ * Creates a duration limit timer which will notify the specified test result when the duration has
+ * expired.
+ *
+ * @param testResult The test result to notify upon expiry of the test duration.
+ */
+ public DurationTimerTask(TKTestResult testResult)
+ {
+ this.testResult = testResult;
+ }
+
+ /**
+ * The action to be performed by this timer task.
+ */
+ public void run()
+ {
+ log.debug("public void run(): called");
+
+ shutdown = true;
+ testResult.shutdownNow();
+ }
+ }
+}
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/InstrumentedTest.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/InstrumentedTest.java
new file mode 100644
index 0000000000..ed792fcd5a
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/InstrumentedTest.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.junit.extensions;
+
+import junit.framework.Test;
+
+/**
+ * An InstrumentedTest is one which can supply some additional instrumentation on top of the pass/fail/error behaviour
+ * of normal junit tests. Tests implementing this interface must additionally supply information about how long they
+ * took to run and how much memory they used.
+ *
+ *
CRC Card
+ *
Responsibilities
+ *
Report test run time.
+ *
Report test memory usage.
+ *
+ *
+ * @author Rupert Smith
+ */
+public interface InstrumentedTest extends Test
+{
+ /**
+ * Reports how long the test took to run.
+ *
+ * @return The time in milliseconds that the test took to run.
+ */
+ public long getTestTime();
+
+ /**
+ * Reports the memory usage at the start of the test.
+ *
+ * @return The memory usage at the start of the test.
+ */
+ public long getTestStartMemory();
+
+ /**
+ * Reports the memory usage at the end of the test.
+ *
+ * @return The memory usage at the end of the test.
+ */
+ public long getTestEndMemory();
+
+ /**
+ * Resets the instrumentation values to zero, and nulls any references to held measurements so that the memory
+ * can be reclaimed.
+ */
+ public void reset();
+}
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/NullResultPrinter.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/NullResultPrinter.java
new file mode 100644
index 0000000000..2ffbcb5bb8
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/NullResultPrinter.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.junit.extensions;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.Test;
+
+import junit.textui.ResultPrinter;
+
+import java.io.PrintStream;
+
+/**
+ * A ResultPrinter that prints nothing. This exists, in order to provide a replacement to JUnit's ResultPrinter, which
+ * is refered to directly by JUnit code, rather that as an abstracted TestListener. JUnit's text ui TestRunner must
+ * have a ResultPrinter. This provides an implementation of it that prints nothing, so that a better mechanism can
+ * be used for providing feedback to the console instead.
+ *
+ *
CRC Card
+ *
Responsibilities
Collaborations
+ *
+ *
+ *
+ * @todo See todo in TKTestRunner about completely replacing the test ui runner. Doing things like this in order to
+ * extend JUnit is not nice, and there needs to be a better way to do it. Delete this class and use a listener
+ * instead.
+ *
+ * @author Rupert Smith
+ */
+public class NullResultPrinter extends ResultPrinter
+{
+ /**
+ * Builds a fake ResultPrinter that prints nothing.
+ *
+ * @param writer The writer to send output to.
+ */
+ public NullResultPrinter(PrintStream writer)
+ {
+ super(writer);
+ }
+
+ /**
+ * Does nothing.
+ *
+ * @param test Ignored.
+ * @param t Ignored.
+ */
+ public void addError(Test test, Throwable t)
+ { }
+
+ /**
+ * Does nothing.
+ *
+ * @param test Ignored.
+ * @param t Ignored.
+ */
+ public void addFailure(Test test, AssertionFailedError t)
+ { }
+
+ /**
+ * Does nothing.
+ *
+ * @param test Ignored.
+ */
+ public void endTest(Test test)
+ { }
+
+ /**
+ * Does nothing.
+ *
+ * @param test Ignored.
+ */
+ public void startTest(Test test)
+ { }
+}
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ParameterVariationTestDecorator.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ParameterVariationTestDecorator.java
new file mode 100644
index 0000000000..60ec156354
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ParameterVariationTestDecorator.java
@@ -0,0 +1,172 @@
+/*
+ *
+ * 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.junit.extensions;
+
+import junit.framework.TestResult;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.junit.extensions.util.MathUtils;
+
+/**
+ * ParameterVariationTestDecorator is a test decorator that runs a test repeatedly under all permutations of its
+ * test parameters.
+ *
+ * a set of integer parameters and a repeat count are specified, then each test is run for the repeat count at each
+ * integer parameter.
+ *
+ *
CRC Card
+ *
Responsibilities
Collaborations
+ *
Repeat a test for each of a set of integer parameters.
+ *
+ * @author Rupert Smith
+ */
+public class ParameterVariationTestDecorator extends WrappedSuiteTestDecorator
+{
+ /** Used for logging. */
+ private static final Logger log = Logger.getLogger(ParameterVariationTestDecorator.class);
+
+ /** The int size parameters to run the test with. */
+ private int[] params;
+
+ /** The number of times the whole test should be repeated. */
+ private int repeat;
+
+ /**
+ * Creates an asymptotic test decorator that wraps a test with repeats and a set of integer 'size' paramters
+ * to call the test with.
+ *
+ * @param test The test to wrap.
+ * @param params The integer 'size' parameters.
+ * @param repeat The number of times to repeat the test.
+ */
+ public ParameterVariationTestDecorator(WrappedSuiteTestDecorator test, int[] params, int repeat)
+ {
+ super(test);
+
+ log.debug("public AsymptoticTestDecorator(Test \"" + test + "\", int[] "
+ + ((params == null) ? null : MathUtils.printArray(params)) + ", int " + repeat + "): called");
+
+ this.params = params;
+ this.repeat = repeat;
+ }
+
+ /**
+ * Creates a new AsymptoticTestDecorator object.
+ *
+ * @param test The test to decorate.
+ * @param start The starting asymptotic integer parameter value.
+ * @param end The ending asymptotic integer parameter value.
+ * @param step The increment size to move from the start to end values by.
+ * @param repeat The number of times to repeat the test at each step of the cycle.
+ */
+ public ParameterVariationTestDecorator(WrappedSuiteTestDecorator test, int start, int end, int step, int repeat)
+ {
+ super(test);
+
+ if (start < 0)
+ {
+ throw new IllegalArgumentException("Start must be >= 0");
+ }
+
+ if (end < start)
+ {
+ throw new IllegalArgumentException("End must be >= start");
+ }
+
+ if (step < 1)
+ {
+ throw new IllegalArgumentException("Step must be >= 1");
+ }
+
+ if (repeat < 1)
+ {
+ throw new IllegalArgumentException("Repeat must be >= 1");
+ }
+
+ // Generate the sequence.
+ params = new int[((end - start) / step) + 1];
+ int i = 0;
+ for (int n = start; n <= end; n += step)
+ {
+ params[i++] = n;
+ }
+
+ this.repeat = repeat;
+ }
+
+ /**
+ * Runs the test repeatedly for each value of the int parameter specified and for the correct number of test
+ * repeats.
+ *
+ * @param result The test result object that the tests will indicate their results to. This is also used
+ * to pass the int parameter from this class to the decorated test class.
+ */
+ public void run(TestResult result)
+ {
+ log.debug("public void run(TestResult result): called");
+
+ if (!(result instanceof TKTestResult))
+ {
+ throw new IllegalArgumentException("AsymptoticTestDecorator only works with TKTestResult");
+ }
+
+ // Cast the test result into a TKTestResult to place the current parameter into.
+ TKTestResult tkResult = (TKTestResult) result;
+
+ log.debug("params = " + ((params == null) ? null : MathUtils.printArray(params)));
+ log.debug("repeat = " + repeat);
+
+ for (int n : params)
+ {
+ for (int j = 0; j < repeat; j++)
+ {
+ log.debug("n = " + n);
+
+ // Set the integer parameter in the TKTestResult to be passed to the tests.
+ tkResult.setN(n);
+
+ if (tkResult.shouldStop())
+ {
+ log.debug("tkResult.shouldStop = " + true);
+
+ break;
+ }
+
+ log.debug("Calling super#run");
+ super.run(tkResult);
+ }
+ }
+ }
+
+ /**
+ * Prints out the name of this test with the string "(parameterized)" appended onto it for debugging purposes.
+ *
+ * @return The name of this test with the string "(parameterized)" appended onto it.
+ */
+ public String toString()
+ {
+ return super.toString() + "(parameterized)";
+ }
+}
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ScaledTestDecorator.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ScaledTestDecorator.java
new file mode 100644
index 0000000000..f5e3e1a758
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ScaledTestDecorator.java
@@ -0,0 +1,375 @@
+/*
+ *
+ * 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.junit.extensions;
+
+import junit.framework.Test;
+import junit.framework.TestResult;
+
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.CyclicBarrier;
+
+/**
+ * A test decorator that runs a test many times simultaneously in many threads.
+ *
+ *
CRC Card
+ *
Responsibilities
Collaborations
+ *
Clone a test run into many threads and run them simultaneously.
+ *
Inform the test results of the start and end of each concurrent test batch.
{@link TKTestResult}
+ *
Inform the test results of the concurrency level.
{@link TKTestResult}
+ *
+ *
+ * @author Rupert Smith
+ */
+public class ScaledTestDecorator extends WrappedSuiteTestDecorator implements ShutdownHookable // TestDecorator
+{
+ /** Used for logging. */
+ // private static final Logger log = Logger.getLogger(ScaledTestDecorator.class);
+
+ /** Determines how long to wait for tests to cleanly exit on shutdown. */
+ private static final long SHUTDOWN_PAUSE = 3000;
+
+ /**
+ * The stress levels or numbers of simultaneous threads to run the test in. The test is repeated at each of
+ * the concurrency levels specified here. Defaults to 1 thread.
+ */
+ private int[] threads = new int[] { 1 };
+
+ /** Used to hold the number of tests currently being run in parallel. */
+ private int concurrencyLevel;
+
+ /** The test to run. */
+ private WrappedSuiteTestDecorator test;
+
+ /**
+ * Used to hold the current {@link TKTestResult} for the tests currently being run. This is made available so that
+ * the shutdown hook can ask it to cleanly end the current tests in the event of a shutdown.
+ */
+ private TKTestResult currentTestResult;
+
+ /** Flag set by the shutdown hook. This decorator will not start any new tests when this is set. */
+ private boolean shutdown = false;
+
+ /**
+ * Creates an active test with default multiplier (1).
+ *
+ * @param test The target test.
+ */
+ public ScaledTestDecorator(WrappedSuiteTestDecorator test)
+ {
+ super(test);
+ this.test = test;
+ }
+
+ /**
+ * Creates a concurrently scaled test with the specified number of threads.
+ *
+ * @param test The target test.
+ * @param numThreads The stress level.
+ */
+ public ScaledTestDecorator(WrappedSuiteTestDecorator test, int numThreads)
+ {
+ this(test, new int[] { numThreads });
+ }
+
+ /**
+ * Creates a concurrently scaled test with the specified thread levels, the test is repeated at each level.
+ *
+ * @param test The target test.
+ * @param threads The concurrency levels.
+ */
+ public ScaledTestDecorator(WrappedSuiteTestDecorator test, int[] threads)
+ {
+ super(test);
+
+ /*log.debug("public ScaledTestDecorator(WrappedSuiteTestDecorator test = \"" + test + "\", int[] threads = "
+ + MathUtils.printArray(threads) + "): called");*/
+
+ this.test = test;
+ this.threads = threads;
+ }
+
+ /**
+ * Runs the test simultaneously in at the specified concurrency levels.
+ *
+ * @param testResult The results object to monitor the test results with.
+ */
+ public void run(TestResult testResult)
+ {
+ // log.debug("public void run(TestResult testResult = " + testResult + "): called");
+
+ // Loop through all of the specified concurrent levels for the test, provided shutdown has not been called.
+ for (int i = 0; (i < threads.length) && !shutdown; i++)
+ {
+ // Get the number of threads for this run.
+ int numThreads = threads[i];
+
+ // Create test thread handlers for all the threads.
+ TestThreadHandler[] threadHandlers = new TestThreadHandler[numThreads];
+
+ // Create a cyclic barrier for the test threads to synch their setups and teardowns on.
+ CyclicBarrier barrier = new CyclicBarrier(numThreads);
+
+ // Set up the test thread handlers to output results to the same test results object.
+ for (int j = 0; j < numThreads; j++)
+ {
+ threadHandlers[j] = new TestThreadHandler(testResult, test, barrier);
+ }
+
+ // Ensure the concurrency level statistic is set up correctly.
+ concurrencyLevel = numThreads;
+
+ // Begin batch.
+ if (testResult instanceof TKTestResult)
+ {
+ TKTestResult tkResult = (TKTestResult) testResult;
+ // tkResult.notifyStartBatch();
+ tkResult.setConcurrencyLevel(numThreads);
+
+ // Set the test result for the currently running tests, so that the shutdown hook can call it if necessary.
+ currentTestResult = tkResult;
+ }
+
+ // Run all the tests and wait for them all to finish.
+ executeAndWaitForRunnables(threadHandlers);
+
+ // Clear the test result for the currently running tests.
+ currentTestResult = null;
+
+ // End batch.
+ if (testResult instanceof TKTestResult)
+ {
+ TKTestResult tkResult = (TKTestResult) testResult;
+ tkResult.notifyEndBatch();
+ }
+
+ // Clear up all the test threads, they hold references to their associated TestResult object and Test object,
+ // which may prevent them from being garbage collected as the TestResult and Test objects are long lived.
+ for (int j = 0; j < numThreads; j++)
+ {
+ threadHandlers[j].testResult = null;
+ threadHandlers[j].test = null;
+ threadHandlers[j] = null;
+ }
+ }
+ }
+
+ /**
+ * Reports the number of tests that the scaled decorator is currently running concurrently.
+ *
+ * @return The number of tests that the scaled decorator is currently running concurrently.
+ */
+ public int getConcurrencyLevel()
+ {
+ return concurrencyLevel;
+ }
+
+ /**
+ * Executes all of the specifed runnable using the thread pool and waits for them all to complete.
+ *
+ * @param runnables The set of runnables to execute concurrently.
+ */
+ private void executeAndWaitForRunnables(Runnable[] runnables)
+ {
+ int numThreads = runnables.length;
+
+ // Used to keep track of the test threads in order to know when they have all completed.
+ Thread[] threads = new Thread[numThreads];
+
+ // Create all the test threads.
+ for (int j = 0; j < numThreads; j++)
+ {
+ threads[j] = new Thread(runnables[j]);
+ }
+
+ // Start all the test threads.
+ for (int j = 0; j < numThreads; j++)
+ {
+ threads[j].start();
+ }
+
+ // Wait for all the test threads to complete.
+ for (int j = 0; j < numThreads; j++)
+ {
+ try
+ {
+ threads[j].join();
+ }
+ catch (InterruptedException e)
+ {
+ // Restore the interrupted state of the thread.
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ /**
+ * Supplies the shut-down hook.
+ *
+ * @return The shut-down hook.
+ */
+ public Thread getShutdownHook()
+ {
+ return new Thread(new Runnable()
+ {
+ public void run()
+ {
+ // log.debug("ScaledTestDecorator::ShutdownHook: called");
+
+ // Set the shutdown flag so that no new tests are started.
+ shutdown = true;
+
+ // Check if tests are currently running, and ask them to complete as soon as possible. Allow
+ // a short pause for this to happen.
+ TKTestResult testResult = currentTestResult;
+
+ if (testResult != null)
+ {
+ // log.debug("There is a test result currently running tests, asking it to terminate ASAP.");
+ testResult.shutdownNow();
+
+ try
+ {
+ Thread.sleep(SHUTDOWN_PAUSE);
+ }
+ catch (InterruptedException e)
+ {
+ // Restore the interrupted state of the thread.
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Prints a string summarizing this test decorator, mainly for debugging purposes.
+ *
+ * @return String representation for debugging purposes.
+ */
+ public String toString()
+ {
+ return "ScaledTestDecorator: [ test = " + test + ", concurrencyLevel = " + concurrencyLevel + " ]";
+ }
+
+ /**
+ * TestThreadHandler is a runnable used to execute a test in. This is static to avoid implicit 'this' reference to
+ * the longer lived ScaledTestDecorator class. The scaled test decorator may execute many repeats but creates fresh
+ * handlers for each one. It re-uses the threads in a pool but does not re-use these handlers.
+ */
+ private static class TestThreadHandler implements Runnable
+ {
+ /** The test result object for the test to be run with. */
+ TestResult testResult;
+
+ /** The test to run. */
+ WrappedSuiteTestDecorator test;
+
+ /** Holds the cyclic barrier to synchronize on the end of the setups and before the tear downs. */
+ CyclicBarrier barrier;
+
+ /**
+ * Creates a new TestThreadHandler object.
+ *
+ * @param testResult The test result object for the test to be run with.
+ * @param test The test to run in a sperate thread.
+ * @param barrier The barrier implementation to use to synchronize per-thread setup completion and test
+ * completion before moving on through the setup, test, teardown phases. The barrier should
+ * be configured for the number of test threads.
+ */
+ TestThreadHandler(TestResult testResult, WrappedSuiteTestDecorator test, CyclicBarrier barrier)
+ {
+ this.testResult = testResult;
+ this.test = test;
+ this.barrier = barrier;
+ }
+
+ /**
+ * Runs the test associated with this pool.
+ */
+ public void run()
+ {
+ try
+ {
+ // Call setup on all underlying tests in the suite that are thread aware.
+ for (Test childTest : test.getAllUnderlyingTests())
+ {
+ // Check that the test is concurrency aware, so provides a setup method to call.
+ if (childTest instanceof TestThreadAware)
+ {
+ // Call the tests per thread setup.
+ TestThreadAware setupTest = (TestThreadAware) childTest;
+ setupTest.threadSetUp();
+ }
+ }
+
+ // Wait until all test threads have completed their setups.
+ barrier.await();
+
+ // Start timing the test batch, only after thread setups have completed.
+ if (testResult instanceof TKTestResult)
+ {
+ ((TKTestResult) testResult).notifyStartBatch();
+ }
+
+ // Run the tests.
+ test.run(testResult);
+
+ // Wait unitl all test threads have completed their tests.
+ barrier.await();
+
+ // Call tear down on all underlying tests in the suite that are thread aware.
+ for (Test childTest : test.getAllUnderlyingTests())
+ {
+ // Check that the test is concurrency aware, so provides a teardown method to call.
+ if (childTest instanceof TestThreadAware)
+ {
+ // Call the tests per thread tear down.
+ TestThreadAware setupTest = (TestThreadAware) childTest;
+ setupTest.threadTearDown();
+ }
+ }
+ }
+ catch (InterruptedException e)
+ {
+ // Restore the interrupted state of the thread.
+ Thread.currentThread().interrupt();
+ }
+ catch (BrokenBarrierException e)
+ {
+ // Set the interrupted state on the thread. The BrokenBarrierException may be caused where one thread
+ // waiting for the barrier is interrupted, causing the remaining threads correctly waiting on the
+ // barrier to fail. This condition is expected during test interruptions, and the response to it is to
+ // interrupt all the other threads running in the same scaled test.
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ /**
+ * Prints the name of the test for debugging purposes.
+ *
+ * @return The name of the test.
+ */
+ public String toString()
+ {
+ return "ScaledTestDecorator: [test = \"" + test + "\"]";
+ }
+ }
+}
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/SetupTaskAware.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/SetupTaskAware.java
new file mode 100644
index 0000000000..320a2c2ac5
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/SetupTaskAware.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2007 Rupert Smith.
+ *
+ * Licensed 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.junit.extensions;
+
+/**
+ * SetupTaskAware is an interface that tests that can accept injectable setup tasks may implement. Typically this
+ * is used by configurable decorator stack to inject setup tasks into tests. It is then up to the test case to run
+ * the tasks in the setup or threadSetup methods as it chooses.
+ *
+ * Set up tasks should be chained so that they are executed in the order that they are applied. Tear down tasks
+ * should be chained so that they are executed in the reverse order to which they are applied. That way the set up and
+ * tear down tasks act as a 'task' stack, with nested setups and tear downs.
+ *
+ *
CRC Card
+ *
Responsibilities.
+ *
Handle injection of set up tasks.
+ *
Handle injection of tear down tasks.
+ *
+ *
+ * @author Rupert Smith
+ */
+public interface SetupTaskAware
+{
+ /**
+ * Adds the specified task to the tests setup.
+ *
+ * @param task The task to add to the tests setup.
+ */
+ public void chainSetupTask(Runnable task);
+
+ /**
+ * Adds the specified task to the tests tear down.
+ *
+ * @param task The task to add to the tests tear down.
+ */
+ public void chainTearDownTask(Runnable task);
+}
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/SetupTaskHandler.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/SetupTaskHandler.java
new file mode 100644
index 0000000000..00736c59e5
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/SetupTaskHandler.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.junit.extensions;
+
+import org.apache.qpid.junit.extensions.util.StackQueue;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+/**
+ * SetupTaskHandler implements a task stack. It can be used, by delegation, as a base implementation for tests that want
+ * to have configurable setup/teardown task stacks. Typically it is up to the test implementation to decide whether the
+ * stack is executed in the setup/teardown methods or in the threadSetup/threadTeaddown methods.
+ *
+ *
CRC Card
+ *
Responsibilities
Collaborations
+ *
Handle injection of set up tasks.
+ *
Handle injection of tear down tasks.
+ *
Run set up tasks in chain order.
+ *
Run tear down tasks in reverse chain order.
+ *
+ *
+ * @author Rupert Smith
+ */
+public class SetupTaskHandler implements SetupTaskAware
+{
+ /** Holds the set up tasks. */
+ Queue setups = new LinkedList();
+
+ /** Holds the tear down tasks. */
+ Queue teardowns = new StackQueue();
+
+ /**
+ * Adds the specified task to the tests setup.
+ *
+ * @param task The task to add to the tests setup.
+ */
+ public void chainSetupTask(Runnable task)
+ {
+ setups.offer(task);
+ }
+
+ /**
+ * Adds the specified task to the tests tear down.
+ *
+ * @param task The task to add to the tests tear down.
+ */
+ public void chainTearDownTask(Runnable task)
+ {
+ teardowns.offer(task);
+ }
+
+ /**
+ * Runs the set up tasks in the order that they way chained.
+ */
+ public void runSetupTasks()
+ {
+ while (!setups.isEmpty())
+ {
+ setups.remove().run();
+ }
+ }
+
+ /**
+ * Runs the tear down tasks in the reverse of the order in which they were chained.
+ */
+ public void runTearDownTasks()
+ {
+ while (!teardowns.isEmpty())
+ {
+ teardowns.remove().run();
+ }
+ }
+}
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ShutdownHookable.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ShutdownHookable.java
new file mode 100644
index 0000000000..344d7abf82
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/ShutdownHookable.java
@@ -0,0 +1,42 @@
+/*
+ *
+ * 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.junit.extensions;
+
+/**
+ * Defines an interface that classes which supply shutdown hooks implement. Code that creates these classes can check
+ * if they supply a shutdown hook and register these hooks when the obejct are created.
+ *
+ *
CRC Card
+ *
Responsibilities
+ *
Supply a shutdown hook.
+ *
+ *
+ * @author Rupert Smith
+ */
+public interface ShutdownHookable
+{
+ /**
+ * Supplies the shutdown hook.
+ *
+ * @return The shut down hook.
+ */
+ public Thread getShutdownHook();
+}
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/SleepThrottle.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/SleepThrottle.java
new file mode 100644
index 0000000000..f7e350b1c7
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/SleepThrottle.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.junit.extensions;
+
+/**
+ * SleepThrottle is a Throttle implementation that generates short pauses using the thread sleep methods. As the pauses
+ * get shorter, this technique gets more innacurate. In practice, around 100 Hz is the cap rate for accuracy.
+ *
+ *
CRC Card
+ *
Responsibilities
Collaborations
+ *
Accept throttling rate in operations per second.
+ *
Inject short pauses to fill out processing cycles to a specified rate.
+ *
Check against a throttle speed without waiting.
+ *
+ *
+ * @author Rupert Smith
+ */
+public class SleepThrottle extends BaseThrottle implements Throttle
+{
+ /** Holds the time of the last call to the throttle method in nano seconds. */
+ private long lastTimeNanos;
+
+ /**
+ * This method can only be called at the rate set by the {@link #setRate} method, if it is called faster than this
+ * it will inject short pauses to restrict the call rate to that rate.
+ */
+ public void throttle()
+ {
+ // Get the current time in nanos.
+ long currentTimeNanos = System.nanoTime();
+
+ // Don't introduce any pause on the first call.
+ if (!firstCall)
+ {
+ // Check if there is any time left in the cycle since the last call to this method and introduce a short pause
+ // to fill that time if there is.
+ long remainingTimeNanos = cycleTimeNanos - (currentTimeNanos - lastTimeNanos);
+
+ if (remainingTimeNanos > 0)
+ {
+ long milliPause = remainingTimeNanos / 1000000;
+ int nanoPause = (int) (remainingTimeNanos % 1000000);
+
+ try
+ {
+ Thread.sleep(milliPause, nanoPause);
+ }
+ catch (InterruptedException e)
+ {
+ // Restore the interrupted thread, in-case the caller is checking for it.
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+ else
+ {
+ firstCall = false;
+ }
+
+ // Update the last time stamp.
+ lastTimeNanos = System.nanoTime();
+ }
+}
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TKTestResult.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TKTestResult.java
new file mode 100644
index 0000000000..c9bcf3eb66
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TKTestResult.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.junit.extensions;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestResult;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.junit.extensions.listeners.TKTestListener;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Properties;
+
+/**
+ * TKTestResult extends TestResult in order to calculate test timings, to pass the variable integer parameter for
+ * parameterized test cases to those test cases and to introduce an optional delay before test starts. Interested
+ * {@link TKTestListener}s may be attached to this and will be informed of all relevant test statistics.
+ *
+ *
CRC Card
+ *
Responsibilities
Collaborations
+ *
Calculate test timings.
+ *
Inform timing listeners of timings.
+ *
Inform memory listeners of memory readings.
+ *
Inform parameters listeners of parameters.
+ *
Pass the integer parameter to parameterized test cases.
+ *
Provide verbose test information on test start and end.
+ *
+ *
+ * @todo Move the verbose test information on test start/end into a test listener instead. It confuses the intention
+ * of this class. Could also move the delay into a listener but that seems less appropriate as it would be a
+ * side-effecting listener. Delay and timing calculation are fundamental enough to this class.
+ *
+ * @todo The need for this class to act as a place-holder for the integer parameter for parameterized test cases is
+ * because this behaviour has been factored out into a test decorator class, see {@link AsymptoticTestDecorator}.
+ * The {@link AsymptoticTestDecorator#run} method takes a TestResult as an argument and cannot easily get to the
+ * {@link AsymptoticTestCase} class other than through this class. The option of using this class as a place hold
+ * for this value was chosen. Alternatively this class could provide a method for decorators to access the
+ * underlying test case through and then leave the setting of this parameter to the decorator which is a more
+ * natural home for this behaviour. It would also provide a more general framework for decorators.
+ *
+ * @todo The memory usage may need to be moved in closer to the test method invocation so that as little code as possible
+ * exists between it and the test or the results may be obscured. In fact it certainly does as the teardown method
+ * is getting called first. Wouldn't be a bad idea to move the timing code in closer too.
+ *
+ * @todo Get rid of the delay logic. Will be replaced by throttle control.
+ *
+ * @author Rupert Smith
+ */
+public class TKTestResult extends TestResult
+{
+ /** Used for logging. */
+ private static final Logger log = Logger.getLogger(TKTestResult.class);
+
+ /** The delay between two tests. */
+ private int delay = 0;
+
+ /**
+ * This flag indicates that the #completeTest method of the timing controller has been called. Once this has
+ * been called once, the end test event for the whole test method should be ignored because tests have taken
+ * charge of outputing their own timings.
+ */
+ private boolean completeTestUsed = false;
+
+ /**
+ * Thread locals to hold test start time for non-instrumented tests. (Instrumented tests hold their own
+ * measurement data).
+ */
+ // private Hashtable threadStartTimeMap = new Hashtable();
+ private ThreadLocal threadLocals = new ThreadLocal();
+
+ /** Used to hold the current integer parameter to pass to parameterized tests. This defaults to 1. */
+ private int n = 1;
+
+ /** The timing listeners. */
+ private Collection tkListeners;
+
+ /** The test case name. */
+ private String testCaseName;
+
+ /** Used to hold the current concurrency level, set by the {@link ScaledTestDecorator}. */
+ private int concurrencyLevel = 1;
+
+ /** Flag used to indicate that this test result should attempt to complete its current tests as soon as possible. */
+ private boolean shutdownNow = false;
+
+ /** Holds the parametes that the test is run with. */
+ private Properties testParameters;
+
+ /**
+ * Creates a new TKTestResult object.
+ *
+ * @param delay A delay in milliseconds to introduce before every test start.
+ * @param testCaseName The name of the test case that this is the TestResult object for.
+ */
+ public TKTestResult(int delay, String testCaseName)
+ {
+ super();
+
+ /*log.debug("public TKTestResult(PrintStream writer, int " + delay + ", boolean " + verbose + ", String "
+ + testCaseName + "): called");*/
+
+ // Keep all the parameters that this is created with.
+ this.delay = delay;
+ this.testCaseName = testCaseName;
+ }
+
+ /**
+ * Callback method use to inform this test result that a test will be started. Waits for the configured delay time
+ * if one has been set, starts the timer, then delegates to the super class implementation.
+ *
+ * @param test The test to be started.
+ */
+ public void startTest(Test test)
+ {
+ // log.debug("public void startTest(Test test): called");
+
+ // If a delay time has been specified then wait for that length of time.
+ if (this.delay > 0)
+ {
+ try
+ {
+ Thread.sleep(delay);
+ }
+ catch (InterruptedException e)
+ {
+ // Ignore, but restore the interrupted flag.
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ // Create the thread local settings for the test.
+ ThreadLocalSettings threadLocalSettings = new ThreadLocalSettings();
+ threadLocals.set(threadLocalSettings);
+
+ // Record the test start time against this thread for calculating the test timing. (Consider using ThreadLocal
+ // instead?)
+ Long startTime = System.nanoTime();
+ threadLocalSettings.startTime = startTime;
+ // log.debug("startTime = " + startTime);
+
+ // Check if the test is timing controller aware, in which case set up a new timing controller and hold it
+ // in the thread local settings.
+ if (test instanceof TimingControllerAware)
+ {
+ TimingControllerAware controllerAware = (TimingControllerAware) test;
+ TimingControllerImpl controller =
+ new TimingControllerImpl(this, test, startTime, Thread.currentThread().getId());
+ controllerAware.setTimingController(controller);
+
+ threadLocalSettings.timingController = controller;
+ }
+
+ // Delegate to the super method to notify test event listeners.
+ super.startTest(test);
+ }
+
+ /**
+ * Callback method use to inform this result that a test was completed. This calculates how long the test took
+ * to run, then delegates to the super class implementation.
+ *
+ * @param test The test that has ended.
+ */
+ public void endTest(Test test)
+ {
+ // log.debug("public void endTest(Test test): called");
+
+ long runTime = 0;
+
+ // Recover the thread local settings.
+ ThreadLocalSettings threadLocalSettings = threadLocals.get();
+
+ // Check if the test is an instrumented test and get the timing information from the instrumentation as this
+ // will be more accurate.
+ if (test instanceof InstrumentedTest)
+ {
+ InstrumentedTest iTest = (InstrumentedTest) test;
+
+ // Calculate the test run time.
+ runTime = iTest.getTestTime();
+ // log.debug("runTime = " + runTime);
+
+ // Calculate the test memory usage.
+ long startMem = iTest.getTestStartMemory();
+ long endMem = iTest.getTestEndMemory();
+
+ // log.debug("startMem = " + startMem);
+ // log.debug("endMem = " + endMem);
+
+ // Inform any memory listeners of the test memory.
+ if (tkListeners != null)
+ {
+ for (TKTestListener memoryListener : tkListeners)
+ {
+ memoryListener.memoryUsed(test, startMem, endMem, null);
+ }
+ }
+ }
+ else
+ {
+ // Calculate the test run time.
+ long endTime = System.nanoTime();
+ Long startTime = threadLocalSettings.startTime;
+ runTime = endTime - startTime;
+ // log.debug("runTime = " + runTime);
+
+ threadLocals.remove();
+ }
+
+ // Output end test stats. This is only done when the tests have not used the timing controller to output
+ // mutiple timings.
+ if (!completeTestUsed)
+ {
+ // Check if the test is an asymptotic test case and get its int parameter if so.
+ if (test instanceof AsymptoticTestCase)
+ {
+ AsymptoticTestCase pTest = (AsymptoticTestCase) test;
+
+ // Set the parameter.
+ int paramValue = pTest.getN();
+
+ // Inform any parameter listeners of the test parameter.
+ if (tkListeners != null)
+ {
+ for (TKTestListener parameterListener : tkListeners)
+ {
+ parameterListener.parameterValue(test, paramValue, null);
+ }
+ }
+ }
+
+ // Inform any timing listeners of the test timing and concurrency level.
+ if (tkListeners != null)
+ {
+ for (TKTestListener tkListener : tkListeners)
+ {
+ TKTestListener next = tkListener;
+
+ next.timing(test, runTime, null);
+ next.concurrencyLevel(test, concurrencyLevel, null);
+ }
+ }
+
+ // Call the super method to notify test event listeners of the end event.
+ super.endTest(test);
+ }
+ }
+
+ /**
+ * Gets the integer parameter to pass to parameterized test cases.
+ *
+ * @return The value of the integer parameter.
+ */
+ public int getN()
+ {
+ return n;
+ }
+
+ /**
+ * Sets the integer parameter to pass to parameterized test cases.
+ *
+ * @param n The new value of the integer parameter.
+ */
+ public void setN(int n)
+ {
+ // log.debug("public void setN(int " + n + "): called");
+
+ this.n = n;
+ }
+
+ /**
+ * Adds a timing listener to pass all timing events to.
+ *
+ * @param listener The timing listener to register.
+ */
+ public void addTKTestListener(TKTestListener listener)
+ {
+ // Create the collection to hold the timing listeners if it does not already exist.
+ if (tkListeners == null)
+ {
+ tkListeners = new ArrayList();
+ }
+
+ // Keep the new timing listener.
+ tkListeners.add(listener);
+ }
+
+ /**
+ * Called by the test runner to notify this that a new test batch is being begun. This method forwards this
+ * notification to all batch listeners.
+ */
+ public void notifyStartBatch()
+ {
+ if (tkListeners != null)
+ {
+ for (TKTestListener batchListener : tkListeners)
+ {
+ batchListener.startBatch();
+ }
+ }
+ }
+
+ /**
+ * Called by the test runner to notify this that the current test batch has been ended. This method forwards this
+ * notification to all batch listener.
+ */
+ public void notifyEndBatch()
+ {
+ // log.debug("public void notifyEndBatch(): called");
+
+ if (tkListeners != null)
+ {
+ for (TKTestListener batchListener : tkListeners)
+ {
+ batchListener.endBatch(testParameters);
+ }
+ }
+ }
+
+ /**
+ * Called by the test runner to notify this of the properties that the test is using.
+ *
+ * @param properties The tests set/read properties.
+ */
+ public void notifyTestProperties(Properties properties)
+ {
+ // log.debug("public void notifyTestProperties(Properties properties): called");
+
+ this.testParameters = properties;
+
+ /*
+ if (tkListeners != null)
+ {
+ for (TKTestListener batchListener : tkListeners)
+ {
+ batchListener.properties(properties);
+ }
+ }
+ */
+ }
+
+ /**
+ * Intercepts the execution of a test case to pass the variable integer parameter to a test if it is a parameterized
+ * test case.
+ *
+ * @param test The test to run.
+ */
+ protected void run(final TestCase test)
+ {
+ // log.debug("protected void run(final TestCase test): called");
+
+ // Check if the test case is a parameterized test and set its integer parameter if so.
+ if (test instanceof AsymptoticTestCase)
+ {
+ AsymptoticTestCase pTest = (AsymptoticTestCase) test;
+
+ // Set up the integer parameter.
+ pTest.setN(n);
+ }
+
+ // Delegate to the super method to run the test.
+ super.run(test);
+ }
+
+ /**
+ * Helper method that generats a String of verbose information about a test. This includes the thread name, test
+ * class name and test method name.
+ *
+ * @param test The test to generate the info string for.
+ *
+ * @return Returns a string with the thread name, test class name and test method name.
+ */
+ protected String getTestInfo(Test test)
+ {
+ // log.debug("protected String getTestInfo(Test test): called");
+
+ return "[" + Thread.currentThread().getName() + "@" + test.getClass().getName() + "."
+ + ((test instanceof TestCase) ? ((TestCase) test).getName() : "") + "]";
+ }
+
+ /**
+ * Sets the concurrency level to pass into the test result.
+ *
+ * @param concurrencyLevel The concurrency level the tests are running out.
+ */
+ public void setConcurrencyLevel(int concurrencyLevel)
+ {
+ this.concurrencyLevel = concurrencyLevel;
+ }
+
+ /**
+ * Tells this test result that it should stop running tests. Once this method has been called this test result
+ * will not start any new tests, and any tests that use the timing controller will be passed interrupted exceptions,
+ * to indicate that they should end immediately. Usually the caller of this method will introduce a short wait
+ * to allow an opporunity for running tests to complete, before forcing the shutdown of the JVM.
+ */
+ public void shutdownNow()
+ {
+ log.debug("public void shutdownNow(): called on " + this);
+
+ shutdownNow = true;
+ }
+
+ /**
+ * Prints a string summary of this class, mainly for debugging purposes.
+ *
+ * @return A string summary of this class, mainly for debugging purposes.
+ */
+ public String toString()
+ {
+ return "TKTestResult@" + Integer.toString(hashCode(), 16) + ": [ testCaseName = " + testCaseName + ", n = " + n
+ + ", tkListeners = " + tkListeners + " ]";
+ }
+
+ /**
+ * Holds things that need to be kept on a per thread basis for each test invocation, such as the test start
+ * time and its timing controller.
+ */
+ private static class ThreadLocalSettings
+ {
+ /** Holds the test start time. */
+ Long startTime;
+
+ /** Holds the test threads timing controller. */
+ TimingController timingController;
+ }
+
+ /**
+ * Provides an implementation of the {@link TimingController} interface that timing aware tests can use to call
+ * back to reset timers, and register additional test timings.
+ */
+ private static class TimingControllerImpl implements TimingController
+ {
+ /** Holds an explicit reference to the test TKTestResult that created this. */
+ TKTestResult testResult;
+
+ /** Holds a reference to the test that this is the timing controller for. */
+ Test test;
+
+ /** Holds the start time for this timing controller. This gets reset to now on each completed test. */
+ long startTime;
+
+ /**
+ * Holds the thread id of the thread that started the test, so that this controller may be called from other
+ * threads but still identify itself correctly to {@link TKTestListener}s as being associated with the
+ * thread that called the test method.
+ */
+ long threadId;
+
+ /**
+ * Creates a timing controller on a specified TKTestResult and a test.
+ *
+ * @param testResult The TKTestResult that this controller interacts with.
+ * @param test The test that this is the timing controller for.
+ * @param startTime The test start time in nanoseconds.
+ * @param threadId The thread id of the thread that is calling the test method.
+ */
+ public TimingControllerImpl(TKTestResult testResult, Test test, long startTime, long threadId)
+ {
+ this.testResult = testResult;
+ this.test = test;
+ this.startTime = startTime;
+ this.threadId = threadId;
+ }
+
+ /**
+ * Gets the timing controller associated with the current test thread. Tests that use timing controller should
+ * always get the timing controller from this method in the same thread that called the setUp, tearDown or test
+ * method. The controller returned by this method may be called from any thread because it remembers the thread
+ * id of the original test thread.
+ *
+ * @return The timing controller associated with the current test thread.
+ */
+ public TimingController getControllerForCurrentThread()
+ {
+ // Recover the thread local settings and extract the timing controller from them.
+ ThreadLocalSettings threadLocalSettings = testResult.threadLocals.get();
+
+ return threadLocalSettings.timingController;
+ }
+
+ /**
+ * Not implemented yet.
+ *
+ * @return Nothing.
+ */
+ public long suspend()
+ {
+ throw new RuntimeException("Method not implemented.");
+ }
+
+ /**
+ * Not implemented yet.
+ *
+ * @return Nothing.
+ */
+ public long resume()
+ {
+ throw new RuntimeException("Method not implemented.");
+ }
+
+ /**
+ * Resets the timer start time to now.
+ *
+ * @return The new value of the start time.
+ */
+ public long restart()
+ {
+ startTime = System.nanoTime();
+
+ return startTime;
+ }
+
+ /**
+ * Register an additional pass/fail for the current test. The test result is assumed to apply to a test of
+ * 'size' parmeter 1. Use the {@link #completeTest(boolean, int)} method to register timings with parameters.
+ *
+ * @param testPassed Whether or not this timing is for a test pass or fail.
+ *
+ * @throws InterruptedException If the test runner decides that testing should stop it throws this exception to
+ * indicate to the test method that it should stop immediately.
+ */
+ public void completeTest(boolean testPassed) throws InterruptedException
+ {
+ completeTest(testPassed, 1);
+ }
+
+ /**
+ * Register an additional pass/fail for the current test. The test result is applies to a test of the specified
+ * 'size' parmeter.
+ *
+ * @param testPassed Whether or not this timing is for a test pass or fail.
+ * @param param The test parameter size for parameterized tests.
+ *
+ * @throws InterruptedException If the test runner decides that testing should stop it throws this exception to
+ * indicate to the test method that it should stop immediately.
+ */
+ public void completeTest(boolean testPassed, int param) throws InterruptedException
+ {
+ /*log.debug("public long completeTest(boolean testPassed = " + testPassed + ", int param = " + param
+ + "): called");*/
+
+ // Calculate the test run time.
+ long endTime = System.nanoTime();
+ long runTime = endTime - startTime;
+ // log.debug("runTime = " + runTime);
+
+ // Reset the test start time to now, to reset the timer for the next result.
+ startTime = endTime;
+
+ completeTest(testPassed, param, runTime);
+ }
+
+ /**
+ * Register an additional pass/fail for the current test. The test result is applies to a test of the specified
+ * 'size' parmeter and allows the caller to sepecify the timing to log.
+ *
+ * @param testPassed Whether or not this timing is for a test pass or fail.
+ * @param param The test parameter size for parameterized tests.
+ * @param timeNanos The time in nano-seconds to log the test result with.
+ *
+ * @throws InterruptedException If the test runner decides that testing should stop it throws this exception to
+ * indicate to the test method that it should stop immediately.
+ */
+ public void completeTest(boolean testPassed, int param, long timeNanos) throws InterruptedException
+ {
+ log.debug("public void completeTest(boolean testPassed, int param, long timeNanos): called");
+ log.debug("testResult = " + testResult);
+
+ // Tell the test result that completeTest has been used, so to not register end test events for the whole
+ // test method.
+ testResult.completeTestUsed = true;
+
+ // Inform any timing listeners of the test timings and parameters and send an end test notification using
+ // the thread id of the thread that started the test.
+ if (testResult.tkListeners != null)
+ {
+ for (TKTestListener listener : testResult.tkListeners)
+ {
+ listener.reset(test, threadId);
+ listener.timing(test, timeNanos, threadId);
+ listener.parameterValue(test, param, threadId);
+ listener.concurrencyLevel(test, testResult.concurrencyLevel, threadId);
+
+ if (!testPassed)
+ {
+ listener.addFailure(test, null, threadId);
+ }
+
+ listener.endTest(test, threadId);
+ }
+ }
+
+ // log.debug("testResult.shutdownNow = " + testResult.shutdownNow);
+
+ // Check if the test runner has been asked to shutdown and raise an interuppted exception if so.
+ if (testResult.shutdownNow)
+ {
+ // log.debug("The shutdown flag is set.");
+
+ throw new InterruptedException("Attempting clean shutdown by suspending current test.");
+ }
+ }
+ }
+}
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TKTestRunner.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TKTestRunner.java
new file mode 100644
index 0000000000..7955a2e2e9
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TKTestRunner.java
@@ -0,0 +1,694 @@
+/*
+ *
+ * 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.junit.extensions;
+
+import junit.framework.Test;
+import junit.framework.TestResult;
+import junit.framework.TestSuite;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.junit.extensions.listeners.CSVTestListener;
+import org.apache.qpid.junit.extensions.listeners.ConsoleTestListener;
+import org.apache.qpid.junit.extensions.listeners.XMLTestListener;
+import org.apache.qpid.junit.extensions.util.CommandLineParser;
+import org.apache.qpid.junit.extensions.util.MathUtils;
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+import org.apache.qpid.junit.extensions.util.TestContextProperties;
+
+import java.io.*;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * TKTestRunner extends {@link junit.textui.TestRunner} with the ability to run tests multiple times, to execute a test
+ * simultaneously using many threads, to put a delay between test runs and adds support for tests that take integer
+ * parameters that can be 'stepped' through on multiple test runs. These features can be accessed by using this class
+ * as an entry point and passing command line arguments to specify which features to use:
+ *
+ *
+ * -w ms The number of milliseconds between invocations of test cases.
+ * -c pattern The number of tests to run concurrently.
+ * -r num The number of times to repeat each test.
+ * -d duration The length of time to run the tests for.
+ * -t name The name of the test case to execute.
+ * -s pattern The size parameter to run tests with.
+ * -o dir The name of the directory to output test timings to.
+ * --csv Output test results in CSV format.
+ * --xml Output test results in XML format.
+ *
+ *
+ * This command line may also have trailing 'name=value' parameters added to it. All of these values are added
+ * to the test context properties and passed to the test, which can access them by name.
+ *
+ * The pattern arguments are of the form [lowest(: ...)(: highest)](:sample=s)(:exp), where round brackets
+ * enclose optional values. Using this pattern form it is possible to specify a single value, a range of values divided
+ * into s samples, a range of values divided into s samples but distributed exponentially, or a fixed set of samples.
+ *
+ * The duration arguments are of the form (dD)(hH)(mM)(sS), where round brackets enclose optional values. At least
+ * one of the optional values must be present.
+ *
+ * When specifying optional test parameters on the command line, in 'name=value' format, it is also possible to use
+ * the format 'name=[value1:value2:value3:...]', to specify multiple values for a parameter. All permutations of all
+ * parameters with multiple values will be created and tested. If the values are numerical, it is also possible to use
+ * the sequence generation patterns instead of fully specifying all of the values.
+ *
+ * Here are some examples:
+ *
+ *
+ *
-c [10:20:30:40:50]
Runs the test with 10,20,...,50 threads.
+ *
-s [1:100]:samples=10
+ *
Runs the test with ten different size parameters evenly spaced between 1 and 100.
+ *
-s [1:1000000]:samples=10:exp
+ *
Runs the test with ten different size parameters exponentially spaced between 1 and 1000000.
+ *
-r 10
Runs each test ten times.
+ *
-d 10H
Runs the test repeatedly for 10 hours.
+ *
-d 1M, -r 10
+ *
Runs the test repeatedly for 1 minute but only takes a timing sample every 10 test runs.
+ *
-r 10, -c [1:5:10:50], -s [100:1000:10000]
+ *
Runs 12 test cycles (4 concurrency samples * 3 size sample), with 10 repeats each. In total the test
+ * will be run 199 times (3 + 15 + 30 + 150)
+ *
cache=true
Passes the 'cache' parameter with value 'true' to the test.
+ *
cache=[true:false]
Runs the test with the 'cache' parameter set to 'true' and 'false'.
+ *
cacheSize=[1000:1000000],samples=4,exp
+ *
Runs the test with the 'cache' parameter set to a series of exponentially increasing sizes.
+ *
+ *
+ *
CRC Card
+ *
Responsibilities
Collaborations
+ *
Create the test configuration specified by the command line parameters.
+ *
+ *
+ * @todo Verify that the output directory exists or can be created.
+ *
+ * @todo Verify that the specific named test case to execute exists.
+ *
+ * @todo Drop the delay parameter as it is being replaced by throttling.
+ *
+ * @todo Completely replace the test ui test runner, instead of having TKTestRunner inherit from it, its just not
+ * good code to extend.
+ *
+ * @author Rupert Smith
+ */
+public class TKTestRunner extends TestRunnerImprovedErrorHandling
+{
+ /** Used for debugging. */
+ private static final Logger log = Logger.getLogger(TKTestRunner.class);
+
+ /** Used for displaying information on the console. */
+ // private static final Logger console = Logger.getLogger("CONSOLE." + TKTestRunner.class.getName());
+
+ /** Used for generating the timestamp when naming output files. */
+ protected static final DateFormat TIME_STAMP_FORMAT = new SimpleDateFormat("yyyy-MM-dd-HH.mm.ss");
+
+ /** Number of times to rerun the test. */
+ protected Integer repetitions = 1;
+
+ /** The length of time to run the tests for. */
+ protected Long duration;
+
+ /** Number of threads running the tests. */
+ protected int[] threads;
+
+ /** Delay in ms to wait between two test cases. */
+ protected int delay = 0;
+
+ /** The parameter values to pass to parameterized tests. */
+ protected int[] params;
+
+ /** Name of the single test case to execute. */
+ protected String testCaseName = null;
+
+ /** Name of the test class. */
+ protected String testClassName = null;
+
+ /** Name of the test run. */
+ protected String testRunName = null;
+
+ /** Directory to output XML reports into, if specified. */
+ protected String reportDir = null;
+
+ /** Flag that indicates the CSV results listener should be used to output results. */
+ protected boolean csvResults;
+
+ /** Flag that indiciates the XML results listener should be used to output results. */
+ protected boolean xmlResults;
+
+ /**
+ * Holds the name of the class of the test currently being run. Ideally passed into the {@link #createTestResult}
+ * method, but as the signature is already fixed for this, the current value gets pushed here as a member variable.
+ */
+ protected String currentTestClassName;
+
+ /** Holds the test results object, which is reponsible for instrumenting tests/threads to record results. */
+ protected TKTestResult result;
+
+ /** Holds a list of factories for instantiating optional user specified test decorators. */
+ protected List decoratorFactories;
+
+ /**
+ * Constructs a TKTestRunner using System.out for all the output.
+ *
+ * @param repetitions The number of times to repeat the test, or test batch size.
+ * @param duration The length of time to run the tests for. -1 means no duration has been set.
+ * @param threads The concurrency levels to ramp up to.
+ * @param delay A delay in milliseconds between test runs.
+ * @param params The sets of 'size' parameters to pass to test.
+ * @param testCaseName The name of the test case to run.
+ * @param reportDir The directory to output the test results to.
+ * @param runName The name of the test run; used to name the output file.
+ * @param csvResults true if the CSV results listener should be attached.
+ * @param xmlResults true if the XML results listener should be attached.
+ * @param decoratorFactories List of factories for user specified decorators.
+ */
+ public TKTestRunner(Integer repetitions, Long duration, int[] threads, int delay, int[] params, String testCaseName,
+ String reportDir, String runName, boolean csvResults, boolean xmlResults,
+ List decoratorFactories)
+ {
+ super(new NullResultPrinter(System.out));
+
+ log.debug("public TKTestRunner(): called");
+
+ // Keep all the test parameters.
+ this.repetitions = repetitions;
+ this.duration = duration;
+ this.threads = threads;
+ this.delay = delay;
+ this.params = params;
+ this.testCaseName = testCaseName;
+ this.reportDir = reportDir;
+ this.testRunName = runName;
+ this.csvResults = csvResults;
+ this.xmlResults = xmlResults;
+ this.decoratorFactories = decoratorFactories;
+ }
+
+ /**
+ * The entry point for the toolkit test runner.
+ *
+ * @param args The command line arguments.
+ */
+ public static void main(String[] args)
+ {
+ // Use the command line parser to evaluate the command line.
+ CommandLineParser commandLine =
+ new CommandLineParser(
+ new String[][]
+ {
+ { "w", "The number of milliseconds between invocations of test cases.", "ms", "false" },
+ { "c", "The number of tests to run concurrently.", "num", "false", MathUtils.SEQUENCE_REGEXP },
+ { "r", "The number of times to repeat each test.", "num", "false" },
+ { "d", "The length of time to run the tests for.", "duration", "false", MathUtils.DURATION_REGEXP },
+ { "f", "The maximum rate to call the tests at.", "frequency", "false", "^([1-9][0-9]*)/([1-9][0-9]*)$" },
+ { "s", "The size parameter to run tests with.", "size", "false", MathUtils.SEQUENCE_REGEXP },
+ { "t", "The name of the test case to execute.", "name", "false" },
+ { "o", "The name of the directory to output test timings to.", "dir", "false" },
+ { "n", "A name for this test run, used to name the output file.", "name", "true" },
+ {
+ "X:decorators", "A list of additional test decorators to wrap the tests in.",
+ "\"class.name[:class.name]*\"", "false"
+ },
+ { "1", "Test class.", "class", "true" },
+ { "-csv", "Output test results in CSV format.", null, "false" },
+ { "-xml", "Output test results in XML format.", null, "false" }
+ });
+
+ // Capture the command line arguments or display errors and correct usage and then exit.
+ ParsedProperties options = null;
+
+ try
+ {
+ options = new ParsedProperties(commandLine.parseCommandLine(args));
+ }
+ catch (IllegalArgumentException e)
+ {
+ System.out.println(commandLine.getErrors());
+ System.out.println(commandLine.getUsage());
+ System.exit(FAILURE_EXIT);
+ }
+
+ // Extract the command line options.
+ Integer delay = options.getPropertyAsInteger("w");
+ String threadsString = options.getProperty("c");
+ Integer repetitions = options.getPropertyAsInteger("r");
+ String durationString = options.getProperty("d");
+ String paramsString = options.getProperty("s");
+ String testCaseName = options.getProperty("t");
+ String reportDir = options.getProperty("o");
+ String testRunName = options.getProperty("n");
+ String decorators = options.getProperty("X:decorators");
+ String testClassName = options.getProperty("1");
+ boolean csvResults = options.getPropertyAsBoolean("-csv");
+ boolean xmlResults = options.getPropertyAsBoolean("-xml");
+
+ int[] threads = (threadsString == null) ? null : MathUtils.parseSequence(threadsString);
+ int[] params = (paramsString == null) ? null : MathUtils.parseSequence(paramsString);
+ Long duration = (durationString == null) ? null : MathUtils.parseDuration(durationString);
+
+ // The test run name defaults to the test class name unless a value was specified for it.
+ testRunName = (testRunName == null) ? testClassName : testRunName;
+
+ // Add all the command line options and trailing settings to test context properties. Tests may pick up
+ // overridden values from there, and these values will be logged in the test results, for analysis and
+ // to make tests repeatable.
+ commandLine.addTrailingPairsToProperties(TestContextProperties.getInstance());
+ commandLine.addOptionsToProperties(TestContextProperties.getInstance());
+
+ // Create and start the test runner.
+ try
+ {
+ // Create a list of test decorator factories for use specified decorators to be applied.
+ List decoratorFactories = parseDecorators(decorators);
+
+ TKTestRunner testRunner =
+ new TKTestRunner(repetitions, duration, threads, (delay == null) ? 0 : delay, params, testCaseName,
+ reportDir, testRunName, csvResults, xmlResults, decoratorFactories);
+
+ TestResult testResult = testRunner.start(testClassName);
+
+ if (!testResult.wasSuccessful())
+ {
+ System.exit(FAILURE_EXIT);
+ }
+ }
+ catch (Exception e)
+ {
+ System.err.println(e.getMessage());
+ e.printStackTrace(new PrintStream(System.err));
+ System.exit(EXCEPTION_EXIT);
+ }
+ }
+
+ /**
+ * Parses a list of test decorators, in the form "class.name[:class.name]*", and creates factories for those
+ * TestDecorator classes , and returns a list of the factories. This list of factories will be in the same
+ * order as specified in the string. The factories can be used to succesively wrap tests in layers of
+ * decorators, as decorators themselves implement the 'Test' interface.
+ *
+ * If the string fails to parse, or if any of the decorators specified in it are cannot be loaded, or are not
+ * TestDecorators, a runtime exception with a suitable error message will be thrown. The factories themselves
+ * throw runtimes if the constructor method calls on the decorators fail.
+ *
+ * @param decorators The decorators list to be parsed.
+ *
+ * @return A list of instantiated decorators.
+ */
+ protected static List parseDecorators(String decorators)
+ {
+ List result = new LinkedList();
+ String toParse = decorators;
+
+ // Check that the decorators string is not null or empty, returning an empty list of decorator factories it
+ // it is.
+ if ((decorators == null) || "".equals(decorators))
+ {
+ return result;
+ }
+
+ // Strip any leading and trailing quotes from the string.
+ if (toParse.charAt(0) == '\"')
+ {
+ toParse = toParse.substring(1, toParse.length() - 1);
+ }
+
+ if (toParse.charAt(toParse.length() - 1) == '\"')
+ {
+ toParse = toParse.substring(0, toParse.length() - 2);
+ }
+
+ // Instantiate all decorators.
+ for (String decoratorClassName : toParse.split(":"))
+ {
+ try
+ {
+ Class decoratorClass = Class.forName(decoratorClassName);
+ final Constructor decoratorConstructor = decoratorClass.getConstructor(WrappedSuiteTestDecorator.class);
+
+ // Check that the decorator is an instance of WrappedSuiteTestDecorator.
+ if (!WrappedSuiteTestDecorator.class.isAssignableFrom(decoratorClass))
+ {
+ throw new RuntimeException("The decorator class " + decoratorClassName
+ + " is not a sub-class of WrappedSuiteTestDecorator, which it needs to be.");
+ }
+
+ result.add(new TestDecoratorFactory()
+ {
+ public WrappedSuiteTestDecorator decorateTest(Test test)
+ {
+ try
+ {
+ return (WrappedSuiteTestDecorator) decoratorConstructor.newInstance(test);
+ }
+ catch (InstantiationException e)
+ {
+ throw new RuntimeException(
+ "The decorator class " + decoratorConstructor.getDeclaringClass().getName()
+ + " cannot be instantiated.", e);
+ }
+ catch (IllegalAccessException e)
+ {
+ throw new RuntimeException(
+ "The decorator class " + decoratorConstructor.getDeclaringClass().getName()
+ + " does not have a publicly accessable constructor.", e);
+ }
+ catch (InvocationTargetException e)
+ {
+ throw new RuntimeException(
+ "The decorator class " + decoratorConstructor.getDeclaringClass().getName()
+ + " cannot be invoked.", e);
+ }
+ }
+ });
+ }
+ catch (ClassNotFoundException e)
+ {
+ throw new RuntimeException("The decorator class " + decoratorClassName + " could not be found.", e);
+ }
+ catch (NoSuchMethodException e)
+ {
+ throw new RuntimeException("The decorator class " + decoratorClassName
+ + " does not have a constructor that accepts a single 'WrappedSuiteTestDecorator' argument.", e);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * TestDecoratorFactory is a factory for creating test decorators from tests.
+ */
+ protected interface TestDecoratorFactory
+ {
+ /**
+ * Decorates the specified test with a new decorator.
+ *
+ * @param test The test to decorate.
+ *
+ * @return The decorated test.
+ */
+ public WrappedSuiteTestDecorator decorateTest(Test test);
+ }
+
+ /**
+ * Runs a test or suite of tests, using the super class implemenation. This method wraps the test to be run
+ * in any test decorators needed to add in the configured toolkits enhanced junit functionality.
+ *
+ * @param test The test to run.
+ * @param wait Undocumented. Nothing in the JUnit javadocs to say what this is for.
+ *
+ * @return The results of the test run.
+ */
+ public TestResult doRun(Test test, boolean wait)
+ {
+ log.debug("public TestResult doRun(Test \"" + test + "\", boolean " + wait + "): called");
+
+ // Wrap the tests in decorators for duration, scaling, repetition, parameterization etc.
+ WrappedSuiteTestDecorator targetTest = decorateTests(test);
+
+ // Delegate to the super method to run the decorated tests.
+ log.debug("About to call super.doRun");
+
+ TestResult result = super.doRun(targetTest, wait);
+ log.debug("super.doRun returned.");
+
+ /*if (result instanceof TKTestResult)
+ {
+ TKTestResult tkResult = (TKTestResult) result;
+
+ tkResult.notifyEndBatch();
+ }*/
+
+ return result;
+ }
+
+ /**
+ * Applies test decorators to the tests for parameterization, duration, scaling and repetition.
+ *
+ * @param test The test to decorat.
+ *
+ * @return The decorated test.
+ */
+ protected WrappedSuiteTestDecorator decorateTests(Test test)
+ {
+ log.debug("params = " + ((params == null) ? null : MathUtils.printArray(params)));
+ log.debug("repetitions = " + repetitions);
+ log.debug("threads = " + ((threads == null) ? null : MathUtils.printArray(threads)));
+ log.debug("duration = " + duration);
+
+ // Wrap all tests in the test suite with WrappedSuiteTestDecorators. This is quite ugly and a bit baffling,
+ // but the reason it is done is because the JUnit implementation of TestDecorator has some bugs in it.
+ WrappedSuiteTestDecorator targetTest = null;
+
+ if (test instanceof TestSuite)
+ {
+ log.debug("targetTest is a TestSuite");
+
+ TestSuite suite = (TestSuite) test;
+
+ int numTests = suite.countTestCases();
+ log.debug("There are " + numTests + " in the suite.");
+
+ for (int i = 0; i < numTests; i++)
+ {
+ Test nextTest = suite.testAt(i);
+ log.debug("suite.testAt(" + i + ") = " + nextTest);
+
+ if (nextTest instanceof TimingControllerAware)
+ {
+ log.debug("nextTest is TimingControllerAware");
+ }
+
+ if (nextTest instanceof TestThreadAware)
+ {
+ log.debug("nextTest is TestThreadAware");
+ }
+ }
+
+ targetTest = new WrappedSuiteTestDecorator(suite);
+ log.debug("Wrapped with a WrappedSuiteTestDecorator.");
+ }
+ // If the test has already been wrapped, no need to do it again.
+ else if (test instanceof WrappedSuiteTestDecorator)
+ {
+ targetTest = (WrappedSuiteTestDecorator) test;
+ }
+
+ // If size parameter values have been set, then wrap the test in an asymptotic test decorator.
+ if (params != null)
+ {
+ targetTest = new AsymptoticTestDecorator(targetTest, params, (repetitions == null) ? 1 : repetitions);
+ log.debug("Wrapped with asymptotic test decorator.");
+ log.debug("targetTest = " + targetTest);
+ }
+
+ // If no size parameters are set but the repitions parameter is, then wrap the test in an asymptotic test decorator.
+ else if ((repetitions != null) && (repetitions > 1))
+ {
+ targetTest = new AsymptoticTestDecorator(targetTest, new int[] { 1 }, repetitions);
+ log.debug("Wrapped with asymptotic test decorator.");
+ log.debug("targetTest = " + targetTest);
+ }
+
+ // Apply any optional user specified decorators.
+ targetTest = applyOptionalUserDecorators(targetTest);
+
+ // If a test run duration has been set then wrap the test in a duration test decorator. This will wrap on
+ // top of size, repeat or concurrency wrappings already applied.
+ if (duration != null)
+ {
+ DurationTestDecorator durationTest = new DurationTestDecorator(targetTest, duration);
+ targetTest = durationTest;
+
+ log.debug("Wrapped with duration test decorator.");
+ log.debug("targetTest = " + targetTest);
+
+ registerShutdownHook(durationTest);
+ }
+
+ // ParameterVariationTestDecorator...
+
+ // If a test thread concurrency level is set then wrap the test in a scaled test decorator. This will wrap on
+ // top of size scaling or repetition wrappings.
+ ScaledTestDecorator scaledDecorator;
+
+ if ((threads != null) && ((threads.length > 1) || (MathUtils.maxInArray(threads) > 1)))
+ {
+ scaledDecorator = new ScaledTestDecorator(targetTest, threads);
+ targetTest = scaledDecorator;
+ log.debug("Wrapped with scaled test decorator.");
+ log.debug("targetTest = " + targetTest);
+ }
+ else
+ {
+ scaledDecorator = new ScaledTestDecorator(targetTest, new int[] { 1 });
+ targetTest = scaledDecorator;
+ log.debug("Wrapped with scaled test decorator with default of 1 thread.");
+ log.debug("targetTest = " + targetTest);
+ }
+
+ // Register the scaled test decorators shutdown hook.
+ registerShutdownHook(scaledDecorator);
+
+ return targetTest;
+ }
+
+ /**
+ * If there were any user specified test decorators on the command line, this method instantiates them and wraps
+ * the test in them, from inner-most to outer-most in the order in which the decorators were supplied on the
+ * command line.
+ *
+ * @param targetTest The test to wrap.
+ *
+ * @return A wrapped test.
+ */
+ protected WrappedSuiteTestDecorator applyOptionalUserDecorators(WrappedSuiteTestDecorator targetTest)
+ {
+ // If there are user defined test decorators apply them in order now.
+ for (TestDecoratorFactory factory : decoratorFactories)
+ {
+ targetTest = factory.decorateTest(targetTest);
+ }
+
+ return targetTest;
+ }
+
+ /**
+ * Creates the TestResult object to be used for test runs. See {@link TKTestResult} for more information and the
+ * enhanced test result class that this uses.
+ *
+ * @return An instance of the enhanced test result object, {@link TKTestResult}.
+ */
+ protected TestResult createTestResult()
+ {
+ log.debug("protected TestResult createTestResult(): called");
+
+ TKTestResult result = new TKTestResult(delay, testCaseName);
+
+ // Check if a directory to output reports to has been specified and attach test listeners if so.
+ if (reportDir != null)
+ {
+ // Create the report directory if it does not already exist.
+ File reportDirFile = new File(reportDir);
+
+ if (!reportDirFile.exists())
+ {
+ reportDirFile.mkdir();
+ }
+
+ // Create the results file (make the name of this configurable as a command line parameter).
+ Writer timingsWriter;
+
+ // Always set up a console feedback listener.
+ ConsoleTestListener feedbackListener = new ConsoleTestListener();
+ result.addListener(feedbackListener);
+ result.addTKTestListener(feedbackListener);
+
+ // Set up an XML results listener to output the timings to the results file, if requested on the command line.
+ if (xmlResults)
+ {
+ try
+ {
+ File timingsFile = new File(reportDirFile, "TEST-" + currentTestClassName + ".xml");
+ timingsWriter = new BufferedWriter(new FileWriter(timingsFile), 20000);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("Unable to create the log file to write test results to: " + e, e);
+ }
+
+ XMLTestListener listener = new XMLTestListener(timingsWriter, currentTestClassName);
+ result.addListener(listener);
+ result.addTKTestListener(listener);
+
+ registerShutdownHook(listener);
+ }
+
+ // Set up an CSV results listener to output the timings to the results file, if requested on the command line.
+ if (csvResults)
+ {
+ try
+ {
+ File timingsFile =
+ new File(reportDirFile, testRunName + "-" + TIME_STAMP_FORMAT.format(new Date()) + "-timings.csv");
+ timingsWriter = new BufferedWriter(new FileWriter(timingsFile), 20000);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("Unable to create the log file to write test results to: " + e, e);
+ }
+
+ CSVTestListener listener = new CSVTestListener(timingsWriter);
+ result.addListener(listener);
+ result.addTKTestListener(listener);
+
+ // Register the results listeners shutdown hook to flush its data if the test framework is shutdown
+ // prematurely.
+ registerShutdownHook(listener);
+ }
+
+ // Register the results listeners shutdown hook to flush its data if the test framework is shutdown
+ // prematurely.
+ // registerShutdownHook(listener);
+
+ // Record the start time of the batch.
+ // result.notifyStartBatch();
+
+ // At this point in time the test class has been instantiated, giving it an opportunity to read its parameters.
+ // Inform any test listers of the test properties.
+ result.notifyTestProperties(TestContextProperties.getAccessedProps());
+ }
+
+ return result;
+ }
+
+ /**
+ * Registers the shutdown hook of a {@link ShutdownHookable}.
+ *
+ * @param hookable The hookable to register.
+ */
+ protected void registerShutdownHook(ShutdownHookable hookable)
+ {
+ Runtime.getRuntime().addShutdownHook(hookable.getShutdownHook());
+ }
+
+ /**
+ * Initializes the test runner with the provided command line arguments and and starts the test run.
+ *
+ * @param testClassName The fully qualified name of the test class to run.
+ *
+ * @return The test results.
+ *
+ * @throws Exception Any exceptions from running the tests are allowed to fall through.
+ */
+ protected TestResult start(String testClassName) throws Exception
+ {
+ // Record the current test class, so that the test results can be output to a file incorporating this name.
+ this.currentTestClassName = testClassName;
+
+ // Delegate to the super method to run the tests.
+ return super.start(new String[] { testClassName });
+ }
+}
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TestRunnerImprovedErrorHandling.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TestRunnerImprovedErrorHandling.java
new file mode 100644
index 0000000000..9b4a8707db
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TestRunnerImprovedErrorHandling.java
@@ -0,0 +1,131 @@
+/*
+ *
+ * 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.junit.extensions;
+
+import junit.framework.Test;
+import junit.framework.TestResult;
+
+import junit.runner.Version;
+
+import junit.textui.ResultPrinter;
+import junit.textui.TestRunner;
+
+import org.apache.log4j.Logger;
+
+import java.io.PrintStream;
+
+/**
+ * The {@link junit.textui.TestRunner} does not provide very good error handling. It does not wrap exceptions and
+ * does not print out stack traces, losing valuable error tracing information. This class overrides methods in it
+ * in order to improve their error handling. The {@link TKTestRunner} is then built on top of this.
+ *
+ *
CRC Card
+ *
Responsibilities
Collaborations
+ *
+ *
+ * @author Rupert Smith
+ */
+public class TestRunnerImprovedErrorHandling extends TestRunner
+{
+ /** Used for logging. */
+ Logger log = Logger.getLogger(TestRunnerImprovedErrorHandling.class);
+
+ /**
+ * Delegates to the super constructor.
+ */
+ public TestRunnerImprovedErrorHandling()
+ {
+ super();
+ }
+
+ /**
+ * Delegates to the super constructor.
+ *
+ * @param printStream The location to write test results to.
+ */
+ public TestRunnerImprovedErrorHandling(PrintStream printStream)
+ {
+ super(printStream);
+ }
+
+ /**
+ * Delegates to the super constructor.
+ *
+ * @param resultPrinter The location to write test results to.
+ */
+ public TestRunnerImprovedErrorHandling(ResultPrinter resultPrinter)
+ {
+ super(resultPrinter);
+ }
+
+ /**
+ * Starts a test run. Analyzes the command line arguments
+ * and runs the given test suite.
+ *
+ * @param args The command line arguments.
+ *
+ * @return The test results.
+ *
+ * @throws Exception Any exceptions falling through the tests are wrapped in Exception and rethrown.
+ */
+ protected TestResult start(String[] args) throws Exception
+ {
+ String testCase = "";
+ boolean wait = false;
+
+ for (int i = 0; i < args.length; i++)
+ {
+ if (args[i].equals("-wait"))
+ {
+ wait = true;
+ }
+ else if (args[i].equals("-c"))
+ {
+ testCase = extractClassName(args[++i]);
+ }
+ else if (args[i].equals("-v"))
+ {
+ System.err.println("JUnit " + Version.id() + " by Kent Beck and Erich Gamma");
+ }
+ else
+ {
+ testCase = args[i];
+ }
+ }
+
+ if (testCase.equals(""))
+ {
+ throw new Exception("Usage: TestRunner [-wait] testCaseName, where name is the name of the TestCase class");
+ }
+
+ try
+ {
+ Test suite = getTest(testCase);
+
+ return doRun(suite, wait);
+ }
+ catch (Exception e)
+ {
+ log.warn("Got exception whilst creating and running test suite.", e);
+ throw new Exception("Could not create and run the test suite.", e);
+ }
+ }
+}
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TestThreadAware.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TestThreadAware.java
new file mode 100644
index 0000000000..aaa773260d
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TestThreadAware.java
@@ -0,0 +1,49 @@
+/*
+ *
+ * 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.junit.extensions;
+
+/**
+ * This interface can be implemented by tests that want to know if they are being run concurrently. It provides
+ * lifecycle notification events to tell the test implementation when test threads are being created and destroyed.
+ * This can assist tests in creating and destroying resources that exist over the life of a test thread. A single
+ * test thread can excute the same test many times, and often it is convenient to keep resources, for example network
+ * connections, open over many test calls.
+ *
+ *
CRC Card
+ *
Responsibilities
+ *
Set up per thread test fixtures.
+ *
Clean up per thread test fixtures.
+ *
+ *
+ * @author Rupert Smith
+ */
+public interface TestThreadAware
+{
+ /**
+ * Called when a test thread is created.
+ */
+ public void threadSetUp();
+
+ /**
+ * Called when a test thread is destroyed.
+ */
+ public void threadTearDown();
+}
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/Throttle.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/Throttle.java
new file mode 100644
index 0000000000..955e47c25b
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/Throttle.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.junit.extensions;
+
+/**
+ * Throttle is an interface that supplies a {@link #throttle} method, that can only be called at the rate specified
+ * in a call to the {@link #setRate} method. This can be used to restict processing to run at a certain number
+ * of operations per second.
+ *
+ * Throttle also supplies a method to check the throttle rate, without waiting. This could be used to update a user
+ * interface every time an event occurs, but only up to a maximum rate. For example, as elements are added to a list,
+ * a count of elements is updated for the user to see, but only up to a maximum rate of ten updates a second, as updating
+ * faster than that slows the processing of element-by-element additions to the list unnecessarily.
+ *
+ *
CRC Card
+ *
Responsibilities
+ *
Accept throttling rate in operations per second.
+ *
Inject short pauses to fill-out processing cycles to a specified rate.
+ *
Check against a throttle speed without waiting.
+ *
+ *
+ * @author Rupert Smith
+ */
+public interface Throttle
+{
+ /**
+ * Specifies the throttling rate in operations per second. This must be called with with a value, the inverse
+ * of which is a measurement in nano seconds, such that the number of nano seconds do not overflow a long integer.
+ * The value must also be larger than zero.
+ *
+ * @param hertz The throttling rate in cycles per second.
+ */
+ public void setRate(float hertz);
+
+ /**
+ * This method can only be called at the rate set by the {@link #setRate} method, if it is called faster than this
+ * it will inject short pauses to restrict the call rate to that rate.
+ *
+ * If the thread executing this method is interrupted, it must ensure that the threads interrupt thread
+ * remains set upon exit from the method. This method does not expose InterruptedException, to indicate interruption
+ * of the throttle during a timed wait. It may be changed so that it does.
+ */
+ public void throttle();
+
+ /**
+ * Checks but does not enforce the throttle rate. When this method is called, it checks if a length of time greater
+ * than that equal to the inverse of the throttling rate has passed since it was last called and returned true
+ *
+ * @return true if a length of time greater than that equal to the inverse of the throttling rate has
+ * passed since this method was last called and returned true, false otherwise. The very
+ * first time this method is called on a throttle, it returns true as the base case to the above
+ * self-referential definition.
+ */
+ public boolean checkThrottle();
+}
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TimingController.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TimingController.java
new file mode 100644
index 0000000000..7b5763f1de
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TimingController.java
@@ -0,0 +1,175 @@
+/*
+ *
+ * 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.junit.extensions;
+
+/**
+ * A TimingController is a interface that a test that is aware of the fact that it is being timed can use to manage
+ * the timer. Using this interface tests can suspend and resume test timers. This is usefull if you want to exclude
+ * some expensive preparation from being timed as part of a test. In general when timing tests to measure the
+ * performance of code, you should try to set up data in the #setUp where possible, or as static members in the test
+ * class. This is not always convenient, this interface gives you a way to suspend and resume, or event completely
+ * restart test timers, to get accurate measurements.
+ *
+ * The interface can also be used to register multiple test pass/fails and timings from a single test method.
+ * In some cases it is easier to write tests in this way. For example a concurrent and asynchronous test may make
+ * many asynchronous requests and then wait for replies to all its requests. Writing such a test with one send/reply
+ * per test method and trying to scale up using many threads will quickly run into limitations if more than about
+ * 100 asynchronous calls need to be made at once. A better way to write such a test is as a single method that sends
+ * many (perhaps thousands or millions) and waits for replies in two threads, one for send, one for replies. It can
+ * then log pass/fails and timings on each individual reply as they come back in, even though the test has been written
+ * to send thousands of requests per test method in order to do volume testing.
+ *
+ * If when the {@link #completeTest(boolean)} is called, the test runner decides that testing should stop (perhaps
+ * because a duration test has expired), it throws an InterruptedException to indicate that the test method should stop
+ * immediately. The test method can do this by allowing this exception to fall through, if no other clean-up handling
+ * is necessary, or it can simply return as soon as it possibly can. The test runner will still call the tearDown
+ * method in the usual way when this happens.
+ *
+ * Below are some examples of how this can be used. Not how checking that the timing controller is really available
+ * rather than assuming it is, means that the test can run as an ordinary JUnit test under the default test runners. In
+ * general code should be written to take advantage of the extended capabilities of junit toolkit, without assuming they
+ * are going to be run under its test runner.
+ *
+ *
+ * public class MyTest extends TestCase implements TimingControllerAware {
+ * ...
+ *
+ * timingUtils = this.getTimingController();
+ *
+ * // Do expensive data preparation here...
+ *
+ * if (timingUtils != null)
+ * timingUtils.restart();
+ *
Allow test timers to be suspended, restarted or reset.
+ *
Allow tests to register multiple pass/fails and timings.
+ *
+ *
+ * @author Rupert Smith
+ */
+public interface TimingController
+{
+ /**
+ * Gets the timing controller associated with the current test thread. Tests that use timing controller should
+ * always get the timing controller from this method in the same thread that called the setUp, tearDown or test
+ * method. The controller returned by this method may be called from any thread because it remembers the thread
+ * id of the original test thread.
+ *
+ * @return The timing controller associated with the current test thread.
+ */
+ public TimingController getControllerForCurrentThread();
+
+ /**
+ * Suspends the test timer.
+ *
+ * @return The current time in nanoseconds.
+ */
+ public long suspend();
+
+ /**
+ * Allows the test timer to continue running after a suspend.
+ *
+ * @return The current time in nanoseconds.
+ */
+ public long resume();
+
+ /**
+ * Completely restarts the test timer from zero.
+ *
+ * @return The current time in nanoseconds.
+ */
+ public long restart();
+
+ /**
+ * Register an additional pass/fail for the current test. The test result is assumed to apply to a test of
+ * 'size' parmeter 1. Use the {@link #completeTest(boolean, int)} method to register timings with parameters.
+ *
+ * @param testPassed Whether or not this timing is for a test pass or fail.
+ *
+ * @throws InterruptedException If the test runner decides that testing should stop it throws this exception to
+ * indicate to the test method that it should stop immediately.
+ */
+ public void completeTest(boolean testPassed) throws InterruptedException;
+
+ /**
+ * Register an additional pass/fail for the current test. The test result is applies to a test of the specified
+ * 'size' parmeter.
+ *
+ * @param testPassed Whether or not this timing is for a test pass or fail.
+ * @param param The test parameter size for parameterized tests.
+ *
+ * @throws InterruptedException If the test runner decides that testing should stop it throws this exception to
+ * indicate to the test method that it should stop immediately.
+ */
+ public void completeTest(boolean testPassed, int param) throws InterruptedException;
+
+ /**
+ * Register an additional pass/fail for the current test. The test result is applies to a test of the specified
+ * 'size' parmeter and allows the caller to sepecify the timing to log.
+ *
+ * @param testPassed Whether or not this timing is for a test pass or fail.
+ * @param param The test parameter size for parameterized tests.
+ * @param timeNanos The time in nano seconds to log the test result with.
+ *
+ * @throws InterruptedException If the test runner decides that testing should stop it throws this exception to
+ * indicate to the test method that it should stop immediately.
+ */
+ public void completeTest(boolean testPassed, int param, long timeNanos) throws InterruptedException;
+}
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TimingControllerAware.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TimingControllerAware.java
new file mode 100644
index 0000000000..1ccdc7dbad
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/TimingControllerAware.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.junit.extensions;
+
+/**
+ * TimingControllerAware is an interface that tests that manipulate the timing controller should implement. It enables
+ * the TK test runner to set the test up with a handle on the timing controller which the test can use to call back
+ * to the test runner to manage the timers.
+ *
+ *
CRC Card
+ *
Responsibilities
+ *
Provide timing controller insertion point for tests.
+ *
+ *
+ * @author Rupert Smith
+ */
+public interface TimingControllerAware
+{
+ /**
+ * Used by test runners that can supply a {@link TimingController} to set the controller on an aware test.
+ *
+ * @param controller The timing controller.
+ */
+ public void setTimingController(TimingController controller);
+}
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/WrappedSuiteTestDecorator.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/WrappedSuiteTestDecorator.java
new file mode 100644
index 0000000000..7a1e537b1c
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/WrappedSuiteTestDecorator.java
@@ -0,0 +1,134 @@
+/*
+ *
+ * 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.junit.extensions;
+
+import junit.extensions.TestDecorator;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import org.apache.log4j.Logger;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * WrappedSuiteTestDecorator is a test decorator that wraps a test suite, or another wrapped suite, but provides the
+ * same functionality for the {@link junit.extensions.TestDecorator#countTestCases()} and {@link TestSuite#testAt(int)}
+ * methods as the underlying suite. It returns the values that these methods provide, to enable classes using decorated
+ * tests to drill down to the underlying tests in the suite. That is to say that it indexes and reports the number of
+ * distinct tests in the suite, not the number of test runs that would result from, for example, wrapping the suite in a
+ * repeating decorator.
+ *
+ *
CRC Card
+ *
Responsibilities
+ *
Provide access to the underlying tests in a suite.
+ *
+ *
+ * @author Rupert Smith
+ */
+public class WrappedSuiteTestDecorator extends TestDecorator
+{
+ /** Used for logging. */
+ private static Logger log = Logger.getLogger(WrappedSuiteTestDecorator.class);
+
+ /** Holds the test suite that this supplies access to. */
+ protected Test suite;
+
+ /**
+ * Creates a wrappred suite test decorator from a test suite.
+ *
+ * @param suite The test suite.
+ */
+ public WrappedSuiteTestDecorator(TestSuite suite)
+ {
+ super(suite);
+ this.suite = suite;
+ }
+
+ /**
+ * Creates a wrapped suite test decorator from another one.
+ *
+ * @param suite The test suite.
+ */
+ public WrappedSuiteTestDecorator(WrappedSuiteTestDecorator suite)
+ {
+ super(suite);
+ this.suite = suite;
+ }
+
+ /**
+ * Returns the test count of the wrapped suite.
+ *
+ * @return The test count of the wrapped suite.
+ */
+ public int countTestCases()
+ {
+ return suite.countTestCases();
+ }
+
+ /**
+ * Gets the ith test from the test suite.
+ *
+ * @param i The index of the test within the suite to get.
+ *
+ * @return The test with the specified index.
+ */
+ public Test testAt(int i)
+ {
+ log.debug("public Test testAt(int i = " + i + "): called");
+
+ if (suite instanceof WrappedSuiteTestDecorator)
+ {
+ return ((WrappedSuiteTestDecorator) suite).testAt(i);
+ }
+ else if (suite instanceof TestSuite)
+ {
+ return ((TestSuite) suite).testAt(i);
+ }
+
+ // This should never happen.
+ return null;
+ }
+
+ /**
+ * Gets all the tests from the underlying test suite.
+ *
+ * @return All the tests from the underlying test suite.
+ */
+ public Collection getAllUnderlyingTests()
+ {
+ log.debug("public Collection getAllUnderlyingTests(): called");
+
+ List tests = new ArrayList();
+
+ int numTests = countTestCases();
+ log.debug("numTests = " + numTests);
+
+ for (int i = 0; i < numTests; i++)
+ {
+ tests.add(testAt(i));
+ }
+
+ return tests;
+ }
+}
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/CSVTestListener.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/CSVTestListener.java
new file mode 100644
index 0000000000..a771e08cf7
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/CSVTestListener.java
@@ -0,0 +1,532 @@
+/*
+ *
+ * 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.junit.extensions.listeners;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestListener;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.junit.extensions.ShutdownHookable;
+import org.apache.qpid.junit.extensions.util.TestContextProperties;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * CSVTestListener is both a test listener, a timings listener, a memory listener and a parameter listener. It listens for test completion events and
+ * then writes out all the data that it has listened to into a '.csv' (comma seperated values) file.
+ *
+ *
CRC Card
+ *
Responsibilities
Collaborations
+ *
Listen to test events; start, end, fail, error.
+ *
Listen to test timings.
+ *
Listen to test memory usage.
+ *
Listen to parameterized test parameters.
+ *
Output all test data to a CSV file.
+ *
+ *
+ * @author Rupert Smith
+ *
+ * @todo Write an XML output class. Write a transform to convert it into an HTML page with timings as graphs.
+ */
+public class CSVTestListener implements TestListener, TKTestListener, ShutdownHookable
+{
+ /** Used for logging. */
+ private static final Logger log = Logger.getLogger(CSVTestListener.class);
+
+ /** The timings file writer. */
+ private Writer timingsWriter;
+
+ /**
+ * Map for holding results on a per thread basis as they come in. A ThreadLocal is not used as sometimes an
+ * explicit thread id must be used, where notifications come from different threads than the ones that called
+ * the test method.
+ */
+ Map threadLocalResults = Collections.synchronizedMap(new HashMap());
+
+ /** Used to record the start time of a complete test run, for outputing statistics at the end of the test run. */
+ private long batchStartTime;
+
+ /** Used to record the number of errors accross a complete test run. */
+ private int numError;
+
+ /** Used to record the number of failures accross a complete test run. */
+ private int numFailed;
+
+ /** Used to record the number of passes accross a complete test run. */
+ private int numPassed;
+
+ /** Used to record the total tests run accross a complete test run. Always equal to passes + errors + fails. */
+ private int totalTests;
+
+ /** Used to recrod the current concurrency level for the test batch. */
+ private int concurrencyLevel;
+
+ /**
+ * Used to record the total 'size' of the tests run, this is the number run times the average value of the test
+ * size parameters.
+ */
+ private int totalSize;
+
+ /**
+ * Used to record the summation of all of the individual test timgings. Note that total time and summed time
+ * are unlikely to be in agreement, exception for a single threaded test (with no setup time). Total time is
+ * the time taken to run all the tests, summed time is the added up time that each individual test took. So if
+ * two tests run in parallel and take one second each, total time will be one seconds, summed time will be two
+ * seconds.
+ */
+ private long summedTime;
+
+ /** Flag to indicate when batch has been started but not ended to ensure end batch stats are output only once. */
+ private boolean batchStarted = false;
+
+ /**
+ * Creates a new CSVTestListener object.
+ *
+ * @param writer A writer where this CSV listener should write out its output to.
+ */
+ public CSVTestListener(Writer writer)
+ {
+ // log.debug("public CSVTestListener(Writer writer): called");
+
+ // Keep the writer.
+ this.timingsWriter = writer;
+ }
+
+ /**
+ * Resets the test results to the default state of time zero, memory usage zero, test passed.
+ *
+ * @param test The test to resest any results for.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void reset(Test test, Long threadId)
+ {
+ // log.debug("public void reset(Test test = \"" + test + "\", Long threadId = " + threadId + "): called");
+
+ TestResult r =
+ (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId);
+
+ r.testTime = 0L;
+ r.testStartMem = 0L;
+ r.testEndMem = 0L;
+ r.testState = "Pass";
+ r.testParam = 0;
+ }
+
+ /**
+ * Called when a test results in an error.
+ *
+ * @param test The test which is in error.
+ * @param t Any Throwable raised by the test in error.
+ */
+ public void addError(Test test, Throwable t)
+ {
+ // log.debug("public void addError(Test test, Throwable t): called");
+
+ TestResult r = threadLocalResults.get(Thread.currentThread().getId());
+ r.testState = "Error";
+ }
+
+ /**
+ * Called when a test results in a failure.
+ *
+ * @param test The test which failed.
+ * @param t The AssertionFailedError that encapsulates the test failure.
+ */
+ public void addFailure(Test test, AssertionFailedError t)
+ {
+ // log.debug("public void addFailure(Test \"" + test + "\", AssertionFailedError t): called");
+
+ TestResult r = threadLocalResults.get(Thread.currentThread().getId());
+ r.testState = "Failure";
+ }
+
+ /**
+ * Called when a test completes to mark it as a test fail. This method should be used when registering a
+ * failure from a different thread than the one that started the test.
+ *
+ * @param test The test which failed.
+ * @param e The assertion that failed the test.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void addFailure(Test test, AssertionFailedError e, Long threadId)
+ {
+ // log.debug("public void addFailure(Test test = \"" + test + "\", AssertionFailedError e, Long threadId = " + threadId
+ // + "): called");
+
+ TestResult r =
+ (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId);
+
+ r.testState = "Failure";
+ }
+
+ /**
+ * Called when a test completes. Success, failure and errors.
+ *
+ * @param test The test which completed.
+ */
+ public void endTest(Test test)
+ {
+ // log.debug("public void endTest(Test \"" + test + "\"): called");
+
+ TestResult r = threadLocalResults.get(Thread.currentThread().getId());
+
+ writeTestResults(r, test);
+
+ // Clear all the test results for the thread.
+ threadLocalResults.remove(Thread.currentThread().getId());
+ }
+
+ /**
+ * Called when a test starts.
+ *
+ * @param test The test wich has started.
+ */
+ public void startTest(Test test)
+ {
+ // log.debug("public void startTest(Test \"" + test + "\"): called");
+
+ // Initialize the thread local test results.
+ threadLocalResults.put(Thread.currentThread().getId(), new TestResult());
+ }
+
+ /**
+ * Should be called every time a test completes with the run time of that test.
+ *
+ * @param test The name of the test.
+ * @param nanos The run time of the test in nanoseconds.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void timing(Test test, long nanos, Long threadId)
+ {
+ // log.debug("public void timing(String \"" + test + "\", long " + nanos + "): called");
+
+ TestResult r =
+ (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId);
+
+ r.testTime = nanos;
+ summedTime += nanos;
+ }
+
+ /**
+ * Should be called every time a test completed with the amount of memory used before and after the test was run.
+ *
+ * @param test The test which memory was measured for.
+ * @param memStart The total JVM memory used before the test was run.
+ * @param memEnd The total JVM memory used after the test was run.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void memoryUsed(Test test, long memStart, long memEnd, Long threadId)
+ {
+ // log.debug("public void memoryUsed(Test \"" + test + "\", long " + memStart + ", long " + memEnd + ", Long "
+ // + threadId + "): called");
+
+ TestResult r =
+ (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId);
+
+ r.testStartMem = memStart;
+ r.testEndMem = memEnd;
+ }
+
+ /**
+ * Should be called every time a parameterized test completed with the int value of its test parameter.
+ *
+ * @param test The test which memory was measured for.
+ * @param parameter The int parameter value.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void parameterValue(Test test, int parameter, Long threadId)
+ {
+ // log.debug("public void parameterValue(Test test = \"" + test + "\", int parameter = " + parameter + "): called");
+
+ TestResult r =
+ (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId);
+
+ r.testParam = parameter;
+ totalSize += parameter;
+ }
+
+ /**
+ * Should be called every time a test completes with the current number of test threads running. This should not
+ * change within a test batch, therefore it is safe to take this as a batch level property value too.
+ *
+ * @param test The test for which the measurement is being generated.
+ * @param threads The number of tests being run concurrently.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void concurrencyLevel(Test test, int threads, Long threadId)
+ {
+ // log.debug("public void concurrencyLevel(Test test = \"" + test + "\", int threads = " + threads + "): called");
+
+ TestResult r =
+ (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId);
+
+ r.testConcurrency = threads;
+ concurrencyLevel = threads;
+
+ }
+
+ /**
+ * Called when a test completes. Success, failure and errors. This method should be used when registering an
+ * end test from a different thread than the one that started the test.
+ *
+ * @param test The test which completed.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void endTest(Test test, Long threadId)
+ {
+ // log.debug("public void endTest(Test test = \"" + test + "\", Long threadId " + threadId + "): called");
+
+ TestResult r =
+ (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId);
+
+ writeTestResults(r, test);
+ }
+
+ /**
+ * Takes a time stamp for the beginning of the batch and resets stats counted for the batch.
+ */
+ public synchronized void startBatch()
+ {
+ numError = 0;
+ numFailed = 0;
+ numPassed = 0;
+ totalTests = 0;
+ totalSize = 0;
+ batchStartTime = System.nanoTime();
+ summedTime = 0;
+ batchStarted = true;
+
+ // Write out the column headers for the batch.
+ writeColumnHeaders();
+ }
+
+ /**
+ * Takes a time stamp for the end of the batch to calculate the total run time.
+ * Write this and other stats out to the tail of the csv file.
+ *
+ * @param parameters The optional test parameters, may be null.
+ */
+ public synchronized void endBatch(Properties parameters)
+ {
+ boolean noParams = (parameters == null) || (parameters.size() == 0);
+
+ // Check that a batch has been started but not ended.
+ if (batchStarted)
+ {
+ long batchEndTime = System.nanoTime();
+ float totalTimeMillis = ((float) (batchEndTime - batchStartTime)) / 1000000f;
+ float summedTimeMillis = ((float) summedTime) / 1000000f;
+
+ // Write the stats for the batch out.
+ try
+ {
+ synchronized (this.getClass())
+ {
+ timingsWriter.write("Total Tests:, " + totalTests + ", ");
+ timingsWriter.write("Total Passed:, " + numPassed + ", ");
+ timingsWriter.write("Total Failed:, " + numFailed + ", ");
+ timingsWriter.write("Total Error:, " + numError + ", ");
+ timingsWriter.write("Total Size:, " + totalSize + ", ");
+ timingsWriter.write("Summed Time:, " + summedTimeMillis + ", ");
+ timingsWriter.write("Concurrency Level:, " + concurrencyLevel + ", ");
+ timingsWriter.write("Total Time:, " + totalTimeMillis + ", ");
+ timingsWriter.write("Test Throughput:, " + (((float) totalTests) / totalTimeMillis) + ", ");
+ timingsWriter.write("Test * Size Throughput:, " + (((float) totalSize) / totalTimeMillis)
+ + (noParams ? "\n\n" : ", "));
+
+ // Write out the test parameters if there are any specified.
+ if (!noParams)
+ {
+ properties(parameters);
+ }
+
+ timingsWriter.flush();
+ }
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("Unable to write out end batch statistics: " + e, e);
+ }
+ }
+
+ // Reset the batch started flag to ensure stats are only output once.
+ batchStarted = false;
+ }
+
+ /**
+ * Notifies listeners of the tests read/set properties.
+ *
+ * @param properties The tests read/set properties.
+ */
+ public void properties(Properties properties)
+ {
+ // log.debug("public void properties(Properties properties): called");
+
+ // Write the properties out to the results file.
+ try
+ {
+ synchronized (this.getClass())
+ {
+ Set keySet = new TreeSet(properties.keySet());
+
+ // timingsWriter.write("\n");
+
+ for (Object key : keySet)
+ {
+ timingsWriter.write(key + " = , " + properties.getProperty((String) key) + ", ");
+ }
+
+ timingsWriter.write("\n\n");
+ // timingsWriter.flush();
+ }
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("Unable to write out test parameters: " + e, e);
+ }
+
+ // Write out the column headers after the properties.
+ // writeColumnHeaders();
+ }
+
+ /**
+ * Writes out and flushes the column headers for raw test data.
+ */
+ private void writeColumnHeaders()
+ {
+ // Write the column headers for the CSV file. Any IO exceptions are ignored.
+ try
+ {
+ timingsWriter.write("Class, ");
+ timingsWriter.write("Method, ");
+ timingsWriter.write("Thread, ");
+ timingsWriter.write("Test Outcome, ");
+ timingsWriter.write("Time (milliseconds), ");
+ timingsWriter.write("Memory Used (bytes), ");
+ timingsWriter.write("Concurrency level, ");
+ timingsWriter.write("Test Size\n");
+
+ timingsWriter.flush();
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("Unable to write out column headers: " + e, e);
+ }
+ }
+
+ /**
+ * Writes out the test results for the specified test. This outputs a single line of results to the csv file.
+ *
+ * @param r The test results to write out.
+ * @param test The test to write them out for.
+ */
+ private void writeTestResults(TestResult r, Test test)
+ {
+ // Update the running stats for this batch.
+ if ("Error".equals(r.testState))
+ {
+ numError++;
+ }
+ else if ("Failure".equals(r.testState))
+ {
+ numFailed++;
+ }
+ else if ("Pass".equals(r.testState))
+ {
+ numPassed++;
+ }
+
+ totalTests++;
+
+ // Write the test name and thread information plus all instrumenation a line of the CSV ouput. Any IO
+ // exceptions are ignored.
+ try
+ {
+ synchronized (this.getClass())
+ {
+ timingsWriter.write(test.getClass().getName() + ", ");
+ timingsWriter.write(((test instanceof TestCase) ? ((TestCase) test).getName() : "") + ", ");
+ timingsWriter.write(Thread.currentThread().getName() + ", ");
+ timingsWriter.write(r.testState + ", ");
+ timingsWriter.write((((float) r.testTime) / 1000000f) + ", ");
+ timingsWriter.write((r.testEndMem - r.testStartMem) + ", ");
+ timingsWriter.write(r.testConcurrency + ", ");
+ timingsWriter.write(r.testParam + "\n");
+ }
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("Unable to write out test results: " + e, e);
+ }
+ }
+
+ /**
+ * Supplies the shutdown hook. This attempts to flush the results in the event of the test runner being prematurely
+ * suspended before the end of the current test batch.
+ *
+ * @return The shut down hook.
+ */
+ public Thread getShutdownHook()
+ {
+ return new Thread(new Runnable()
+ {
+ public void run()
+ {
+ log.debug("CSVTestListener::ShutdownHook: called");
+
+ // Complete the current test batch stats.
+ endBatch(TestContextProperties.getInstance());
+ }
+ });
+ }
+
+ /** Captures test results packaged into a single object, so that it can be set up as a thread local. */
+ private static class TestResult
+ {
+ /** Used to hold the test timing. */
+ public long testTime;
+
+ /** Used to hold the test start memory usage. */
+ public long testStartMem;
+
+ /** Used to hold the test end memory usage. */
+ public long testEndMem;
+
+ /** Used to hold the test pass/fail/error state. */
+ public String testState = "Pass";
+
+ /** Used to hold the test parameter value. */
+ public int testParam;
+
+ /** Used to hold the concurrency level under which the test was run. */
+ public int testConcurrency;
+ }
+}
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/ConsoleTestListener.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/ConsoleTestListener.java
new file mode 100644
index 0000000000..5c328a8814
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/ConsoleTestListener.java
@@ -0,0 +1,264 @@
+/*
+ *
+ * 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.junit.extensions.listeners;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.Test;
+import junit.framework.TestListener;
+
+import org.apache.qpid.junit.extensions.SleepThrottle;
+import org.apache.qpid.junit.extensions.Throttle;
+
+import java.util.Properties;
+
+/**
+ * ConsoleTestListener provides feedback to the console, as test timings are taken, by drawing a '.', or an 'E', or an
+ * 'F', for each test that passes, is in error or fails. It does this for every test result registered with the framework,
+ * not just on the completion of each test method as the JUnit one does. It also uses a throttle to cap the rate of
+ * dot drawing, as exessively high rates can degrade test performance without providing much usefull feedback to the user.
+ * Unlike the JUnit dot drawing feedback, this one will correctly wrap lines when tests are run concurrently (the
+ * rate capping ensures that this does not become a hot-spot for thread contention).
+ *
+ * Where rate capping causes the conflation of multiple requested dots into a single dot, the dot that is actually
+ * drawn will be the worst result within the conflation period, that is, error is worse than fail which is worse than pass.
+ *
+ *
CRC Card
+ *
Responsibilities
Collaborations
+ *
Draw dots as each test result completes, at a capped rate.
+ *
+ *
+ * @author Rupert Smith
+ */
+public class ConsoleTestListener implements TestListener, TKTestListener
+{
+ /** Used to indicate a test pass. */
+ private static final int PASS = 1;
+
+ /** Used to indicate a test failure. */
+ private static final int FAIL = 2;
+
+ /** Used to indicate a test error. */
+ private static final int ERROR = 3;
+
+ /** Defines the maximum number of columns of dots to print. */
+ private static final int MAX_COLUMNS = 80;
+
+ /** Used to throttle the dot writing rate. */
+ Throttle throttle;
+
+ /** Tracks the worst test result so far, when the throttled print method must conflate results due to throttling. */
+ private int conflatedResult = 0;
+
+ /** Tracks the column count as dots are printed, so that newlines can be inserted at the right margin. */
+ private int columnCount = 0;
+
+ /** Used as a monitor on the print method criticial section, to ensure that line ends always happen in the right place. */
+ private final Object printMonitor = new Object();
+
+ /**
+ * Creates a dot drawing feedback test listener, set by default to 80 columns at 80 dots per second capped rate.
+ */
+ public ConsoleTestListener()
+ {
+ throttle = new SleepThrottle();
+ throttle.setRate(80f);
+ }
+
+ /**
+ * Prints dots at a capped rate, conflating the requested type of dot to draw if this method is called at a rate
+ * higher than the capped rate. The conflation works by always printing the worst result that occurs within the
+ * conflation period, that is, error is worse than fail which is worse than a pass.
+ *
+ * @param result The type of dot to draw, {@link #PASS}, {@link #FAIL} or {@link #ERROR}.
+ */
+ private void throttledPrint(int result)
+ {
+ conflatedResult = (result > conflatedResult) ? result : conflatedResult;
+
+ if (throttle.checkThrottle())
+ {
+ synchronized (printMonitor)
+ {
+ switch (conflatedResult)
+ {
+ default:
+ case PASS:
+ System.out.print('.');
+ break;
+
+ case FAIL:
+ System.out.print('F');
+ break;
+
+ case ERROR:
+ System.out.print('E');
+ break;
+ }
+
+ columnCount = (columnCount >= MAX_COLUMNS) ? 0 : (columnCount + 1);
+
+ if (columnCount == 0)
+ {
+ System.out.print('\n');
+ }
+
+ conflatedResult = 0;
+ }
+ }
+ }
+
+ /**
+ * An error occurred.
+ *
+ * @param test The test in error. Ignored.
+ * @param t The error that the test threw. Ignored.
+ */
+ public void addError(Test test, Throwable t)
+ {
+ throttledPrint(ERROR);
+ }
+
+ /**
+ * A failure occurred.
+ *
+ * @param test The test that failed. Ignored.
+ * @param t The assertion failure that the test threw. Ignored.
+ */
+ public void addFailure(Test test, AssertionFailedError t)
+ {
+ throttledPrint(FAIL);
+ }
+
+ /**
+ * A test ended.
+ *
+ * @param test The test that ended. Ignored.
+ */
+ public void endTest(Test test)
+ {
+ throttledPrint(PASS);
+ }
+
+ /**
+ * A test started.
+ *
+ * @param test The test that started. Ignored.
+ */
+ public void startTest(Test test)
+ { }
+
+ /**
+ * Resets the test results to the default state of time zero, memory usage zero, parameter zero, test passed.
+ *
+ * @param test The test to resest any results for.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void reset(Test test, Long threadId)
+ { }
+
+ /**
+ * Should be called every time a test completes with the run time of that test.
+ *
+ * @param test The name of the test.
+ * @param nanos The run time of the test in nanoseconds.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void timing(Test test, long nanos, Long threadId)
+ { }
+
+ /**
+ * Should be called every time a test completed with the amount of memory used before and after the test was run.
+ *
+ * @param test The test which memory was measured for.
+ * @param memStart The total JVM memory used before the test was run.
+ * @param memEnd The total JVM memory used after the test was run.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void memoryUsed(Test test, long memStart, long memEnd, Long threadId)
+ { }
+
+ /**
+ * Should be called every time a parameterized test completed with the int value of its test parameter.
+ *
+ * @param test The test which memory was measured for.
+ * @param parameter The int parameter value.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void parameterValue(Test test, int parameter, Long threadId)
+ { }
+
+ /**
+ * Should be called every time a test completes with the current number of test threads running.
+ *
+ * @param test The test for which the measurement is being generated.
+ * @param threads The number of tests being run concurrently.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void concurrencyLevel(Test test, int threads, Long threadId)
+ { }
+
+ /**
+ * Called when a test completes. Success, failure and errors. This method should be used when registering an
+ * end test from a different thread than the one that started the test.
+ *
+ * @param test The test which completed.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void endTest(Test test, Long threadId)
+ {
+ throttledPrint(PASS);
+ }
+
+ /**
+ * Called when a test completes to mark it as a test fail. This method should be used when registering a
+ * failure from a different thread than the one that started the test.
+ *
+ * @param test The test which failed.
+ * @param e The assertion that failed the test.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void addFailure(Test test, AssertionFailedError e, Long threadId)
+ {
+ throttledPrint(FAIL);
+ }
+
+ /**
+ * Notifies listeners of the start of a complete run of tests.
+ */
+ public void startBatch()
+ { }
+
+ /**
+ * Notifies listeners of the end of a complete run of tests.
+ *
+ * @param parameters The optional test parameters to log out with the batch results.
+ */
+ public void endBatch(Properties parameters)
+ { }
+
+ /**
+ * Notifies listeners of the tests read/set properties.
+ *
+ * @param properties The tests read/set properties.
+ */
+ public void properties(Properties properties)
+ { }
+}
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/TKTestListener.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/TKTestListener.java
new file mode 100644
index 0000000000..4f08e8bf2d
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/TKTestListener.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.junit.extensions.listeners;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.Test;
+import junit.framework.TestListener;
+
+import java.util.Properties;
+
+/**
+ * TKTestListener is a listener interface for listeners that want to be informed of the run times of tests, the memory
+ * usage of tests, the 'size' parameters of parameterized tests and the begin and end events of complete test runs.
+ * {@link org.apache.qpid.junit.extensions.TKTestResult} is an example of a test result class that listeners
+ * interested in these events can be attached to.
+ *
+ * The {@link #timing(junit.framework.Test, long, Long)}, {@link #memoryUsed(junit.framework.Test, long, long, Long)},
+ * {@link #parameterValue(junit.framework.Test, int, Long)} and {@link #endTest(junit.framework.Test, Long)} methods
+ * all accept on optional thread id parameter.
+ *
+ *
CRC Card
+ *
Responsibilities
+ *
Listen to test timings.
+ *
Listen to test memory usages.
+ *
Listen to parameterized test parameters.
+ *
+ *
+ * @author Rupert Smith
+ */
+public interface TKTestListener extends TestListener
+{
+ /**
+ * Resets the test results to the default state of time zero, memory usage zero, parameter zero, test passed.
+ *
+ * @param test The test to resest any results for.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void reset(Test test, Long threadId);
+
+ /**
+ * Should be called every time a test completes with the run time of that test.
+ *
+ * @param test The name of the test.
+ * @param nanos The run time of the test in nanoseconds.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void timing(Test test, long nanos, Long threadId);
+
+ /**
+ * Should be called every time a test completed with the amount of memory used before and after the test was run.
+ *
+ * @param test The test which memory was measured for.
+ * @param memStart The total JVM memory used before the test was run.
+ * @param memEnd The total JVM memory used after the test was run.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void memoryUsed(Test test, long memStart, long memEnd, Long threadId);
+
+ /**
+ * Should be called every time a parameterized test completed with the int value of its test parameter.
+ *
+ * @param test The test which memory was measured for.
+ * @param parameter The int parameter value.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void parameterValue(Test test, int parameter, Long threadId);
+
+ /**
+ * Should be called every time a test completes with the current number of test threads running.
+ *
+ * @param test The test for which the measurement is being generated.
+ * @param threads The number of tests being run concurrently.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void concurrencyLevel(Test test, int threads, Long threadId);
+
+ /**
+ * Called when a test completes. Success, failure and errors. This method should be used when registering an
+ * end test from a different thread than the one that started the test.
+ *
+ * @param test The test which completed.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void endTest(Test test, Long threadId);
+
+ /**
+ * Called when a test completes to mark it as a test fail. This method should be used when registering a
+ * failure from a different thread than the one that started the test.
+ *
+ * @param test The test which failed.
+ * @param e The assertion that failed the test.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void addFailure(Test test, AssertionFailedError e, Long threadId);
+
+ /**
+ * Notifies listeners of the start of a complete run of tests.
+ */
+ public void startBatch();
+
+ /**
+ * Notifies listeners of the end of a complete run of tests.
+ *
+ * @param parameters The optional test parameters to log out with the batch results.
+ */
+ public void endBatch(Properties parameters);
+
+ /**
+ * Notifies listeners of the tests read/set properties.
+ *
+ * @param properties The tests read/set properties.
+ */
+ public void properties(Properties properties);
+}
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/XMLTestListener.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/XMLTestListener.java
new file mode 100644
index 0000000000..a88837e323
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/XMLTestListener.java
@@ -0,0 +1,400 @@
+/*
+ *
+ * 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.junit.extensions.listeners;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.junit.extensions.ShutdownHookable;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.util.*;
+
+/**
+ * Listens for test results for a named test and outputs these in the standard JUnit XML format to the specified
+ * writer.
+ *
+ * The API for this listener accepts notifications about different aspects of a tests results through different
+ * methods, so some assumption needs to be made as to which test result a notification refers to. For example
+ * {@link #startTest} will be called, then possibly {@link #timing} will be called, even though the test instance is
+ * passed in both cases, it is not enough to distinguish a particular run of the test, as the test case instance may
+ * be being shared between multiple threads, or being run a repeated number of times, and can therfore be re-used
+ * between calls. The listeners make the assumption that, for every test, a unique thread will call {@link #startTest}
+ * and {@link #endTest} to delimit each test. All calls to set test parameters, timings, state and so on, will occur
+ * between the start and end and will be given with the same thread id as the start and end, so the thread id provides
+ * a unqiue value to identify a particular test run against.
+ *
+ *
CRC Card
+ *
Responsibilities
Collaborations
+ *
Listen to test lifecycle notifications.
+ *
Listen to test errors and failures.
+ *
Listen to test timings.
+ *
Listen to test memory usages.
+ *
Listen to parameterized test parameters.
+ *
Responsibilities
+ *
+ *
+ * @todo Merge this class with CSV test listener, making the collection of results common to both, and only factoring
+ * out the results printing code into sub-classes. Provide a simple XML results formatter with the same format as
+ * the ant XML formatter, and a more structured one for outputing results with timings and summaries from
+ * performance tests.
+ *
+ * @author Rupert Smith
+ */
+public class XMLTestListener implements TKTestListener, ShutdownHookable
+{
+ /** Used for debugging. */
+ private static final Logger log = Logger.getLogger(XMLTestListener.class);
+
+ /** The results file writer. */
+ protected Writer writer;
+
+ /** Holds the results for individual tests. */
+ // protected Map results = new LinkedHashMap();
+ // protected List results = new ArrayList();
+
+ /**
+ * Map for holding results on a per thread basis as they come in. A ThreadLocal is not used as sometimes an
+ * explicit thread id must be used, where notifications come from different threads than the ones that called
+ * the test method.
+ */
+ Map threadLocalResults = Collections.synchronizedMap(new LinkedHashMap());
+
+ /**
+ * Holds results for tests that have ended. Transferring these results here from the per-thread results map, means
+ * that the thread id is freed for the thread to generate more results.
+ */
+ List results = new ArrayList();
+
+ /** Holds the overall error count. */
+ protected int errors = 0;
+
+ /** Holds the overall failure count. */
+ protected int failures = 0;
+
+ /** Holds the overall tests run count. */
+ protected int runs = 0;
+
+ /** Holds the name of the class that tests are being run for. */
+ String testClassName;
+
+ /**
+ * Creates a new XML results output listener that writes to the specified location.
+ *
+ * @param writer The location to write results to.
+ * @param testClassName The name of the test class to include in the test results.
+ */
+ public XMLTestListener(Writer writer, String testClassName)
+ {
+ log.debug("public XMLTestListener(Writer writer, String testClassName = " + testClassName + "): called");
+
+ this.writer = writer;
+ this.testClassName = testClassName;
+ }
+
+ /**
+ * Resets the test results to the default state of time zero, memory usage zero, parameter zero, test passed.
+ *
+ * @param test The test to resest any results for.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void reset(Test test, Long threadId)
+ {
+ log.debug("public void reset(Test test = " + test + ", Long threadId = " + threadId + "): called");
+
+ XMLTestListener.Result r =
+ (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId);
+
+ r.error = null;
+ r.failure = null;
+
+ }
+
+ /**
+ * Notification that a test started.
+ *
+ * @param test The test that started.
+ */
+ public void startTest(Test test)
+ {
+ log.debug("public void startTest(Test test = " + test + "): called");
+
+ Result newResult = new Result(test.getClass().getName(), ((TestCase) test).getName());
+
+ // Initialize the thread local test results.
+ threadLocalResults.put(Thread.currentThread().getId(), newResult);
+ runs++;
+ }
+
+ /**
+ * Should be called every time a test completes with the run time of that test.
+ *
+ * @param test The name of the test.
+ * @param nanos The run time of the test in nanoseconds.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void timing(Test test, long nanos, Long threadId)
+ { }
+
+ /**
+ * Should be called every time a test completed with the amount of memory used before and after the test was run.
+ *
+ * @param test The test which memory was measured for.
+ * @param memStart The total JVM memory used before the test was run.
+ * @param memEnd The total JVM memory used after the test was run.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void memoryUsed(Test test, long memStart, long memEnd, Long threadId)
+ { }
+
+ /**
+ * Should be called every time a parameterized test completed with the int value of its test parameter.
+ *
+ * @param test The test which memory was measured for.
+ * @param parameter The int parameter value.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void parameterValue(Test test, int parameter, Long threadId)
+ { }
+
+ /**
+ * Should be called every time a test completes with the current number of test threads running.
+ *
+ * @param test The test for which the measurement is being generated.
+ * @param threads The number of tests being run concurrently.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void concurrencyLevel(Test test, int threads, Long threadId)
+ { }
+
+ /**
+ * Notifies listeners of the tests read/set properties.
+ *
+ * @param properties The tests read/set properties.
+ */
+ public void properties(Properties properties)
+ { }
+
+ /**
+ * Notification that a test ended.
+ *
+ * @param test The test that ended.
+ */
+ public void endTest(Test test)
+ {
+ log.debug("public void endTest(Test test = " + test + "): called");
+
+ // Move complete test results into the completed tests list.
+ Result r = threadLocalResults.get(Thread.currentThread().getId());
+ results.add(r);
+
+ // Clear all the test results for the thread.
+ threadLocalResults.remove(Thread.currentThread().getId());
+ }
+
+ /**
+ * Called when a test completes. Success, failure and errors. This method should be used when registering an
+ * end test from a different thread than the one that started the test.
+ *
+ * @param test The test which completed.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void endTest(Test test, Long threadId)
+ {
+ log.debug("public void endTest(Test test = " + test + ", Long threadId = " + threadId + "): called");
+
+ // Move complete test results into the completed tests list.
+ Result r =
+ (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId);
+ results.add(r);
+
+ // Clear all the test results for the thread.
+ threadLocalResults.remove(Thread.currentThread().getId());
+ }
+
+ /**
+ * An error occurred.
+ *
+ * @param test The test in which the error occurred.
+ * @param t The throwable that resulted from the error.
+ */
+ public void addError(Test test, Throwable t)
+ {
+ log.debug("public void addError(Test test = " + test + ", Throwable t = " + t + "): called");
+
+ Result r = threadLocalResults.get(Thread.currentThread().getId());
+ r.error = t;
+ errors++;
+ }
+
+ /**
+ * A failure occurred.
+ *
+ * @param test The test in which the failure occurred.
+ * @param t The JUnit assertions that led to the failure.
+ */
+ public void addFailure(Test test, AssertionFailedError t)
+ {
+ log.debug("public void addFailure(Test test = " + test + ", AssertionFailedError t = " + t + "): called");
+
+ Result r = threadLocalResults.get(Thread.currentThread().getId());
+ r.failure = t;
+ failures++;
+ }
+
+ /**
+ * Called when a test completes to mark it as a test fail. This method should be used when registering a
+ * failure from a different thread than the one that started the test.
+ *
+ * @param test The test which failed.
+ * @param e The assertion that failed the test.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void addFailure(Test test, AssertionFailedError e, Long threadId)
+ {
+ log.debug("public void addFailure(Test test, AssertionFailedError e, Long threadId): called");
+
+ Result r =
+ (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId);
+ r.failure = e;
+ failures++;
+ }
+
+ /**
+ * Notifies listeners of the start of a complete run of tests.
+ */
+ public void startBatch()
+ {
+ log.debug("public void startBatch(): called");
+
+ // Reset all results counts.
+ threadLocalResults = Collections.synchronizedMap(new HashMap());
+ errors = 0;
+ failures = 0;
+ runs = 0;
+
+ // Write out the file header.
+ try
+ {
+ writer.write("\n");
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("Unable to write the test results.", e);
+ }
+ }
+
+ /**
+ * Notifies listeners of the end of a complete run of tests.
+ *
+ * @param parameters The optional test parameters to log out with the batch results.
+ */
+ public void endBatch(Properties parameters)
+ {
+ log.debug("public void endBatch(Properties parameters = " + parameters + "): called");
+
+ // Write out the results.
+ try
+ {
+ // writer.write("\n");
+ writer.write("\n");
+
+ for (Result result : results)
+ {
+ writer.write(" \n");
+
+ if (result.error != null)
+ {
+ writer.write(" ");
+ result.error.printStackTrace(new PrintWriter(writer));
+ writer.write(" ");
+ }
+ else if (result.failure != null)
+ {
+ writer.write(" ");
+ result.failure.printStackTrace(new PrintWriter(writer));
+ writer.write(" ");
+ }
+
+ writer.write(" \n");
+ }
+
+ writer.write("\n");
+ writer.flush();
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("Unable to write the test results.", e);
+ }
+ }
+
+ /**
+ * Supplies the shutdown hook.
+ *
+ * @return The shut down hook.
+ */
+ public Thread getShutdownHook()
+ {
+ return new Thread(new Runnable()
+ {
+ public void run()
+ {
+ log.debug("XMLTestListener::ShutdownHook: called");
+ }
+ });
+ }
+
+ /**
+ * Used to capture the results of a particular test run.
+ */
+ protected static class Result
+ {
+ /** Holds the name of the test class. */
+ public String testClass;
+
+ /** Holds the name of the test method. */
+ public String testName;
+
+ /** Holds the exception that caused error in this test. */
+ public Throwable error;
+
+ /** Holds the assertion exception that caused failure in this test. */
+ public AssertionFailedError failure;
+
+ /**
+ * Creates a placeholder for the results of a test.
+ *
+ * @param testClass The test class.
+ * @param testName The name of the test that was run.
+ */
+ public Result(String testClass, String testName)
+ {
+ this.testClass = testClass;
+ this.testName = testName;
+ }
+ }
+}
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/package.html b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/package.html
new file mode 100644
index 0000000000..326d6e176e
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/listeners/package.html
@@ -0,0 +1,6 @@
+
+
+Listners for test statistics are defined in this package. At the moment there is only one listener which writes all test
+statistics out to a CSV (comma seperated values) file which can be loaded by most spread sheets.
+
+
\ No newline at end of file
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/package.html b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/package.html
new file mode 100644
index 0000000000..091dcce08e
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/package.html
@@ -0,0 +1,12 @@
+
+
+Basic JUnit is enahanced with test runners to run tests repeatedly, simultaneously in many threads and with increasing
+test sizes for asymptotic performance measurements. There are features to measure the time and amount of memory that
+tests use as well as to record the asymptotic test size parameters. There are some utilities to write these test
+statistics to various file formats too and these can be found in the listeners package.
+
+
The main test runner class is TKTestRunner which can be called with command line parameters to specify how tests
+should be run.
+
+
+
\ No newline at end of file
diff --git a/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/CommandLineParser.java b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/CommandLineParser.java
new file mode 100644
index 0000000000..744d59d905
--- /dev/null
+++ b/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/CommandLineParser.java
@@ -0,0 +1,782 @@
+/*
+ * Copyright 2007 Rupert Smith.
+ *
+ * Licensed 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.junit.extensions.util;
+
+import java.text.CharacterIterator;
+import java.text.StringCharacterIterator;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * CommandLineParser provides a utility for specifying the format of a command line and parsing command lines to ensure
+ * that they fit their specified format. A command line is made up of flags and options, both may be refered to as
+ * options. A flag is an option that does not take an argument (specifying it means it has the value 'true' and not
+ * specifying it means it has the value 'false'). Options must take arguments but they can be set up with defaults so
+ * that they take a default value when not set. Options may be mandatory in wich case it is an error not to specify
+ * them on the command line. Flags are never mandatory because they are implicitly set to false when not specified.
+ *
+ * Some examples command line are:
+ *
+ *
+ *
This one has two options that expect arguments:
+ *
This has one no-arg flag and two 'free' arguments:
+ *
+ * zip -r project.zip project/*
+ *
+ *
This one concatenates multiple flags into a single block with only one '-':
+ *
+ * jar -tvf mytar.tar
+ *
+ *
+ * The parsing rules are:
+ *
+ *
+ *
Flags may be combined after a single '-' because they never take arguments. Normally such flags are single letter
+ * flags but this is only a convention and not enforced. Flags of more than one letter are usually specified on their own.
+ *
Options expecting arguments must always be on their own.
+ *
The argument to an option may be seperated from it by whitespace or appended directly onto the option.
+ *
The argument to an option may never begin with a '-' character.
+ *
All other arguments not beginning with a '-' character are free arguments that do not belong to any option.
+ *
The second or later of a set of duplicate or repeated flags override earlier ones.
+ *
Options are matched up to the shortest matching option. This is because of the possibility of having no space
+ * between an option and its argument. This rules out the possibility of using two options where one is an opening
+ * substring of the other. For example, the options "foo" and "foobar" cannot be used on the same command line because
+ * it is not possible to distinguish the argument "-foobar" from being the "foobar" option or the "foo" option with
+ * the "bar" argument.
+ *
+ *
+ * By default, unknown options are simply ignored if specified on the command line. This behaviour may be changed
+ * so that the parser reports all unknowns as errors by using the {@link #setErrorsOnUnknowns} method.
+ *
+ *
CRC Card
+ *
Responsibilities
Collaborations
+ *
Accept a command line specification.
+ *
Parse a command line into properties, validating it against its specification.
+ *
Report all errors between a command line and its specification.
+ *
Provide a formatted usage string for a command line.
+ *
Provide a formatted options in force string for a command line.
+ *
Allow errors on unknowns behaviour to be turned on or off.
+ *
+ *
+ * @author Rupert Smith
+ */
+public class CommandLineParser
+{
+ /**
+ * Holds a mapping from command line option names to detailed information about those options.
+ * Use of a tree map ensures that the options are easy to print in alphabetical order as a usage string.
+ * An alternative might be to use a LinkedHashMap to print them in the order they are specified.
+ */
+ private Map optionMap = new TreeMap();
+
+ /** Holds a list of parsing errors. */
+ private List parsingErrors = new ArrayList();
+
+ /** Holds the regular head matcher to match command line options with. */
+ private Matcher optionMatcher = null;
+
+ /** Holds the parsed command line properties after parsing. */
+ private Properties parsedProperties = null;
+
+ /** Holds any trailing name=value pairs specified in the free arguments. */
+ private Properties trailingProperties = null;
+
+ /** Flag used to indicate that errors should be created for unknown options. False by default. */
+ private boolean errorsOnUnknowns = false;
+
+ /**
+ * Creates a command line options parser from a command line specification. This is passed to this constructor
+ * as an array of arrays of strings. Each array of strings specifies the command line for a single option. A static
+ * array may therefore easily be used to configure the command line parser in a single method call with an easily
+ * readable format.
+ *
+ * Each array of strings must be 2, 3, 4 or 5 elements long. If any of the last three elements are missing they
+ * are assumed to be null. The elements specify the following parameters:
+ *
+ *
The name of the option without the leading '-'. For example, "file". To specify the format of the 'free'
+ * arguments use the option names "1", "2", ... and so on.
+ *
The option comment. A line of text describing the usage of the option. For example, "The file to be processed."
+ *
The options argument. This is a very short description of the argument to the option, often a single word
+ * or a reminder as to the arguments format. When this element is null the option is a flag and does not
+ * accept any arguments. For example, "filename" or "(unix | windows)" or null. The actual text specified
+ * is only used to print in the usage message to remind the user of the usage of the option.
+ *
The mandatory flag. When set to "true" an option must always be specified. Any other value, including null,
+ * means that the option is mandatory. Flags are always mandatory (see class javadoc for explanation of why) so
+ * this is ignored for flags.
+ *
A regular head describing the format that the argument must take. Ignored if null.
+ *
+ * An example call to this constructor is:
+ *
+ *
+ * CommandLineParser commandLine = new CommandLineParser(
+ * new String[][] {{"file", "The file to be processed. ", "filename", "true"},
+ * {"dir", "Directory to store results in. Current dir used if not set.", "out dir"},
+ * {"os", "Operating system EOL format to use.", "(windows | unix)", null, "windows\|unix"},
+ * {"v", "Verbose mode. Prints information about the processing as it goes."},
+ * {"1", "The processing command to run.", "command", "true", "add\|remove\|list"}});
+ *
+ *
+ * @param config The configuration as an array of arrays of strings.
+ */
+ public CommandLineParser(String[][] config)
+ {
+ // Loop through all the command line option specifications creating details for each in the options map.
+ for (String[] nextOptionSpec : config)
+ {
+ addOption(nextOptionSpec[0], nextOptionSpec[1], (nextOptionSpec.length > 2) ? nextOptionSpec[2] : null,
+ (nextOptionSpec.length > 3) && ("true".equals(nextOptionSpec[3])),
+ (nextOptionSpec.length > 4) ? nextOptionSpec[4] : null);
+ }
+ }
+
+ /**
+ * Extracts all name=value pairs from the command line, sets them all as system properties and also returns
+ * a map of properties containing them.
+ *
+ * @param args The command line.
+ * @param commandLine The command line parser.
+ * @param properties The properties object to inject all parsed properties into (optional may be null).
+ *
+ * @return A set of properties containing all name=value pairs from the command line.
+ */
+ public static Properties processCommandLine(String[] args, CommandLineParser commandLine, Properties properties)
+ {
+ // Capture the command line arguments or display errors and correct usage and then exit.
+ Properties options = null;
+
+ try
+ {
+ options = commandLine.parseCommandLine(args);
+
+ // Add all the command line options and trailing settings to properties if the optional properties object
+ // to copy them into has been set.
+ if (properties != null)
+ {
+ commandLine.addTrailingPairsToProperties(properties);
+ commandLine.addOptionsToProperties(properties);
+ }
+ }
+ catch (IllegalArgumentException e)
+ {
+ System.out.println(commandLine.getErrors());
+ System.out.println(commandLine.getUsage());
+ System.exit(1);
+ }
+
+ return options;
+ }
+
+ /**
+ * Lists all the parsing errors from the most recent parsing in a string.
+ *
+ * @return All the parsing errors from the most recent parsing.
+ */
+ public String getErrors()
+ {
+ // Return the empty string if there are no errors.
+ if (parsingErrors.isEmpty())
+ {
+ return "";
+ }
+
+ // Concatenate all the parsing errors together.
+ String result = "";
+
+ for (String s : parsingErrors)
+ {
+ result += s;
+ }
+
+ return result;
+ }
+
+ /**
+ * Lists the properties set from the most recent parsing or an empty string if no parsing has been done yet.
+ *
+ * @return The properties set from the most recent parsing or an empty string if no parsing has been done yet.
+ */
+ public String getOptionsInForce()
+ {
+ // Check if there are no properties to report and return and empty string if so.
+ if (parsedProperties == null)
+ {
+ return "";
+ }
+
+ // List all the properties.
+ String result = "Options in force:\n";
+
+ for (Map.Entry