You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by rg...@apache.org on 2007/03/15 11:12:58 UTC

svn commit: r518559 - in /incubator/qpid/trunk/qpid/java: common/src/main/java/org/apache/qpid/util/ integrationtests/src/main/java/org/apache/qpid/interop/coordinator/ integrationtests/src/main/java/org/apache/qpid/interop/testclient/ integrationtests...

Author: rgreig
Date: Thu Mar 15 03:12:57 2007
New Revision: 518559

URL: http://svn.apache.org/viewvc?view=rev&rev=518559
Log:
Commit of interop test stuff prior to M2 branch.

Added:
    incubator/qpid/trunk/qpid/java/common/src/main/java/org/apache/qpid/util/PropertiesUtils.java
    incubator/qpid/trunk/qpid/java/common/src/main/java/org/apache/qpid/util/ReflectionUtils.java
    incubator/qpid/trunk/qpid/java/common/src/main/java/org/apache/qpid/util/ReflectionUtilsException.java
Modified:
    incubator/qpid/trunk/qpid/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/CoordinatingTestCase.java
    incubator/qpid/trunk/qpid/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/TestClientDetails.java
    incubator/qpid/trunk/qpid/java/integrationtests/src/main/java/org/apache/qpid/interop/testclient/TestClient.java
    incubator/qpid/trunk/qpid/java/integrationtests/src/main/java/org/apache/qpid/util/ClasspathScanner.java
    incubator/qpid/trunk/qpid/java/integrationtests/src/main/java/org/apache/qpid/util/ConversationHelper.java

Added: incubator/qpid/trunk/qpid/java/common/src/main/java/org/apache/qpid/util/PropertiesUtils.java
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/java/common/src/main/java/org/apache/qpid/util/PropertiesUtils.java?view=auto&rev=518559
==============================================================================
--- incubator/qpid/trunk/qpid/java/common/src/main/java/org/apache/qpid/util/PropertiesUtils.java (added)
+++ incubator/qpid/trunk/qpid/java/common/src/main/java/org/apache/qpid/util/PropertiesUtils.java Thu Mar 15 03:12:57 2007
@@ -0,0 +1,199 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Iterator;
+import java.util.Properties;
+
+import org.apache.log4j.Logger;
+
+/**
+ * PropertiesHelper defines some static methods which are useful when working with properties
+ * files.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Read properties from an input stream
+ * <tr><td> Read properties from a file
+ * <tr><td> Read properties from a URL
+ * <tr><td> Read properties given a path to a file
+ * <tr><td> Trim any whitespace from property values
+ * </table>
+ */
+public class PropertiesUtils
+{
+    /** Used for logging. */
+    private static final Logger log = Logger.getLogger(PropertiesUtils.class);
+
+    /**
+     * Get properties from an input stream.
+     *
+     * @param is The input stream.
+     *
+     * @return The properties loaded from the input stream.
+     *
+     * @throws IOException If the is an I/O error reading from the stream.
+     */
+    public static Properties getProperties(InputStream is) throws IOException
+    {
+        log.debug("getProperties(InputStream): called");
+
+        // Create properties object laoded from input stream
+        Properties properties = new Properties();
+
+        properties.load(is);
+
+        return properties;
+    }
+
+    /**
+     * Get properties from a file.
+     *
+     * @param file The file.
+     *
+     * @return The properties loaded from the file.
+     *
+     * @throws IOException If there is an I/O error reading from the file.
+     */
+    public static Properties getProperties(File file) throws IOException
+    {
+        log.debug("getProperties(File): called");
+
+        // Open the file as an input stream
+        InputStream is = new FileInputStream(file);
+
+        // Create properties object loaded from the stream
+        Properties properties = getProperties(is);
+
+        // Close the file
+        is.close();
+
+        return properties;
+    }
+
+    /**
+     * Get properties from a url.
+     *
+     * @param url The URL.
+     *
+     * @return The properties loaded from the url.
+     *
+     * @throws IOException If there is an I/O error reading from the URL.
+     */
+    public static Properties getProperties(URL url) throws IOException
+    {
+        log.debug("getProperties(URL): called");
+
+        // Open the URL as an input stream
+        InputStream is = url.openStream();
+
+        // Create properties object loaded from the stream
+        Properties properties = getProperties(is);
+
+        // Close the url
+        is.close();
+
+        return properties;
+    }
+
+    /**
+     * Get properties from a path name. The path name may refer to either a file or a URL.
+     *
+     * @param pathname The path name.
+     *
+     * @return The properties loaded from the file or URL.
+     *
+     * @throws IOException If there is an I/O error reading from the URL or file named by the path.
+     */
+    public static Properties getProperties(String pathname) throws IOException
+    {
+        log.debug("getProperties(String): called");
+
+        // Check that the path is not null
+        if (pathname == null)
+        {
+            return null;
+        }
+
+        // Check if the path is a URL
+        if (isURL(pathname))
+        {
+            // The path is a URL
+            return getProperties(new URL(pathname));
+        }
+        else
+        {
+            // Assume the path is a file name
+            return getProperties(new File(pathname));
+        }
+    }
+
+    /**
+     * Trims whitespace from property values. This method returns a new set of properties
+     * the same as the properties specified as an argument but with any white space removed by
+     * the {@link java.lang.String#trim} method.
+     *
+     * @param properties The properties to trim whitespace from.
+     *
+     * @return The white space trimmed properties.
+     */
+    public static Properties trim(Properties properties)
+    {
+        Properties trimmedProperties = new Properties();
+
+        // Loop over all the properties
+        for (Iterator i = properties.keySet().iterator(); i.hasNext();)
+        {
+            String next = (String) i.next();
+            String nextValue = properties.getProperty(next);
+
+            // Trim the value if it is not null
+            if (nextValue != null)
+            {
+                nextValue.trim();
+            }
+
+            // Store the trimmed value in the trimmed properties
+            trimmedProperties.setProperty(next, nextValue);
+        }
+
+        return trimmedProperties;
+    }
+
+    /**
+     * Helper method. Guesses whether a string is a URL or not. A String is considered to be a url if it begins with
+     * http:, ftp:, or uucp:.
+     *
+     * @param name The string to test for being a URL.
+     *
+     * @return True if the string is a URL and false if not.
+     */
+    private static boolean isURL(String name)
+    {
+        return (name.toLowerCase().startsWith("http:") || name.toLowerCase().startsWith("ftp:")
+                || name.toLowerCase().startsWith("uucp:"));
+    }
+}

Added: incubator/qpid/trunk/qpid/java/common/src/main/java/org/apache/qpid/util/ReflectionUtils.java
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/java/common/src/main/java/org/apache/qpid/util/ReflectionUtils.java?view=auto&rev=518559
==============================================================================
--- incubator/qpid/trunk/qpid/java/common/src/main/java/org/apache/qpid/util/ReflectionUtils.java (added)
+++ incubator/qpid/trunk/qpid/java/common/src/main/java/org/apache/qpid/util/ReflectionUtils.java Thu Mar 15 03:12:57 2007
@@ -0,0 +1,228 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.util;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Provides helper methods for operating on classes and methods using reflection. Reflection methods tend to return
+ * a lot of checked exception so writing code to use them can be tedious and harder to read, especially when such errors
+ * are not expected to occur. This class always works with {@link ReflectionUtilsException}, which is a runtime exception,
+ * to wrap the checked exceptions raised by the standard Java reflection methods. Code using it does not normally
+ * expect these errors to occur, usually does not have a recovery mechanism for them when they do, but is cleaner,
+ * quicker to write and easier to read in the majority of cases.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Look up Classes by name.
+ * <tr><td> Instantiate Classes by no-arg constructor.
+ * </table>
+ */
+public class ReflectionUtils
+{
+    /**
+    * Gets the Class object for a named class.
+    *
+    * @param className The class to get the Class object for.
+    *
+    * @return The Class object for the named class.
+    */
+    public static Class<?> forName(String className)
+    {
+        try
+        {
+            return Class.forName(className);
+        }
+        catch (ClassNotFoundException e)
+        {
+            throw new ReflectionUtilsException("ClassNotFoundException whilst finding class.", e);
+        }
+    }
+
+    /**
+     * Creates an instance of a Class, instantiated through its no-args constructor.
+     *
+     * @param cls The Class to instantiate.
+     * @param <T> The Class type.
+     *
+     * @return An instance of the class.
+     */
+    public static <T> T newInstance(Class<? extends T> cls)
+    {
+        try
+        {
+            return cls.newInstance();
+        }
+        catch (InstantiationException e)
+        {
+            throw new ReflectionUtilsException("InstantiationException whilst instantiating class.", e);
+        }
+        catch (IllegalAccessException e)
+        {
+            throw new ReflectionUtilsException("IllegalAccessException whilst instantiating class.", e);
+        }
+    }
+
+    /**
+     * Calls a named method on an object with a specified set of parameters, any Java access modifier are overridden.
+     *
+     * @param o            The object to call.
+     * @param method       The method name to call.
+     * @param params       The parameters to pass.
+     * @param paramClasses The argument types.
+     *
+     * @return The return value from the method call.
+     */
+    public static Object callMethodOverridingIllegalAccess(Object o, String method, Object[] params, Class[] paramClasses)
+    {
+        // Get the objects class.
+        Class cls = o.getClass();
+
+        // Get the classes of the parameters.
+        /*Class[] paramClasses = new Class[params.length];
+
+        for (int i = 0; i < params.length; i++)
+        {
+            paramClasses[i] = params[i].getClass();
+        }*/
+
+        try
+        {
+            // Try to find the matching method on the class.
+            Method m = cls.getDeclaredMethod(method, paramClasses);
+
+            // Make it accessible.
+            m.setAccessible(true);
+
+            // Invoke it with the parameters.
+            return m.invoke(o, params);
+        }
+        catch (NoSuchMethodException e)
+        {
+            throw new RuntimeException(e);
+        }
+        catch (IllegalAccessException e)
+        {
+            throw new RuntimeException(e);
+        }
+        catch (InvocationTargetException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Calls a named method on an object with a specified set of parameters.
+     *
+     * @param o      The object to call.
+     * @param method The method name to call.
+     * @param params The parameters to pass.
+     *
+     * @return The return value from the method call.
+     */
+    public static Object callMethod(Object o, String method, Object[] params)
+    {
+        // Get the objects class.
+        Class cls = o.getClass();
+
+        // Get the classes of the parameters.
+        Class[] paramClasses = new Class[params.length];
+
+        for (int i = 0; i < params.length; i++)
+        {
+            paramClasses[i] = params[i].getClass();
+        }
+
+        try
+        {
+            // Try to find the matching method on the class.
+            Method m = cls.getMethod(method, paramClasses);
+
+            // Invoke it with the parameters.
+            return m.invoke(o, params);
+        }
+        catch (NoSuchMethodException e)
+        {
+            throw new RuntimeException(e);
+        }
+        catch (IllegalAccessException e)
+        {
+            throw new RuntimeException(e);
+        }
+        catch (InvocationTargetException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Calls a constuctor witht the specified arguments.
+     *
+     * @param constructor The constructor.
+     * @param args        The arguments.
+     * @param <T>         The Class type.
+     *
+     * @return An instance of the class that the constructor is for.
+     */
+    public static <T> T newInstance(Constructor<T> constructor, Object[] args)
+    {
+        try
+        {
+            return constructor.newInstance(args);
+        }
+        catch (InstantiationException e)
+        {
+            throw new RuntimeException(e);
+        }
+        catch (IllegalAccessException e)
+        {
+            throw new RuntimeException(e);
+        }
+        catch (InvocationTargetException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Gets the constructor of a class that takes the specified set of arguments if any matches. If no matching
+     * constructor is found then a runtime exception is raised.
+     *
+     * @param cls  The class to get a constructor from.
+     * @param args The arguments to match.
+     * @param <T>  The class type.
+     *
+     * @return The constructor.
+     */
+    public static <T> Constructor<T> getConstructor(Class<T> cls, Class[] args)
+    {
+        try
+        {
+            return cls.getConstructor(args);
+        }
+        catch (NoSuchMethodException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+}

Added: incubator/qpid/trunk/qpid/java/common/src/main/java/org/apache/qpid/util/ReflectionUtilsException.java
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/java/common/src/main/java/org/apache/qpid/util/ReflectionUtilsException.java?view=auto&rev=518559
==============================================================================
--- incubator/qpid/trunk/qpid/java/common/src/main/java/org/apache/qpid/util/ReflectionUtilsException.java (added)
+++ incubator/qpid/trunk/qpid/java/common/src/main/java/org/apache/qpid/util/ReflectionUtilsException.java Thu Mar 15 03:12:57 2007
@@ -0,0 +1,44 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.util;
+
+/**
+ * Wraps a checked exception that occurs when {@link ReflectionUtils} encounters checked exceptions using standard
+ * Java reflection methods.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Wrap a checked reflection exception.
+ * </table>
+ */
+public class ReflectionUtilsException extends RuntimeException
+{
+    /**
+     * Creates a runtime reflection exception, from a checked one.
+     *
+     * @param message The message.
+     * @param cause   The causing exception.
+     */
+    public ReflectionUtilsException(String message, Throwable cause)
+    {
+        super(message, cause);
+    }
+}

Modified: incubator/qpid/trunk/qpid/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/CoordinatingTestCase.java
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/CoordinatingTestCase.java?view=diff&rev=518559&r1=518558&r2=518559
==============================================================================
--- incubator/qpid/trunk/qpid/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/CoordinatingTestCase.java (original)
+++ incubator/qpid/trunk/qpid/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/CoordinatingTestCase.java Thu Mar 15 03:12:57 2007
@@ -67,8 +67,8 @@
      * @param allClients     The list of all possible test clients that may accept the invitation.
      * @param testProperties The test case definition.
      */
-    public CoordinatingTestCase(TestClientDetails sender, TestClientDetails receiver,
-                                Collection<TestClientDetails> allClients, Properties testProperties)
+    public void TestCase(TestClientDetails sender, TestClientDetails receiver, Collection<TestClientDetails> allClients,
+                         Properties testProperties)
     { }
 
     /**
@@ -83,8 +83,7 @@
      *
      * @return The test results from the senders and receivers.
      */
-    protected Object[] sequenceTest(TestClientDetails sender, TestClientDetails receiver,
-                                    Collection<TestClientDetails> allParticipatingClients, Properties testProperties)
+    protected Object[] sequenceTest(TestClientDetails sender, TestClientDetails receiver, Properties testProperties)
     {
         // Check if the sender and recevier did not accept the invite to this test.
         {

Modified: incubator/qpid/trunk/qpid/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/TestClientDetails.java
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/TestClientDetails.java?view=diff&rev=518559&r1=518558&r2=518559
==============================================================================
--- incubator/qpid/trunk/qpid/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/TestClientDetails.java (original)
+++ incubator/qpid/trunk/qpid/java/integrationtests/src/main/java/org/apache/qpid/interop/coordinator/TestClientDetails.java Thu Mar 15 03:12:57 2007
@@ -28,8 +28,10 @@
 public class TestClientDetails
 {
     /** The test clients name. */
+    public String clientName;
 
     /* The test clients unqiue sequence number. Not currently used. */
 
     /** The routing key of the test clients control topic. */
+    public String privateControlKey;
 }

Modified: incubator/qpid/trunk/qpid/java/integrationtests/src/main/java/org/apache/qpid/interop/testclient/TestClient.java
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/java/integrationtests/src/main/java/org/apache/qpid/interop/testclient/TestClient.java?view=diff&rev=518559&r1=518558&r2=518559
==============================================================================
--- incubator/qpid/trunk/qpid/java/integrationtests/src/main/java/org/apache/qpid/interop/testclient/TestClient.java (original)
+++ incubator/qpid/trunk/qpid/java/integrationtests/src/main/java/org/apache/qpid/interop/testclient/TestClient.java Thu Mar 15 03:12:57 2007
@@ -20,12 +20,22 @@
  */
 package org.apache.qpid.interop.testclient;
 
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Properties;
 
-import javax.jms.Message;
-import javax.jms.MessageListener;
+import javax.jms.*;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
 
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.util.ClasspathScanner;
 import org.apache.qpid.util.CommandLineParser;
+import org.apache.qpid.util.PropertiesUtils;
 
 /**
  * Implements a test client as described in the interop testing spec
@@ -49,76 +59,26 @@
  */
 public class TestClient implements MessageListener
 {
+    private static Logger log = Logger.getLogger(TestClient.class);
+
     /** Holds the URL of the broker to run the tests on. */
     String brokerUrl;
 
     /** Holds the virtual host to run the tests on. If <tt>null</tt>, then the default virtual host is used. */
     String virtualHost;
 
-    /** Defines an enumeration of the control message types and handling behaviour for each. */
-    protected enum ControlMessages implements MessageListener
-    {
-        INVITE_COMPULSORY
-        {
-            public void onMessage(Message message)
-            {
-                // Reply with the client name in an Enlist message.
-            }
-        },
-        INVITE
-        {
-            public void onMessage(Message message)
-            {
-                // Extract the test properties.
-
-                // Check if the requested test case is available.
-                {
-                    // Make the requested test case the current test case.
-
-                    // Reply by accepting the invite in an Enlist message.
-                }
-            }
-        },
-        ASSIGN_ROLE
-        {
-            public void onMessage(Message message)
-            {
-                // Extract the test properties.
+    /** Holds all the test cases loaded from the classpath. */
+    Map<String, InteropClientTestCase> testCases = new HashMap<String, InteropClientTestCase>();
 
-                // Reply by accepting the role in an Accept Role message.
-            }
-        },
-        START
-        {
-            public void onMessage(Message message)
-            {
-                // Start the current test case.
+    InteropClientTestCase currentTestCase;
 
-                // Generate the report from the test case and reply with it as a Report message.
-            }
-        },
-        STATUS_REQUEST
-        {
-            public void onMessage(Message message)
-            {
-                // Generate the report from the test case and reply with it as a Report message.
-            }
-        },
-        UNKNOWN
-        {
-            public void onMessage(Message message)
-            {
-                // Log a warning about this but otherwise ignore it.
-            }
-        };
+    public static final String CONNECTION_PROPERTY = "connectionfactory.broker";
+    public static final String CONNECTION_NAME = "broker";
+    public static final String CLIENT_NAME = "java";
+    public static final String DEFAULT_CONNECTION_PROPS_RESOURCE = "org/apache/qpid/interop/client/connection.properties";
 
-        /**
-         * Handles control messages appropriately depending on the message type.
-         *
-         * @param message The incoming message to handle.
-         */
-        public abstract void onMessage(Message message);
-    }
+    private MessageProducer producer;
+    private Session session;
 
     public TestClient(String brokerUrl, String virtualHost)
     {
@@ -172,42 +132,199 @@
 
         // Create a test client and start it running.
         TestClient client = new TestClient(brokerUrl, virtualHost);
-        client.start();
+
+        try
+        {
+            client.start();
+        }
+        catch (Exception e)
+        {
+            log.error("The test client was unable to start.", e);
+            System.exit(1);
+        }
     }
 
-    private void start()
+    private void start() throws JMSException
     {
         // Use a class path scanner to find all the interop test case implementations.
+        Collection<Class<? extends InteropClientTestCase>> testCaseClasses =
+            ClasspathScanner.getMatches(InteropClientTestCase.class, "^TestCase.*", true);
 
         // Create all the test case implementations and index them by the test names.
+        for (Class<? extends InteropClientTestCase> nextClass : testCaseClasses)
+        {
+            try
+            {
+                InteropClientTestCase testCase = nextClass.newInstance();
+                testCases.put(testCase.getName(), testCase);
+            }
+            catch (InstantiationException e)
+            {
+                log.warn("Could not instantiate test case class: " + nextClass.getName(), e);
+                // Ignored.
+            }
+            catch (IllegalAccessException e)
+            {
+                log.warn("Could not instantiate test case class due to illegal access: " + nextClass.getName(), e);
+                // Ignored.
+            }
+        }
 
         // Open a connection to communicate with the coordinator on.
+        Connection connection = createConnection(DEFAULT_CONNECTION_PROPS_RESOURCE, brokerUrl, virtualHost);
+
+        session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
 
         // Set this up to listen for control messages.
+        MessageConsumer consumer = session.createConsumer(session.createTopic("iop.control." + CLIENT_NAME));
+        consumer.setMessageListener(this);
 
         // Create a producer to send replies with.
+        producer = session.createProducer(null);
+
+        // Start listening for incoming control messages.
+        connection.start();
     }
 
     /**
-     * Handles all incoming control messages.
+     * Establishes a JMS connection using a properties file and qpids built in JNDI implementation. This is a simple
+     * convenience method for code that does anticipate handling connection failures. All exceptions that indicate
+     * that the connection has failed, are wrapped as rutime exceptions, preumably handled by a top level failure
+     * handler.
      *
-     * @param message The incoming message.
+     * @todo Make username/password configurable. Allow multiple urls for fail over. Once it feels right, move it
+     *       to a Utils library class.
+     *
+     * @param connectionPropsResource The name of the connection properties file.
+     * @param brokerUrl               The broker url to connect to, <tt>null</tt> to use the default from the properties.
+     * @param virtualHost             The virtual host to connectio to, <tt>null</tt> to use the default.
+     *
+     * @return A JMS conneciton.
      */
-    public void onMessage(Message message)
+    private static Connection createConnection(String connectionPropsResource, String brokerUrl, String virtualHost)
     {
-        // Delegate the message handling to the message type specific handler.
-        extractMessageType(message).onMessage(message);
+        try
+        {
+            Properties connectionProps =
+                PropertiesUtils.getProperties(TestClient.class.getClassLoader().getResourceAsStream(
+                                                  connectionPropsResource));
+
+            String connectionString =
+                "amqp://guest:guest/" + ((virtualHost != null) ? virtualHost : "") + "?brokerlist='" + brokerUrl + "'";
+            connectionProps.setProperty(CONNECTION_PROPERTY, connectionString);
+
+            Context ctx = new InitialContext(connectionProps);
+
+            ConnectionFactory cf = (ConnectionFactory) ctx.lookup(CONNECTION_NAME);
+            Connection connection = cf.createConnection();
+
+            return connection;
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+        catch (NamingException e)
+        {
+            throw new RuntimeException(e);
+        }
+        catch (JMSException e)
+        {
+            throw new RuntimeException(e);
+        }
     }
 
     /**
-     * Determines the control messsage type of incoming messages.
-     *
-     * @param message The message to determine the type of.
+     * Handles all incoming control messages.
      *
-     * @return The control message type of the message.
+     * @param message The incoming message.
      */
-    protected ControlMessages extractMessageType(Message message)
+    public void onMessage(Message message)
     {
-        return null;
+        try
+        {
+            String controlType = message.getStringProperty("CONTROL_TYPE");
+            String testName = message.getStringProperty("TEST_NAME");
+
+            // Check if the message is a test invite.
+            if ("INVITE".equals(controlType))
+            {
+                String testCaseName = message.getStringProperty("TEST_NAME");
+
+                // Flag used to indicate that an enlist should be sent. Only enlist to compulsory invites or invites
+                // for which test cases exist.
+                boolean enlist = false;
+
+                if (testCaseName != null)
+                {
+                    // Check if the requested test case is available.
+                    InteropClientTestCase testCase = testCases.get(testCaseName);
+
+                    if (testCase != null)
+                    {
+                        // Make the requested test case the current test case.
+                        currentTestCase = testCase;
+                        enlist = true;
+                    }
+                }
+                else
+                {
+                    enlist = true;
+                }
+
+                if (enlist)
+                {
+                    // Reply with the client name in an Enlist message.
+                    Message enlistMessage = session.createMessage();
+                    enlistMessage.setStringProperty("CONTROL_TYPE", "ENLIST");
+                    enlistMessage.setStringProperty("CLIENT_NAME", CLIENT_NAME);
+                    enlistMessage.setStringProperty("CLIENT_PRIVATE_CONTROL_KEY", "iop.control." + CLIENT_NAME);
+                    enlistMessage.setJMSCorrelationID(message.getJMSCorrelationID());
+
+                    producer.send(message.getJMSReplyTo(), enlistMessage);
+                }
+            }
+            else if ("ASSIGN_ROLE".equals(controlType))
+            {
+                // Assign the role to the current test case.
+                String roleName = message.getStringProperty("");
+                InteropClientTestCase.Roles role = Enum.valueOf(InteropClientTestCase.Roles.class, roleName);
+
+                currentTestCase.assignRole(role, message);
+
+                // Reply by accepting the role in an Accept Role message.
+                Message acceptRoleMessage = session.createMessage();
+                acceptRoleMessage.setStringProperty("CONTROL_TYPE", "ACCEPT_ROLE");
+                acceptRoleMessage.setJMSCorrelationID(message.getJMSCorrelationID());
+
+                producer.send(message.getJMSReplyTo(), acceptRoleMessage);
+            }
+            else if ("START".equals(controlType) || "STATUS_REQUEST".equals(controlType))
+            {
+                if ("START".equals(controlType))
+                {
+                    // Start the current test case.
+                    currentTestCase.start();
+                }
+
+                // Generate the report from the test case and reply with it as a Report message.
+                Message reportMessage = currentTestCase.getReport(session);
+                reportMessage.setStringProperty("CONTROL_TYPE", "REPORT");
+                reportMessage.setJMSCorrelationID(message.getJMSCorrelationID());
+
+                producer.send(message.getJMSReplyTo(), reportMessage);
+            }
+            else
+            {
+                // Log a warning about this but otherwise ignore it.
+                log.warn("Got an unknown control message: " + message);
+            }
+        }
+        catch (JMSException e)
+        {
+            // Log a warning about this, but otherwise ignore it.
+            log.warn("A JMSException occurred whilst handling a message.");
+            log.debug("Got JMSException whilst handling message: " + message, e);
+        }
     }
 }

Modified: incubator/qpid/trunk/qpid/java/integrationtests/src/main/java/org/apache/qpid/util/ClasspathScanner.java
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/java/integrationtests/src/main/java/org/apache/qpid/util/ClasspathScanner.java?view=diff&rev=518559&r1=518558&r2=518559
==============================================================================
--- incubator/qpid/trunk/qpid/java/integrationtests/src/main/java/org/apache/qpid/util/ClasspathScanner.java (original)
+++ incubator/qpid/trunk/qpid/java/integrationtests/src/main/java/org/apache/qpid/util/ClasspathScanner.java Thu Mar 15 03:12:57 2007
@@ -22,6 +22,10 @@
 
 import java.io.File;
 import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.log4j.Logger;
 
 /**
  * An ClasspathScanner scans the classpath for classes that implement an interface or extend a base class and have names
@@ -38,10 +42,12 @@
  * <tr><th> Responsibilities <th> Collaborations
  * <tr><td> Find all classes matching type and name pattern on the classpath.
  * </table>
+ *
+ * @todo Add logic to scan jars as well as directories.
  */
 public class ClasspathScanner
 {
-    static final int SUFFIX_LENGTH = ".class".length();
+    private static final Logger log = Logger.getLogger(ClasspathScanner.class);
 
     /**
      * Scans the classpath and returns all classes that extend a specified class and match a specified name.
@@ -49,58 +55,124 @@
      * that have a default constructor).
      *
      * @param matchingClass  The class or interface to match.
-     * @param matchingRegexp The reular expression to match against the class name.
+     * @param matchingRegexp The regular expression to match against the class name.
      * @param beanOnly       Flag to indicate that onyl classes with default constructors should be matched.
      *
      * @return All the classes that match this collector.
      */
-    public static Collection<Class<?>> getMatches(Class<?> matchingClass, String matchingRegexp, boolean beanOnly)
+    public static <T> Collection<Class<? extends T>> getMatches(Class<T> matchingClass, String matchingRegexp,
+                                                                boolean beanOnly)
     {
+        // Build a compiled regular expression from the pattern to match.
+        Pattern matchPattern = Pattern.compile(matchingRegexp);
+
         String classPath = System.getProperty("java.class.path");
-        Map result = new HashMap();
+        Map<String, Class<? extends T>> result = new HashMap<String, Class<? extends T>>();
 
+        // Find matching classes starting from all roots in the classpath.
         for (String path : splitClassPath(classPath))
         {
-            gatherFiles(new File(path), "", result);
+            gatherFiles(new File(path), "", result, matchPattern, matchingClass);
         }
 
         return result.values();
     }
 
-    private static void gatherFiles(File classRoot, String classFileName, Map result)
+    /**
+     * Finds all matching classes rooted at a given location in the file system. If location is a directory it
+     * is recursively examined.
+     *
+     * @param classRoot     The root of the current point in the file system being examined.
+     * @param classFileName The name of the current file or directory to examine.
+     * @param result        The accumulated mapping from class names to classes that match the scan.
+     *
+     * @todo Recursion ok as file system depth is not likely to exhaust the stack. Might be better to replace with
+     *       iteration.
+     */
+    private static <T> void gatherFiles(File classRoot, String classFileName, Map<String, Class<? extends T>> result,
+                                        Pattern matchPattern, Class<? extends T> matchClass)
     {
         File thisRoot = new File(classRoot, classFileName);
 
+        // If the current location is a file, check if it is a matching class.
         if (thisRoot.isFile())
         {
-            if (matchesName(classFileName))
+            // Check that the file has a matching name.
+            if (matchesName(classFileName, matchPattern))
             {
                 String className = classNameFromFile(classFileName);
-                result.put(className, className);
+
+                // Check that the class has matching type.
+                try
+                {
+                    Class<?> candidateClass = Class.forName(className);
+
+                    Class matchedClass = matchesClass(candidateClass, matchClass);
+
+                    if (matchedClass != null)
+                    {
+                        result.put(className, matchedClass);
+                    }
+                }
+                catch (ClassNotFoundException e)
+                {
+                    // Ignore this. The matching class could not be loaded.
+                    log.debug("Got ClassNotFoundException, ignoring.", e);
+                }
             }
 
             return;
         }
-
-        String[] contents = thisRoot.list();
-
-        if (contents != null)
+        // Otherwise the current location is a directory, so examine all of its contents.
+        else
         {
-            for (String content : contents)
+            String[] contents = thisRoot.list();
+
+            if (contents != null)
             {
-                gatherFiles(classRoot, classFileName + File.separatorChar + content, result);
+                for (String content : contents)
+                {
+                    gatherFiles(classRoot, classFileName + File.separatorChar + content, result, matchPattern, matchClass);
+                }
             }
         }
     }
 
-    private static boolean matchesName(String classFileName)
+    /**
+     * Checks if the specified class file name corresponds to a class with name matching the specified regular expression.
+     *
+     * @param classFileName The class file name.
+     * @param matchPattern  The regular expression pattern to match.
+     *
+     * @return <tt>true</tt> if the class name matches, <tt>false</tt> otherwise.
+     */
+    private static boolean matchesName(String classFileName, Pattern matchPattern)
     {
-        return classFileName.endsWith(".class") && (classFileName.indexOf('$') < 0) && (classFileName.indexOf("Test") > 0);
+        String className = classNameFromFile(classFileName);
+        Matcher matcher = matchPattern.matcher(className);
+
+        return matcher.matches();
     }
 
-    private static boolean matchesInterface()
+    /**
+     * Checks if the specified class to compare extends the base class being scanned for.
+     *
+     * @param matchingClass The base class to match against.
+     * @param toMatch       The class to match against the base class.
+     *
+     * @return The class to check, cast as an instance of the class to match if the class extends the base class, or
+     *         <tt>null</tt> otherwise.
+     */
+    private static <T> Class<? extends T> matchesClass(Class<?> matchingClass, Class<? extends T> toMatch)
     {
-        return false;
+        try
+        {
+            return matchingClass.asSubclass(toMatch);
+        }
+        catch (ClassCastException e)
+        {
+            return null;
+        }
     }
 
     /**
@@ -125,17 +197,22 @@
     }
 
     /**
-     * convert /a/b.class to a.b
+     * Translates from the filename of a class to its fully qualified classname. Files are named using forward slash
+     * seperators and end in ".class", whereas fully qualified class names use "." sperators and no ".class" ending.
      *
-     * @param classFileName
+     * @param classFileName The filename of the class to translate to a class name.
      *
-     * @return
+     * @return The fully qualified class name.
      */
     private static String classNameFromFile(String classFileName)
     {
+        // Remove the .class ending.
+        String s = classFileName.substring(0, classFileName.length() - ".class".length());
 
-        String s = classFileName.substring(0, classFileName.length() - SUFFIX_LENGTH);
+        // Turn / seperators in . seperators.
         String s2 = s.replace(File.separatorChar, '.');
+
+        // Knock off any leading . caused by a leading /.
         if (s2.startsWith("."))
         {
             return s2.substring(1);

Modified: incubator/qpid/trunk/qpid/java/integrationtests/src/main/java/org/apache/qpid/util/ConversationHelper.java
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/java/integrationtests/src/main/java/org/apache/qpid/util/ConversationHelper.java?view=diff&rev=518559&r1=518558&r2=518559
==============================================================================
--- incubator/qpid/trunk/qpid/java/integrationtests/src/main/java/org/apache/qpid/util/ConversationHelper.java (original)
+++ incubator/qpid/trunk/qpid/java/integrationtests/src/main/java/org/apache/qpid/util/ConversationHelper.java Thu Mar 15 03:12:57 2007
@@ -20,13 +20,13 @@
  */
 package org.apache.qpid.util;
 
-import java.util.Collection;
+import java.util.*;
 import java.util.Queue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.atomic.AtomicLong;
 
-import javax.jms.Connection;
-import javax.jms.Destination;
-import javax.jms.Message;
-import javax.jms.MessageListener;
+import javax.jms.*;
 
 /**
  * A conversation helper, uses a message correlation id pattern to match up sent and received messages as a conversation
@@ -89,7 +89,8 @@
  *       transactional mode, commits must happen before receiving, or no replies will come in. (unless there were some
  *       pending on the queue?). Also, having received on a particular session, must ensure that session is used for all
  *       subsequent sends and receive at least until the transaction is committed. So a message selector must be used
- *       to restrict receives on that session to prevent it picking up messages bound for other conversations.
+ *       to restrict receives on that session to prevent it picking up messages bound for other conversations. Or use
+ *       a temporary response queue, with only that session listening to it.
  *
  * @todo Want something convenient that hides many details. Write out some example use cases to get the best feel for
  *       it. Pass in connection, send destination, receive destination. Provide endConvo, send, receive
@@ -101,6 +102,32 @@
  */
 public class ConversationHelper
 {
+    /** Holds a map from correlation id's to queues. */
+    private Map<Long, BlockingQueue<Message>> idsToQueues = new HashMap<Long, BlockingQueue<Message>>();
+
+    private Session session;
+    private MessageProducer producer;
+    private MessageConsumer consumer;
+
+    Class<? extends BlockingQueue<Message>> queueClass;
+
+    BlockingQueue<Message> deadLetterBox = new LinkedBlockingQueue<Message>();
+
+    ThreadLocal<PerThreadSettings> threadLocals =
+        new ThreadLocal<PerThreadSettings>()
+        {
+            protected PerThreadSettings initialValue()
+            {
+                PerThreadSettings settings = new PerThreadSettings();
+                settings.conversationId = conversationIdGenerator.getAndIncrement();
+
+                return settings;
+            }
+        };
+
+    /** Generates new coversation id's as needed. */
+    AtomicLong conversationIdGenerator = new AtomicLong();
+
     /**
      * Creates a conversation helper on the specified connection with the default sending destination, and listening
      * to the specified receiving destination.
@@ -109,19 +136,53 @@
      * @param sendDestination    The default sending destiation for all messages.
      * @param receiveDestination The destination to listen to for incoming messages.
      * @param queueClass         The queue implementation class.
+     *
+     * @throws JMSException All undelying JMSExceptions are allowed to fall through.
      */
     public ConversationHelper(Connection connection, Destination sendDestination, Destination receiveDestination,
-                              Class<? extends Queue> queueClass)
-    { }
+                              Class<? extends BlockingQueue<Message>> queueClass) throws JMSException
+    {
+        session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+        producer = session.createProducer(sendDestination);
+        consumer = session.createConsumer(receiveDestination);
+
+        consumer.setMessageListener(new Receiver());
+
+        this.queueClass = queueClass;
+    }
 
     /**
      * Sends a message to the default sending location. The correlation id of the message will be assigned by this
      * method, overriding any previously set value.
      *
      * @param message The message to send.
+     *
+     * @throws JMSException All undelying JMSExceptions are allowed to fall through.
+     */
+    public void send(Message message) throws JMSException
+    {
+        PerThreadSettings settings = threadLocals.get();
+        long conversationId = settings.conversationId;
+        message.setJMSCorrelationID(Long.toString(conversationId));
+
+        // Ensure that the reply queue for this conversation exists.
+        initQueueForId(conversationId);
+
+        producer.send(message);
+    }
+
+    /**
+     * Ensures that the reply queue for a conversation exists.
+     *
+     * @param conversationId The conversation correlation id.
      */
-    public void send(Message message)
-    { }
+    private void initQueueForId(long conversationId)
+    {
+        if (!idsToQueues.containsKey(conversationId))
+        {
+            idsToQueues.put(conversationId, ReflectionUtils.<BlockingQueue<Message>>newInstance(queueClass));
+        }
+    }
 
     /**
      * Gets the next message in an ongoing conversation. This method may block until such a message is received.
@@ -130,7 +191,22 @@
      */
     public Message receive()
     {
-        return null;
+        PerThreadSettings settings = threadLocals.get();
+        long conversationId = settings.conversationId;
+
+        // Ensure that the reply queue for this conversation exists.
+        initQueueForId(conversationId);
+
+        BlockingQueue<Message> queue = idsToQueues.get(conversationId);
+
+        try
+        {
+            return queue.take();
+        }
+        catch (InterruptedException e)
+        {
+            return null;
+        }
     }
 
     /**
@@ -138,7 +214,18 @@
      * conversation are no longer valid, and any incoming messages using them will go to the dead letter box.
      */
     public void end()
-    { }
+    {
+        // Ensure that the thread local for the current thread is cleaned up.
+        PerThreadSettings settings = threadLocals.get();
+        long conversationId = settings.conversationId;
+        threadLocals.remove();
+
+        // Ensure that its queue is removed from the queue map.
+        BlockingQueue<Message> queue = idsToQueues.remove(conversationId);
+
+        // Move any outstanding messages on the threads conversation id into the dead letter box.
+        queue.drainTo(deadLetterBox);
+    }
 
     /**
      * Clears the dead letter box, returning all messages that were in it.
@@ -147,7 +234,10 @@
      */
     public Collection<Message> emptyDeadLetterBox()
     {
-        return null;
+        Collection<Message> result = new LinkedList<Message>();
+        deadLetterBox.drainTo(result);
+
+        return result;
     }
 
     /**
@@ -162,6 +252,32 @@
          * @param message The incoming message.
          */
         public void onMessage(Message message)
-        { }
+        {
+            try
+            {
+                Long conversationId = Long.parseLong(message.getJMSCorrelationID());
+
+                // Find the converstaion queue to place the message on. If there is no conversation for the message id,
+                // the the dead letter box queue is used.
+                BlockingQueue<Message> queue = idsToQueues.get(conversationId);
+                queue = (queue == null) ? deadLetterBox : queue;
+
+                queue.put(message);
+            }
+            catch (JMSException e)
+            {
+                throw new RuntimeException(e);
+            }
+            catch (InterruptedException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    protected class PerThreadSettings
+    {
+        /** Holds the correlation id for the current threads conversation. */
+        long conversationId;
     }
 }