QPID-8074: [JMS AMQP 0-x][System Tests] Build framework to run JMS client system tests
diff --git a/pom.xml b/pom.xml
index 4a33a8a..0bc3c55 100644
--- a/pom.xml
+++ b/pom.xml
@@ -100,6 +100,8 @@
     <mockito-version>1.9.5</mockito-version>
     <hamcrest-version>1.3</hamcrest-version>
 
+    <maven-core-version>3.5.0</maven-core-version>
+    <maven-resolver-version>1.0.3</maven-resolver-version>
     <exec-maven-plugin-version>1.3.2</exec-maven-plugin-version>
     <javacc-maven-plugin-version>2.6</javacc-maven-plugin-version>
     <maven-rar-plugin-version>2.3</maven-rar-plugin-version>
@@ -118,6 +120,7 @@
     <module>client</module>
     <module>client/example</module>
     <module>doc</module>
+    <module>systests</module>
   </modules>
 
   <dependencyManagement>
@@ -162,7 +165,6 @@
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
         <version>${junit-version}</version>
-        <scope>test</scope>
       </dependency>
 
       <dependency>
@@ -171,6 +173,52 @@
         <version>${mockito-version}</version>
         <scope>test</scope>
       </dependency>
+
+      <dependency>
+        <groupId>com.fasterxml.jackson.core</groupId>
+        <artifactId>jackson-core</artifactId>
+        <version>${fasterxml-jackson-version}</version>
+      </dependency>
+      <dependency>
+        <groupId>com.fasterxml.jackson.core</groupId>
+        <artifactId>jackson-databind</artifactId>
+        <version>${fasterxml-jackson-version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.hamcrest</groupId>
+        <artifactId>hamcrest-library</artifactId>
+        <version>${hamcrest-version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.hamcrest</groupId>
+        <artifactId>hamcrest-integration</artifactId>
+        <version>${hamcrest-version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.maven</groupId>
+        <artifactId>maven-core</artifactId>
+        <version>${maven-core-version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.maven.resolver</groupId>
+        <artifactId>maven-resolver-api</artifactId>
+        <version>${maven-resolver-version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.maven.resolver</groupId>
+        <artifactId>maven-resolver-connector-basic</artifactId>
+        <version>${maven-resolver-version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.maven.resolver</groupId>
+        <artifactId>maven-resolver-transport-file</artifactId>
+        <version>${maven-resolver-version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.maven.resolver</groupId>
+        <artifactId>maven-resolver-transport-http</artifactId>
+        <version>${maven-resolver-version}</version>
+      </dependency>
     </dependencies>
   </dependencyManagement>
 
diff --git a/systests/pom.xml b/systests/pom.xml
new file mode 100644
index 0000000..70bca14
--- /dev/null
+++ b/systests/pom.xml
@@ -0,0 +1,188 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.qpid</groupId>
+        <artifactId>qpid-jms-amqp-0-x-parent</artifactId>
+        <version>6.4.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>qpid-client-systests</artifactId>
+    <name>Apache Qpid JMS AMQP 0-x System Tests</name>
+    <description>Apache Qpid JMS AMQP 0-x System Tests</description>
+
+    <properties>
+        <qpid.amqp.version>0-10</qpid.amqp.version>
+    </properties>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>org.apache.qpid</groupId>
+            <artifactId>qpid-client</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.geronimo.specs</groupId>
+            <artifactId>geronimo-jms_1.1_spec</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.maven</groupId>
+            <artifactId>maven-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.maven.resolver</groupId>
+            <artifactId>maven-resolver-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.maven.resolver</groupId>
+            <artifactId>maven-resolver-connector-basic</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.maven.resolver</groupId>
+            <artifactId>maven-resolver-transport-file</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.maven.resolver</groupId>
+            <artifactId>maven-resolver-transport-http</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-library</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-integration</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <systemPropertyVariables>
+                       <qpid.amqp.version>${qpid.amqp.version}</qpid.amqp.version>
+                    </systemPropertyVariables>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <profiles>
+        <profile>
+            <id>broker-j</id>
+
+            <properties>
+                <qpid-broker-j-version>7.0.0</qpid-broker-j-version>
+                <qpid.systest.broker_admin>org.apache.qpid.systest.core.brokerj.SpawnQpidBrokerAdmin</qpid.systest.broker_admin>
+                <qpid.systest.java8.executable>/usr/bin/java</qpid.systest.java8.executable>
+                <qpid.systest.virtualhost.blueprint>{"type":"BDB","globalAddressDomains":"[]"}</qpid.systest.virtualhost.blueprint>
+                <qpid.systest.brokerj.dependencies>org.apache.qpid:qpid-broker:${qpid-broker-j-version},org.apache.qpid:qpid-broker-core:${qpid-broker-j-version},org.apache.qpid:qpid-bdbstore:${qpid-broker-j-version},org.apache.qpid:qpid-broker-plugins-amqp-0-8-protocol:${qpid-broker-j-version},org.apache.qpid:qpid-broker-plugins-amqp-0-10-protocol:${qpid-broker-j-version},org.apache.qpid:qpid-broker-plugins-amqp-msg-conv-0-8-to-0-10:${qpid-broker-j-version},org.apache.qpid:qpid-broker-plugins-management-amqp:${qpid-broker-j-version},org.apache.qpid:qpid-broker-plugins-access-control:${qpid-broker-j-version},org.apache.qpid:qpid-broker-plugins-derby-store:${qpid-broker-j-version},org.apache.qpid:qpid-broker-plugins-jdbc-provider-bone:${qpid-broker-j-version},org.apache.qpid:qpid-broker-plugins-jdbc-store:${qpid-broker-j-version},org.apache.qpid:qpid-broker-plugins-logging-logback:${qpid-broker-j-version},org.apache.qpid:qpid-broker-plugins-management-amqp:${qpid-broker-j-version},org.apache.qpid:qpid-broker-plugins-memory-store:${qpid-broker-j-version},org.apache.qpid:qpid-bdbstore:${qpid-broker-j-version}</qpid.systest.brokerj.dependencies>
+            </properties>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <configuration>
+                            <systemPropertyVariables>
+                                <qpid.systest.brokerj.dependencies>${qpid.systest.brokerj.dependencies}</qpid.systest.brokerj.dependencies>
+                                <qpid.systest.java8.executable>${qpid.systest.java8.executable}</qpid.systest.java8.executable>
+                                <qpid.systest.build.classpath.file>${project.build.directory}/qpid.build.classpath.txt</qpid.systest.build.classpath.file>
+                                <qpid.systest.initialConfigurationLocation>classpath:broker-j-config-with-logging.json</qpid.systest.initialConfigurationLocation>
+                                <qpid.systest.broker_admin>${qpid.systest.broker_admin}</qpid.systest.broker_admin>
+                                <qpid.systest.virtualhostnode.type>JSON</qpid.systest.virtualhostnode.type>
+                                <qpid.systest.virtualhost.blueprint>${qpid.systest.virtualhost.blueprint}</qpid.systest.virtualhost.blueprint>
+                                <qpid.systest.logback.logs_dir>${project.basedir}${file.separator}target${file.separator}surefire-reports</qpid.systest.logback.logs_dir>
+                                <qpid.systest.broker.clean.between.tests>true</qpid.systest.broker.clean.between.tests>
+                            </systemPropertyVariables>
+                        </configuration>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-enforcer-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>enforce-java8-check</id>
+                                <goals>
+                                    <goal>enforce</goal>
+                                </goals>
+                                <configuration>
+                                    <rules>
+                                        <requireFilesExist>
+                                            <files>
+                                                <file>${qpid.systest.java8.executable}</file>
+                                            </files>
+                                        </requireFilesExist>
+                                    </rules>
+                                    <fail>true</fail>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+
+</project>
diff --git a/systests/src/main/java/org/apache/qpid/systest/core/BrokerAdmin.java b/systests/src/main/java/org/apache/qpid/systest/core/BrokerAdmin.java
new file mode 100644
index 0000000..b2b41cc
--- /dev/null
+++ b/systests/src/main/java/org/apache/qpid/systest/core/BrokerAdmin.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.systest.core;
+
+import java.lang.reflect.Method;
+import java.net.InetSocketAddress;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+@SuppressWarnings("unused")
+public interface BrokerAdmin
+{
+    void create(final Class testClass);
+    void start(final Class testClass, final Method method);
+    void stop(final Class testClass, final Method method);
+    void destroy(final Class testClass);
+    ListenableFuture<Void> restart();
+
+    InetSocketAddress getBrokerAddress(PortType portType);
+    boolean supportsPersistence();
+
+    String getValidUsername();
+    String getValidPassword();
+
+    String getType();
+    BrokerType getBrokerType();
+
+    Connection getConnection() throws JMSException;
+
+    enum PortType
+    {
+        ANONYMOUS_AMQP,
+        AMQP
+    }
+
+    enum BrokerType
+    {
+        BROKERJ,
+        CPP
+    }
+}
diff --git a/systests/src/main/java/org/apache/qpid/systest/core/BrokerAdminException.java b/systests/src/main/java/org/apache/qpid/systest/core/BrokerAdminException.java
new file mode 100644
index 0000000..5b6a3f5
--- /dev/null
+++ b/systests/src/main/java/org/apache/qpid/systest/core/BrokerAdminException.java
@@ -0,0 +1,34 @@
+/*
+ *
+ * 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.systest.core;
+
+public class BrokerAdminException extends RuntimeException
+{
+    public BrokerAdminException(final String message)
+    {
+        super(message);
+    }
+
+    public BrokerAdminException(final String message, final Throwable cause)
+    {
+        super(message, cause);
+    }
+}
diff --git a/systests/src/main/java/org/apache/qpid/systest/core/BrokerAdminFactory.java b/systests/src/main/java/org/apache/qpid/systest/core/BrokerAdminFactory.java
new file mode 100644
index 0000000..5cfdc53
--- /dev/null
+++ b/systests/src/main/java/org/apache/qpid/systest/core/BrokerAdminFactory.java
@@ -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.systest.core;
+
+class BrokerAdminFactory
+{
+    BrokerAdmin createInstance()
+    {
+        String type = System.getProperty("qpid.systest.broker_admin");
+        if (type != null)
+        {
+            try
+            {
+                @SuppressWarnings("unchecked")
+                Class<BrokerAdmin> c = (Class<BrokerAdmin>) Class.forName(type);
+                return c.newInstance();
+            }
+            catch (InstantiationException | IllegalAccessException | ClassNotFoundException e)
+            {
+                throw new BrokerAdminException(String.format("Could not find BrokerAdmin implementation of type '%s'",
+                                                         type));
+            }
+        }
+        return null;
+    }
+}
diff --git a/systests/src/main/java/org/apache/qpid/systest/core/BrokerAdminUsingTestBase.java b/systests/src/main/java/org/apache/qpid/systest/core/BrokerAdminUsingTestBase.java
new file mode 100644
index 0000000..72858ca
--- /dev/null
+++ b/systests/src/main/java/org/apache/qpid/systest/core/BrokerAdminUsingTestBase.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.systest.core;
+
+import org.junit.runner.RunWith;
+
+@RunWith(QpidTestRunner.class)
+public abstract class BrokerAdminUsingTestBase
+{
+    private BrokerAdmin _brokerAdmin;
+
+    public void init(final BrokerAdmin brokerAdmin)
+    {
+        _brokerAdmin = brokerAdmin;
+    }
+
+    public BrokerAdmin getBrokerAdmin()
+    {
+        return _brokerAdmin;
+    }
+}
diff --git a/systests/src/main/java/org/apache/qpid/systest/core/JmsTestBase.java b/systests/src/main/java/org/apache/qpid/systest/core/JmsTestBase.java
new file mode 100644
index 0000000..49cc3b2
--- /dev/null
+++ b/systests/src/main/java/org/apache/qpid/systest/core/JmsTestBase.java
@@ -0,0 +1,88 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.systest.core;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.junit.Assume.assumeThat;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.QueueConnection;
+import javax.jms.TopicConnection;
+import javax.naming.NamingException;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TestName;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public abstract class JmsTestBase extends BrokerAdminUsingTestBase
+{
+    private static final Logger LOGGER = LoggerFactory.getLogger(JmsTestBase.class);
+
+    @Rule
+    public final TestName _testName = new TestName();
+
+    @Before
+    public void setUpTestBase()
+    {
+        assumeThat(String.format("BrokerAdmin is not available. Skipping the test %s#%s",
+                                 getClass().getName(),
+                                 _testName.getMethodName()),
+                   getBrokerAdmin(), is(notNullValue()));
+        LOGGER.debug("Test receive timeout is {} milliseconds", getReceiveTimeout());
+    }
+
+
+    protected Connection getConnection() throws JMSException, NamingException
+    {
+        assumeThat(String.format("BrokerAdmin is not available. Skipping the test %s#%s",
+                                 getClass().getName(),
+                                 _testName.getMethodName()),
+                   getBrokerAdmin(), is(notNullValue()));
+
+        return getBrokerAdmin().getConnection();
+    }
+
+    protected static long getReceiveTimeout()
+    {
+        return Long.getLong("qpid.test_receive_timeout", 1000L);
+    }
+
+    protected String getTestName()
+    {
+        return _testName.getMethodName();
+    }
+
+
+    protected TopicConnection getTopicConnection() throws JMSException, NamingException
+    {
+        return (TopicConnection) getConnection();
+    }
+
+    protected QueueConnection getQueueConnection() throws JMSException, NamingException
+    {
+        return (QueueConnection) getConnection();
+    }
+}
diff --git a/systests/src/main/java/org/apache/qpid/systest/core/QpidTestRunner.java b/systests/src/main/java/org/apache/qpid/systest/core/QpidTestRunner.java
new file mode 100644
index 0000000..37bd96c
--- /dev/null
+++ b/systests/src/main/java/org/apache/qpid/systest/core/QpidTestRunner.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.systest.core;
+
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+
+public class QpidTestRunner extends BlockJUnit4ClassRunner
+{
+    private final BrokerAdmin _brokerAdmin;
+    private final Class _testClass;
+
+    public QpidTestRunner(final Class<?> klass) throws InitializationError
+    {
+        super(klass);
+        _testClass = klass;
+        _brokerAdmin = new BrokerAdminFactory().createInstance();
+    }
+
+    @Override
+    protected Object createTest() throws Exception
+    {
+        Object test = super.createTest();
+        BrokerAdminUsingTestBase qpidTest = ((BrokerAdminUsingTestBase) test);
+        qpidTest.init(_brokerAdmin);
+        return test;
+    }
+
+    @Override
+    public void run(final RunNotifier notifier)
+    {
+        if (_brokerAdmin != null)
+        {
+            _brokerAdmin.create(_testClass);
+        }
+        try
+        {
+            super.run(notifier);
+        }
+        finally
+        {
+            if (_brokerAdmin != null)
+            {
+                _brokerAdmin.destroy(_testClass);
+            }
+        }
+    }
+
+    @Override
+    protected void runChild(final FrameworkMethod method, final RunNotifier notifier)
+    {
+        if (_brokerAdmin != null)
+        {
+            _brokerAdmin.start(_testClass, method.getMethod());
+        }
+        try
+        {
+            super.runChild(method, notifier);
+        }
+        finally
+        {
+            if (_brokerAdmin != null)
+            {
+                _brokerAdmin.stop(_testClass, method.getMethod());
+            }
+        }
+    }
+}
diff --git a/systests/src/main/java/org/apache/qpid/systest/core/brokerj/AmqpManagementFacade.java b/systests/src/main/java/org/apache/qpid/systest/core/brokerj/AmqpManagementFacade.java
new file mode 100644
index 0000000..ed7f116
--- /dev/null
+++ b/systests/src/main/java/org/apache/qpid/systest/core/brokerj/AmqpManagementFacade.java
@@ -0,0 +1,554 @@
+/*
+ * 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.systest.core.brokerj;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.jms.BytesMessage;
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.MapMessage;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.ObjectMessage;
+import javax.jms.Queue;
+import javax.jms.Session;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+class AmqpManagementFacade
+{
+    private static final String AMQP_0_X_REPLY_TO_DESTINATION = "ADDR:!response";
+    private static final String AMQP_0_X_CONSUMER_REPLY_DESTINATION =
+            "ADDR:$management ; {assert : never, node: { type: queue }, link:{name: \"!response\"}}";
+    private final String _managementAddress;
+
+
+    AmqpManagementFacade()
+    {
+        _managementAddress = "ADDR:$management";
+    }
+
+    @SuppressWarnings("unused")
+    Map<String, Object> createEntityUsingAmqpManagement(final String name,
+                                                        final String type,
+                                                        final Session session)
+            throws JMSException
+    {
+        return createEntityUsingAmqpManagement(name, type, Collections.<String, Object>emptyMap(), session);
+    }
+
+    Map<String, Object> createEntityUsingAmqpManagement(final String name,
+                                                        final String type,
+                                                        Map<String, Object> attributes,
+                                                        final Session session)
+            throws JMSException
+    {
+        Destination replyToDestination = session.createQueue(AMQP_0_X_REPLY_TO_DESTINATION);
+        Destination replyConsumerDestination = session.createQueue(AMQP_0_X_CONSUMER_REPLY_DESTINATION);
+
+        MessageConsumer consumer = session.createConsumer(replyConsumerDestination);
+        MessageProducer producer = session.createProducer(session.createQueue(_managementAddress));
+
+        MapMessage createMessage = session.createMapMessage();
+        createMessage.setStringProperty("type", type);
+        createMessage.setStringProperty("operation", "CREATE");
+        createMessage.setString("name", name);
+        createMessage.setString("object-path", name);
+        createMessage.setJMSReplyTo(replyToDestination);
+        for (Map.Entry<String, Object> entry : attributes.entrySet())
+        {
+            createMessage.setObject(entry.getKey(), entry.getValue());
+        }
+        producer.send(createMessage);
+        if (session.getTransacted())
+        {
+            session.commit();
+        }
+        producer.close();
+
+        Message response = consumer.receive(getManagementResponseTimeout());
+        try
+        {
+            if (response != null)
+            {
+                int statusCode = response.getIntProperty("statusCode");
+                if (statusCode == 201)
+                {
+                    if (response instanceof MapMessage)
+                    {
+                        MapMessage bodyMap = (MapMessage) response;
+                        Map<String, Object> result = new HashMap<>();
+                        Enumeration keys = bodyMap.getMapNames();
+                        while (keys.hasMoreElements())
+                        {
+                            final String key = String.valueOf(keys.nextElement());
+                            Object value = bodyMap.getObject(key);
+                            result.put(key, value);
+                        }
+                        return result;
+                    }
+                    else if (response instanceof ObjectMessage)
+                    {
+                        Object body = ((ObjectMessage) response).getObject();
+                        if (body instanceof Map)
+                        {
+                            @SuppressWarnings("unchecked")
+                            Map<String, Object> bodyMap = (Map<String, Object>) body;
+                            return new HashMap<>(bodyMap);
+                        }
+                    }
+                }
+                else
+                {
+                    throw new OperationUnsuccessfulException(response.getStringProperty("statusDescription"),
+                                                             statusCode);
+                }
+            }
+
+            throw new OperationUnsuccessfulException("Cannot get the results from a management create operation", -1);
+        }
+        finally
+        {
+            consumer.close();
+        }
+    }
+
+    void updateEntityUsingAmqpManagement(final String name,
+                                         final String type,
+                                         final Map<String, Object> attributes,
+                                         final Session session)
+            throws JMSException
+    {
+        Destination replyToDestination = session.createQueue(AMQP_0_X_REPLY_TO_DESTINATION);
+        Destination replyConsumerDestination = session.createQueue(AMQP_0_X_CONSUMER_REPLY_DESTINATION);
+
+        MessageConsumer consumer = session.createConsumer(replyConsumerDestination);
+
+        MessageProducer producer = session.createProducer(session.createQueue(_managementAddress));
+
+        MapMessage createMessage = session.createMapMessage();
+        createMessage.setStringProperty("type", type);
+        createMessage.setStringProperty("operation", "UPDATE");
+        createMessage.setStringProperty("index", "object-path");
+        createMessage.setStringProperty("key", name);
+        createMessage.setJMSReplyTo(replyToDestination);
+        for (Map.Entry<String, Object> entry : attributes.entrySet())
+        {
+            createMessage.setObject(entry.getKey(), entry.getValue());
+        }
+        producer.send(createMessage);
+        if (session.getTransacted())
+        {
+            session.commit();
+        }
+        producer.close();
+
+        Message response = consumer.receive(getManagementResponseTimeout());
+        try
+        {
+            if (response != null)
+            {
+                int statusCode = response.getIntProperty("statusCode");
+                if (statusCode != 200)
+                {
+
+                    throw new OperationUnsuccessfulException(response.getStringProperty("statusDescription"),
+                                                             statusCode);
+                }
+            }
+            else
+            {
+                throw new OperationUnsuccessfulException("Cannot get the results from a management update operation",
+                                                         -1);
+            }
+        }
+        finally
+        {
+            consumer.close();
+        }
+    }
+
+    void deleteEntityUsingAmqpManagement(final String name,
+                                         final String type,
+                                         final Session session)
+            throws JMSException
+    {
+        Destination replyToDestination = session.createQueue(AMQP_0_X_REPLY_TO_DESTINATION);
+        Destination replyConsumerDestination = session.createQueue(AMQP_0_X_CONSUMER_REPLY_DESTINATION);
+
+        MessageConsumer consumer = session.createConsumer(replyConsumerDestination);
+
+        MessageProducer producer = session.createProducer(session.createQueue(_managementAddress));
+
+        MapMessage createMessage = session.createMapMessage();
+        createMessage.setStringProperty("type", type);
+        createMessage.setStringProperty("operation", "DELETE");
+        createMessage.setStringProperty("index", "object-path");
+        createMessage.setJMSReplyTo(replyToDestination);
+
+        createMessage.setStringProperty("key", name);
+        producer.send(createMessage);
+        if (session.getTransacted())
+        {
+            session.commit();
+        }
+
+        Message response = consumer.receive(getManagementResponseTimeout());
+        try
+        {
+            if (response != null)
+            {
+                int statusCode = response.getIntProperty("statusCode");
+                if (statusCode != 200 && statusCode != 204)
+                {
+
+                    throw new OperationUnsuccessfulException(response.getStringProperty("statusDescription"),
+                                                             statusCode);
+                }
+            }
+            else
+            {
+                throw new OperationUnsuccessfulException("Cannot get the results from a management delete operation",
+                                                         -1);
+            }
+        }
+        finally
+        {
+            consumer.close();
+        }
+    }
+
+    @SuppressWarnings("unused")
+    Object performOperationUsingAmqpManagement(final String name,
+                                               final String type,
+                                               final String operation,
+                                               final Map<String, Object> arguments,
+                                               final Session session)
+            throws JMSException
+    {
+        Destination replyToDestination = session.createQueue(AMQP_0_X_REPLY_TO_DESTINATION);
+        Destination replyConsumerDestination = session.createQueue(AMQP_0_X_CONSUMER_REPLY_DESTINATION);
+
+        MessageConsumer consumer = session.createConsumer(replyConsumerDestination);
+
+        MessageProducer producer = session.createProducer(session.createQueue(_managementAddress));
+
+        MapMessage opMessage = session.createMapMessage();
+        opMessage.setStringProperty("type", type);
+        opMessage.setStringProperty("operation", operation);
+        opMessage.setStringProperty("index", "object-path");
+        opMessage.setJMSReplyTo(replyToDestination);
+
+        opMessage.setStringProperty("key", name);
+        for (Map.Entry<String, Object> argument : arguments.entrySet())
+        {
+            Object value = argument.getValue();
+            if (value.getClass().isPrimitive() || value instanceof String)
+            {
+                opMessage.setObjectProperty(argument.getKey(), value);
+            }
+            else
+            {
+                ObjectMapper objectMapper = new ObjectMapper();
+                String jsonifiedValue;
+                try
+                {
+                    jsonifiedValue = objectMapper.writeValueAsString(value);
+                }
+                catch (JsonProcessingException e)
+                {
+                    throw new IllegalArgumentException(String.format(
+                            "Cannot convert the argument '%s' to JSON to meet JMS type restrictions",
+                            argument.getKey()));
+                }
+                opMessage.setObjectProperty(argument.getKey(), jsonifiedValue);
+            }
+        }
+
+        producer.send(opMessage);
+        if (session.getTransacted())
+        {
+            session.commit();
+        }
+
+        Message response = consumer.receive(getManagementResponseTimeout());
+        try
+        {
+            int statusCode = response.getIntProperty("statusCode");
+            if (statusCode < 200 || statusCode > 299)
+            {
+                throw new OperationUnsuccessfulException(response.getStringProperty("statusDescription"), statusCode);
+            }
+            if (response instanceof MapMessage)
+            {
+                MapMessage bodyMap = (MapMessage) response;
+                Map<String, Object> result = new TreeMap<>();
+                Enumeration mapNames = bodyMap.getMapNames();
+                while (mapNames.hasMoreElements())
+                {
+                    String key = (String) mapNames.nextElement();
+                    result.put(key, bodyMap.getObject(key));
+                }
+                return result;
+            }
+            else if (response instanceof ObjectMessage)
+            {
+                return ((ObjectMessage) response).getObject();
+            }
+            else if (response instanceof BytesMessage)
+            {
+                BytesMessage bytesMessage = (BytesMessage) response;
+                if (bytesMessage.getBodyLength() == 0)
+                {
+                    return null;
+                }
+                else
+                {
+                    byte[] buf = new byte[(int) bytesMessage.getBodyLength()];
+                    bytesMessage.readBytes(buf);
+                    return buf;
+                }
+            }
+            throw new IllegalArgumentException(
+                    "Cannot parse the results from a management operation.  JMS response message : " + response);
+        }
+        finally
+        {
+            if (session.getTransacted())
+            {
+                session.commit();
+            }
+            consumer.close();
+        }
+    }
+
+    @SuppressWarnings(value = {"unused", "unchecked"})
+    List<Map<String, Object>> managementQueryObjects(final String type, final Session session)
+            throws JMSException
+    {
+        Destination replyToDestination = session.createQueue(AMQP_0_X_REPLY_TO_DESTINATION);
+        Destination replyConsumerDestination = session.createQueue(AMQP_0_X_CONSUMER_REPLY_DESTINATION);
+
+        MessageConsumer consumer = session.createConsumer(replyConsumerDestination);
+        MapMessage message = session.createMapMessage();
+        message.setStringProperty("identity", "self");
+        message.setStringProperty("type", "org.amqp.management");
+        message.setStringProperty("operation", "QUERY");
+        message.setStringProperty("entityType", type);
+        message.setString("attributeNames", "[]");
+        message.setJMSReplyTo(replyToDestination);
+
+        MessageProducer producer = session.createProducer(session.createQueue(_managementAddress));
+        producer.send(message);
+
+        Message response = consumer.receive(getManagementResponseTimeout());
+        try
+        {
+            if (response instanceof MapMessage)
+            {
+                MapMessage bodyMap = (MapMessage) response;
+                List<String> attributeNames = (List<String>) bodyMap.getObject("attributeNames");
+                List<List<Object>> attributeValues = (List<List<Object>>) bodyMap.getObject("results");
+                return getResultsAsMaps(attributeNames, attributeValues);
+            }
+            else if (response instanceof ObjectMessage)
+            {
+                Object body = ((ObjectMessage) response).getObject();
+                if (body instanceof Map)
+                {
+                    Map<String, ?> bodyMap = (Map<String, ?>) body;
+                    List<String> attributeNames = (List<String>) bodyMap.get("attributeNames");
+                    List<List<Object>> attributeValues = (List<List<Object>>) bodyMap.get("results");
+                    return getResultsAsMaps(attributeNames, attributeValues);
+                }
+            }
+            throw new IllegalArgumentException("Cannot parse the results from a management query");
+        }
+        finally
+        {
+            consumer.close();
+        }
+    }
+
+    @SuppressWarnings("unused")
+    Map<String, Object> readEntityUsingAmqpManagement(final String name,
+                                                      final String type,
+                                                      final boolean actuals,
+                                                      final Session session)
+            throws JMSException
+    {
+        Destination replyToDestination = session.createQueue(AMQP_0_X_REPLY_TO_DESTINATION);
+        Destination replyConsumerDestination = session.createQueue(AMQP_0_X_CONSUMER_REPLY_DESTINATION);
+
+        MessageConsumer consumer = session.createConsumer(replyConsumerDestination);
+
+        MessageProducer producer = session.createProducer(session.createQueue(_managementAddress));
+
+        MapMessage request = session.createMapMessage();
+        request.setStringProperty("type", type);
+        request.setStringProperty("operation", "READ");
+        request.setString("name", name);
+        request.setString("object-path", name);
+        request.setStringProperty("index", "object-path");
+        request.setStringProperty("key", name);
+        request.setBooleanProperty("actuals", actuals);
+        request.setJMSReplyTo(replyToDestination);
+
+        producer.send(request);
+        if (session.getTransacted())
+        {
+            session.commit();
+        }
+
+        Message response = consumer.receive(getManagementResponseTimeout());
+        if (session.getTransacted())
+        {
+            session.commit();
+        }
+        try
+        {
+            if (response instanceof MapMessage)
+            {
+                MapMessage bodyMap = (MapMessage) response;
+                Map<String, Object> data = new HashMap<>();
+                @SuppressWarnings("unchecked")
+                Enumeration<String> keys = bodyMap.getMapNames();
+                while (keys.hasMoreElements())
+                {
+                    String key = keys.nextElement();
+                    data.put(key, bodyMap.getObject(key));
+                }
+                return data;
+            }
+            else if (response instanceof ObjectMessage)
+            {
+                Object body = ((ObjectMessage) response).getObject();
+                if (body instanceof Map)
+                {
+                    @SuppressWarnings("unchecked")
+                    Map<String, ?> bodyMap = (Map<String, ?>) body;
+                    return new HashMap<>(bodyMap);
+                }
+            }
+            throw new IllegalArgumentException("Management read failed : "
+                                               + response.getStringProperty("statusCode")
+                                               + " - "
+                                               + response.getStringProperty("statusDescription"));
+        }
+        finally
+        {
+            consumer.close();
+        }
+    }
+
+    @SuppressWarnings("unused")
+    long getQueueDepth(final Queue destination, final Session session) throws Exception
+    {
+        final String escapedName = getEscapedName(destination);
+        Map<String, Object> arguments =
+                Collections.singletonMap("statistics", (Object) Collections.singletonList("queueDepthMessages"));
+
+        Object statistics = performOperationUsingAmqpManagement(escapedName,
+                                                                "org.apache.qpid.Queue", "getStatistics",
+                                                                arguments, session
+                                                               );
+        @SuppressWarnings("unchecked")
+        Map<String, Object> statisticsMap = (Map<String, Object>) statistics;
+        return ((Number) statisticsMap.get("queueDepthMessages")).intValue();
+    }
+
+    @SuppressWarnings("unused")
+    boolean isQueueExist(final Queue destination, final Session session) throws Exception
+    {
+        final String escapedName = getEscapedName(destination);
+        try
+        {
+            performOperationUsingAmqpManagement(escapedName,
+                                                "org.apache.qpid.Queue",
+                                                "READ",
+                                                Collections.<String, Object>emptyMap(),
+                                                session);
+            return true;
+        }
+        catch (AmqpManagementFacade.OperationUnsuccessfulException e)
+        {
+            if (e.getStatusCode() == 404)
+            {
+                return false;
+            }
+            else
+            {
+                throw e;
+            }
+        }
+    }
+
+    private String getEscapedName(final Queue destination) throws JMSException
+    {
+        return destination.getQueueName().replaceAll("([/\\\\])", "\\\\$1");
+    }
+
+    private List<Map<String, Object>> getResultsAsMaps(final List<String> attributeNames,
+                                                       final List<List<Object>> attributeValues)
+    {
+        List<Map<String, Object>> results = new ArrayList<>();
+        for (List<Object> resultObject : attributeValues)
+        {
+            Map<String, Object> result = new HashMap<>();
+            for (int i = 0; i < attributeNames.size(); ++i)
+            {
+                result.put(attributeNames.get(i), resultObject.get(i));
+            }
+            results.add(result);
+        }
+        return results;
+    }
+
+    private int getManagementResponseTimeout()
+    {
+        return Integer.getInteger("qpid.systests.management_response_timeout", 5000);
+    }
+
+    static class OperationUnsuccessfulException extends RuntimeException
+    {
+        private final int _statusCode;
+
+        private OperationUnsuccessfulException(final String message, final int statusCode)
+        {
+            super(message == null ? String.format("Unexpected status code %d", statusCode) : message);
+            _statusCode = statusCode;
+        }
+
+        int getStatusCode()
+        {
+            return _statusCode;
+        }
+    }
+}
diff --git a/systests/src/main/java/org/apache/qpid/systest/core/brokerj/SpawnQpidBrokerAdmin.java b/systests/src/main/java/org/apache/qpid/systest/core/brokerj/SpawnQpidBrokerAdmin.java
new file mode 100644
index 0000000..3893288
--- /dev/null
+++ b/systests/src/main/java/org/apache/qpid/systest/core/brokerj/SpawnQpidBrokerAdmin.java
@@ -0,0 +1,898 @@
+/*
+ * 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.systest.core.brokerj;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.lang.reflect.Method;
+import java.net.InetSocketAddress;
+import java.nio.file.Files;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.jms.Connection;
+import javax.jms.ConnectionFactory;
+import javax.jms.JMSException;
+import javax.jms.Session;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+
+import ch.qos.logback.classic.LoggerContext;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.io.ByteStreams;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.qpid.systest.core.BrokerAdmin;
+import org.apache.qpid.systest.core.BrokerAdminException;
+import org.apache.qpid.systest.core.dependency.ClasspathQuery;
+import org.apache.qpid.systest.core.logback.LogbackPropertyValueDiscriminator;
+import org.apache.qpid.systest.core.util.FileUtils;
+import org.apache.qpid.systest.core.logback.LogbackSocketPortNumberDefiner;
+import org.apache.qpid.systest.core.util.SystemUtils;
+
+public class SpawnQpidBrokerAdmin implements BrokerAdmin
+{
+    private static final Logger LOGGER = LoggerFactory.getLogger(SpawnQpidBrokerAdmin.class);
+    private static final String BROKER_LOG_PREFIX = "BROKER";
+    private static final String SYSTEST_PROPERTY_PREFIX = "qpid.systest.";
+    private static final String SYSTEST_PROPERTY_BROKER_READY = "qpid.systest.broker.ready";
+    private static final String SYSTEST_PROPERTY_BROKER_STOPPED = "qpid.systest.broker.stopped";
+    private static final String SYSTEST_PROPERTY_BROKER_LISTENING = "qpid.systest.broker.listening";
+    private static final String SYSTEST_PROPERTY_BROKER_PROCESS = "qpid.systest.broker.process";
+    private static final String SYSTEST_PROPERTY_SPAWN_BROKER_STARTUP_TIME = "qpid.systest.broker_startup_time";
+    private static final String SYSTEST_PROPERTY_BROKER_CLEAN_BETWEEN_TESTS = "qpid.systest.broker.clean.between.tests";
+
+    static final String SYSTEST_PROPERTY_VIRTUALHOSTNODE_TYPE = "qpid.systest.virtualhostnode.type";
+    static final String SYSTEST_PROPERTY_VIRTUALHOST_BLUEPRINT = "qpid.systest.virtualhost.blueprint";
+    static final String SYSTEST_PROPERTY_INITIAL_CONFIGURATION_LOCATION = "qpid.systest.initialConfigurationLocation";
+    static final String SYSTEST_PROPERTY_BUILD_CLASSPATH_FILE = "qpid.systest.build.classpath.file";
+    static final String SYSTEST_PROPERTY_JAVA8_EXECUTABLE = "qpid.systest.java8.executable";
+    static final String SYSTEST_PROPERTY_BROKERJ_DEPENDECIES = "qpid.systest.brokerj.dependencies";
+
+    private final static AtomicLong BROKER_INSTANCE_COUNTER = new AtomicLong();
+
+    private volatile List<ListeningPort> _ports;
+    private volatile Process _process;
+    private volatile Integer _pid;
+    private volatile String _currentWorkDirectory;
+    private volatile boolean _isPersistentStore;
+    private volatile String _virtualHostNodeName;
+
+    @Override
+    public void create(final Class testClass)
+    {
+        setClassQualifiedTestName(testClass.getName());
+        LOGGER.info("========================= starting broker for test class : {}", testClass.getSimpleName());
+        startBroker(testClass);
+    }
+
+    @Override
+    public void start(final Class testClass, final Method method)
+    {
+        LOGGER.info("========================= prepare test environment for test : {}#{}",
+                    testClass.getSimpleName(),
+                    method.getName());
+        String virtualHostNodeName = getVirtualHostNodeName(testClass, method);
+        createVirtualHost(virtualHostNodeName);
+        _virtualHostNodeName = virtualHostNodeName;
+        LOGGER.info("========================= executing test : {}#{}", testClass.getSimpleName(), method.getName());
+        setClassQualifiedTestName(testClass.getName() + "." + method.getName());
+        LOGGER.info("========================= start executing test : {}#{}",
+                    testClass.getSimpleName(),
+                    method.getName());
+    }
+
+
+    @Override
+    public void stop(final Class testClass, final Method method)
+    {
+        LOGGER.info("========================= stop executing test : {}#{}",
+                    testClass.getSimpleName(),
+                    method.getName());
+        setClassQualifiedTestName(testClass.getName());
+        LOGGER.info("========================= cleaning up test environment for test : {}#{}",
+                    testClass.getSimpleName(),
+                    method.getName());
+        deleteVirtualHost(getVirtualHostNodeName(testClass, method));
+        _virtualHostNodeName = null;
+        LOGGER.info("========================= cleaning done for test : {}#{}",
+                    testClass.getSimpleName(),
+                    method.getName());
+    }
+
+    @Override
+    public void destroy(final Class testClass)
+    {
+        LOGGER.info("========================= stopping broker for test class: {}", testClass.getSimpleName());
+        shutdown();
+        _ports.clear();
+        if (Boolean.getBoolean(SYSTEST_PROPERTY_BROKER_CLEAN_BETWEEN_TESTS))
+        {
+            FileUtils.delete(new File(_currentWorkDirectory), true);
+        }
+        _isPersistentStore = false;
+        LOGGER.info("========================= stopping broker done for test class : {}", testClass.getSimpleName());
+        setClassQualifiedTestName(null);
+    }
+
+    @Override
+    public InetSocketAddress getBrokerAddress(final PortType portType)
+    {
+        Integer port = null;
+        switch (portType)
+        {
+            case AMQP:
+                for (ListeningPort p : _ports)
+                {
+                    if (p.getProtocol() == null
+                        && (p.getTransport().contains("TCP") /*|| p.getTransport().contains("SSL") */))
+                    {
+                        port = p.getPort();
+                        break;
+                    }
+                }
+                break;
+            default:
+                throw new IllegalArgumentException(String.format("Unknown port type '%s'", portType));
+        }
+        if (port == null)
+        {
+            throw new IllegalArgumentException(String.format("Cannot find port of type '%s'", portType));
+        }
+        return new InetSocketAddress(port);
+    }
+
+    @Override
+    public boolean supportsPersistence()
+    {
+        return _isPersistentStore;
+    }
+
+    @Override
+    public ListenableFuture<Void> restart()
+    {
+        if (_virtualHostNodeName == null)
+        {
+            throw new BrokerAdminException("Virtual host is not started");
+        }
+        return restartVirtualHost(_virtualHostNodeName);
+    }
+
+    @Override
+    public String getValidUsername()
+    {
+        return "guest";
+    }
+
+    @Override
+    public String getValidPassword()
+    {
+        return "guest";
+    }
+
+    @Override
+    public String getType()
+    {
+        return SpawnQpidBrokerAdmin.class.getSimpleName();
+    }
+
+    @Override
+    public BrokerType getBrokerType()
+    {
+        return BrokerType.BROKERJ;
+    }
+
+    @Override
+    public Connection getConnection() throws JMSException
+    {
+        return createConnection(_virtualHostNodeName);
+    }
+
+    private void startBroker(final Class testClass)
+    {
+        try
+        {
+            start(testClass);
+        }
+        catch (Exception e)
+        {
+            if (e instanceof RuntimeException)
+            {
+                throw (RuntimeException) e;
+            }
+            else
+            {
+                throw new BrokerAdminException("Unexpected exception on broker startup", e);
+            }
+        }
+    }
+
+    void start(final Class testClass) throws Exception
+    {
+        String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date(System.currentTimeMillis()));
+        _currentWorkDirectory =
+                Files.createTempDirectory(String.format("qpid-work-%s-%s-", timestamp, testClass.getSimpleName()))
+                     .toString();
+
+        String initialConfiguration = System.getProperty(SYSTEST_PROPERTY_INITIAL_CONFIGURATION_LOCATION);
+        if (initialConfiguration == null)
+        {
+            throw new BrokerAdminException(
+                    String.format("No initial configuration is found: JVM property '%s' is not set.",
+                                  SYSTEST_PROPERTY_INITIAL_CONFIGURATION_LOCATION));
+        }
+
+        File testInitialConfiguration = new File(_currentWorkDirectory, "initial-configuration.json");
+        if (!testInitialConfiguration.createNewFile())
+        {
+            throw new BrokerAdminException("Failed to create a file for a copy of initial configuration");
+        }
+        if (initialConfiguration.startsWith("classpath:"))
+        {
+            String config = initialConfiguration.substring("classpath:".length());
+            try (InputStream is = getClass().getClassLoader().getResourceAsStream(config);
+                 OutputStream os = new FileOutputStream(testInitialConfiguration))
+            {
+                ByteStreams.copy(is, os);
+            }
+        }
+        else
+        {
+            Files.copy(new File(initialConfiguration).toPath(), testInitialConfiguration.toPath());
+        }
+
+        String classpath;
+        File file = new File(System.getProperty(SYSTEST_PROPERTY_BUILD_CLASSPATH_FILE));
+        if (!file.exists())
+        {
+            String dependencies = System.getProperty(SYSTEST_PROPERTY_BROKERJ_DEPENDECIES);
+            final ClasspathQuery classpathQuery = new ClasspathQuery(SpawnQpidBrokerAdmin.class,
+                                                                     Arrays.asList(dependencies.split(",")));
+            classpath = classpathQuery.getClasspath();
+            Files.write(file.toPath(), Collections.singleton(classpath), UTF_8);
+        }
+        else
+        {
+            classpath = new String(Files.readAllBytes(file.toPath()), UTF_8);
+        }
+
+        // grab Qpid related JVM settings
+        List<String> jvmArguments = new ArrayList<>();
+        Properties jvmProperties = System.getProperties();
+        for (String jvmProperty : jvmProperties.stringPropertyNames())
+        {
+            if (jvmProperty.startsWith(SYSTEST_PROPERTY_PREFIX)
+                || jvmProperty.equalsIgnoreCase("java.io.tmpdir"))
+            {
+                jvmArguments.add("-D" + jvmProperty + "=" + jvmProperties.getProperty(jvmProperty));
+            }
+        }
+
+        jvmArguments.add(0, System.getProperty(SYSTEST_PROPERTY_JAVA8_EXECUTABLE, "/usr/bin/java"));
+        jvmArguments.add(1, "-cp");
+        jvmArguments.add(2, classpath);
+        jvmArguments.add("-Dqpid.systest.logback.socket.port="
+                         + LogbackSocketPortNumberDefiner.getLogbackSocketPortNumber());
+        jvmArguments.add("-Dqpid.systest.logback.logs_dir=" + System.getProperty("qpid.systest.logback.logs_dir",
+                                                                                 "${qpid.work_dir}"));
+        jvmArguments.add(String.format("-Dqpid.systest.logback.origin=%s-%d",
+                                       BROKER_LOG_PREFIX,
+                                       BROKER_INSTANCE_COUNTER.getAndIncrement()));
+        jvmArguments.add("-Dqpid.systest.logback.context=" + testClass.getName());
+        if (System.getProperty("qpid.systest.remote_debugger") != null)
+        {
+            jvmArguments.add(System.getProperty("qpid.systest.remote_debugger"));
+        }
+        jvmArguments.add("org.apache.qpid.server.Main");
+        jvmArguments.add("-prop");
+        jvmArguments.add(String.format("qpid.work_dir=%s", escapePath(_currentWorkDirectory)));
+        jvmArguments.add("--store-type");
+        jvmArguments.add("JSON");
+        jvmArguments.add("--initial-config-path");
+        jvmArguments.add(escapePath(testInitialConfiguration.toString()));
+
+        LOGGER.debug("Spawning broker JVM :", jvmArguments);
+
+        String ready = System.getProperty(SYSTEST_PROPERTY_BROKER_READY, "BRK-1004 : Qpid Broker Ready");
+        String stopped = System.getProperty(SYSTEST_PROPERTY_BROKER_STOPPED, "BRK-1005 : Stopped");
+        String amqpListening = System.getProperty(SYSTEST_PROPERTY_BROKER_LISTENING,
+                                                  "BRK-1002 : Starting( : \\w*)? : Listening on (\\w*) port ([0-9]+)");
+        String process = System.getProperty(SYSTEST_PROPERTY_BROKER_PROCESS, "BRK-1017 : Process : PID : ([0-9]+)");
+        int startUpTime = Integer.getInteger(SYSTEST_PROPERTY_SPAWN_BROKER_STARTUP_TIME, 30000);
+
+        LOGGER.debug("Spawning broker permitted start-up time: {}", startUpTime);
+
+        String[] cmd = jvmArguments.toArray(new String[jvmArguments.size()]);
+
+        ProcessBuilder processBuilder = new ProcessBuilder(cmd);
+        processBuilder.redirectErrorStream(true);
+
+        Map<String, String> processEnvironment = processBuilder.environment();
+        processEnvironment.put("QPID_PNAME", "-DPNAME=QPBRKR -DTNAME=\"" + testClass.getName() + "\"");
+
+        long startTime = System.currentTimeMillis();
+        _process = processBuilder.start();
+
+        BrokerSystemOutpuHandler brokerSystemOutpuHandler = new BrokerSystemOutpuHandler(_process.getInputStream(),
+                                                                                         ready,
+                                                                                         stopped,
+                                                                                         process,
+                                                                                         amqpListening,
+                                                                                         getClass().getName());
+
+        boolean brokerStarted = false;
+        ExecutorService executorService = Executors.newFixedThreadPool(1);
+        try
+        {
+            Future<?> result = executorService.submit(brokerSystemOutpuHandler);
+            result.get(startUpTime, TimeUnit.MILLISECONDS);
+
+            _pid = brokerSystemOutpuHandler.getPID();
+            _ports = brokerSystemOutpuHandler.getAmqpPorts();
+
+            if (_pid == -1)
+            {
+                throw new BrokerAdminException("Broker PID is not detected");
+            }
+
+            if (_ports.size() == 0)
+            {
+                throw new BrokerAdminException("Broker port is not detected");
+            }
+
+            try
+            {
+                //test that the broker is still running and hasn't exited unexpectedly
+                int exit = _process.exitValue();
+                LOGGER.info("broker aborted: {}", exit);
+                throw new BrokerAdminException("broker aborted: " + exit);
+            }
+            catch (IllegalThreadStateException e)
+            {
+                // this is expect if the broker started successfully
+            }
+
+            LOGGER.info("Broker was started successfully within {} milliseconds, broker PID {}",
+                        System.currentTimeMillis() - startTime,
+                        _pid);
+            LOGGER.info("Broker ports: {}", _ports);
+            brokerStarted = true;
+        }
+        catch (RuntimeException e)
+        {
+            throw e;
+        }
+        catch (TimeoutException e)
+        {
+            LOGGER.warn("Spawned broker failed to become ready within {} ms. Ready line '{}'",
+                        startUpTime, brokerSystemOutpuHandler.getReady());
+            String threadDump = dumpThreads();
+            if (!threadDump.isEmpty())
+            {
+                LOGGER.warn("the result of a try to capture thread dump:" + threadDump);
+            }
+            throw new BrokerAdminException(String.format("Broker failed to become ready within %d ms. Stop line : %s",
+                                                         startUpTime,
+                                                         brokerSystemOutpuHandler.getStopLine()));
+        }
+        catch (ExecutionException e)
+        {
+            throw new BrokerAdminException(String.format("Broker startup failed due to %s", e.getCause()),
+                                           e.getCause());
+        }
+        catch (Exception e)
+        {
+            throw new BrokerAdminException(String.format("Unexpected exception on broker startup: %s", e), e);
+        }
+        finally
+        {
+            if (!brokerStarted)
+            {
+                LOGGER.warn("Broker failed to start");
+                _process.destroy();
+            }
+            executorService.shutdown();
+        }
+    }
+
+    void createVirtualHost(final String virtualHostNodeName)
+    {
+        final String nodeType = System.getProperty(SYSTEST_PROPERTY_VIRTUALHOSTNODE_TYPE);
+        _isPersistentStore = !"Memory".equals(nodeType);
+
+        String storeDir = null;
+        if (System.getProperty("profile", "").startsWith("java-dby-mem"))
+        {
+            storeDir = ":memory:";
+        }
+        else if (!"Memory".equals(nodeType))
+        {
+            storeDir = "${qpid.work_dir}" + File.separator + virtualHostNodeName;
+        }
+
+        String blueprint = System.getProperty(SYSTEST_PROPERTY_VIRTUALHOST_BLUEPRINT);
+        LOGGER.debug("Creating Virtual host from blueprint: {}", blueprint);
+
+        Map<String, Object> attributes = new HashMap<>();
+        attributes.put("name", virtualHostNodeName);
+        attributes.put("type", nodeType);
+        attributes.put("qpid-type", nodeType);
+        String contextAsString;
+        try
+        {
+            contextAsString =
+                    new ObjectMapper().writeValueAsString(Collections.singletonMap("virtualhostBlueprint", blueprint));
+        }
+        catch (JsonProcessingException e)
+        {
+            throw new BrokerAdminException("Cannot create virtual host as context serialization failed", e);
+        }
+        attributes.put("context", contextAsString);
+        attributes.put("defaultVirtualHostNode", true);
+        attributes.put("virtualHostInitialConfiguration", blueprint);
+        if (storeDir != null)
+        {
+            attributes.put("storePath", storeDir);
+        }
+
+        try
+        {
+            Connection connection = createConnection("$management");
+            try
+            {
+                connection.start();
+                final Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+                try
+                {
+                    new AmqpManagementFacade().createEntityUsingAmqpManagement(virtualHostNodeName,
+                                                                               "org.apache.qpid.VirtualHostNode",
+                                                                               attributes,
+                                                                               session);
+                }
+                catch (AmqpManagementFacade.OperationUnsuccessfulException e)
+                {
+                    throw new BrokerAdminException(String.format("Cannot create test virtual host '%s'",
+                                                                 virtualHostNodeName), e);
+                }
+                finally
+                {
+                    session.close();
+                }
+            }
+            finally
+            {
+                connection.close();
+            }
+        }
+        catch (JMSException e)
+        {
+            throw new BrokerAdminException(String.format("Cannot create virtual host '%s'", virtualHostNodeName), e);
+        }
+    }
+
+    void deleteVirtualHost(final String virtualHostNodeName)
+    {
+        try
+        {
+            Connection connection = createConnection("$management");
+            try
+            {
+                connection.start();
+                final Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+                try
+                {
+                    new AmqpManagementFacade().deleteEntityUsingAmqpManagement(virtualHostNodeName,
+                                                                               "org.apache.qpid.VirtualHostNode",
+                                                                               session);
+                }
+                catch (AmqpManagementFacade.OperationUnsuccessfulException e)
+                {
+                    throw new BrokerAdminException(String.format("Cannot delete test virtual host '%s'",
+                                                                 virtualHostNodeName), e);
+                }
+                finally
+                {
+                    session.close();
+                }
+            }
+            finally
+            {
+                connection.close();
+            }
+        }
+        catch (JMSException e)
+        {
+            throw new BrokerAdminException(String.format("Cannot delete virtual host '%s'", virtualHostNodeName), e);
+        }
+    }
+
+    ListenableFuture<Void> restartVirtualHost(final String virtualHostNodeName)
+    {
+        try
+        {
+            Connection connection = createConnection("$management");
+            try
+            {
+                connection.start();
+                updateVirtualHostNode(virtualHostNodeName,
+                                      Collections.<String, Object>singletonMap("desiredState", "STOPPED"), connection);
+                updateVirtualHostNode(virtualHostNodeName,
+                                      Collections.<String, Object>singletonMap("desiredState", "ACTIVE"), connection);
+                return Futures.immediateFuture(null);
+            }
+            finally
+            {
+                connection.close();
+            }
+        }
+        catch (JMSException e)
+        {
+            throw new BrokerAdminException(String.format("Cannot create virtual host %s", virtualHostNodeName), e);
+        }
+    }
+
+    private void updateVirtualHostNode(final String virtualHostNodeName,
+                                       final Map<String, Object> attributes,
+                                       final Connection connection) throws JMSException
+    {
+        final Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+        try
+        {
+            new AmqpManagementFacade().updateEntityUsingAmqpManagement(virtualHostNodeName,
+                                                                       "org.apache.qpid.VirtualHostNode",
+                                                                       attributes,
+                                                                       session);
+        }
+        catch (AmqpManagementFacade.OperationUnsuccessfulException e)
+        {
+            throw new BrokerAdminException(String.format("Cannot create test virtual host '%s'", virtualHostNodeName),
+                                           e);
+        }
+        finally
+        {
+            session.close();
+        }
+    }
+
+    void shutdown()
+    {
+        if (SystemUtils.isWindows())
+        {
+            doWindowsKill();
+        }
+
+        if (_process != null)
+        {
+            LOGGER.info("Destroying broker process");
+            _process.destroy();
+
+            reapChildProcess();
+        }
+    }
+
+    private String escapePath(String value)
+    {
+        if (SystemUtils.isWindows() && value.contains("\"") && !value.startsWith("\""))
+        {
+            return "\"" + value.replaceAll("\"", "\"\"") + "\"";
+        }
+        else
+        {
+            return value;
+        }
+    }
+
+    private Connection createConnection(String virtualHostName) throws JMSException
+    {
+        final Hashtable<Object, Object> initialContextEnvironment = new Hashtable<>();
+        initialContextEnvironment.put(Context.INITIAL_CONTEXT_FACTORY,
+                                      "org.apache.qpid.jndi.PropertiesFileInitialContextFactory");
+        final String factoryName = "connectionFactory";
+        String urlTemplate = "amqp://:@%s/%s?brokerlist='tcp://localhost:%d?failover='nofailover''";
+        String url = String.format(urlTemplate,
+                                   "spawn_broker_admin",
+                                   virtualHostName,
+                                   getBrokerAddress(PortType.AMQP).getPort());
+        initialContextEnvironment.put("connectionfactory." + factoryName, url);
+        try
+        {
+            InitialContext initialContext = new InitialContext(initialContextEnvironment);
+            try
+            {
+                ConnectionFactory factory = (ConnectionFactory) initialContext.lookup(factoryName);
+                return factory.createConnection(getValidUsername(), getValidPassword());
+            }
+            finally
+            {
+                initialContext.close();
+            }
+        }
+        catch (NamingException e)
+        {
+            throw new BrokerAdminException("Unexpected exception on connection lookup", e);
+        }
+    }
+
+    private void setClassQualifiedTestName(final String name)
+    {
+        final LoggerContext loggerContext = ((ch.qos.logback.classic.Logger) LOGGER).getLoggerContext();
+        loggerContext.putProperty(LogbackPropertyValueDiscriminator.CLASS_QUALIFIED_TEST_NAME, name);
+    }
+
+
+    private String getVirtualHostNodeName(final Class testClass, final Method method)
+    {
+        return testClass.getSimpleName() + "_" + method.getName();
+    }
+
+
+    private void doWindowsKill()
+    {
+        try
+        {
+
+            Process p;
+            p = Runtime.getRuntime().exec(new String[]{"taskkill", "/PID", Integer.toString(_pid), "/T", "/F"});
+            consumeAllOutput(p);
+        }
+        catch (IOException e)
+        {
+            LOGGER.error("Error whilst killing process " + _pid, e);
+        }
+    }
+
+    private static void consumeAllOutput(Process p) throws IOException
+    {
+        try (InputStreamReader inputStreamReader = new InputStreamReader(p.getInputStream()))
+        {
+            try (BufferedReader reader = new BufferedReader(inputStreamReader))
+            {
+                String line;
+                while ((line = reader.readLine()) != null)
+                {
+                    LOGGER.debug("Consuming output: {}", line);
+                }
+            }
+        }
+    }
+
+    private void reapChildProcess()
+    {
+        try
+        {
+            _process.waitFor();
+            LOGGER.info("broker exited: " + _process.exitValue());
+        }
+        catch (InterruptedException e)
+        {
+            LOGGER.error("Interrupted whilst waiting for process shutdown");
+            Thread.currentThread().interrupt();
+        }
+        finally
+        {
+            try
+            {
+                _process.getInputStream().close();
+                _process.getErrorStream().close();
+                _process.getOutputStream().close();
+            }
+            catch (IOException ignored)
+            {
+            }
+        }
+    }
+
+    private String dumpThreads()
+    {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try
+        {
+            Process process = Runtime.getRuntime().exec("jstack " + _pid);
+            InputStream is = process.getInputStream();
+            byte[] buffer = new byte[1024];
+            int length;
+            while ((length = is.read(buffer)) != -1)
+            {
+                baos.write(buffer, 0, length);
+            }
+        }
+        catch (Exception e)
+        {
+            LOGGER.error("Error whilst collecting thread dump for " + _pid, e);
+        }
+        return new String(baos.toByteArray());
+    }
+
+
+    public final class BrokerSystemOutpuHandler implements Runnable
+    {
+
+        private final BufferedReader _in;
+        private final Logger _out;
+        private final String _ready;
+        private final String _stopped;
+        private final List<ListeningPort> _amqpPorts;
+        private final Pattern _pidPattern;
+        private final Pattern _amqpPortPattern;
+        private volatile boolean _seenReady;
+        private volatile String _stopLine;
+        private volatile int _pid;
+
+        private BrokerSystemOutpuHandler(InputStream in,
+                                         String ready,
+                                         String stopped,
+                                         String pidRegExp,
+                                         String amqpPortRegExp,
+                                         String loggerName)
+        {
+            _amqpPorts = new ArrayList<>();
+            _in = new BufferedReader(new InputStreamReader(in));
+            _out = LoggerFactory.getLogger(loggerName);
+            _ready = ready;
+            _stopped = stopped;
+            _seenReady = false;
+            _amqpPortPattern = Pattern.compile(amqpPortRegExp);
+            _pidPattern = Pattern.compile(pidRegExp);
+        }
+
+        @Override
+        public void run()
+        {
+            try
+            {
+                String line;
+                while ((line = _in.readLine()) != null)
+                {
+                    _out.info(line);
+
+                    checkPortListeningLog(line, _amqpPortPattern, _amqpPorts);
+
+                    Matcher pidMatcher = _pidPattern.matcher(line);
+                    if (pidMatcher.find())
+                    {
+                        if (pidMatcher.groupCount() > 1)
+                        {
+                            _pid = Integer.parseInt(pidMatcher.group(1));
+                        }
+                    }
+
+                    if (line.contains(_ready))
+                    {
+                        _seenReady = true;
+                        break;
+                    }
+
+                    if (!_seenReady && line.contains(_stopped))
+                    {
+                        _stopLine = line;
+                    }
+                }
+            }
+            catch (IOException e)
+            {
+                LOGGER.warn(e.getMessage()
+                            + " : Broker stream from unexpectedly closed; last log lines written by Broker may be lost.");
+            }
+        }
+
+        private void checkPortListeningLog(final String line,
+                                           final Pattern portPattern,
+                                           final List<ListeningPort> ports)
+        {
+            Matcher portMatcher = portPattern.matcher(line);
+            if (portMatcher.find())
+            {
+                ports.add(new ListeningPort(portMatcher.group(1),
+                                            portMatcher.group(2),
+                                            Integer.parseInt(portMatcher.group(3))));
+            }
+        }
+
+        String getStopLine()
+        {
+            return _stopLine;
+        }
+
+        String getReady()
+        {
+            return _ready;
+        }
+
+        int getPID()
+        {
+            return _pid;
+        }
+
+        List<ListeningPort> getAmqpPorts()
+        {
+            return _amqpPorts;
+        }
+    }
+
+    private static class ListeningPort
+    {
+        private String _protocol;
+        private String _transport;
+        private int _port;
+
+        ListeningPort(final String protocol, final String transport, final int port)
+        {
+            _transport = transport;
+            _port = port;
+            _protocol = protocol;
+        }
+
+        String getTransport()
+        {
+            return _transport;
+        }
+
+        int getPort()
+        {
+            return _port;
+        }
+
+        String getProtocol()
+        {
+            return _protocol;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "ListeningPort{" +
+                   "_protocol='" + _protocol + '\'' +
+                   ", _transport='" + _transport + '\'' +
+                   ", _port=" + _port +
+                   '}';
+        }
+    }
+}
diff --git a/systests/src/main/java/org/apache/qpid/systest/core/dependency/Booter.java b/systests/src/main/java/org/apache/qpid/systest/core/dependency/Booter.java
new file mode 100644
index 0000000..b928f4c
--- /dev/null
+++ b/systests/src/main/java/org/apache/qpid/systest/core/dependency/Booter.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.systest.core.dependency;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.repository.LocalRepository;
+import org.eclipse.aether.repository.RemoteRepository;
+
+public class Booter
+{
+    private static final String FALLBACK_LOCAL_REPO_URL = System.getProperty("user.home")
+                                                          + File.separator
+                                                          + ".m2"
+                                                          + File.separator
+                                                          + "repository";
+    private static final String REMOTE_REPO_URL = System.getProperty(
+            "qpid.systests.end_to_end_conversion.remoteRepository",
+            "https://repo.maven.apache.org/maven2/");
+    private static final String LOCAL_REPO =
+            System.getProperty("qpid.systests.end_to_end_conversion.localRepository", FALLBACK_LOCAL_REPO_URL);
+
+    public static RepositorySystem newRepositorySystem()
+    {
+        return ManualRepositorySystemFactory.newRepositorySystem();
+    }
+
+    public static DefaultRepositorySystemSession newRepositorySystemSession(RepositorySystem system)
+    {
+        DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
+
+        LocalRepository localRepo = new LocalRepository("target/local-repo");
+        session.setLocalRepositoryManager(system.newLocalRepositoryManager(session, localRepo));
+
+        session.setTransferListener(new ConsoleTransferListener());
+        session.setRepositoryListener(new ConsoleRepositoryListener());
+
+        // uncomment to generate dirty trees
+        // session.setDependencyGraphTransformer( null );
+
+        return session;
+    }
+
+    public static List<RemoteRepository> newRepositories()
+    {
+        return Arrays.asList(newLocalRepository(), newCentralRepository());
+    }
+
+    private static RemoteRepository newCentralRepository()
+    {
+        return new RemoteRepository.Builder("central", "default", REMOTE_REPO_URL).build();
+    }
+
+    private static RemoteRepository newLocalRepository()
+    {
+        final URL localRepoUrl = toUrl(LOCAL_REPO);
+        return new RemoteRepository.Builder("local", "default", localRepoUrl.toString()).build();
+    }
+
+    private static URL toUrl(final String localRepo)
+    {
+        try
+        {
+            return new URL(localRepo);
+        }
+        catch (MalformedURLException e)
+        {
+            try
+            {
+                return new File(localRepo).toURI().toURL();
+            }
+            catch (MalformedURLException e1)
+            {
+                throw new RuntimeException(String.format("Failed to convert '%s' into a URL", localRepo), e);
+            }
+        }
+    }
+}
diff --git a/systests/src/main/java/org/apache/qpid/systest/core/dependency/ClasspathQuery.java b/systests/src/main/java/org/apache/qpid/systest/core/dependency/ClasspathQuery.java
new file mode 100644
index 0000000..9f4844c
--- /dev/null
+++ b/systests/src/main/java/org/apache/qpid/systest/core/dependency/ClasspathQuery.java
@@ -0,0 +1,202 @@
+/*
+ * 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.systest.core.dependency;
+
+import java.io.File;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import com.google.common.base.Joiner;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.collection.CollectRequest;
+import org.eclipse.aether.graph.Dependency;
+import org.eclipse.aether.graph.DependencyFilter;
+import org.eclipse.aether.resolution.ArtifactResult;
+import org.eclipse.aether.resolution.DependencyRequest;
+import org.eclipse.aether.resolution.DependencyResolutionException;
+import org.eclipse.aether.util.artifact.JavaScopes;
+import org.eclipse.aether.util.filter.DependencyFilterUtils;
+
+public class ClasspathQuery
+{
+    private static final LoadingCache<Collection<String>, List<File>> _classpathCache;
+    private static final RepositorySystem _mavenRepositorySystem;
+    private static final RepositorySystemSession _mavenRepositorySession;
+
+    static
+    {
+        _mavenRepositorySystem = Booter.newRepositorySystem();
+        _mavenRepositorySession = Booter.newRepositorySystemSession(_mavenRepositorySystem);
+        _classpathCache = CacheBuilder.newBuilder()
+                                      .maximumSize(8)
+                                      .recordStats()
+                                      .build(new CacheLoader<Collection<String>, List<File>>()
+                                      {
+                                          @Override
+                                          public List<File> load(final Collection<String> key) throws Exception
+                                          {
+                                              return doBuildClassPath(key);
+                                          }
+                                      });
+    }
+
+    private final Class<?> _clientClass;
+    private final Collection<String> _dependencyGavs;
+
+
+    public ClasspathQuery(final Class<?> clientClass, final Collection<String> gavs)
+    {
+        _clientClass = clientClass;
+        _dependencyGavs = gavs;
+    }
+
+    public static String getCacheStats()
+    {
+        return _classpathCache.stats().toString();
+    }
+
+    private static List<File> doBuildClassPath(final Collection<String> gavs)
+    {
+        return Collections.unmodifiableList(new ArrayList<>(getJarFiles(gavs)));
+    }
+
+    private static Set<File> getJarFiles(final Collection<String> gavs)
+    {
+        Set<File> jars = new HashSet<>();
+
+        for (final String gav : gavs)
+        {
+            Artifact artifact = new DefaultArtifact(gav);
+
+            DependencyFilter classpathFlter = DependencyFilterUtils.classpathFilter(JavaScopes.COMPILE);
+
+            CollectRequest collectRequest = new CollectRequest();
+            collectRequest.setRoot(new Dependency(artifact, JavaScopes.COMPILE));
+            collectRequest.setRepositories(Booter.newRepositories());
+
+            DependencyRequest dependencyRequest = new DependencyRequest(collectRequest, classpathFlter);
+
+            List<ArtifactResult> artifactResults;
+            try
+            {
+                artifactResults = _mavenRepositorySystem.resolveDependencies(_mavenRepositorySession, dependencyRequest)
+                                                        .getArtifactResults();
+            }
+            catch (DependencyResolutionException e)
+            {
+                throw new RuntimeException(String.format("Error while dependency resolution for '%s'", gav), e);
+            }
+
+            if (artifactResults == null)
+            {
+                throw new RuntimeException(String.format("Could not resolve dependency for '%s'", gav));
+            }
+
+            for (ArtifactResult artifactResult : artifactResults)
+            {
+                System.out.println(artifactResult.getArtifact() + " resolved to "
+                                   + artifactResult.getArtifact().getFile());
+            }
+
+            for (ArtifactResult result : artifactResults)
+            {
+                jars.add(result.getArtifact().getFile());
+            }
+        }
+        return jars;
+    }
+
+    public Class<?> getClientClass()
+    {
+        return _clientClass;
+    }
+
+    public Collection<String> getDependencyGavs()
+    {
+        return _dependencyGavs;
+    }
+
+    public String getClasspath()
+    {
+        return buildClassPath(_clientClass, _dependencyGavs);
+    }
+
+    private String buildClassPath(final Class<?> clientClazz, final Collection<String> gavs)
+    {
+        List<File> classpathElements = new ArrayList<>(_classpathCache.getUnchecked(gavs));
+        classpathElements.add(getLocalClasspathElement(clientClazz));
+
+        return Joiner.on(System.getProperty("path.separator")).join(classpathElements);
+    }
+
+    private File getLocalClasspathElement(final Class<?> clazz)
+    {
+        int packageDepth = getPackageDepth(clazz);
+        final URL resource = clazz.getResource("/" + clazz.getName().replace(".", "/") + ".class");
+        // TODO handle JAR case
+        try
+        {
+            Path path = new File(resource.toURI()).toPath();
+            for (int i = 0; i < packageDepth + 1; ++i)
+            {
+                path = path.getParent();
+            }
+
+            return path.toFile();
+        }
+        catch (URISyntaxException e)
+        {
+            throw new RuntimeException(String.format("Failed to get classpath element for %s", clazz), e);
+        }
+    }
+
+    private int getPackageDepth(Class clazz)
+    {
+        final String publisherClassName = clazz.getName();
+        int lastIndex = 0;
+        int count = 0;
+
+        while (lastIndex != -1)
+        {
+            lastIndex = publisherClassName.indexOf(".", lastIndex);
+
+            if (lastIndex != -1)
+            {
+                count++;
+                lastIndex += 1;
+            }
+        }
+        return count;
+    }
+}
diff --git a/systests/src/main/java/org/apache/qpid/systest/core/dependency/ConsoleRepositoryListener.java b/systests/src/main/java/org/apache/qpid/systest/core/dependency/ConsoleRepositoryListener.java
new file mode 100644
index 0000000..aa2557d
--- /dev/null
+++ b/systests/src/main/java/org/apache/qpid/systest/core/dependency/ConsoleRepositoryListener.java
@@ -0,0 +1,128 @@
+/*
+ * 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.systest.core.dependency;
+
+import java.io.PrintStream;
+
+import org.eclipse.aether.AbstractRepositoryListener;
+import org.eclipse.aether.RepositoryEvent;
+
+public class ConsoleRepositoryListener extends AbstractRepositoryListener
+{
+
+    private PrintStream _out;
+
+    public ConsoleRepositoryListener()
+    {
+        this(null);
+    }
+
+    public ConsoleRepositoryListener(PrintStream out)
+    {
+        this._out = (out != null) ? out : System.out;
+    }
+
+    public void artifactDeployed(RepositoryEvent event)
+    {
+        _out.println("Deployed " + event.getArtifact() + " to " + event.getRepository());
+    }
+
+    public void artifactDeploying(RepositoryEvent event)
+    {
+        _out.println("Deploying " + event.getArtifact() + " to " + event.getRepository());
+    }
+
+    public void artifactDescriptorInvalid(RepositoryEvent event)
+    {
+        _out.println("Invalid artifact descriptor for " + event.getArtifact() + ": "
+                     + event.getException().getMessage());
+    }
+
+    public void artifactDescriptorMissing(RepositoryEvent event)
+    {
+        _out.println("Missing artifact descriptor for " + event.getArtifact());
+    }
+
+    public void artifactInstalled(RepositoryEvent event)
+    {
+        _out.println("Installed " + event.getArtifact() + " to " + event.getFile());
+    }
+
+    public void artifactInstalling(RepositoryEvent event)
+    {
+        _out.println("Installing " + event.getArtifact() + " to " + event.getFile());
+    }
+
+    public void artifactResolved(RepositoryEvent event)
+    {
+        _out.println("Resolved artifact " + event.getArtifact() + " from " + event.getRepository());
+    }
+
+    public void artifactDownloading(RepositoryEvent event)
+    {
+        _out.println("Downloading artifact " + event.getArtifact() + " from " + event.getRepository());
+    }
+
+    public void artifactDownloaded(RepositoryEvent event)
+    {
+        _out.println("Downloaded artifact " + event.getArtifact() + " from " + event.getRepository());
+    }
+
+    public void artifactResolving(RepositoryEvent event)
+    {
+        _out.println("Resolving artifact " + event.getArtifact());
+    }
+
+    public void metadataDeployed(RepositoryEvent event)
+    {
+        _out.println("Deployed " + event.getMetadata() + " to " + event.getRepository());
+    }
+
+    public void metadataDeploying(RepositoryEvent event)
+    {
+        _out.println("Deploying " + event.getMetadata() + " to " + event.getRepository());
+    }
+
+    public void metadataInstalled(RepositoryEvent event)
+    {
+        _out.println("Installed " + event.getMetadata() + " to " + event.getFile());
+    }
+
+    public void metadataInstalling(RepositoryEvent event)
+    {
+        _out.println("Installing " + event.getMetadata() + " to " + event.getFile());
+    }
+
+    public void metadataInvalid(RepositoryEvent event)
+    {
+        _out.println("Invalid metadata " + event.getMetadata());
+    }
+
+    public void metadataResolved(RepositoryEvent event)
+    {
+        _out.println("Resolved metadata " + event.getMetadata() + " from " + event.getRepository());
+    }
+
+    public void metadataResolving(RepositoryEvent event)
+    {
+        _out.println("Resolving metadata " + event.getMetadata() + " from " + event.getRepository());
+    }
+}
diff --git a/systests/src/main/java/org/apache/qpid/systest/core/dependency/ConsoleTransferListener.java b/systests/src/main/java/org/apache/qpid/systest/core/dependency/ConsoleTransferListener.java
new file mode 100644
index 0000000..dc2882d
--- /dev/null
+++ b/systests/src/main/java/org/apache/qpid/systest/core/dependency/ConsoleTransferListener.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.systest.core.dependency;
+
+import java.io.PrintStream;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.aether.transfer.AbstractTransferListener;
+import org.eclipse.aether.transfer.MetadataNotFoundException;
+import org.eclipse.aether.transfer.TransferEvent;
+import org.eclipse.aether.transfer.TransferResource;
+
+public class ConsoleTransferListener extends AbstractTransferListener
+{
+
+    private PrintStream _out;
+
+    private Map<TransferResource, Long> _downloads = new ConcurrentHashMap<>();
+
+    private int _lastLength;
+
+    public ConsoleTransferListener()
+    {
+        this(null);
+    }
+
+    public ConsoleTransferListener(PrintStream out)
+    {
+        this._out = (out != null) ? out : System.out;
+    }
+
+    @Override
+    public void transferInitiated(TransferEvent event)
+    {
+        String message = event.getRequestType() == TransferEvent.RequestType.PUT ? "Uploading" : "Downloading";
+
+        _out.println(message + ": " + event.getResource().getRepositoryUrl() + event.getResource()
+                                                                                    .getResourceName());
+    }
+
+    @Override
+    public void transferProgressed(TransferEvent event)
+    {
+        TransferResource resource = event.getResource();
+        _downloads.put(resource, Long.valueOf(event.getTransferredBytes()));
+
+        StringBuilder buffer = new StringBuilder(64);
+
+        for (Map.Entry<TransferResource, Long> entry : _downloads.entrySet())
+        {
+            long total = entry.getKey().getContentLength();
+            long complete = entry.getValue().longValue();
+
+            buffer.append(getStatus(complete, total)).append("  ");
+        }
+
+        int pad = _lastLength - buffer.length();
+        _lastLength = buffer.length();
+        pad(buffer, pad);
+        buffer.append('\r');
+
+        _out.print(buffer);
+    }
+
+    private String getStatus(long complete, long total)
+    {
+        if (total >= 1024)
+        {
+            return toKB(complete) + "/" + toKB(total) + " KB ";
+        }
+        else if (total >= 0)
+        {
+            return complete + "/" + total + " B ";
+        }
+        else if (complete >= 1024)
+        {
+            return toKB(complete) + " KB ";
+        }
+        else
+        {
+            return complete + " B ";
+        }
+    }
+
+    private void pad(StringBuilder buffer, int spaces)
+    {
+        String block = "                                        ";
+        while (spaces > 0)
+        {
+            int n = Math.min(spaces, block.length());
+            buffer.append(block, 0, n);
+            spaces -= n;
+        }
+    }
+
+    @Override
+    public void transferSucceeded(TransferEvent event)
+    {
+        transferCompleted(event);
+
+        TransferResource resource = event.getResource();
+        long contentLength = event.getTransferredBytes();
+        if (contentLength >= 0)
+        {
+            String type = (event.getRequestType() == TransferEvent.RequestType.PUT ? "Uploaded" : "Downloaded");
+            String len = contentLength >= 1024 ? toKB(contentLength) + " KB" : contentLength + " B";
+
+            String throughput = "";
+            long duration = System.currentTimeMillis() - resource.getTransferStartTime();
+            if (duration > 0)
+            {
+                long bytes = contentLength - resource.getResumeOffset();
+                DecimalFormat format = new DecimalFormat("0.0", new DecimalFormatSymbols(Locale.ENGLISH));
+                double kbPerSec = (bytes / 1024.0) / (duration / 1000.0);
+                throughput = " at " + format.format(kbPerSec) + " KB/sec";
+            }
+
+            _out.println(type + ": " + resource.getRepositoryUrl() + resource.getResourceName() + " (" + len
+                         + throughput + ")");
+        }
+    }
+
+    @Override
+    public void transferFailed(TransferEvent event)
+    {
+        transferCompleted(event);
+
+        if (!(event.getException() instanceof MetadataNotFoundException))
+        {
+            event.getException().printStackTrace(_out);
+        }
+    }
+
+    private void transferCompleted(TransferEvent event)
+    {
+        _downloads.remove(event.getResource());
+
+        StringBuilder buffer = new StringBuilder(64);
+        pad(buffer, _lastLength);
+        buffer.append('\r');
+        _out.print(buffer);
+    }
+
+    public void transferCorrupted(TransferEvent event)
+    {
+        event.getException().printStackTrace(_out);
+    }
+
+    protected long toKB(long bytes)
+    {
+        return (bytes + 1023) / 1024;
+    }
+}
diff --git a/systests/src/main/java/org/apache/qpid/systest/core/dependency/ManualRepositorySystemFactory.java b/systests/src/main/java/org/apache/qpid/systest/core/dependency/ManualRepositorySystemFactory.java
new file mode 100644
index 0000000..a742d9b
--- /dev/null
+++ b/systests/src/main/java/org/apache/qpid/systest/core/dependency/ManualRepositorySystemFactory.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.systest.core.dependency;
+
+import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
+import org.eclipse.aether.impl.DefaultServiceLocator;
+import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
+import org.eclipse.aether.spi.connector.transport.TransporterFactory;
+import org.eclipse.aether.transport.file.FileTransporterFactory;
+import org.eclipse.aether.transport.http.HttpTransporterFactory;
+
+public class ManualRepositorySystemFactory
+{
+
+    public static RepositorySystem newRepositorySystem()
+    {
+    /*
+     * Aether's components implement org.eclipse.aether.spi.locator.Service to ease manual wiring and using the
+     * prepopulated DefaultServiceLocator, we only need to register the repository connector and transporter
+     * factories.
+     */
+        DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator();
+        locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class);
+        locator.addService(TransporterFactory.class, FileTransporterFactory.class);
+        locator.addService(TransporterFactory.class, HttpTransporterFactory.class);
+
+        locator.setErrorHandler(new DefaultServiceLocator.ErrorHandler()
+        {
+            @Override
+            public void serviceCreationFailed(Class<?> type, Class<?> impl, Throwable exception)
+            {
+                exception.printStackTrace();
+            }
+        });
+
+        return locator.getService(RepositorySystem.class);
+    }
+}
diff --git a/systests/src/main/java/org/apache/qpid/systest/core/logback/LogbackPropertyValueDiscriminator.java b/systests/src/main/java/org/apache/qpid/systest/core/logback/LogbackPropertyValueDiscriminator.java
new file mode 100644
index 0000000..b564a1f
--- /dev/null
+++ b/systests/src/main/java/org/apache/qpid/systest/core/logback/LogbackPropertyValueDiscriminator.java
@@ -0,0 +1,65 @@
+/*
+ *
+ * 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.systest.core.logback;
+
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.classic.spi.LoggerContextVO;
+import ch.qos.logback.core.sift.AbstractDiscriminator;
+
+public class LogbackPropertyValueDiscriminator extends AbstractDiscriminator<ILoggingEvent>
+{
+    public static final String CLASS_QUALIFIED_TEST_NAME = "classQualifiedTestName";
+
+    private String _key;
+    private String _defaultValue;
+
+    @Override
+    public String getDiscriminatingValue(final ILoggingEvent event)
+    {
+        final LoggerContextVO context = event.getLoggerContextVO();
+        if (context != null && context.getPropertyMap() != null && context.getPropertyMap().get(_key) != null)
+        {
+            return context.getPropertyMap().get(_key);
+        }
+        return _defaultValue;
+    }
+
+    @Override
+    public String getKey()
+    {
+        return _key;
+    }
+
+    public void setKey(String key)
+    {
+        _key = key;
+    }
+
+    public String getDefaultValue()
+    {
+        return _defaultValue;
+    }
+
+    public void setDefaultValue(String defaultValue)
+    {
+        _defaultValue = defaultValue;
+    }
+}
diff --git a/systests/src/main/java/org/apache/qpid/systest/core/logback/LogbackSocketPortNumberDefiner.java b/systests/src/main/java/org/apache/qpid/systest/core/logback/LogbackSocketPortNumberDefiner.java
new file mode 100644
index 0000000..18e5653
--- /dev/null
+++ b/systests/src/main/java/org/apache/qpid/systest/core/logback/LogbackSocketPortNumberDefiner.java
@@ -0,0 +1,45 @@
+/*
+ *
+ * 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.systest.core.logback;
+
+import ch.qos.logback.core.PropertyDefinerBase;
+
+import org.apache.qpid.systest.core.util.PortHelper;
+
+public class LogbackSocketPortNumberDefiner extends PropertyDefinerBase
+{
+    /**
+     * Port number that will be bound by a Logback Socket Receiver.  This is assigned once per JVM instance.
+     */
+    private static final int LOGBACK_SOCKET_PORT_NUMBER = new PortHelper().getNextAvailable(Integer.getInteger("qpid.systests.logback.receiver.port", 15000));
+
+    public static int getLogbackSocketPortNumber()
+    {
+        return LOGBACK_SOCKET_PORT_NUMBER;
+    }
+
+    @Override
+    public String getPropertyValue()
+    {
+        return String.valueOf(LOGBACK_SOCKET_PORT_NUMBER);
+    }
+}
diff --git a/systests/src/main/java/org/apache/qpid/systest/core/util/FileUtils.java b/systests/src/main/java/org/apache/qpid/systest/core/util/FileUtils.java
new file mode 100644
index 0000000..6266b95
--- /dev/null
+++ b/systests/src/main/java/org/apache/qpid/systest/core/util/FileUtils.java
@@ -0,0 +1,58 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.systest.core.util;
+
+import java.io.File;
+
+public class FileUtils
+{
+    public static boolean delete(File file, boolean recursive)
+    {
+        boolean success = true;
+
+        if (file.isDirectory())
+        {
+            if (recursive)
+            {
+                File[] files = file.listFiles();
+
+                // This can occur if the file is deleted outside the JVM
+                if (files == null)
+                {
+                    return false;
+                }
+
+                for (int i = 0; i < files.length; i++)
+                {
+                    success = delete(files[i], true) && success;
+                }
+
+                final boolean directoryDeleteSuccess = file.delete();
+                return success && directoryDeleteSuccess;
+            }
+
+            return false;
+        }
+
+        success = file.delete();
+        return success;
+    }
+}
diff --git a/systests/src/main/java/org/apache/qpid/systest/core/util/PortHelper.java b/systests/src/main/java/org/apache/qpid/systest/core/util/PortHelper.java
new file mode 100644
index 0000000..4a15ae3
--- /dev/null
+++ b/systests/src/main/java/org/apache/qpid/systest/core/util/PortHelper.java
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.qpid.systest.core.util;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.util.HashSet;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class PortHelper
+{
+    private static final Logger _logger = LoggerFactory.getLogger(PortHelper.class);
+
+    public static final int START_PORT_NUMBER = 10000;
+
+    private static final int DEFAULT_TIMEOUT_MILLIS = 5000;
+
+    public static final int MIN_PORT_NUMBER = 1;
+    public static final int MAX_PORT_NUMBER = 49151;
+
+    private int _timeout = DEFAULT_TIMEOUT_MILLIS;
+
+
+    private final Set<Integer> _allocatedPorts = new HashSet<>();
+    private int _highestIssuedPort = -1;
+
+    /**
+     * Gets the next available port starting from given point.
+     *
+     * @param fromPort the port to scan for availability
+     * @throws NoSuchElementException if there are no ports available
+     */
+    public int getNextAvailable(int fromPort)
+    {
+        if ((fromPort < MIN_PORT_NUMBER) || (fromPort > MAX_PORT_NUMBER))
+        {
+            throw new IllegalArgumentException("Invalid start port: " + fromPort);
+        }
+
+        for (int i = fromPort; i <= MAX_PORT_NUMBER; i++)
+        {
+            if (isPortAvailable(i))
+            {
+                _allocatedPorts.add(i);
+                _highestIssuedPort = Math.max(_highestIssuedPort, i);
+                return i;
+            }
+        }
+
+        throw new NoSuchElementException("Could not find an available port above " + fromPort);
+    }
+
+    /**
+     * Gets the next available port that is higher than all other port numbers issued
+     * thus far.  If no port numbers have been issued, a default is used.
+     *
+     * @throws NoSuchElementException if there are no ports available
+     */
+    public int getNextAvailable()
+    {
+
+        if (_highestIssuedPort < 0)
+        {
+            return getNextAvailable(START_PORT_NUMBER);
+        }
+        else
+        {
+            return getNextAvailable(_highestIssuedPort + 1);
+        }
+    }
+
+    /**
+     * Tests that all ports allocated by getNextAvailable are free.
+     */
+    public void waitUntilAllocatedPortsAreFree()
+    {
+        waitUntilPortsAreFree(_allocatedPorts);
+    }
+
+    public void waitUntilPortsAreFree(Set<Integer> ports)
+    {
+        _logger.debug("Checking if ports " + ports + " are free...");
+
+        for (Integer port : ports)
+        {
+            if (port > 0)
+            {
+                waitUntilPortIsFree(port);
+            }
+        }
+
+        _logger.debug("ports " + ports + " are free");
+    }
+
+    private void waitUntilPortIsFree(int port)
+    {
+        long startTime = System.currentTimeMillis();
+        long deadline = startTime + _timeout;
+        boolean alreadyFailed = false;
+
+        while (true)
+        {
+            if (System.currentTimeMillis() > deadline)
+            {
+                throw new RuntimeException("Timed out after " + _timeout + " ms waiting for port " + port + " to become available");
+            }
+
+            if (isPortAvailable(port))
+            {
+                if(alreadyFailed)
+                {
+                    _logger.debug("port " + port + " is now available");
+                }
+                return;
+            }
+            else
+            {
+                alreadyFailed = true;
+            }
+
+            try
+            {
+                Thread.sleep(500);
+            }
+            catch (InterruptedException e)
+            {
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    public boolean isPortAvailable(int port)
+    {
+        ServerSocket serverSocket = null;
+        try
+        {
+            serverSocket = new ServerSocket();
+            serverSocket.setReuseAddress(true); // ensures that the port is subsequently usable
+            serverSocket.bind(new InetSocketAddress(port));
+
+            return true;
+        }
+        catch (IOException e)
+        {
+            _logger.debug("port " + port + " is not free");
+            return false;
+        }
+        finally
+        {
+            if (serverSocket != null)
+            {
+                try
+                {
+                    serverSocket.close();
+                }
+                catch (IOException e)
+                {
+                    throw new RuntimeException("Couldn't close port "
+                                               + port
+                                               + " that we created to check its availability", e);
+                }
+            }
+        }
+    }
+
+    public void setTimeout(int timeout)
+    {
+        this._timeout = timeout;
+    }
+}
diff --git a/systests/src/main/java/org/apache/qpid/systest/core/util/SystemUtils.java b/systests/src/main/java/org/apache/qpid/systest/core/util/SystemUtils.java
new file mode 100644
index 0000000..593b135
--- /dev/null
+++ b/systests/src/main/java/org/apache/qpid/systest/core/util/SystemUtils.java
@@ -0,0 +1,35 @@
+/*
+ *
+ * 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.systest.core.util;
+
+public class SystemUtils
+{
+
+    private SystemUtils()
+    {
+    }
+
+    public static boolean isWindows()
+    {
+        return System.getProperty("os.name", "unknown").toLowerCase().contains("windows");
+    }
+
+}
diff --git a/systests/src/main/java/org/apache/qpid/systest/core/util/Utils.java b/systests/src/main/java/org/apache/qpid/systest/core/util/Utils.java
new file mode 100644
index 0000000..b05c841
--- /dev/null
+++ b/systests/src/main/java/org/apache/qpid/systest/core/util/Utils.java
@@ -0,0 +1,122 @@
+/*
+ *
+ * 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.systest.core.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jms.Connection;
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+
+public class Utils
+{
+    private static final int DEFAULT_MESSAGE_SIZE = 1024;
+    public static final String INDEX = "index";
+    private static final String DEFAULT_MESSAGE_PAYLOAD = createString(DEFAULT_MESSAGE_SIZE);
+
+    public static void sendTextMessage(final Connection connection, final Destination destination, String message)
+            throws JMSException
+    {
+        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+        try
+        {
+            MessageProducer producer = session.createProducer(destination);
+            producer.send(session.createTextMessage(message));
+        }
+        finally
+        {
+            session.close();
+        }
+    }
+
+    public static void sendMessages(final Connection connection, final Destination destination, final int count)
+            throws JMSException
+    {
+        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+        try
+        {
+            sendMessages(session, destination, count);
+        }
+        finally
+        {
+            session.close();
+        }
+    }
+
+    public static List<Message> sendMessages(Session session, Destination destination, int count) throws JMSException
+    {
+        List<Message> messages = new ArrayList<>(count);
+        MessageProducer producer = session.createProducer(destination);
+
+        for (int i = 0; i < (count); i++)
+        {
+            Message next = createNextMessage(session, i);
+            producer.send(next);
+            messages.add(next);
+        }
+
+        if (session.getTransacted())
+        {
+            session.commit();
+        }
+
+        return messages;
+    }
+
+    public static Message createNextMessage(Session session, int msgCount) throws JMSException
+    {
+        Message message = createMessage(session, DEFAULT_MESSAGE_SIZE);
+        message.setIntProperty(INDEX, msgCount);
+
+        return message;
+    }
+
+    public static Message createMessage(Session session, int messageSize) throws JMSException
+    {
+        String payload;
+        if (messageSize == DEFAULT_MESSAGE_SIZE)
+        {
+            payload = DEFAULT_MESSAGE_PAYLOAD;
+        }
+        else
+        {
+            payload = createString(messageSize);
+        }
+
+        return session.createTextMessage(payload);
+    }
+
+    private static String createString(final int stringSize)
+    {
+        final String payload;
+        StringBuilder stringBuilder = new StringBuilder();
+        for (int i = 0; i < stringSize; ++i)
+        {
+            stringBuilder.append("x");
+        }
+        payload = stringBuilder.toString();
+        return payload;
+    }
+}
diff --git a/systests/src/main/resources/broker-j-config-with-logging.json b/systests/src/main/resources/broker-j-config-with-logging.json
new file mode 100644
index 0000000..ccce97e
--- /dev/null
+++ b/systests/src/main/resources/broker-j-config-with-logging.json
@@ -0,0 +1,83 @@
+/*
+ *
+ * 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.
+ *
+ */
+{
+  "name" : "${broker.name}",
+  "modelVersion" : "7.0",
+  "authenticationproviders" : [ {
+    "name" : "plain",
+    "type" : "Plain",
+    "secureOnlyMechanisms" : [],
+    "users" : [ {
+      "name" : "admin",
+      "type" : "managed",
+      "password" : "admin"
+    }, {
+      "name" : "guest",
+      "type" : "managed",
+      "password" : "guest"
+    } ]
+  } ],
+  "brokerloggers" : [ {
+    "name" : "logsocket",
+    "port" : "${qpid.systest.logback.socket.port}",
+    "type" : "BrokerLogbackSocket",
+    "mappedDiagnosticContext" :  {"origin" : "${qpid.systest.logback.origin}" },
+    "contextProperties" : {"classQualifiedTestName" : "${qpid.systest.logback.context}"},
+    "brokerloginclusionrules" : [ {
+      "name" : "Root",
+      "type" : "NameAndLevel",
+      "level" : "INFO",
+      "loggerName" : "ROOT"
+    }, {
+      "name" : "Qpid",
+      "type" : "NameAndLevel",
+      "level" : "DEBUG",
+      "loggerName" : "org.apache.qpid.*"
+    }, {
+      "name" : "Operational",
+      "type" : "NameAndLevel",
+      "level" : "INFO",
+      "loggerName" : "qpid.message.*"
+    }, {
+      "name" : "Statistics",
+      "type" : "NameAndLevel",
+      "level" : "INFO",
+      "loggerName" : "qpid.statistics.*"
+    } ]
+  } ],
+  "ports" : [ {
+    "name" : "AMQP",
+    "type" : "AMQP",
+    "authenticationProvider" : "plain",
+    "port" : "0",
+    "virtualhostaliases" : [ {
+      "name" : "defaultAlias",
+      "type" : "defaultAlias"
+    }, {
+      "name" : "hostnameAlias",
+      "type" : "hostnameAlias"
+    }, {
+      "name" : "nameAlias",
+      "type" : "nameAlias"
+    } ]
+  } ],
+  "virtualhostnodes" : []
+}
diff --git a/systests/src/test/java/org/apache/qpid/systest/core/brokerj/SpawnQpidBrokerAdminTest.java b/systests/src/test/java/org/apache/qpid/systest/core/brokerj/SpawnQpidBrokerAdminTest.java
new file mode 100644
index 0000000..47a7bfa
--- /dev/null
+++ b/systests/src/test/java/org/apache/qpid/systest/core/brokerj/SpawnQpidBrokerAdminTest.java
@@ -0,0 +1,282 @@
+/*
+ *
+ * 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.systest.core.brokerj;
+
+import static org.apache.qpid.systest.core.brokerj.SpawnQpidBrokerAdmin.SYSTEST_PROPERTY_BROKERJ_DEPENDECIES;
+import static org.apache.qpid.systest.core.brokerj.SpawnQpidBrokerAdmin.SYSTEST_PROPERTY_BUILD_CLASSPATH_FILE;
+import static org.apache.qpid.systest.core.brokerj.SpawnQpidBrokerAdmin.SYSTEST_PROPERTY_INITIAL_CONFIGURATION_LOCATION;
+import static org.apache.qpid.systest.core.brokerj.SpawnQpidBrokerAdmin.SYSTEST_PROPERTY_JAVA8_EXECUTABLE;
+import static org.apache.qpid.systest.core.brokerj.SpawnQpidBrokerAdmin.SYSTEST_PROPERTY_VIRTUALHOST_BLUEPRINT;
+import static org.apache.qpid.systest.core.brokerj.SpawnQpidBrokerAdmin.SYSTEST_PROPERTY_VIRTUALHOSTNODE_TYPE;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeThat;
+
+import java.io.File;
+import java.util.Hashtable;
+
+import javax.jms.Connection;
+import javax.jms.ConnectionFactory;
+import javax.jms.JMSException;
+import javax.jms.Session;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+
+import org.junit.Test;
+
+import org.apache.qpid.systest.core.BrokerAdmin;
+import org.apache.qpid.systest.core.BrokerAdminException;
+
+public class SpawnQpidBrokerAdminTest
+{
+    @Test
+    public void startBroker() throws Exception
+    {
+        assumeBrokerCanBeStarted();
+
+        SpawnQpidBrokerAdmin spawnQpidBrokerAdmin = new SpawnQpidBrokerAdmin();
+        try
+        {
+            spawnQpidBrokerAdmin.start(SpawnQpidBrokerAdminTest.class);
+        }
+        finally
+        {
+            spawnQpidBrokerAdmin.shutdown();
+        }
+    }
+
+    @Test
+    public void createVirtualHosts() throws Exception
+    {
+        assumeBrokerCanBeStarted();
+        assumeVirtualHostCanBeCreated();
+
+        SpawnQpidBrokerAdmin spawnQpidBrokerAdmin = new SpawnQpidBrokerAdmin();
+        try
+        {
+            spawnQpidBrokerAdmin.start(SpawnQpidBrokerAdminTest.class);
+
+            spawnQpidBrokerAdmin.createVirtualHost("test");
+            try
+            {
+                spawnQpidBrokerAdmin.createVirtualHost("test2");
+                fail("The creation of second default virtual host should fail");
+            }
+            catch(BrokerAdminException e)
+            {
+                // pass
+            }
+            finally
+            {
+                spawnQpidBrokerAdmin.deleteVirtualHost("test");
+            }
+        }
+        finally
+        {
+            spawnQpidBrokerAdmin.shutdown();
+        }
+    }
+
+    @Test
+    public void createVirtualHostAndConnect() throws Exception
+    {
+        assumeBrokerCanBeStarted();
+        assumeVirtualHostCanBeCreated();
+
+        SpawnQpidBrokerAdmin spawnQpidBrokerAdmin = new SpawnQpidBrokerAdmin();
+        try
+        {
+            spawnQpidBrokerAdmin.start(SpawnQpidBrokerAdminTest.class);
+
+            final String virtualHostName = "test";
+            spawnQpidBrokerAdmin.createVirtualHost(virtualHostName);
+            try
+            {
+                Connection connection = getConnection(virtualHostName, spawnQpidBrokerAdmin);
+                try
+                {
+                    connection.createSession(true, Session.SESSION_TRANSACTED).close();
+                }
+                finally
+                {
+                    connection.close();
+                }
+            }
+            finally
+            {
+                spawnQpidBrokerAdmin.deleteVirtualHost(virtualHostName);
+            }
+        }
+        finally
+        {
+            spawnQpidBrokerAdmin.shutdown();
+        }
+    }
+
+    @Test
+    public void deleteVirtualHost() throws Exception
+    {
+        assumeBrokerCanBeStarted();
+        assumeVirtualHostCanBeCreated();
+
+        SpawnQpidBrokerAdmin spawnQpidBrokerAdmin = new SpawnQpidBrokerAdmin();
+        try
+        {
+            spawnQpidBrokerAdmin.start(SpawnQpidBrokerAdminTest.class);
+
+            // create and delete VH twice
+            spawnQpidBrokerAdmin.createVirtualHost("test");
+            spawnQpidBrokerAdmin.deleteVirtualHost("test");
+
+            // if previous deletion failed, than creation should fail as well
+            spawnQpidBrokerAdmin.createVirtualHost("test");
+            spawnQpidBrokerAdmin.deleteVirtualHost("test");
+        }
+        finally
+        {
+            spawnQpidBrokerAdmin.shutdown();
+        }
+    }
+
+    @Test
+    public void restartVirtualHost() throws Exception
+    {
+        assumeBrokerCanBeStarted();
+        assumeVirtualHostCanBeCreated();
+
+        String virtualHostName = "test";
+        SpawnQpidBrokerAdmin spawnQpidBrokerAdmin = new SpawnQpidBrokerAdmin();
+        try
+        {
+            spawnQpidBrokerAdmin.start(SpawnQpidBrokerAdminTest.class);
+
+            try
+            {
+                spawnQpidBrokerAdmin.restartVirtualHost(virtualHostName);
+                fail("Virtual host restart should fail as virtual host is no created yet");
+            }
+            catch (BrokerAdminException e)
+            {
+                // pass
+            }
+
+            spawnQpidBrokerAdmin.createVirtualHost(virtualHostName);
+            try
+            {
+                Connection connection = getConnection(virtualHostName, spawnQpidBrokerAdmin);
+                try
+                {
+                    Session session = connection.createSession(true, Session.SESSION_TRANSACTED);
+                    spawnQpidBrokerAdmin.restartVirtualHost(virtualHostName);
+
+                    try
+                    {
+                        session.commit();
+                        fail("Session should be closed and commit should not be permitted on closed session");
+                    }
+                    catch (JMSException e)
+                    {
+                        // pass
+                    }
+                }
+                finally
+                {
+                    connection.close();
+                }
+
+                Connection connection2 = getConnection(virtualHostName, spawnQpidBrokerAdmin);
+                try
+                {
+                    connection2.createSession(true, Session.SESSION_TRANSACTED).commit();
+                }
+                finally
+                {
+                    connection2.close();
+                }
+            }
+            finally
+            {
+                spawnQpidBrokerAdmin.deleteVirtualHost(virtualHostName);
+            }
+        }
+        finally
+        {
+            spawnQpidBrokerAdmin.shutdown();
+        }
+    }
+
+    private Connection getConnection(String virtualHostName, BrokerAdmin brokerAdmin) throws JMSException
+    {
+        final Hashtable<Object, Object> initialContextEnvironment = new Hashtable<>();
+        initialContextEnvironment.put(Context.INITIAL_CONTEXT_FACTORY,
+                                      "org.apache.qpid.jndi.PropertiesFileInitialContextFactory");
+        final String factoryName = "connectionFactory";
+        String urlTemplate = "amqp://:@%s/%s?brokerlist='tcp://localhost:%d?failover='nofailover''";
+        String url = String.format(urlTemplate,
+                                   "system_test",
+                                   virtualHostName,
+                                   brokerAdmin.getBrokerAddress(BrokerAdmin.PortType.AMQP).getPort());
+        initialContextEnvironment.put("connectionfactory." + factoryName, url);
+        try
+        {
+            InitialContext initialContext = new InitialContext(initialContextEnvironment);
+            try
+            {
+                ConnectionFactory factory = (ConnectionFactory) initialContext.lookup(factoryName);
+                return factory.createConnection(brokerAdmin.getValidUsername(), brokerAdmin.getValidPassword());
+            }
+            finally
+            {
+                initialContext.close();
+            }
+        }
+        catch (NamingException e)
+        {
+            throw new RuntimeException("Unexpected exception on connection lookup", e);
+        }
+    }
+
+    private void assumeVirtualHostCanBeCreated()
+    {
+        assumeThat(String.format("Virtual host type property (%s) is not set", SYSTEST_PROPERTY_VIRTUALHOSTNODE_TYPE),
+                   System.getProperty(SYSTEST_PROPERTY_VIRTUALHOSTNODE_TYPE), is(notNullValue()));
+        assumeThat(String.format("Virtual host blueprint property (%s) is not set",
+                                 SYSTEST_PROPERTY_VIRTUALHOST_BLUEPRINT),
+                   System.getProperty(SYSTEST_PROPERTY_VIRTUALHOST_BLUEPRINT), is(notNullValue()));
+    }
+
+    private void assumeBrokerCanBeStarted()
+    {
+        assumeThat(String.format("Java 8 executable property (%s) is not set", SYSTEST_PROPERTY_JAVA8_EXECUTABLE),
+                   System.getProperty(SYSTEST_PROPERTY_JAVA8_EXECUTABLE), is(notNullValue()));
+        assumeThat("Java 8 executable does not exist",
+                   new File(System.getProperty(SYSTEST_PROPERTY_JAVA8_EXECUTABLE)).exists(), is(equalTo(true)));
+        assumeThat(String.format("Broker-J classpath property (%s) is not set", SYSTEST_PROPERTY_BUILD_CLASSPATH_FILE),
+                   System.getProperty(SYSTEST_PROPERTY_BUILD_CLASSPATH_FILE), is(notNullValue()));
+        assumeThat(String.format("Broker dependencies property (%s) is not set", SYSTEST_PROPERTY_BROKERJ_DEPENDECIES),
+                   System.getProperty(SYSTEST_PROPERTY_BROKERJ_DEPENDECIES), is(notNullValue()));
+        assumeThat(String.format("Broker-J initial configuration property (%s) is not set", SYSTEST_PROPERTY_INITIAL_CONFIGURATION_LOCATION),
+                   System.getProperty(SYSTEST_PROPERTY_INITIAL_CONFIGURATION_LOCATION), is(notNullValue()));
+    }
+}
\ No newline at end of file
diff --git a/systests/src/test/resources/logback-test.xml b/systests/src/test/resources/logback-test.xml
new file mode 100644
index 0000000..5c04d01
--- /dev/null
+++ b/systests/src/test/resources/logback-test.xml
@@ -0,0 +1,58 @@
+<?xml version="1.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
+  ~
+  ~   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.
+  ~
+  -->
+<configuration debug="true">
+
+    <contextName>qpid-systests</contextName>
+
+    <!-- Logging configuration used for 'systests' modules.  This is named
+         logback-test.xml in order that it is found in preference to the logback.xml.  -->
+
+    <define name="receiverPort" class="org.apache.qpid.systest.core.logback.LogbackSocketPortNumberDefiner"/>
+
+    <appender name="RootSiftAppender" class="ch.qos.logback.classic.sift.SiftingAppender">
+        <discriminator class="org.apache.qpid.systest.core.logback.LogbackPropertyValueDiscriminator">
+            <Key>classQualifiedTestName</Key>
+            <DefaultValue>testrun</DefaultValue>
+        </discriminator>
+        <sift>
+            <appender name="FILE-${classQualifiedTestName}" class="ch.qos.logback.core.FileAppender">
+                <File>${qpid.systest.logback.logs_dir}${file.separator}TEST-${classQualifiedTestName}.txt</File>
+                <Append>False</Append>
+                <encoder>
+                    <!-- origin identifies the broker, valuable when the test involves multiple brokers -->
+                    <pattern>%date %-8X{origin} %-5level [%thread] %logger{10} %msg%n</pattern>
+                </encoder>
+            </appender>
+        </sift>
+    </appender>
+    <!-- Used to receive the log output from spawned brokers so this log is a consolidated one. -->
+    <receiver class="ch.qos.logback.classic.net.server.ServerSocketReceiver">
+        <port>${receiverPort}</port>
+    </receiver>
+    <logger name="qpid.message" level="info" />
+    <logger name="qpid.statistics" level="info" />
+    <logger name="org.apache.qpid" level="debug" />
+    <logger name="org.apache.qpid.jms.provider.amqp.FRAMES" level="trace"/>
+    <root level="warn">
+        <appender-ref ref="RootSiftAppender"/>
+    </root>
+    <shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/>
+</configuration>