You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by ta...@apache.org on 2014/09/23 20:20:30 UTC

[06/27] Initial drop of donated AMQP Client Code.

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/e4decdc1/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/sections/MessageHeaderSectionMatcher.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/sections/MessageHeaderSectionMatcher.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/sections/MessageHeaderSectionMatcher.java
new file mode 100644
index 0000000..eed6826
--- /dev/null
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/sections/MessageHeaderSectionMatcher.java
@@ -0,0 +1,118 @@
+/*
+ * 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.jms.test.testpeer.matchers.sections;
+
+import java.util.HashMap;
+import org.apache.qpid.proton.amqp.Symbol;
+import org.apache.qpid.proton.amqp.UnsignedLong;
+import org.apache.qpid.jms.test.testpeer.matchers.sections.MessageListSectionMatcher;
+import org.hamcrest.Matcher;
+
+/**
+ * Generated by generate-message-section-matchers.xsl, which resides in this package.
+ */
+public class MessageHeaderSectionMatcher extends MessageListSectionMatcher
+{
+
+    public static final Symbol DESCRIPTOR_SYMBOL = Symbol.valueOf("amqp:header:list");
+    public static final UnsignedLong DESCRIPTOR_CODE = UnsignedLong.valueOf(0x0000000000000070L);
+
+    /** Note that the ordinals of the Field enums match the order specified in the AMQP spec */
+    public enum Field
+    {
+        DURABLE,
+        PRIORITY,
+        TTL,
+        FIRST_ACQUIRER,
+        DELIVERY_COUNT,
+    }
+
+    public MessageHeaderSectionMatcher(boolean expectTrailingBytes)
+    {
+        super(DESCRIPTOR_CODE,
+              DESCRIPTOR_SYMBOL,
+              new HashMap<Object, Matcher<?>>(),
+              expectTrailingBytes);
+    }
+
+
+    public MessageHeaderSectionMatcher withDurable(Matcher<?> m)
+    {
+        getMatchers().put(Field.DURABLE, m);
+        return this;
+    }
+
+    public MessageHeaderSectionMatcher withPriority(Matcher<?> m)
+    {
+        getMatchers().put(Field.PRIORITY, m);
+        return this;
+    }
+
+    public MessageHeaderSectionMatcher withTtl(Matcher<?> m)
+    {
+        getMatchers().put(Field.TTL, m);
+        return this;
+    }
+
+    public MessageHeaderSectionMatcher withFirstAcquirer(Matcher<?> m)
+    {
+        getMatchers().put(Field.FIRST_ACQUIRER, m);
+        return this;
+    }
+
+    public MessageHeaderSectionMatcher withDeliveryCount(Matcher<?> m)
+    {
+        getMatchers().put(Field.DELIVERY_COUNT, m);
+        return this;
+    }
+
+    public Object getReceivedDurable()
+    {
+        return getReceivedFields().get(Field.DURABLE);
+    }
+
+    public Object getReceivedPriority()
+    {
+        return getReceivedFields().get(Field.PRIORITY);
+    }
+
+    public Object getReceivedTtl()
+    {
+        return getReceivedFields().get(Field.TTL);
+    }
+
+    public Object getReceivedFirstAcquirer()
+    {
+        return getReceivedFields().get(Field.FIRST_ACQUIRER);
+    }
+
+    public Object getReceivedDeliveryCount()
+    {
+        return getReceivedFields().get(Field.DELIVERY_COUNT);
+    }
+
+    @Override
+    protected Enum<?> getField(int fieldIndex)
+    {
+        return Field.values()[fieldIndex];
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/e4decdc1/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/sections/MessageListSectionMatcher.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/sections/MessageListSectionMatcher.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/sections/MessageListSectionMatcher.java
new file mode 100644
index 0000000..01d8ab0
--- /dev/null
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/sections/MessageListSectionMatcher.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.jms.test.testpeer.matchers.sections;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.qpid.proton.amqp.Symbol;
+import org.apache.qpid.proton.amqp.UnsignedLong;
+import org.hamcrest.Matcher;
+
+public abstract class MessageListSectionMatcher extends AbstractMessageSectionMatcher
+{
+    public MessageListSectionMatcher(UnsignedLong numericDescriptor,
+                                            Symbol symbolicDescriptor,
+                                            Map<Object, Matcher<?>> fieldMatchers,
+                                            boolean expectTrailingBytes)
+    {
+        super(numericDescriptor, symbolicDescriptor, fieldMatchers, expectTrailingBytes);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    protected void verifyReceivedDescribedObject(Object described)
+    {
+        if(!(described instanceof List))
+        {
+            throw new IllegalArgumentException("Unexpected section contents. Expected List, but got: "
+                                              + (described == null ? "null" : described.getClass()));
+        }
+
+        int fieldNumber = 0;
+        Map<Object, Object> valueMap = new HashMap<Object, Object>();
+        for(Object value : (List<Object>)described)
+        {
+            valueMap.put(getField(fieldNumber++), value);
+        }
+
+        verifyReceivedFields(valueMap);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/e4decdc1/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/sections/MessageMapSectionMatcher.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/sections/MessageMapSectionMatcher.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/sections/MessageMapSectionMatcher.java
new file mode 100644
index 0000000..be3f0a4
--- /dev/null
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/sections/MessageMapSectionMatcher.java
@@ -0,0 +1,55 @@
+/*
+ * 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.jms.test.testpeer.matchers.sections;
+
+import java.util.Map;
+
+import org.apache.qpid.proton.amqp.Symbol;
+import org.apache.qpid.proton.amqp.UnsignedLong;
+import org.hamcrest.Matcher;
+
+public abstract class MessageMapSectionMatcher extends AbstractMessageSectionMatcher
+{
+    public MessageMapSectionMatcher(UnsignedLong numericDescriptor,
+                                            Symbol symbolicDescriptor,
+                                            Map<Object, Matcher<?>> fieldMatchers,
+                                            boolean expectTrailingBytes)
+    {
+        super(numericDescriptor, symbolicDescriptor, fieldMatchers, expectTrailingBytes);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    protected void verifyReceivedDescribedObject(Object described)
+    {
+        if(!(described instanceof Map))
+        {
+            throw new IllegalArgumentException("Unexpected section contents. Expected Map, but got: "
+                                              + (described == null ? "null" : described.getClass()));
+        }
+
+        verifyReceivedFields((Map<Object,Object>) described);
+    }
+
+    public MessageMapSectionMatcher withEntry(Object key, Matcher<?> m)
+    {
+        getMatchers().put(key, m);
+        return this;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/e4decdc1/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/sections/MessagePropertiesSectionMatcher.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/sections/MessagePropertiesSectionMatcher.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/sections/MessagePropertiesSectionMatcher.java
new file mode 100644
index 0000000..35a523a
--- /dev/null
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/sections/MessagePropertiesSectionMatcher.java
@@ -0,0 +1,214 @@
+/*
+ * 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.jms.test.testpeer.matchers.sections;
+
+import java.util.HashMap;
+import org.apache.qpid.proton.amqp.Symbol;
+import org.apache.qpid.proton.amqp.UnsignedLong;
+import org.apache.qpid.jms.test.testpeer.matchers.sections.MessageListSectionMatcher;
+import org.hamcrest.Matcher;
+
+/**
+ * Generated by generate-message-section-matchers.xsl, which resides in this package.
+ */
+public class MessagePropertiesSectionMatcher extends MessageListSectionMatcher
+{
+
+    public static final Symbol DESCRIPTOR_SYMBOL = Symbol.valueOf("amqp:properties:list");
+    public static final UnsignedLong DESCRIPTOR_CODE = UnsignedLong.valueOf(0x0000000000000073L);
+
+    /** Note that the ordinals of the Field enums match the order specified in the AMQP spec */
+    public enum Field
+    {
+        MESSAGE_ID,
+        USER_ID,
+        TO,
+        SUBJECT,
+        REPLY_TO,
+        CORRELATION_ID,
+        CONTENT_TYPE,
+        CONTENT_ENCODING,
+        ABSOLUTE_EXPIRY_TIME,
+        CREATION_TIME,
+        GROUP_ID,
+        GROUP_SEQUENCE,
+        REPLY_TO_GROUP_ID,
+    }
+
+    public MessagePropertiesSectionMatcher(boolean expectTrailingBytes)
+    {
+        super(DESCRIPTOR_CODE,
+              DESCRIPTOR_SYMBOL,
+              new HashMap<Object, Matcher<?>>(),
+              expectTrailingBytes);
+    }
+
+
+    public MessagePropertiesSectionMatcher withMessageId(Matcher<?> m)
+    {
+        getMatchers().put(Field.MESSAGE_ID, m);
+        return this;
+    }
+
+    public MessagePropertiesSectionMatcher withUserId(Matcher<?> m)
+    {
+        getMatchers().put(Field.USER_ID, m);
+        return this;
+    }
+
+    public MessagePropertiesSectionMatcher withTo(Matcher<?> m)
+    {
+        getMatchers().put(Field.TO, m);
+        return this;
+    }
+
+    public MessagePropertiesSectionMatcher withSubject(Matcher<?> m)
+    {
+        getMatchers().put(Field.SUBJECT, m);
+        return this;
+    }
+
+    public MessagePropertiesSectionMatcher withReplyTo(Matcher<?> m)
+    {
+        getMatchers().put(Field.REPLY_TO, m);
+        return this;
+    }
+
+    public MessagePropertiesSectionMatcher withCorrelationId(Matcher<?> m)
+    {
+        getMatchers().put(Field.CORRELATION_ID, m);
+        return this;
+    }
+
+    public MessagePropertiesSectionMatcher withContentType(Matcher<?> m)
+    {
+        getMatchers().put(Field.CONTENT_TYPE, m);
+        return this;
+    }
+
+    public MessagePropertiesSectionMatcher withContentEncoding(Matcher<?> m)
+    {
+        getMatchers().put(Field.CONTENT_ENCODING, m);
+        return this;
+    }
+
+    public MessagePropertiesSectionMatcher withAbsoluteExpiryTime(Matcher<?> m)
+    {
+        getMatchers().put(Field.ABSOLUTE_EXPIRY_TIME, m);
+        return this;
+    }
+
+    public MessagePropertiesSectionMatcher withCreationTime(Matcher<?> m)
+    {
+        getMatchers().put(Field.CREATION_TIME, m);
+        return this;
+    }
+
+    public MessagePropertiesSectionMatcher withGroupId(Matcher<?> m)
+    {
+        getMatchers().put(Field.GROUP_ID, m);
+        return this;
+    }
+
+    public MessagePropertiesSectionMatcher withGroupSequence(Matcher<?> m)
+    {
+        getMatchers().put(Field.GROUP_SEQUENCE, m);
+        return this;
+    }
+
+    public MessagePropertiesSectionMatcher withReplyToGroupId(Matcher<?> m)
+    {
+        getMatchers().put(Field.REPLY_TO_GROUP_ID, m);
+        return this;
+    }
+
+    public Object getReceivedMessageId()
+    {
+        return getReceivedFields().get(Field.MESSAGE_ID);
+    }
+
+    public Object getReceivedUserId()
+    {
+        return getReceivedFields().get(Field.USER_ID);
+    }
+
+    public Object getReceivedTo()
+    {
+        return getReceivedFields().get(Field.TO);
+    }
+
+    public Object getReceivedSubject()
+    {
+        return getReceivedFields().get(Field.SUBJECT);
+    }
+
+    public Object getReceivedReplyTo()
+    {
+        return getReceivedFields().get(Field.REPLY_TO);
+    }
+
+    public Object getReceivedCorrelationId()
+    {
+        return getReceivedFields().get(Field.CORRELATION_ID);
+    }
+
+    public Object getReceivedContentType()
+    {
+        return getReceivedFields().get(Field.CONTENT_TYPE);
+    }
+
+    public Object getReceivedContentEncoding()
+    {
+        return getReceivedFields().get(Field.CONTENT_ENCODING);
+    }
+
+    public Object getReceivedAbsoluteExpiryTime()
+    {
+        return getReceivedFields().get(Field.ABSOLUTE_EXPIRY_TIME);
+    }
+
+    public Object getReceivedCreationTime()
+    {
+        return getReceivedFields().get(Field.CREATION_TIME);
+    }
+
+    public Object getReceivedGroupId()
+    {
+        return getReceivedFields().get(Field.GROUP_ID);
+    }
+
+    public Object getReceivedGroupSequence()
+    {
+        return getReceivedFields().get(Field.GROUP_SEQUENCE);
+    }
+
+    public Object getReceivedReplyToGroupId()
+    {
+        return getReceivedFields().get(Field.REPLY_TO_GROUP_ID);
+    }
+
+    @Override
+    protected Enum<?> getField(int fieldIndex)
+    {
+        return Field.values()[fieldIndex];
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/e4decdc1/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/sections/TransferPayloadCompositeMatcher.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/sections/TransferPayloadCompositeMatcher.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/sections/TransferPayloadCompositeMatcher.java
new file mode 100644
index 0000000..a49b8a4
--- /dev/null
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/sections/TransferPayloadCompositeMatcher.java
@@ -0,0 +1,224 @@
+/*
+ *
+ * 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.jms.test.testpeer.matchers.sections;
+
+
+import org.apache.qpid.proton.amqp.Binary;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.StringDescription;
+import org.hamcrest.TypeSafeMatcher;
+
+/**
+ * Used to verify the Transfer frame payload, i.e the sections of the AMQP message
+ * such as the header, properties, and body sections.
+ */
+public class TransferPayloadCompositeMatcher extends TypeSafeMatcher<Binary>
+{
+    private MessageHeaderSectionMatcher _msgHeadersMatcher;
+    private String _msgHeaderMatcherFailureDescription;
+
+    private MessageAnnotationsSectionMatcher _msgAnnotationsMatcher;
+    private String _msgAnnotationsMatcherFailureDescription;
+    private MessagePropertiesSectionMatcher _propsMatcher;
+    private String _propsMatcherFailureDescription;
+    private Matcher<Binary> _msgContentMatcher;
+    private String _msgContentMatcherFailureDescription;
+    private ApplicationPropertiesSectionMatcher _appPropsMatcher;
+    private String _appPropsMatcherFailureDescription;
+
+    public TransferPayloadCompositeMatcher()
+    {
+    }
+
+    @Override
+    protected boolean matchesSafely(final Binary receivedBinary)
+    {
+        int origLength = receivedBinary.getLength();
+        int bytesConsumed = 0;
+
+        //MessageHeader Section
+        if(_msgHeadersMatcher != null)
+        {
+            Binary msgHeaderEtcSubBinary = receivedBinary.subBinary(bytesConsumed, origLength - bytesConsumed);
+            try
+            {
+                bytesConsumed += _msgHeadersMatcher.verify(msgHeaderEtcSubBinary);
+            }
+            catch(Throwable t)
+            {
+                _msgHeaderMatcherFailureDescription = "\nActual encoded form of remaining bytes passed to MessageHeaderMatcher: " + msgHeaderEtcSubBinary;
+                _msgHeaderMatcherFailureDescription += "\nMessageHeaderMatcher generated throwable: " + t;
+
+                return false;
+            }
+        }
+
+        //MessageAnnotations Section
+        if(_msgAnnotationsMatcher != null)
+        {
+            Binary msgAnnotationsEtcSubBinary = receivedBinary.subBinary(bytesConsumed, origLength - bytesConsumed);
+            try
+            {
+                bytesConsumed += _msgAnnotationsMatcher.verify(msgAnnotationsEtcSubBinary);
+            }
+            catch(Throwable t)
+            {
+                _msgAnnotationsMatcherFailureDescription = "\nActual encoded form of remaining bytes passed to MessageAnnotationsMatcher: " + msgAnnotationsEtcSubBinary;
+                _msgAnnotationsMatcherFailureDescription += "\nMessageAnnotationsMatcher generated throwable: " + t;
+
+                return false;
+            }
+        }
+
+        //Properties Section
+        if(_propsMatcher != null)
+        {
+            Binary propsEtcSubBinary = receivedBinary.subBinary(bytesConsumed, origLength - bytesConsumed);
+            try
+            {
+                bytesConsumed += _propsMatcher.verify(propsEtcSubBinary);
+            }
+            catch(Throwable t)
+            {
+                _propsMatcherFailureDescription = "\nActual encoded form of remaining bytes passed to PropertiesMatcher: " + propsEtcSubBinary;
+                _propsMatcherFailureDescription += "\nPropertiesMatcher generated throwable: " + t;
+
+                return false;
+            }
+        }
+
+        //Application Properties Section
+        if(_appPropsMatcher != null)
+        {
+            Binary appPropsEtcSubBinary = receivedBinary.subBinary(bytesConsumed, origLength - bytesConsumed);
+            try
+            {
+                bytesConsumed += _appPropsMatcher.verify(appPropsEtcSubBinary);
+            }
+            catch(Throwable t)
+            {
+                _appPropsMatcherFailureDescription = "\nActual encoded form of remaining bytes passed to ApplicationPropertiesMatcher: " + appPropsEtcSubBinary;
+                _appPropsMatcherFailureDescription += "\nApplicationPropertiesMatcher generated throwable: " + t;
+
+                return false;
+            }
+        }
+        //Message Content Body Section, already a Matcher<Binary>
+        if(_msgContentMatcher != null)
+        {
+            Binary msgContentBodyEtcSubBinary = receivedBinary.subBinary(bytesConsumed, origLength - bytesConsumed);
+            boolean contentMatches = _msgContentMatcher.matches(msgContentBodyEtcSubBinary);
+            if(!contentMatches)
+            {
+                Description desc = new StringDescription();
+                _msgContentMatcher.describeTo(desc);
+                _msgContentMatcher.describeMismatch(msgContentBodyEtcSubBinary, desc);
+
+                _msgContentMatcherFailureDescription = "\nMessageContentMatcher mismatch Description:";
+                _msgContentMatcherFailureDescription += desc.toString();
+
+                return false;
+            }
+        }
+
+        //TODO: we will need figure out a way to determine how many bytes the
+        //MessageContentMatcher did/should consume when it comes time to handle footers
+        return true;
+    }
+
+    @Override
+    public void describeTo(Description description)
+    {
+        description.appendText("a Binary encoding of a Transfer frames payload, containing an AMQP message");
+    }
+
+    @Override
+    protected void describeMismatchSafely(Binary item, Description mismatchDescription)
+    {
+        mismatchDescription.appendText("\nActual encoded form of the full Transfer frame payload: ").appendValue(item);
+
+        //MessageHeaders Section
+        if(_msgHeaderMatcherFailureDescription != null)
+        {
+            mismatchDescription.appendText("\nMessageHeadersMatcherFailed!");
+            mismatchDescription.appendText(_msgHeaderMatcherFailureDescription);
+            return;
+        }
+
+        //MessageAnnotations Section
+        if(_msgAnnotationsMatcherFailureDescription != null)
+        {
+            mismatchDescription.appendText("\nMessageAnnotationsMatcherFailed!");
+            mismatchDescription.appendText(_msgAnnotationsMatcherFailureDescription);
+            return;
+        }
+
+        //Properties Section
+        if(_propsMatcherFailureDescription != null)
+        {
+            mismatchDescription.appendText("\nPropertiesMatcherFailed!");
+            mismatchDescription.appendText(_propsMatcherFailureDescription);
+            return;
+        }
+
+        //Application Properties Section
+        if(_appPropsMatcherFailureDescription != null)
+        {
+            mismatchDescription.appendText("\nApplicationPropertiesMatcherFailed!");
+            mismatchDescription.appendText(_appPropsMatcherFailureDescription);
+            return;
+        }
+
+        //Message Content Body Section
+        if(_msgContentMatcherFailureDescription != null)
+        {
+            mismatchDescription.appendText("\nContentMatcherFailed!");
+            mismatchDescription.appendText(_msgContentMatcherFailureDescription);
+            return;
+        }
+    }
+
+    public void setHeadersMatcher(MessageHeaderSectionMatcher msgHeadersMatcher)
+    {
+        _msgHeadersMatcher = msgHeadersMatcher;
+    }
+
+    public void setMessageAnnotationsMatcher(MessageAnnotationsSectionMatcher msgAnnotationsMatcher)
+    {
+        _msgAnnotationsMatcher = msgAnnotationsMatcher;
+    }
+
+    public void setPropertiesMatcher(MessagePropertiesSectionMatcher propsMatcher)
+    {
+        _propsMatcher = propsMatcher;
+    }
+
+    public void setApplicationPropertiesMatcher(ApplicationPropertiesSectionMatcher appPropsMatcher)
+    {
+        _appPropsMatcher = appPropsMatcher;
+    }
+
+    public void setMessageContentMatcher(Matcher<Binary> msgContentMatcher)
+    {
+        _msgContentMatcher = msgContentMatcher;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/e4decdc1/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/sections/generate-message-section-matchers.xsl
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/sections/generate-message-section-matchers.xsl b/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/sections/generate-message-section-matchers.xsl
new file mode 100644
index 0000000..22277d3
--- /dev/null
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/sections/generate-message-section-matchers.xsl
@@ -0,0 +1,166 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
+                xmlns:exsl="http://exslt.org/common"
+                extension-element-prefixes="exsl">
+
+<!-- Used to generate the Java classes in this package.
+     Changes to these classes should be effected by modifying this stylesheet then re-running it,
+     using a stylesheet processor that understands the exsl directives such as xsltproc -->
+
+<xsl:template match="/">
+    <xsl:variable name="license">/*
+ * 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.
+ *
+ */
+</xsl:variable>
+
+    <xsl:for-each select="descendant-or-self::node()[name()='type']">
+        <xsl:variable name="classname">Message<xsl:call-template name="dashToCamel"><xsl:with-param name="input" select="@name"/></xsl:call-template>SectionMatcher</xsl:variable>
+        <xsl:variable name="superclass">
+            <xsl:choose>
+                <xsl:when test="@name = 'header' or @name='properties'">MessageListSectionMatcher</xsl:when>
+                <xsl:otherwise>NotYetImplemented</xsl:otherwise>
+            </xsl:choose>
+        </xsl:variable>
+
+        <xsl:if test="@provides = 'section'">
+            <xsl:if test="@name = 'header' or @name='properties'">
+              <xsl:call-template name="typeClass">
+                  <xsl:with-param name="license" select="$license"/>
+                  <xsl:with-param name="classname" select="$classname"/>
+                  <xsl:with-param name="superclass" select="$superclass"/>
+              </xsl:call-template>
+            </xsl:if>
+        </xsl:if>
+
+    </xsl:for-each>
+</xsl:template>
+
+
+<!-- *************************************************************************************************************** -->
+
+<xsl:template name="typeClass">
+    <xsl:param name="license"/>
+    <xsl:param name="classname"/>
+    <xsl:param name="superclass"/>
+  <exsl:document href="{$classname}.java" method="text">
+  <xsl:value-of select="$license"/>
+package org.apache.qpid.jms.test.testpeer.matchers.sections;
+
+import java.util.HashMap;
+import org.apache.qpid.proton.amqp.Symbol;
+import org.apache.qpid.proton.amqp.UnsignedLong;
+import org.apache.qpid.jms.test.testpeer.matchers.sections.<xsl:value-of select="$superclass"/>;
+import org.hamcrest.Matcher;
+
+/**
+ * Generated by generate-message-section-matchers.xsl, which resides in this package.
+ */
+public class <xsl:value-of select="$classname"/> extends <xsl:value-of select="$superclass"/>
+{
+
+    public static final Symbol DESCRIPTOR_SYMBOL = Symbol.valueOf("<xsl:value-of select="descendant::node()[name()='descriptor']/@name"/>");
+    public static final UnsignedLong DESCRIPTOR_CODE = UnsignedLong.valueOf(<xsl:value-of select="concat(substring(descendant::node()[name()='descriptor']/@code,1,10),substring(descendant::node()[name()='descriptor']/@code,14))"/>L);
+
+    /** Note that the ordinals of the Field enums match the order specified in the AMQP spec */
+    public enum Field
+    {
+<xsl:for-each select="descendant::node()[name()='field']">
+<xsl:text>        </xsl:text><xsl:call-template name="toUpperDashToUnderscore"><xsl:with-param name="input" select="@name"/></xsl:call-template>,
+</xsl:for-each>    }
+
+    public <xsl:value-of select="$classname"/>(boolean expectTrailingBytes)
+    {
+        super(DESCRIPTOR_CODE,
+              DESCRIPTOR_SYMBOL,
+              new HashMap&lt;Object, Matcher&lt;?&gt;&gt;(),
+              expectTrailingBytes);
+    }
+
+<xsl:for-each select="descendant::node()[name()='field']">
+    public <xsl:value-of select="$classname"/> with<xsl:call-template name="dashToCamel"><xsl:with-param name="input" select="@name"/></xsl:call-template>(Matcher&lt;?&gt; m)
+    {
+        getMatchers().put(Field.<xsl:call-template name="toUpperDashToUnderscore"><xsl:with-param name="input" select="@name"/></xsl:call-template>, m);
+        return this;
+    }
+</xsl:for-each>
+<xsl:for-each select="descendant::node()[name()='field']">
+    public Object getReceived<xsl:call-template name="dashToCamel"><xsl:with-param name="input" select="@name"/></xsl:call-template>()
+    {
+        return getReceivedFields().get(Field.<xsl:call-template name="toUpperDashToUnderscore"><xsl:with-param name="input" select="@name"/></xsl:call-template>);
+    }
+</xsl:for-each>
+    @Override
+    protected Enum&lt;?&gt; getField(int fieldIndex)
+    {
+        return Field.values()[fieldIndex];
+    }
+}
+
+</exsl:document>
+
+</xsl:template>
+
+<!-- *************************************************************************************************************** -->
+
+<xsl:template name="constructFromLiteral">
+    <xsl:param name="type"/>
+    <xsl:param name="value"/>
+    <xsl:choose>
+        <xsl:when test="$type = 'string'">"<xsl:value-of select="$value"/></xsl:when>
+        <xsl:when test="$type = 'symbol'">Symbol.valueOf("<xsl:value-of select="$value"/>")</xsl:when>
+        <xsl:when test="$type = 'ubyte'">UnsignedByte.valueOf((byte) <xsl:value-of select="$value"/>)</xsl:when>
+        <xsl:when test="$type = 'ushort'">UnsignedShort.valueOf((short) <xsl:value-of select="$value"/>)</xsl:when>
+        <xsl:when test="$type = 'uint'">UnsignedInteger.valueOf(<xsl:value-of select="$value"/>)</xsl:when>
+        <xsl:when test="$type = 'ulong'">UnsignedLong.valueOf(<xsl:value-of select="$value"/>L)</xsl:when>
+        <xsl:when test="$type = 'long'"><xsl:value-of select="$value"/>L</xsl:when>
+        <xsl:when test="$type = 'short'">(short)<xsl:value-of select="$value"/></xsl:when>
+        <xsl:when test="$type = 'short'">(byte)<xsl:value-of select="$value"/></xsl:when>
+        <xsl:otherwise><xsl:value-of select="$value"/></xsl:otherwise>
+    </xsl:choose>
+</xsl:template>
+
+<!-- *************************************************************************************************************** -->
+<xsl:template name="substringAfterLast"><xsl:param name="input"/><xsl:param name="arg"/>
+        <xsl:choose>
+            <xsl:when test="contains($input,$arg)"><xsl:call-template name="substringAfterLast"><xsl:with-param name="input"><xsl:value-of select="substring-after($input,$arg)"/></xsl:with-param><xsl:with-param name="arg"><xsl:value-of select="$arg"/></xsl:with-param></xsl:call-template></xsl:when>
+            <xsl:otherwise><xsl:value-of select="$input"/></xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+    <xsl:template name="initCap"><xsl:param name="input"/><xsl:value-of select="translate(substring($input,1,1),'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/><xsl:value-of select="substring($input,2)"/></xsl:template>
+
+    <xsl:template name="initLower"><xsl:param name="input"/><xsl:value-of select="translate(substring($input,1,1),'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')"/><xsl:value-of select="substring($input,2)"/></xsl:template>
+
+    <xsl:template name="toUpper"><xsl:param name="input"/><xsl:value-of select="translate($input,'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/></xsl:template>
+
+    <xsl:template name="toUpperDashToUnderscore"><xsl:param name="input"/><xsl:value-of select="translate($input,'abcdefghijklmnopqrstuvwxyz-','ABCDEFGHIJKLMNOPQRSTUVWXYZ_')"/></xsl:template>
+
+    <xsl:template name="dashToCamel">
+        <xsl:param name="input"/>
+        <xsl:choose>
+            <xsl:when test="contains($input,'-')"><xsl:call-template name="initCap"><xsl:with-param name="input" select="substring-before($input,'-')"/></xsl:call-template><xsl:call-template name="dashToCamel"><xsl:with-param name="input" select="substring-after($input,'-')"/></xsl:call-template></xsl:when>
+            <xsl:otherwise><xsl:call-template name="initCap"><xsl:with-param name="input" select="$input"/></xsl:call-template></xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+    <xsl:template name="dashToLowerCamel">
+        <xsl:param name="input"/>
+        <xsl:call-template name="initLower"><xsl:with-param name="input"><xsl:call-template name="dashToCamel"><xsl:with-param name="input" select="$input"/></xsl:call-template></xsl:with-param></xsl:call-template>
+    </xsl:template>
+</xsl:stylesheet>

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/e4decdc1/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/types/EncodedAmqpTypeMatcher.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/types/EncodedAmqpTypeMatcher.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/types/EncodedAmqpTypeMatcher.java
new file mode 100644
index 0000000..797ee4a
--- /dev/null
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/types/EncodedAmqpTypeMatcher.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.qpid.jms.test.testpeer.matchers.types;
+
+import org.apache.qpid.proton.Proton;
+import org.apache.qpid.proton.amqp.Binary;
+import org.apache.qpid.proton.amqp.DescribedType;
+import org.apache.qpid.proton.amqp.Symbol;
+import org.apache.qpid.proton.amqp.UnsignedLong;
+import org.apache.qpid.proton.codec.Data;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+
+public abstract class EncodedAmqpTypeMatcher extends TypeSafeMatcher<Binary>
+{
+    private final Symbol _descriptorSymbol;
+    private final UnsignedLong _descriptorCode;
+    private final Object _expectedValue;
+    private boolean _permitTrailingBytes;
+    private DescribedType _decodedDescribedType;
+    private boolean _unexpectedTrailingBytes;
+
+    public EncodedAmqpTypeMatcher(Symbol symbol, UnsignedLong code, Object expectedValue)
+    {
+        this(symbol, code, expectedValue, false);
+    }
+
+    public EncodedAmqpTypeMatcher(Symbol symbol, UnsignedLong code, Object expectedValue, boolean permitTrailingBytes)
+    {
+        _descriptorSymbol = symbol;
+        _descriptorCode = code;
+        _expectedValue = expectedValue;
+        _permitTrailingBytes = permitTrailingBytes;
+    }
+
+    protected Object getExpectedValue()
+    {
+        return _expectedValue;
+    }
+
+    @Override
+    protected boolean matchesSafely(Binary receivedBinary)
+    {
+        int length = receivedBinary.getLength();
+        Data data = Proton.data(length);
+        long decoded = data.decode(receivedBinary.asByteBuffer());
+        _decodedDescribedType = data.getDescribedType();
+        Object descriptor = _decodedDescribedType.getDescriptor();
+
+        if(!(_descriptorCode.equals(descriptor) || _descriptorSymbol.equals(descriptor)))
+        {
+            return false;
+        }
+
+        if(_expectedValue == null && _decodedDescribedType.getDescribed() != null)
+        {
+            return false;
+        }
+        else if(_expectedValue != null && !_expectedValue.equals(_decodedDescribedType.getDescribed()))
+        {
+            return false;
+        }
+
+        if(decoded < length && !_permitTrailingBytes)
+        {
+            _unexpectedTrailingBytes = true;
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    protected void describeMismatchSafely(Binary item, Description mismatchDescription)
+    {
+        mismatchDescription.appendText("\nActual encoded form: ").appendValue(item);
+
+        if(_decodedDescribedType != null)
+        {
+            mismatchDescription.appendText("\nExpected descriptor: ")
+                .appendValue(_descriptorSymbol)
+                .appendText(" / ")
+                .appendValue(_descriptorCode);
+
+            mismatchDescription.appendText("\nActual described type: ").appendValue(_decodedDescribedType);
+        }
+
+        if(_unexpectedTrailingBytes)
+        {
+            mismatchDescription.appendText("\nUnexpected trailing bytes in provided bytes after decoding!");
+        }
+    }
+
+    /**
+     * Provide a description of this matcher.
+     */
+    public abstract void describeTo(Description description);
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/e4decdc1/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/types/EncodedAmqpValueMatcher.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/types/EncodedAmqpValueMatcher.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/types/EncodedAmqpValueMatcher.java
new file mode 100644
index 0000000..93dcc36
--- /dev/null
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/types/EncodedAmqpValueMatcher.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.qpid.jms.test.testpeer.matchers.types;
+
+import org.apache.qpid.proton.amqp.Symbol;
+import org.apache.qpid.proton.amqp.UnsignedLong;
+import org.hamcrest.Description;
+
+public class EncodedAmqpValueMatcher extends EncodedAmqpTypeMatcher
+{
+    private static final Symbol DESCRIPTOR_SYMBOL = Symbol.valueOf("amqp:amqp-value:*");
+    private static final UnsignedLong DESCRIPTOR_CODE = UnsignedLong.valueOf(0x0000000000000077L);
+
+    /**
+     * @param expectedValue the value that is expected to be IN the
+     * received {@link AmqpValue}
+     */
+    public EncodedAmqpValueMatcher(Object expectedValue)
+    {
+        this(expectedValue,false);
+    }
+
+    /**
+     * @param expectedValue the value that is expected to be IN the
+     * received {@link AmqpValue}
+     * @param permitTrailingBytes if it is permitted for bytes to be left in the Binary after consuming the {@link AmqpValue}
+     */
+    public EncodedAmqpValueMatcher(Object expectedValue, boolean permitTrailingBytes)
+    {
+        super(DESCRIPTOR_SYMBOL, DESCRIPTOR_CODE, expectedValue, permitTrailingBytes);
+    }
+
+    @Override
+    public void describeTo(Description description)
+    {
+        description
+            .appendText("a Binary encoding of an AmqpValue that wraps: ")
+            .appendValue(getExpectedValue());
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/e4decdc1/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/types/EncodedDataMatcher.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/types/EncodedDataMatcher.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/types/EncodedDataMatcher.java
new file mode 100644
index 0000000..94cea5e
--- /dev/null
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/matchers/types/EncodedDataMatcher.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.jms.test.testpeer.matchers.types;
+
+import org.apache.qpid.proton.amqp.Binary;
+import org.apache.qpid.proton.amqp.Symbol;
+import org.apache.qpid.proton.amqp.UnsignedLong;
+import org.hamcrest.Description;
+
+public class EncodedDataMatcher extends EncodedAmqpTypeMatcher
+{
+    private static final Symbol DESCRIPTOR_SYMBOL = Symbol.valueOf("amqp:data:binary");
+    private static final UnsignedLong DESCRIPTOR_CODE = UnsignedLong.valueOf(0x0000000000000075L);
+
+    /**
+     * @param expectedValue the value that is expected to be IN the
+     * received {@link org.apache.qpid.proton.amqp.messaging.Data}
+     */
+    public EncodedDataMatcher(Binary expectedValue)
+    {
+        this(expectedValue, false);
+    }
+
+    /**
+     * @param expectedValue the value that is expected to be IN the
+     * received {@link org.apache.qpid.proton.amqp.messaging.Data}
+     * @param permitTrailingBytes if it is permitted for bytes to be left in the Binary after consuming the {@link AmqpValue}
+     */
+    public EncodedDataMatcher(Binary expectedValue, boolean permitTrailingBytes)
+    {
+        super(DESCRIPTOR_SYMBOL, DESCRIPTOR_CODE, expectedValue, permitTrailingBytes);
+    }
+
+    @Override
+    public void describeTo(Description description)
+    {
+        description
+            .appendText("a Binary encoding of a Data that wraps a Binary containing: ")
+            .appendValue(getExpectedValue());
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/e4decdc1/qpid-jms-client/src/test/java/org/apache/qpid/jms/util/URISupportTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/util/URISupportTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/util/URISupportTest.java
new file mode 100644
index 0000000..7459e82
--- /dev/null
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/util/URISupportTest.java
@@ -0,0 +1,216 @@
+/**
+ * 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.jms.util;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.qpid.jms.util.PropertyUtil;
+import org.apache.qpid.jms.util.URISupport;
+import org.apache.qpid.jms.util.URISupport.CompositeData;
+
+import junit.framework.TestCase;
+
+public class URISupportTest extends TestCase {
+
+    public void testEmptyCompositePath() throws Exception {
+        CompositeData data = URISupport.parseComposite(new URI("broker:()/localhost?persistent=false"));
+        assertEquals(0, data.getComponents().length);
+    }
+
+    public void testCompositePath() throws Exception {
+        CompositeData data = URISupport.parseComposite(new URI("test:(path)/path"));
+        assertEquals("path", data.getPath());
+        data = URISupport.parseComposite(new URI("test:path"));
+        assertNull(data.getPath());
+    }
+
+    public void testSimpleComposite() throws Exception {
+        CompositeData data = URISupport.parseComposite(new URI("test:part1"));
+        assertEquals(1, data.getComponents().length);
+    }
+
+    public void testComposite() throws Exception {
+        URI uri = new URI("test:(part1://host,part2://(sub1://part,sube2:part))");
+        CompositeData data = URISupport.parseComposite(uri);
+        assertEquals(2, data.getComponents().length);
+    }
+
+    public void testEmptyCompositeWithParenthesisInParam() throws Exception {
+        URI uri = new URI("failover://()?updateURIsURL=file:/C:/Dir(1)/a.csv");
+        CompositeData data = URISupport.parseComposite(uri);
+        assertEquals(0, data.getComponents().length);
+        assertEquals(1, data.getParameters().size());
+        assertTrue(data.getParameters().containsKey("updateURIsURL"));
+        assertEquals("file:/C:/Dir(1)/a.csv", data.getParameters().get("updateURIsURL"));
+    }
+
+    public void testCompositeWithParenthesisInParam() throws Exception {
+        URI uri = new URI("failover://(test)?updateURIsURL=file:/C:/Dir(1)/a.csv");
+        CompositeData data = URISupport.parseComposite(uri);
+        assertEquals(1, data.getComponents().length);
+        assertEquals(1, data.getParameters().size());
+        assertTrue(data.getParameters().containsKey("updateURIsURL"));
+        assertEquals("file:/C:/Dir(1)/a.csv", data.getParameters().get("updateURIsURL"));
+    }
+
+    public void testCompositeWithComponentParam() throws Exception {
+        CompositeData data = URISupport.parseComposite(new URI("test:(part1://host?part1=true)?outside=true"));
+        assertEquals(1, data.getComponents().length);
+        assertEquals(1, data.getParameters().size());
+        Map<String, String> part1Params = URISupport.parseParameters(data.getComponents()[0]);
+        assertEquals(1, part1Params.size());
+        assertTrue(part1Params.containsKey("part1"));
+    }
+
+    public void testParsingURI() throws Exception {
+        URI source = new URI("tcp://localhost:61626/foo/bar?cheese=Edam&x=123");
+
+        Map<String, String> map = PropertyUtil.parseParameters(source);
+
+        assertEquals("Size: " + map, 2, map.size());
+        assertMapKey(map, "cheese", "Edam");
+        assertMapKey(map, "x", "123");
+
+        URI result = URISupport.removeQuery(source);
+
+        assertEquals("result", new URI("tcp://localhost:61626/foo/bar"), result);
+    }
+
+    protected void assertMapKey(Map<String, String> map, String key, Object expected) {
+        assertEquals("Map key: " + key, map.get(key), expected);
+    }
+
+    public void testParsingCompositeURI() throws URISyntaxException {
+        CompositeData data = URISupport.parseComposite(new URI("broker://(tcp://localhost:61616)?name=foo"));
+        assertEquals("one component", 1, data.getComponents().length);
+        assertEquals("Size: " + data.getParameters(), 1, data.getParameters().size());
+    }
+
+    public void testCheckParenthesis() throws Exception {
+        String str = "fred:(((ddd))";
+        assertFalse(URISupport.checkParenthesis(str));
+        str += ")";
+        assertTrue(URISupport.checkParenthesis(str));
+    }
+
+    public void testCreateWithQuery() throws Exception {
+        URI source = new URI("vm://localhost");
+        URI dest = PropertyUtil.replaceQuery(source, "network=true&one=two");
+
+        assertEquals("correct param count", 2, URISupport.parseParameters(dest).size());
+        assertEquals("same uri, host", source.getHost(), dest.getHost());
+        assertEquals("same uri, scheme", source.getScheme(), dest.getScheme());
+        assertFalse("same uri, ssp", dest.getQuery().equals(source.getQuery()));
+    }
+
+    public void testParsingParams() throws Exception {
+        URI uri = new URI("static:(http://localhost:61617?proxyHost=jo&proxyPort=90)?proxyHost=localhost&proxyPort=80");
+        Map<String,String>parameters = URISupport.parseParameters(uri);
+        verifyParams(parameters);
+        uri = new URI("static://http://localhost:61617?proxyHost=localhost&proxyPort=80");
+        parameters = URISupport.parseParameters(uri);
+        verifyParams(parameters);
+        uri = new URI("http://0.0.0.0:61616");
+        parameters = URISupport.parseParameters(uri);
+    }
+
+    public void testCompositeCreateURIWithQuery() throws Exception {
+        String queryString = "query=value";
+        URI originalURI = new URI("outerscheme:(innerscheme:innerssp)");
+        URI querylessURI = originalURI;
+        assertEquals(querylessURI, PropertyUtil.eraseQuery(originalURI));
+        assertEquals(querylessURI, PropertyUtil.replaceQuery(originalURI, ""));
+        assertEquals(new URI(querylessURI + "?" + queryString), PropertyUtil.replaceQuery(originalURI, queryString));
+        originalURI = new URI("outerscheme:(innerscheme:innerssp)?outerquery=0");
+        assertEquals(querylessURI, PropertyUtil.eraseQuery(originalURI));
+        assertEquals(querylessURI, PropertyUtil.replaceQuery(originalURI, ""));
+        assertEquals(new URI(querylessURI + "?" + queryString), PropertyUtil.replaceQuery(originalURI, queryString));
+        originalURI = new URI("outerscheme:(innerscheme:innerssp?innerquery=0)");
+        querylessURI = originalURI;
+        assertEquals(querylessURI, PropertyUtil.eraseQuery(originalURI));
+        assertEquals(querylessURI, PropertyUtil.replaceQuery(originalURI, ""));
+        assertEquals(new URI(querylessURI + "?" + queryString), PropertyUtil.replaceQuery(originalURI, queryString));
+        originalURI = new URI("outerscheme:(innerscheme:innerssp?innerquery=0)?outerquery=0");
+        assertEquals(querylessURI, PropertyUtil.eraseQuery(originalURI));
+        assertEquals(querylessURI, PropertyUtil.replaceQuery(originalURI, ""));
+        assertEquals(new URI(querylessURI + "?" + queryString), PropertyUtil.replaceQuery(originalURI, queryString));
+    }
+
+    public void testApplyParameters() throws Exception {
+
+        URI uri = new URI("http://0.0.0.0:61616");
+        Map<String,String> parameters = new HashMap<String, String>();
+        parameters.put("t.proxyHost", "localhost");
+        parameters.put("t.proxyPort", "80");
+
+        uri = URISupport.applyParameters(uri, parameters);
+        Map<String,String> appliedParameters = URISupport.parseParameters(uri);
+        assertEquals("all params applied  with no prefix", 2, appliedParameters.size());
+
+        // strip off params again
+        uri = PropertyUtil.eraseQuery(uri);
+
+        uri = URISupport.applyParameters(uri, parameters, "joe");
+        appliedParameters = URISupport.parseParameters(uri);
+        assertTrue("no params applied as none match joe", appliedParameters.isEmpty());
+
+        uri = URISupport.applyParameters(uri, parameters, "t.");
+        verifyParams(URISupport.parseParameters(uri));
+    }
+
+    private void verifyParams(Map<String,String> parameters) {
+        assertEquals(parameters.get("proxyHost"), "localhost");
+        assertEquals(parameters.get("proxyPort"), "80");
+    }
+
+    public void testIsCompositeURIWithQueryNoSlashes() throws URISyntaxException {
+        URI[] compositeURIs = new URI[] { new URI("test:(part1://host?part1=true)?outside=true"), new URI("broker:(tcp://localhost:61616)?name=foo") };
+        for (URI uri : compositeURIs) {
+            assertTrue(uri + " must be detected as composite URI", URISupport.isCompositeURI(uri));
+        }
+    }
+
+    public void testIsCompositeURIWithQueryAndSlashes() throws URISyntaxException {
+        URI[] compositeURIs = new URI[] { new URI("test://(part1://host?part1=true)?outside=true"), new URI("broker://(tcp://localhost:61616)?name=foo") };
+        for (URI uri : compositeURIs) {
+            assertTrue(uri + " must be detected as composite URI", URISupport.isCompositeURI(uri));
+        }
+    }
+
+    public void testIsCompositeURINoQueryNoSlashes() throws URISyntaxException {
+        URI[] compositeURIs = new URI[] { new URI("test:(part1://host,part2://(sub1://part,sube2:part))"), new URI("test:(path)/path") };
+        for (URI uri : compositeURIs) {
+            assertTrue(uri + " must be detected as composite URI", URISupport.isCompositeURI(uri));
+        }
+    }
+
+    public void testIsCompositeURINoQueryNoSlashesNoParentheses() throws URISyntaxException {
+        assertFalse("test:part1" + " must be detected as non-composite URI", URISupport.isCompositeURI(new URI("test:part1")));
+    }
+
+    public void testIsCompositeURINoQueryWithSlashes() throws URISyntaxException {
+        URI[] compositeURIs = new URI[] { new URI("failover://(tcp://bla:61616,tcp://bla:61617)"),
+                new URI("failover://(tcp://localhost:61616,ssl://anotherhost:61617)") };
+        for (URI uri : compositeURIs) {
+            assertTrue(uri + " must be detected as composite URI", URISupport.isCompositeURI(uri));
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/e4decdc1/qpid-jms-client/src/test/resources/keystore
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/resources/keystore b/qpid-jms-client/src/test/resources/keystore
new file mode 100644
index 0000000..9ee6adf
Binary files /dev/null and b/qpid-jms-client/src/test/resources/keystore differ

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/e4decdc1/qpid-jms-client/src/test/resources/log4j.properties
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/resources/log4j.properties b/qpid-jms-client/src/test/resources/log4j.properties
new file mode 100644
index 0000000..8df5a9e
--- /dev/null
+++ b/qpid-jms-client/src/test/resources/log4j.properties
@@ -0,0 +1,38 @@
+## ---------------------------------------------------------------------------
+## 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.
+## ---------------------------------------------------------------------------
+
+#
+# The logging properties used during tests..
+#
+log4j.rootLogger=INFO, out, stdout
+
+log4j.logger.org.apache.qpid.jms=DEBUG
+
+# Tune the TestPeer as needed for debugging.
+log4j.logger.org.apache.qpid.jms.test.testpeer=TRACE
+
+# CONSOLE appender not used by default
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d [%-15.15t] - %-5p %-30.30c{1} - %m%n
+
+# File appender
+log4j.appender.out=org.apache.log4j.FileAppender
+log4j.appender.out.layout=org.apache.log4j.PatternLayout
+log4j.appender.out.layout.ConversionPattern=%d [%-15.15t] - %-5p %-30.30c{1} - %m%n
+log4j.appender.out.file=target/activemq-test.log
+log4j.appender.out.append=true

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/e4decdc1/qpid-jms-discovery/.gitignore
----------------------------------------------------------------------
diff --git a/qpid-jms-discovery/.gitignore b/qpid-jms-discovery/.gitignore
new file mode 100644
index 0000000..ea8c4bf
--- /dev/null
+++ b/qpid-jms-discovery/.gitignore
@@ -0,0 +1 @@
+/target

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/e4decdc1/qpid-jms-discovery/pom.xml
----------------------------------------------------------------------
diff --git a/qpid-jms-discovery/pom.xml b/qpid-jms-discovery/pom.xml
new file mode 100644
index 0000000..58204cf
--- /dev/null
+++ b/qpid-jms-discovery/pom.xml
@@ -0,0 +1,93 @@
+<?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.
+-->
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.apache.qpid</groupId>
+    <artifactId>qpid-jms-parent</artifactId>
+    <version>1.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>qpid-jms-discovery</artifactId>
+  <name>QpidJMS Discovery Library</name>
+  <description>The Broker Discovery module for QpidJMS</description>
+  <packaging>jar</packaging>
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+
+  <dependencies>
+    <!-- =================================== -->
+    <!-- Required Dependencies                -->
+    <!-- =================================== -->
+    <dependency>
+      <groupId>org.apache.qpid</groupId>
+      <artifactId>qpid-jms-client</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
+
+    <!-- =================================== -->
+    <!-- Testing Dependencies                -->
+    <!-- =================================== -->
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-log4j12</artifactId>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.activemq</groupId>
+      <artifactId>activemq-broker</artifactId>
+      <version>${activemq-version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.activemq</groupId>
+      <artifactId>activemq-kahadb-store</artifactId>
+      <version>${activemq-version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.activemq</groupId>
+      <artifactId>activemq-amqp</artifactId>
+      <version>${activemq-version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.activemq</groupId>
+      <artifactId>activemq-jaas</artifactId>
+      <version>${activemq-version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.activemq</groupId>
+      <artifactId>activemq-spring</artifactId>
+      <version>${activemq-version}</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+</project>

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/e4decdc1/qpid-jms-discovery/src/main/java/org/apache/qpid/jms/provider/discovery/DiscoveryAgent.java
----------------------------------------------------------------------
diff --git a/qpid-jms-discovery/src/main/java/org/apache/qpid/jms/provider/discovery/DiscoveryAgent.java b/qpid-jms-discovery/src/main/java/org/apache/qpid/jms/provider/discovery/DiscoveryAgent.java
new file mode 100644
index 0000000..051f567
--- /dev/null
+++ b/qpid-jms-discovery/src/main/java/org/apache/qpid/jms/provider/discovery/DiscoveryAgent.java
@@ -0,0 +1,63 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.qpid.jms.provider.discovery;
+
+import java.io.IOException;
+
+/**
+ * Interface for all agents used to detect instances of remote peers on the network.
+ */
+public interface DiscoveryAgent {
+
+    /**
+     * Sets the discovery listener
+     *
+     * @param listener
+     *        the listener to notify on discovery events, or null to clear.
+     */
+    void setDiscoveryListener(DiscoveryListener listener);
+
+    /**
+     * Starts the agent after which new remote peers can start to be found.
+     *
+     * @throws IOException if an IO error occurs while starting the agent.
+     * @throws IllegalStateException if the agent is not properly configured.
+     */
+    void start() throws IOException, IllegalStateException;
+
+    /**
+     * Stops the agent after which no new remote peers will be found.  This
+     * method should attempt to close any agent resources and if an error occurs
+     * it should handle it and not re-throw to the calling entity.
+     */
+    void close();
+
+    /**
+     * Suspends the Agent which suppresses any new attempts to discover remote
+     * peers until the agent is resumed.  If the service is not able to be suspended
+     * then this method should not throw an Exception, simply return as if successful.
+     */
+    void suspend();
+
+    /**
+     * Resumes discovery by this agent if it was previously suspended.  If the agent
+     * does not support being suspended or is closed this method should simply return
+     * without throwing any exceptions.
+     */
+    void resume();
+
+}

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/e4decdc1/qpid-jms-discovery/src/main/java/org/apache/qpid/jms/provider/discovery/DiscoveryAgentFactory.java
----------------------------------------------------------------------
diff --git a/qpid-jms-discovery/src/main/java/org/apache/qpid/jms/provider/discovery/DiscoveryAgentFactory.java b/qpid-jms-discovery/src/main/java/org/apache/qpid/jms/provider/discovery/DiscoveryAgentFactory.java
new file mode 100644
index 0000000..f37745c
--- /dev/null
+++ b/qpid-jms-discovery/src/main/java/org/apache/qpid/jms/provider/discovery/DiscoveryAgentFactory.java
@@ -0,0 +1,112 @@
+/**
+ * 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.jms.provider.discovery;
+
+import java.io.IOException;
+import java.net.URI;
+
+import org.apache.qpid.jms.util.FactoryFinder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Factory used to find and create instances of DiscoveryAgent using the name
+ * of the desired agent to locate it's factory class definition file.
+ */
+public abstract class DiscoveryAgentFactory {
+
+    private static final Logger LOG = LoggerFactory.getLogger(DiscoveryAgentFactory.class);
+
+    private static final FactoryFinder<DiscoveryAgentFactory> AGENT_FACTORY_FINDER =
+        new FactoryFinder<DiscoveryAgentFactory>(DiscoveryAgentFactory.class,
+            "META-INF/services/org/apache/qpid/jms/provider/agents/");
+
+    /**
+     * Creates an instance of the given DiscoveryAgent and configures it using the
+     * properties set on the given remote broker URI.
+     *
+     * @param remoteURI
+     *        The URI used to configure remote discovery.
+     *
+     * @return a new DiscoveryAgent instance.
+     *
+     * @throws Exception if an error occurs while creating the DiscoveryAgent instance.
+     */
+    public abstract DiscoveryAgent createDiscoveryAgent(URI remoteURI) throws Exception;
+
+    /**
+     * @return the name of this discovery agent, e.g. Multicast, Zeroconf, etc.
+     */
+    public abstract String getName();
+
+    /**
+     * Static create method that performs the DiscoveryAgent search and handles the
+     * configuration and setup.
+     *
+     * @param remoteURI
+     *        the URI used to configure the discovery mechanism.
+     *
+     * @return a new DiscoveryAgent instance that is ready for use.
+     *
+     * @throws Exception if an error occurs while creating the DiscoveryAgent instance.
+     */
+    public static DiscoveryAgent createAgent(URI remoteURI) throws Exception {
+        DiscoveryAgent result = null;
+
+        try {
+            DiscoveryAgentFactory factory = findAgentFactory(remoteURI);
+            result = factory.createDiscoveryAgent(remoteURI);
+        } catch (Exception ex) {
+            LOG.error("Failed to create DiscoveryAgent instance for: {}", remoteURI.getScheme());
+            LOG.trace("Error: ", ex);
+            throw ex;
+        }
+
+        return result;
+    }
+
+    /**
+     * Searches for a DiscoveryAgentFactory by using the scheme from the given URI.
+     *
+     * The search first checks the local cache of discovery agent factories before moving on
+     * to search in the classpath.
+     *
+     * @param location
+     *        The URI whose scheme will be used to locate a DiscoveryAgentFactory.
+     *
+     * @return a DiscoveryAgentFactory instance matching the URI's scheme.
+     *
+     * @throws IOException if an error occurs while locating the factory.
+     */
+    protected static DiscoveryAgentFactory findAgentFactory(URI location) throws IOException {
+        String scheme = location.getScheme();
+        if (scheme == null) {
+            throw new IOException("No Discovery Agent scheme specified: [" + location + "]");
+        }
+
+        DiscoveryAgentFactory factory = null;
+        if (factory == null) {
+            try {
+                factory = AGENT_FACTORY_FINDER.newInstance(scheme);
+            } catch (Throwable e) {
+                throw new IOException("Discovery Agent scheme NOT recognized: [" + scheme + "]", e);
+            }
+        }
+
+        return factory;
+    }
+}

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/e4decdc1/qpid-jms-discovery/src/main/java/org/apache/qpid/jms/provider/discovery/DiscoveryEvent.java
----------------------------------------------------------------------
diff --git a/qpid-jms-discovery/src/main/java/org/apache/qpid/jms/provider/discovery/DiscoveryEvent.java b/qpid-jms-discovery/src/main/java/org/apache/qpid/jms/provider/discovery/DiscoveryEvent.java
new file mode 100644
index 0000000..0fc2f29
--- /dev/null
+++ b/qpid-jms-discovery/src/main/java/org/apache/qpid/jms/provider/discovery/DiscoveryEvent.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.jms.provider.discovery;
+
+/**
+ * Event class used to convey discovered remote peer information to the
+ * DiscoveryProvider.
+ */
+public class DiscoveryEvent {
+
+    public enum EventType {
+        ALIVE,
+        SHUTDOWN
+    };
+
+    private final String peerUri;
+    private final EventType type;
+
+    public DiscoveryEvent(String peerUri, EventType type) {
+        this.peerUri = peerUri;
+        this.type = type;
+    }
+
+    public String getPeerUri() {
+        return peerUri;
+    }
+
+    public EventType getType() {
+        return type;
+    }
+}

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/e4decdc1/qpid-jms-discovery/src/main/java/org/apache/qpid/jms/provider/discovery/DiscoveryListener.java
----------------------------------------------------------------------
diff --git a/qpid-jms-discovery/src/main/java/org/apache/qpid/jms/provider/discovery/DiscoveryListener.java b/qpid-jms-discovery/src/main/java/org/apache/qpid/jms/provider/discovery/DiscoveryListener.java
new file mode 100644
index 0000000..07e9895
--- /dev/null
+++ b/qpid-jms-discovery/src/main/java/org/apache/qpid/jms/provider/discovery/DiscoveryListener.java
@@ -0,0 +1,40 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.qpid.jms.provider.discovery;
+
+/**
+ * A listener of services being added or removed from a network
+ */
+public interface DiscoveryListener {
+
+    /**
+     * Called when a DiscoveryAgent becomes aware of a new remote peer.
+     *
+     * @param event
+     *        the event data which contains the peer address and optional name.
+     */
+    void onServiceAdd(DiscoveryEvent event);
+
+    /**
+     * Called when a DiscoveryAgent can no longer detect a previously known remote peer.
+     *
+     * @param event
+     *        the event data which contains the peer address and optional name.
+     */
+    void onServiceRemove(DiscoveryEvent event);
+
+}

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/e4decdc1/qpid-jms-discovery/src/main/java/org/apache/qpid/jms/provider/discovery/DiscoveryProvider.java
----------------------------------------------------------------------
diff --git a/qpid-jms-discovery/src/main/java/org/apache/qpid/jms/provider/discovery/DiscoveryProvider.java b/qpid-jms-discovery/src/main/java/org/apache/qpid/jms/provider/discovery/DiscoveryProvider.java
new file mode 100644
index 0000000..1d3a0f0
--- /dev/null
+++ b/qpid-jms-discovery/src/main/java/org/apache/qpid/jms/provider/discovery/DiscoveryProvider.java
@@ -0,0 +1,139 @@
+/**
+ * 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.jms.provider.discovery;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.qpid.jms.provider.ProviderWrapper;
+import org.apache.qpid.jms.provider.failover.FailoverProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An AsyncProvider instance that wraps the FailoverProvider and listens for
+ * events about discovered remote peers using a configured DiscoveryAgent
+ * instance.
+ */
+public class DiscoveryProvider extends ProviderWrapper<FailoverProvider> implements DiscoveryListener {
+
+    private static final Logger LOG = LoggerFactory.getLogger(DiscoveryProviderFactory.class);
+
+    private final URI discoveryUri;
+    private DiscoveryAgent discoveryAgent;
+    private final ConcurrentHashMap<String, URI> serviceURIs = new ConcurrentHashMap<String, URI>();
+
+    /**
+     * Creates a new instance of the DiscoveryProvider.
+     *
+     * The Provider is created and initialized with the original URI used to create it,
+     * and an instance of a FailoverProcider which it will use to initiate and maintain
+     * connections to the discovered peers.
+     *
+     * @param discoveryUri
+     * @param next
+     */
+    public DiscoveryProvider(URI discoveryUri, FailoverProvider next) {
+        super(next);
+        this.discoveryUri = discoveryUri;
+    }
+
+    @Override
+    public void start() throws IOException, IllegalStateException {
+        if (this.discoveryAgent == null) {
+            throw new IllegalStateException("No DiscoveryAgent configured.");
+        }
+
+        discoveryAgent.setDiscoveryListener(this);
+        discoveryAgent.start();
+
+        super.start();
+    }
+
+    @Override
+    public void close() {
+        discoveryAgent.close();
+        super.close();
+    }
+
+    //------------------- Property Accessors ---------------------------------//
+
+    /**
+     * @return the original URI used to configure this DiscoveryProvider.
+     */
+    public URI getDiscoveryURI() {
+        return this.discoveryUri;
+    }
+
+    /**
+     * @return the configured DiscoveryAgent instance used by this DiscoveryProvider.
+     */
+    public DiscoveryAgent getDiscoveryAgent() {
+        return this.discoveryAgent;
+    }
+
+    /**
+     * Sets the discovery agent used by this provider to locate remote peer instance.
+     *
+     * @param agent
+     *        the agent to use to discover remote peers
+     */
+    public void setDiscoveryAgent(DiscoveryAgent agent) {
+        this.discoveryAgent = agent;
+    }
+
+    //------------------- Discovery Event Handlers ---------------------------//
+
+    @Override
+    public void onServiceAdd(DiscoveryEvent event) {
+        String url = event.getPeerUri();
+        if (url != null) {
+            try {
+                URI uri = new URI(url);
+                LOG.info("Adding new peer connection URL: {}", uri);
+                serviceURIs.put(event.getPeerUri(), uri);
+                next.add(uri);
+            } catch (URISyntaxException e) {
+                LOG.warn("Could not add remote URI: {} due to bad URI syntax: {}", url, e.getMessage());
+            }
+        }
+    }
+
+    @Override
+    public void onServiceRemove(DiscoveryEvent event) {
+        URI uri = serviceURIs.get(event.getPeerUri());
+        if (uri != null) {
+            next.remove(uri);
+        }
+    }
+
+    //------------------- Connection State Handlers --------------------------//
+
+    @Override
+    public void onConnectionInterrupted(URI remoteURI) {
+        this.discoveryAgent.resume();
+        super.onConnectionInterrupted(remoteURI);
+    }
+
+    @Override
+    public void onConnectionRestored(URI remoteURI) {
+        this.discoveryAgent.suspend();
+        super.onConnectionRestored(remoteURI);
+    }
+}

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/e4decdc1/qpid-jms-discovery/src/main/java/org/apache/qpid/jms/provider/discovery/DiscoveryProviderFactory.java
----------------------------------------------------------------------
diff --git a/qpid-jms-discovery/src/main/java/org/apache/qpid/jms/provider/discovery/DiscoveryProviderFactory.java b/qpid-jms-discovery/src/main/java/org/apache/qpid/jms/provider/discovery/DiscoveryProviderFactory.java
new file mode 100644
index 0000000..cd2ab5a
--- /dev/null
+++ b/qpid-jms-discovery/src/main/java/org/apache/qpid/jms/provider/discovery/DiscoveryProviderFactory.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.jms.provider.discovery;
+
+import java.net.URI;
+import java.util.Map;
+
+import org.apache.qpid.jms.provider.Provider;
+import org.apache.qpid.jms.provider.ProviderFactory;
+import org.apache.qpid.jms.provider.failover.FailoverProvider;
+import org.apache.qpid.jms.util.PropertyUtil;
+import org.apache.qpid.jms.util.URISupport;
+import org.apache.qpid.jms.util.URISupport.CompositeData;
+
+/**
+ * Factory for creating the Discovery Provider
+ */
+public class DiscoveryProviderFactory extends ProviderFactory {
+
+    private static final String DISCOVERED_OPTION_PREFIX = "discovered.";
+
+    @Override
+    public Provider createAsyncProvider(URI remoteURI) throws Exception {
+
+        CompositeData composite = URISupport.parseComposite(remoteURI);
+        Map<String, String> options = composite.getParameters();
+
+        // Failover will apply the nested options to each URI while attempting to connect.
+        Map<String, String> nested = PropertyUtil.filterProperties(options, DISCOVERED_OPTION_PREFIX);
+        FailoverProvider failover = new FailoverProvider(nested);
+        PropertyUtil.setProperties(failover, options);
+
+        // TODO - Revisit URI options setting and enhance the ProperyUtils to provide a
+        //        means of setting some properties on a object and obtaining the leftovers
+        //        so we can pass those along to the next until we consume them all or we
+        //        have leftovers which implies a bad URI.
+
+        DiscoveryProvider discovery = new DiscoveryProvider(remoteURI, failover);
+        PropertyUtil.setProperties(discovery, options);
+
+        DiscoveryAgent agent = DiscoveryAgentFactory.createAgent(composite.getComponents()[0]);
+        discovery.setDiscoveryAgent(agent);
+
+        return discovery;
+    }
+
+    @Override
+    public String getName() {
+        return "Discovery";
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org