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 2016/09/12 20:12:02 UTC

[1/7] qpid-jms git commit: QPIDJMS-207 Adds support for Asynchronous JMS 2.0 sends.

Repository: qpid-jms
Updated Branches:
  refs/heads/master 6553cfd5b -> 6e442f4c6


http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/failover/FailoverIntegrationTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/failover/FailoverIntegrationTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/failover/FailoverIntegrationTest.java
index 8eaf707..d6dc443 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/failover/FailoverIntegrationTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/failover/FailoverIntegrationTest.java
@@ -43,11 +43,14 @@ import javax.jms.MessageProducer;
 import javax.jms.Queue;
 import javax.jms.QueueBrowser;
 import javax.jms.Session;
+import javax.jms.TextMessage;
 import javax.jms.Topic;
 
+import org.apache.qpid.jms.JmsCompletionListener;
 import org.apache.qpid.jms.JmsConnection;
 import org.apache.qpid.jms.JmsConnectionFactory;
 import org.apache.qpid.jms.JmsDefaultConnectionListener;
+import org.apache.qpid.jms.JmsMessageProducer;
 import org.apache.qpid.jms.JmsOperationTimedOutException;
 import org.apache.qpid.jms.JmsSendTimedOutException;
 import org.apache.qpid.jms.policy.JmsDefaultPrefetchPolicy;
@@ -56,6 +59,7 @@ import org.apache.qpid.jms.test.testpeer.TestAmqpPeer;
 import org.apache.qpid.jms.test.testpeer.basictypes.AmqpError;
 import org.apache.qpid.jms.test.testpeer.basictypes.TerminusDurability;
 import org.apache.qpid.jms.test.testpeer.describedtypes.Accepted;
+import org.apache.qpid.jms.test.testpeer.describedtypes.Rejected;
 import org.apache.qpid.jms.test.testpeer.describedtypes.sections.AmqpValueDescribedType;
 import org.apache.qpid.jms.test.testpeer.matchers.SourceMatcher;
 import org.apache.qpid.jms.test.testpeer.matchers.sections.MessageAnnotationsSectionMatcher;
@@ -1034,6 +1038,153 @@ public class FailoverIntegrationTest extends QpidJmsTestCase {
         }
     }
 
+    @Test(timeout = 20000)
+    public void testFailoverPassthroughOfCompletedAsyncSend() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            final Connection connection = establishAnonymousConnecton(
+                "failover.reconnectDelay=2000&failover.maxReconnectAttempts=5", testPeer);
+
+            testPeer.expectSaslAnonymousConnect();
+            testPeer.expectBegin();
+            testPeer.expectBegin();
+            testPeer.expectSenderAttach();
+
+            Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+            Queue queue = session.createQueue("myQueue");
+
+            // TODO Can change to plain MessageProducer when JMS 2.0 API dependency is added.
+            JmsMessageProducer producer = (JmsMessageProducer) session.createProducer(queue);
+
+            // Create and transfer a new message
+            String text = "myMessage";
+            testPeer.expectTransfer(new TransferPayloadCompositeMatcher());
+            testPeer.expectClose();
+
+            TextMessage message = session.createTextMessage(text);
+            TestJmsCompletionListener listener = new TestJmsCompletionListener();
+
+            producer.send(message, listener);
+
+            assertTrue("Did not get async callback", listener.awaitCompletion(2000, TimeUnit.SECONDS));
+            assertNull(listener.exception);
+            assertNotNull(listener.message);
+            assertTrue(listener.message instanceof TextMessage);
+
+            connection.close();
+
+            testPeer.waitForAllHandlersToComplete(1000);
+        }
+    }
+
+    @Test(timeout = 20000)
+    public void testFalioverPassthroughOfRejectedAsyncCompletionSend() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            final JmsConnection connection = establishAnonymousConnecton(
+                "failover.reconnectDelay=2000&failover.maxReconnectAttempts=5", testPeer);
+
+            testPeer.expectSaslAnonymousConnect();
+            testPeer.expectBegin();
+            testPeer.expectBegin();
+            testPeer.expectSenderAttach();
+
+            Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+            Queue queue = session.createQueue("myQueue");
+
+            // TODO Can change to plain MessageProducer when JMS 2.0 API dependency is added.
+            JmsMessageProducer producer = (JmsMessageProducer) session.createProducer(queue);
+
+            Message message = session.createTextMessage("content");
+            testPeer.expectTransfer(new TransferPayloadCompositeMatcher(), nullValue(), false, new Rejected(), true);
+
+            assertNull("Should not yet have a JMSDestination", message.getJMSDestination());
+
+            TestJmsCompletionListener listener = new TestJmsCompletionListener();
+            try {
+                producer.send(message, listener);
+            } catch (JMSException e) {
+                LOG.warn("Caught unexpected error: {}", e.getMessage());
+                fail("No expected exception for this send.");
+            }
+
+            assertTrue("Did not get async callback", listener.awaitCompletion(2000, TimeUnit.SECONDS));
+            assertNotNull(listener.exception);
+            assertNotNull(listener.message);
+            assertTrue(listener.message instanceof TextMessage);
+
+            testPeer.expectTransfer(new TransferPayloadCompositeMatcher());
+            testPeer.expectClose();
+
+            listener = new TestJmsCompletionListener();
+            try {
+                producer.send(message, listener);
+            } catch (JMSException e) {
+                LOG.warn("Caught unexpected error: {}", e.getMessage());
+                fail("No expected exception for this send.");
+            }
+
+            assertTrue("Did not get async callback", listener.awaitCompletion(2000, TimeUnit.SECONDS));
+            assertNull(listener.exception);
+            assertNotNull(listener.message);
+            assertTrue(listener.message instanceof TextMessage);
+
+            connection.close();
+
+            testPeer.waitForAllHandlersToComplete(2000);
+        }
+    }
+
+    @Test(timeout = 20000)
+    public void testFailoverConnectionLossFailsWaitingAsyncCompletionSends() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            final JmsConnection connection = establishAnonymousConnecton(
+                "failover.reconnectDelay=2000&failover.maxReconnectAttempts=60",
+                testPeer);
+
+            testPeer.expectSaslAnonymousConnect();
+            testPeer.expectBegin();
+            testPeer.expectBegin();
+            testPeer.expectSenderAttach();
+
+            Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+            Queue queue = session.createQueue("myQueue");
+
+            // TODO Can change to plain MessageProducer when JMS 2.0 API dependency is added.
+            JmsMessageProducer producer = (JmsMessageProducer) session.createProducer(queue);
+
+            final int MSG_COUNT = 5;
+
+            Message message = session.createTextMessage("content");
+            for (int i = 0; i < MSG_COUNT; ++i) {
+                testPeer.expectTransferButDoNotRespond(new TransferPayloadCompositeMatcher());
+            }
+
+            // Accept one which shouldn't complete until after the others have failed.
+            testPeer.expectTransfer(new TransferPayloadCompositeMatcher(), nullValue(), false, new Accepted(), true);
+            testPeer.dropAfterLastHandler();
+
+            TestJmsCompletionListener listener = new TestJmsCompletionListener(MSG_COUNT + 1);
+            try {
+                for (int i = 0; i < MSG_COUNT; ++i) {
+                    producer.send(message, listener);
+                }
+
+                producer.send(message, listener);
+            } catch (JMSException e) {
+                LOG.warn("Caught unexpected error: {}", e.getMessage());
+                fail("No expected exception for this send.");
+            }
+
+            assertTrue("Did not get async callback", listener.awaitCompletion(2000, TimeUnit.SECONDS));
+            assertEquals(MSG_COUNT, listener.errorCount);
+            assertEquals(1, listener.successCount);
+            assertNotNull(listener.exception);
+            assertNotNull(listener.message);
+            assertTrue(listener.message instanceof TextMessage);
+
+            connection.close();
+        }
+    }
+
     private JmsConnection establishAnonymousConnecton(TestAmqpPeer... peers) throws JMSException {
         return establishAnonymousConnecton(null, null, peers);
     }
@@ -1076,4 +1227,47 @@ public class FailoverIntegrationTest extends QpidJmsTestCase {
     private String createPeerURI(TestAmqpPeer peer, String params) {
         return "amqp://localhost:" + peer.getServerPort() + (params != null ? "?" + params : "");
     }
+
+    private class TestJmsCompletionListener implements JmsCompletionListener {
+
+        private final CountDownLatch completed;
+
+        public volatile int successCount;
+        public volatile int errorCount;
+
+        public volatile Message message;
+        public volatile Exception exception;
+
+        public TestJmsCompletionListener() {
+            this(1);
+        }
+
+        public TestJmsCompletionListener(int expected) {
+            this.completed = new CountDownLatch(expected);
+        }
+
+        public boolean awaitCompletion(long timeout, TimeUnit units) throws InterruptedException {
+            return completed.await(timeout, units);
+        }
+
+        @Override
+        public void onCompletion(Message message) {
+            LOG.info("JmsCompletionListener onCompletion called with message: {}", message);
+            this.message = message;
+            this.successCount++;
+
+            completed.countDown();
+        }
+
+        @Override
+        public void onException(Message message, Exception exception) {
+            LOG.info("JmsCompletionListener onException called with message: {} error {}", message, exception);
+
+            this.message = message;
+            this.exception = exception;
+            this.errorCount++;
+
+            completed.countDown();
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/mock/MockProvider.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/mock/MockProvider.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/mock/MockProvider.java
index 210ce2c..35d305c 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/mock/MockProvider.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/mock/MockProvider.java
@@ -274,7 +274,17 @@ public class MockProvider implements Provider {
                 try {
                     checkClosed();
                     stats.recordSendCall();
+
                     request.onSuccess();
+                    if (envelope.isCompletionRequired()) {
+                        if (configuration.isDelayCompletionCalls()) {
+                            context.recordPendingCompletion(MockProvider.this, envelope);
+                        } else {
+                            if (listener != null) {
+                                listener.onCompletedMessageSend(envelope);
+                            }
+                        }
+                    }
                 } catch (Exception error) {
                     request.onFailure(error);
                 }
@@ -422,7 +432,6 @@ public class MockProvider implements Provider {
         });
     }
 
-
     /**
      * Switch state to closed without sending any notifications
      */
@@ -489,7 +498,6 @@ public class MockProvider implements Provider {
 
     //----- Implementation details -------------------------------------------//
 
-
     private void checkClosed() throws ProviderClosedException {
         if (closed.get()) {
             throw new ProviderClosedException("This Provider is already closed");

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/mock/MockProviderConfiguration.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/mock/MockProviderConfiguration.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/mock/MockProviderConfiguration.java
index 7c78fff..d8c9019 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/mock/MockProviderConfiguration.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/mock/MockProviderConfiguration.java
@@ -25,6 +25,8 @@ public class MockProviderConfiguration {
     private boolean failOnStart;
     private boolean failOnClose;
 
+    private boolean delayCompletionCalls;
+
     public boolean isFailOnConnect() {
         return failOnConnect;
     }
@@ -48,4 +50,12 @@ public class MockProviderConfiguration {
     public void setFailOnClose(boolean value) {
         this.failOnClose = value;
     }
+
+    public boolean isDelayCompletionCalls() {
+        return delayCompletionCalls;
+    }
+
+    public void setDelayCompletionCalls(boolean delayCompletionCalls) {
+        this.delayCompletionCalls = delayCompletionCalls;
+    }
 }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/mock/MockRemotePeer.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/mock/MockRemotePeer.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/mock/MockRemotePeer.java
index 99fbfbc..a964282 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/mock/MockRemotePeer.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/mock/MockRemotePeer.java
@@ -18,10 +18,17 @@ package org.apache.qpid.jms.provider.mock;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.Message;
+
+import org.apache.qpid.jms.message.JmsOutboundMessageDispatch;
 import org.apache.qpid.jms.meta.JmsResource;
 
 /**
@@ -42,6 +49,9 @@ public class MockRemotePeer {
     private ResourceLifecycleFilter stopFilter;
     private ResourceLifecycleFilter destroyFilter;
 
+    private final Map<Destination, List<PendingCompletion>> pendingCompletions =
+        new ConcurrentHashMap<Destination, List<PendingCompletion>>();
+
     public void connect(MockProvider provider) throws IOException {
         if (offline) {
             throw new IOException();
@@ -146,4 +156,117 @@ public class MockRemotePeer {
     public void setResourceDestroyFilter(ResourceLifecycleFilter filter) {
         destroyFilter = filter;
     }
+
+    //----- Controls handling of Message Send Completions --------------------//
+
+    public void recordPendingCompletion(MockProvider provider, JmsOutboundMessageDispatch envelope) {
+        Destination destination = envelope.getDestination();
+        if (!pendingCompletions.containsKey(destination)) {
+            pendingCompletions.put(destination, new ArrayList<PendingCompletion>());
+        }
+
+        pendingCompletions.get(destination).add(new PendingCompletion(provider, envelope));
+    }
+
+    public void completeAllPendingSends(Destination destination) {
+        if (pendingCompletions.containsKey(destination)) {
+
+            for (List<PendingCompletion> pendingSends : pendingCompletions.values()) {
+                for (PendingCompletion pending : pendingSends) {
+                    pending.provider.getProviderListener().onCompletedMessageSend(pending.envelope);
+                }
+            }
+
+            pendingCompletions.remove(destination);
+        }
+    }
+
+    public void failAllPendingSends(Destination destination, Exception error) {
+        if (pendingCompletions.containsKey(destination)) {
+
+            for (List<PendingCompletion> pendingSends : pendingCompletions.values()) {
+                for (PendingCompletion pending : pendingSends) {
+                    pending.provider.getProviderListener().onFailedMessageSend(pending.envelope, error);
+                }
+            }
+
+            pendingCompletions.remove(destination);
+        }
+    }
+
+    public void completePendingSend(Message message) throws JMSException {
+        List<PendingCompletion> pendingSends = pendingCompletions.get(message.getJMSDestination());
+        Iterator<PendingCompletion> iterator = pendingSends.iterator();
+        while (iterator.hasNext()) {
+            PendingCompletion pending = iterator.next();
+            if (pending.envelope.getMessage().getJMSMessageID().equals(message.getJMSMessageID())) {
+                pending.provider.getProviderListener().onCompletedMessageSend(pending.envelope);
+                iterator.remove();
+            }
+        }
+    }
+
+    public void completePendingSend(JmsOutboundMessageDispatch envelope) throws JMSException {
+        List<PendingCompletion> pendingSends = pendingCompletions.get(envelope.getDestination());
+        Iterator<PendingCompletion> iterator = pendingSends.iterator();
+        while (iterator.hasNext()) {
+            PendingCompletion pending = iterator.next();
+            if (pending.envelope.getMessage().getJMSMessageID().equals(envelope.getMessage().getJMSMessageID())) {
+                pending.provider.getProviderListener().onCompletedMessageSend(pending.envelope);
+                iterator.remove();
+            }
+        }
+    }
+
+    public void failPendingSend(Message message, Exception error) throws JMSException {
+        List<PendingCompletion> pendingSends = pendingCompletions.get(message.getJMSDestination());
+        Iterator<PendingCompletion> iterator = pendingSends.iterator();
+        while (iterator.hasNext()) {
+            PendingCompletion pending = iterator.next();
+            if (pending.envelope.getMessage().getJMSMessageID().equals(message.getJMSMessageID())) {
+                pending.provider.getProviderListener().onFailedMessageSend(pending.envelope, error);
+                iterator.remove();
+            }
+        }
+    }
+
+    public void failPendingSend(JmsOutboundMessageDispatch envelope, Exception error) throws JMSException {
+        List<PendingCompletion> pendingSends = pendingCompletions.get(envelope.getDestination());
+        Iterator<PendingCompletion> iterator = pendingSends.iterator();
+        while (iterator.hasNext()) {
+            PendingCompletion pending = iterator.next();
+            if (pending.envelope.getMessage().getJMSMessageID().equals(envelope.getMessage().getJMSMessageID())) {
+                pending.provider.getProviderListener().onFailedMessageSend(pending.envelope, error);
+                iterator.remove();
+            }
+        }
+    }
+
+    public List<JmsOutboundMessageDispatch> getPendingCompletions(Destination destination) {
+        List<JmsOutboundMessageDispatch> result = null;
+
+        if (pendingCompletions.containsKey(destination)) {
+            result = new ArrayList<JmsOutboundMessageDispatch>();
+            List<PendingCompletion> pendingMessages = pendingCompletions.get(destination);
+            for (PendingCompletion pending : pendingMessages) {
+                result.add(pending.envelope);
+            }
+        } else {
+            result = Collections.emptyList();
+        }
+
+        return result;
+    }
+
+    private class PendingCompletion {
+
+        public final MockProvider provider;
+        public final JmsOutboundMessageDispatch envelope;
+
+        public PendingCompletion(MockProvider provider, JmsOutboundMessageDispatch envelope) {
+            this.provider = provider;
+            this.envelope = envelope;
+        }
+
+    }
 }


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


[5/7] qpid-jms git commit: QPIDJMS-207 Adds dependency on JMS 2.0 API and initial implementation.

Posted by ta...@apache.org.
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsMessagePropertyIntercepter.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsMessagePropertyIntercepter.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsMessagePropertyIntercepter.java
index 418d85c..bb2eb0b 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsMessagePropertyIntercepter.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsMessagePropertyIntercepter.java
@@ -16,6 +16,8 @@
  */
 package org.apache.qpid.jms.message;
 
+import static org.apache.qpid.jms.message.JmsMessagePropertySupport.checkPropertyNameIsValid;
+import static org.apache.qpid.jms.message.JmsMessagePropertySupport.checkValidObject;
 import static org.apache.qpid.jms.message.JmsMessageSupport.JMSX_DELIVERY_COUNT;
 import static org.apache.qpid.jms.message.JmsMessageSupport.JMSX_GROUPID;
 import static org.apache.qpid.jms.message.JmsMessageSupport.JMSX_GROUPSEQ;
@@ -42,7 +44,6 @@ import javax.jms.DeliveryMode;
 import javax.jms.Destination;
 import javax.jms.JMSException;
 import javax.jms.Message;
-import javax.jms.MessageFormatException;
 
 import org.apache.qpid.jms.exceptions.JmsExceptionSupport;
 import org.apache.qpid.jms.util.TypeConversionSupport;
@@ -808,74 +809,4 @@ public class JmsMessagePropertyIntercepter {
 
         return names;
     }
-
-    //----- Property Validation Methods --------------------------------------//
-
-    private static void checkPropertyNameIsValid(String propertyName, boolean validateNames) throws IllegalArgumentException {
-        if (propertyName == null) {
-            throw new IllegalArgumentException("Property name must not be null");
-        } else if (propertyName.length() == 0) {
-            throw new IllegalArgumentException("Property name must not be the empty string");
-        }
-
-        if (validateNames) {
-            checkIdentifierLetterAndDigitRequirements(propertyName);
-            checkIdentifierIsntNullTrueFalse(propertyName);
-            checkIdentifierIsntLogicOperator(propertyName);
-        }
-    }
-
-    private static void checkIdentifierIsntLogicOperator(String identifier) {
-        // Identifiers cannot be NOT, AND, OR, BETWEEN, LIKE, IN, IS, or ESCAPE.
-        if ("NOT".equals(identifier) || "AND".equals(identifier) || "OR".equals(identifier) ||
-            "BETWEEN".equals(identifier) || "LIKE".equals(identifier) || "IN".equals(identifier) ||
-            "IS".equals(identifier) || "ESCAPE".equals(identifier)) {
-
-            throw new IllegalArgumentException("Identifier not allowed in JMS: '" + identifier + "'");
-        }
-    }
-
-    private static void checkIdentifierIsntNullTrueFalse(String identifier) {
-        // Identifiers cannot be the names NULL, TRUE, and FALSE.
-        if ("NULL".equals(identifier) || "TRUE".equals(identifier) || "FALSE".equals(identifier)) {
-            throw new IllegalArgumentException("Identifier not allowed in JMS: '" + identifier + "'");
-        }
-    }
-
-    private static void checkIdentifierLetterAndDigitRequirements(String identifier) {
-        // An identifier is an unlimited-length sequence of letters and digits, the first of
-        // which must be a letter.  A letter is any character for which the method
-        // Character.isJavaLetter returns true.  This includes '_' and '$'.  A letter or digit
-        // is any character for which the method Character.isJavaLetterOrDigit returns true.
-        char startChar = identifier.charAt(0);
-        if (!(Character.isJavaIdentifierStart(startChar))) {
-            throw new IllegalArgumentException("Identifier does not begin with a valid JMS identifier start character: '" + identifier + "' ");
-        }
-
-        // JMS part character
-        int length = identifier.length();
-        for (int i = 1; i < length; i++) {
-            char ch = identifier.charAt(i);
-            if (!(Character.isJavaIdentifierPart(ch))) {
-                throw new IllegalArgumentException("Identifier contains invalid JMS identifier character '" + ch + "': '" + identifier + "' ");
-            }
-        }
-    }
-
-    private static void checkValidObject(Object value) throws MessageFormatException {
-        boolean valid = value instanceof Boolean ||
-                        value instanceof Byte ||
-                        value instanceof Short ||
-                        value instanceof Integer ||
-                        value instanceof Long ||
-                        value instanceof Float ||
-                        value instanceof Double ||
-                        value instanceof Character ||
-                        value instanceof String ||
-                        value == null;
-
-        if (!valid) {
-            throw new MessageFormatException("Only objectified primitive objects and String types are allowed but was: " + value + " type: " + value.getClass());
-        }
-    }
 }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsMessagePropertySupport.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsMessagePropertySupport.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsMessagePropertySupport.java
new file mode 100644
index 0000000..2c3576c
--- /dev/null
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsMessagePropertySupport.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.qpid.jms.message;
+
+import javax.jms.JMSException;
+import javax.jms.MessageFormatException;
+
+import org.apache.qpid.jms.util.TypeConversionSupport;
+
+/**
+ * Provides methods for use when working with JMS Message Properties and their values.
+ */
+public class JmsMessagePropertySupport {
+
+    //----- Conversions Validation for Message Properties --------------------//
+
+    @SuppressWarnings("unchecked")
+    public static <T> T convertPropertyTo(String name, Object value, Class<T> target) throws JMSException {
+        if (value == null) {
+            if (Boolean.class.equals(target)) {
+                return (T) Boolean.FALSE;
+            } else if (Float.class.equals(target) || Double.class.equals(target)) {
+                throw new NullPointerException("property " + name + " was null");
+            } else if (Number.class.isAssignableFrom(target)) {
+                throw new NumberFormatException("property " + name + " was null");
+            } else {
+                return null;
+            }
+        }
+
+        T rc = (T) TypeConversionSupport.convert(value, target);
+        if (rc == null) {
+            throw new MessageFormatException("Property " + name + " was a " + value.getClass().getName() + " and cannot be read as a " + target.getName());
+        }
+
+        return rc;
+    }
+
+    //----- Property Name Validation Methods ---------------------------------//
+
+    public static void checkPropertyNameIsValid(String propertyName, boolean validateNames) throws IllegalArgumentException {
+        if (propertyName == null) {
+            throw new IllegalArgumentException("Property name must not be null");
+        } else if (propertyName.length() == 0) {
+            throw new IllegalArgumentException("Property name must not be the empty string");
+        }
+
+        if (validateNames) {
+            checkIdentifierLetterAndDigitRequirements(propertyName);
+            checkIdentifierIsntNullTrueFalse(propertyName);
+            checkIdentifierIsntLogicOperator(propertyName);
+        }
+    }
+
+    public static void checkIdentifierIsntLogicOperator(String identifier) {
+        // Identifiers cannot be NOT, AND, OR, BETWEEN, LIKE, IN, IS, or ESCAPE.
+        if ("NOT".equals(identifier) || "AND".equals(identifier) || "OR".equals(identifier) ||
+            "BETWEEN".equals(identifier) || "LIKE".equals(identifier) || "IN".equals(identifier) ||
+            "IS".equals(identifier) || "ESCAPE".equals(identifier)) {
+
+            throw new IllegalArgumentException("Identifier not allowed in JMS: '" + identifier + "'");
+        }
+    }
+
+    public static void checkIdentifierIsntNullTrueFalse(String identifier) {
+        // Identifiers cannot be the names NULL, TRUE, and FALSE.
+        if ("NULL".equals(identifier) || "TRUE".equals(identifier) || "FALSE".equals(identifier)) {
+            throw new IllegalArgumentException("Identifier not allowed in JMS: '" + identifier + "'");
+        }
+    }
+
+    public static void checkIdentifierLetterAndDigitRequirements(String identifier) {
+        // An identifier is an unlimited-length sequence of letters and digits, the first of
+        // which must be a letter.  A letter is any character for which the method
+        // Character.isJavaLetter returns true.  This includes '_' and '$'.  A letter or digit
+        // is any character for which the method Character.isJavaLetterOrDigit returns true.
+        char startChar = identifier.charAt(0);
+        if (!(Character.isJavaIdentifierStart(startChar))) {
+            throw new IllegalArgumentException("Identifier does not begin with a valid JMS identifier start character: '" + identifier + "' ");
+        }
+
+        // JMS part character
+        int length = identifier.length();
+        for (int i = 1; i < length; i++) {
+            char ch = identifier.charAt(i);
+            if (!(Character.isJavaIdentifierPart(ch))) {
+                throw new IllegalArgumentException("Identifier contains invalid JMS identifier character '" + ch + "': '" + identifier + "' ");
+            }
+        }
+    }
+
+    //----- Property Type Validation Methods ---------------------------------//
+
+    public static void checkValidObject(Object value) throws MessageFormatException {
+        boolean valid = value instanceof Boolean ||
+                        value instanceof Byte ||
+                        value instanceof Short ||
+                        value instanceof Integer ||
+                        value instanceof Long ||
+                        value instanceof Float ||
+                        value instanceof Double ||
+                        value instanceof Character ||
+                        value instanceof String ||
+                        value == null;
+
+        if (!valid) {
+            throw new MessageFormatException("Only objectified primitive objects and String types are allowed but was: " + value + " type: " + value.getClass());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsObjectMessage.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsObjectMessage.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsObjectMessage.java
index 902719b..e8e0d22 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsObjectMessage.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsObjectMessage.java
@@ -60,6 +60,25 @@ public class JmsObjectMessage extends JmsMessage implements ObjectMessage {
     }
 
     @Override
+    public boolean isBodyAssignableTo(@SuppressWarnings("rawtypes") Class target) throws JMSException {
+        if (!facade.hasBody()) {
+            return true;
+        }
+
+        return Serializable.class == target || Object.class == target || target.isInstance(getObject());
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    protected <T> T doGetBody(Class<T> asType) throws JMSException {
+        try {
+            return (T) getObject();
+        } catch (JMSException e) {
+            throw new MessageFormatException("Failed to read Object: " + e.getMessage());
+        }
+    }
+
+    @Override
     public String toString() {
         return "JmsObjectMessageFacade { " + facade.toString() + " }";
     }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsStreamMessage.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsStreamMessage.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsStreamMessage.java
index b334249..c9765f3 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsStreamMessage.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsStreamMessage.java
@@ -475,6 +475,11 @@ public class JmsStreamMessage extends JmsMessage implements StreamMessage {
         return "JmsStreamMessage { " + facade.toString() + " }";
     }
 
+    @Override
+    public boolean isBodyAssignableTo(@SuppressWarnings("rawtypes") Class target) throws JMSException {
+        return false;
+    }
+
     private void checkBytesInFlight() throws MessageFormatException {
         if (remainingBytes != NO_BYTES_IN_FLIGHT) {
             throw new MessageFormatException(

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsTextMessage.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsTextMessage.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsTextMessage.java
index 69aefe8..9647b57 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsTextMessage.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsTextMessage.java
@@ -22,6 +22,7 @@ import javax.jms.TextMessage;
 
 import org.apache.qpid.jms.message.facade.JmsTextMessageFacade;
 
+@SuppressWarnings("unchecked")
 public class JmsTextMessage extends JmsMessage implements TextMessage {
 
     private final JmsTextMessageFacade facade;
@@ -57,4 +58,14 @@ public class JmsTextMessage extends JmsMessage implements TextMessage {
     public String toString() {
         return "JmsTextMessage { " + facade.toString() + " }";
     }
+
+    @Override
+    public boolean isBodyAssignableTo(@SuppressWarnings("rawtypes") Class target) throws JMSException {
+        return facade.hasBody() ? target.isAssignableFrom(String.class) : true;
+    }
+
+    @Override
+    protected <T> T doGetBody(Class<T> asType) throws JMSException {
+        return (T) getText();
+    }
 }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/facade/JmsBytesMessageFacade.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/facade/JmsBytesMessageFacade.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/facade/JmsBytesMessageFacade.java
index 7321a8e..73117b9 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/facade/JmsBytesMessageFacade.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/facade/JmsBytesMessageFacade.java
@@ -86,4 +86,10 @@ public interface JmsBytesMessageFacade extends JmsMessageFacade {
      * @return the number of bytes contained in the body of the message.
      */
     int getBodyLength();
+
+    /**
+     * @return a copy of the bytes contained in the body of the message.
+     */
+    byte[] copyBody();
+
 }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/facade/JmsMessageFacade.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/facade/JmsMessageFacade.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/facade/JmsMessageFacade.java
index 1741251..d5616f5 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/facade/JmsMessageFacade.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/facade/JmsMessageFacade.java
@@ -315,6 +315,25 @@ public interface JmsMessageFacade {
     void setExpiration(long expiration);
 
     /**
+     * Returns the set delivery time for this message.
+     *
+     * The value should be returned as an absolute time given in GMT time.
+     *
+     * @return the earliest time that the message should be made available for delivery.
+     */
+    long getDeliveryTime();
+
+    /**
+     * Sets an desired delivery time on this message.
+     *
+     * The delivery time will be given as an absolute time in GMT time.
+     *
+     * @param deliveryTime
+     *        the earliest time that the message should be made available for delivery.
+     */
+    void setDeliveryTime(long deliveryTime);
+
+    /**
      * Gets the Destination value that was assigned to this message at the time it was
      * sent.
      *
@@ -429,4 +448,11 @@ public interface JmsMessageFacade {
      */
     void setProviderMessageIdObject(Object messageId);
 
+    /**
+     * Returns true if the underlying message has a body, false if the body is empty.
+     *
+     * @return true if the underlying message has a body, false if the body is empty.
+     */
+    boolean hasBody();
+
 }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsBytesMessageFacade.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsBytesMessageFacade.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsBytesMessageFacade.java
index 369dfa9..6b63fb7 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsBytesMessageFacade.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsBytesMessageFacade.java
@@ -19,10 +19,6 @@ package org.apache.qpid.jms.provider.amqp.message;
 import static org.apache.qpid.jms.provider.amqp.message.AmqpMessageSupport.JMS_BYTES_MESSAGE;
 import static org.apache.qpid.jms.provider.amqp.message.AmqpMessageSupport.JMS_MSG_TYPE;
 import static org.apache.qpid.jms.provider.amqp.message.AmqpMessageSupport.OCTET_STREAM_CONTENT_TYPE;
-import io.netty.buffer.ByteBuf;
-import io.netty.buffer.ByteBufInputStream;
-import io.netty.buffer.ByteBufOutputStream;
-import io.netty.buffer.Unpooled;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -40,6 +36,11 @@ import org.apache.qpid.proton.amqp.messaging.Data;
 import org.apache.qpid.proton.amqp.messaging.Section;
 import org.apache.qpid.proton.message.Message;
 
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufInputStream;
+import io.netty.buffer.ByteBufOutputStream;
+import io.netty.buffer.Unpooled;
+
 /**
  * A JmsBytesMessageFacade that wraps around Proton AMQP Message instances to provide
  * access to the underlying bytes contained in the message.
@@ -216,6 +217,21 @@ public class AmqpJmsBytesMessageFacade extends AmqpJmsMessageFacade implements J
     }
 
     @Override
+    public boolean hasBody() {
+        return getBinaryFromBody().getLength() != 0;
+    }
+
+    @Override
+    public byte[] copyBody() {
+        Binary content = getBinaryFromBody();
+        byte[] result = new byte[content.getLength()];
+
+        System.arraycopy(content.getArray(), content.getArrayOffset(), result, 0, content.getLength());
+
+        return result;
+    }
+
+    @Override
     public void onSend(long producerTtl) throws JMSException {
         super.onSend(producerTtl);
 

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMapMessageFacade.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMapMessageFacade.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMapMessageFacade.java
index dc2cf0b..fe242f7 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMapMessageFacade.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMapMessageFacade.java
@@ -141,6 +141,11 @@ public class AmqpJmsMapMessageFacade extends AmqpJmsMessageFacade implements Jms
         messageBodyMap.clear();
     }
 
+    @Override
+    public boolean hasBody() {
+        return !messageBodyMap.isEmpty();
+    }
+
     private void initializeEmptyBody() {
         // Using LinkedHashMap because AMQP map equality considers order,
         // so we should behave in as predictable a manner as possible

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageFacade.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageFacade.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageFacade.java
index f98dccc..82f63e0 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageFacade.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageFacade.java
@@ -553,6 +553,18 @@ public class AmqpJmsMessageFacade implements JmsMessageFacade {
         }
     }
 
+    @Override
+    public long getDeliveryTime() {
+        // TODO Auto-generated method stub
+        return 0;
+    }
+
+    @Override
+    public void setDeliveryTime(long deliveryTime) {
+        // TODO Auto-generated method stub
+
+    }
+
     /**
      * Sets a value which will be used to override any ttl value that may otherwise be set
      * based on the expiration value when sending the underlying AMQP message. A value of 0
@@ -702,6 +714,11 @@ public class AmqpJmsMessageFacade implements JmsMessageFacade {
         }
     }
 
+    @Override
+    public boolean hasBody() {
+        return message.getBody() == null;
+    }
+
     /**
      * @return the true AMQP Message instance wrapped by this Facade.
      */

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsObjectMessageFacade.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsObjectMessageFacade.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsObjectMessageFacade.java
index fabefed..f4b541f 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsObjectMessageFacade.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsObjectMessageFacade.java
@@ -125,6 +125,11 @@ public class AmqpJmsObjectMessageFacade extends AmqpJmsMessageFacade implements
     }
 
     @Override
+    public boolean hasBody() {
+        return delegate.hasBody();
+    }
+
+    @Override
     public void onSend(long producerTtl) throws JMSException {
         super.onSend(producerTtl);
         delegate.onSend();

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsStreamMessageFacade.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsStreamMessageFacade.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsStreamMessageFacade.java
index d63d4a3..64f1fbd 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsStreamMessageFacade.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsStreamMessageFacade.java
@@ -161,6 +161,11 @@ public class AmqpJmsStreamMessageFacade extends AmqpJmsMessageFacade implements
         position = 0;
     }
 
+    @Override
+    public boolean hasBody() {
+        return !list.isEmpty();
+    }
+
     private List<Object> initializeEmptyBodyList(boolean useSequenceBody) {
         List<Object> emptyList = new ArrayList<Object>();
 

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsTextMessageFacade.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsTextMessageFacade.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsTextMessageFacade.java
index e3106e6..44ed9e6 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsTextMessageFacade.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsTextMessageFacade.java
@@ -135,6 +135,15 @@ public class AmqpJmsTextMessageFacade extends AmqpJmsMessageFacade implements Jm
         setText(null);
     }
 
+    @Override
+    public boolean hasBody() {
+        try {
+            return getText() != null;
+        } catch (JMSException e) {
+            return false;
+        }
+    }
+
     Charset getCharset() {
         return charset;
     }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpObjectTypeDelegate.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpObjectTypeDelegate.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpObjectTypeDelegate.java
index 7657343..3c1fa12 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpObjectTypeDelegate.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpObjectTypeDelegate.java
@@ -64,4 +64,7 @@ public interface AmqpObjectTypeDelegate {
     void copyInto(AmqpObjectTypeDelegate copy) throws Exception;
 
     boolean isAmqpTypeEncoded();
+
+    boolean hasBody();
+
 }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpSerializedObjectDelegate.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpSerializedObjectDelegate.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpSerializedObjectDelegate.java
index 618d123..ec73ba9 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpSerializedObjectDelegate.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpSerializedObjectDelegate.java
@@ -194,4 +194,13 @@ public class AmqpSerializedObjectDelegate implements AmqpObjectTypeDelegate, Tru
             return true;
         }
     }
+
+    @Override
+    public boolean hasBody() {
+        try {
+            return getObject() != null;
+        } catch (Exception e) {
+            return false;
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpTypedObjectDelegate.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpTypedObjectDelegate.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpTypedObjectDelegate.java
index cc1038f..1296eaa 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpTypedObjectDelegate.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpTypedObjectDelegate.java
@@ -168,6 +168,15 @@ public class AmqpTypedObjectDelegate implements AmqpObjectTypeDelegate {
         return true;
     }
 
+    @Override
+    public boolean hasBody() {
+        try {
+            return getObject() != null;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
     //----- Internal implementation ------------------------------------------//
 
     private boolean isSupportedAmqpValueObjectType(Serializable serializable) {

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/test/java/org/apache/qpid/jms/JmsConnectionTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/JmsConnectionTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/JmsConnectionTest.java
index 3f5b84b..4928faa 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/JmsConnectionTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/JmsConnectionTest.java
@@ -266,8 +266,14 @@ public class JmsConnectionTest {
     }
 
     @Test(timeout=30000, expected=JMSException.class)
-    public void testCreateDurableConnectionConsumer() throws Exception {
+    public void testCreateSharedConnectionConsumer() throws Exception {
         connection = new JmsConnection("ID:TEST:1", provider, clientIdGenerator);
-        connection.createDurableConnectionConsumer(new JmsTopic(), "id", "", null, 1);
+        connection.createSharedConnectionConsumer(new JmsTopic(), "id", "", null, 1);
+    }
+
+    @Test(timeout=30000, expected=JMSException.class)
+    public void testCreateSharedDurableConnectionConsumer() throws Exception {
+        connection = new JmsConnection("ID:TEST:1", provider, clientIdGenerator);
+        connection.createSharedDurableConnectionConsumer(new JmsTopic(), "id", "", null, 1);
     }
 }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/test/java/org/apache/qpid/jms/JmsConnectionTestSupport.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/JmsConnectionTestSupport.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/JmsConnectionTestSupport.java
index 0f4a9ce..3986f8b 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/JmsConnectionTestSupport.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/JmsConnectionTestSupport.java
@@ -18,6 +18,8 @@ package org.apache.qpid.jms;
 
 import java.net.URI;
 
+import javax.jms.JMSContext;
+
 import org.apache.qpid.jms.provider.Provider;
 import org.apache.qpid.jms.provider.ProviderListener;
 import org.apache.qpid.jms.provider.mock.MockProviderFactory;
@@ -51,6 +53,13 @@ public class JmsConnectionTestSupport extends QpidJmsTestCase {
         });
     }
 
+    protected JmsContext createJMSContextToMockProvider() throws Exception {
+        JmsConnection connection = new JmsConnection("ID:TEST:1", createMockProvider(), clientIdGenerator);
+        JmsContext context = new JmsContext(connection, JMSContext.AUTO_ACKNOWLEDGE);
+
+        return context;
+    }
+
     protected JmsConnection createConnectionToMockProvider() throws Exception {
         return new JmsConnection("ID:TEST:1", createMockProvider(), clientIdGenerator);
     }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ConnectionIntegrationTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ConnectionIntegrationTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ConnectionIntegrationTest.java
index a3a6ce2..2e559ac 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ConnectionIntegrationTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ConnectionIntegrationTest.java
@@ -106,6 +106,30 @@ public class ConnectionIntegrationTest extends QpidJmsTestCase {
     }
 
     @Test(timeout = 20000)
+    public void testCreateAutoAckSessionByDefault() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            Connection connection = testFixture.establishConnecton(testPeer);
+            testPeer.expectBegin();
+            Session session = connection.createSession();
+            assertNotNull("Session should not be null", session);
+            testPeer.expectClose();
+            connection.close();
+        }
+    }
+
+    @Test(timeout = 20000)
+    public void testCreateAutoAckSessionUsingAckModeOnlyMethod() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            Connection connection = testFixture.establishConnecton(testPeer);
+            testPeer.expectBegin();
+            Session session = connection.createSession(Session.AUTO_ACKNOWLEDGE);
+            assertNotNull("Session should not be null", session);
+            testPeer.expectClose();
+            connection.close();
+        }
+    }
+
+    @Test(timeout = 20000)
     public void testCreateTransactedSession() throws Exception {
         try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
             Connection connection = testFixture.establishConnecton(testPeer);
@@ -132,6 +156,32 @@ public class ConnectionIntegrationTest extends QpidJmsTestCase {
     }
 
     @Test(timeout = 20000)
+    public void testCreateTransactedSessionUsingAckModeOnlyMethod() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            Connection connection = testFixture.establishConnecton(testPeer);
+
+            testPeer.expectBegin();
+            // Expect the session, with an immediate link to the transaction coordinator
+            // using a target with the expected capabilities only.
+            CoordinatorMatcher txCoordinatorMatcher = new CoordinatorMatcher();
+            txCoordinatorMatcher.withCapabilities(arrayContaining(TxnCapability.LOCAL_TXN));
+            testPeer.expectSenderAttach(txCoordinatorMatcher, false, false);
+
+            // First expect an unsettled 'declare' transfer to the txn coordinator, and
+            // reply with a declared disposition state containing the txnId.
+            Binary txnId = new Binary(new byte[]{ (byte) 1, (byte) 2, (byte) 3, (byte) 4});
+            testPeer.expectDeclare(txnId);
+            testPeer.expectDischarge(txnId, true);
+            testPeer.expectClose();
+
+            Session session = connection.createSession(Session.SESSION_TRANSACTED);
+            assertNotNull("Session should not be null", session);
+
+            connection.close();
+        }
+    }
+
+    @Test(timeout = 20000)
     public void testCreateTransactedSessionFailsWhenNoDetachResponseSent() throws Exception {
         try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
             Connection connection = testFixture.establishConnecton(testPeer);

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/IntegrationTestFixture.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/IntegrationTestFixture.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/IntegrationTestFixture.java
index c0a3cbc..7c33fc1 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/IntegrationTestFixture.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/IntegrationTestFixture.java
@@ -24,6 +24,7 @@ import java.util.Map;
 
 import javax.jms.Connection;
 import javax.jms.ConnectionFactory;
+import javax.jms.JMSContext;
 import javax.jms.JMSException;
 
 import org.apache.qpid.jms.JmsConnectionFactory;
@@ -61,22 +62,80 @@ public class IntegrationTestFixture {
         // Each connection creates a session for managing temporary destinations etc
         testPeer.expectBegin();
 
-        String scheme = ssl ? "amqps" : "amqp";
-        final String baseURI = scheme + "://localhost:" + testPeer.getServerPort();
-        String remoteURI = baseURI;
-        if (optionsString != null) {
-            remoteURI = baseURI + optionsString;
-        }
+        String remoteURI = buildURI(testPeer, ssl, optionsString);
 
         ConnectionFactory factory = new JmsConnectionFactory(remoteURI);
         Connection connection = factory.createConnection("guest", "guest");
 
-        if(setClientId) {
+        if (setClientId) {
             // Set a clientId to provoke the actual AMQP connection process to occur.
             connection.setClientID("clientName");
         }
 
         assertNull(testPeer.getThrowable());
+
         return connection;
     }
+
+    JMSContext createJMSContext(TestAmqpPeer testPeer) throws JMSException {
+        return createJMSContext(testPeer, null, null, null);
+    }
+
+    JMSContext createJMSContext(TestAmqpPeer testPeer, int sessionMode) throws JMSException {
+        return createJMSContext(testPeer, false, null, null, null, true, sessionMode);
+    }
+
+    JMSContext createJMSContext(TestAmqpPeer testPeer, String optionsString) throws JMSException {
+        return createJMSContext(testPeer, optionsString, null, null);
+    }
+
+    JMSContext createJMSContext(TestAmqpPeer testPeer, Symbol[] serverCapabilities) throws JMSException {
+        return createJMSContext(testPeer, null, serverCapabilities, null);
+    }
+
+    JMSContext createJMSContext(TestAmqpPeer testPeer, Symbol[] serverCapabilities, Map<Symbol, Object> serverProperties) throws JMSException {
+        return createJMSContext(testPeer, null, serverCapabilities, serverProperties);
+    }
+
+    JMSContext createJMSContext(TestAmqpPeer testPeer, String optionsString, Symbol[] serverCapabilities, Map<Symbol, Object> serverProperties) throws JMSException {
+        return createJMSContext(testPeer, false, optionsString, serverCapabilities, serverProperties, true, JMSContext.AUTO_ACKNOWLEDGE);
+    }
+
+    JMSContext createJMSContext(TestAmqpPeer testPeer, boolean ssl, String optionsString, Symbol[] serverCapabilities, Map<Symbol, Object> serverProperties, boolean setClientId) throws JMSException {
+        return createJMSContext(testPeer, false, optionsString, serverCapabilities, serverProperties, setClientId, JMSContext.AUTO_ACKNOWLEDGE);
+    }
+
+    JMSContext createJMSContext(TestAmqpPeer testPeer, boolean ssl, String optionsString, Symbol[] serverCapabilities, Map<Symbol, Object> serverProperties, boolean setClientId, int sessionMode) throws JMSException {
+        Symbol[] desiredCapabilities = new Symbol[] { AmqpSupport.SOLE_CONNECTION_CAPABILITY };
+
+        testPeer.expectSaslPlainConnect("guest", "guest", desiredCapabilities, serverCapabilities, serverProperties);
+
+        // Each connection creates a session for managing temporary destinations etc
+        testPeer.expectBegin();
+
+        String remoteURI = buildURI(testPeer, ssl, optionsString);
+
+        ConnectionFactory factory = new JmsConnectionFactory(remoteURI);
+        JMSContext context = factory.createContext("guest", "guest", sessionMode);
+
+        if (setClientId) {
+            // Set a clientId to provoke the actual AMQP connection process to occur.
+            context.setClientID("clientName");
+        }
+
+        assertNull(testPeer.getThrowable());
+
+        return context;
+    }
+
+    String buildURI(TestAmqpPeer testPeer, boolean ssl, String optionsString) {
+        String scheme = ssl ? "amqps" : "amqp";
+        final String baseURI = scheme + "://localhost:" + testPeer.getServerPort();
+        String remoteURI = baseURI;
+        if (optionsString != null) {
+            remoteURI = baseURI + optionsString;
+        }
+
+        return remoteURI;
+    }
 }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/JMSConsumerIntegrationTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/JMSConsumerIntegrationTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/JMSConsumerIntegrationTest.java
new file mode 100644
index 0000000..adc546d
--- /dev/null
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/JMSConsumerIntegrationTest.java
@@ -0,0 +1,559 @@
+/*
+ * 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.integration;
+
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectOutputStream;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.jms.IllegalStateRuntimeException;
+import javax.jms.JMSConsumer;
+import javax.jms.JMSContext;
+import javax.jms.JMSRuntimeException;
+import javax.jms.Message;
+import javax.jms.MessageFormatRuntimeException;
+import javax.jms.MessageListener;
+import javax.jms.Queue;
+
+import org.apache.qpid.jms.provider.amqp.message.AmqpMessageSupport;
+import org.apache.qpid.jms.test.QpidJmsTestCase;
+import org.apache.qpid.jms.test.Wait;
+import org.apache.qpid.jms.test.testpeer.TestAmqpPeer;
+import org.apache.qpid.jms.test.testpeer.basictypes.AmqpError;
+import org.apache.qpid.jms.test.testpeer.describedtypes.sections.AmqpValueDescribedType;
+import org.apache.qpid.jms.test.testpeer.describedtypes.sections.DataDescribedType;
+import org.apache.qpid.jms.test.testpeer.describedtypes.sections.MessageAnnotationsDescribedType;
+import org.apache.qpid.jms.test.testpeer.describedtypes.sections.PropertiesDescribedType;
+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.UnsignedInteger;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class JMSConsumerIntegrationTest extends QpidJmsTestCase {
+
+    private static final Logger LOG = LoggerFactory.getLogger(JMSConsumerIntegrationTest.class);
+
+    private final IntegrationTestFixture testFixture = new IntegrationTestFixture();
+
+    @Test(timeout = 20000)
+    public void testCreateConsumer() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            JMSContext context = testFixture.createJMSContext(testPeer);
+            testPeer.expectBegin();
+            testPeer.expectReceiverAttach();
+            testPeer.expectLinkFlow();
+
+            Queue queue = context.createQueue("test");
+            JMSConsumer consumer = context.createConsumer(queue);
+            assertNotNull(consumer);
+
+            testPeer.expectEnd();
+            testPeer.expectClose();
+            context.close();
+
+            testPeer.waitForAllHandlersToComplete(1000);
+        }
+    }
+
+    @Test(timeout = 20000)
+    public void testRemotelyCloseJMSConsumer() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            JMSContext context = testFixture.createJMSContext(testPeer);
+
+            testPeer.expectBegin();
+
+            // Create a consumer, then remotely end it afterwards.
+            testPeer.expectReceiverAttach();
+            testPeer.expectLinkFlow();
+            testPeer.remotelyDetachLastOpenedLinkOnLastOpenedSession(true, true, AmqpError.RESOURCE_DELETED, "resource closed");
+
+            Queue queue = context.createQueue("myQueue");
+            final JMSConsumer consumer = context.createConsumer(queue);
+
+            // Verify the consumer gets marked closed
+            testPeer.waitForAllHandlersToComplete(1000);
+            assertTrue("JMSConsumer never closed.", Wait.waitFor(new Wait.Condition() {
+                @Override
+                public boolean isSatisified() throws Exception {
+                    try {
+                        consumer.getMessageListener();
+                    } catch (IllegalStateRuntimeException jmsise) {
+                        return true;
+                    }
+                    return false;
+                }
+            }, 10000, 10));
+
+            // Try closing it explicitly, should effectively no-op in client.
+            // The test peer will throw during close if it sends anything.
+            consumer.close();
+
+            testPeer.expectEnd();
+            testPeer.expectClose();
+            context.close();
+
+            testPeer.waitForAllHandlersToComplete(1000);
+        }
+    }
+
+    @Test(timeout = 20000)
+    public void testReceiveMessageWithReceiveZeroTimeout() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            JMSContext context = testFixture.createJMSContext(testPeer);
+
+            testPeer.expectBegin();
+
+            Queue queue = context.createQueue("myQueue");
+
+            DescribedType amqpValueNullContent = new AmqpValueDescribedType(null);
+
+            testPeer.expectReceiverAttach();
+            testPeer.expectLinkFlowRespondWithTransfer(null, null, null, null, amqpValueNullContent);
+            testPeer.expectDispositionThatIsAcceptedAndSettled();
+
+            JMSConsumer messageConsumer = context.createConsumer(queue);
+            Message receivedMessage = messageConsumer.receive(0);
+
+            assertNotNull("A message should have been recieved", receivedMessage);
+
+            testPeer.expectEnd();
+            testPeer.expectClose();
+            context.close();
+
+            testPeer.waitForAllHandlersToComplete(2000);
+        }
+    }
+
+    @Test(timeout=20000)
+    public void testConsumerReceiveNoWaitThrowsIfConnectionLost() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            JMSContext context = testFixture.createJMSContext(testPeer);
+
+            testPeer.expectBegin();
+
+            Queue queue = context.createQueue("queue");
+
+            testPeer.expectReceiverAttach();
+            testPeer.expectLinkFlow(false, notNullValue(UnsignedInteger.class));
+            testPeer.expectLinkFlow(true, notNullValue(UnsignedInteger.class));
+            testPeer.dropAfterLastHandler();
+
+            final JMSConsumer consumer = context.createConsumer(queue);
+
+            try {
+                consumer.receiveNoWait();
+                fail("An exception should have been thrown");
+            } catch (JMSRuntimeException jmsre) {
+                // Expected
+            }
+        }
+    }
+
+    @Test(timeout=20000)
+    public void testNoReceivedMessagesWhenConnectionNotStarted() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            JMSContext context = testFixture.createJMSContext(testPeer);
+            context.setAutoStart(false);
+
+            testPeer.expectBegin();
+
+            Queue destination = context.createQueue(getTestName());
+
+            testPeer.expectReceiverAttach();
+            testPeer.expectLinkFlowRespondWithTransfer(null, null, null, null, new AmqpValueDescribedType("content"), 3);
+            testPeer.expectDispositionThatIsAcceptedAndSettled();
+
+            JMSConsumer consumer = context.createConsumer(destination);
+
+            assertNull(consumer.receive(100));
+
+            context.start();
+
+            assertNotNull(consumer.receive(2000));
+
+            testPeer.expectEnd();
+            testPeer.expectClose();
+            context.close();
+
+            testPeer.waitForAllHandlersToComplete(2000);
+        }
+    }
+
+    @Test(timeout=60000)
+    public void testSyncReceiveFailsWhenListenerSet() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            JMSContext context = testFixture.createJMSContext(testPeer);
+
+            testPeer.expectBegin();
+
+            Queue destination = context.createQueue(getTestName());
+
+            testPeer.expectReceiverAttach();
+            testPeer.expectLinkFlow();
+
+            JMSConsumer consumer = context.createConsumer(destination);
+
+            consumer.setMessageListener(new MessageListener() {
+                @Override
+                public void onMessage(Message m) {
+                    LOG.warn("Async consumer got unexpected Message: {}", m);
+                }
+            });
+
+            try {
+                consumer.receive();
+                fail("Should have thrown an exception.");
+            } catch (JMSRuntimeException ex) {
+            }
+
+            try {
+                consumer.receive(1000);
+                fail("Should have thrown an exception.");
+            } catch (JMSRuntimeException ex) {
+            }
+
+            try {
+                consumer.receiveNoWait();
+                fail("Should have thrown an exception.");
+            } catch (JMSRuntimeException ex) {
+            }
+
+            testPeer.expectEnd();
+            testPeer.expectClose();
+            context.close();
+
+            testPeer.waitForAllHandlersToComplete(2000);
+        }
+    }
+
+    @Test(timeout = 20000)
+    public void testReceiveBodyMapMessage() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            JMSContext context = testFixture.createJMSContext(testPeer);
+
+            testPeer.expectBegin();
+
+            Queue queue = context.createQueue("myQueue");
+
+            // Prepare an AMQP message for the test peer to send, containing an
+            // AmqpValue section holding a map with entries for each supported type,
+            // and annotated as a JMS map message.
+            String myBoolKey = "myBool";
+            boolean myBool = true;
+            String myByteKey = "myByte";
+            byte myByte = 4;
+            String myBytesKey = "myBytes";
+            byte[] myBytes = myBytesKey.getBytes();
+            String myCharKey = "myChar";
+            char myChar = 'd';
+            String myDoubleKey = "myDouble";
+            double myDouble = 1234567890123456789.1234;
+            String myFloatKey = "myFloat";
+            float myFloat = 1.1F;
+            String myIntKey = "myInt";
+            int myInt = Integer.MAX_VALUE;
+            String myLongKey = "myLong";
+            long myLong = Long.MAX_VALUE;
+            String myShortKey = "myShort";
+            short myShort = 25;
+            String myStringKey = "myString";
+            String myString = myStringKey;
+
+            Map<String, Object> map = new LinkedHashMap<String, Object>();
+            map.put(myBoolKey, myBool);
+            map.put(myByteKey, myByte);
+            map.put(myBytesKey, new Binary(myBytes));// the underlying AMQP message uses Binary rather than byte[] directly.
+            map.put(myCharKey, myChar);
+            map.put(myDoubleKey, myDouble);
+            map.put(myFloatKey, myFloat);
+            map.put(myIntKey, myInt);
+            map.put(myLongKey, myLong);
+            map.put(myShortKey, myShort);
+            map.put(myStringKey, myString);
+
+            MessageAnnotationsDescribedType msgAnnotations = new MessageAnnotationsDescribedType();
+            msgAnnotations.setSymbolKeyedAnnotation(AmqpMessageSupport.JMS_MSG_TYPE, AmqpMessageSupport.JMS_MAP_MESSAGE);
+
+            DescribedType amqpValueSectionContent = new AmqpValueDescribedType(map);
+
+            // receive the message from the test peer
+            testPeer.expectReceiverAttach();
+            testPeer.expectLinkFlowRespondWithTransfer(null, msgAnnotations, null, null, amqpValueSectionContent);
+            testPeer.expectDispositionThatIsAcceptedAndSettled();
+            testPeer.expectEnd();
+            testPeer.expectClose();
+
+            JMSConsumer messageConsumer = context.createConsumer(queue);
+            @SuppressWarnings("unchecked")
+            Map<String, Object> receivedMap = messageConsumer.receiveBody(Map.class, 3000);
+
+            // verify the content is as expected
+            assertNotNull("Map was not received", receivedMap);
+
+            assertEquals("Unexpected boolean value", myBool, receivedMap.get(myBoolKey));
+            assertEquals("Unexpected byte value", myByte, receivedMap.get(myByteKey));
+            byte[] readBytes = (byte[]) receivedMap.get(myBytesKey);
+            assertTrue("Read bytes were not as expected: " + Arrays.toString(readBytes), Arrays.equals(myBytes, readBytes));
+            assertEquals("Unexpected char value", myChar, receivedMap.get(myCharKey));
+            assertEquals("Unexpected double value", myDouble, (double) receivedMap.get(myDoubleKey), 0.0);
+            assertEquals("Unexpected float value", myFloat, (float) receivedMap.get(myFloatKey), 0.0);
+            assertEquals("Unexpected int value", myInt, receivedMap.get(myIntKey));
+            assertEquals("Unexpected long value", myLong, receivedMap.get(myLongKey));
+            assertEquals("Unexpected short value", myShort, receivedMap.get(myShortKey));
+            assertEquals("Unexpected UTF value", myString, receivedMap.get(myStringKey));
+
+            context.close();
+
+            testPeer.waitForAllHandlersToComplete(3000);
+        }
+    }
+
+    @Test(timeout = 20000)
+    public void testReceiveBodyTextMessage() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            JMSContext context = testFixture.createJMSContext(testPeer);
+
+            testPeer.expectBegin();
+
+            final String content = "Message-Content";
+            Queue queue = context.createQueue("myQueue");
+
+            DescribedType amqpValueContent = new AmqpValueDescribedType(content);
+
+            testPeer.expectReceiverAttach();
+            testPeer.expectLinkFlowRespondWithTransfer(null, null, null, null, amqpValueContent);
+            testPeer.expectDispositionThatIsAcceptedAndSettled();
+            testPeer.expectEnd();
+            testPeer.expectClose();
+
+            JMSConsumer messageConsumer = context.createConsumer(queue);
+            String received = messageConsumer.receiveBody(String.class, 3000);
+
+            assertNotNull(received);
+            assertEquals(content, received);
+
+            context.close();
+
+            testPeer.waitForAllHandlersToComplete(3000);
+        }
+    }
+
+    @Test(timeout = 20000)
+    public void testReceiveBodyObjectMessage() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            JMSContext context = testFixture.createJMSContext(testPeer);
+
+            testPeer.expectBegin();
+
+            Queue queue = context.createQueue("myQueue");
+
+            PropertiesDescribedType properties = new PropertiesDescribedType();
+            properties.setContentType(Symbol.valueOf(AmqpMessageSupport.SERIALIZED_JAVA_OBJECT_CONTENT_TYPE));
+
+            String expectedContent = "expectedContent";
+
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            ObjectOutputStream oos = new ObjectOutputStream(baos);
+            oos.writeObject(expectedContent);
+            oos.flush();
+            oos.close();
+            byte[] bytes = baos.toByteArray();
+
+            MessageAnnotationsDescribedType msgAnnotations = new MessageAnnotationsDescribedType();
+            msgAnnotations.setSymbolKeyedAnnotation(AmqpMessageSupport.JMS_MSG_TYPE, AmqpMessageSupport.JMS_OBJECT_MESSAGE);
+
+            DescribedType dataContent = new DataDescribedType(new Binary(bytes));
+
+            testPeer.expectReceiverAttach();
+            testPeer.expectLinkFlowRespondWithTransfer(null, msgAnnotations, properties, null, dataContent);
+            testPeer.expectDispositionThatIsAcceptedAndSettled();
+            testPeer.expectEnd();
+            testPeer.expectClose();
+
+            JMSConsumer messageConsumer = context.createConsumer(queue);
+            String received = messageConsumer.receiveBody(String.class, 3000);
+
+            assertNotNull(received);
+            assertEquals(expectedContent, received);
+
+            context.close();
+
+            testPeer.waitForAllHandlersToComplete(3000);
+        }
+    }
+
+    @Test(timeout = 20000)
+    public void testReceiveBodyBytesMessage() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            JMSContext context = testFixture.createJMSContext(testPeer);
+
+            testPeer.expectBegin();
+
+            Queue queue = context.createQueue("myQueue");
+
+            PropertiesDescribedType properties = new PropertiesDescribedType();
+            properties.setContentType(Symbol.valueOf(AmqpMessageSupport.OCTET_STREAM_CONTENT_TYPE));
+
+            MessageAnnotationsDescribedType msgAnnotations = null;
+            msgAnnotations = new MessageAnnotationsDescribedType();
+            msgAnnotations.setSymbolKeyedAnnotation(AmqpMessageSupport.JMS_MSG_TYPE, AmqpMessageSupport.JMS_BYTES_MESSAGE);
+
+            final byte[] expectedContent = "expectedContent".getBytes();
+            DescribedType dataContent = new DataDescribedType(new Binary(expectedContent));
+
+            testPeer.expectReceiverAttach();
+            testPeer.expectLinkFlowRespondWithTransfer(null, msgAnnotations, properties, null, dataContent);
+            testPeer.expectDispositionThatIsAcceptedAndSettled();
+
+            JMSConsumer messageConsumer = context.createConsumer(queue);
+            byte[] received = messageConsumer.receiveBody(byte[].class, 3000);
+            testPeer.waitForAllHandlersToComplete(3000);
+
+            assertNotNull(received);
+            assertTrue(Arrays.equals(expectedContent, received));
+
+            testPeer.expectEnd();
+            testPeer.expectClose();
+
+            context.close();
+
+            testPeer.waitForAllHandlersToComplete(3000);
+        }
+    }
+
+    @Test(timeout = 20000)
+    public void testReceiveBodyFailsDoesNotAcceptMessageAutoAck() throws Exception {
+        doTestReceiveBodyFailsDoesNotAcceptMessage(JMSContext.AUTO_ACKNOWLEDGE);
+    }
+
+    @Test(timeout = 20000)
+    public void testReceiveBodyFailsDoesNotAcceptMessageDupsOk() throws Exception {
+        doTestReceiveBodyFailsDoesNotAcceptMessage(JMSContext.DUPS_OK_ACKNOWLEDGE);
+    }
+
+    @Test(timeout = 20000)
+    public void testReceiveBodyFailsDoesNotAcceptMessageClientAck() throws Exception {
+        doTestReceiveBodyFailsDoesNotAcceptMessage(JMSContext.CLIENT_ACKNOWLEDGE);
+    }
+
+    public void doTestReceiveBodyFailsDoesNotAcceptMessage(int sessionMode) throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            JMSContext context = testFixture.createJMSContext(testPeer);
+
+            testPeer.expectBegin();
+
+            final String content = "Message-Content";
+            Queue queue = context.createQueue("myQueue");
+
+            DescribedType amqpValueContent = new AmqpValueDescribedType(content);
+
+            testPeer.expectReceiverAttach();
+            testPeer.expectLinkFlowRespondWithTransfer(null, null, null, null, amqpValueContent);
+            testPeer.expectEnd();
+            testPeer.expectClose();
+
+            JMSConsumer messageConsumer = context.createConsumer(queue);
+            try {
+                messageConsumer.receiveBody(Boolean.class, 3000);
+                fail("Should not read as Boolean type");
+            } catch (MessageFormatRuntimeException mfre) {
+            }
+
+            context.close();
+
+            testPeer.waitForAllHandlersToComplete(3000);
+        }
+    }
+
+    @Test(timeout = 20000)
+    public void testReceiveBodyFailsThenAcceptsOnSuccessfullyNextCallAutoAck() throws Exception {
+        doTestReceiveBodyFailsDoesNotAcceptMessage(JMSContext.AUTO_ACKNOWLEDGE);
+    }
+
+    @Test(timeout = 20000)
+    public void testReceiveBodyFailsThenAcceptsOnSuccessfullyNextCallDupsOk() throws Exception {
+        doTestReceiveBodyFailsDoesNotAcceptMessage(JMSContext.DUPS_OK_ACKNOWLEDGE);
+    }
+
+    @Test(timeout = 20000)
+    public void testReceiveBodyFailsThenGetNullOnNextAttemptClientAck() throws Exception {
+        doTestReceiveBodyFailsDoesNotAcceptMessage(JMSContext.CLIENT_ACKNOWLEDGE);
+    }
+
+    public void doTestReceiveBodyFailsThenCalledWithCorrectType(int sessionMode) throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            JMSContext context = testFixture.createJMSContext(testPeer);
+
+            testPeer.expectBegin();
+
+            final String content = "Message-Content";
+            Queue queue = context.createQueue("myQueue");
+
+            DescribedType amqpValueContent = new AmqpValueDescribedType(content);
+
+            testPeer.expectReceiverAttach();
+            testPeer.expectLinkFlowRespondWithTransfer(null, null, null, null, amqpValueContent);
+
+            JMSConsumer messageConsumer = context.createConsumer(queue);
+            try {
+                messageConsumer.receiveBody(Boolean.class, 3000);
+                fail("Should not read as Boolean type");
+            } catch (MessageFormatRuntimeException mfre) {
+            }
+
+            testPeer.waitForAllHandlersToComplete(3000);
+
+            if (sessionMode == JMSContext.AUTO_ACKNOWLEDGE ||
+                sessionMode == JMSContext.DUPS_OK_ACKNOWLEDGE) {
+
+                testPeer.expectDispositionThatIsAcceptedAndSettled();
+            }
+
+            String received = messageConsumer.receiveBody(String.class, 3000);
+
+            if (sessionMode == JMSContext.AUTO_ACKNOWLEDGE ||
+                sessionMode == JMSContext.DUPS_OK_ACKNOWLEDGE) {
+
+                assertNotNull(received);
+                assertEquals(content, received);
+            } else {
+                assertNull(received);
+            }
+
+            testPeer.expectEnd();
+            testPeer.expectClose();
+
+            context.close();
+
+            testPeer.waitForAllHandlersToComplete(3000);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/JMSContextIntegrationTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/JMSContextIntegrationTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/JMSContextIntegrationTest.java
new file mode 100644
index 0000000..7c3d2d1
--- /dev/null
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/JMSContextIntegrationTest.java
@@ -0,0 +1,198 @@
+/*
+ * 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.integration;
+
+import static org.apache.qpid.jms.provider.amqp.AmqpSupport.ANONYMOUS_RELAY;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.util.UUID;
+
+import javax.jms.JMSContext;
+import javax.jms.JMSProducer;
+
+import org.apache.qpid.jms.test.QpidJmsTestCase;
+import org.apache.qpid.jms.test.testpeer.TestAmqpPeer;
+import org.apache.qpid.proton.amqp.Binary;
+import org.apache.qpid.proton.amqp.Symbol;
+import org.junit.Test;
+
+public class JMSContextIntegrationTest extends QpidJmsTestCase {
+
+    private final IntegrationTestFixture testFixture = new IntegrationTestFixture();
+
+    private Symbol[] SERVER_ANONYMOUS_RELAY = new Symbol[]{ANONYMOUS_RELAY};
+
+    @Test(timeout = 20000)
+    public void testCreateAndCloseContext() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            JMSContext context = testFixture.createJMSContext(testPeer);
+            testPeer.expectClose();
+            context.close();
+
+            testPeer.waitForAllHandlersToComplete(1000);
+        }
+    }
+
+    @Test(timeout = 20000)
+    public void testCreateContextWithClientId() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            JMSContext context = testFixture.createJMSContext(testPeer, false, null, null, null, true);
+            testPeer.expectClose();
+            context.close();
+
+            testPeer.waitForAllHandlersToComplete(1000);
+        }
+    }
+
+    @Test(timeout = 20000)
+    public void testCreateContextAndSetClientID() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            JMSContext context = testFixture.createJMSContext(testPeer, false, null, null, null, false);
+            context.setClientID(UUID.randomUUID().toString());
+            testPeer.expectClose();
+            context.close();
+
+            testPeer.waitForAllHandlersToComplete(1000);
+        }
+    }
+
+    @Test(timeout = 20000)
+    public void testCreateAutoAckSessionByDefault() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            JMSContext context = testFixture.createJMSContext(testPeer);
+            assertEquals(JMSContext.AUTO_ACKNOWLEDGE, context.getSessionMode());
+            testPeer.expectBegin();
+            context.createTopic("TopicName");
+            testPeer.expectEnd();
+            testPeer.expectClose();
+            context.close();
+
+            testPeer.waitForAllHandlersToComplete(1000);
+        }
+    }
+
+    @Test(timeout = 20000)
+    public void testCreateContextWithTransactedSessionMode() throws Exception {
+        Binary txnId = new Binary(new byte[]{ (byte) 5, (byte) 6, (byte) 7, (byte) 8});
+
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            JMSContext context = testFixture.createJMSContext(testPeer, JMSContext.SESSION_TRANSACTED);
+            assertEquals(JMSContext.SESSION_TRANSACTED, context.getSessionMode());
+
+            // Session should be created and a coordinator should be attached since this
+            // should be a TX session, then a new TX is declared, once closed the TX should
+            // be discharged as a roll back.
+            testPeer.expectBegin();
+            testPeer.expectCoordinatorAttach();
+            testPeer.expectDeclare(txnId);
+            testPeer.expectDischarge(txnId, true);
+            testPeer.expectEnd();
+            testPeer.expectClose();
+
+            context.createTopic("TopicName");
+
+            context.close();
+
+            testPeer.waitForAllHandlersToComplete(1000);
+        }
+    }
+
+    @Test(timeout = 20000)
+    public void testCreateContextFromContextWithSessionsActive() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            JMSContext context = testFixture.createJMSContext(testPeer);
+            assertEquals(JMSContext.AUTO_ACKNOWLEDGE, context.getSessionMode());
+            testPeer.expectBegin();
+            context.createTopic("TopicName");
+
+            // Create a second should not create a new session yet, once a new connection is
+            // create on demand then close of the second context should only close the session
+            JMSContext other = context.createContext(JMSContext.CLIENT_ACKNOWLEDGE);
+            assertEquals(JMSContext.CLIENT_ACKNOWLEDGE, other.getSessionMode());
+            testPeer.expectBegin();
+            testPeer.expectEnd();
+            other.createTopic("TopicName");
+            other.close();
+
+            testPeer.waitForAllHandlersToComplete(1000);
+
+            // Now the connection should close down.
+            testPeer.expectEnd();
+            testPeer.expectClose();
+            context.close();
+
+            testPeer.waitForAllHandlersToComplete(1000);
+        }
+    }
+
+    @Test(timeout = 20000)
+    public void testOnlyOneProducerCreatedInSingleContext() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            JMSContext context = testFixture.createJMSContext(testPeer, SERVER_ANONYMOUS_RELAY);
+            assertEquals(JMSContext.AUTO_ACKNOWLEDGE, context.getSessionMode());
+            testPeer.expectBegin();
+            testPeer.expectSenderAttach();
+
+            // One producer created should send an attach.
+            JMSProducer producer1 = context.createProducer();
+            assertNotNull(producer1);
+
+            // An additional one should not result in an attach
+            JMSProducer producer2 = context.createProducer();
+            assertNotNull(producer2);
+
+            testPeer.expectEnd();
+            testPeer.expectClose();
+            context.close();
+
+            testPeer.waitForAllHandlersToComplete(1000);
+        }
+    }
+
+    @Test(timeout = 20000)
+    public void testEachContextGetsItsOwnProducer() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            JMSContext context = testFixture.createJMSContext(testPeer, SERVER_ANONYMOUS_RELAY);
+            assertEquals(JMSContext.AUTO_ACKNOWLEDGE, context.getSessionMode());
+            testPeer.expectBegin();
+            testPeer.expectSenderAttach();
+            testPeer.expectBegin();
+            testPeer.expectSenderAttach();
+
+            // One producer created should send an attach.
+            JMSProducer producer1 = context.createProducer();
+            assertNotNull(producer1);
+
+            // An additional one should not result in an attach
+            JMSContext other = context.createContext(JMSContext.AUTO_ACKNOWLEDGE);
+            JMSProducer producer2 = other.createProducer();
+            assertNotNull(producer2);
+
+            testPeer.expectEnd();
+            testPeer.expectEnd();
+            testPeer.expectClose();
+
+            other.close();
+            context.close();
+
+            testPeer.waitForAllHandlersToComplete(1000);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/JMSProducerIntegrationTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/JMSProducerIntegrationTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/JMSProducerIntegrationTest.java
new file mode 100644
index 0000000..4096c1f
--- /dev/null
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/JMSProducerIntegrationTest.java
@@ -0,0 +1,200 @@
+/*
+ * 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.integration;
+
+import static org.apache.qpid.jms.provider.amqp.AmqpSupport.ANONYMOUS_RELAY;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import javax.jms.JMSContext;
+import javax.jms.JMSProducer;
+import javax.jms.Message;
+import javax.jms.Queue;
+
+import org.apache.qpid.jms.test.QpidJmsTestCase;
+import org.apache.qpid.jms.test.testpeer.TestAmqpPeer;
+import org.apache.qpid.jms.test.testpeer.matchers.sections.ApplicationPropertiesSectionMatcher;
+import org.apache.qpid.jms.test.testpeer.matchers.sections.MessageAnnotationsSectionMatcher;
+import org.apache.qpid.jms.test.testpeer.matchers.sections.MessageHeaderSectionMatcher;
+import org.apache.qpid.jms.test.testpeer.matchers.sections.MessagePropertiesSectionMatcher;
+import org.apache.qpid.jms.test.testpeer.matchers.sections.TransferPayloadCompositeMatcher;
+import org.apache.qpid.proton.amqp.Symbol;
+import org.junit.Test;
+
+public class JMSProducerIntegrationTest extends QpidJmsTestCase {
+
+    private final IntegrationTestFixture testFixture = new IntegrationTestFixture();
+
+    private Symbol[] SERVER_ANONYMOUS_RELAY = new Symbol[]{ANONYMOUS_RELAY};
+
+    private static final String NULL_STRING_PROP = "nullStringProperty";
+    private static final String NULL_STRING_PROP_VALUE = null;
+    private static final String STRING_PROP = "stringProperty";
+    private static final String STRING_PROP_VALUE = "string";
+    private static final String BOOLEAN_PROP = "booleanProperty";
+    private static final boolean BOOLEAN_PROP_VALUE = true;
+    private static final String BYTE_PROP = "byteProperty";
+    private static final byte   BYTE_PROP_VALUE = (byte)1;
+    private static final String SHORT_PROP = "shortProperty";
+    private static final short  SHORT_PROP_VALUE = (short)1;
+    private static final String INT_PROP = "intProperty";
+    private static final int    INT_PROP_VALUE = Integer.MAX_VALUE;
+    private static final String LONG_PROP = "longProperty";
+    private static final long   LONG_PROP_VALUE = Long.MAX_VALUE;
+    private static final String FLOAT_PROP = "floatProperty";
+    private static final float  FLOAT_PROP_VALUE = Float.MAX_VALUE;
+    private static final String DOUBLE_PROP = "doubleProperty";
+    private static final double DOUBLE_PROP_VALUE = Double.MAX_VALUE;
+
+    @Test(timeout = 20000)
+    public void testCreateProducer() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            JMSContext context = testFixture.createJMSContext(testPeer, SERVER_ANONYMOUS_RELAY);
+            testPeer.expectBegin();
+            testPeer.expectSenderAttach();
+
+            JMSProducer producer = context.createProducer();
+            assertNotNull(producer);
+
+            testPeer.expectEnd();
+            testPeer.expectClose();
+            context.close();
+
+            testPeer.waitForAllHandlersToComplete(1000);
+        }
+    }
+
+    @Test(timeout = 20000)
+    public void testJMSProducerHasDefaultConfiguration() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            JMSContext context = testFixture.createJMSContext(testPeer, SERVER_ANONYMOUS_RELAY);
+            testPeer.expectBegin();
+            testPeer.expectSenderAttach();
+
+            JMSProducer producer = context.createProducer();
+            assertNotNull(producer);
+
+            assertEquals(Message.DEFAULT_DELIVERY_DELAY, producer.getDeliveryDelay());
+            assertEquals(Message.DEFAULT_DELIVERY_MODE, producer.getDeliveryMode());
+            assertEquals(Message.DEFAULT_PRIORITY, producer.getPriority());
+            assertEquals(Message.DEFAULT_TIME_TO_LIVE, producer.getTimeToLive());
+
+            testPeer.expectEnd();
+            testPeer.expectClose();
+            context.close();
+
+            testPeer.waitForAllHandlersToComplete(1000);
+        }
+    }
+
+    @Test(timeout = 20000)
+    public void testJMSProducerSetPropertySendsApplicationProperties() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            JMSContext context = testFixture.createJMSContext(testPeer, SERVER_ANONYMOUS_RELAY);
+            testPeer.expectBegin();
+            testPeer.expectSenderAttach();
+
+            String queueName = "myQueue";
+            Queue queue = context.createQueue(queueName);
+            JMSProducer producer = context.createProducer();
+
+            ApplicationPropertiesSectionMatcher appPropsMatcher = new ApplicationPropertiesSectionMatcher(true);
+            appPropsMatcher.withEntry(NULL_STRING_PROP, nullValue());
+            appPropsMatcher.withEntry(STRING_PROP, equalTo(STRING_PROP_VALUE));
+            appPropsMatcher.withEntry(BOOLEAN_PROP, equalTo(BOOLEAN_PROP_VALUE));
+            appPropsMatcher.withEntry(BYTE_PROP, equalTo(BYTE_PROP_VALUE));
+            appPropsMatcher.withEntry(SHORT_PROP, equalTo(SHORT_PROP_VALUE));
+            appPropsMatcher.withEntry(INT_PROP, equalTo(INT_PROP_VALUE));
+            appPropsMatcher.withEntry(LONG_PROP, equalTo(LONG_PROP_VALUE));
+            appPropsMatcher.withEntry(FLOAT_PROP, equalTo(FLOAT_PROP_VALUE));
+            appPropsMatcher.withEntry(DOUBLE_PROP, equalTo(DOUBLE_PROP_VALUE));
+
+            MessageHeaderSectionMatcher headersMatcher = new MessageHeaderSectionMatcher(true).withDurable(equalTo(true));
+            MessageAnnotationsSectionMatcher msgAnnotationsMatcher = new MessageAnnotationsSectionMatcher(true);
+            MessagePropertiesSectionMatcher propsMatcher = new MessagePropertiesSectionMatcher(true).withTo(equalTo(queueName));
+
+            TransferPayloadCompositeMatcher messageMatcher = new TransferPayloadCompositeMatcher();
+            messageMatcher.setHeadersMatcher(headersMatcher);
+            messageMatcher.setMessageAnnotationsMatcher(msgAnnotationsMatcher);
+            messageMatcher.setPropertiesMatcher(propsMatcher);
+            messageMatcher.setApplicationPropertiesMatcher(appPropsMatcher);
+            testPeer.expectTransfer(messageMatcher);
+
+            producer.setProperty(NULL_STRING_PROP, NULL_STRING_PROP_VALUE);
+            producer.setProperty(STRING_PROP, STRING_PROP_VALUE);
+            producer.setProperty(BOOLEAN_PROP, BOOLEAN_PROP_VALUE);
+            producer.setProperty(BYTE_PROP, BYTE_PROP_VALUE);
+            producer.setProperty(SHORT_PROP, SHORT_PROP_VALUE);
+            producer.setProperty(INT_PROP, INT_PROP_VALUE);
+            producer.setProperty(LONG_PROP, LONG_PROP_VALUE);
+            producer.setProperty(FLOAT_PROP, FLOAT_PROP_VALUE);
+            producer.setProperty(DOUBLE_PROP, DOUBLE_PROP_VALUE);
+
+            producer.send(queue, "test");
+
+            testPeer.expectEnd();
+            testPeer.expectClose();
+
+            context.close();
+
+            testPeer.waitForAllHandlersToComplete(1000);
+        }
+    }
+
+    @Test(timeout = 20000)
+    public void testJMSProducerPropertyOverridesMessageValue() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            JMSContext context = testFixture.createJMSContext(testPeer, SERVER_ANONYMOUS_RELAY);
+            testPeer.expectBegin();
+            testPeer.expectSenderAttach();
+
+            String queueName = "myQueue";
+            Queue queue = context.createQueue(queueName);
+            Message message = context.createMessage();
+            JMSProducer producer = context.createProducer();
+
+            ApplicationPropertiesSectionMatcher appPropsMatcher = new ApplicationPropertiesSectionMatcher(true);
+            appPropsMatcher.withEntry(STRING_PROP, equalTo(STRING_PROP_VALUE));
+
+            MessageHeaderSectionMatcher headersMatcher = new MessageHeaderSectionMatcher(true).withDurable(equalTo(true));
+            MessageAnnotationsSectionMatcher msgAnnotationsMatcher = new MessageAnnotationsSectionMatcher(true);
+            MessagePropertiesSectionMatcher propsMatcher = new MessagePropertiesSectionMatcher(true).withTo(equalTo(queueName));
+
+            TransferPayloadCompositeMatcher messageMatcher = new TransferPayloadCompositeMatcher();
+            messageMatcher.setHeadersMatcher(headersMatcher);
+            messageMatcher.setMessageAnnotationsMatcher(msgAnnotationsMatcher);
+            messageMatcher.setPropertiesMatcher(propsMatcher);
+            messageMatcher.setApplicationPropertiesMatcher(appPropsMatcher);
+            testPeer.expectTransfer(messageMatcher);
+
+            message.setStringProperty(STRING_PROP, "ThisShouldNotBeTransmitted");
+            producer.setProperty(STRING_PROP, STRING_PROP_VALUE);
+            producer.send(queue, message);
+
+            testPeer.expectEnd();
+            testPeer.expectClose();
+
+            context.close();
+
+            testPeer.waitForAllHandlersToComplete(1000);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/MapMessageIntegrationTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/MapMessageIntegrationTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/MapMessageIntegrationTest.java
index 8141751..10bcffd 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/MapMessageIntegrationTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/MapMessageIntegrationTest.java
@@ -20,8 +20,10 @@ package org.apache.qpid.jms.integration;
 
 import static org.hamcrest.Matchers.equalTo;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.util.Arrays;
 import java.util.LinkedHashMap;
@@ -31,6 +33,7 @@ import javax.jms.Connection;
 import javax.jms.MapMessage;
 import javax.jms.Message;
 import javax.jms.MessageConsumer;
+import javax.jms.MessageFormatException;
 import javax.jms.MessageProducer;
 import javax.jms.Queue;
 import javax.jms.Session;
@@ -116,10 +119,10 @@ public class MapMessageIntegrationTest extends QpidJmsTestCase {
             testPeer.expectReceiverAttach();
             testPeer.expectLinkFlowRespondWithTransfer(null, msgAnnotations, null, null, amqpValueSectionContent);
             testPeer.expectDispositionThatIsAcceptedAndSettled();
+            testPeer.expectClose();
 
             MessageConsumer messageConsumer = session.createConsumer(queue);
             Message receivedMessage = messageConsumer.receive(3000);
-            testPeer.waitForAllHandlersToComplete(3000);
 
             // verify the content is as expected
             assertNotNull("Message was not received", receivedMessage);
@@ -137,6 +140,23 @@ public class MapMessageIntegrationTest extends QpidJmsTestCase {
             assertEquals("Unexpected long value", myLong, receivedMapMessage.getLong(myLongKey));
             assertEquals("Unexpected short value", myShort, receivedMapMessage.getShort(myShortKey));
             assertEquals("Unexpected UTF value", myString, receivedMapMessage.getString(myStringKey));
+
+            assertTrue(receivedMapMessage.isBodyAssignableTo(Map.class));
+            assertTrue(receivedMapMessage.isBodyAssignableTo(Object.class));
+            assertFalse(receivedMapMessage.isBodyAssignableTo(Boolean.class));
+            assertFalse(receivedMapMessage.isBodyAssignableTo(byte[].class));
+
+            assertNotNull(receivedMapMessage.getBody(Object.class));
+            assertNotNull(receivedMapMessage.getBody(Map.class));
+            try {
+                receivedMapMessage.getBody(byte[].class);
+                fail("Cannot read TextMessage with this type.");
+            } catch (MessageFormatException mfe) {
+            }
+
+            connection.close();
+
+            testPeer.waitForAllHandlersToComplete(3000);
         }
     }
 
@@ -219,9 +239,28 @@ public class MapMessageIntegrationTest extends QpidJmsTestCase {
             messageMatcher.setPropertiesMatcher(propertiesMatcher);
             messageMatcher.setMessageContentMatcher(new EncodedAmqpValueMatcher(map));
 
-            // send the message
             testPeer.expectTransfer(messageMatcher);
+            testPeer.expectClose();
+
+            // send the message
             producer.send(mapMessage);
+
+            assertTrue(mapMessage.isBodyAssignableTo(Map.class));
+            assertTrue(mapMessage.isBodyAssignableTo(Object.class));
+            assertFalse(mapMessage.isBodyAssignableTo(Boolean.class));
+            assertFalse(mapMessage.isBodyAssignableTo(byte[].class));
+
+            assertNotNull(mapMessage.getBody(Object.class));
+            assertNotNull(mapMessage.getBody(Map.class));
+            try {
+                mapMessage.getBody(byte[].class);
+                fail("Cannot read TextMessage with this type.");
+            } catch (MessageFormatException mfe) {
+            }
+
+            connection.close();
+
+            testPeer.waitForAllHandlersToComplete(3000);
         }
     }
 }


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


[4/7] qpid-jms git commit: QPIDJMS-207 Adds dependency on JMS 2.0 API and initial implementation.

Posted by ta...@apache.org.
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/MessageIntegrationTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/MessageIntegrationTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/MessageIntegrationTest.java
index ec84479..48adaea 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/MessageIntegrationTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/MessageIntegrationTest.java
@@ -31,6 +31,7 @@ import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import java.io.IOException;
+import java.io.Serializable;
 import java.util.Date;
 import java.util.Enumeration;
 import java.util.HashMap;
@@ -98,6 +99,45 @@ public class MessageIntegrationTest extends QpidJmsTestCase
 
     private final IntegrationTestFixture testFixture = new IntegrationTestFixture();
 
+    @Test(timeout = 20000)
+    public void testReceiveMessageAndGetBody() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            Connection connection = testFixture.establishConnecton(testPeer);
+            connection.start();
+
+            testPeer.expectBegin();
+
+            Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+            Queue queue = session.createQueue("myQueue");
+
+            DescribedType amqpValueNullContent = new AmqpValueDescribedType(null);
+
+            testPeer.expectReceiverAttach();
+            testPeer.expectLinkFlowRespondWithTransfer(null, null, null, null, amqpValueNullContent);
+            testPeer.expectDispositionThatIsAcceptedAndSettled();
+            testPeer.expectClose();
+
+            MessageConsumer messageConsumer = session.createConsumer(queue);
+            Message receivedMessage = messageConsumer.receive(3000);
+
+            assertTrue(receivedMessage.isBodyAssignableTo(Object.class));
+            assertTrue(receivedMessage.isBodyAssignableTo(String.class));
+            assertTrue(receivedMessage.isBodyAssignableTo(byte[].class));
+            assertTrue(receivedMessage.isBodyAssignableTo(Serializable.class));
+            assertTrue(receivedMessage.isBodyAssignableTo(Map.class));
+
+            assertNull(receivedMessage.getBody(Object.class));
+            assertNull(receivedMessage.getBody(String.class));
+            assertNull(receivedMessage.getBody(byte[].class));
+            assertNull(receivedMessage.getBody(Serializable.class));
+            assertNull(receivedMessage.getBody(Map.class));
+
+            connection.close();
+
+            testPeer.waitForAllHandlersToComplete(3000);
+        }
+    }
+
     //==== Application Properties Section ====
     //========================================
 

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ObjectMessageIntegrationTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ObjectMessageIntegrationTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ObjectMessageIntegrationTest.java
index a9bc0c5..e382324 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ObjectMessageIntegrationTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ObjectMessageIntegrationTest.java
@@ -21,20 +21,25 @@ package org.apache.qpid.jms.integration;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.nullValue;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.ObjectOutputStream;
+import java.io.Serializable;
 import java.util.HashMap;
+import java.util.Map;
 import java.util.UUID;
 
 import javax.jms.Connection;
 import javax.jms.JMSException;
 import javax.jms.Message;
 import javax.jms.MessageConsumer;
+import javax.jms.MessageFormatException;
 import javax.jms.MessageProducer;
 import javax.jms.ObjectMessage;
 import javax.jms.Queue;
@@ -113,6 +118,7 @@ public class ObjectMessageIntegrationTest extends QpidJmsTestCase {
             messageMatcher.setMessageContentMatcher(new EncodedDataMatcher(new Binary(bytes)));
 
             testPeer.expectTransfer(messageMatcher);
+            testPeer.expectClose();
 
             ObjectMessage message = session.createObjectMessage();
             if (content != null || setObjectIfNull) {
@@ -121,6 +127,38 @@ public class ObjectMessageIntegrationTest extends QpidJmsTestCase {
 
             producer.send(message);
 
+            if (content == null) {
+                assertTrue(message.isBodyAssignableTo(String.class));
+                assertTrue(message.isBodyAssignableTo(Serializable.class));
+                assertTrue(message.isBodyAssignableTo(Object.class));
+                assertTrue(message.isBodyAssignableTo(Boolean.class));
+                assertTrue(message.isBodyAssignableTo(byte[].class));
+            } else {
+                assertTrue(message.isBodyAssignableTo(String.class));
+                assertTrue(message.isBodyAssignableTo(Serializable.class));
+                assertTrue(message.isBodyAssignableTo(Object.class));
+                assertFalse(message.isBodyAssignableTo(Boolean.class));
+                assertFalse(message.isBodyAssignableTo(byte[].class));
+            }
+
+            if (content == null) {
+                assertNull(message.getBody(Object.class));
+                assertNull(message.getBody(Serializable.class));
+                assertNull(message.getBody(String.class));
+                assertNull(message.getBody(byte[].class));
+            } else {
+                assertNotNull(message.getBody(Object.class));
+                assertNotNull(message.getBody(Serializable.class));
+                assertNotNull(message.getBody(String.class));
+                try {
+                    message.getBody(byte[].class);
+                    fail("Cannot read TextMessage with this type.");
+                } catch (MessageFormatException mfe) {
+                }
+            }
+
+            connection.close();
+
             testPeer.waitForAllHandlersToComplete(3000);
         }
     }
@@ -156,10 +194,10 @@ public class ObjectMessageIntegrationTest extends QpidJmsTestCase {
             testPeer.expectReceiverAttach();
             testPeer.expectLinkFlowRespondWithTransfer(null, msgAnnotations, properties, null, dataContent);
             testPeer.expectDispositionThatIsAcceptedAndSettled();
+            testPeer.expectClose();
 
             MessageConsumer messageConsumer = session.createConsumer(queue);
             Message receivedMessage = messageConsumer.receive(3000);
-            testPeer.waitForAllHandlersToComplete(3000);
 
             assertNotNull(receivedMessage);
             assertTrue(receivedMessage instanceof ObjectMessage);
@@ -168,6 +206,25 @@ public class ObjectMessageIntegrationTest extends QpidJmsTestCase {
             Object object = objectMessage.getObject();
             assertNotNull("Expected object but got null", object);
             assertEquals("Message body object was not as expected", expectedContent, object);
+
+            assertTrue(receivedMessage.isBodyAssignableTo(String.class));
+            assertTrue(receivedMessage.isBodyAssignableTo(Serializable.class));
+            assertTrue(receivedMessage.isBodyAssignableTo(Object.class));
+            assertFalse(receivedMessage.isBodyAssignableTo(Boolean.class));
+            assertFalse(receivedMessage.isBodyAssignableTo(byte[].class));
+
+            assertNotNull(receivedMessage.getBody(Object.class));
+            assertNotNull(receivedMessage.getBody(Serializable.class));
+            assertNotNull(receivedMessage.getBody(String.class));
+            try {
+                receivedMessage.getBody(byte[].class);
+                fail("Cannot read TextMessage with this type.");
+            } catch (MessageFormatException mfe) {
+            }
+
+            connection.close();
+
+            testPeer.waitForAllHandlersToComplete(3000);
         }
     }
 
@@ -224,9 +281,27 @@ public class ObjectMessageIntegrationTest extends QpidJmsTestCase {
             messageMatcher.setMessageContentMatcher(new EncodedDataMatcher(new Binary(bytes)));
 
             testPeer.expectTransfer(messageMatcher);
+            testPeer.expectClose();
 
             producer.send(receivedMessage);
 
+            assertTrue(receivedMessage.isBodyAssignableTo(String.class));
+            assertTrue(receivedMessage.isBodyAssignableTo(Serializable.class));
+            assertTrue(receivedMessage.isBodyAssignableTo(Object.class));
+            assertFalse(receivedMessage.isBodyAssignableTo(Boolean.class));
+            assertFalse(receivedMessage.isBodyAssignableTo(byte[].class));
+
+            assertNotNull(receivedMessage.getBody(Object.class));
+            assertNotNull(receivedMessage.getBody(Serializable.class));
+            assertNotNull(receivedMessage.getBody(String.class));
+            try {
+                receivedMessage.getBody(byte[].class);
+                fail("Cannot read TextMessage with this type.");
+            } catch (MessageFormatException mfe) {
+            }
+
+            connection.close();
+
             testPeer.waitForAllHandlersToComplete(3000);
         }
     }
@@ -373,6 +448,7 @@ public class ObjectMessageIntegrationTest extends QpidJmsTestCase {
             messageMatcher.setMessageContentMatcher(new EncodedAmqpValueMatcher(map));
 
             testPeer.expectTransfer(messageMatcher);
+            testPeer.expectClose();
 
             ObjectMessage message = session.createObjectMessage();
             message.setBooleanProperty(AmqpMessageSupport.JMS_AMQP_TYPED_ENCODING, true);
@@ -380,6 +456,23 @@ public class ObjectMessageIntegrationTest extends QpidJmsTestCase {
 
             producer.send(message);
 
+            assertTrue(message.isBodyAssignableTo(Map.class));
+            assertTrue(message.isBodyAssignableTo(Serializable.class));
+            assertTrue(message.isBodyAssignableTo(Object.class));
+            assertFalse(message.isBodyAssignableTo(Boolean.class));
+            assertFalse(message.isBodyAssignableTo(byte[].class));
+
+            assertNotNull(message.getBody(Object.class));
+            assertNotNull(message.getBody(Serializable.class));
+            assertNotNull(message.getBody(Map.class));
+            try {
+                message.getBody(byte[].class);
+                fail("Cannot read TextMessage with this type.");
+            } catch (MessageFormatException mfe) {
+            }
+
+            connection.close();
+
             testPeer.waitForAllHandlersToComplete(3000);
         }
     }
@@ -406,10 +499,10 @@ public class ObjectMessageIntegrationTest extends QpidJmsTestCase {
             testPeer.expectReceiverAttach();
             testPeer.expectLinkFlowRespondWithTransfer(null, msgAnnotations, null, null, amqpValueContent);
             testPeer.expectDispositionThatIsAcceptedAndSettled();
+            testPeer.expectClose();
 
             MessageConsumer messageConsumer = session.createConsumer(queue);
             Message receivedMessage = messageConsumer.receive(3000);
-            testPeer.waitForAllHandlersToComplete(3000);
 
             assertNotNull(receivedMessage);
             assertTrue("Expected ObjectMessage instance, but got: " + receivedMessage.getClass().getName(), receivedMessage instanceof ObjectMessage);
@@ -418,6 +511,25 @@ public class ObjectMessageIntegrationTest extends QpidJmsTestCase {
             Object object = objectMessage.getObject();
             assertNotNull("Expected object but got null", object);
             assertEquals("Message body object was not as expected", map, object);
+
+            assertTrue(receivedMessage.isBodyAssignableTo(Map.class));
+            assertTrue(receivedMessage.isBodyAssignableTo(Serializable.class));
+            assertTrue(receivedMessage.isBodyAssignableTo(Object.class));
+            assertFalse(receivedMessage.isBodyAssignableTo(Boolean.class));
+            assertFalse(receivedMessage.isBodyAssignableTo(byte[].class));
+
+            assertNotNull(receivedMessage.getBody(Object.class));
+            assertNotNull(receivedMessage.getBody(Serializable.class));
+            assertNotNull(receivedMessage.getBody(Map.class));
+            try {
+                receivedMessage.getBody(byte[].class);
+                fail("Cannot read TextMessage with this type.");
+            } catch (MessageFormatException mfe) {
+            }
+
+            connection.close();
+
+            testPeer.waitForAllHandlersToComplete(3000);
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/PresettledProducerIntegrationTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/PresettledProducerIntegrationTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/PresettledProducerIntegrationTest.java
index 2ed9f41..ba851a6 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/PresettledProducerIntegrationTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/PresettledProducerIntegrationTest.java
@@ -29,6 +29,7 @@ import static org.junit.Assert.fail;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
+import javax.jms.CompletionListener;
 import javax.jms.Connection;
 import javax.jms.Destination;
 import javax.jms.Message;
@@ -40,7 +41,6 @@ import javax.jms.TemporaryTopic;
 import javax.jms.TextMessage;
 import javax.jms.Topic;
 
-import org.apache.qpid.jms.JmsCompletionListener;
 import org.apache.qpid.jms.JmsMessageProducer;
 import org.apache.qpid.jms.test.QpidJmsTestCase;
 import org.apache.qpid.jms.test.testpeer.ListDescribedType;
@@ -608,7 +608,7 @@ public class PresettledProducerIntegrationTest extends QpidJmsTestCase {
         }
     }
 
-    private class TestJmsCompletionListener implements JmsCompletionListener {
+    private class TestJmsCompletionListener implements CompletionListener {
 
         private final CountDownLatch completed;
 

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ProducerIntegrationTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ProducerIntegrationTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ProducerIntegrationTest.java
index 1e69eb8..0dee795 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ProducerIntegrationTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ProducerIntegrationTest.java
@@ -44,6 +44,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 
 import javax.jms.BytesMessage;
+import javax.jms.CompletionListener;
 import javax.jms.Connection;
 import javax.jms.DeliveryMode;
 import javax.jms.ExceptionListener;
@@ -57,7 +58,6 @@ import javax.jms.Session;
 import javax.jms.TextMessage;
 import javax.jms.Topic;
 
-import org.apache.qpid.jms.JmsCompletionListener;
 import org.apache.qpid.jms.JmsConnection;
 import org.apache.qpid.jms.JmsConnectionFactory;
 import org.apache.qpid.jms.JmsDefaultConnectionListener;
@@ -2381,7 +2381,7 @@ public class ProducerIntegrationTest extends QpidJmsTestCase {
         }
     }
 
-    private class TestJmsCompletionListener implements JmsCompletionListener {
+    private class TestJmsCompletionListener implements CompletionListener {
 
         private final CountDownLatch completed;
 

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/SessionIntegrationTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/SessionIntegrationTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/SessionIntegrationTest.java
index 34d60f1..7e5744f 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/SessionIntegrationTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/SessionIntegrationTest.java
@@ -37,6 +37,7 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import javax.jms.CompletionListener;
 import javax.jms.Connection;
 import javax.jms.Destination;
 import javax.jms.IllegalStateException;
@@ -57,7 +58,6 @@ import javax.jms.TextMessage;
 import javax.jms.Topic;
 import javax.jms.TopicSubscriber;
 
-import org.apache.qpid.jms.JmsCompletionListener;
 import org.apache.qpid.jms.JmsConnection;
 import org.apache.qpid.jms.JmsDefaultConnectionListener;
 import org.apache.qpid.jms.JmsMessageProducer;
@@ -1198,6 +1198,33 @@ public class SessionIntegrationTest extends QpidJmsTestCase {
     }
 
     @Test(timeout = 20000)
+    public void testCreateDurableConsumer() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            Connection connection = testFixture.establishConnecton(testPeer);
+            connection.start();
+
+            testPeer.expectBegin();
+            Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+            String topicName = "myTopic";
+            Topic dest = session.createTopic(topicName);
+            String subscriptionName = "mySubscription";
+
+            testPeer.expectDurableSubscriberAttach(topicName, subscriptionName);
+            testPeer.expectLinkFlow();
+
+            MessageConsumer consumer = session.createDurableConsumer(dest, subscriptionName);
+            assertNotNull("MessageConsumer object was null", consumer);
+            assertNull("MessageConsumer should not have a selector", consumer.getMessageSelector());
+
+            testPeer.expectClose();
+            connection.close();
+
+            testPeer.waitForAllHandlersToComplete(1000);
+        }
+    }
+
+    @Test(timeout = 20000)
     public void testDurableSubscriptionUnsubscribeInUseThrowsJMSEx() throws Exception {
         try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
             Connection connection = testFixture.establishConnecton(testPeer);
@@ -1995,7 +2022,7 @@ public class SessionIntegrationTest extends QpidJmsTestCase {
         }
     }
 
-    private class TestJmsCompletionListener implements JmsCompletionListener {
+    private class TestJmsCompletionListener implements CompletionListener {
 
         private final CountDownLatch completed;
 

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/StreamMessageIntegrationTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/StreamMessageIntegrationTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/StreamMessageIntegrationTest.java
index 21e26f8..507d34e 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/StreamMessageIntegrationTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/StreamMessageIntegrationTest.java
@@ -21,8 +21,10 @@ package org.apache.qpid.jms.integration;
 import static org.hamcrest.Matchers.equalTo;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -30,6 +32,7 @@ import java.util.List;
 import javax.jms.Connection;
 import javax.jms.Message;
 import javax.jms.MessageConsumer;
+import javax.jms.MessageFormatException;
 import javax.jms.MessageProducer;
 import javax.jms.Queue;
 import javax.jms.Session;
@@ -106,10 +109,10 @@ public class StreamMessageIntegrationTest extends QpidJmsTestCase {
             testPeer.expectReceiverAttach();
             testPeer.expectLinkFlowRespondWithTransfer(null, msgAnnotations, null, null, amqpValueSectionContent);
             testPeer.expectDispositionThatIsAcceptedAndSettled();
+            testPeer.expectClose();
 
             MessageConsumer messageConsumer = session.createConsumer(queue);
             Message receivedMessage = messageConsumer.receive(3000);
-            testPeer.waitForAllHandlersToComplete(3000);
 
             //verify the content is as expected
             assertNotNull("Message was not received", receivedMessage);
@@ -127,6 +130,33 @@ public class StreamMessageIntegrationTest extends QpidJmsTestCase {
             assertEquals("Unexpected long value", myLong, receivedStreamMessage.readLong());
             assertEquals("Unexpected short value", myShort, receivedStreamMessage.readShort());
             assertEquals("Unexpected UTF value", myString, receivedStreamMessage.readString());
+
+            assertFalse(receivedStreamMessage.isBodyAssignableTo(String.class));
+            assertFalse(receivedStreamMessage.isBodyAssignableTo(Object.class));
+            assertFalse(receivedStreamMessage.isBodyAssignableTo(Boolean.class));
+            assertFalse(receivedStreamMessage.isBodyAssignableTo(byte[].class));
+
+            try {
+                receivedStreamMessage.getBody(Object.class);
+                fail("Cannot read TextMessage with this type.");
+            } catch (MessageFormatException mfe) {
+            }
+
+            try {
+                receivedStreamMessage.getBody(byte[].class);
+                fail("Cannot read TextMessage with this type.");
+            } catch (MessageFormatException mfe) {
+            }
+
+            try {
+                receivedStreamMessage.getBody(String.class);
+                fail("Cannot read TextMessage with this type.");
+            } catch (MessageFormatException mfe) {
+            }
+
+            connection.close();
+
+            testPeer.waitForAllHandlersToComplete(3000);
         }
     }
 
@@ -204,9 +234,38 @@ public class StreamMessageIntegrationTest extends QpidJmsTestCase {
             messageMatcher.setPropertiesMatcher(propertiesMatcher);
             messageMatcher.setMessageContentMatcher(new EncodedAmqpSequenceMatcher(list));
 
-            //send the message
             testPeer.expectTransfer(messageMatcher);
+            testPeer.expectClose();
+
+            //send the message
             producer.send(streamMessage);
+
+            assertFalse(streamMessage.isBodyAssignableTo(String.class));
+            assertFalse(streamMessage.isBodyAssignableTo(Object.class));
+            assertFalse(streamMessage.isBodyAssignableTo(Boolean.class));
+            assertFalse(streamMessage.isBodyAssignableTo(byte[].class));
+
+            try {
+                streamMessage.getBody(Object.class);
+                fail("Cannot read TextMessage with this type.");
+            } catch (MessageFormatException mfe) {
+            }
+
+            try {
+                streamMessage.getBody(byte[].class);
+                fail("Cannot read TextMessage with this type.");
+            } catch (MessageFormatException mfe) {
+            }
+
+            try {
+                streamMessage.getBody(String.class);
+                fail("Cannot read TextMessage with this type.");
+            } catch (MessageFormatException mfe) {
+            }
+
+            connection.close();
+
+            testPeer.waitForAllHandlersToComplete(3000);
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/TextMessageIntegrationTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/TextMessageIntegrationTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/TextMessageIntegrationTest.java
index b7b27e8..2869d03 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/TextMessageIntegrationTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/TextMessageIntegrationTest.java
@@ -20,9 +20,11 @@ package org.apache.qpid.jms.integration;
 
 import static org.hamcrest.Matchers.equalTo;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.io.IOException;
 
@@ -30,6 +32,7 @@ import javax.jms.Connection;
 import javax.jms.JMSException;
 import javax.jms.Message;
 import javax.jms.MessageConsumer;
+import javax.jms.MessageFormatException;
 import javax.jms.MessageProducer;
 import javax.jms.Queue;
 import javax.jms.Session;
@@ -75,10 +78,27 @@ public class TextMessageIntegrationTest extends QpidJmsTestCase {
             messageMatcher.setPropertiesMatcher(propsMatcher);
             messageMatcher.setMessageContentMatcher(new EncodedAmqpValueMatcher(text));
             testPeer.expectTransfer(messageMatcher);
+            testPeer.expectClose();
 
             Message message = session.createTextMessage(text);
-
             producer.send(message);
+
+            assertTrue(message.isBodyAssignableTo(String.class));
+            assertTrue(message.isBodyAssignableTo(Object.class));
+            assertFalse(message.isBodyAssignableTo(Boolean.class));
+            assertFalse(message.isBodyAssignableTo(byte[].class));
+
+            assertNotNull(message.getBody(Object.class));
+            assertNotNull(message.getBody(String.class));
+            try {
+                message.getBody(byte[].class);
+                fail("Cannot read TextMessage with this type.");
+            } catch (MessageFormatException mfe) {
+            }
+
+            connection.close();
+
+            testPeer.waitForAllHandlersToComplete(1000);
         }
     }
 
@@ -99,14 +119,31 @@ public class TextMessageIntegrationTest extends QpidJmsTestCase {
             testPeer.expectReceiverAttach();
             testPeer.expectLinkFlowRespondWithTransfer(null, null, null, null, amqpValueStringContent);
             testPeer.expectDispositionThatIsAcceptedAndSettled();
+            testPeer.expectClose();
 
             MessageConsumer messageConsumer = session.createConsumer(queue);
             Message receivedMessage = messageConsumer.receive(3000);
-            testPeer.waitForAllHandlersToComplete(3000);
+
+            assertTrue(receivedMessage.isBodyAssignableTo(String.class));
+            assertTrue(receivedMessage.isBodyAssignableTo(Object.class));
+            assertFalse(receivedMessage.isBodyAssignableTo(Boolean.class));
+            assertFalse(receivedMessage.isBodyAssignableTo(byte[].class));
+
+            assertNotNull(receivedMessage.getBody(Object.class));
+            assertNotNull(receivedMessage.getBody(String.class));
+            try {
+                receivedMessage.getBody(byte[].class);
+                fail("Cannot read TextMessage with this type.");
+            } catch (MessageFormatException mfe) {
+            }
 
             assertNotNull(receivedMessage);
             assertTrue(receivedMessage instanceof TextMessage);
             assertEquals(expectedMessageContent, ((TextMessage) receivedMessage).getText());
+
+            connection.close();
+
+            testPeer.waitForAllHandlersToComplete(3000);
         }
     }
 
@@ -131,10 +168,21 @@ public class TextMessageIntegrationTest extends QpidJmsTestCase {
             messageMatcher.setPropertiesMatcher(propsMatcher);
             messageMatcher.setMessageContentMatcher(new EncodedAmqpValueMatcher(null));
             testPeer.expectTransfer(messageMatcher);
+            testPeer.expectClose();
 
             Message message = session.createTextMessage();
 
             producer.send(message);
+
+            // Message has no content so all are assignable
+            assertTrue(message.isBodyAssignableTo(String.class));
+            assertTrue(message.isBodyAssignableTo(Object.class));
+            assertTrue(message.isBodyAssignableTo(Boolean.class));
+            assertTrue(message.isBodyAssignableTo(byte[].class));
+
+            connection.close();
+
+            testPeer.waitForAllHandlersToComplete(3000);
         }
     }
 
@@ -154,14 +202,24 @@ public class TextMessageIntegrationTest extends QpidJmsTestCase {
             testPeer.expectReceiverAttach();
             testPeer.expectLinkFlowRespondWithTransfer(null, null, null, null, amqpValueNullContent);
             testPeer.expectDispositionThatIsAcceptedAndSettled();
+            testPeer.expectClose();
 
             MessageConsumer messageConsumer = session.createConsumer(queue);
             Message receivedMessage = messageConsumer.receive(3000);
-            testPeer.waitForAllHandlersToComplete(3000);
 
             assertNotNull(receivedMessage);
             assertTrue(receivedMessage instanceof TextMessage);
             assertNull(((TextMessage) receivedMessage).getText());
+
+            // Message has no content so all are assignable
+            assertTrue(receivedMessage.isBodyAssignableTo(String.class));
+            assertTrue(receivedMessage.isBodyAssignableTo(Object.class));
+            assertTrue(receivedMessage.isBodyAssignableTo(Boolean.class));
+            assertTrue(receivedMessage.isBodyAssignableTo(byte[].class));
+
+            connection.close();
+
+            testPeer.waitForAllHandlersToComplete(3000);
         }
     }
 
@@ -232,16 +290,33 @@ public class TextMessageIntegrationTest extends QpidJmsTestCase {
             testPeer.expectReceiverAttach();
             testPeer.expectLinkFlowRespondWithTransfer(null, null, properties, null, dataContent);
             testPeer.expectDispositionThatIsAcceptedAndSettled();
+            testPeer.expectClose();
 
             MessageConsumer messageConsumer = session.createConsumer(queue);
             Message receivedMessage = messageConsumer.receive(3000);
-            testPeer.waitForAllHandlersToComplete(3000);
 
             assertNotNull(receivedMessage);
             assertTrue(receivedMessage instanceof TextMessage);
             String text = ((TextMessage) receivedMessage).getText();
 
             assertEquals(expectedString, text);
+
+            assertTrue(receivedMessage.isBodyAssignableTo(String.class));
+            assertTrue(receivedMessage.isBodyAssignableTo(Object.class));
+            assertFalse(receivedMessage.isBodyAssignableTo(Boolean.class));
+            assertFalse(receivedMessage.isBodyAssignableTo(byte[].class));
+
+            assertNotNull(receivedMessage.getBody(Object.class));
+            assertNotNull(receivedMessage.getBody(String.class));
+            try {
+                receivedMessage.getBody(byte[].class);
+                fail("Cannot read TextMessage with this type.");
+            } catch (MessageFormatException mfe) {
+            }
+
+            connection.close();
+
+            testPeer.waitForAllHandlersToComplete(3000);
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/JmsMessageTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/JmsMessageTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/JmsMessageTest.java
index a0994a6..16cc9a7 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/JmsMessageTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/JmsMessageTest.java
@@ -26,13 +26,21 @@ import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import java.io.Serializable;
 import java.util.Enumeration;
+import java.util.Map;
+import java.util.UUID;
 
+import javax.jms.BytesMessage;
 import javax.jms.DeliveryMode;
 import javax.jms.JMSException;
+import javax.jms.MapMessage;
 import javax.jms.Message;
 import javax.jms.MessageFormatException;
 import javax.jms.MessageNotWriteableException;
+import javax.jms.ObjectMessage;
+import javax.jms.StreamMessage;
+import javax.jms.TextMessage;
 
 import org.apache.qpid.jms.JmsAcknowledgeCallback;
 import org.apache.qpid.jms.JmsConnection;
@@ -1498,6 +1506,333 @@ public class JmsMessageTest {
         assertEquals("Unexpected ack type value after setting prop", RELEASED, callback.getAckType());
     }
 
+    //--------- Test isBodyAssignableTo method -------------------------------//
+
+    @Test
+    public void testMessageIsBodyAssignableTo() throws Exception {
+        Message message = factory.createMessage();
+
+        assertTrue(message.isBodyAssignableTo(String.class));
+        assertTrue(message.isBodyAssignableTo(Boolean.class));
+        assertTrue(message.isBodyAssignableTo(Object.class));
+        assertTrue(message.isBodyAssignableTo(Map.class));
+    }
+
+    @Test
+    public void testTextMessageIsBodyAssignableTo() throws Exception {
+        JmsTextMessage message = factory.createTextMessage();
+
+        assertTrue(message.isBodyAssignableTo(String.class));
+        assertTrue(message.isBodyAssignableTo(Boolean.class));
+        assertTrue(message.isBodyAssignableTo(Map.class));
+        assertTrue(message.isBodyAssignableTo(Object.class));
+
+        message.setText("test");
+
+        assertTrue(message.isBodyAssignableTo(String.class));
+        assertFalse(message.isBodyAssignableTo(Boolean.class));
+        assertFalse(message.isBodyAssignableTo(Map.class));
+        assertTrue(message.isBodyAssignableTo(Object.class));
+    }
+
+    @Test
+    public void testStreamMessageIsBodyAssignableTo() throws Exception {
+        JmsStreamMessage message = factory.createStreamMessage();
+
+        assertFalse(message.isBodyAssignableTo(String.class));
+        assertFalse(message.isBodyAssignableTo(Boolean.class));
+        assertFalse(message.isBodyAssignableTo(Map.class));
+        assertFalse(message.isBodyAssignableTo(Object.class));
+
+        message.writeBoolean(false);
+
+        assertFalse(message.isBodyAssignableTo(String.class));
+        assertFalse(message.isBodyAssignableTo(Boolean.class));
+        assertFalse(message.isBodyAssignableTo(Map.class));
+        assertFalse(message.isBodyAssignableTo(Object.class));
+    }
+
+    @Test
+    public void testMapMessageIsBodyAssignableTo() throws Exception {
+        JmsMapMessage message = factory.createMapMessage();
+
+        assertTrue(message.isBodyAssignableTo(String.class));
+        assertTrue(message.isBodyAssignableTo(Boolean.class));
+        assertTrue(message.isBodyAssignableTo(Map.class));
+        assertTrue(message.isBodyAssignableTo(Object.class));
+
+        message.setBoolean("Boolean", true);
+
+        assertFalse(message.isBodyAssignableTo(String.class));
+        assertFalse(message.isBodyAssignableTo(Boolean.class));
+        assertTrue(message.isBodyAssignableTo(Map.class));
+        assertTrue(message.isBodyAssignableTo(Object.class));
+    }
+
+    @Test
+    public void testBytesMessageIsBodyAssignableTo() throws Exception {
+        JmsBytesMessage message = factory.createBytesMessage();
+
+        assertTrue(message.isBodyAssignableTo(byte[].class));
+        assertTrue(message.isBodyAssignableTo(Boolean.class));
+        assertTrue(message.isBodyAssignableTo(Map.class));
+        assertTrue(message.isBodyAssignableTo(String.class));
+        assertTrue(message.isBodyAssignableTo(Object.class));
+
+        message.writeBoolean(false);
+
+        // The message doesn't technically have a body until it is reset
+        message.reset();
+
+        assertTrue(message.isBodyAssignableTo(byte[].class));
+        assertFalse(message.isBodyAssignableTo(Boolean.class));
+        assertFalse(message.isBodyAssignableTo(Map.class));
+        assertFalse(message.isBodyAssignableTo(String.class));
+        assertTrue(message.isBodyAssignableTo(Object.class));
+    }
+
+    @Test
+    public void testObjectMessageIsBodyAssignableTo() throws Exception {
+        JmsObjectMessage message = factory.createObjectMessage();
+
+        assertTrue(message.isBodyAssignableTo(Boolean.class));
+        assertTrue(message.isBodyAssignableTo(Map.class));
+        assertTrue(message.isBodyAssignableTo(String.class));
+        assertTrue(message.isBodyAssignableTo(Serializable.class));
+        assertTrue(message.isBodyAssignableTo(Object.class));
+
+        message.setObject(UUID.randomUUID());
+
+        assertFalse(message.isBodyAssignableTo(Boolean.class));
+        assertFalse(message.isBodyAssignableTo(Map.class));
+        assertFalse(message.isBodyAssignableTo(String.class));
+        assertTrue(message.isBodyAssignableTo(Serializable.class));
+        assertTrue(message.isBodyAssignableTo(Object.class));
+        assertTrue(message.isBodyAssignableTo(UUID.class));
+    }
+
+    //--------- Test for getBody method --------------------------------------//
+
+    @Test
+    public void testGetBodyOnMessage() throws Exception {
+        Message message = factory.createMessage();
+
+        assertNull(message.getBody(String.class));
+        assertNull(message.getBody(Boolean.class));
+        assertNull(message.getBody(byte[].class));
+        assertNull(message.getBody(Object.class));
+    }
+
+    @Test
+    public void testGetBodyOnTextMessage() throws Exception {
+        TextMessage message = factory.createTextMessage();
+
+        assertNull(message.getBody(String.class));
+        assertNull(message.getBody(Boolean.class));
+        assertNull(message.getBody(byte[].class));
+        assertNull(message.getBody(Object.class));
+
+        message.setText("test");
+
+        assertNotNull(message.getBody(String.class));
+        assertNotNull(message.getBody(Object.class));
+
+        try {
+            message.getBody(Boolean.class);
+            fail("Should have thrown an exception");
+        } catch (MessageFormatException mfe) {
+            LOG.info("caught expected MessageFormatException");
+        }
+
+        try {
+            message.getBody(Map.class);
+            fail("Should have thrown an exception");
+        } catch (MessageFormatException mfe) {
+            LOG.info("caught expected MessageFormatException");
+        }
+
+        try {
+            message.getBody(byte[].class);
+            fail("Should have thrown an exception");
+        } catch (MessageFormatException mfe) {
+            LOG.info("caught expected MessageFormatException");
+        }
+    }
+
+    @Test
+    public void testGetBodyOnMapMessage() throws Exception {
+        MapMessage message = factory.createMapMessage();
+
+        assertNull(message.getBody(String.class));
+        assertNull(message.getBody(Boolean.class));
+        assertNull(message.getBody(byte[].class));
+        assertNull(message.getBody(Object.class));
+
+        message.setString("test", "test");
+
+        assertNotNull(message.getBody(Map.class));
+        assertNotNull(message.getBody(Object.class));
+
+        try {
+            message.getBody(Boolean.class);
+            fail("Should have thrown an exception");
+        } catch (MessageFormatException mfe) {
+            LOG.info("caught expected MessageFormatException");
+        }
+
+        try {
+            message.getBody(String.class);
+            fail("Should have thrown an exception");
+        } catch (MessageFormatException mfe) {
+            LOG.info("caught expected MessageFormatException");
+        }
+
+        try {
+            message.getBody(byte[].class);
+            fail("Should have thrown an exception");
+        } catch (MessageFormatException mfe) {
+            LOG.info("caught expected MessageFormatException");
+        }
+    }
+
+    @Test
+    public void testGetBodyOnObjectMessage() throws Exception {
+        ObjectMessage message = factory.createObjectMessage();
+
+        assertNull(message.getBody(String.class));
+        assertNull(message.getBody(Boolean.class));
+        assertNull(message.getBody(byte[].class));
+        assertNull(message.getBody(Serializable.class));
+        assertNull(message.getBody(Object.class));
+
+        message.setObject(UUID.randomUUID());
+
+        assertNotNull(message.getBody(UUID.class));
+        assertNotNull(message.getBody(Serializable.class));
+        assertNotNull(message.getBody(Object.class));
+
+        try {
+            message.getBody(Boolean.class);
+            fail("Should have thrown an exception");
+        } catch (MessageFormatException mfe) {
+            LOG.info("caught expected MessageFormatException");
+        }
+
+        try {
+            message.getBody(String.class);
+            fail("Should have thrown an exception");
+        } catch (MessageFormatException mfe) {
+            LOG.info("caught expected MessageFormatException");
+        }
+
+        try {
+            message.getBody(byte[].class);
+            fail("Should have thrown an exception");
+        } catch (MessageFormatException mfe) {
+            LOG.info("caught expected MessageFormatException");
+        }
+    }
+
+    @Test
+    public void testGetBodyOnBytesMessage() throws Exception {
+        BytesMessage message = factory.createBytesMessage();
+
+        assertNull(message.getBody(String.class));
+        assertNull(message.getBody(Boolean.class));
+        assertNull(message.getBody(byte[].class));
+        assertNull(message.getBody(Object.class));
+
+        message.writeUTF("test");
+        message.reset();
+
+        assertNotNull(message.getBody(byte[].class));
+        assertNotNull(message.getBody(Object.class));
+
+        try {
+            message.getBody(Boolean.class);
+            fail("Should have thrown an exception");
+        } catch (MessageFormatException mfe) {
+            LOG.info("caught expected MessageFormatException");
+        }
+
+        try {
+            message.getBody(Map.class);
+            fail("Should have thrown an exception");
+        } catch (MessageFormatException mfe) {
+            LOG.info("caught expected MessageFormatException");
+        }
+
+        try {
+            message.getBody(String.class);
+            fail("Should have thrown an exception");
+        } catch (MessageFormatException mfe) {
+            LOG.info("caught expected MessageFormatException");
+        }
+    }
+
+    @Test
+    public void testGetBodyOnStreamMessage() throws Exception {
+        StreamMessage message = factory.createStreamMessage();
+
+        try {
+            message.getBody(Object.class);
+            fail("Should have thrown an exception");
+        } catch (MessageFormatException mfe) {
+            LOG.info("caught expected MessageFormatException");
+        }
+
+        try {
+            message.getBody(Boolean.class);
+            fail("Should have thrown an exception");
+        } catch (MessageFormatException mfe) {
+            LOG.info("caught expected MessageFormatException");
+        }
+
+        try {
+            message.getBody(String.class);
+            fail("Should have thrown an exception");
+        } catch (MessageFormatException mfe) {
+            LOG.info("caught expected MessageFormatException");
+        }
+
+        try {
+            message.getBody(byte[].class);
+            fail("Should have thrown an exception");
+        } catch (MessageFormatException mfe) {
+            LOG.info("caught expected MessageFormatException");
+        }
+
+        message.writeBoolean(false);
+
+        try {
+            message.getBody(Object.class);
+            fail("Should have thrown an exception");
+        } catch (MessageFormatException mfe) {
+            LOG.info("caught expected MessageFormatException");
+        }
+
+        try {
+            message.getBody(Boolean.class);
+            fail("Should have thrown an exception");
+        } catch (MessageFormatException mfe) {
+            LOG.info("caught expected MessageFormatException");
+        }
+
+        try {
+            message.getBody(String.class);
+            fail("Should have thrown an exception");
+        } catch (MessageFormatException mfe) {
+            LOG.info("caught expected MessageFormatException");
+        }
+
+        try {
+            message.getBody(byte[].class);
+            fail("Should have thrown an exception");
+        } catch (MessageFormatException mfe) {
+            LOG.info("caught expected MessageFormatException");
+        }
+    }
+
     //--------- Test support method ------------------------------------------//
 
     private void assertGetMissingPropertyThrowsNumberFormatException(JmsMessage testMessage, String propertyName, Class<?> clazz) throws JMSException {

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestBytesMessageFacade.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestBytesMessageFacade.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestBytesMessageFacade.java
index 286682c..8b53651 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestBytesMessageFacade.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestBytesMessageFacade.java
@@ -16,11 +16,6 @@
  */
 package org.apache.qpid.jms.message.facade.test;
 
-import io.netty.buffer.ByteBuf;
-import io.netty.buffer.ByteBufInputStream;
-import io.netty.buffer.ByteBufOutputStream;
-import io.netty.buffer.Unpooled;
-
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -30,6 +25,11 @@ import javax.jms.JMSException;
 
 import org.apache.qpid.jms.message.facade.JmsBytesMessageFacade;
 
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufInputStream;
+import io.netty.buffer.ByteBufOutputStream;
+import io.netty.buffer.Unpooled;
+
 /**
  * A test implementation of the JmsBytesMessageFacade that simply holds a raw Buffer
  */
@@ -133,4 +133,19 @@ public final class JmsTestBytesMessageFacade extends JmsTestMessageFacade implem
     public int getBodyLength() {
         return content.readableBytes();
     }
+
+    @Override
+    public boolean hasBody() {
+        return content.isReadable();
+    }
+
+    @Override
+    public byte[] copyBody() {
+        ByteBuf duplicate = content.duplicate();
+        byte[] result = new byte[content.readableBytes()];
+
+        duplicate.readBytes(result);
+
+        return result;
+    }
 }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestMapMessageFacade.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestMapMessageFacade.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestMapMessageFacade.java
index 8629cd9..6f6b8c5 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestMapMessageFacade.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestMapMessageFacade.java
@@ -72,4 +72,9 @@ public class JmsTestMapMessageFacade extends JmsTestMessageFacade implements Jms
     public void clearBody() {
         map.clear();
     }
+
+    @Override
+    public boolean hasBody() {
+        return !map.isEmpty();
+    }
 }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestMessageFacade.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestMessageFacade.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestMessageFacade.java
index 3a6bf93..f1ccb4c 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestMessageFacade.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestMessageFacade.java
@@ -60,6 +60,7 @@ public class JmsTestMessageFacade implements JmsMessageFacade {
     protected int groupSequence;
     protected Object messageId;
     protected long expiration;
+    protected long deliveryTime;
     protected long timestamp;
     protected String correlationId;
     protected boolean persistent;
@@ -283,6 +284,16 @@ public class JmsTestMessageFacade implements JmsMessageFacade {
     }
 
     @Override
+    public long getDeliveryTime() {
+        return deliveryTime;
+    }
+
+    @Override
+    public void setDeliveryTime(long deliveryTime) {
+        this.deliveryTime = deliveryTime;
+    }
+
+    @Override
     public JmsDestination getDestination() {
         return this.destination;
     }
@@ -345,4 +356,9 @@ public class JmsTestMessageFacade implements JmsMessageFacade {
     public void setGroupSequence(int groupSequence) {
         this.groupSequence = groupSequence;
     }
+
+    @Override
+    public boolean hasBody() {
+        return false;
+    }
 }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestObjectMessageFacade.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestObjectMessageFacade.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestObjectMessageFacade.java
index 44bab21..a2fc530 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestObjectMessageFacade.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestObjectMessageFacade.java
@@ -96,4 +96,9 @@ public class JmsTestObjectMessageFacade extends JmsTestMessageFacade implements
 
         this.object = serialized;
     }
+
+    @Override
+    public boolean hasBody() {
+        return object != null && object.length > 0;
+    }
 }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestStreamMessageFacade.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestStreamMessageFacade.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestStreamMessageFacade.java
index 983569c..aadee67 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestStreamMessageFacade.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestStreamMessageFacade.java
@@ -82,4 +82,9 @@ public class JmsTestStreamMessageFacade extends JmsTestMessageFacade implements
     public void reset() {
         index = -1;
     }
+
+    @Override
+    public boolean hasBody() {
+        return !stream.isEmpty();
+    }
 }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestTextMessageFacade.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestTextMessageFacade.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestTextMessageFacade.java
index f701570..a4a103f 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestTextMessageFacade.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestTextMessageFacade.java
@@ -54,4 +54,9 @@ public final class JmsTestTextMessageFacade extends JmsTestMessageFacade impleme
     public void setText(String text) {
         this.text = text;
     }
+
+    @Override
+    public boolean hasBody() {
+        return text != null;
+    }
 }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/foreign/ForeignJmsMessage.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/foreign/ForeignJmsMessage.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/foreign/ForeignJmsMessage.java
index 8ef1643..51215a2 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/foreign/ForeignJmsMessage.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/foreign/ForeignJmsMessage.java
@@ -265,4 +265,24 @@ public class ForeignJmsMessage implements Message {
     public void clearBody() throws JMSException {
         message.clearBody();
     }
+
+    @Override
+    public long getJMSDeliveryTime() throws JMSException {
+        return message.getJMSDeliveryTime();
+    }
+
+    @Override
+    public void setJMSDeliveryTime(long delay) throws JMSException {
+        message.setJMSDeliveryTime(delay);
+    }
+
+    @Override
+    public <T> T getBody(Class<T> asType) throws JMSException {
+        return message.getBody(asType);
+    }
+
+    @Override
+    public boolean isBodyAssignableTo(@SuppressWarnings("rawtypes") Class target) throws JMSException {
+        return message.isBodyAssignableTo(target);
+    }
 }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/test/java/org/apache/qpid/jms/producer/JmsMessageProducerTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/producer/JmsMessageProducerTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/producer/JmsMessageProducerTest.java
index 7117e9f..a2629a9 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/producer/JmsMessageProducerTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/producer/JmsMessageProducerTest.java
@@ -26,6 +26,7 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
+import javax.jms.CompletionListener;
 import javax.jms.Connection;
 import javax.jms.DeliveryMode;
 import javax.jms.Destination;
@@ -35,7 +36,6 @@ import javax.jms.Message;
 import javax.jms.MessageProducer;
 import javax.jms.Session;
 
-import org.apache.qpid.jms.JmsCompletionListener;
 import org.apache.qpid.jms.JmsConnectionFactory;
 import org.apache.qpid.jms.JmsConnectionTestSupport;
 import org.apache.qpid.jms.JmsDestination;
@@ -541,7 +541,7 @@ public class JmsMessageProducerTest extends JmsConnectionTestSupport {
         }
     }
 
-    private class MyCompletionListener implements JmsCompletionListener {
+    private class MyCompletionListener implements CompletionListener {
 
         private final List<Message> completed = new ArrayList<Message>();
         private final List<Message> failed = new ArrayList<Message>();

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/test/java/org/apache/qpid/jms/producer/JmsProducerTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/producer/JmsProducerTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/producer/JmsProducerTest.java
new file mode 100644
index 0000000..5a0938a
--- /dev/null
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/producer/JmsProducerTest.java
@@ -0,0 +1,182 @@
+/*
+ * 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.producer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.UUID;
+
+import javax.jms.JMSProducer;
+import javax.jms.MessageFormatRuntimeException;
+
+import org.apache.qpid.jms.JmsConnectionTestSupport;
+import org.apache.qpid.jms.JmsContext;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test various behaviors of the JMSProducer implementation.
+ */
+public class JmsProducerTest extends JmsConnectionTestSupport {
+
+    private JmsContext context;
+
+    private final String BAD_PROPERTY_NAME = "%_BAD_PROPERTY_NAME";
+    private final String GOOD_PROPERTY_NAME = "GOOD_PROPERTY_NAME";
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        context = createJMSContextToMockProvider();
+    }
+
+    @Test
+    public void testGetPropertyNames() {
+        JMSProducer producer = context.createProducer();
+
+        producer.setProperty("Property_1", "1");
+        producer.setProperty("Property_2", "2");
+        producer.setProperty("Property_3", "3");
+
+        assertEquals(3, producer.getPropertyNames().size());
+
+        assertTrue(producer.getPropertyNames().contains("Property_1"));
+        assertTrue(producer.getPropertyNames().contains("Property_2"));
+        assertTrue(producer.getPropertyNames().contains("Property_3"));
+    }
+
+    @Test
+    public void testClearProperties() {
+        JMSProducer producer = context.createProducer();
+
+        producer.setProperty("Property_1", "1");
+        producer.setProperty("Property_2", "2");
+        producer.setProperty("Property_3", "3");
+
+        assertEquals(3, producer.getPropertyNames().size());
+
+        producer.clearProperties();
+
+        assertEquals(0, producer.getPropertyNames().size());
+    }
+
+    @Test
+    public void testSetStringPropetryWithBadPropetyName() {
+        JMSProducer producer = context.createProducer();
+
+        try {
+            producer.setProperty(BAD_PROPERTY_NAME, "X");
+            fail("Should not accept invalid property name");
+        } catch (IllegalArgumentException iae) {}
+    }
+
+    @Test
+    public void testSetBytePropetryWithBadPropetyName() {
+        JMSProducer producer = context.createProducer();
+
+        try {
+            producer.setProperty(BAD_PROPERTY_NAME, (byte) 1);
+            fail("Should not accept invalid property name");
+        } catch (IllegalArgumentException iae) {}
+    }
+
+    @Test
+    public void testSetBooleanPropetryWithBadPropetyName() {
+        JMSProducer producer = context.createProducer();
+
+        try {
+            producer.setProperty(BAD_PROPERTY_NAME, true);
+            fail("Should not accept invalid property name");
+        } catch (IllegalArgumentException iae) {}
+    }
+
+    @Test
+    public void testSetDoublePropetryWithBadPropetyName() {
+        JMSProducer producer = context.createProducer();
+
+        try {
+            producer.setProperty(BAD_PROPERTY_NAME, 100.0);
+            fail("Should not accept invalid property name");
+        } catch (IllegalArgumentException iae) {}
+    }
+
+    @Test
+    public void testSetFloatPropetryWithBadPropetyName() {
+        JMSProducer producer = context.createProducer();
+
+        try {
+            producer.setProperty(BAD_PROPERTY_NAME, 100.0f);
+            fail("Should not accept invalid property name");
+        } catch (IllegalArgumentException iae) {}
+    }
+
+    @Test
+    public void testSetShortPropetryWithBadPropetyName() {
+        JMSProducer producer = context.createProducer();
+
+        try {
+            producer.setProperty(BAD_PROPERTY_NAME, (short) 100);
+            fail("Should not accept invalid property name");
+        } catch (IllegalArgumentException iae) {}
+    }
+
+    @Test
+    public void testSetIntPropetryWithBadPropetyName() {
+        JMSProducer producer = context.createProducer();
+
+        try {
+            producer.setProperty(BAD_PROPERTY_NAME, 100);
+            fail("Should not accept invalid property name");
+        } catch (IllegalArgumentException iae) {}
+    }
+
+    @Test
+    public void testSetLongPropetryWithBadPropetyName() {
+        JMSProducer producer = context.createProducer();
+
+        try {
+            producer.setProperty(BAD_PROPERTY_NAME, 100l);
+            fail("Should not accept invalid property name");
+        } catch (IllegalArgumentException iae) {}
+    }
+
+    @Test
+    public void testSetObjectPropetryWithBadPropetyName() {
+        JMSProducer producer = context.createProducer();
+
+        try {
+            producer.setProperty(BAD_PROPERTY_NAME, UUID.randomUUID());
+            fail("Should not accept invalid property name");
+        } catch (IllegalArgumentException iae) {}
+    }
+
+    @Test
+    public void testSetObjectPropetryWithInvalidObject() {
+        JMSProducer producer = context.createProducer();
+
+        try {
+            producer.setProperty(GOOD_PROPERTY_NAME, UUID.randomUUID());
+            fail("Should not accept invalid property name");
+        } catch (MessageFormatRuntimeException mfre) {}
+    }
+}

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/failover/FailoverIntegrationTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/failover/FailoverIntegrationTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/failover/FailoverIntegrationTest.java
index d6dc443..301f93a 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/failover/FailoverIntegrationTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/failover/FailoverIntegrationTest.java
@@ -32,6 +32,7 @@ import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
+import javax.jms.CompletionListener;
 import javax.jms.Connection;
 import javax.jms.ConnectionFactory;
 import javax.jms.InvalidDestinationException;
@@ -46,7 +47,6 @@ import javax.jms.Session;
 import javax.jms.TextMessage;
 import javax.jms.Topic;
 
-import org.apache.qpid.jms.JmsCompletionListener;
 import org.apache.qpid.jms.JmsConnection;
 import org.apache.qpid.jms.JmsConnectionFactory;
 import org.apache.qpid.jms.JmsDefaultConnectionListener;
@@ -1228,7 +1228,7 @@ public class FailoverIntegrationTest extends QpidJmsTestCase {
         return "amqp://localhost:" + peer.getServerPort() + (params != null ? "?" + params : "");
     }
 
-    private class TestJmsCompletionListener implements JmsCompletionListener {
+    private class TestJmsCompletionListener implements CompletionListener {
 
         private final CountDownLatch completed;
 

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-interop-tests/qpid-jms-activemq-tests/src/test/java/org/apache/qpid/jms/JmsMessageIntegrityTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-interop-tests/qpid-jms-activemq-tests/src/test/java/org/apache/qpid/jms/JmsMessageIntegrityTest.java b/qpid-jms-interop-tests/qpid-jms-activemq-tests/src/test/java/org/apache/qpid/jms/JmsMessageIntegrityTest.java
index d09d7bc..c9ca39e 100644
--- a/qpid-jms-interop-tests/qpid-jms-activemq-tests/src/test/java/org/apache/qpid/jms/JmsMessageIntegrityTest.java
+++ b/qpid-jms-interop-tests/qpid-jms-activemq-tests/src/test/java/org/apache/qpid/jms/JmsMessageIntegrityTest.java
@@ -33,6 +33,7 @@ import javax.jms.JMSException;
 import javax.jms.MapMessage;
 import javax.jms.MessageConsumer;
 import javax.jms.MessageEOFException;
+import javax.jms.MessageFormatException;
 import javax.jms.MessageProducer;
 import javax.jms.ObjectMessage;
 import javax.jms.Session;
@@ -212,6 +213,7 @@ public class JmsMessageIntegrityTest extends AmqpTestSupport {
 
         public int deliveryMode;
 
+        private long deliveryTime;
         private String messageId;
         private long timestamp;
         private String correlationId;
@@ -447,6 +449,32 @@ public class JmsMessageIntegrityTest extends AmqpTestSupport {
         public String getText() throws JMSException {
             return text;
         }
+
+        @Override
+        public void setJMSDeliveryTime(long deliveryTime) throws JMSException {
+            this.deliveryTime = deliveryTime;
+        }
+
+        @Override
+        public long getJMSDeliveryTime() throws JMSException {
+            return deliveryTime;
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public <T> T getBody(Class<T> target) throws JMSException {
+            if (isBodyAssignableTo(target)) {
+                return (T) text;
+            }
+
+            throw new MessageFormatException("Cannot covert body to type: " + target.getName());
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public boolean isBodyAssignableTo(@SuppressWarnings("rawtypes") Class target) throws JMSException {
+            return target.isAssignableFrom(String.class);
+        }
     }
 
     // TODO - implement proper handling of foreign JMS Message and Destination types.


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


[6/7] qpid-jms git commit: QPIDJMS-207 Adds dependency on JMS 2.0 API and initial implementation.

Posted by ta...@apache.org.
QPIDJMS-207 Adds dependency on JMS 2.0 API and initial implementation.

Project: http://git-wip-us.apache.org/repos/asf/qpid-jms/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-jms/commit/0c39522c
Tree: http://git-wip-us.apache.org/repos/asf/qpid-jms/tree/0c39522c
Diff: http://git-wip-us.apache.org/repos/asf/qpid-jms/diff/0c39522c

Branch: refs/heads/master
Commit: 0c39522cde1c845d8b57978dddfa931ee440e9e3
Parents: 3a03663
Author: Timothy Bish <ta...@gmail.com>
Authored: Mon Sep 12 14:39:42 2016 -0400
Committer: Timothy Bish <ta...@gmail.com>
Committed: Mon Sep 12 14:39:42 2016 -0400

----------------------------------------------------------------------
 pom.xml                                         |   7 +-
 qpid-jms-client/pom.xml                         |   6 +-
 .../apache/qpid/jms/JmsCompletionListener.java  |  47 --
 .../java/org/apache/qpid/jms/JmsConnection.java |  24 +
 .../apache/qpid/jms/JmsConnectionFactory.java   |  34 +-
 .../java/org/apache/qpid/jms/JmsConsumer.java   | 135 +++++
 .../java/org/apache/qpid/jms/JmsContext.java    | 510 +++++++++++++++++
 .../org/apache/qpid/jms/JmsMessageConsumer.java |  53 ++
 .../org/apache/qpid/jms/JmsMessageProducer.java | 106 ++--
 .../java/org/apache/qpid/jms/JmsProducer.java   | 454 +++++++++++++++
 .../java/org/apache/qpid/jms/JmsSession.java    |  80 ++-
 .../jms/exceptions/JmsExceptionSupport.java     |  66 +++
 .../qpid/jms/message/JmsBytesMessage.java       |  15 +
 .../apache/qpid/jms/message/JmsMapMessage.java  |  24 +
 .../org/apache/qpid/jms/message/JmsMessage.java | 111 ++--
 .../message/JmsMessagePropertyIntercepter.java  |  73 +--
 .../jms/message/JmsMessagePropertySupport.java  | 124 ++++
 .../qpid/jms/message/JmsObjectMessage.java      |  19 +
 .../qpid/jms/message/JmsStreamMessage.java      |   5 +
 .../apache/qpid/jms/message/JmsTextMessage.java |  11 +
 .../message/facade/JmsBytesMessageFacade.java   |   6 +
 .../jms/message/facade/JmsMessageFacade.java    |  26 +
 .../amqp/message/AmqpJmsBytesMessageFacade.java |  24 +-
 .../amqp/message/AmqpJmsMapMessageFacade.java   |   5 +
 .../amqp/message/AmqpJmsMessageFacade.java      |  17 +
 .../message/AmqpJmsObjectMessageFacade.java     |   5 +
 .../message/AmqpJmsStreamMessageFacade.java     |   5 +
 .../amqp/message/AmqpJmsTextMessageFacade.java  |   9 +
 .../amqp/message/AmqpObjectTypeDelegate.java    |   3 +
 .../message/AmqpSerializedObjectDelegate.java   |   9 +
 .../amqp/message/AmqpTypedObjectDelegate.java   |   9 +
 .../org/apache/qpid/jms/JmsConnectionTest.java  |  10 +-
 .../qpid/jms/JmsConnectionTestSupport.java      |   9 +
 .../integration/ConnectionIntegrationTest.java  |  50 ++
 .../jms/integration/IntegrationTestFixture.java |  73 ++-
 .../integration/JMSConsumerIntegrationTest.java | 559 +++++++++++++++++++
 .../integration/JMSContextIntegrationTest.java  | 198 +++++++
 .../integration/JMSProducerIntegrationTest.java | 200 +++++++
 .../integration/MapMessageIntegrationTest.java  |  43 +-
 .../jms/integration/MessageIntegrationTest.java |  40 ++
 .../ObjectMessageIntegrationTest.java           | 116 +++-
 .../PresettledProducerIntegrationTest.java      |   4 +-
 .../integration/ProducerIntegrationTest.java    |   4 +-
 .../jms/integration/SessionIntegrationTest.java |  31 +-
 .../StreamMessageIntegrationTest.java           |  63 ++-
 .../integration/TextMessageIntegrationTest.java |  83 ++-
 .../apache/qpid/jms/message/JmsMessageTest.java | 335 +++++++++++
 .../facade/test/JmsTestBytesMessageFacade.java  |  25 +-
 .../facade/test/JmsTestMapMessageFacade.java    |   5 +
 .../facade/test/JmsTestMessageFacade.java       |  16 +
 .../facade/test/JmsTestObjectMessageFacade.java |   5 +
 .../facade/test/JmsTestStreamMessageFacade.java |   5 +
 .../facade/test/JmsTestTextMessageFacade.java   |   5 +
 .../jms/message/foreign/ForeignJmsMessage.java  |  20 +
 .../jms/producer/JmsMessageProducerTest.java    |   4 +-
 .../qpid/jms/producer/JmsProducerTest.java      | 182 ++++++
 .../failover/FailoverIntegrationTest.java       |   4 +-
 .../qpid/jms/JmsMessageIntegrityTest.java       |  28 +
 58 files changed, 3825 insertions(+), 314 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index bd7a642..fbb9396 100644
--- a/pom.xml
+++ b/pom.xml
@@ -46,7 +46,8 @@
     <proton-version>0.14.0</proton-version>
     <netty-version>4.0.41.Final</netty-version>
     <slf4j-version>1.7.21</slf4j-version>
-    <geronimo-jms-1-1-spec-version>1.1.1</geronimo-jms-1-1-spec-version>
+    <!-- <geronimo-jms-1-1-spec-version>1.1.1</geronimo-jms-1-1-spec-version> -->
+    <geronimo.jms.2.spec.version>1.0-alpha-2</geronimo.jms.2.spec.version>
     <!-- Test Dependency Versions for this Project -->
     <activemq-version>5.14.0</activemq-version>
     <junit-version>4.12</junit-version>
@@ -113,8 +114,8 @@
       </dependency>
       <dependency>
         <groupId>org.apache.geronimo.specs</groupId>
-        <artifactId>geronimo-jms_1.1_spec</artifactId>
-        <version>${geronimo-jms-1-1-spec-version}</version>
+        <artifactId>geronimo-jms_2.0_spec</artifactId>
+        <version>${geronimo.jms.2.spec.version}</version>
       </dependency>
       <dependency>
         <groupId>io.netty</groupId>

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/pom.xml
----------------------------------------------------------------------
diff --git a/qpid-jms-client/pom.xml b/qpid-jms-client/pom.xml
index a5d1dd6..24b647e 100644
--- a/qpid-jms-client/pom.xml
+++ b/qpid-jms-client/pom.xml
@@ -38,8 +38,12 @@
     </dependency>
     <dependency>
       <groupId>org.apache.geronimo.specs</groupId>
-      <artifactId>geronimo-jms_1.1_spec</artifactId>
+      <artifactId>geronimo-jms_2.0_spec</artifactId>
     </dependency>
+    <!-- <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-jms_1.1_spec</artifactId>
+    </dependency> -->
     <dependency>
       <groupId>org.apache.qpid</groupId>
       <artifactId>proton-j</artifactId>

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsCompletionListener.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsCompletionListener.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsCompletionListener.java
deleted file mode 100644
index 7a6c4d6..0000000
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsCompletionListener.java
+++ /dev/null
@@ -1,47 +0,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.
- */
-package org.apache.qpid.jms;
-
-import javax.jms.Message;
-
-/**
- * Interface used to implement listeners for asynchronous {@link javax.jms.Message}
- * sends which will be notified on successful completion of a send or be notified of an
- * error that was encountered while attempting to send a {@link javax.jms.Message}.
- */
-public interface JmsCompletionListener {
-
-    /**
-     * Called when an asynchronous send operation completes successfully.
-     *
-     * @param message
-     *      the {@link javax.jms.Message} that was successfully sent.
-     */
-    void onCompletion(Message message);
-
-    /**
-     * Called when an asynchronous send operation fails to complete, the state
-     * of the send is unknown at this point.
-     *
-     * @param message
-     *      the {@link javax.jms.Message} that was to be sent.
-     * @param exception
-     *      the {@link java.lang.Exception} that describes the send error.
-     */
-    void onException(Message message, Exception exception);
-
-}

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsConnection.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsConnection.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsConnection.java
index a04d1b3..79fe8d8 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsConnection.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsConnection.java
@@ -257,6 +257,16 @@ public class JmsConnection implements AutoCloseable, Connection, TopicConnection
     }
 
     @Override
+    public Session createSession() throws JMSException {
+        return createSession(false, Session.AUTO_ACKNOWLEDGE);
+    }
+
+    @Override
+    public Session createSession(int acknowledgeMode) throws JMSException {
+        return createSession(acknowledgeMode == Session.SESSION_TRANSACTED ? true : false, acknowledgeMode);
+    }
+
+    @Override
     public Session createSession(boolean transacted, int acknowledgeMode) throws JMSException {
         checkClosedOrFailed();
         connect();
@@ -347,6 +357,20 @@ public class JmsConnection implements AutoCloseable, Connection, TopicConnection
     }
 
     @Override
+    public ConnectionConsumer createSharedConnectionConsumer(Topic topic, String subscriptionName, String messageSelector, ServerSessionPool sessionPool, int maxMessages) throws JMSException {
+        checkClosedOrFailed();
+        connect();
+        throw new JMSException("Not supported");
+    }
+
+    @Override
+    public ConnectionConsumer createSharedDurableConnectionConsumer(Topic topic, String subscriptionName, String messageSelector, ServerSessionPool sessionPool, int maxMessages) throws JMSException {
+        checkClosedOrFailed();
+        connect();
+        throw new JMSException("Not supported");
+    }
+
+    @Override
     public ConnectionConsumer createDurableConnectionConsumer(Topic topic, String subscriptionName,
                                                               String messageSelector, ServerSessionPool sessionPool, int maxMessages) throws JMSException {
         checkClosedOrFailed();

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsConnectionFactory.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsConnectionFactory.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsConnectionFactory.java
index 217749c..4105842 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsConnectionFactory.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsConnectionFactory.java
@@ -25,6 +25,7 @@ import java.util.Map;
 import javax.jms.Connection;
 import javax.jms.ConnectionFactory;
 import javax.jms.ExceptionListener;
+import javax.jms.JMSContext;
 import javax.jms.JMSException;
 import javax.jms.QueueConnection;
 import javax.jms.QueueConnectionFactory;
@@ -234,6 +235,35 @@ public class JmsConnectionFactory extends JNDIStorable implements ConnectionFact
         }
     }
 
+    //----- JMSContext Creation methods --------------------------------------//
+
+    @Override
+    public JMSContext createContext() {
+        return createContext(getUsername(), getPassword(), JMSContext.AUTO_ACKNOWLEDGE);
+    }
+
+    @Override
+    public JMSContext createContext(int sessionMode) {
+        return createContext(getUsername(), getPassword(), sessionMode);
+    }
+
+    @Override
+    public JMSContext createContext(String username, String password) {
+        return createContext(username, password, JMSContext.AUTO_ACKNOWLEDGE);
+    }
+
+    @Override
+    public JMSContext createContext(String username, String password, int sessionMode) {
+        try {
+            JmsConnection connection = (JmsConnection) createConnection(username, password);
+            return new JmsContext(connection, sessionMode);
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    //----- Internal Support Methods -----------------------------------------//
+
     protected Provider createProvider(URI remoteURI) throws Exception {
         if (remoteURI == null) {
             remoteURI = new URI(getDefaultRemoteAddress());
@@ -279,9 +309,7 @@ public class JmsConnectionFactory extends JNDIStorable implements ConnectionFact
         this.connectionIdGenerator = connectionIdGenerator;
     }
 
-    //////////////////////////////////////////////////////////////////////////
-    // Property getters and setters
-    //////////////////////////////////////////////////////////////////////////
+    //----- Property Access Methods ------------------------------------------//
 
     /**
      * @return the remoteURI

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsConsumer.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsConsumer.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsConsumer.java
new file mode 100644
index 0000000..19691e7
--- /dev/null
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsConsumer.java
@@ -0,0 +1,135 @@
+/*
+ * 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;
+
+import javax.jms.JMSConsumer;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageListener;
+
+import org.apache.qpid.jms.exceptions.JmsExceptionSupport;
+
+@SuppressWarnings("unused")
+public class JmsConsumer implements JMSConsumer, AutoCloseable {
+
+    private final JmsSession session;
+    private final JmsMessageConsumer consumer;
+
+    public JmsConsumer(JmsSession session, JmsMessageConsumer consumer) {
+        this.session = session;
+        this.consumer = consumer;
+    }
+
+    @Override
+    public void close() {
+        try {
+            consumer.close();
+        } catch (JMSException e) {
+            throw JmsExceptionSupport.createRuntimeException(e);
+        }
+    }
+
+    //----- MessageConsumer Property Methods ---------------------------------//
+
+    @Override
+    public MessageListener getMessageListener() {
+        try {
+            return consumer.getMessageListener();
+        } catch (JMSException e) {
+            throw JmsExceptionSupport.createRuntimeException(e);
+        }
+    }
+
+    @Override
+    public String getMessageSelector() {
+        try {
+            return consumer.getMessageSelector();
+        } catch (JMSException e) {
+            throw JmsExceptionSupport.createRuntimeException(e);
+        }
+    }
+
+    @Override
+    public void setMessageListener(MessageListener listener) {
+        try {
+            consumer.setMessageListener(listener);
+        } catch (JMSException e) {
+            throw JmsExceptionSupport.createRuntimeException(e);
+        }
+    }
+
+    //----- Receive Methods --------------------------------------------------//
+
+    @Override
+    public Message receive() {
+        try {
+            return consumer.receive();
+        } catch (JMSException e) {
+            throw JmsExceptionSupport.createRuntimeException(e);
+        }
+    }
+
+    @Override
+    public Message receive(long timeout) {
+        try {
+            return consumer.receive(timeout);
+        } catch (JMSException e) {
+            throw JmsExceptionSupport.createRuntimeException(e);
+        }
+    }
+
+    @Override
+    public Message receiveNoWait() {
+        try {
+            return consumer.receiveNoWait();
+        } catch (JMSException e) {
+            throw JmsExceptionSupport.createRuntimeException(e);
+        }
+    }
+
+    @Override
+    public <T> T receiveBody(Class<T> desired) {
+        try {
+            return consumer.receiveBody(desired, -1);
+        } catch (JMSException e) {
+            throw JmsExceptionSupport.createRuntimeException(e);
+        }
+    }
+
+    @Override
+    public <T> T receiveBody(Class<T> desired, long timeout) {
+        try {
+            // Configure for infinite wait when timeout is zero (JMS Spec)
+            if (timeout == 0) {
+                timeout = -1;
+            }
+
+            return consumer.receiveBody(desired, timeout);
+        } catch (JMSException e) {
+            throw JmsExceptionSupport.createRuntimeException(e);
+        }
+    }
+
+    @Override
+    public <T> T receiveBodyNoWait(Class<T> desired) {
+        try {
+            return consumer.receiveBody(desired, 0);
+        } catch (JMSException e) {
+            throw JmsExceptionSupport.createRuntimeException(e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsContext.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsContext.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsContext.java
new file mode 100644
index 0000000..008da24
--- /dev/null
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsContext.java
@@ -0,0 +1,510 @@
+/*
+ * 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;
+
+import java.io.Serializable;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.jms.BytesMessage;
+import javax.jms.ConnectionMetaData;
+import javax.jms.Destination;
+import javax.jms.ExceptionListener;
+import javax.jms.IllegalStateRuntimeException;
+import javax.jms.JMSConsumer;
+import javax.jms.JMSContext;
+import javax.jms.JMSException;
+import javax.jms.JMSProducer;
+import javax.jms.JMSRuntimeException;
+import javax.jms.MapMessage;
+import javax.jms.Message;
+import javax.jms.ObjectMessage;
+import javax.jms.Queue;
+import javax.jms.QueueBrowser;
+import javax.jms.Session;
+import javax.jms.StreamMessage;
+import javax.jms.TemporaryQueue;
+import javax.jms.TemporaryTopic;
+import javax.jms.TextMessage;
+import javax.jms.Topic;
+
+import org.apache.qpid.jms.exceptions.JmsExceptionSupport;
+import org.apache.qpid.jms.provider.ProviderConstants.ACK_TYPE;
+
+public class JmsContext implements JMSContext, AutoCloseable {
+
+    private final JmsConnection connection;
+    private final AtomicLong connectionRefCount;
+    private final int sessionMode;
+
+    private JmsSession session;
+    private JmsMessageProducer sharedProducer;
+    private boolean autoStart = true;
+
+    public JmsContext(JmsConnection connection, int sessionMode) {
+        this(connection, sessionMode, new AtomicLong(1));
+    }
+
+    private JmsContext(JmsConnection connection, int sessionMode, AtomicLong connectionRefCount) {
+        this.connection = connection;
+        this.sessionMode = sessionMode;
+        this.connectionRefCount = connectionRefCount;
+    }
+
+    @Override
+    public void start() {
+        try {
+            connection.start();
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public void stop() {
+        try {
+            connection.stop();
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public void close() {
+        JMSRuntimeException failure = null;
+
+        synchronized (this) {
+            try {
+                if (session != null) {
+                    session.close();
+                }
+            } catch (JMSException jmse) {
+                failure = JmsExceptionSupport.createRuntimeException(jmse);
+            }
+
+            if (connectionRefCount.decrementAndGet() == 0) {
+                try {
+                    connection.close();
+                } catch (JMSException jmse) {
+                    failure = JmsExceptionSupport.createRuntimeException(jmse);
+                }
+            }
+        }
+
+        if (failure != null) {
+            throw failure;
+        }
+    }
+
+    //----- Session state management -----------------------------------------//
+
+    @Override
+    public void acknowledge() {
+        if (getSessionMode() == Session.CLIENT_ACKNOWLEDGE) {
+            try {
+                getSession().acknowledge(ACK_TYPE.ACCEPTED);
+            } catch (JMSException jmse) {
+                throw JmsExceptionSupport.createRuntimeException(jmse);
+            }
+        }
+    }
+
+    @Override
+    public void commit() {
+        try {
+            getSession().commit();
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public void rollback() {
+        try {
+            getSession().rollback();
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public void recover() {
+        try {
+            getSession().recover();
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public void unsubscribe(String name) {
+        try {
+            getSession().unsubscribe(name);
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    //----- Message Factory methods ------------------------------------------//
+
+    @Override
+    public BytesMessage createBytesMessage() {
+        try {
+            return getSession().createBytesMessage();
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public MapMessage createMapMessage() {
+        try {
+            return getSession().createMapMessage();
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public Message createMessage() {
+        try {
+            return getSession().createMessage();
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public ObjectMessage createObjectMessage() {
+        try {
+            return getSession().createObjectMessage();
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public ObjectMessage createObjectMessage(Serializable object) {
+        try {
+            return getSession().createObjectMessage(object);
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public StreamMessage createStreamMessage() {
+        try {
+            return getSession().createStreamMessage();
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public TextMessage createTextMessage() {
+        try {
+            return getSession().createTextMessage();
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public TextMessage createTextMessage(String text) {
+        try {
+            return getSession().createTextMessage(text);
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    //----- Destination Creation ---------------------------------------------//
+
+    @Override
+    public Queue createQueue(String queueName) {
+        try {
+            return getSession().createQueue(queueName);
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public Topic createTopic(String topicName) {
+        try {
+            return getSession().createTopic(topicName);
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public TemporaryQueue createTemporaryQueue() {
+        try {
+            return getSession().createTemporaryQueue();
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public TemporaryTopic createTemporaryTopic() {
+        try {
+            return getSession().createTemporaryTopic();
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    //----- JMSContext factory methods --------------------------------------//
+
+    @Override
+    public JMSContext createContext(int sessionMode) {
+        synchronized (this) {
+            if (connectionRefCount.get() == 0) {
+                throw new IllegalStateRuntimeException("The Connection is closed");
+            }
+
+            connectionRefCount.incrementAndGet();
+
+            return new JmsContext(connection, sessionMode, connectionRefCount);
+        }
+    }
+
+    //----- JMSProducer factory methods --------------------------------------//
+
+    @Override
+    public JMSProducer createProducer() {
+        try {
+            if (sharedProducer == null) {
+                synchronized (this) {
+                    if (sharedProducer == null) {
+                        sharedProducer = (JmsMessageProducer) getSession().createProducer(null);
+                    }
+                }
+            }
+
+            return new JmsProducer(getSession(), sharedProducer);
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    //----- JMSConsumer factory methods --------------------------------------//
+
+    @Override
+    public JMSConsumer createConsumer(Destination destination) {
+        try {
+            return startIfNeeded(new JmsConsumer(getSession(), (JmsMessageConsumer) getSession().createConsumer(destination)));
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public JMSConsumer createConsumer(Destination destination, String selector) {
+        try {
+            return startIfNeeded(new JmsConsumer(getSession(), (JmsMessageConsumer) getSession().createConsumer(destination, selector)));
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public JMSConsumer createConsumer(Destination destination, String selector, boolean noLocal) {
+        try {
+            return startIfNeeded(new JmsConsumer(getSession(), (JmsMessageConsumer) getSession().createConsumer(destination, selector, noLocal)));
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public JMSConsumer createDurableConsumer(Topic topic, String name) {
+        try {
+            return startIfNeeded(new JmsConsumer(getSession(), (JmsMessageConsumer) getSession().createDurableConsumer(topic, name)));
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public JMSConsumer createDurableConsumer(Topic topic, String name, String selector, boolean noLocal) {
+        try {
+            return startIfNeeded(new JmsConsumer(getSession(), (JmsMessageConsumer) getSession().createDurableConsumer(topic, name, selector, noLocal)));
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public JMSConsumer createSharedConsumer(Topic topic, String name) {
+        try {
+            return startIfNeeded(new JmsConsumer(getSession(), (JmsMessageConsumer) getSession().createSharedConsumer(topic, name)));
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public JMSConsumer createSharedConsumer(Topic topic, String name, String selector) {
+        try {
+            return startIfNeeded(new JmsConsumer(getSession(), (JmsMessageConsumer) getSession().createSharedConsumer(topic, name, selector)));
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public JMSConsumer createSharedDurableConsumer(Topic topic, String name) {
+        try {
+            return startIfNeeded(new JmsConsumer(getSession(), (JmsMessageConsumer) getSession().createSharedDurableConsumer(topic, name)));
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public JMSConsumer createSharedDurableConsumer(Topic topic, String name, String selector) {
+        try {
+            return startIfNeeded(new JmsConsumer(getSession(), (JmsMessageConsumer) getSession().createSharedDurableConsumer(topic, name, selector)));
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    //----- QueueBrowser Factory Methods -------------------------------------//
+
+    @Override
+    public QueueBrowser createBrowser(Queue queue) {
+        try {
+            return startIfNeeded(getSession().createBrowser(queue));
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public QueueBrowser createBrowser(Queue queue, String selector) {
+        try {
+            return startIfNeeded(getSession().createBrowser(queue, selector));
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    //----- Get or Set Context and Session values ----------------------------//
+
+    @Override
+    public boolean getAutoStart() {
+        return autoStart;
+    }
+
+    @Override
+    public void setAutoStart(boolean autoStart) {
+        this.autoStart = autoStart;
+    }
+
+    @Override
+    public String getClientID() {
+        try {
+            return connection.getClientID();
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public void setClientID(String clientID) {
+        try {
+            connection.setClientID(clientID);
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public ExceptionListener getExceptionListener() {
+        try {
+            return connection.getExceptionListener();
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public void setExceptionListener(ExceptionListener listener) {
+        try {
+            connection.setExceptionListener(listener);
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public ConnectionMetaData getMetaData() {
+        try {
+            return connection.getMetaData();
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public int getSessionMode() {
+        return sessionMode;
+    }
+
+    @Override
+    public boolean getTransacted() {
+        return sessionMode == JMSContext.SESSION_TRANSACTED;
+    }
+
+    //----- Internal implementation methods ----------------------------------//
+
+    private JmsSession getSession() {
+        if (session == null) {
+            synchronized (this) {
+                if (session == null) {
+                    try {
+                        session = (JmsSession) connection.createSession(getSessionMode());
+                    } catch (JMSException jmse) {
+                        throw JmsExceptionSupport.createRuntimeException(jmse);
+                    }
+                }
+            }
+        }
+
+        return session;
+    }
+
+    private QueueBrowser startIfNeeded(QueueBrowser browser) throws JMSException {
+        if (getAutoStart()) {
+            connection.start();
+        }
+
+        return browser;
+    }
+
+    private JmsConsumer startIfNeeded(JmsConsumer consumer) throws JMSException {
+        if (getAutoStart()) {
+            connection.start();
+        }
+
+        return consumer;
+    }
+}

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsMessageConsumer.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsMessageConsumer.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsMessageConsumer.java
index 18fd764..ea93019 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsMessageConsumer.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsMessageConsumer.java
@@ -26,6 +26,7 @@ import javax.jms.IllegalStateException;
 import javax.jms.JMSException;
 import javax.jms.Message;
 import javax.jms.MessageConsumer;
+import javax.jms.MessageFormatException;
 import javax.jms.MessageListener;
 import javax.jms.Session;
 
@@ -213,6 +214,58 @@ public class JmsMessageConsumer implements AutoCloseable, MessageConsumer, JmsMe
     }
 
     /**
+     * Reads the next available message for this consumer and returns the body of that message
+     * if the type requested matches that of the message.  The amount of time this method blocks
+     * is based on the timeout value.
+     *
+     *   {@literal timeout < 0} then it blocks until a message is received.
+     *   {@literal timeout = 0} then it returns the body immediately or null if none available.
+     *   {@literal timeout > 0} then it blocks up to timeout amount of time.
+     *
+     * @param desired
+     *      The type to assign the body of the message to for return.
+     * @param timeout
+     *      The time to wait for an incoming message before this method returns null.
+     *
+     * @return the assigned body of the next available message or null if the consumer is closed
+     *         or the specified timeout elapses.
+     *
+     * @throws MessageFormatException if the message body cannot be assigned to the requested type.
+     * @throws JMSException if an error occurs while receiving the next message.
+     */
+    public <T> T receiveBody(Class<T> desired, long timeout) throws JMSException {
+        checkClosed();
+        checkMessageListener();
+
+        T messageBody = null;
+        JmsInboundMessageDispatch envelope = null;
+
+        try {
+            envelope = dequeue(timeout, connection.isReceiveLocalOnly());
+            if (envelope != null) {
+                messageBody = envelope.getMessage().getBody(desired);
+            }
+        } catch (MessageFormatException mfe) {
+            // Should behave as if receiveBody never happened in these modes.
+            if (acknowledgementMode == Session.AUTO_ACKNOWLEDGE ||
+                acknowledgementMode == Session.DUPS_OK_ACKNOWLEDGE) {
+
+                envelope.setEnqueueFirst(true);
+                onInboundMessage(envelope);
+                envelope = null;
+            }
+
+            throw mfe;
+        } finally {
+            if (envelope != null) {
+                ackFromReceive(envelope);
+            }
+        }
+
+        return messageBody;
+    }
+
+    /**
      * Used to get an enqueued message from the unconsumedMessages list. The
      * amount of time this method blocks is based on the timeout value.
      *

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsMessageProducer.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsMessageProducer.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsMessageProducer.java
index 65812b7..6e9c96d 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsMessageProducer.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsMessageProducer.java
@@ -20,6 +20,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.AtomicReference;
 
+import javax.jms.CompletionListener;
 import javax.jms.DeliveryMode;
 import javax.jms.Destination;
 import javax.jms.IllegalStateException;
@@ -43,6 +44,7 @@ public class JmsMessageProducer implements AutoCloseable, MessageProducer {
     protected final JmsConnection connection;
     protected JmsProducerInfo producerInfo;
     protected final boolean anonymousProducer;
+    protected long deliveryDelay = Message.DEFAULT_DELIVERY_DELAY;
     protected int deliveryMode = DeliveryMode.PERSISTENT;
     protected int priority = Message.DEFAULT_PRIORITY;
     protected long timeToLive = Message.DEFAULT_TIME_TO_LIVE;
@@ -110,44 +112,50 @@ public class JmsMessageProducer implements AutoCloseable, MessageProducer {
     }
 
     @Override
+    public long getDeliveryDelay() throws JMSException {
+        checkClosed();
+        return deliveryMode;
+    }
+
+    @Override
     public int getDeliveryMode() throws JMSException {
         checkClosed();
-        return this.deliveryMode;
+        return deliveryMode;
     }
 
     @Override
     public Destination getDestination() throws JMSException {
         checkClosed();
-        return this.producerInfo.getDestination();
+        return producerInfo.getDestination();
     }
 
     @Override
     public boolean getDisableMessageID() throws JMSException {
         checkClosed();
-        return this.disableMessageId;
+        return disableMessageId;
     }
 
     @Override
     public boolean getDisableMessageTimestamp() throws JMSException {
         checkClosed();
-        return this.disableTimestamp;
+        return disableTimestamp;
     }
 
     @Override
     public int getPriority() throws JMSException {
         checkClosed();
-        return this.priority;
+        return priority;
     }
 
     @Override
     public long getTimeToLive() throws JMSException {
         checkClosed();
-        return this.timeToLive;
+        return timeToLive;
     }
 
     @Override
     public void send(Message message) throws JMSException {
-        send(message, this.deliveryMode, this.priority, this.timeToLive);
+        send(message, deliveryMode, priority, timeToLive);
     }
 
     @Override
@@ -163,7 +171,7 @@ public class JmsMessageProducer implements AutoCloseable, MessageProducer {
 
     @Override
     public void send(Destination destination, Message message) throws JMSException {
-        send(destination, message, this.deliveryMode, this.priority, this.timeToLive);
+        send(destination, message, deliveryMode, priority, timeToLive);
     }
 
     @Override
@@ -177,37 +185,13 @@ public class JmsMessageProducer implements AutoCloseable, MessageProducer {
         sendMessage(destination, message, deliveryMode, priority, timeToLive, null);
     }
 
-    /**
-     * Sends the message asynchronously and notifies the assigned listener on success or failure
-     *
-     * @param message
-     *      the {@link javax.jms.Message} to send.
-     * @param listener
-     *      the {@link JmsCompletionListener} to notify on send success or failure.
-     *
-     * @throws JMSException if an error occurs while attempting to send the Message.
-     */
-    public void send(Message message, JmsCompletionListener listener) throws JMSException {
-        send(message, this.deliveryMode, this.priority, this.timeToLive, listener);
+    @Override
+    public void send(Message message, CompletionListener listener) throws JMSException {
+        send(message, deliveryMode, priority, timeToLive, listener);
     }
 
-    /**
-     * Sends the message asynchronously and notifies the assigned listener on success or failure
-     *
-     * @param message
-     *      the {@link javax.jms.Message} to send.
-     * @param deliveryMode
-     *      the delivery mode to assign to the outbound Message.
-     * @param priority
-     *      the priority to assign to the outbound Message.
-     * @param timeToLive
-     *      the time to live value to assign to the outbound Message.
-     * @param listener
-     *      the {@link JmsCompletionListener} to notify on send success or failure.
-     *
-     * @throws JMSException if an error occurs while attempting to send the Message.
-     */
-    public void send(Message message, int deliveryMode, int priority, long timeToLive, JmsCompletionListener listener) throws JMSException {
+    @Override
+    public void send(Message message, int deliveryMode, int priority, long timeToLive, CompletionListener listener) throws JMSException {
         checkClosed();
 
         if (anonymousProducer) {
@@ -221,41 +205,13 @@ public class JmsMessageProducer implements AutoCloseable, MessageProducer {
         sendMessage(producerInfo.getDestination(), message, deliveryMode, priority, timeToLive, listener);
     }
 
-    /**
-     * Sends the message asynchronously and notifies the assigned listener on success or failure
-     *
-     * @param destination
-     *      the Destination to send the given Message to.
-     * @param message
-     *      the {@link javax.jms.Message} to send.
-     * @param listener
-     *      the {@link JmsCompletionListener} to notify on send success or failure.
-     *
-     * @throws JMSException if an error occurs while attempting to send the Message.
-     */
-    public void send(Destination destination, Message message, JmsCompletionListener listener) throws JMSException {
+    @Override
+    public void send(Destination destination, Message message, CompletionListener listener) throws JMSException {
         send(destination, message, this.deliveryMode, this.priority, this.timeToLive, listener);
     }
 
-    /**
-     * Sends the message asynchronously and notifies the assigned listener on success or failure
-     *
-     * @param destination
-     *      the Destination to send the given Message to.
-     * @param message
-     *      the {@link javax.jms.Message} to send.
-     * @param deliveryMode
-     *      the delivery mode to assign to the outbound Message.
-     * @param priority
-     *      the priority to assign to the outbound Message.
-     * @param timeToLive
-     *      the time to live value to assign to the outbound Message.
-     * @param listener
-     *      the {@link JmsCompletionListener} to notify on send success or failure.
-     *
-     * @throws JMSException if an error occurs while attempting to send the Message.
-     */
-    public void send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive, JmsCompletionListener listener) throws JMSException {
+    @Override
+    public void send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive, CompletionListener listener) throws JMSException {
         checkClosed();
 
         if (!anonymousProducer) {
@@ -269,12 +225,18 @@ public class JmsMessageProducer implements AutoCloseable, MessageProducer {
         sendMessage(destination, message, deliveryMode, priority, timeToLive, listener);
     }
 
-    private void sendMessage(Destination destination, Message message, int deliveryMode, int priority, long timeToLive, JmsCompletionListener listener) throws JMSException {
+    private void sendMessage(Destination destination, Message message, int deliveryMode, int priority, long timeToLive, CompletionListener listener) throws JMSException {
         if (destination == null) {
             throw new InvalidDestinationException("Don't understand null destinations");
         }
 
-        this.session.send(this, destination, message, deliveryMode, priority, timeToLive, disableMessageId, disableTimestamp, listener);
+        this.session.send(this, destination, message, deliveryMode, priority, timeToLive, disableMessageId, disableTimestamp, deliveryDelay, listener);
+    }
+
+    @Override
+    public void setDeliveryDelay(long deliveryDelay) throws JMSException {
+        checkClosed();
+        this.deliveryDelay = deliveryDelay;
     }
 
     @Override
@@ -318,7 +280,7 @@ public class JmsMessageProducer implements AutoCloseable, MessageProducer {
      * @return the next logical sequence for a Message sent from this Producer.
      */
     protected long getNextMessageSequence() {
-        return this.messageSequence.incrementAndGet();
+        return messageSequence.incrementAndGet();
     }
 
     protected void checkClosed() throws IllegalStateException {

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsProducer.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsProducer.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsProducer.java
new file mode 100644
index 0000000..a90ec23
--- /dev/null
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsProducer.java
@@ -0,0 +1,454 @@
+/*
+ * 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;
+
+import static org.apache.qpid.jms.message.JmsMessagePropertySupport.checkPropertyNameIsValid;
+import static org.apache.qpid.jms.message.JmsMessagePropertySupport.checkValidObject;
+import static org.apache.qpid.jms.message.JmsMessagePropertySupport.convertPropertyTo;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jms.BytesMessage;
+import javax.jms.CompletionListener;
+import javax.jms.DeliveryMode;
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.JMSProducer;
+import javax.jms.MapMessage;
+import javax.jms.Message;
+import javax.jms.ObjectMessage;
+import javax.jms.TextMessage;
+
+import org.apache.qpid.jms.exceptions.JmsExceptionSupport;
+import org.apache.qpid.jms.message.JmsMessageTransformation;
+
+public class JmsProducer implements JMSProducer {
+
+    private final JmsSession session;
+    private final JmsMessageProducer producer;
+
+    private CompletionListener completionListener;
+
+    // Message Headers
+    private String correlationId;
+    private String type;
+    private Destination replyTo;
+    private byte[] correlationIdBytes;
+
+    // Producer send configuration
+    private long deliveryDelay = Message.DEFAULT_DELIVERY_DELAY;
+    private int deliveryMode = DeliveryMode.PERSISTENT;
+    private int priority = Message.DEFAULT_PRIORITY;
+    private long timeToLive = Message.DEFAULT_TIME_TO_LIVE;
+    private boolean disableMessageId;
+    private boolean disableTimestamp;
+
+    // Message Properties
+    private final Map<String, Object> messageProperties = new HashMap<String, Object>();
+
+    /**
+     * Create a new JMSProducer instance.
+     *
+     * The producer is backed by the given Session object and uses the shared MessageProducer
+     * instance to send all of its messages.
+     *
+     * @param session
+     *      The Session that created this JMSProducer
+     * @param producer
+     *      The shared MessageProducer owned by the parent Session.
+     */
+    public JmsProducer(JmsSession session, JmsMessageProducer producer) {
+        this.session = session;
+        this.producer = producer;
+    }
+
+    //----- Send Methods -----------------------------------------------------//
+
+    @Override
+    public JMSProducer send(Destination destination, Message message) {
+        try {
+            doSend(destination, message);
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+
+        return this;
+    }
+
+    @Override
+    public JMSProducer send(Destination destination, byte[] body) {
+        try {
+            BytesMessage message = session.createBytesMessage();
+            message.writeBytes(body);
+            doSend(destination, message);
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+
+        return this;
+    }
+
+    @Override
+    public JMSProducer send(Destination destination, Map<String, Object> body) {
+        try {
+            MapMessage message = session.createMapMessage();
+            for (Map.Entry<String, Object> entry : body.entrySet()) {
+                message.setObject(entry.getKey(), entry.getValue());
+            }
+
+            doSend(destination, message);
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+
+        return this;
+    }
+
+    @Override
+    public JMSProducer send(Destination destination, Serializable body) {
+        try {
+            ObjectMessage message = session.createObjectMessage();
+            message.setObject(body);
+            doSend(destination, message);
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+
+        return this;
+    }
+
+    @Override
+    public JMSProducer send(Destination destination, String body) {
+        try {
+            TextMessage message = session.createTextMessage(body);
+            doSend(destination, message);
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+
+        return this;
+    }
+
+    private void doSend(Destination destination, Message message) throws JMSException {
+
+        for (Map.Entry<String, Object> entry : messageProperties.entrySet()) {
+            message.setObjectProperty(entry.getKey(), entry.getValue());
+        }
+
+        if (correlationId != null) {
+            message.setJMSCorrelationID(correlationId);
+        }
+        if (correlationIdBytes != null) {
+            message.setJMSCorrelationIDAsBytes(correlationIdBytes);
+        }
+        if (type != null) {
+            message.setJMSType(type);
+        }
+        if (replyTo != null) {
+            message.setJMSReplyTo(replyTo);
+        }
+
+        session.send(producer, destination, message, deliveryMode, priority, timeToLive, disableMessageId, disableTimestamp, deliveryDelay, completionListener);
+    }
+
+    //----- Message Property Methods -----------------------------------------//
+
+    @Override
+    public JMSProducer clearProperties() {
+        messageProperties.clear();
+        return this;
+    }
+
+    @Override
+    public Set<String> getPropertyNames() {
+        return new HashSet<String>(messageProperties.keySet());
+    }
+
+    @Override
+    public boolean propertyExists(String name) {
+        return messageProperties.containsKey(name);
+    }
+
+    @Override
+    public boolean getBooleanProperty(String name) {
+        try {
+            return convertPropertyTo(name, messageProperties.get(name), Boolean.class);
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public byte getByteProperty(String name) {
+        try {
+            return convertPropertyTo(name, messageProperties.get(name), Byte.class);
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public double getDoubleProperty(String name) {
+        try {
+            return convertPropertyTo(name, messageProperties.get(name), Double.class);
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public float getFloatProperty(String name) {
+        try {
+            return convertPropertyTo(name, messageProperties.get(name), Float.class);
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public int getIntProperty(String name) {
+        try {
+            return convertPropertyTo(name, messageProperties.get(name), Integer.class);
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public long getLongProperty(String name) {
+        try {
+            return convertPropertyTo(name, messageProperties.get(name), Long.class);
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public Object getObjectProperty(String name) {
+        return messageProperties.get(name);
+    }
+
+    @Override
+    public short getShortProperty(String name) {
+        try {
+            return convertPropertyTo(name, messageProperties.get(name), Short.class);
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public String getStringProperty(String name) {
+        try {
+            return convertPropertyTo(name, messageProperties.get(name), String.class);
+        } catch (JMSException jmse) {
+            throw JmsExceptionSupport.createRuntimeException(jmse);
+        }
+    }
+
+    @Override
+    public JMSProducer setProperty(String name, boolean value) {
+        return setObjectProperty(name, value);
+    }
+
+    @Override
+    public JMSProducer setProperty(String name, byte value) {
+        return setObjectProperty(name, value);
+    }
+
+    @Override
+    public JMSProducer setProperty(String name, double value) {
+        return setObjectProperty(name, value);
+    }
+
+    @Override
+    public JMSProducer setProperty(String name, float value) {
+        return setObjectProperty(name, value);
+    }
+
+    @Override
+    public JMSProducer setProperty(String name, int value) {
+        return setObjectProperty(name, value);
+    }
+
+    @Override
+    public JMSProducer setProperty(String name, long value) {
+        return setObjectProperty(name, value);
+    }
+
+    @Override
+    public JMSProducer setProperty(String name, Object value) {
+        return setObjectProperty(name, value);
+    }
+
+    @Override
+    public JMSProducer setProperty(String name, short value) {
+        return setObjectProperty(name, value);
+    }
+
+    @Override
+    public JMSProducer setProperty(String name, String value) {
+        return setObjectProperty(name, value);
+    }
+
+    //----- Message Headers --------------------------------------------------//
+
+    @Override
+    public String getJMSCorrelationID() {
+        return correlationId;
+    }
+
+    @Override
+    public JMSProducer setJMSCorrelationID(String correlationId) {
+        this.correlationId = correlationId;
+        return this;
+    }
+
+    @Override
+    public byte[] getJMSCorrelationIDAsBytes() {
+        return correlationIdBytes;
+    }
+
+    @Override
+    public JMSProducer setJMSCorrelationIDAsBytes(byte[] correlationIdBytes) {
+        this.correlationIdBytes = correlationIdBytes;
+        return this;
+    }
+
+    @Override
+    public Destination getJMSReplyTo() {
+        return replyTo;
+    }
+
+    @Override
+    public JMSProducer setJMSReplyTo(Destination replyTo) {
+        try {
+            JmsMessageTransformation.transformDestination(session.getConnection(), replyTo);
+        } catch (JMSException e) {
+            throw JmsExceptionSupport.createRuntimeException(e);
+        }
+
+        return this;
+    }
+
+    @Override
+    public String getJMSType() {
+        return type;
+    }
+
+    @Override
+    public JMSProducer setJMSType(String type) {
+        this.type = type;
+        return this;
+    }
+
+    //----- Producer Send Configuration --------------------------------------//
+
+    @Override
+    public CompletionListener getAsync() {
+        return completionListener;
+    }
+
+    @Override
+    public JMSProducer setAsync(CompletionListener completionListener) {
+        this.completionListener = completionListener;
+        return this;
+    }
+
+    @Override
+    public long getDeliveryDelay() {
+        return deliveryDelay;
+    }
+
+    @Override
+    public JMSProducer setDeliveryDelay(long deliveryDelay) {
+        this.deliveryDelay = deliveryDelay;
+        return this;
+    }
+
+    @Override
+    public int getDeliveryMode() {
+        return deliveryMode;
+    }
+
+    @Override
+    public JMSProducer setDeliveryMode(int deliveryMode) {
+        this.deliveryMode = deliveryMode;
+        return this;
+    }
+
+    @Override
+    public boolean getDisableMessageID() {
+        return disableMessageId;
+    }
+
+    @Override
+    public JMSProducer setDisableMessageID(boolean disableMessageId) {
+        this.disableMessageId = disableMessageId;
+        return this;
+    }
+
+    @Override
+    public boolean getDisableMessageTimestamp() {
+        return disableTimestamp;
+    }
+
+    @Override
+    public JMSProducer setDisableMessageTimestamp(boolean disableTimestamp) {
+        this.disableTimestamp = disableTimestamp;
+        return this;
+    }
+
+    @Override
+    public int getPriority() {
+        return priority;
+    }
+
+    @Override
+    public JMSProducer setPriority(int priority) {
+        this.priority = priority;
+        return this;
+    }
+
+    @Override
+    public long getTimeToLive() {
+        return timeToLive;
+    }
+
+    @Override
+    public JMSProducer setTimeToLive(long timeToLive) {
+        this.timeToLive = timeToLive;
+        return this;
+    }
+
+    //----- Internal support methods -----------------------------------------//
+
+    private JMSProducer setObjectProperty(String name, Object value) {
+        try {
+            checkPropertyNameIsValid(name, session.getConnection().isValidatePropertyNames());
+            checkValidObject(value);
+            messageProperties.put(name, value);
+            return this;
+        } catch (JMSException e) {
+            throw JmsExceptionSupport.createRuntimeException(e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsSession.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsSession.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsSession.java
index 817f342..e740ba7 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsSession.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsSession.java
@@ -34,6 +34,7 @@ import java.util.concurrent.atomic.AtomicReference;
 import java.util.concurrent.locks.ReentrantLock;
 
 import javax.jms.BytesMessage;
+import javax.jms.CompletionListener;
 import javax.jms.DeliveryMode;
 import javax.jms.Destination;
 import javax.jms.IllegalStateException;
@@ -471,6 +472,22 @@ public class JmsSession implements AutoCloseable, Session, QueueSession, TopicSe
         return result;
     }
 
+    /**
+     * @see javax.jms.Session#createDurableConsumer(javax.jms.Topic, java.lang.String)
+     */
+    @Override
+    public MessageConsumer createDurableConsumer(Topic topic, String name) throws JMSException {
+        return createDurableSubscriber(topic, name, null, false);
+    }
+
+    /**
+     * @see javax.jms.Session#createDurableConsumer(javax.jms.Topic, java.lang.String, java.lang.String, boolean)
+     */
+    @Override
+    public MessageConsumer createDurableConsumer(Topic topic, String name, String messageSelector, boolean noLocal) throws JMSException {
+        return createDurableSubscriber(topic, name, messageSelector, noLocal);
+    }
+
     protected void checkClientIDWasSetExplicitly() throws IllegalStateException {
         if (!connection.isExplicitClientID()) {
             throw new IllegalStateException("You must specify a unique clientID for the Connection to use a DurableSubscriber");
@@ -486,6 +503,46 @@ public class JmsSession implements AutoCloseable, Session, QueueSession, TopicSe
         connection.unsubscribe(name);
     }
 
+    /**
+     * @see javax.jms.Session#createSharedConsumer(javax.jms.Topic, java.lang.String)
+     */
+    @Override
+    public MessageConsumer createSharedConsumer(Topic topic, String name) throws JMSException {
+        checkClosed();
+        // TODO Auto-generated method stub
+        throw new JMSException("Not yet implemented");
+    }
+
+    /**
+     * @see javax.jms.Session#createSharedConsumer(javax.jms.Topic, java.lang.String, java.lang.String)
+     */
+    @Override
+    public MessageConsumer createSharedConsumer(Topic topic, String name, String selector) throws JMSException {
+        checkClosed();
+        // TODO Auto-generated method stub
+        throw new JMSException("Not yet implemented");
+    }
+
+    /**
+     * @see javax.jms.Session#createSharedDurableConsumer(javax.jms.Topic, java.lang.String)
+     */
+    @Override
+    public MessageConsumer createSharedDurableConsumer(Topic topic, String name) throws JMSException {
+        checkClosed();
+        // TODO Auto-generated method stub
+        throw new JMSException("Not yet implemented");
+    }
+
+    /**
+     * @see javax.jms.Session#createSharedDurableConsumer(javax.jms.Topic, java.lang.String, java.lang.String)
+     */
+    @Override
+    public MessageConsumer createSharedDurableConsumer(Topic topic, String name, String selector) throws JMSException {
+        checkClosed();
+        // TODO Auto-generated method stub
+        throw new JMSException("Not yet implemented");
+    }
+
     //////////////////////////////////////////////////////////////////////////
     // Producer creation
     //////////////////////////////////////////////////////////////////////////
@@ -653,17 +710,17 @@ public class JmsSession implements AutoCloseable, Session, QueueSession, TopicSe
         connection.onException(ex);
     }
 
-    protected void send(JmsMessageProducer producer, Destination dest, Message msg, int deliveryMode, int priority, long timeToLive, boolean disableMsgId, boolean disableTimestamp, JmsCompletionListener listener) throws JMSException {
+    protected void send(JmsMessageProducer producer, Destination dest, Message msg, int deliveryMode, int priority, long timeToLive, boolean disableMsgId, boolean disableTimestamp, long deliveryDelay, CompletionListener listener) throws JMSException {
         JmsDestination destination = JmsMessageTransformation.transformDestination(connection, dest);
 
         if (destination.isTemporary() && ((JmsTemporaryDestination) destination).isDeleted()) {
             throw new IllegalStateException("Temporary destination has been deleted");
         }
 
-        send(producer, destination, msg, deliveryMode, priority, timeToLive, disableMsgId, disableTimestamp, listener);
+        send(producer, destination, msg, deliveryMode, priority, timeToLive, disableMsgId, disableTimestamp, deliveryDelay, listener);
     }
 
-    private void send(JmsMessageProducer producer, JmsDestination destination, Message original, int deliveryMode, int priority, long timeToLive, boolean disableMsgId, boolean disableTimestamp, JmsCompletionListener listener) throws JMSException {
+    private void send(JmsMessageProducer producer, JmsDestination destination, Message original, int deliveryMode, int priority, long timeToLive, boolean disableMsgId, boolean disableTimestamp, long deliveryDelay, CompletionListener listener) throws JMSException {
         sendLock.lock();
         try {
             original.setJMSDeliveryMode(deliveryMode);
@@ -672,7 +729,8 @@ public class JmsSession implements AutoCloseable, Session, QueueSession, TopicSe
             original.setJMSDestination(destination);
 
             long timeStamp = System.currentTimeMillis();
-            boolean hasTTL = timeToLive > 0;
+            boolean hasTTL = timeToLive > Message.DEFAULT_TIME_TO_LIVE;
+            boolean hasDelay = deliveryDelay > Message.DEFAULT_DELIVERY_DELAY;
 
             if (!disableTimestamp) {
                 original.setJMSTimestamp(timeStamp);
@@ -686,6 +744,12 @@ public class JmsSession implements AutoCloseable, Session, QueueSession, TopicSe
                 original.setJMSExpiration(0);
             }
 
+            if (hasDelay) {
+                original.setJMSDeliveryTime(timeStamp + timeToLive);
+            } else {
+                original.setJMSDeliveryTime(0);
+            }
+
             boolean isJmsMessage = original instanceof JmsMessage;
 
             long messageSequence = producer.getNextMessageSequence();
@@ -966,6 +1030,10 @@ public class JmsSession implements AutoCloseable, Session, QueueSession, TopicSe
         return sessionInfo.getId();
     }
 
+    protected int getSessionMode() {
+        return acknowledgementMode;
+    }
+
     protected JmsConsumerId getNextConsumerId() {
         return new JmsConsumerId(sessionInfo.getId(), consumerIdGenerator.incrementAndGet());
     }
@@ -1272,12 +1340,12 @@ public class JmsSession implements AutoCloseable, Session, QueueSession, TopicSe
     private final class SendCompletion {
 
         private final JmsOutboundMessageDispatch envelope;
-        private final JmsCompletionListener listener;
+        private final CompletionListener listener;
 
         private Exception failureCause;
         private boolean completed;
 
-        public SendCompletion(JmsOutboundMessageDispatch envelope, JmsCompletionListener listener) {
+        public SendCompletion(JmsOutboundMessageDispatch envelope, CompletionListener listener) {
             this.envelope = envelope;
             this.listener = listener;
         }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/main/java/org/apache/qpid/jms/exceptions/JmsExceptionSupport.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/exceptions/JmsExceptionSupport.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/exceptions/JmsExceptionSupport.java
index 48493bd..5f255f2 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/exceptions/JmsExceptionSupport.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/exceptions/JmsExceptionSupport.java
@@ -16,9 +16,29 @@
  */
 package org.apache.qpid.jms.exceptions;
 
+import javax.jms.IllegalStateException;
+import javax.jms.IllegalStateRuntimeException;
+import javax.jms.InvalidClientIDException;
+import javax.jms.InvalidClientIDRuntimeException;
+import javax.jms.InvalidDestinationException;
+import javax.jms.InvalidDestinationRuntimeException;
+import javax.jms.InvalidSelectorException;
+import javax.jms.InvalidSelectorRuntimeException;
 import javax.jms.JMSException;
+import javax.jms.JMSRuntimeException;
+import javax.jms.JMSSecurityException;
+import javax.jms.JMSSecurityRuntimeException;
 import javax.jms.MessageEOFException;
 import javax.jms.MessageFormatException;
+import javax.jms.MessageFormatRuntimeException;
+import javax.jms.MessageNotWriteableException;
+import javax.jms.MessageNotWriteableRuntimeException;
+import javax.jms.ResourceAllocationException;
+import javax.jms.ResourceAllocationRuntimeException;
+import javax.jms.TransactionInProgressException;
+import javax.jms.TransactionInProgressRuntimeException;
+import javax.jms.TransactionRolledBackException;
+import javax.jms.TransactionRolledBackRuntimeException;
 
 /**
  * Exception support class.
@@ -143,4 +163,50 @@ public final class JmsExceptionSupport {
         exception.initCause(cause);
         return exception;
     }
+
+    /**
+     * Creates the proper instance of a JMSRuntimeException based on the type
+     * of JMSException that is passed.
+     *
+     * @param exception
+     *      The JMSException instance to convert to a JMSRuntimeException
+     *
+     * @return a new {@link JMSRuntimeException} instance that reflects the original error.
+     */
+    public static JMSRuntimeException createRuntimeException(Exception exception) {
+        JMSRuntimeException result = null;
+        JMSException source = null;
+
+        if (!(exception instanceof JMSException)) {
+            throw new JMSRuntimeException(exception.getMessage(), null, exception);
+        } else {
+            source = (JMSException) exception;
+        }
+
+        if (source instanceof IllegalStateException) {
+            result = new IllegalStateRuntimeException(source.getMessage(), source.getErrorCode(), source);
+        } else if (source instanceof InvalidClientIDException) {
+            result = new InvalidClientIDRuntimeException(source.getMessage(), source.getErrorCode(), source);
+        } else if (source instanceof InvalidDestinationException) {
+            result = new InvalidDestinationRuntimeException(source.getMessage(), source.getErrorCode(), source);
+        } else if (source instanceof InvalidSelectorException) {
+            result = new InvalidSelectorRuntimeException(source.getMessage(), source.getErrorCode(), source);
+        } else if (source instanceof JMSSecurityException) {
+            result = new JMSSecurityRuntimeException(source.getMessage(), source.getErrorCode(), source);
+        } else if (source instanceof MessageFormatException) {
+            result = new MessageFormatRuntimeException(source.getMessage(), source.getErrorCode(), source);
+        } else if (source instanceof MessageNotWriteableException) {
+            result = new MessageNotWriteableRuntimeException(source.getMessage(), source.getErrorCode(), source);
+        } else if (source instanceof ResourceAllocationException) {
+            result = new ResourceAllocationRuntimeException(source.getMessage(), source.getErrorCode(), source);
+        } else if (source instanceof TransactionInProgressException) {
+            result = new TransactionInProgressRuntimeException(source.getMessage(), source.getErrorCode(), source);
+        } else if (source instanceof TransactionRolledBackException) {
+            result = new TransactionRolledBackRuntimeException(source.getMessage(), source.getErrorCode(), source);
+        } else {
+            result = new JMSRuntimeException(source.getMessage(), source.getErrorCode(), source);
+        }
+
+        return result;
+    }
 }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsBytesMessage.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsBytesMessage.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsBytesMessage.java
index 95e51e0..75bc30c 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsBytesMessage.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsBytesMessage.java
@@ -28,6 +28,7 @@ import javax.jms.MessageFormatException;
 import org.apache.qpid.jms.exceptions.JmsExceptionSupport;
 import org.apache.qpid.jms.message.facade.JmsBytesMessageFacade;
 
+@SuppressWarnings("unchecked")
 public class JmsBytesMessage extends JmsMessage implements BytesMessage {
 
     protected transient DataOutputStream dataOut;
@@ -396,6 +397,20 @@ public class JmsBytesMessage extends JmsMessage implements BytesMessage {
         return "JmsBytesMessage { " + facade + " }";
     }
 
+    @Override
+    public boolean isBodyAssignableTo(@SuppressWarnings("rawtypes") Class target) throws JMSException {
+        return facade.hasBody() ? target.isAssignableFrom(byte[].class) : true;
+    }
+
+    @Override
+    protected <T> T doGetBody(Class<T> asType) throws JMSException {
+        if (!facade.hasBody()) {
+            return null;
+        }
+
+        return (T) facade.copyBody();
+    }
+
     private void initializeWriting() throws JMSException {
         checkReadOnlyBody();
         if (this.dataOut == null) {

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsMapMessage.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsMapMessage.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsMapMessage.java
index add21f1..4ec02fe 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsMapMessage.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsMapMessage.java
@@ -17,6 +17,8 @@
 package org.apache.qpid.jms.message;
 
 import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
 
 import javax.jms.JMSException;
 import javax.jms.MapMessage;
@@ -27,6 +29,7 @@ import org.apache.qpid.jms.message.facade.JmsMapMessageFacade;
 /**
  * Implementation of the JMS MapMessage.
  */
+@SuppressWarnings("unchecked")
 public class JmsMapMessage extends JmsMessage implements MapMessage {
 
     JmsMapMessageFacade facade;
@@ -299,6 +302,27 @@ public class JmsMapMessage extends JmsMessage implements MapMessage {
         return "JmsMapMessage { " + facade + " }";
     }
 
+    @Override
+    public boolean isBodyAssignableTo(@SuppressWarnings("rawtypes") Class target) throws JMSException {
+        return facade.hasBody() ? target.isAssignableFrom(Map.class) : true;
+    }
+
+    @Override
+    protected <T> T doGetBody(Class<T> asType) throws JMSException {
+        if (!facade.hasBody()) {
+            return null;
+        }
+
+        Map<String, Object> copy = new HashMap<String, Object>();
+        Enumeration<String> keys = facade.getMapNames();
+        while (keys.hasMoreElements()) {
+            String key = keys.nextElement();
+            copy.put(key, getObject(key));
+        }
+
+        return (T) copy;
+    }
+
     private void put(String name, Object value) throws JMSException {
         checkReadOnlyBody();
         checkKeyNameIsValid(name);

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/0c39522c/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsMessage.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsMessage.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsMessage.java
index 68db061..05143d8 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsMessage.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsMessage.java
@@ -16,6 +16,8 @@
  */
 package org.apache.qpid.jms.message;
 
+import static org.apache.qpid.jms.message.JmsMessagePropertySupport.convertPropertyTo;
+
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashSet;
@@ -32,7 +34,6 @@ import org.apache.qpid.jms.JmsAcknowledgeCallback;
 import org.apache.qpid.jms.JmsConnection;
 import org.apache.qpid.jms.exceptions.JmsExceptionSupport;
 import org.apache.qpid.jms.message.facade.JmsMessageFacade;
-import org.apache.qpid.jms.util.TypeConversionSupport;
 
 public class JmsMessage implements javax.jms.Message {
 
@@ -103,6 +104,24 @@ public class JmsMessage implements javax.jms.Message {
     }
 
     @Override
+    public boolean isBodyAssignableTo(@SuppressWarnings("rawtypes") Class target) throws JMSException {
+        return true;
+    }
+
+    @Override
+    public final <T> T getBody(Class<T> asType) throws JMSException {
+        if (isBodyAssignableTo(asType)) {
+            return doGetBody(asType);
+        }
+
+        throw new MessageFormatException("Message body cannot be read as type: " + asType);
+    }
+
+    protected <T> T doGetBody(Class<T> asType) throws JMSException {
+        return null;
+    }
+
+    @Override
     public void clearBody() throws JMSException {
         readOnlyBody = false;
         facade.clearBody();
@@ -248,6 +267,16 @@ public class JmsMessage implements javax.jms.Message {
     }
 
     @Override
+    public long getJMSDeliveryTime() throws JMSException {
+        return facade.getDeliveryTime();
+    }
+
+    @Override
+    public void setJMSDeliveryTime(long deliveryTime) throws JMSException {
+        facade.setDeliveryTime(deliveryTime);
+    }
+
+    @Override
     public void clearProperties() throws JMSException {
         JmsMessagePropertyIntercepter.clearProperties(this, true);
     }
@@ -288,106 +317,42 @@ public class JmsMessage implements javax.jms.Message {
 
     @Override
     public boolean getBooleanProperty(String name) throws JMSException {
-        Object value = getObjectProperty(name);
-        if (value == null) {
-            return false;
-        }
-        Boolean rc = (Boolean) TypeConversionSupport.convert(value, Boolean.class);
-        if (rc == null) {
-            throw new MessageFormatException("Property " + name + " was a " + value.getClass().getName() + " and cannot be read as a boolean");
-        }
-        return rc.booleanValue();
+        return convertPropertyTo(name, getObjectProperty(name), Boolean.class);
     }
 
     @Override
     public byte getByteProperty(String name) throws JMSException {
-        Object value = getObjectProperty(name);
-        if (value == null) {
-            throw new NumberFormatException("property " + name + " was null");
-        }
-        Byte rc = (Byte) TypeConversionSupport.convert(value, Byte.class);
-        if (rc == null) {
-            throw new MessageFormatException("Property " + name + " was a " + value.getClass().getName() + " and cannot be read as a byte");
-        }
-        return rc.byteValue();
+        return convertPropertyTo(name, getObjectProperty(name), Byte.class);
     }
 
     @Override
     public short getShortProperty(String name) throws JMSException {
-        Object value = getObjectProperty(name);
-        if (value == null) {
-            throw new NumberFormatException("property " + name + " was null");
-        }
-        Short rc = (Short) TypeConversionSupport.convert(value, Short.class);
-        if (rc == null) {
-            throw new MessageFormatException("Property " + name + " was a " + value.getClass().getName() + " and cannot be read as a short");
-        }
-        return rc.shortValue();
+        return convertPropertyTo(name, getObjectProperty(name), Short.class);
     }
 
     @Override
     public int getIntProperty(String name) throws JMSException {
-        Object value = getObjectProperty(name);
-        if (value == null) {
-            throw new NumberFormatException("property " + name + " was null");
-        }
-        Integer rc = (Integer) TypeConversionSupport.convert(value, Integer.class);
-        if (rc == null) {
-            throw new MessageFormatException("Property " + name + " was a " + value.getClass().getName() + " and cannot be read as an integer");
-        }
-        return rc.intValue();
+        return convertPropertyTo(name, getObjectProperty(name), Integer.class);
     }
 
     @Override
     public long getLongProperty(String name) throws JMSException {
-        Object value = getObjectProperty(name);
-        if (value == null) {
-            throw new NumberFormatException("property " + name + " was null");
-        }
-        Long rc = (Long) TypeConversionSupport.convert(value, Long.class);
-        if (rc == null) {
-            throw new MessageFormatException("Property " + name + " was a " + value.getClass().getName() + " and cannot be read as a long");
-        }
-        return rc.longValue();
+        return convertPropertyTo(name, getObjectProperty(name), Long.class);
     }
 
     @Override
     public float getFloatProperty(String name) throws JMSException {
-        Object value = getObjectProperty(name);
-        if (value == null) {
-            throw new NullPointerException("property " + name + " was null");
-        }
-        Float rc = (Float) TypeConversionSupport.convert(value, Float.class);
-        if (rc == null) {
-            throw new MessageFormatException("Property " + name + " was a " + value.getClass().getName() + " and cannot be read as a float");
-        }
-        return rc.floatValue();
+        return convertPropertyTo(name, getObjectProperty(name), Float.class);
     }
 
     @Override
     public double getDoubleProperty(String name) throws JMSException {
-        Object value = getObjectProperty(name);
-        if (value == null) {
-            throw new NullPointerException("property " + name + " was null");
-        }
-        Double rc = (Double) TypeConversionSupport.convert(value, Double.class);
-        if (rc == null) {
-            throw new MessageFormatException("Property " + name + " was a " + value.getClass().getName() + " and cannot be read as a double");
-        }
-        return rc.doubleValue();
+        return convertPropertyTo(name, getObjectProperty(name), Double.class);
     }
 
     @Override
     public String getStringProperty(String name) throws JMSException {
-        Object value = getObjectProperty(name);
-        if (value == null) {
-            return null;
-        }
-        String rc = (String) TypeConversionSupport.convert(value, String.class);
-        if (rc == null) {
-            throw new MessageFormatException("Property " + name + " was a " + value.getClass().getName() + " and cannot be read as a String");
-        }
-        return rc;
+        return convertPropertyTo(name, getObjectProperty(name), String.class);
     }
 
     @Override


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


[7/7] qpid-jms git commit: QPIDJMS-207 Adds support for the JMS 2.0 Delayed Delivery feature

Posted by ta...@apache.org.
QPIDJMS-207 Adds support for the JMS 2.0 Delayed Delivery feature

Project: http://git-wip-us.apache.org/repos/asf/qpid-jms/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-jms/commit/6e442f4c
Tree: http://git-wip-us.apache.org/repos/asf/qpid-jms/tree/6e442f4c
Diff: http://git-wip-us.apache.org/repos/asf/qpid-jms/diff/6e442f4c

Branch: refs/heads/master
Commit: 6e442f4c6aa1401a14031c6f2f05d7edbd58037c
Parents: 0c39522
Author: Timothy Bish <ta...@gmail.com>
Authored: Mon Sep 12 15:20:25 2016 -0400
Committer: Timothy Bish <ta...@gmail.com>
Committed: Mon Sep 12 15:20:25 2016 -0400

----------------------------------------------------------------------
 .../message/JmsMessagePropertyIntercepter.java  |  32 +++++
 .../qpid/jms/message/JmsMessageSupport.java     |   1 +
 .../provider/amqp/AmqpConnectionProperties.java |  23 ++++
 .../jms/provider/amqp/AmqpFixedProducer.java    |  13 +-
 .../qpid/jms/provider/amqp/AmqpSupport.java     |   1 +
 .../amqp/message/AmqpJmsMessageFacade.java      |  16 ++-
 .../amqp/message/AmqpMessageSupport.java        |   5 +
 .../integration/ProducerIntegrationTest.java    |  82 +++++++++++++
 .../JmsMessagePropertyIntercepterTest.java      | 110 +++++++++++++++++
 .../amqp/message/AmqpJmsMessageFacadeTest.java  |  10 ++
 .../transports/netty/NettySimpleAmqpServer.java | 123 ++++++++++++++++++-
 11 files changed, 407 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/6e442f4c/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsMessagePropertyIntercepter.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsMessagePropertyIntercepter.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsMessagePropertyIntercepter.java
index bb2eb0b..65c8c9a 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsMessagePropertyIntercepter.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsMessagePropertyIntercepter.java
@@ -24,6 +24,7 @@ import static org.apache.qpid.jms.message.JmsMessageSupport.JMSX_GROUPSEQ;
 import static org.apache.qpid.jms.message.JmsMessageSupport.JMSX_USERID;
 import static org.apache.qpid.jms.message.JmsMessageSupport.JMS_AMQP_ACK_TYPE;
 import static org.apache.qpid.jms.message.JmsMessageSupport.JMS_CORRELATIONID;
+import static org.apache.qpid.jms.message.JmsMessageSupport.JMS_DELIVERYTIME;
 import static org.apache.qpid.jms.message.JmsMessageSupport.JMS_DELIVERY_MODE;
 import static org.apache.qpid.jms.message.JmsMessageSupport.JMS_DESTINATION;
 import static org.apache.qpid.jms.message.JmsMessageSupport.JMS_EXPIRATION;
@@ -135,6 +136,7 @@ public class JmsMessagePropertyIntercepter {
         STANDARD_HEADERS.add(JMS_TYPE);
         STANDARD_HEADERS.add(JMS_EXPIRATION);
         STANDARD_HEADERS.add(JMS_PRIORITY);
+        STANDARD_HEADERS.add(JMS_DELIVERYTIME);
 
         VENDOR_PROPERTIES.add(JMS_AMQP_ACK_TYPE);
 
@@ -638,6 +640,36 @@ public class JmsMessagePropertyIntercepter {
                 return true;
             }
         });
+        PROPERTY_INTERCEPTERS.put(JMS_DELIVERYTIME, new PropertyIntercepter() {
+            @Override
+            public Object getProperty(JmsMessage message) throws JMSException {
+                return Long.valueOf(message.getFacade().getDeliveryTime());
+            }
+
+            @Override
+            public void setProperty(JmsMessage message, Object value) throws JMSException {
+                Long rc = (Long) TypeConversionSupport.convert(value, Long.class);
+                if (rc == null) {
+                    throw new JMSException("Property JMSDeliveryTime cannot be set from a " + value.getClass().getName() + ".");
+                }
+                message.getFacade().setDeliveryTime(rc.longValue());
+            }
+
+            @Override
+            public boolean propertyExists(JmsMessage message) {
+                return message.getFacade().getDeliveryTime() > 0;
+            }
+
+            @Override
+            public void clearProperty(JmsMessage message) {
+                message.getFacade().setDeliveryTime(0);
+            }
+
+            @Override
+            public boolean isAlwaysWritable() {
+                return false;
+            }
+        });
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/6e442f4c/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsMessageSupport.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsMessageSupport.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsMessageSupport.java
index c3ce451..657542c 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsMessageSupport.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsMessageSupport.java
@@ -31,6 +31,7 @@ public class JmsMessageSupport {
     public static final String JMS_CORRELATIONID = "JMSCorrelationID";
     public static final String JMS_EXPIRATION = "JMSExpiration";
     public static final String JMS_REDELIVERED = "JMSRedelivered";
+    public static final String JMS_DELIVERYTIME = "JMSDeliveryTime";
 
     public static final String JMSX_GROUPID = "JMSXGroupID";
     public static final String JMSX_GROUPSEQ = "JMSXGroupSeq";

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/6e442f4c/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpConnectionProperties.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpConnectionProperties.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpConnectionProperties.java
index 79a0d95..c090853 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpConnectionProperties.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpConnectionProperties.java
@@ -18,6 +18,7 @@ package org.apache.qpid.jms.provider.amqp;
 
 import static org.apache.qpid.jms.provider.amqp.AmqpSupport.ANONYMOUS_RELAY;
 import static org.apache.qpid.jms.provider.amqp.AmqpSupport.CONNECTION_OPEN_FAILED;
+import static org.apache.qpid.jms.provider.amqp.AmqpSupport.DELAYED_DELIVERY;
 import static org.apache.qpid.jms.provider.amqp.AmqpSupport.QUEUE_PREFIX;
 import static org.apache.qpid.jms.provider.amqp.AmqpSupport.TOPIC_PREFIX;
 
@@ -41,6 +42,7 @@ public class AmqpConnectionProperties {
 
     private final JmsConnectionInfo connectionInfo;
 
+    private boolean delayedDeliverySupported = false;
     private boolean anonymousRelaySupported = false;
     private boolean connectionOpenFailed = false;
 
@@ -78,6 +80,10 @@ public class AmqpConnectionProperties {
         if (list.contains(ANONYMOUS_RELAY)) {
             anonymousRelaySupported = true;
         }
+
+        if (list.contains(DELAYED_DELIVERY)) {
+            delayedDeliverySupported = true;
+        }
     }
 
     protected void processProperties(Map<Symbol, Object> properties) {
@@ -104,6 +110,23 @@ public class AmqpConnectionProperties {
     }
 
     /**
+     * @return true if the connection supports sending message with delivery delays.
+     */
+    public boolean isDelayedDeliverySupported() {
+        return delayedDeliverySupported;
+    }
+
+    /**
+     * Sets if the connection supports sending message with assigned delivery delays.
+     *
+     * @param deliveryDelaySupported
+     *      true if the delivery delay features is supported.
+     */
+    public void setDeliveryDelaySupported(boolean deliveryDelaySupported) {
+        this.delayedDeliverySupported = deliveryDelaySupported;
+    }
+
+    /**
      * @return true if the connection supports sending to an anonymous relay.
      */
     public boolean isAnonymousRelaySupported() {

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/6e442f4c/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpFixedProducer.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpFixedProducer.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpFixedProducer.java
index 9233ce1..df39b78 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpFixedProducer.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpFixedProducer.java
@@ -68,12 +68,16 @@ public class AmqpFixedProducer extends AmqpProducer {
 
     private AsyncResult sendCompletionWatcher;
 
+    private final AmqpConnection connection;
+
     public AmqpFixedProducer(AmqpSession session, JmsProducerInfo info) {
-        super(session, info);
+        this(session, info, null);
     }
 
     public AmqpFixedProducer(AmqpSession session, JmsProducerInfo info, Sender sender) {
         super(session, info, sender);
+
+        connection = session.getConnection();
     }
 
     @Override
@@ -93,7 +97,12 @@ public class AmqpFixedProducer extends AmqpProducer {
             request.onFailure(new IllegalStateException("The MessageProducer is closed"));
         }
 
-        if (getEndpoint().getCredit() <= 0) {
+        if (!connection.getProperties().isDelayedDeliverySupported() &&
+            envelope.getMessage().getJMSDeliveryTime() != 0) {
+
+            // Don't allow sends with delay if the remote said it can't handle them
+            request.onFailure(new JMSException("Remote does not support delayed message delivery"));
+        } else if (getEndpoint().getCredit() <= 0) {
             LOG.trace("Holding Message send until credit is available.");
 
             InFlightSend send = new InFlightSend(envelope, request);

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/6e442f4c/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpSupport.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpSupport.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpSupport.java
index 10ae94f..9738d68 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpSupport.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpSupport.java
@@ -43,6 +43,7 @@ public class AmqpSupport {
     // Symbols used for connection capabilities
     public static final Symbol SOLE_CONNECTION_CAPABILITY = Symbol.valueOf("sole-connection-for-container");
     public static final Symbol ANONYMOUS_RELAY = Symbol.valueOf("ANONYMOUS-RELAY");
+    public static final Symbol DELAYED_DELIVERY = Symbol.valueOf("DELAYED_DELIVERY");
 
     // Symbols used to announce connection error information
     public static final Symbol CONNECTION_OPEN_FAILED = Symbol.valueOf("amqp:connection-establishment-failed");

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/6e442f4c/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageFacade.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageFacade.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageFacade.java
index 82f63e0..89094a1 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageFacade.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageFacade.java
@@ -17,6 +17,7 @@
 package org.apache.qpid.jms.provider.amqp.message;
 
 import static org.apache.qpid.jms.provider.amqp.message.AmqpMessageSupport.JMS_AMQP_TTL;
+import static org.apache.qpid.jms.provider.amqp.message.AmqpMessageSupport.JMS_DELIVERY_TIME;
 import static org.apache.qpid.jms.provider.amqp.message.AmqpMessageSupport.JMS_MESSAGE;
 import static org.apache.qpid.jms.provider.amqp.message.AmqpMessageSupport.JMS_MSG_TYPE;
 
@@ -555,14 +556,21 @@ public class AmqpJmsMessageFacade implements JmsMessageFacade {
 
     @Override
     public long getDeliveryTime() {
-        // TODO Auto-generated method stub
-        return 0;
+        Object deliveryTime = getMessageAnnotation(JMS_DELIVERY_TIME);
+        if (deliveryTime != null) {
+            return (long) deliveryTime;
+        }
+
+        return 0l;
     }
 
     @Override
     public void setDeliveryTime(long deliveryTime) {
-        // TODO Auto-generated method stub
-
+        if (deliveryTime != 0) {
+            setMessageAnnotation(JMS_DELIVERY_TIME, deliveryTime);
+        } else {
+            removeMessageAnnotation(JMS_DELIVERY_TIME);
+        }
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/6e442f4c/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpMessageSupport.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpMessageSupport.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpMessageSupport.java
index 271481b..40987e1 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpMessageSupport.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpMessageSupport.java
@@ -37,6 +37,11 @@ public final class AmqpMessageSupport {
     public static final String JMS_MSG_TYPE = "x-opt-jms-msg-type";
 
     /**
+     * Attribute used to mark the Application defined delivery time assigned to the message
+     */
+    public static final String JMS_DELIVERY_TIME = "x-opt-delivery-time";
+
+    /**
      * Value mapping for JMS_MSG_TYPE which indicates the message is a generic JMS Message
      * which has no body.
      */

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/6e442f4c/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ProducerIntegrationTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ProducerIntegrationTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ProducerIntegrationTest.java
index 0dee795..0e6b445 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ProducerIntegrationTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ProducerIntegrationTest.java
@@ -18,11 +18,13 @@
  */
 package org.apache.qpid.jms.integration;
 
+import static org.apache.qpid.jms.provider.amqp.AmqpSupport.DELAYED_DELIVERY;
 import static org.hamcrest.Matchers.both;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.greaterThanOrEqualTo;
 import static org.hamcrest.Matchers.isA;
 import static org.hamcrest.Matchers.lessThanOrEqualTo;
+import static org.hamcrest.Matchers.notNullValue;
 import static org.hamcrest.Matchers.nullValue;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -86,6 +88,7 @@ import org.apache.qpid.jms.test.testpeer.matchers.sections.MessagePropertiesSect
 import org.apache.qpid.jms.test.testpeer.matchers.sections.TransferPayloadCompositeMatcher;
 import org.apache.qpid.jms.test.testpeer.matchers.types.EncodedAmqpValueMatcher;
 import org.apache.qpid.proton.amqp.Binary;
+import org.apache.qpid.proton.amqp.Symbol;
 import org.apache.qpid.proton.amqp.UnsignedByte;
 import org.apache.qpid.proton.amqp.UnsignedInteger;
 import org.hamcrest.Matcher;
@@ -1834,6 +1837,85 @@ public class ProducerIntegrationTest extends QpidJmsTestCase {
     }
 
     @Test(timeout = 20000)
+    public void testSendFailsWhenDelayedDeliveryIsNotSupported() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+
+            // DO NOT add capability to indicate server support for DELAYED-DELIVERY
+
+            Connection connection = testFixture.establishConnecton(testPeer);
+
+            connection.start();
+
+            testPeer.expectBegin();
+            testPeer.expectSenderAttach();
+
+            Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+            String topicName = "myTopic";
+            Topic dest = session.createTopic(topicName);
+
+            MessageProducer producer = session.createProducer(dest);
+            producer.setDeliveryDelay(5000);
+
+            // Producer should fail to send when message has delivery delay since remote
+            // did not report that it supports that option.
+            Message message = session.createMessage();
+            try {
+                producer.send(message);
+                fail("Send should fail");
+            } catch (JMSException jmsEx) {
+                LOG.debug("Caught expected error from failed send.");
+            }
+
+            testPeer.expectClose();
+            connection.close();
+
+            testPeer.waitForAllHandlersToComplete(1000);
+        }
+    }
+
+    @Test(timeout = 20000)
+    public void testSendWorksWhenDelayedDeliveryIsSupported() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+
+            String topicName = "myTopic";
+
+            // DO add capability to indicate server support for DELAYED-DELIVERY
+
+            Connection connection = testFixture.establishConnecton(testPeer, new Symbol[]{ DELAYED_DELIVERY });
+
+            connection.start();
+
+            testPeer.expectBegin();
+            testPeer.expectSenderAttach();
+
+            MessageHeaderSectionMatcher headersMatcher = new MessageHeaderSectionMatcher(true).withDurable(equalTo(true));
+            MessageAnnotationsSectionMatcher msgAnnotationsMatcher = new MessageAnnotationsSectionMatcher(true);
+            Symbol annotationKey = AmqpMessageSupport.getSymbol(AmqpMessageSupport.JMS_DELIVERY_TIME);
+            msgAnnotationsMatcher.withEntry(annotationKey, notNullValue());
+
+            TransferPayloadCompositeMatcher messageMatcher = new TransferPayloadCompositeMatcher();
+            messageMatcher.setHeadersMatcher(headersMatcher);
+            messageMatcher.setMessageAnnotationsMatcher(msgAnnotationsMatcher);
+
+            testPeer.expectTransfer(messageMatcher);
+
+            Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+            Topic dest = session.createTopic(topicName);
+
+            MessageProducer producer = session.createProducer(dest);
+            producer.setDeliveryDelay(5000);
+            producer.send(session.createMessage());
+
+            testPeer.expectClose();
+            connection.close();
+
+            testPeer.waitForAllHandlersToComplete(1000);
+        }
+    }
+
+    @Test(timeout = 20000)
     public void testAsyncCompletionAfterSendMessageGetDispoation() throws Exception {
         try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
             Connection connection = testFixture.establishConnecton(testPeer);

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/6e442f4c/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/JmsMessagePropertyIntercepterTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/JmsMessagePropertyIntercepterTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/JmsMessagePropertyIntercepterTest.java
index 75a1a78..055602d 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/JmsMessagePropertyIntercepterTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/JmsMessagePropertyIntercepterTest.java
@@ -22,6 +22,7 @@ import static org.apache.qpid.jms.message.JmsMessageSupport.JMSX_GROUPSEQ;
 import static org.apache.qpid.jms.message.JmsMessageSupport.JMSX_USERID;
 import static org.apache.qpid.jms.message.JmsMessageSupport.JMS_AMQP_ACK_TYPE;
 import static org.apache.qpid.jms.message.JmsMessageSupport.JMS_CORRELATIONID;
+import static org.apache.qpid.jms.message.JmsMessageSupport.JMS_DELIVERYTIME;
 import static org.apache.qpid.jms.message.JmsMessageSupport.JMS_DELIVERY_MODE;
 import static org.apache.qpid.jms.message.JmsMessageSupport.JMS_DESTINATION;
 import static org.apache.qpid.jms.message.JmsMessageSupport.JMS_EXPIRATION;
@@ -1778,4 +1779,113 @@ public class JmsMessagePropertyIntercepterTest {
         JmsMessagePropertyIntercepter.clearProperties(message, true);
         assertFalse(JmsMessagePropertyIntercepter.propertyExists(message, JMS_AMQP_ACK_TYPE));
     }
+
+    //---------- JMSDeliveryTime ---------------------------------------------//
+
+    @Test
+    public void testJMSDeliveryTimeInGetAllPropertyNames() throws JMSException {
+        JmsMessageFacade facade = Mockito.mock(JmsMessageFacade.class);
+        JmsMessage message = Mockito.mock(JmsMapMessage.class);
+        Mockito.when(message.getFacade()).thenReturn(facade);
+        assertTrue(JmsMessagePropertyIntercepter.getAllPropertyNames(message).contains(JMS_DELIVERYTIME));
+    }
+
+    @Test
+    public void testGetJMSDeliveryWhenNotSet() throws JMSException {
+        JmsMessageFacade facade = Mockito.mock(JmsMessageFacade.class);
+        JmsMessage message = Mockito.mock(JmsMapMessage.class);
+        Mockito.when(message.getFacade()).thenReturn(facade);
+        Mockito.when(facade.getDeliveryTime()).thenReturn(0L);
+        assertEquals(Long.valueOf(0L), JmsMessagePropertyIntercepter.getProperty(message, JMS_DELIVERYTIME));
+        Mockito.verify(facade).getDeliveryTime();
+    }
+
+    @Test
+    public void testGetJMSDeliveryTimeWhenSet() throws JMSException {
+        JmsMessageFacade facade = Mockito.mock(JmsMessageFacade.class);
+        JmsMessage message = Mockito.mock(JmsMapMessage.class);
+        Mockito.when(message.getFacade()).thenReturn(facade);
+        Mockito.when(facade.getDeliveryTime()).thenReturn(900L);
+        assertEquals(900L, JmsMessagePropertyIntercepter.getProperty(message, JMS_DELIVERYTIME));
+    }
+
+    @Test
+    public void testSetJMSDeliveryTime() throws JMSException {
+        JmsMessageFacade facade = Mockito.mock(JmsMessageFacade.class);
+        JmsMessage message = Mockito.mock(JmsMapMessage.class);
+        Mockito.when(message.getFacade()).thenReturn(facade);
+        JmsMessagePropertyIntercepter.setProperty(message, JMS_DELIVERYTIME, 65536L);
+        Mockito.verify(facade).setDeliveryTime(65536L);
+    }
+
+    @Test
+    public void testJMSDeliveryTimeInGetPropertyNamesWhenSet() throws JMSException {
+        doJMSDeliveryTimeInGetPropertyNamesWhenSetTestImpl(false);
+    }
+
+    @Test
+    public void testJMSDeliveryTimeNotInGetPropertyNamesWhenSetAndExcludingStandardJMSHeaders() throws JMSException {
+        doJMSDeliveryTimeInGetPropertyNamesWhenSetTestImpl(true);
+    }
+
+    private void doJMSDeliveryTimeInGetPropertyNamesWhenSetTestImpl(boolean excludeStandardJmsHeaders) throws JMSException {
+        JmsMessageFacade facade = Mockito.mock(JmsMessageFacade.class);
+        JmsMessage message = Mockito.mock(JmsMapMessage.class);
+        Mockito.when(message.getFacade()).thenReturn(facade);
+        Mockito.when(facade.getDeliveryTime()).thenReturn(900L);
+        if (excludeStandardJmsHeaders) {
+            assertFalse(JmsMessagePropertyIntercepter.getPropertyNames(message, true).contains(JMS_DELIVERYTIME));
+        } else {
+            assertTrue(JmsMessagePropertyIntercepter.getPropertyNames(message, false).contains(JMS_DELIVERYTIME));
+        }
+    }
+
+    @Test
+    public void testJMSDeliveryTimeNotInGetPropertyNamesWhenNotSet() throws JMSException {
+        JmsMessageFacade facade = Mockito.mock(JmsMessageFacade.class);
+        JmsMessage message = Mockito.mock(JmsMapMessage.class);
+        Mockito.when(message.getFacade()).thenReturn(facade);
+        assertFalse(JmsMessagePropertyIntercepter.getPropertyNames(message, false).contains(JMS_DELIVERYTIME));
+    }
+
+    @Test
+    public void testJMSDeliveryTimePropertExistsWhenSet() throws JMSException {
+        JmsMessageFacade facade = Mockito.mock(JmsMessageFacade.class);
+        JmsMessage message = Mockito.mock(JmsMapMessage.class);
+        Mockito.when(message.getFacade()).thenReturn(facade);
+        Mockito.when(facade.getDeliveryTime()).thenReturn(900L);
+        assertTrue(JmsMessagePropertyIntercepter.propertyExists(message, JMS_DELIVERYTIME));
+    }
+
+    @Test
+    public void testJMSDeliveryTimePropertExistsWhenNotSet() throws JMSException {
+        JmsMessageFacade facade = Mockito.mock(JmsMessageFacade.class);
+        JmsMessage message = Mockito.mock(JmsMapMessage.class);
+        Mockito.when(message.getFacade()).thenReturn(facade);
+        Mockito.when(facade.getDeliveryTime()).thenReturn(0L);
+        assertFalse(JmsMessagePropertyIntercepter.propertyExists(message, JMS_DELIVERYTIME));
+    }
+
+    @Test
+    public void testSetJMSDeliveryTimeConversionChecks() throws JMSException {
+        JmsMessageFacade facade = Mockito.mock(JmsMessageFacade.class);
+        JmsMessage message = Mockito.mock(JmsMapMessage.class);
+        Mockito.when(message.getFacade()).thenReturn(facade);
+        try {
+            JmsMessagePropertyIntercepter.setProperty(message, JMS_DELIVERYTIME, new byte[1]);
+            fail("Should have thrown an exception for this call");
+        } catch (JMSException e) {
+        }
+    }
+
+    @Test
+    public void testJMSDeliveryTimeClearedWhenRequested() throws JMSException {
+        JmsMessageFacade facade = Mockito.mock(JmsMessageFacade.class);
+        JmsMessage message = Mockito.mock(JmsMapMessage.class);
+        Mockito.when(message.getFacade()).thenReturn(facade);
+        JmsMessagePropertyIntercepter.clearProperties(message, true);
+        Mockito.verify(facade, Mockito.never()).setDeliveryTime(0);
+        JmsMessagePropertyIntercepter.clearProperties(message, false);
+        Mockito.verify(facade).setDeliveryTime(0);
+    }
 }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/6e442f4c/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageFacadeTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageFacadeTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageFacadeTest.java
index e430f87..bd50a67 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageFacadeTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageFacadeTest.java
@@ -1518,6 +1518,16 @@ public class AmqpJmsMessageFacadeTest extends AmqpJmsMessageTypesTestCase  {
     }
 
     @Test
+    public void testNewMessageDoesNotHaveUnderlyingMessageAnnotationsSectionWithDeliveryTime() {
+        AmqpJmsMessageFacade amqpMessageFacade = createNewMessageFacade();;
+
+        Message underlying = amqpMessageFacade.getAmqpMessage();
+        assertNotNull(underlying.getMessageAnnotations());
+        Symbol annotationKey = AmqpMessageSupport.getSymbol(AmqpMessageSupport.JMS_DELIVERY_TIME);
+        assertNull(underlying.getMessageAnnotations().getValue().get(annotationKey));
+    }
+
+    @Test
     public void testMessageAnnotationExistsUsingReceivedMessageWithoutMessageAnnotationsSection() {
         String symbolKeyName = "myTestSymbolName";
 

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/6e442f4c/qpid-jms-client/src/test/java/org/apache/qpid/jms/transports/netty/NettySimpleAmqpServer.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/transports/netty/NettySimpleAmqpServer.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/transports/netty/NettySimpleAmqpServer.java
index 7c23d86..1525144 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/transports/netty/NettySimpleAmqpServer.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/transports/netty/NettySimpleAmqpServer.java
@@ -19,6 +19,7 @@ package org.apache.qpid.jms.transports.netty;
 import static org.apache.qpid.jms.provider.amqp.AmqpSupport.ANONYMOUS_RELAY;
 import static org.apache.qpid.jms.provider.amqp.AmqpSupport.CONNECTION_OPEN_FAILED;
 import static org.apache.qpid.jms.provider.amqp.AmqpSupport.CONTAINER_ID;
+import static org.apache.qpid.jms.provider.amqp.AmqpSupport.DELAYED_DELIVERY;
 import static org.apache.qpid.jms.provider.amqp.AmqpSupport.INVALID_FIELD;
 import static org.apache.qpid.jms.provider.amqp.AmqpSupport.PLATFORM;
 import static org.apache.qpid.jms.provider.amqp.AmqpSupport.PRODUCT;
@@ -32,8 +33,13 @@ import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.qpid.jms.transports.TransportOptions;
+import org.apache.qpid.jms.util.IdGenerator;
 import org.apache.qpid.proton.Proton;
 import org.apache.qpid.proton.amqp.Symbol;
 import org.apache.qpid.proton.amqp.transport.AmqpError;
@@ -42,7 +48,9 @@ import org.apache.qpid.proton.engine.Collector;
 import org.apache.qpid.proton.engine.Connection;
 import org.apache.qpid.proton.engine.EndpointState;
 import org.apache.qpid.proton.engine.Event;
+import org.apache.qpid.proton.engine.Receiver;
 import org.apache.qpid.proton.engine.Sasl;
+import org.apache.qpid.proton.engine.Sender;
 import org.apache.qpid.proton.engine.Session;
 import org.apache.qpid.proton.engine.Transport;
 import org.apache.qpid.proton.engine.impl.CollectorImpl;
@@ -60,15 +68,19 @@ import io.netty.channel.SimpleChannelInboundHandler;
  * Simple Netty based server that can handle a small subset of AMQP events
  * using Proton-J as the protocol engine.
  */
+@SuppressWarnings( "unused" )
 public class NettySimpleAmqpServer extends NettyServer {
 
     private static final Logger LOG = LoggerFactory.getLogger(NettySimpleAmqpServer.class);
 
+    private static final AtomicInteger SERVER_SEQUENCE = new AtomicInteger();
+
     private static final int CHANNEL_MAX = 32767;
     private static final int HEADER_SIZE = 8;
     private static final int SASL_PROTOCOL = 3;
 
     private final Map<String, List<Connection>> connections = new HashMap<String, List<Connection>>();
+    private final ScheduledExecutorService serializer;
 
     private boolean allowNonSaslConnections;
     private ConnectionIntercepter connectionIntercepter;
@@ -83,6 +95,18 @@ public class NettySimpleAmqpServer extends NettyServer {
 
     public NettySimpleAmqpServer(TransportOptions options, boolean needClientAuth, boolean webSocketServer) {
         super(options, needClientAuth, webSocketServer);
+
+        this.serializer = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
+
+            @Override
+            public Thread newThread(Runnable runner) {
+                Thread serial = new Thread(runner);
+                serial.setDaemon(true);
+                serial.setName(NettySimpleAmqpServer.this.getClass().getSimpleName() + ":(" +
+                               SERVER_SEQUENCE.incrementAndGet() + "):Worker");
+                return serial;
+            }
+        });
     }
 
     @Override
@@ -111,11 +135,15 @@ public class NettySimpleAmqpServer extends NettyServer {
 
     private final class ProtonConnection extends SimpleChannelInboundHandler<ByteBuf>  {
 
+        private final IdGenerator sessionIdGenerator = new IdGenerator();
+
         private final Transport protonTransport = Proton.transport();
         private final Connection protonConnection = Proton.connection();
         private final Collector eventCollector = new CollectorImpl();
         private SaslAuthenticator authenticator;
 
+        private final Map<String, ProtonSession> sessions = new HashMap<String, ProtonSession>();
+
         private boolean exclusiveContainerId;
         private boolean headerRead;
         private final ByteBuf headerBuf = Unpooled.buffer(HEADER_SIZE, HEADER_SIZE);
@@ -223,6 +251,21 @@ public class NettySimpleAmqpServer extends NettyServer {
                     case SESSION_REMOTE_CLOSE:
                         processSessionClose(event.getSession());
                         break;
+                    case LINK_REMOTE_OPEN:
+                        //processLinkOpen(event.getLink());
+                        break;
+                    case LINK_REMOTE_DETACH:
+                        //processLinkDetach(event.getLink());
+                        break;
+                    case LINK_REMOTE_CLOSE:
+                        //processLinkClose(event.getLink());
+                        break;
+                    case LINK_FLOW:
+                        //processLinkFlow(event.getLink());
+                        break;
+                    case DELIVERY:
+                        //processDelivery(event.getDelivery());
+                        break;
                     default:
                         break;
                 }
@@ -263,11 +306,17 @@ public class NettySimpleAmqpServer extends NettyServer {
         }
 
         private void processSessionClose(Session session) {
+            ProtonSession protonSession = (ProtonSession) session.getContext();
+
+            sessions.remove(protonSession.getId());
+
             session.close();
             session.free();
         }
 
         private void processSessionOpen(Session session) {
+            ProtonSession protonSession = new ProtonSession(sessionIdGenerator.generateId(), session);
+            sessions.put(protonSession.getId(), protonSession);
             session.open();
         }
 
@@ -387,7 +436,7 @@ public class NettySimpleAmqpServer extends NettyServer {
     }
 
     private Symbol[] getConnectionCapabilitiesOffered() {
-        return new Symbol[]{ ANONYMOUS_RELAY };
+        return new Symbol[]{ ANONYMOUS_RELAY, DELAYED_DELIVERY };
     }
 
     private Map<Symbol, Object> getConnetionProperties() {
@@ -453,7 +502,74 @@ public class NettySimpleAmqpServer extends NettyServer {
 
     }
 
-    //----- Internal Type Implementations ------------------------------------//
+    //----- Session Manager --------------------------------------------------//
+
+    private class ProtonSession {
+
+        private final String sessionId;
+        private final Session session;
+
+        private Map<String, ProtonSender> senders = new HashMap<String, ProtonSender>();
+        private Map<String, ProtonReceiver> receivers = new HashMap<String, ProtonReceiver>();
+
+        public ProtonSession(String sessionId, Session session) {
+            this.sessionId = sessionId;
+            this.session = session;
+            this.session.setContext(this);
+        }
+
+        public Session getSession() {
+            return session;
+        }
+
+        public String getId() {
+            return sessionId;
+        }
+    }
+
+    //----- Sender Manager ---------------------------------------------------//
+
+    private class ProtonSender {
+
+        private final String senderId;
+        private final Sender sender;
+
+        public ProtonSender(String senderId, Sender sender) {
+            this.senderId = senderId;
+            this.sender = sender;
+        }
+
+        public String getId() {
+            return senderId;
+        }
+
+        public Sender getSender() {
+            return sender;
+        }
+    }
+
+    //----- Receiver Manager ---------------------------------------------------//
+
+    private class ProtonReceiver {
+
+        private final String receiverId;
+        private final Receiver receiver;
+
+        public ProtonReceiver(String receiverId, Receiver receiver) {
+            this.receiverId = receiverId;
+            this.receiver = receiver;
+        }
+
+        public String getId() {
+            return receiverId;
+        }
+
+        public Receiver getReceiver() {
+            return receiver;
+        }
+    }
+
+    //----- SASL Authentication Manager --------------------------------------//
 
     private class SaslAuthenticator {
 
@@ -496,7 +612,8 @@ public class NettySimpleAmqpServer extends NettyServer {
         }
     }
 
-    @SuppressWarnings("unused")
+    //----- Simple AMQP Header Wrapper ---------------------------------------//
+
     private class AmqpHeader {
 
         private final byte[] PREFIX = new byte[] { 'A', 'M', 'Q', 'P' };


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


[3/7] qpid-jms git commit: QPIDJMS-207 Adds support for Asynchronous JMS 2.0 sends.

Posted by ta...@apache.org.
QPIDJMS-207 Adds support for Asynchronous JMS 2.0 sends.

Project: http://git-wip-us.apache.org/repos/asf/qpid-jms/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-jms/commit/3a03663b
Tree: http://git-wip-us.apache.org/repos/asf/qpid-jms/tree/3a03663b
Diff: http://git-wip-us.apache.org/repos/asf/qpid-jms/diff/3a03663b

Branch: refs/heads/master
Commit: 3a03663b79f98f80cd75f297cd9b70241ac68da3
Parents: 6553cfd
Author: Timothy Bish <ta...@gmail.com>
Authored: Mon Sep 12 13:09:56 2016 -0400
Committer: Timothy Bish <ta...@gmail.com>
Committed: Mon Sep 12 13:09:56 2016 -0400

----------------------------------------------------------------------
 .../apache/qpid/jms/JmsCompletionListener.java  |  47 ++
 .../java/org/apache/qpid/jms/JmsConnection.java |  36 +-
 .../org/apache/qpid/jms/JmsMessageConsumer.java |   6 +-
 .../org/apache/qpid/jms/JmsMessageProducer.java | 100 +++-
 .../java/org/apache/qpid/jms/JmsSession.java    | 349 +++++++++--
 .../jms/message/JmsInboundMessageDispatch.java  |  21 +-
 .../jms/message/JmsOutboundMessageDispatch.java |  42 +-
 .../jms/provider/DefaultProviderListener.java   |   9 +
 .../qpid/jms/provider/ProviderListener.java     |  22 +
 .../qpid/jms/provider/ProviderWrapper.java      |  10 +
 .../jms/provider/amqp/AmqpAbstractResource.java |   5 +-
 .../amqp/AmqpAnonymousFallbackProducer.java     |  17 +-
 .../qpid/jms/provider/amqp/AmqpConsumer.java    |  55 +-
 .../qpid/jms/provider/amqp/AmqpEventSink.java   |   6 +-
 .../jms/provider/amqp/AmqpExceptionBuilder.java |  34 ++
 .../jms/provider/amqp/AmqpFixedProducer.java    | 257 +++++---
 .../qpid/jms/provider/amqp/AmqpProducer.java    |  15 +-
 .../qpid/jms/provider/amqp/AmqpProvider.java    |  39 +-
 .../provider/amqp/AmqpTransactionContext.java   | 155 +++--
 .../amqp/AmqpTransactionCoordinator.java        |   4 +-
 .../amqp/builders/AmqpResourceBuilder.java      |  13 +-
 .../jms/provider/failover/FailoverProvider.java |  18 +
 .../integration/ConsumerIntegrationTest.java    | 109 ++++
 .../PresettledProducerIntegrationTest.java      | 231 ++++++++
 .../integration/ProducerIntegrationTest.java    | 592 +++++++++++++++++++
 .../jms/integration/SessionIntegrationTest.java | 114 +++-
 .../jms/producer/JmsMessageProducerTest.java    | 421 ++++++++++++-
 .../failover/FailoverIntegrationTest.java       | 194 ++++++
 .../qpid/jms/provider/mock/MockProvider.java    |  12 +-
 .../mock/MockProviderConfiguration.java         |  10 +
 .../qpid/jms/provider/mock/MockRemotePeer.java  | 123 ++++
 31 files changed, 2793 insertions(+), 273 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsCompletionListener.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsCompletionListener.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsCompletionListener.java
new file mode 100644
index 0000000..7a6c4d6
--- /dev/null
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsCompletionListener.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.qpid.jms;
+
+import javax.jms.Message;
+
+/**
+ * Interface used to implement listeners for asynchronous {@link javax.jms.Message}
+ * sends which will be notified on successful completion of a send or be notified of an
+ * error that was encountered while attempting to send a {@link javax.jms.Message}.
+ */
+public interface JmsCompletionListener {
+
+    /**
+     * Called when an asynchronous send operation completes successfully.
+     *
+     * @param message
+     *      the {@link javax.jms.Message} that was successfully sent.
+     */
+    void onCompletion(Message message);
+
+    /**
+     * Called when an asynchronous send operation fails to complete, the state
+     * of the send is unknown at this point.
+     *
+     * @param message
+     *      the {@link javax.jms.Message} that was to be sent.
+     * @param exception
+     *      the {@link java.lang.Exception} that describes the send error.
+     */
+    void onException(Message message, Exception exception);
+
+}

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsConnection.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsConnection.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsConnection.java
index 827da11..a04d1b3 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsConnection.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsConnection.java
@@ -156,6 +156,11 @@ public class JmsConnection implements AutoCloseable, Connection, TopicConnection
     public void close() throws JMSException {
         boolean interrupted = Thread.interrupted();
 
+        for (JmsSession session : sessions.values()) {
+            session.checkIsDeliveryThread();
+            session.checkIsCompletionThread();
+        }
+
         try {
 
             if (!closed.get() && !failed.get()) {
@@ -1072,6 +1077,26 @@ public class JmsConnection implements AutoCloseable, Connection, TopicConnection
     }
 
     @Override
+    public void onCompletedMessageSend(JmsOutboundMessageDispatch envelope) {
+        JmsSession session = sessions.get(envelope.getProducerId().getParentId());
+        if (session != null) {
+            session.onCompletedMessageSend(envelope);
+        } else {
+            LOG.debug("No matching Session found for async send result");
+        }
+    }
+
+    @Override
+    public void onFailedMessageSend(JmsOutboundMessageDispatch envelope, Throwable cause) {
+        JmsSession session = sessions.get(envelope.getProducerId().getParentId());
+        if (session != null) {
+            session.onFailedMessageSend(envelope, cause);
+        } else {
+            LOG.debug("No matching Session found for failed async send result");
+        }
+    }
+
+    @Override
     public void onConnectionInterrupted(final URI remoteURI) {
         for (JmsSession session : sessions.values()) {
             session.onConnectionInterrupted();
@@ -1161,6 +1186,12 @@ public class JmsConnection implements AutoCloseable, Connection, TopicConnection
     public void onConnectionFailure(final IOException ex) {
         providerFailed(ex);
 
+        // Signal that connection dropped we need to mark transactions as
+        // failed, deliver failure events to asynchronous send completions etc.
+        for (JmsSession session : sessions.values()) {
+            session.onConnectionInterrupted();
+        }
+
         onProviderException(ex);
 
         for (AsyncResult request : requests.keySet()) {
@@ -1304,10 +1335,7 @@ public class JmsConnection implements AutoCloseable, Connection, TopicConnection
         if (!closed.get() && !closing.get()) {
             if (this.exceptionListener != null) {
 
-                if (!(error instanceof JMSException)) {
-                    error = JmsExceptionSupport.create(error);
-                }
-                final JMSException jmsError = (JMSException)error;
+                final JMSException jmsError = JmsExceptionSupport.create(error);
 
                 executor.execute(new Runnable() {
                     @Override

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsMessageConsumer.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsMessageConsumer.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsMessageConsumer.java
index de7ef63..18fd764 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsMessageConsumer.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsMessageConsumer.java
@@ -438,10 +438,10 @@ public class JmsMessageConsumer implements AutoCloseable, MessageConsumer, JmsMe
             }
 
             if (this.messageListener != null && this.started) {
-                session.getExecutor().execute(new MessageDeliverTask());
+                session.getDispatcherExecutor().execute(new MessageDeliverTask());
             } else {
                 if (availableListener != null) {
-                    session.getExecutor().execute(new Runnable() {
+                    session.getDispatcherExecutor().execute(new Runnable() {
                         @Override
                         public void run() {
                             if (session.isStarted()) {
@@ -507,7 +507,7 @@ public class JmsMessageConsumer implements AutoCloseable, MessageConsumer, JmsMe
 
     void drainMessageQueueToListener() {
         if (this.messageListener != null && this.started) {
-            session.getExecutor().execute(new MessageDeliverTask());
+            session.getDispatcherExecutor().execute(new MessageDeliverTask());
         }
     }
 

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsMessageProducer.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsMessageProducer.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsMessageProducer.java
index a1bbe38..65812b7 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsMessageProducer.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsMessageProducer.java
@@ -158,7 +158,7 @@ public class JmsMessageProducer implements AutoCloseable, MessageProducer {
             throw new UnsupportedOperationException("Using this method is not supported on producers created without an explicit Destination");
         }
 
-        sendMessage(producerInfo.getDestination(), message, deliveryMode, priority, timeToLive);
+        sendMessage(producerInfo.getDestination(), message, deliveryMode, priority, timeToLive, null);
     }
 
     @Override
@@ -174,15 +174,107 @@ public class JmsMessageProducer implements AutoCloseable, MessageProducer {
             throw new UnsupportedOperationException("Using this method is not supported on producers created with an explicit Destination.");
         }
 
-        sendMessage(destination, message, deliveryMode, priority, timeToLive);
+        sendMessage(destination, message, deliveryMode, priority, timeToLive, null);
     }
 
-    private void sendMessage(Destination destination, Message message, int deliveryMode, int priority, long timeToLive) throws JMSException {
+    /**
+     * Sends the message asynchronously and notifies the assigned listener on success or failure
+     *
+     * @param message
+     *      the {@link javax.jms.Message} to send.
+     * @param listener
+     *      the {@link JmsCompletionListener} to notify on send success or failure.
+     *
+     * @throws JMSException if an error occurs while attempting to send the Message.
+     */
+    public void send(Message message, JmsCompletionListener listener) throws JMSException {
+        send(message, this.deliveryMode, this.priority, this.timeToLive, listener);
+    }
+
+    /**
+     * Sends the message asynchronously and notifies the assigned listener on success or failure
+     *
+     * @param message
+     *      the {@link javax.jms.Message} to send.
+     * @param deliveryMode
+     *      the delivery mode to assign to the outbound Message.
+     * @param priority
+     *      the priority to assign to the outbound Message.
+     * @param timeToLive
+     *      the time to live value to assign to the outbound Message.
+     * @param listener
+     *      the {@link JmsCompletionListener} to notify on send success or failure.
+     *
+     * @throws JMSException if an error occurs while attempting to send the Message.
+     */
+    public void send(Message message, int deliveryMode, int priority, long timeToLive, JmsCompletionListener listener) throws JMSException {
+        checkClosed();
+
+        if (anonymousProducer) {
+            throw new UnsupportedOperationException("Using this method is not supported on producers created without an explicit Destination");
+        }
+
+        if (listener == null) {
+            throw new IllegalArgumentException("JmsCompletetionListener cannot be null");
+        }
+
+        sendMessage(producerInfo.getDestination(), message, deliveryMode, priority, timeToLive, listener);
+    }
+
+    /**
+     * Sends the message asynchronously and notifies the assigned listener on success or failure
+     *
+     * @param destination
+     *      the Destination to send the given Message to.
+     * @param message
+     *      the {@link javax.jms.Message} to send.
+     * @param listener
+     *      the {@link JmsCompletionListener} to notify on send success or failure.
+     *
+     * @throws JMSException if an error occurs while attempting to send the Message.
+     */
+    public void send(Destination destination, Message message, JmsCompletionListener listener) throws JMSException {
+        send(destination, message, this.deliveryMode, this.priority, this.timeToLive, listener);
+    }
+
+    /**
+     * Sends the message asynchronously and notifies the assigned listener on success or failure
+     *
+     * @param destination
+     *      the Destination to send the given Message to.
+     * @param message
+     *      the {@link javax.jms.Message} to send.
+     * @param deliveryMode
+     *      the delivery mode to assign to the outbound Message.
+     * @param priority
+     *      the priority to assign to the outbound Message.
+     * @param timeToLive
+     *      the time to live value to assign to the outbound Message.
+     * @param listener
+     *      the {@link JmsCompletionListener} to notify on send success or failure.
+     *
+     * @throws JMSException if an error occurs while attempting to send the Message.
+     */
+    public void send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive, JmsCompletionListener listener) throws JMSException {
+        checkClosed();
+
+        if (!anonymousProducer) {
+            throw new UnsupportedOperationException("Using this method is not supported on producers created with an explicit Destination.");
+        }
+
+        if (listener == null) {
+            throw new IllegalArgumentException("JmsCompletetionListener cannot be null");
+        }
+
+        sendMessage(destination, message, deliveryMode, priority, timeToLive, listener);
+    }
+
+    private void sendMessage(Destination destination, Message message, int deliveryMode, int priority, long timeToLive, JmsCompletionListener listener) throws JMSException {
         if (destination == null) {
             throw new InvalidDestinationException("Don't understand null destinations");
         }
 
-        this.session.send(this, destination, message, deliveryMode, priority, timeToLive, disableMessageId, disableTimestamp);
+        this.session.send(this, destination, message, deliveryMode, priority, timeToLive, disableMessageId, disableTimestamp, listener);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsSession.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsSession.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsSession.java
index 4644267..817f342 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsSession.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsSession.java
@@ -18,8 +18,11 @@ package org.apache.qpid.jms;
 
 import java.io.Serializable;
 import java.util.ArrayList;
+import java.util.Deque;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedDeque;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -58,6 +61,7 @@ import javax.jms.TopicPublisher;
 import javax.jms.TopicSession;
 import javax.jms.TopicSubscriber;
 
+import org.apache.qpid.jms.exceptions.JmsExceptionSupport;
 import org.apache.qpid.jms.message.JmsInboundMessageDispatch;
 import org.apache.qpid.jms.message.JmsMessage;
 import org.apache.qpid.jms.message.JmsMessageTransformation;
@@ -98,14 +102,18 @@ public class JmsSession implements AutoCloseable, Session, QueueSession, TopicSe
     private final LinkedBlockingQueue<JmsInboundMessageDispatch> stoppedMessages =
         new LinkedBlockingQueue<JmsInboundMessageDispatch>(10000);
     private final JmsSessionInfo sessionInfo;
-    private volatile ExecutorService executor;
     private final ReentrantLock sendLock = new ReentrantLock();
+    private volatile ExecutorService deliveryExecutor;
+    private volatile ExecutorService completionExcecutor;
+    private Thread deliveryThread;
+    private Thread completionThread;
 
     private final AtomicLong consumerIdGenerator = new AtomicLong();
     private final AtomicLong producerIdGenerator = new AtomicLong();
     private JmsTransactionContext transactionContext;
     private boolean sessionRecovered;
     private final AtomicReference<Exception> failureCause = new AtomicReference<Exception>();
+    private final Deque<SendCompletion> asyncSendQueue = new ConcurrentLinkedDeque<SendCompletion>();
 
     protected JmsSession(JmsConnection connection, JmsSessionId sessionId, int acknowledgementMode) throws JMSException {
         this.connection = connection;
@@ -178,6 +186,7 @@ public class JmsSession implements AutoCloseable, Session, QueueSession, TopicSe
     @Override
     public void commit() throws JMSException {
         checkClosed();
+        checkIsCompletionThread();
 
         if (!getTransacted()) {
             throw new javax.jms.IllegalStateException("Not a transacted session");
@@ -189,6 +198,7 @@ public class JmsSession implements AutoCloseable, Session, QueueSession, TopicSe
     @Override
     public void rollback() throws JMSException {
         checkClosed();
+        checkIsCompletionThread();
 
         if (!getTransacted()) {
             throw new javax.jms.IllegalStateException("Not a transacted session");
@@ -223,6 +233,9 @@ public class JmsSession implements AutoCloseable, Session, QueueSession, TopicSe
 
     @Override
     public void close() throws JMSException {
+        checkIsDeliveryThread();
+        checkIsCompletionThread();
+
         if (!closed.get()) {
             doClose();
         }
@@ -272,11 +285,22 @@ public class JmsSession implements AutoCloseable, Session, QueueSession, TopicSe
             }
 
             transactionContext.shutdown();
+
+            synchronized (sessionInfo) {
+                if (completionExcecutor != null) {
+                    completionExcecutor.shutdown();
+                    completionExcecutor = null;
+                }
+            }
         }
     }
 
     void sessionClosed(Exception cause) {
         try {
+            // TODO - This assumes we can't rely on the AmqpProvider to signal all pending
+            //        asynchronous send completions that they are failed when the session
+            //        is remotely closed.
+            getCompletionExecutor().execute(new FailOrCompleteAsyncCompletionsTask(JmsExceptionSupport.create(cause)));
             shutdown(cause);
         } catch (Throwable error) {
             LOG.trace("Ignoring exception thrown during cleanup of closed session", error);
@@ -306,6 +330,11 @@ public class JmsSession implements AutoCloseable, Session, QueueSession, TopicSe
 
         try {
             if (producer != null) {
+                // TODO - This assumes we can't rely on the AmqpProvider to signal all pending
+                //        asynchronous send completions that they are failed when the producer
+                //        is remotely closed.
+                getCompletionExecutor().execute(new FailOrCompleteAsyncCompletionsTask(
+                    producer.getProducerId(), JmsExceptionSupport.create(cause)));
                 producer.shutdown(cause);
             }
         } catch (Throwable error) {
@@ -624,17 +653,17 @@ public class JmsSession implements AutoCloseable, Session, QueueSession, TopicSe
         connection.onException(ex);
     }
 
-    protected void send(JmsMessageProducer producer, Destination dest, Message msg, int deliveryMode, int priority, long timeToLive, boolean disableMsgId, boolean disableTimestamp) throws JMSException {
+    protected void send(JmsMessageProducer producer, Destination dest, Message msg, int deliveryMode, int priority, long timeToLive, boolean disableMsgId, boolean disableTimestamp, JmsCompletionListener listener) throws JMSException {
         JmsDestination destination = JmsMessageTransformation.transformDestination(connection, dest);
 
         if (destination.isTemporary() && ((JmsTemporaryDestination) destination).isDeleted()) {
             throw new IllegalStateException("Temporary destination has been deleted");
         }
 
-        send(producer, destination, msg, deliveryMode, priority, timeToLive, disableMsgId, disableTimestamp);
+        send(producer, destination, msg, deliveryMode, priority, timeToLive, disableMsgId, disableTimestamp, listener);
     }
 
-    private void send(JmsMessageProducer producer, JmsDestination destination, Message original, int deliveryMode, int priority, long timeToLive, boolean disableMsgId, boolean disableTimestamp) throws JMSException {
+    private void send(JmsMessageProducer producer, JmsDestination destination, Message original, int deliveryMode, int priority, long timeToLive, boolean disableMsgId, boolean disableTimestamp, JmsCompletionListener listener) throws JMSException {
         sendLock.lock();
         try {
             original.setJMSDeliveryMode(deliveryMode);
@@ -707,14 +736,35 @@ public class JmsSession implements AutoCloseable, Session, QueueSession, TopicSe
             envelope.setMessage(copy);
             envelope.setProducerId(producer.getProducerId());
             envelope.setDestination(destination);
-            envelope.setSendAsync(!sync);
+            envelope.setSendAsync(listener == null ? !sync : true);
             envelope.setDispatchId(messageSequence);
+            envelope.setCompletionRequired(listener != null);
 
             if (producer.isAnonymous()) {
                 envelope.setPresettle(getPresettlePolicy().isProducerPresttled(this, destination));
             }
 
-            transactionContext.send(connection, envelope);
+            SendCompletion completion = null;
+            if (envelope.isCompletionRequired()) {
+                completion = new SendCompletion(envelope, listener);
+                asyncSendQueue.addLast(completion);
+            }
+
+            try {
+                transactionContext.send(connection, envelope);
+            } catch (JMSException jmsEx) {
+                // If the synchronous portion of the send fails the completion be
+                // notified but might depending on the circumstances of the failures,
+                // remove it from the queue and check if is is already completed.
+                if (completion != null) {
+                    asyncSendQueue.remove(completion);
+                    if (completion.hasCompleted()) {
+                        return;
+                    }
+                }
+
+                throw jmsEx;
+            }
         } finally {
             sendLock.unlock();
         }
@@ -837,9 +887,9 @@ public class JmsSession implements AutoCloseable, Session, QueueSession, TopicSe
         }
 
         synchronized (sessionInfo) {
-            if (executor != null) {
-                executor.shutdown();
-                executor = null;
+            if (deliveryExecutor != null) {
+                deliveryExecutor.shutdown();
+                deliveryExecutor = null;
             }
         }
     }
@@ -852,29 +902,62 @@ public class JmsSession implements AutoCloseable, Session, QueueSession, TopicSe
         return connection;
     }
 
-    Executor getExecutor() {
-        ExecutorService exec = executor;
-        if(exec == null) {
+    Executor getDispatcherExecutor() {
+        ExecutorService exec = deliveryExecutor;
+        if (exec == null) {
             synchronized (sessionInfo) {
-                if (executor == null) {
-                    executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
-                        @Override
-                        public Thread newThread(Runnable runner) {
-                            Thread executor = new Thread(runner);
-                            executor.setName("JmsSession ["+ sessionInfo.getId() + "] dispatcher");
-                            executor.setDaemon(true);
-                            return executor;
-                        }
-                    });
+                if (deliveryExecutor == null) {
+                    deliveryExecutor = createExecutor("delivery dispatcher");
                 }
 
-                exec = executor;
+                exec = deliveryExecutor;
+                exec.execute(new Runnable() {
+
+                    @Override
+                    public void run() {
+                        JmsSession.this.deliveryThread = Thread.currentThread();
+                    }
+                });
             }
         }
 
         return exec;
     }
 
+    Executor getCompletionExecutor() {
+        ExecutorService exec = completionExcecutor;
+        if (exec == null) {
+            synchronized (sessionInfo) {
+                if (completionExcecutor == null) {
+                    completionExcecutor = createExecutor("completion dispatcher");
+                }
+
+                exec = completionExcecutor;
+                exec.execute(new Runnable() {
+
+                    @Override
+                    public void run() {
+                        JmsSession.this.completionThread = Thread.currentThread();
+                    }
+                });
+            }
+        }
+
+        return exec;
+    }
+
+    private ExecutorService createExecutor(final String threadNameSuffix) {
+        return Executors.newSingleThreadExecutor(new ThreadFactory() {
+            @Override
+            public Thread newThread(Runnable runner) {
+                Thread executor = new Thread(runner);
+                executor.setName("JmsSession ["+ sessionInfo.getId() + "] " + threadNameSuffix);
+                executor.setDaemon(true);
+                return executor;
+            }
+        });
+    }
+
     protected JmsSessionInfo getSessionInfo() {
         return sessionInfo;
     }
@@ -925,6 +1008,18 @@ public class JmsSession implements AutoCloseable, Session, QueueSession, TopicSe
         }
     }
 
+    void checkIsDeliveryThread() throws JMSException {
+        if (Thread.currentThread().equals(deliveryThread)) {
+            throw new IllegalStateException("Illegal invocation from MessageListener callback");
+        }
+    }
+
+    void checkIsCompletionThread() throws JMSException {
+        if (Thread.currentThread().equals(completionThread)) {
+            throw new IllegalStateException("Illegal invocation from CompletionListener callback");
+        }
+    }
+
     public JmsMessageIDPolicy getMessageIDPolicy() {
         return sessionInfo.getMessageIDPolicy();
     }
@@ -945,6 +1040,36 @@ public class JmsSession implements AutoCloseable, Session, QueueSession, TopicSe
         return sessionInfo.getDeserializationPolicy();
     }
 
+    /**
+     * Sets the transaction context of the session.
+     *
+     * @param transactionContext
+     *        provides the means to control a JMS transaction.
+     */
+    public void setTransactionContext(JmsTransactionContext transactionContext) {
+        this.transactionContext = transactionContext;
+    }
+
+    /**
+     * Returns the transaction context of the session.
+     *
+     * @return transactionContext
+     *         session's transaction context.
+     */
+    public JmsTransactionContext getTransactionContext() {
+        return transactionContext;
+    }
+
+    boolean isSessionRecovered() {
+        return sessionRecovered;
+    }
+
+    void clearSessionRecovered() {
+        sessionRecovered = false;
+    }
+
+    //----- Event handlers ---------------------------------------------------//
+
     @Override
     public void onInboundMessage(JmsInboundMessageDispatch envelope) {
         if (started.get()) {
@@ -954,10 +1079,22 @@ public class JmsSession implements AutoCloseable, Session, QueueSession, TopicSe
         }
     }
 
+    protected void onCompletedMessageSend(final JmsOutboundMessageDispatch envelope) {
+        getCompletionExecutor().execute(new AsyncCompletionTask(envelope));
+    }
+
+    protected void onFailedMessageSend(final JmsOutboundMessageDispatch envelope, final Throwable cause) {
+        getCompletionExecutor().execute(new AsyncCompletionTask(envelope, cause));
+    }
+
     protected void onConnectionInterrupted() {
 
         transactionContext.onConnectionInterrupted();
 
+        // TODO - Synthesize a better exception
+        JMSException failureCause = new JMSException("Send failed due to connection loss");
+        getCompletionExecutor().execute(new FailOrCompleteAsyncCompletionsTask(failureCause));
+
         for (JmsMessageProducer producer : producers.values()) {
             producer.onConnectionInterrupted();
         }
@@ -1019,31 +1156,155 @@ public class JmsSession implements AutoCloseable, Session, QueueSession, TopicSe
         }
     }
 
-    /**
-     * Sets the transaction context of the session.
-     *
-     * @param transactionContext
-     *        provides the means to control a JMS transaction.
-     */
-    public void setTransactionContext(JmsTransactionContext transactionContext) {
-        this.transactionContext = transactionContext;
-    }
+    //----- Asynchronous Send Helpers ----------------------------------------//
 
-    /**
-     * Returns the transaction context of the session.
-     *
-     * @return transactionContext
-     *         session's transaction context.
-     */
-    public JmsTransactionContext getTransactionContext() {
-        return transactionContext;
+    private final class FailOrCompleteAsyncCompletionsTask implements Runnable {
+
+        private final JMSException failureCause;
+        private final JmsProducerId producerId;
+
+        public FailOrCompleteAsyncCompletionsTask(JMSException failureCause) {
+            this(null, failureCause);
+        }
+
+        public FailOrCompleteAsyncCompletionsTask(JmsProducerId producerId, JMSException failureCause) {
+            this.failureCause = failureCause;
+            this.producerId = producerId;
+        }
+
+        @Override
+        public void run() {
+            // For any completion that is not yet marked as complete we fail it
+            // otherwise we send the already marked completion state event.
+            Iterator<SendCompletion> pending = asyncSendQueue.iterator();
+            while (pending.hasNext()) {
+                SendCompletion completion = pending.next();
+
+                if (!completion.hasCompleted()) {
+                    if (producerId == null || producerId.equals(completion.envelope.getProducerId())) {
+                        completion.markAsFailed(failureCause);
+                    }
+                }
+
+                try {
+                    completion.signalCompletion();
+                } catch (Throwable error) {
+                    LOG.trace("Signaled completion of send: {}", completion.envelope);
+                }
+            }
+
+            asyncSendQueue.clear();
+        }
     }
 
-    boolean isSessionRecovered() {
-        return sessionRecovered;
+    private final class AsyncCompletionTask implements Runnable {
+
+        private final JmsOutboundMessageDispatch envelope;
+        private final Throwable cause;
+
+        public AsyncCompletionTask(JmsOutboundMessageDispatch envelope) {
+            this(envelope, null);
+        }
+
+        public AsyncCompletionTask(JmsOutboundMessageDispatch envelope, Throwable cause) {
+            this.envelope = envelope;
+            this.cause = cause;
+        }
+
+        @Override
+        public void run() {
+            try {
+                SendCompletion completion = asyncSendQueue.peek();
+                if (completion.getEnvelope().getDispatchId() == envelope.getDispatchId()) {
+                    try {
+                        completion = asyncSendQueue.remove();
+                        if (cause == null) {
+                            completion.markAsComplete();
+                        } else {
+                            completion.markAsFailed(JmsExceptionSupport.create(cause));
+                        }
+                        completion.signalCompletion();
+                    } catch (Throwable error) {
+                        LOG.trace("Failed while performing send completion: {}", envelope);
+                        // TODO - What now?
+                    }
+
+                    // Signal any trailing completions that have been marked complete
+                    // before this one was that they have now that the one in front has
+                    Iterator<SendCompletion> pending = asyncSendQueue.iterator();
+                    while (pending.hasNext()) {
+                        completion = pending.next();
+                        if (completion.hasCompleted()) {
+                            try {
+                                completion.signalCompletion();
+                            } catch (Throwable error) {
+                                LOG.trace("Failed while performing send completion: {}", envelope);
+                                // TODO - What now?
+                            } finally {
+                                pending.remove();
+                            }
+                        } else {
+                            break;
+                        }
+                    }
+                } else {
+                    // Not head so mark as complete and wait for the one in front to send
+                    // the notification of completion.
+                    Iterator<SendCompletion> pending = asyncSendQueue.iterator();
+                    while (pending.hasNext()) {
+                        completion = pending.next();
+                        if (completion.getEnvelope().getDispatchId() == envelope.getDispatchId()) {
+                            if (cause == null) {
+                                completion.markAsComplete();
+                            } else {
+                                completion.markAsFailed(JmsExceptionSupport.create(cause));
+                            }
+                        }
+                    }
+                }
+            } catch (Exception ex) {
+                LOG.debug("Send completion task encounted unexpected error: {}", ex.getMessage());
+                // TODO - What now
+            }
+        }
     }
 
-    void clearSessionRecovered() {
-        sessionRecovered = false;
+    private final class SendCompletion {
+
+        private final JmsOutboundMessageDispatch envelope;
+        private final JmsCompletionListener listener;
+
+        private Exception failureCause;
+        private boolean completed;
+
+        public SendCompletion(JmsOutboundMessageDispatch envelope, JmsCompletionListener listener) {
+            this.envelope = envelope;
+            this.listener = listener;
+        }
+
+        public void markAsComplete() {
+            completed = true;
+        }
+
+        public void markAsFailed(Exception cause) {
+            completed = true;
+            failureCause = cause;
+        }
+
+        public boolean hasCompleted() {
+            return completed;
+        }
+
+        public void signalCompletion() {
+            if (failureCause == null) {
+                listener.onCompletion(envelope.getMessage());
+            } else {
+                listener.onException(envelope.getMessage(), failureCause);
+            }
+        }
+
+        public JmsOutboundMessageDispatch getEnvelope() {
+            return envelope;
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsInboundMessageDispatch.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsInboundMessageDispatch.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsInboundMessageDispatch.java
index 577ac17..c038519 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsInboundMessageDispatch.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsInboundMessageDispatch.java
@@ -30,6 +30,8 @@ public class JmsInboundMessageDispatch extends JmsAbstractResourceId {
     private JmsMessage message;
     private boolean enqueueFirst;
 
+    private transient String stringView;
+
     public JmsInboundMessageDispatch(long sequence) {
         this.sequence = sequence;
     }
@@ -74,10 +76,21 @@ public class JmsInboundMessageDispatch extends JmsAbstractResourceId {
 
     @Override
     public String toString() {
-        return "JmsInboundMessageDispatch {sequence = " + sequence
-                                      + ", messageId = " + messageId
-                                      + ", consumerId = " + consumerId
-                                      + "}";
+        if (stringView == null) {
+            StringBuilder builder = new StringBuilder();
+
+            builder.append("JmsInboundMessageDispatch { sequence = ");
+            builder.append(sequence);
+            builder.append(", messageId = ");
+            builder.append(messageId);
+            builder.append(", consumerId = ");
+            builder.append(consumerId);
+            builder.append(" }");
+
+            stringView = builder.toString();
+        }
+
+        return stringView;
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsOutboundMessageDispatch.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsOutboundMessageDispatch.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsOutboundMessageDispatch.java
index 05b9089..a34768f 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsOutboundMessageDispatch.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/message/JmsOutboundMessageDispatch.java
@@ -29,8 +29,11 @@ public class JmsOutboundMessageDispatch {
     private JmsDestination destination;
     private boolean sendAsync;
     private boolean presettle;
+    private boolean completionRequired;
     private long dispatchId;
 
+    private transient String stringView;
+
     public JmsDestination getDestination() {
         return destination;
     }
@@ -39,6 +42,10 @@ public class JmsOutboundMessageDispatch {
         this.destination = destination;
     }
 
+    public Object getMessageId() {
+        return message.getFacade().getProviderMessageIdObject();
+    }
+
     public JmsMessage getMessage() {
         return message;
     }
@@ -79,15 +86,34 @@ public class JmsOutboundMessageDispatch {
         this.presettle = presettle;
     }
 
-    @Override
-    public String toString() {
-        StringBuilder value = new StringBuilder();
+    public boolean isCompletionRequired() {
+        return completionRequired;
+    }
 
-        value.append("JmsOutboundMessageDispatch {dispatchId = ");
-        value.append(getProducerId());
-        value.append("-");
-        value.append(getDispatchId());
+    public void setCompletionRequired(boolean completionRequired) {
+        this.completionRequired = completionRequired;
+    }
 
-        return value.toString();
+    @Override
+    public String toString() {
+        if (stringView == null) {
+            StringBuilder value = new StringBuilder();
+
+            value.append("JmsOutboundMessageDispatch {dispatchId = ");
+            value.append(getProducerId());
+            value.append("-");
+            value.append(getDispatchId());
+            value.append(", MessageID = ");
+            try {
+                value.append(message.getJMSMessageID());
+            } catch (Throwable e) {
+                value.append("<unknown>");
+            }
+            value.append(" }");
+
+            stringView = value.toString();
+        }
+
+        return stringView;
     }
 }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/DefaultProviderListener.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/DefaultProviderListener.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/DefaultProviderListener.java
index 22e204e..d2eb95c 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/DefaultProviderListener.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/DefaultProviderListener.java
@@ -20,6 +20,7 @@ import java.io.IOException;
 import java.net.URI;
 
 import org.apache.qpid.jms.message.JmsInboundMessageDispatch;
+import org.apache.qpid.jms.message.JmsOutboundMessageDispatch;
 import org.apache.qpid.jms.meta.JmsResource;
 
 /**
@@ -32,6 +33,14 @@ public class DefaultProviderListener implements ProviderListener {
     }
 
     @Override
+    public void onCompletedMessageSend(JmsOutboundMessageDispatch envelope) {
+    }
+
+    @Override
+    public void onFailedMessageSend(JmsOutboundMessageDispatch envelope, Throwable cause) {
+    }
+
+    @Override
     public void onConnectionInterrupted(URI remoteURI) {
     }
 

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/ProviderListener.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/ProviderListener.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/ProviderListener.java
index 5c758ed..11a5f6b 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/ProviderListener.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/ProviderListener.java
@@ -20,6 +20,7 @@ import java.io.IOException;
 import java.net.URI;
 
 import org.apache.qpid.jms.message.JmsInboundMessageDispatch;
+import org.apache.qpid.jms.message.JmsOutboundMessageDispatch;
 import org.apache.qpid.jms.meta.JmsResource;
 
 /**
@@ -36,6 +37,27 @@ public interface ProviderListener {
     void onInboundMessage(JmsInboundMessageDispatch envelope);
 
     /**
+     * Called when an outbound message dispatch that requested a completion callback
+     * has reached a state where the send can be considered successful based on the QoS
+     * level associated of the outbound message.
+     *
+     * @param envelope
+     *      the original outbound message dispatch that is now complete.
+     */
+    void onCompletedMessageSend(JmsOutboundMessageDispatch envelope);
+
+    /**
+     * Called when an outbound message dispatch that requested a completion callback
+     * has reached a state where the send can be considered failed.
+     *
+     * @param envelope
+     *      the original outbound message dispatch that should be treated as a failed send.
+     * @param cause
+     *      the exception that describes the cause of the failed send.
+     */
+    void onFailedMessageSend(JmsOutboundMessageDispatch envelope, Throwable cause);
+
+    /**
      * Called from a fault tolerant Provider instance to signal that the underlying
      * connection to the Broker has been lost.  The Provider will attempt to reconnect
      * following this event unless closed.

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/ProviderWrapper.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/ProviderWrapper.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/ProviderWrapper.java
index 8574e04..3a3d383 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/ProviderWrapper.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/ProviderWrapper.java
@@ -154,6 +154,16 @@ public class ProviderWrapper<E extends Provider> implements Provider, ProviderLi
     }
 
     @Override
+    public void onCompletedMessageSend(JmsOutboundMessageDispatch envelope) {
+        listener.onCompletedMessageSend(envelope);
+    }
+
+    @Override
+    public void onFailedMessageSend(JmsOutboundMessageDispatch envelope, Throwable cause) {
+        listener.onFailedMessageSend(envelope, cause);
+    }
+
+    @Override
     public void onConnectionInterrupted(URI remoteURI) {
         listener.onConnectionInterrupted(remoteURI);
     }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpAbstractResource.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpAbstractResource.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpAbstractResource.java
index a8599ac..634cafd 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpAbstractResource.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpAbstractResource.java
@@ -23,6 +23,7 @@ import org.apache.qpid.jms.JmsOperationTimedOutException;
 import org.apache.qpid.jms.meta.JmsConnectionInfo;
 import org.apache.qpid.jms.meta.JmsResource;
 import org.apache.qpid.jms.provider.AsyncResult;
+import org.apache.qpid.proton.engine.Delivery;
 import org.apache.qpid.proton.engine.Endpoint;
 import org.apache.qpid.proton.engine.EndpointState;
 import org.slf4j.Logger;
@@ -158,7 +159,7 @@ public abstract class AmqpAbstractResource<R extends JmsResource, E extends Endp
             closeOrDetachEndpoint();
         }
 
-        // Process the close now, so that child close operations see the correct state.
+        // Process the close before moving on to closing down child resources
         provider.pumpToProtonTransport();
 
         handleResourceClosure(provider, error);
@@ -253,7 +254,7 @@ public abstract class AmqpAbstractResource<R extends JmsResource, E extends Endp
     }
 
     @Override
-    public void processDeliveryUpdates(AmqpProvider provider) throws IOException {
+    public void processDeliveryUpdates(AmqpProvider provider, Delivery delivery) throws IOException {
         // Nothing do be done here, subclasses can override as needed.
     }
 

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpAnonymousFallbackProducer.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpAnonymousFallbackProducer.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpAnonymousFallbackProducer.java
index b44e3b3..3e07b25 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpAnonymousFallbackProducer.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpAnonymousFallbackProducer.java
@@ -69,7 +69,7 @@ public class AmqpAnonymousFallbackProducer extends AmqpProducer {
     }
 
     @Override
-    public boolean send(JmsOutboundMessageDispatch envelope, AsyncResult request) throws IOException, JMSException {
+    public void send(JmsOutboundMessageDispatch envelope, AsyncResult request) throws IOException, JMSException {
         LOG.trace("Started send chain for anonymous producer: {}", getProducerId());
 
         // Force sends marked as asynchronous to be sent synchronous so that the temporary
@@ -91,7 +91,8 @@ public class AmqpAnonymousFallbackProducer extends AmqpProducer {
 
             // We open a Fixed Producer instance with the target destination.  Once it opens
             // it will trigger the open event which will in turn trigger the send event.
-            // If caching is disabled the created producer will be closed immediately.
+            // If caching is disabled the created producer will be closed immediately after
+            // the entire send chain has finished and the delivery has been acknowledged.
             AmqpProducerBuilder builder = new AmqpProducerBuilder(session, info);
             builder.buildResource(new AnonymousSendRequest(request, builder, envelope));
 
@@ -100,9 +101,9 @@ public class AmqpAnonymousFallbackProducer extends AmqpProducer {
                 producerCache.put(envelope.getDestination(), builder.getResource());
             }
 
-            return true;
+            getParent().getProvider().pumpToProtonTransport(request);
         } else {
-            return producer.send(envelope, request);
+            producer.send(envelope, request);
         }
     }
 
@@ -135,6 +136,14 @@ public class AmqpAnonymousFallbackProducer extends AmqpProducer {
         return new JmsProducerId(producerIdKey, -1, producerIdCount++);
     }
 
+    @Override
+    public void addSendCompletionWatcher(AsyncResult watcher) {
+        throw new UnsupportedOperationException(
+            "The fallback producer parent should never have a watcher assigned.");
+    }
+
+    //----- AsyncResult objects used to complete the sends -------------------//
+
     private abstract class AnonymousRequest extends WrappedAsyncResult {
 
         protected final JmsOutboundMessageDispatch envelope;

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpConsumer.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpConsumer.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpConsumer.java
index c15fd08..89586e3 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpConsumer.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpConsumer.java
@@ -385,42 +385,35 @@ public class AmqpConsumer extends AmqpAbstractResource<JmsConsumerInfo, Receiver
     }
 
     @Override
-    public void processDeliveryUpdates(AmqpProvider provider) throws IOException {
-        Delivery incoming = null;
-        do {
-            incoming = getEndpoint().current();
-            if (incoming != null) {
-                if (incoming.isReadable() && !incoming.isPartial()) {
-                    LOG.trace("{} has incoming Message(s).", this);
-                    try {
-                        if (processDelivery(incoming)) {
-                            // We processed a message, signal completion
-                            // of a message pull request if there is one.
-                            if (pullRequest != null) {
-                                pullRequest.onSuccess();
-                                pullRequest = null;
-                            }
-                        }
-                    } catch (Exception e) {
-                        throw IOExceptionSupport.create(e);
+    public void processDeliveryUpdates(AmqpProvider provider, Delivery delivery) throws IOException {
+        if (delivery.isReadable() && !delivery.isPartial()) {
+            LOG.trace("{} has incoming Message(s).", this);
+            try {
+                if (processDelivery(delivery)) {
+                    // We processed a message, signal completion
+                    // of a message pull request if there is one.
+                    if (pullRequest != null) {
+                        pullRequest.onSuccess();
+                        pullRequest = null;
                     }
-                } else {
-                    LOG.trace("{} has a partial incoming Message(s), deferring.", this);
-                    incoming = null;
                 }
-            } else {
-                // We have exhausted the locally queued messages on this link.
-                // Check if we tried to stop and have now run out of credit.
-                if (getEndpoint().getRemoteCredit() <= 0) {
-                    if (stopRequest != null) {
-                        stopRequest.onSuccess();
-                        stopRequest = null;
-                    }
+            } catch (Exception e) {
+                throw IOExceptionSupport.create(e);
+            }
+        }
+
+        if (getEndpoint().current() == null) {
+            // We have exhausted the locally queued messages on this link.
+            // Check if we tried to stop and have now run out of credit.
+            if (getEndpoint().getRemoteCredit() <= 0) {
+                if (stopRequest != null) {
+                    stopRequest.onSuccess();
+                    stopRequest = null;
                 }
             }
-        } while (incoming != null);
+        }
 
-        super.processDeliveryUpdates(provider);
+        super.processDeliveryUpdates(provider, delivery);
     }
 
     private boolean processDelivery(Delivery incoming) throws Exception {

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpEventSink.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpEventSink.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpEventSink.java
index b3e7501..2e25ad1 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpEventSink.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpEventSink.java
@@ -18,6 +18,8 @@ package org.apache.qpid.jms.provider.amqp;
 
 import java.io.IOException;
 
+import org.apache.qpid.proton.engine.Delivery;
+
 /**
  * Interface used by classes that want to process AMQP events sent from
  * the transport layer.
@@ -60,10 +62,12 @@ public interface AmqpEventSink {
      *
      * @param provider
      *        the AmqpProvider instance for easier access to fire events.
+     * @param delivery
+     *        the Delivery that has an update to its state which needs handled.
      *
      * @throws IOException if an error occurs while processing the update.
      */
-    void processDeliveryUpdates(AmqpProvider provider) throws IOException;
+    void processDeliveryUpdates(AmqpProvider provider, Delivery delivery) throws IOException;
 
     /**
      * Called when the Proton Engine signals an Flow related event has been triggered

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpExceptionBuilder.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpExceptionBuilder.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpExceptionBuilder.java
new file mode 100644
index 0000000..2ecf245
--- /dev/null
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpExceptionBuilder.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.jms.provider.amqp;
+
+/**
+ * Used to provide a source for an exception based on some event such as
+ * operation timed out, etc.
+ */
+public interface AmqpExceptionBuilder {
+
+    /**
+     * Creates an exception appropriate to some failure condition
+     *
+     * @return a new Exception instance that describes a failure condition.
+     */
+    Exception createException();
+
+}

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpFixedProducer.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpFixedProducer.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpFixedProducer.java
index c354822..9233ce1 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpFixedProducer.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpFixedProducer.java
@@ -18,10 +18,10 @@ package org.apache.qpid.jms.provider.amqp;
 
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.LinkedHashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
 import java.util.concurrent.ScheduledFuture;
 
 import javax.jms.IllegalStateException;
@@ -62,10 +62,12 @@ public class AmqpFixedProducer extends AmqpProducer {
     private static final byte[] EMPTY_BYTE_ARRAY = new byte[] {};
 
     private final AmqpTransferTagGenerator tagGenerator = new AmqpTransferTagGenerator(true);
-    private final Set<Delivery> sent = new LinkedHashSet<Delivery>();
-    private final LinkedList<InFlightSend> blocked = new LinkedList<InFlightSend>();
+    private final Map<Object, InFlightSend> sent = new LinkedHashMap<Object, InFlightSend>();
+    private final Map<Object, InFlightSend> blocked = new LinkedHashMap<Object, InFlightSend>();
     private byte[] encodeBuffer = new byte[1024 * 8];
 
+    private AsyncResult sendCompletionWatcher;
+
     public AmqpFixedProducer(AmqpSession session, JmsProducerInfo info) {
         super(session, info);
     }
@@ -86,29 +88,24 @@ public class AmqpFixedProducer extends AmqpProducer {
     }
 
     @Override
-    public boolean send(JmsOutboundMessageDispatch envelope, AsyncResult request) throws IOException, JMSException {
+    public void send(JmsOutboundMessageDispatch envelope, AsyncResult request) throws IOException, JMSException {
         if (isClosed()) {
             request.onFailure(new IllegalStateException("The MessageProducer is closed"));
         }
 
         if (getEndpoint().getCredit() <= 0) {
             LOG.trace("Holding Message send until credit is available.");
-            // Once a message goes into a held mode we no longer can send it async, so
-            // we clear the async flag if set to avoid the sender never getting notified.
-            envelope.setSendAsync(false);
 
             InFlightSend send = new InFlightSend(envelope, request);
 
             if (getSendTimeout() > JmsConnectionInfo.INFINITE) {
-                send.requestTimeout = getParent().getProvider().scheduleRequestTimeout(
-                    send, getSendTimeout(), new JmsSendTimedOutException("Timed out waiting for credit to send Message", envelope.getMessage()));
+                send.requestTimeout = getParent().getProvider().scheduleRequestTimeout(send, getSendTimeout(), send);
             }
 
-            blocked.addLast(send);
-            return false;
+            blocked.put(envelope.getMessageId(), send);
+            getParent().getProvider().pumpToProtonTransport(request);
         } else {
             doSend(envelope, request);
-            return true;
         }
     }
 
@@ -133,35 +130,54 @@ public class AmqpFixedProducer extends AmqpProducer {
             delivery = getEndpoint().delivery(tag, 0, tag.length);
         }
 
-        delivery.setContext(request);
-
         if (session.isTransacted()) {
-            Binary amqpTxId = session.getTransactionContext().getAmqpTransactionId();
+            AmqpTransactionContext context = session.getTransactionContext();
+            Binary amqpTxId = context.getAmqpTransactionId();
             TransactionalState state = new TransactionalState();
             state.setTxnId(amqpTxId);
             delivery.disposition(state);
+            context.registerTxProducer(this);
         }
 
         AmqpJmsMessageFacade amqpMessageFacade = (AmqpJmsMessageFacade) facade;
         encodeAndSend(amqpMessageFacade.getAmqpMessage(), delivery);
 
+        AmqpProvider provider = getParent().getProvider();
+
+        InFlightSend send = null;
+        if (request instanceof InFlightSend) {
+            send = (InFlightSend) request;
+        } else {
+            send = new InFlightSend(envelope, request);
+
+            if (!presettle && getSendTimeout() != JmsConnectionInfo.INFINITE) {
+                send.requestTimeout = getParent().getProvider().scheduleRequestTimeout(send, getSendTimeout(), send);
+            }
+        }
+
         if (presettle) {
             delivery.settle();
         } else {
-            sent.add(delivery);
+            sent.put(envelope.getMessageId(), send);
             getEndpoint().advance();
         }
 
-        if (envelope.isSendAsync() || presettle) {
-            request.onSuccess();
-        } else if (getSendTimeout() != JmsConnectionInfo.INFINITE) {
-            InFlightSend send = new InFlightSend(envelope, request);
-
-            send.requestTimeout = getParent().getProvider().scheduleRequestTimeout(
-                send, getSendTimeout(), new JmsSendTimedOutException("Timed out waiting for disposition of sent Message", envelope.getMessage()));
-
-            // Update context so the incoming disposition can cancel any pending timeout
-            delivery.setContext(send);
+        send.setDelivery(delivery);
+        delivery.setContext(send);
+
+        // Put it on the wire and let it fail if the connection is broken, if it does
+        // get written then continue on to determine when we should complete it.
+        if (provider.pumpToProtonTransport(request)) {
+            // For presettled messages we can just mark as successful and we are done, but
+            // for any other message we still track it until the remote settles.  If the send
+            // was tagged as asynchronous we must mark the original request as complete but
+            // we still need to wait for the disposition before we can consider the send as
+            // having been successful.
+            if (presettle) {
+                send.onSuccess();
+            } else if (envelope.isSendAsync()) {
+                send.getOriginalRequest().onSuccess();
+            }
         }
     }
 
@@ -195,13 +211,16 @@ public class AmqpFixedProducer extends AmqpProducer {
     @Override
     public void processFlowUpdates(AmqpProvider provider) throws IOException {
         if (!blocked.isEmpty() && getEndpoint().getCredit() > 0) {
-            while (getEndpoint().getCredit() > 0 && !blocked.isEmpty()) {
+            Iterator<InFlightSend> blockedSends = blocked.values().iterator();
+            while (getEndpoint().getCredit() > 0 && blockedSends.hasNext()) {
                 LOG.trace("Dispatching previously held send");
-                InFlightSend held = blocked.pop();
+                InFlightSend held = blockedSends.next();
                 try {
-                    doSend(held.envelope, held);
+                    doSend(held.getEnvelope(), held);
                 } catch (JMSException e) {
                     throw IOExceptionSupport.create(e);
+                } finally {
+                    blockedSends.remove();
                 }
             }
         }
@@ -211,25 +230,18 @@ public class AmqpFixedProducer extends AmqpProducer {
             getEndpoint().drained();
         }
 
-        // Once the pending sends queue is drained we can propagate the close request.
-        if (blocked.isEmpty() && isAwaitingClose() && !isClosed()) {
-            super.close(closeRequest);
-        }
-
         super.processFlowUpdates(provider);
     }
 
     @Override
-    public void processDeliveryUpdates(AmqpProvider provider) throws IOException {
-        List<Delivery> toRemove = new ArrayList<Delivery>();
-
-        for (Delivery delivery : sent) {
-            DeliveryState state = delivery.getRemoteState();
-            if (state == null) {
-                continue;
-            }
+    public void processDeliveryUpdates(AmqpProvider provider, Delivery delivery) throws IOException {
+        DeliveryState state = delivery.getRemoteState();
+        if (state != null) {
 
+            InFlightSend send = (InFlightSend) delivery.getContext();
+            Exception deliveryError = null;
             Outcome outcome = null;
+
             if (state instanceof TransactionalState) {
                 LOG.trace("State of delivery is Transactional, retrieving outcome: {}", state);
                 outcome = ((TransactionalState) state).getOutcome();
@@ -240,14 +252,9 @@ public class AmqpFixedProducer extends AmqpProducer {
                 outcome = null;
             }
 
-            AsyncResult request = (AsyncResult) delivery.getContext();
-            Exception deliveryError = null;
-
             if (outcome instanceof Accepted) {
                 LOG.trace("Outcome of delivery was accepted: {}", delivery);
-                if (request != null && !request.isComplete()) {
-                    request.onSuccess();
-                }
+                send.onSuccess();
             } else if (outcome instanceof Rejected) {
                 LOG.trace("Outcome of delivery was rejected: {}", delivery);
                 ErrorCondition remoteError = ((Rejected) outcome).getError();
@@ -265,21 +272,11 @@ public class AmqpFixedProducer extends AmqpProducer {
             }
 
             if (deliveryError != null) {
-                if (request != null && !request.isComplete()) {
-                    request.onFailure(deliveryError);
-                } else {
-                    connection.getProvider().fireNonFatalProviderException(deliveryError);
-                }
+                send.onFailure(deliveryError);
             }
-
-            tagGenerator.returnTag(delivery.getTag());
-            toRemove.add(delivery);
-            delivery.settle();
         }
 
-        sent.removeAll(toRemove);
-
-        super.processDeliveryUpdates(provider);
+        super.processDeliveryUpdates(provider, delivery);
     }
 
     public AmqpSession getSession() {
@@ -312,45 +309,45 @@ public class AmqpFixedProducer extends AmqpProducer {
             error = new JMSException("Producer closed remotely before message transfer result was notified");
         }
 
-        for (Delivery delivery : sent) {
+        Collection<InFlightSend> inflightSends = new ArrayList<InFlightSend>(sent.values());
+        for (InFlightSend send : inflightSends) {
             try {
-                AsyncResult request = (AsyncResult) delivery.getContext();
-
-                if (request != null && !request.isComplete()) {
-                    request.onFailure(error);
-                }
-
-                delivery.settle();
-                tagGenerator.returnTag(delivery.getTag());
+                send.onFailure(error);
             } catch (Exception e) {
-                LOG.debug("Caught exception when failing pending send during remote producer closure: {}", delivery, e);
+                LOG.debug("Caught exception when failing pending send during remote producer closure: {}", send, e);
             }
         }
 
-        sent.clear();
-
-        for (InFlightSend blockedSend : blocked) {
+        Collection<InFlightSend> blockedSends = new ArrayList<InFlightSend>(blocked.values());
+        for (InFlightSend send : blockedSends) {
             try {
-                AsyncResult request = blockedSend.request;
-                if (request != null && !request.isComplete()) {
-                    request.onFailure(error);
-                }
+                send.onFailure(error);
             } catch (Exception e) {
-                LOG.debug("Caught exception when failing blocked send during remote producer closure: {}", blockedSend, e);
+                LOG.debug("Caught exception when failing blocked send during remote producer closure: {}", send, e);
             }
         }
+    }
 
-        blocked.clear();
+    @Override
+    public void addSendCompletionWatcher(AsyncResult watcher) {
+        // If none pending signal done already.
+        // TODO - If we don't include blocked sends then update this.
+        if (blocked.isEmpty() && sent.isEmpty()) {
+            watcher.onSuccess();
+        } else {
+            this.sendCompletionWatcher = watcher;
+        }
     }
 
     //----- Class used to manage held sends ----------------------------------//
 
-    private class InFlightSend implements AsyncResult {
+    private class InFlightSend implements AsyncResult, AmqpExceptionBuilder {
 
-        public final JmsOutboundMessageDispatch envelope;
-        public final AsyncResult request;
+        private final JmsOutboundMessageDispatch envelope;
+        private final AsyncResult request;
 
-        public ScheduledFuture<?> requestTimeout;
+        private Delivery delivery;
+        private ScheduledFuture<?> requestTimeout;
 
         public InFlightSend(JmsOutboundMessageDispatch envelope, AsyncResult request) {
             this.envelope = envelope;
@@ -359,31 +356,95 @@ public class AmqpFixedProducer extends AmqpProducer {
 
         @Override
         public void onFailure(Throwable cause) {
-            if (requestTimeout != null) {
-                requestTimeout.cancel(false);
-                requestTimeout = null;
-            }
-
-            blocked.remove(this);
+            handleSendCompletion(false);
 
-            request.onFailure(cause);
+            if (request.isComplete()) {
+                // Asynchronous sends can still be awaiting a completion in which case we
+                // send to them otherwise send to the listener to be reported.
+                if (envelope.isCompletionRequired()) {
+                    getParent().getProvider().getProviderListener().onFailedMessageSend(envelope, cause);
+                } else {
+                    getParent().getProvider().fireNonFatalProviderException(IOExceptionSupport.create(cause));
+                }
+            } else {
+                request.onFailure(cause);
+            }
         }
 
         @Override
         public void onSuccess() {
-            if (requestTimeout != null) {
-                requestTimeout.cancel(false);
-                requestTimeout = null;
+            handleSendCompletion(true);
+
+            if (!request.isComplete()) {
+                request.onSuccess();
             }
 
-            blocked.remove(this);
+            if (envelope.isCompletionRequired()) {
+                getParent().getProvider().getProviderListener().onCompletedMessageSend(envelope);
+            }
+        }
 
-            request.onSuccess();
+        public void setRequestTimeout(ScheduledFuture<?> requestTimeout) {
+            if (this.requestTimeout != null) {
+                this.requestTimeout.cancel(false);
+            }
+
+            this.requestTimeout = requestTimeout;
+        }
+
+        public JmsOutboundMessageDispatch getEnvelope() {
+            return envelope;
+        }
+
+        public AsyncResult getOriginalRequest() {
+            return request;
+        }
+
+        public void setDelivery(Delivery delivery) {
+            this.delivery = delivery;
+        }
+
+        public Delivery getDelivery() {
+            return delivery;
         }
 
         @Override
         public boolean isComplete() {
             return request.isComplete();
         }
+
+        private void handleSendCompletion(boolean successful) {
+            setRequestTimeout(null);
+
+            if (getDelivery() != null) {
+                sent.remove(envelope.getMessageId());
+                delivery.settle();
+                tagGenerator.returnTag(delivery.getTag());
+            } else {
+                blocked.remove(envelope.getMessageId());
+            }
+
+            // TODO - Should this take blocked sends into consideration.
+            // Signal the watcher that all pending sends have completed if one is registered
+            // and both the in-flight sends and blocked sends have completed.
+            if (sendCompletionWatcher != null && sent.isEmpty() && blocked.isEmpty()) {
+                sendCompletionWatcher.onSuccess();
+            }
+
+            // Once the pending sends queue is drained and all in-flight sends have been
+            // settled we can propagate the close request.
+            if (isAwaitingClose() && !isClosed() && blocked.isEmpty() && sent.isEmpty()) {
+                AmqpFixedProducer.super.close(closeRequest);
+            }
+        }
+
+        @Override
+        public Exception createException() {
+            if (delivery == null) {
+                return new JmsSendTimedOutException("Timed out waiting for credit to send Message", envelope.getMessage());
+            } else {
+                return new JmsSendTimedOutException("Timed out waiting for disposition of sent Message", envelope.getMessage());
+            }
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpProducer.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpProducer.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpProducer.java
index 74fe457..3ace6d2 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpProducer.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpProducer.java
@@ -54,13 +54,10 @@ public abstract class AmqpProducer extends AmqpAbstractResource<JmsProducerInfo,
      * @param request
      *        The AsyncRequest that will be notified on send success or failure.
      *
-     * @return true if the producer had credit to send or false if there was no available
-     *         credit and the send needed to be deferred.
-     *
      * @throws IOException if an error occurs sending the message
      * @throws JMSException if an error occurs while preparing the message for send.
      */
-    public abstract boolean send(JmsOutboundMessageDispatch envelope, AsyncResult request) throws IOException, JMSException;
+    public abstract void send(JmsOutboundMessageDispatch envelope, AsyncResult request) throws IOException, JMSException;
 
     /**
      * @return true if this is an anonymous producer or false if fixed to a given destination.
@@ -92,4 +89,14 @@ public abstract class AmqpProducer extends AmqpAbstractResource<JmsProducerInfo,
     public void setPresettle(boolean presettle) {
         this.presettle = presettle;
     }
+
+    /**
+     * Allows a completion request to be added to this producer that will be notified
+     * once all outstanding sends have completed.
+     *
+     * @param watcher
+     *      The AsyncResult that will be signaled once this producer has no pending sends.
+     */
+    public abstract void addSendCompletionWatcher(AsyncResult watcher);
+
 }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpProvider.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpProvider.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpProvider.java
index 3bfe099..ca8a0e1 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpProvider.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpProvider.java
@@ -59,6 +59,7 @@ import org.apache.qpid.jms.transports.TransportListener;
 import org.apache.qpid.jms.util.IOExceptionSupport;
 import org.apache.qpid.proton.engine.Collector;
 import org.apache.qpid.proton.engine.Connection;
+import org.apache.qpid.proton.engine.Delivery;
 import org.apache.qpid.proton.engine.EndpointState;
 import org.apache.qpid.proton.engine.Event;
 import org.apache.qpid.proton.engine.Event.Type;
@@ -477,11 +478,7 @@ public class AmqpProvider implements Provider, TransportListener , AmqpResourceP
                         producer = session.getProducer(producerId);
                     }
 
-                    boolean couldSend = producer.send(envelope, request);
-                    pumpToProtonTransport(request);
-                    if (couldSend && envelope.isSendAsync()) {
-                        request.onSuccess();
-                    }
+                    producer.send(envelope, request);
                 } catch (Throwable t) {
                     request.onFailure(t);
                 }
@@ -816,7 +813,7 @@ public class AmqpProvider implements Provider, TransportListener , AmqpResourceP
                     case DELIVERY:
                         amqpEventSink = (AmqpEventSink) protonEvent.getLink().getContext();
                         if (amqpEventSink != null) {
-                            amqpEventSink.processDeliveryUpdates(this);
+                            amqpEventSink.processDeliveryUpdates(this, (Delivery) protonEvent.getContext());
                         }
                         break;
                     default:
@@ -1175,6 +1172,36 @@ public class AmqpProvider implements Provider, TransportListener , AmqpResourceP
         return null;
     }
 
+    /**
+     * Allows a resource to request that its parent resource schedule a future
+     * cancellation of a request and return it a {@link Future} instance that
+     * can be used to cancel the scheduled automatic failure of the request.
+     *
+     * @param request
+     *      The request that should be marked as failed based on configuration.
+     * @param timeout
+     *      The time to wait before marking the request as failed.
+     * @param builder
+     *      An AmqpExceptionBuilder to use when creating a timed out exception.
+     *
+     * @return a {@link ScheduledFuture} that can be stored by the caller.
+     */
+    public ScheduledFuture<?> scheduleRequestTimeout(final AsyncResult request, long timeout, final AmqpExceptionBuilder builder) {
+        if (timeout != JmsConnectionInfo.INFINITE) {
+            return serializer.schedule(new Runnable() {
+
+                @Override
+                public void run() {
+                    request.onFailure(builder.createException());
+                    pumpToProtonTransport();
+                }
+
+            }, timeout, TimeUnit.MILLISECONDS);
+        }
+
+        return null;
+    }
+
     //----- Internal implementation ------------------------------------------//
 
     private void checkClosed() throws ProviderClosedException {

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpTransactionContext.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpTransactionContext.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpTransactionContext.java
index 577c128..2d08bc7 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpTransactionContext.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpTransactionContext.java
@@ -44,6 +44,7 @@ public class AmqpTransactionContext implements AmqpResourceParent {
 
     private final AmqpSession session;
     private final Set<AmqpConsumer> txConsumers = new LinkedHashSet<AmqpConsumer>();
+    private final Set<AmqpProducer> txProducers = new LinkedHashSet<AmqpProducer>();
 
     private JmsTransactionId current;
     private AmqpTransactionCoordinator coordinator;
@@ -85,7 +86,6 @@ public class AmqpTransactionContext implements AmqpResourceParent {
             }
         };
 
-
         if (coordinator == null || coordinator.isClosed()) {
             AmqpTransactionCoordinatorBuilder builder =
                 new AmqpTransactionCoordinatorBuilder(this, session.getResourceInfo());
@@ -115,7 +115,7 @@ public class AmqpTransactionContext implements AmqpResourceParent {
         }
     }
 
-    public void commit(JmsTransactionInfo transactionInfo, final AsyncResult request) throws Exception {
+    public void commit(final JmsTransactionInfo transactionInfo, final AsyncResult request) throws Exception {
         if (!transactionInfo.getId().equals(current)) {
             if (!transactionInfo.isInDoubt() && current == null) {
                 throw new IllegalStateException("Commit called with no active Transaction.");
@@ -128,29 +128,10 @@ public class AmqpTransactionContext implements AmqpResourceParent {
 
         preCommit();
 
-        LOG.trace("TX Context[{}] committing current TX[[]]", this, current);
-        coordinator.discharge(current, new AsyncResult() {
-
-            @Override
-            public void onSuccess() {
-                current = null;
-                postCommit();
-                request.onSuccess();
-            }
-
-            @Override
-            public void onFailure(Throwable result) {
-                current = null;
-                postCommit();
-                request.onFailure(result);
-            }
-
-            @Override
-            public boolean isComplete() {
-                return current == null;
-            }
+        DischargeCompletion dischargeResult = new DischargeCompletion(request, true);
 
-        }, true);
+        LOG.trace("TX Context[{}] committing current TX[[]]", this, current);
+        coordinator.discharge(current, dischargeResult, true);
     }
 
     public void rollback(JmsTransactionInfo transactionInfo, final AsyncResult request) throws Exception {
@@ -167,29 +148,17 @@ public class AmqpTransactionContext implements AmqpResourceParent {
 
         preRollback();
 
-        LOG.trace("TX Context[{}] rolling back current TX[[]]", this, current);
-        coordinator.discharge(current, new AsyncResult() {
-
-            @Override
-            public void onSuccess() {
-                current = null;
-                postRollback();
-                request.onSuccess();
-            }
-
-            @Override
-            public void onFailure(Throwable result) {
-                current = null;
-                postRollback();
-                request.onFailure(result);
-            }
+        DischargeCompletion dischargeResult = new DischargeCompletion(request, false);
 
-            @Override
-            public boolean isComplete() {
-                return current == null;
+        if (txProducers.isEmpty()) {
+            LOG.trace("TX Context[{}] rolling back current TX[[]]", this, current);
+            coordinator.discharge(current, dischargeResult, false);
+        } else {
+            SendCompletion producersSendCompletion = new SendCompletion(transactionInfo, dischargeResult, txProducers.size(), false);
+            for (AmqpProducer producer : txProducers) {
+                producer.addSendCompletionWatcher(producersSendCompletion);
             }
-
-        }, false);
+        }
     }
 
     //----- Context utility methods ------------------------------------------//
@@ -198,6 +167,10 @@ public class AmqpTransactionContext implements AmqpResourceParent {
         txConsumers.add(consumer);
     }
 
+    public void registerTxProducer(AmqpProducer producer) {
+        txProducers.add(producer);
+    }
+
     public AmqpSession getSession() {
         return session;
     }
@@ -243,6 +216,7 @@ public class AmqpTransactionContext implements AmqpResourceParent {
         }
 
         txConsumers.clear();
+        txProducers.clear();
     }
 
     private void postRollback() {
@@ -251,6 +225,7 @@ public class AmqpTransactionContext implements AmqpResourceParent {
         }
 
         txConsumers.clear();
+        txProducers.clear();
     }
 
     //----- Resource Parent implementation -----------------------------------//
@@ -273,4 +248,94 @@ public class AmqpTransactionContext implements AmqpResourceParent {
     public AmqpProvider getProvider() {
         return session.getProvider();
     }
+
+    //----- Completion for Commit or Rollback operation ----------------------//
+
+    private class DischargeCompletion implements AsyncResult {
+
+        private final AsyncResult request;
+        private final boolean commit;
+
+        public DischargeCompletion(AsyncResult request, boolean commit) {
+            this.request = request;
+            this.commit = commit;
+        }
+
+        @Override
+        public void onFailure(Throwable result) {
+            cleanup();
+            request.onFailure(result);
+        }
+
+        @Override
+        public void onSuccess() {
+            cleanup();
+            request.onSuccess();
+        }
+
+        @Override
+        public boolean isComplete() {
+            return request.isComplete();
+        }
+
+        private void cleanup() {
+            current = null;
+            if (commit) {
+                postCommit();
+            } else {
+                postRollback();
+            }
+        }
+    }
+
+    //----- Completion result for Producers ----------------------------------//
+
+    @SuppressWarnings("unused")
+    private class SendCompletion implements AsyncResult {
+
+        private int pendingCompletions;
+
+        private final JmsTransactionInfo info;
+        private final DischargeCompletion request;
+
+        private boolean commit;
+
+        public SendCompletion(JmsTransactionInfo info, DischargeCompletion request, int pendingCompletions, boolean commit) {
+            this.info = info;
+            this.request = request;
+            this.pendingCompletions = pendingCompletions;
+            this.commit = commit;
+        }
+
+        @Override
+        public void onFailure(Throwable result) {
+            if (--pendingCompletions == 0) {
+                try {
+                    LOG.trace("TX Context[{}] rolling back current TX[[]]", this, current);
+                    coordinator.discharge(current, request, false);
+                } catch (Throwable error) {
+                    request.onFailure(error);
+                }
+            } else {
+                commit = false;
+            }
+        }
+
+        @Override
+        public void onSuccess() {
+            if (--pendingCompletions == 0) {
+                try {
+                    LOG.trace("TX Context[{}] {} current TX[[]]", this, commit ? "committing" : "rolling back" ,current);
+                    coordinator.discharge(current, request, commit);
+                } catch (Throwable error) {
+                    request.onFailure(error);
+                }
+            }
+        }
+
+        @Override
+        public boolean isComplete() {
+            return request.isComplete();
+        }
+    }
 }


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


[2/7] qpid-jms git commit: QPIDJMS-207 Adds support for Asynchronous JMS 2.0 sends.

Posted by ta...@apache.org.
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpTransactionCoordinator.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpTransactionCoordinator.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpTransactionCoordinator.java
index 2fa2644..d18e95b 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpTransactionCoordinator.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpTransactionCoordinator.java
@@ -67,7 +67,7 @@ public class AmqpTransactionCoordinator extends AmqpAbstractResource<JmsSessionI
     }
 
     @Override
-    public void processDeliveryUpdates(AmqpProvider provider) throws IOException {
+    public void processDeliveryUpdates(AmqpProvider provider, Delivery delivery) throws IOException {
         try {
             if (pendingDelivery != null && pendingDelivery.remotelySettled()) {
                 DeliveryState state = pendingDelivery.getRemoteState();
@@ -105,7 +105,7 @@ public class AmqpTransactionCoordinator extends AmqpAbstractResource<JmsSessionI
                 }
             }
 
-            super.processDeliveryUpdates(provider);
+            super.processDeliveryUpdates(provider, delivery);
         } catch (Exception e) {
             throw IOExceptionSupport.create(e);
         }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/builders/AmqpResourceBuilder.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/builders/AmqpResourceBuilder.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/builders/AmqpResourceBuilder.java
index 229e61f..5912f7f 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/builders/AmqpResourceBuilder.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/builders/AmqpResourceBuilder.java
@@ -24,10 +24,12 @@ import org.apache.qpid.jms.meta.JmsConnectionInfo;
 import org.apache.qpid.jms.meta.JmsResource;
 import org.apache.qpid.jms.provider.AsyncResult;
 import org.apache.qpid.jms.provider.amqp.AmqpEventSink;
+import org.apache.qpid.jms.provider.amqp.AmqpExceptionBuilder;
 import org.apache.qpid.jms.provider.amqp.AmqpProvider;
 import org.apache.qpid.jms.provider.amqp.AmqpResource;
 import org.apache.qpid.jms.provider.amqp.AmqpResourceParent;
 import org.apache.qpid.jms.provider.amqp.AmqpSupport;
+import org.apache.qpid.proton.engine.Delivery;
 import org.apache.qpid.proton.engine.Endpoint;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -40,7 +42,7 @@ import org.slf4j.LoggerFactory;
  * @param <INFO> The Type of JmsResource used to describe the target resource.
  * @param <ENDPOINT> The AMQP Endpoint that the target resource encapsulates.
  */
-public abstract class AmqpResourceBuilder<TARGET extends AmqpResource, PARENT extends AmqpResourceParent, INFO extends JmsResource, ENDPOINT extends Endpoint> implements AmqpEventSink {
+public abstract class AmqpResourceBuilder<TARGET extends AmqpResource, PARENT extends AmqpResourceParent, INFO extends JmsResource, ENDPOINT extends Endpoint> implements AmqpEventSink, AmqpExceptionBuilder {
 
     private static final Logger LOG = LoggerFactory.getLogger(AmqpResourceBuilder.class);
 
@@ -97,7 +99,7 @@ public abstract class AmqpResourceBuilder<TARGET extends AmqpResource, PARENT ex
                     return request.isComplete();
                 }
 
-            }, getRequestTimeout(), new JmsOperationTimedOutException("Request to open resource " + getResource() + " timed out"));
+            }, getRequestTimeout(), this);
         }
     }
 
@@ -119,7 +121,7 @@ public abstract class AmqpResourceBuilder<TARGET extends AmqpResource, PARENT ex
     }
 
     @Override
-    public void processDeliveryUpdates(AmqpProvider provider) throws IOException {
+    public void processDeliveryUpdates(AmqpProvider provider, Delivery delivery) throws IOException {
         // No implementation needed here for this event.
     }
 
@@ -185,6 +187,11 @@ public abstract class AmqpResourceBuilder<TARGET extends AmqpResource, PARENT ex
         getRequest().onFailure(openError);
     }
 
+    @Override
+    public Exception createException() {
+        return new JmsOperationTimedOutException("Request to open resource " + getResource() + " timed out");
+    }
+
     //----- Implementation methods used to customize the build process -------//
 
     /**

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/failover/FailoverProvider.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/failover/FailoverProvider.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/failover/FailoverProvider.java
index e6e36df..2490b19 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/failover/FailoverProvider.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/failover/FailoverProvider.java
@@ -801,6 +801,24 @@ public class FailoverProvider extends DefaultProviderListener implements Provide
     }
 
     @Override
+    public void onCompletedMessageSend(final JmsOutboundMessageDispatch envelope) {
+        if (closingConnection.get() || closed.get() || failed.get()) {
+            return;
+        }
+
+        listener.onCompletedMessageSend(envelope);
+    }
+
+    @Override
+    public void onFailedMessageSend(final JmsOutboundMessageDispatch envelope, Throwable cause) {
+        if (closingConnection.get() || closed.get() || failed.get()) {
+            return;
+        }
+
+        listener.onFailedMessageSend(envelope, cause);
+    }
+
+    @Override
     public void onConnectionFailure(final IOException ex) {
         if (closingConnection.get() || closed.get() || failed.get()) {
             return;

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ConsumerIntegrationTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ConsumerIntegrationTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ConsumerIntegrationTest.java
index c8914ae..a8fcf2d 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ConsumerIntegrationTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ConsumerIntegrationTest.java
@@ -29,6 +29,7 @@ import java.io.IOException;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
 
 import javax.jms.Connection;
 import javax.jms.IllegalStateException;
@@ -932,4 +933,112 @@ public class ConsumerIntegrationTest extends QpidJmsTestCase {
             testPeer.waitForAllHandlersToComplete(3000);
         }
     }
+
+    @Test(timeout=20000)
+    public void testMessageListenerCallsConnectionCloseThrowsIllegalStateException() throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        final AtomicReference<Exception> asyncError = new AtomicReference<Exception>(null);
+
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            final Connection connection = testFixture.establishConnecton(testPeer);
+            connection.start();
+
+            testPeer.expectBegin();
+
+            Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+            Queue destination = session.createQueue(getTestName());
+            connection.start();
+
+            testPeer.expectReceiverAttach();
+            testPeer.expectLinkFlowRespondWithTransfer(null, null, null, null, new AmqpValueDescribedType("content"), 1);
+
+            MessageConsumer consumer = session.createConsumer(destination);
+
+            testPeer.expectDisposition(true, new AcceptedMatcher());
+
+            consumer.setMessageListener(new MessageListener() {
+                @Override
+                public void onMessage(Message m) {
+                    try {
+                        LOG.debug("Async consumer got Message: {}", m);
+                        connection.close();
+                    } catch (Exception ex) {
+                        asyncError.set(ex);
+                    }
+
+                    latch.countDown();
+                }
+            });
+
+            boolean await = latch.await(3000, TimeUnit.MILLISECONDS);
+            assertTrue("Messages not received within given timeout. Count remaining: " + latch.getCount(), await);
+
+            assertNotNull(asyncError.get());
+            assertTrue(asyncError.get() instanceof IllegalStateException);
+
+            testPeer.waitForAllHandlersToComplete(2000);
+
+            testPeer.expectDetach(true, true, true);
+            consumer.close();
+
+            testPeer.expectClose();
+            connection.close();
+
+            testPeer.waitForAllHandlersToComplete(2000);
+        }
+    }
+
+    @Test(timeout=20000)
+    public void testMessageListenerCallsSessionCloseThrowsIllegalStateException() throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        final AtomicReference<Exception> asyncError = new AtomicReference<Exception>(null);
+
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            Connection connection = testFixture.establishConnecton(testPeer);
+            connection.start();
+
+            testPeer.expectBegin();
+
+            final Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+            Queue destination = session.createQueue(getTestName());
+            connection.start();
+
+            testPeer.expectReceiverAttach();
+            testPeer.expectLinkFlowRespondWithTransfer(null, null, null, null, new AmqpValueDescribedType("content"), 1);
+
+            MessageConsumer consumer = session.createConsumer(destination);
+
+            testPeer.expectDisposition(true, new AcceptedMatcher());
+
+            consumer.setMessageListener(new MessageListener() {
+                @Override
+                public void onMessage(Message m) {
+                    try {
+                        LOG.debug("Async consumer got Message: {}", m);
+                        session.close();
+                    } catch (Exception ex) {
+                        asyncError.set(ex);
+                    }
+
+                    latch.countDown();
+                }
+            });
+
+            boolean await = latch.await(3000, TimeUnit.MILLISECONDS);
+            assertTrue("Messages not received within given timeout. Count remaining: " + latch.getCount(), await);
+
+            assertNotNull(asyncError.get());
+            assertTrue(asyncError.get() instanceof IllegalStateException);
+
+            testPeer.waitForAllHandlersToComplete(2000);
+
+            testPeer.expectDetach(true, true, true);
+            consumer.close();
+
+            testPeer.expectClose();
+            connection.close();
+
+            testPeer.waitForAllHandlersToComplete(2000);
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/PresettledProducerIntegrationTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/PresettledProducerIntegrationTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/PresettledProducerIntegrationTest.java
index 5485857..2ed9f41 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/PresettledProducerIntegrationTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/PresettledProducerIntegrationTest.java
@@ -20,8 +20,15 @@ import static org.apache.qpid.jms.provider.amqp.AmqpSupport.ANONYMOUS_RELAY;
 import static org.hamcrest.Matchers.arrayContaining;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 import javax.jms.Connection;
 import javax.jms.Destination;
 import javax.jms.Message;
@@ -30,8 +37,11 @@ import javax.jms.Queue;
 import javax.jms.Session;
 import javax.jms.TemporaryQueue;
 import javax.jms.TemporaryTopic;
+import javax.jms.TextMessage;
 import javax.jms.Topic;
 
+import org.apache.qpid.jms.JmsCompletionListener;
+import org.apache.qpid.jms.JmsMessageProducer;
 import org.apache.qpid.jms.test.QpidJmsTestCase;
 import org.apache.qpid.jms.test.testpeer.ListDescribedType;
 import org.apache.qpid.jms.test.testpeer.TestAmqpPeer;
@@ -47,12 +57,16 @@ import org.apache.qpid.proton.amqp.Symbol;
 import org.apache.qpid.proton.amqp.transaction.TxnCapability;
 import org.hamcrest.Matcher;
 import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Test MessageProducers created using various configuration of the presettle options
  */
 public class PresettledProducerIntegrationTest extends QpidJmsTestCase {
 
+    private static final Logger LOG = LoggerFactory.getLogger(PresettledProducerIntegrationTest.class);
+
     private final IntegrationTestFixture testFixture = new IntegrationTestFixture();
 
     private final Symbol[] serverCapabilities = new Symbol[] { ANONYMOUS_RELAY };
@@ -419,4 +433,221 @@ public class PresettledProducerIntegrationTest extends QpidJmsTestCase {
             testPeer.waitForAllHandlersToComplete(1000);
         }
     }
+
+    //----- Test the jms.presettleAll with asynchronous completion -----------//
+
+    @Test(timeout = 20000)
+    public void testAsyncCompletionPresettleAllSendToTopic() throws Exception {
+        String presettleConfig = "?jms.presettlePolicy.presettleAll=true";
+        doTestAsyncCompletionProducerWithPresettleOptions(presettleConfig, false, false, true, true, Topic.class);
+    }
+
+    @Test(timeout = 20000)
+    public void testAsyncCompletionPresettleAllSendToQueue() throws Exception {
+        String presettleConfig = "?jms.presettlePolicy.presettleAll=true";
+        doTestAsyncCompletionProducerWithPresettleOptions(presettleConfig, false, false, true, true, Queue.class);
+    }
+
+    @Test(timeout = 20000)
+    public void testsyncCompletionPresettleAllAnonymousSendToTopic() throws Exception {
+        String presettleConfig = "?jms.presettlePolicy.presettleAll=true";
+        doTestAsyncCompletionProducerWithPresettleOptions(presettleConfig, false, true, true, true, Topic.class);
+    }
+
+    @Test(timeout = 20000)
+    public void testsyncCompletionPresettleAllAnonymousSendToQueue() throws Exception {
+        String presettleConfig = "?jms.presettlePolicy.presettleAll=true";
+        doTestAsyncCompletionProducerWithPresettleOptions(presettleConfig, false, true, true, true, Queue.class);
+    }
+
+    //----- Test the jms.presettleProducers with asynchronous completion -----//
+
+    @Test(timeout = 20000)
+    public void testAsyncCompletionPresettleProducersTopic() throws Exception {
+        String presettleConfig = "?jms.presettlePolicy.presettleProducers=true";
+        doTestAsyncCompletionProducerWithPresettleOptions(presettleConfig, false, false, true, true, Topic.class);
+    }
+
+    @Test(timeout = 20000)
+    public void testAsyncCompletionPresettleProducersQueue() throws Exception {
+        String presettleConfig = "?jms.presettlePolicy.presettleProducers=true";
+        doTestAsyncCompletionProducerWithPresettleOptions(presettleConfig, false, false, true, true, Queue.class);
+    }
+
+    @Test(timeout = 20000)
+    public void testAsyncCompletionPresettleProducersAnonymousTopic() throws Exception {
+        String presettleConfig = "?jms.presettlePolicy.presettleProducers=true";
+        doTestAsyncCompletionProducerWithPresettleOptions(presettleConfig, false, true, true, true, Topic.class);
+    }
+
+    @Test(timeout = 20000)
+    public void testAsyncCompletionPresettleProducersAnonymousQueue() throws Exception {
+        String presettleConfig = "?jms.presettlePolicy.presettleProducers=true";
+        doTestAsyncCompletionProducerWithPresettleOptions(presettleConfig, false, true, true, true, Queue.class);
+    }
+
+    //----- Asynchronous Completion test method implementation ---------------//
+
+    private void doTestAsyncCompletionProducerWithPresettleOptions(String uriOptions, boolean transacted, boolean anonymous, boolean senderSettled, boolean transferSettled, Class<? extends Destination> destType) throws Exception {
+        doTestAsyncCompletionProducerWithPresettleOptions(uriOptions, transacted, anonymous, true, senderSettled, transferSettled, destType);
+    }
+
+    private void doTestAsyncCompletionProducerWithPresettleOptions(String uriOptions, boolean transacted, boolean anonymous, boolean relaySupported, boolean senderSettled, boolean transferSettled, Class<? extends Destination> destType) throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            Connection connection = testFixture.establishConnecton(testPeer, uriOptions, relaySupported ? serverCapabilities : null, null);
+            testPeer.expectBegin();
+
+            Session session = null;
+            Binary txnId = null;
+
+            if (transacted) {
+                // Expect the session, with an immediate link to the transaction coordinator
+                // using a target with the expected capabilities only.
+                CoordinatorMatcher txCoordinatorMatcher = new CoordinatorMatcher();
+                txCoordinatorMatcher.withCapabilities(arrayContaining(TxnCapability.LOCAL_TXN));
+                testPeer.expectSenderAttach(txCoordinatorMatcher, false, false);
+
+                // First expect an unsettled 'declare' transfer to the txn coordinator, and
+                // reply with a declared disposition state containing the txnId.
+                txnId = new Binary(new byte[]{ (byte) 1, (byte) 2, (byte) 3, (byte) 4});
+                testPeer.expectDeclare(txnId);
+
+                session = connection.createSession(true, Session.SESSION_TRANSACTED);
+            } else {
+                session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+            }
+
+            Destination destination = null;
+            if (destType == Queue.class) {
+                destination = session.createQueue("MyQueue");
+            } else if (destType == Topic.class) {
+                destination = session.createTopic("MyTopis");
+            } else if (destType == TemporaryQueue.class) {
+                String dynamicAddress = "myTempQueueAddress";
+                testPeer.expectTempQueueCreationAttach(dynamicAddress);
+                destination = session.createTemporaryQueue();
+            } else if (destType == TemporaryTopic.class) {
+                String dynamicAddress = "myTempTopicAddress";
+                testPeer.expectTempTopicCreationAttach(dynamicAddress);
+                destination = session.createTemporaryTopic();
+            } else {
+                fail("unexpected type");
+            }
+
+            if (senderSettled) {
+                testPeer.expectSettledSenderAttach();
+            } else {
+                testPeer.expectSenderAttach();
+            }
+
+            TestJmsCompletionListener listener = new TestJmsCompletionListener();
+            // TODO Can change to plain MessageProducer when JMS 2.0 API dependency is added.
+            JmsMessageProducer producer = null;
+            if (anonymous) {
+                producer = (JmsMessageProducer) session.createProducer(null);
+            } else {
+                producer = (JmsMessageProducer) session.createProducer(destination);
+            }
+
+            // Create and transfer a new message
+            MessageHeaderSectionMatcher headersMatcher = new MessageHeaderSectionMatcher(true);
+            headersMatcher.withDurable(equalTo(true));
+            MessageAnnotationsSectionMatcher msgAnnotationsMatcher = new MessageAnnotationsSectionMatcher(true);
+            TransferPayloadCompositeMatcher messageMatcher = new TransferPayloadCompositeMatcher();
+            messageMatcher.setHeadersMatcher(headersMatcher);
+            messageMatcher.setMessageAnnotationsMatcher(msgAnnotationsMatcher);
+
+            Matcher<?> stateMatcher = nullValue();
+            if (transacted) {
+                stateMatcher = new TransactionalStateMatcher();
+                ((TransactionalStateMatcher) stateMatcher).withTxnId(equalTo(txnId));
+                ((TransactionalStateMatcher) stateMatcher).withOutcome(nullValue());
+            }
+
+            ListDescribedType responseState = new Accepted();
+            if (transacted) {
+                TransactionalState txState = new TransactionalState();
+                txState.setTxnId(txnId);
+                txState.setOutcome(new Accepted());
+            }
+
+            if (transferSettled) {
+                testPeer.expectTransfer(messageMatcher, stateMatcher, true, false, responseState, false);
+            } else {
+                testPeer.expectTransfer(messageMatcher, stateMatcher, false, true, responseState, true);
+            }
+
+            if (anonymous && !relaySupported) {
+                testPeer.expectDetach(true, true, true);
+            }
+
+            Message message = session.createTextMessage();
+
+            if (anonymous) {
+                producer.send(destination, message, listener);
+            } else {
+                producer.send(message, listener);
+            }
+
+            if (transacted) {
+                testPeer.expectDischarge(txnId, true);
+            }
+
+            testPeer.expectClose();
+
+            assertTrue("Did not get async callback", listener.awaitCompletion(2000, TimeUnit.SECONDS));
+            assertNull(listener.exception);
+            assertNotNull(listener.message);
+            assertTrue(listener.message instanceof TextMessage);
+            assertEquals(1, listener.successCount);
+            assertEquals(0, listener.errorCount);
+
+            connection.close();
+
+            testPeer.waitForAllHandlersToComplete(1000);
+        }
+    }
+
+    private class TestJmsCompletionListener implements JmsCompletionListener {
+
+        private final CountDownLatch completed;
+
+        public volatile int successCount;
+        public volatile int errorCount;
+
+        public volatile Message message;
+        public volatile Exception exception;
+
+        public TestJmsCompletionListener() {
+            this(1);
+        }
+
+        public TestJmsCompletionListener(int expected) {
+            this.completed = new CountDownLatch(expected);
+        }
+
+        public boolean awaitCompletion(long timeout, TimeUnit units) throws InterruptedException {
+            return completed.await(timeout, units);
+        }
+
+        @Override
+        public void onCompletion(Message message) {
+            LOG.info("JmsCompletionListener onCompletion called with message: {}", message);
+            this.message = message;
+            this.successCount++;
+
+            completed.countDown();
+        }
+
+        @Override
+        public void onException(Message message, Exception exception) {
+            LOG.info("JmsCompletionListener onException called with message: {} error {}", message, exception);
+
+            this.message = message;
+            this.exception = exception;
+            this.errorCount++;
+
+            completed.countDown();
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ProducerIntegrationTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ProducerIntegrationTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ProducerIntegrationTest.java
index 6c53398..1e69eb8 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ProducerIntegrationTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ProducerIntegrationTest.java
@@ -40,7 +40,10 @@ import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
 
+import javax.jms.BytesMessage;
 import javax.jms.Connection;
 import javax.jms.DeliveryMode;
 import javax.jms.ExceptionListener;
@@ -54,9 +57,11 @@ import javax.jms.Session;
 import javax.jms.TextMessage;
 import javax.jms.Topic;
 
+import org.apache.qpid.jms.JmsCompletionListener;
 import org.apache.qpid.jms.JmsConnection;
 import org.apache.qpid.jms.JmsConnectionFactory;
 import org.apache.qpid.jms.JmsDefaultConnectionListener;
+import org.apache.qpid.jms.JmsMessageProducer;
 import org.apache.qpid.jms.JmsOperationTimedOutException;
 import org.apache.qpid.jms.JmsSendTimedOutException;
 import org.apache.qpid.jms.message.foreign.ForeignJmsMessage;
@@ -68,10 +73,13 @@ import org.apache.qpid.jms.test.testpeer.ListDescribedType;
 import org.apache.qpid.jms.test.testpeer.TestAmqpPeer;
 import org.apache.qpid.jms.test.testpeer.basictypes.AmqpError;
 import org.apache.qpid.jms.test.testpeer.basictypes.TerminusDurability;
+import org.apache.qpid.jms.test.testpeer.describedtypes.Accepted;
 import org.apache.qpid.jms.test.testpeer.describedtypes.Modified;
 import org.apache.qpid.jms.test.testpeer.describedtypes.Rejected;
 import org.apache.qpid.jms.test.testpeer.describedtypes.Released;
+import org.apache.qpid.jms.test.testpeer.describedtypes.TransactionalState;
 import org.apache.qpid.jms.test.testpeer.matchers.TargetMatcher;
+import org.apache.qpid.jms.test.testpeer.matchers.TransactionalStateMatcher;
 import org.apache.qpid.jms.test.testpeer.matchers.sections.MessageAnnotationsSectionMatcher;
 import org.apache.qpid.jms.test.testpeer.matchers.sections.MessageHeaderSectionMatcher;
 import org.apache.qpid.jms.test.testpeer.matchers.sections.MessagePropertiesSectionMatcher;
@@ -1000,6 +1008,79 @@ public class ProducerIntegrationTest extends QpidJmsTestCase {
     }
 
     @Test(timeout = 20000)
+    public void testRemotelyEndProducerCompletesAsyncSends() throws Exception {
+        final String BREAD_CRUMB = "ErrorMessage";
+
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            final AtomicBoolean producerClosed = new AtomicBoolean();
+            JmsConnection connection = (JmsConnection) testFixture.establishConnecton(testPeer);
+            connection.addConnectionListener(new JmsDefaultConnectionListener() {
+                @Override
+                public void onProducerClosed(MessageProducer producer, Exception exception) {
+                    producerClosed.set(true);
+                }
+            });
+
+            testPeer.expectBegin();
+            Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+            // Create a producer, then remotely end the session afterwards.
+            testPeer.expectSenderAttach();
+
+            Queue queue = session.createQueue("myQueue");
+            // TODO - Can revert to just MessageProducer once JMS 2.0 API is used
+            final JmsMessageProducer producer = (JmsMessageProducer) session.createProducer(queue);
+
+            Message message = session.createTextMessage("content");
+
+            final int MSG_COUNT = 3;
+
+            for (int i = 0; i < MSG_COUNT; ++i) {
+                testPeer.expectTransferButDoNotRespond(new TransferPayloadCompositeMatcher());
+            }
+
+            testPeer.remotelyDetachLastOpenedLinkOnLastOpenedSession(true, true, AmqpError.RESOURCE_LIMIT_EXCEEDED, BREAD_CRUMB, 50);
+
+            TestJmsCompletionListener listener = new TestJmsCompletionListener(MSG_COUNT);
+            try {
+                for (int i = 0; i < MSG_COUNT; ++i) {
+                    producer.send(message, listener);
+                }
+            } catch (JMSException e) {
+                LOG.warn("Caught unexpected error: {}", e.getMessage());
+                fail("No expected exception for this send.");
+            }
+
+            testPeer.waitForAllHandlersToComplete(1000);
+
+            // Verify the producer gets marked closed
+            assertTrue(listener.awaitCompletion(2000, TimeUnit.SECONDS));
+            assertEquals(MSG_COUNT, listener.errorCount);
+
+            // Verify the session is now marked closed
+            try {
+                producer.getDeliveryMode();
+                fail("Expected ISE to be thrown due to being closed");
+            } catch (IllegalStateException jmsise) {
+                String errorMessage = jmsise.getCause().getMessage();
+                assertTrue(errorMessage.contains(AmqpError.RESOURCE_LIMIT_EXCEEDED.toString()));
+                assertTrue(errorMessage.contains(BREAD_CRUMB));
+            }
+
+            assertTrue("Producer closed callback didn't trigger", producerClosed.get());
+
+            // Try closing it explicitly, should effectively no-op in client.
+            // The test peer will throw during close if it sends anything.
+            producer.close();
+
+            testPeer.expectClose();
+            connection.close();
+
+            testPeer.waitForAllHandlersToComplete(1000);
+        }
+    }
+
+    @Test(timeout = 20000)
     public void testRemotelyCloseConnectionDuringSyncSend() throws Exception {
         final String BREAD_CRUMB = "ErrorMessageBreadCrumb";
 
@@ -1150,6 +1231,50 @@ public class ProducerIntegrationTest extends QpidJmsTestCase {
     }
 
     @Test(timeout = 20000)
+    public void testAsyncCompletionGetsTimedOutErrorWhenNoDispostionArrives() throws Exception {
+        try(TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            JmsConnection connection = (JmsConnection) testFixture.establishConnecton(testPeer);
+            connection.setSendTimeout(500);
+
+            testPeer.expectBegin();
+
+            Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+            String queueName = "myQueue";
+            Queue queue = session.createQueue(queueName);
+
+            Message message = session.createTextMessage("text");
+            TransferPayloadCompositeMatcher messageMatcher = new TransferPayloadCompositeMatcher();
+
+            // Expect the producer to attach and grant it some credit, it should send
+            // a transfer which we will not send any response for which should cause the
+            // send operation to time out.
+            testPeer.expectSenderAttach();
+            testPeer.expectTransferButDoNotRespond(messageMatcher);
+            testPeer.expectClose();
+
+            // TODO - Can revert to plain JMS once 2.0 is supported.
+            JmsMessageProducer producer = (JmsMessageProducer) session.createProducer(queue);
+            TestJmsCompletionListener listener = new TestJmsCompletionListener();
+
+            try {
+                producer.send(message, listener);
+            } catch (Throwable error) {
+                LOG.info("Caught expected error: {}", error.getMessage());
+                fail("Send should not fail for async.");
+            }
+
+            assertTrue("Did not get async callback", listener.awaitCompletion(2000, TimeUnit.SECONDS));
+            assertNotNull(listener.exception);
+            assertTrue(listener.exception instanceof JmsSendTimedOutException);
+            assertNotNull(listener.message);
+
+            connection.close();
+
+            testPeer.waitForAllHandlersToComplete(1000);
+        }
+    }
+
+    @Test(timeout = 20000)
     public void testSyncSendMessageRejected() throws Exception {
         doSyncSendMessageNotAcceptedTestImpl(new Rejected());
     }
@@ -1709,6 +1834,430 @@ public class ProducerIntegrationTest extends QpidJmsTestCase {
     }
 
     @Test(timeout = 20000)
+    public void testAsyncCompletionAfterSendMessageGetDispoation() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            Connection connection = testFixture.establishConnecton(testPeer);
+            testPeer.expectBegin();
+            testPeer.expectSenderAttach();
+
+            Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+            Queue queue = session.createQueue("myQueue");
+
+            // TODO Can change to plain MessageProducer when JMS 2.0 API dependency is added.
+            JmsMessageProducer producer = (JmsMessageProducer) session.createProducer(queue);
+
+            // Create and transfer a new message
+            String text = "myMessage";
+            testPeer.expectTransfer(new TransferPayloadCompositeMatcher());
+            testPeer.expectClose();
+
+            TextMessage message = session.createTextMessage(text);
+            TestJmsCompletionListener listener = new TestJmsCompletionListener();
+
+            producer.send(message, listener);
+
+            assertTrue("Did not get async callback", listener.awaitCompletion(2000, TimeUnit.SECONDS));
+            assertNull(listener.exception);
+            assertNotNull(listener.message);
+            assertTrue(listener.message instanceof TextMessage);
+
+            connection.close();
+
+            testPeer.waitForAllHandlersToComplete(1000);
+        }
+    }
+
+    @Test(timeout = 20000)
+    public void testAsyncCompletionResetsBytesMessage() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            Connection connection = testFixture.establishConnecton(testPeer);
+            testPeer.expectBegin();
+            testPeer.expectSenderAttach();
+
+            Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+            Queue queue = session.createQueue("myQueue");
+
+            // TODO Can change to plain MessageProducer when JMS 2.0 API dependency is added.
+            JmsMessageProducer producer = (JmsMessageProducer) session.createProducer(queue);
+
+            // Create and transfer a new message
+            testPeer.expectTransfer(new TransferPayloadCompositeMatcher());
+            testPeer.expectClose();
+
+            Binary payload = new Binary(new byte[] {1, 2, 3, 4});
+            BytesMessage message = session.createBytesMessage();
+            message.writeBytes(payload.getArray());
+
+            TestJmsCompletionListener listener = new TestJmsCompletionListener();
+
+            producer.send(message, listener);
+
+            assertTrue("Did not get async callback", listener.awaitCompletion(2000, TimeUnit.SECONDS));
+            assertNull(listener.exception);
+            assertNotNull(listener.message);
+            assertTrue(listener.message instanceof BytesMessage);
+
+            BytesMessage completed = (BytesMessage) listener.message;
+            assertEquals(payload.getLength(), completed.getBodyLength());
+            byte[] data = new byte[payload.getLength()];
+            completed.readBytes(data);
+
+            connection.close();
+
+            testPeer.waitForAllHandlersToComplete(1000);
+        }
+    }
+
+    @Test(timeout = 20000)
+    public void testAsyncCompletionSendMessageRejected() throws Exception {
+        doAsyncCompletionSendMessageNotAcceptedTestImpl(new Rejected());
+    }
+
+    @Test(timeout = 20000)
+    public void testAsyncCompletionSendMessageReleased() throws Exception {
+        doAsyncCompletionSendMessageNotAcceptedTestImpl(new Released());
+    }
+
+    @Test(timeout = 20000)
+    public void testAsyncCompletionSendMessageModifiedDeliveryFailed() throws Exception {
+        Modified modified = new Modified();
+        modified.setDeliveryFailed(true);
+
+        doAsyncCompletionSendMessageNotAcceptedTestImpl(modified);
+    }
+
+    @Test(timeout = 20000)
+    public void testAsyncCompletionSendMessageModifiedUndeliverable() throws Exception {
+        Modified modified = new Modified();
+        modified.setUndeliverableHere(true);
+
+        doAsyncCompletionSendMessageNotAcceptedTestImpl(modified);
+    }
+
+    @Test(timeout = 20000)
+    public void testAsyncCompletionSendMessageModifiedDeliveryFailedUndeliverable() throws Exception {
+        Modified modified = new Modified();
+        modified.setDeliveryFailed(true);
+        modified.setUndeliverableHere(true);
+
+        doAsyncCompletionSendMessageNotAcceptedTestImpl(modified);
+    }
+
+    private void doAsyncCompletionSendMessageNotAcceptedTestImpl(ListDescribedType responseState) throws JMSException, InterruptedException, Exception, IOException {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            JmsConnection connection = (JmsConnection) testFixture.establishConnecton(testPeer);
+
+            final CountDownLatch asyncError = new CountDownLatch(1);
+
+            connection.setExceptionListener(new ExceptionListener() {
+
+                @Override
+                public void onException(JMSException exception) {
+                    LOG.debug("ExceptionListener got error: {}", exception.getMessage());
+                    asyncError.countDown();
+                }
+            });
+
+            testPeer.expectBegin();
+            testPeer.expectSenderAttach();
+            testPeer.expectSenderAttach();
+
+            Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+            Queue queue = session.createQueue("myQueue");
+
+            // TODO Can change to plain MessageProducer when JMS 2.0 API dependency is added.
+            JmsMessageProducer producer = (JmsMessageProducer) session.createProducer(queue);
+
+            // Create a second producer which allows for a safe wait for credit for the
+            // first producer without the need for a sleep.  Otherwise the first producer
+            // might not do an actual async send due to not having received credit yet.
+            session.createProducer(queue);
+
+            Message message = session.createTextMessage("content");
+
+            testPeer.expectTransfer(new TransferPayloadCompositeMatcher(), nullValue(), false, responseState, true);
+
+            assertNull("Should not yet have a JMSDestination", message.getJMSDestination());
+
+            TestJmsCompletionListener listener = new TestJmsCompletionListener();
+            try {
+                producer.send(message, listener);
+            } catch (JMSException e) {
+                LOG.warn("Caught unexpected error: {}", e.getMessage());
+                fail("No expected exception for this send.");
+            }
+
+            assertTrue("Did not get async callback", listener.awaitCompletion(2000, TimeUnit.SECONDS));
+            assertNotNull(listener.exception);
+            assertNotNull(listener.message);
+            assertTrue(listener.message instanceof TextMessage);
+
+            testPeer.expectTransfer(new TransferPayloadCompositeMatcher());
+            testPeer.expectClose();
+
+            listener = new TestJmsCompletionListener();
+            try {
+                producer.send(message, listener);
+            } catch (JMSException e) {
+                LOG.warn("Caught unexpected error: {}", e.getMessage());
+                fail("No expected exception for this send.");
+            }
+
+            assertTrue("Did not get async callback", listener.awaitCompletion(2000, TimeUnit.SECONDS));
+            assertNull(listener.exception);
+            assertNotNull(listener.message);
+            assertTrue(listener.message instanceof TextMessage);
+
+            connection.close();
+
+            testPeer.waitForAllHandlersToComplete(2000);
+        }
+    }
+
+    @Test(timeout = 20000)
+    public void testAsyncCompletionSessionCloseThrowsIllegalStateException() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            Connection connection = testFixture.establishConnecton(testPeer);
+            testPeer.expectBegin();
+            testPeer.expectSenderAttach();
+
+            final Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+            Queue queue = session.createQueue("myQueue");
+
+            // TODO Can change to plain MessageProducer when JMS 2.0 API dependency is added.
+            JmsMessageProducer producer = (JmsMessageProducer) session.createProducer(queue);
+
+            // Create and transfer a new message
+            String text = "myMessage";
+            testPeer.expectTransfer(new TransferPayloadCompositeMatcher());
+            testPeer.expectClose();
+
+            final AtomicReference<JMSException> closeError = new AtomicReference<JMSException>(null);
+            TextMessage message = session.createTextMessage(text);
+            TestJmsCompletionListener listener = new TestJmsCompletionListener() {
+
+                @Override
+                public void onCompletion(Message message) {
+
+                    try {
+                        session.close();
+                    } catch (JMSException jmsEx) {
+                        closeError.set(jmsEx);
+                    }
+
+                    super.onCompletion(message);
+                };
+            };
+
+            producer.send(message, listener);
+
+            assertTrue("Did not get async callback", listener.awaitCompletion(2000, TimeUnit.SECONDS));
+            assertNull(listener.exception);
+            assertNotNull(listener.message);
+            assertTrue(listener.message instanceof TextMessage);
+            assertNotNull(closeError.get());
+            assertTrue(closeError.get() instanceof IllegalStateException);
+
+            connection.close();
+
+            testPeer.waitForAllHandlersToComplete(1000);
+        }
+    }
+
+    @Test(timeout = 20000)
+    public void testAsyncCompletionConnectionCloseThrowsIllegalStateException() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            final Connection connection = testFixture.establishConnecton(testPeer);
+            testPeer.expectBegin();
+            testPeer.expectSenderAttach();
+
+            final Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+            Queue queue = session.createQueue("myQueue");
+
+            // TODO Can change to plain MessageProducer when JMS 2.0 API dependency is added.
+            JmsMessageProducer producer = (JmsMessageProducer) session.createProducer(queue);
+
+            // Create and transfer a new message
+            String text = "myMessage";
+            testPeer.expectTransfer(new TransferPayloadCompositeMatcher());
+            testPeer.expectClose();
+
+            final AtomicReference<JMSException> closeError = new AtomicReference<JMSException>(null);
+            TextMessage message = session.createTextMessage(text);
+            TestJmsCompletionListener listener = new TestJmsCompletionListener() {
+
+                @Override
+                public void onCompletion(Message message) {
+
+                    try {
+                        connection.close();
+                    } catch (JMSException jmsEx) {
+                        closeError.set(jmsEx);
+                    }
+
+                    super.onCompletion(message);
+                };
+            };
+
+            producer.send(message, listener);
+
+            assertTrue("Did not get async callback", listener.awaitCompletion(2000, TimeUnit.SECONDS));
+            assertNull(listener.exception);
+            assertNotNull(listener.message);
+            assertNotNull(closeError.get());
+            assertTrue(closeError.get() instanceof IllegalStateException);
+
+            connection.close();
+
+            testPeer.waitForAllHandlersToComplete(1000);
+        }
+    }
+
+    @Test(timeout = 20000)
+    public void testAsyncCompletionSessionCommitThrowsIllegalStateException() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            final Connection connection = testFixture.establishConnecton(testPeer);
+            testPeer.expectBegin();
+            testPeer.expectCoordinatorAttach();
+
+            // First expect an unsettled 'declare' transfer to the txn coordinator, and
+            // reply with a declared disposition state containing the txnId.
+            Binary txnId = new Binary(new byte[]{ (byte) 5, (byte) 6, (byte) 7, (byte) 8});
+            testPeer.expectDeclare(txnId);
+
+            testPeer.expectSenderAttach();
+
+            final Session session = connection.createSession(true, Session.SESSION_TRANSACTED);
+            Queue queue = session.createQueue("myQueue");
+
+            // TODO Can change to plain MessageProducer when JMS 2.0 API dependency is added.
+            JmsMessageProducer producer = (JmsMessageProducer) session.createProducer(queue);
+
+            // Create and transfer a new message
+            String text = "myMessage";
+            MessageHeaderSectionMatcher headersMatcher = new MessageHeaderSectionMatcher(true);
+            MessageAnnotationsSectionMatcher msgAnnotationsMatcher = new MessageAnnotationsSectionMatcher(true);
+            MessagePropertiesSectionMatcher propsMatcher = new MessagePropertiesSectionMatcher(true);
+            TransferPayloadCompositeMatcher messageMatcher = new TransferPayloadCompositeMatcher();
+            messageMatcher.setHeadersMatcher(headersMatcher);
+            messageMatcher.setMessageAnnotationsMatcher(msgAnnotationsMatcher);
+            messageMatcher.setPropertiesMatcher(propsMatcher);
+            messageMatcher.setMessageContentMatcher(new EncodedAmqpValueMatcher(text));
+            TransactionalStateMatcher stateMatcher = new TransactionalStateMatcher();
+            stateMatcher.withTxnId(equalTo(txnId));
+            stateMatcher.withOutcome(nullValue());
+            TransactionalState txState = new TransactionalState();
+            txState.setTxnId(txnId);
+            txState.setOutcome(new Accepted());
+
+            testPeer.expectTransfer(messageMatcher, stateMatcher, false, txState, true);
+            testPeer.expectDischarge(txnId, true);
+            testPeer.expectClose();
+
+            final AtomicReference<JMSException> commitError = new AtomicReference<JMSException>(null);
+            TextMessage message = session.createTextMessage(text);
+            TestJmsCompletionListener listener = new TestJmsCompletionListener() {
+
+                @Override
+                public void onCompletion(Message message) {
+
+                    try {
+                        session.commit();
+                    } catch (JMSException jmsEx) {
+                        commitError.set(jmsEx);
+                    }
+
+                    super.onCompletion(message);
+                };
+            };
+
+            producer.send(message, listener);
+
+            assertTrue("Did not get async callback", listener.awaitCompletion(2000, TimeUnit.SECONDS));
+            assertNull(listener.exception);
+            assertNotNull(listener.message);
+            assertNotNull(commitError.get());
+            assertTrue(commitError.get() instanceof IllegalStateException);
+
+            connection.close();
+
+            testPeer.waitForAllHandlersToComplete(1000);
+        }
+    }
+
+    @Test(timeout = 20000)
+    public void testAsyncCompletionSessionRollbackThrowsIllegalStateException() throws Exception {
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            final Connection connection = testFixture.establishConnecton(testPeer);
+            testPeer.expectBegin();
+            testPeer.expectCoordinatorAttach();
+
+            // First expect an unsettled 'declare' transfer to the txn coordinator, and
+            // reply with a declared disposition state containing the txnId.
+            Binary txnId = new Binary(new byte[]{ (byte) 5, (byte) 6, (byte) 7, (byte) 8});
+            testPeer.expectDeclare(txnId);
+
+            testPeer.expectSenderAttach();
+
+            final Session session = connection.createSession(true, Session.SESSION_TRANSACTED);
+            Queue queue = session.createQueue("myQueue");
+
+            // TODO Can change to plain MessageProducer when JMS 2.0 API dependency is added.
+            JmsMessageProducer producer = (JmsMessageProducer) session.createProducer(queue);
+
+            // Create and transfer a new message
+            String text = "myMessage";
+            MessageHeaderSectionMatcher headersMatcher = new MessageHeaderSectionMatcher(true);
+            MessageAnnotationsSectionMatcher msgAnnotationsMatcher = new MessageAnnotationsSectionMatcher(true);
+            MessagePropertiesSectionMatcher propsMatcher = new MessagePropertiesSectionMatcher(true);
+            TransferPayloadCompositeMatcher messageMatcher = new TransferPayloadCompositeMatcher();
+            messageMatcher.setHeadersMatcher(headersMatcher);
+            messageMatcher.setMessageAnnotationsMatcher(msgAnnotationsMatcher);
+            messageMatcher.setPropertiesMatcher(propsMatcher);
+            messageMatcher.setMessageContentMatcher(new EncodedAmqpValueMatcher(text));
+            TransactionalStateMatcher stateMatcher = new TransactionalStateMatcher();
+            stateMatcher.withTxnId(equalTo(txnId));
+            stateMatcher.withOutcome(nullValue());
+            TransactionalState txState = new TransactionalState();
+            txState.setTxnId(txnId);
+            txState.setOutcome(new Accepted());
+
+            testPeer.expectTransfer(messageMatcher, stateMatcher, false, txState, true);
+            testPeer.expectDischarge(txnId, true);
+            testPeer.expectClose();
+
+            final AtomicReference<JMSException> rollback = new AtomicReference<JMSException>(null);
+            TextMessage message = session.createTextMessage(text);
+            TestJmsCompletionListener listener = new TestJmsCompletionListener() {
+
+                @Override
+                public void onCompletion(Message message) {
+
+                    try {
+                        session.rollback();
+                    } catch (JMSException jmsEx) {
+                        rollback.set(jmsEx);
+                    }
+
+                    super.onCompletion(message);
+                };
+            };
+
+            producer.send(message, listener);
+
+            assertTrue("Did not get async callback", listener.awaitCompletion(2000, TimeUnit.SECONDS));
+            assertNull(listener.exception);
+            assertNotNull(listener.message);
+            assertNotNull(rollback.get());
+            assertTrue(rollback.get() instanceof IllegalStateException);
+
+            connection.close();
+
+            testPeer.waitForAllHandlersToComplete(1000);
+        }
+    }
+
+    @Test(timeout = 20000)
     public void testAnonymousProducerSendFailureHandledWhenAnonymousRelayNodeIsNotSupported() throws Exception {
         try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
 
@@ -1831,4 +2380,47 @@ public class ProducerIntegrationTest extends QpidJmsTestCase {
             testPeer.waitForAllHandlersToComplete(1000);
         }
     }
+
+    private class TestJmsCompletionListener implements JmsCompletionListener {
+
+        private final CountDownLatch completed;
+
+        public volatile int successCount;
+        public volatile int errorCount;
+
+        public volatile Message message;
+        public volatile Exception exception;
+
+        public TestJmsCompletionListener() {
+            this(1);
+        }
+
+        public TestJmsCompletionListener(int expected) {
+            this.completed = new CountDownLatch(expected);
+        }
+
+        public boolean awaitCompletion(long timeout, TimeUnit units) throws InterruptedException {
+            return completed.await(timeout, units);
+        }
+
+        @Override
+        public void onCompletion(Message message) {
+            LOG.info("JmsCompletionListener onCompletion called with message: {}", message);
+            this.message = message;
+            this.successCount++;
+
+            completed.countDown();
+        }
+
+        @Override
+        public void onException(Message message, Exception exception) {
+            LOG.info("JmsCompletionListener onException called with message: {} error {}", message, exception);
+
+            this.message = message;
+            this.exception = exception;
+            this.errorCount++;
+
+            completed.countDown();
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/SessionIntegrationTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/SessionIntegrationTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/SessionIntegrationTest.java
index 7bf35e4..34d60f1 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/SessionIntegrationTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/SessionIntegrationTest.java
@@ -57,8 +57,10 @@ import javax.jms.TextMessage;
 import javax.jms.Topic;
 import javax.jms.TopicSubscriber;
 
+import org.apache.qpid.jms.JmsCompletionListener;
 import org.apache.qpid.jms.JmsConnection;
 import org.apache.qpid.jms.JmsDefaultConnectionListener;
+import org.apache.qpid.jms.JmsMessageProducer;
 import org.apache.qpid.jms.JmsOperationTimedOutException;
 import org.apache.qpid.jms.JmsSession;
 import org.apache.qpid.jms.policy.JmsDefaultPrefetchPolicy;
@@ -1446,7 +1448,7 @@ public class SessionIntegrationTest extends QpidJmsTestCase {
     public void testCreateAnonymousProducerWhenAnonymousRelayNodeIsNotSupported() throws Exception {
         try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
 
-            //DO NOT add capability to indicate server support for ANONYMOUS-RELAY
+            // DO NOT add capability to indicate server support for ANONYMOUS-RELAY
 
             Connection connection = testFixture.establishConnecton(testPeer);
             connection.start();
@@ -1460,12 +1462,12 @@ public class SessionIntegrationTest extends QpidJmsTestCase {
             // Expect no AMQP traffic when we create the anonymous producer, as it will wait
             // for an actual send to occur on the producer before anything occurs on the wire
 
-            //Create an anonymous producer
+            // Create an anonymous producer
             MessageProducer producer = session.createProducer(null);
             assertNotNull("Producer object was null", producer);
 
-            //Expect a new message sent by the above producer to cause creation of a new
-            //sender link to the given destination, then closing the link after the message is sent.
+            // Expect a new message sent by the above producer to cause creation of a new
+            // sender link to the given destination, then closing the link after the message is sent.
             TargetMatcher targetMatcher = new TargetMatcher();
             targetMatcher.withAddress(equalTo(topicName));
             targetMatcher.withDynamic(equalTo(false));
@@ -1484,7 +1486,7 @@ public class SessionIntegrationTest extends QpidJmsTestCase {
             Message message = session.createMessage();
             producer.send(dest, message);
 
-            //Repeat the send and observe another attach->transfer->detach.
+            // Repeat the send and observe another attach->transfer->detach.
             testPeer.expectSenderAttach(targetMatcher, false, false);
             testPeer.expectTransfer(messageMatcher);
             testPeer.expectDetach(true, true, true);
@@ -1675,6 +1677,78 @@ public class SessionIntegrationTest extends QpidJmsTestCase {
     }
 
     @Test(timeout = 20000)
+    public void testRemotelyEndSessionWithProducerCompletesAsyncSends() throws Exception {
+        final String BREAD_CRUMB = "ErrorMessage";
+
+        try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+            final AtomicBoolean sessionClosed = new AtomicBoolean();
+            JmsConnection connection = (JmsConnection) testFixture.establishConnecton(testPeer);
+            connection.addConnectionListener(new JmsDefaultConnectionListener() {
+                @Override
+                public void onSessionClosed(Session session, Exception exception) {
+                    sessionClosed.set(true);
+                }
+            });
+
+            testPeer.expectBegin();
+            Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+            // Create a producer, then remotely end the session afterwards.
+            testPeer.expectSenderAttach();
+
+            Queue queue = session.createQueue("myQueue");
+            // TODO - Can revert to just MessageProducer once JMS 2.0 API is used
+            final JmsMessageProducer producer = (JmsMessageProducer) session.createProducer(queue);
+
+            Message message = session.createTextMessage("content");
+
+            final int MSG_COUNT = 3;
+
+            for (int i = 0; i < MSG_COUNT; ++i) {
+                testPeer.expectTransferButDoNotRespond(new TransferPayloadCompositeMatcher());
+            }
+
+            testPeer.remotelyEndLastOpenedSession(true, 0, AmqpError.RESOURCE_DELETED, BREAD_CRUMB);
+
+            TestJmsCompletionListener listener = new TestJmsCompletionListener(MSG_COUNT);
+            try {
+                for (int i = 0; i < MSG_COUNT; ++i) {
+                    producer.send(message, listener);
+                }
+            } catch (JMSException e) {
+                LOG.warn("Caught unexpected error: {}", e.getMessage());
+                fail("No expected exception for this send.");
+            }
+
+            testPeer.waitForAllHandlersToComplete(1000);
+
+            // Verify the producer gets marked closed
+            assertTrue(listener.awaitCompletion(2000, TimeUnit.SECONDS));
+            assertEquals(MSG_COUNT, listener.errorCount);
+            assertEquals(0, listener.successCount);
+
+            // Verify the session is now marked closed
+            try {
+                session.getAcknowledgeMode();
+                fail("Expected ISE to be thrown due to being closed");
+            } catch (IllegalStateException jmsise) {
+                String errorMessage = jmsise.getCause().getMessage();
+                assertTrue(errorMessage.contains(AmqpError.RESOURCE_DELETED.toString()));
+                assertTrue(errorMessage.contains(BREAD_CRUMB));
+            }
+
+            assertTrue("Session closed callback didn't trigger", sessionClosed.get());
+
+            // Try closing it explicitly, should effectively no-op in client.
+            // The test peer will throw during close if it sends anything.
+            producer.close();
+
+            testPeer.expectClose();
+            connection.close();
+        }
+    }
+
+    @Test(timeout = 20000)
     public void testRemotelyEndSessionWithConsumer() throws Exception {
         final String BREAD_CRUMB = "ErrorMessage";
 
@@ -1920,4 +1994,34 @@ public class SessionIntegrationTest extends QpidJmsTestCase {
             connection.close();
         }
     }
+
+    private class TestJmsCompletionListener implements JmsCompletionListener {
+
+        private final CountDownLatch completed;
+
+        public volatile int successCount;
+        public volatile int errorCount;
+
+        public TestJmsCompletionListener(int expected) {
+            completed = new CountDownLatch(expected);
+        }
+
+        public boolean awaitCompletion(long timeout, TimeUnit units) throws InterruptedException {
+            return completed.await(timeout, units);
+        }
+
+        @Override
+        public void onCompletion(Message message) {
+            LOG.info("JmsCompletionListener onCompletion called with message: {}", message);
+            successCount++;
+            completed.countDown();
+        }
+
+        @Override
+        public void onException(Message message, Exception exception) {
+            LOG.info("JmsCompletionListener onException called with message: {} error {}", message, exception);
+            errorCount++;
+            completed.countDown();
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/3a03663b/qpid-jms-client/src/test/java/org/apache/qpid/jms/producer/JmsMessageProducerTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/producer/JmsMessageProducerTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/producer/JmsMessageProducerTest.java
index 80a6e6a..7117e9f 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/producer/JmsMessageProducerTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/producer/JmsMessageProducerTest.java
@@ -22,35 +22,63 @@ import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.jms.Connection;
 import javax.jms.DeliveryMode;
+import javax.jms.Destination;
 import javax.jms.InvalidDestinationException;
+import javax.jms.JMSException;
 import javax.jms.Message;
 import javax.jms.MessageProducer;
 import javax.jms.Session;
 
+import org.apache.qpid.jms.JmsCompletionListener;
+import org.apache.qpid.jms.JmsConnectionFactory;
 import org.apache.qpid.jms.JmsConnectionTestSupport;
 import org.apache.qpid.jms.JmsDestination;
+import org.apache.qpid.jms.JmsMessageProducer;
 import org.apache.qpid.jms.JmsQueue;
 import org.apache.qpid.jms.JmsSession;
+import org.apache.qpid.jms.message.JmsOutboundMessageDispatch;
+import org.apache.qpid.jms.provider.mock.MockRemotePeer;
+import org.apache.qpid.jms.test.Wait;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mockito;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Test basic functionality around JmsConnection
  */
 public class JmsMessageProducerTest extends JmsConnectionTestSupport {
 
+    private static final Logger LOG = LoggerFactory.getLogger(JmsMessageProducerTest.class);
+
+    private final MyCompletionListener completionListener = new MyCompletionListener();
     private JmsSession session;
+    private final MockRemotePeer remotePeer = new MockRemotePeer();
 
     @Override
     @Before
     public void setUp() throws Exception {
         super.setUp();
+        remotePeer.start();
         connection = createConnectionToMockProvider();
         session = (JmsSession) connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
     }
 
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        remotePeer.shutdown();
+        super.tearDown();
+    }
+
     @Test(timeout = 10000)
     public void testMultipleCloseCallsNoErrors() throws Exception {
         MessageProducer producer = session.createProducer(null);
@@ -106,7 +134,7 @@ public class JmsMessageProducerTest extends JmsConnectionTestSupport {
 
     @Test(timeout = 10000)
     public void testAnonymousProducerThrowsUOEWhenExplictDestinationNotProvided() throws Exception {
-        MessageProducer producer = session.createProducer(null);
+        JmsMessageProducer producer = (JmsMessageProducer) session.createProducer(null);
 
         Message message = Mockito.mock(Message.class);
         try {
@@ -117,17 +145,31 @@ public class JmsMessageProducerTest extends JmsConnectionTestSupport {
         }
 
         try {
+            producer.send(message, completionListener);
+            fail("Expected exception not thrown");
+        } catch (UnsupportedOperationException uoe) {
+            // expected
+        }
+
+        try {
             producer.send(message, Message.DEFAULT_DELIVERY_MODE, Message.DEFAULT_PRIORITY, Message.DEFAULT_TIME_TO_LIVE);
             fail("Expected exception not thrown");
         } catch (UnsupportedOperationException uoe) {
             // expected
         }
+
+        try {
+            producer.send(message, Message.DEFAULT_DELIVERY_MODE, Message.DEFAULT_PRIORITY, Message.DEFAULT_TIME_TO_LIVE, completionListener);
+            fail("Expected exception not thrown");
+        } catch (UnsupportedOperationException uoe) {
+            // expected
+        }
     }
 
     @Test(timeout = 10000)
     public void testExplicitProducerThrowsUOEWhenExplictDestinationIsProvided() throws Exception {
         JmsDestination dest = new JmsQueue("explicitDestination");
-        MessageProducer producer = session.createProducer(new JmsQueue());
+        JmsMessageProducer producer = (JmsMessageProducer) session.createProducer(dest);
 
         Message message = Mockito.mock(Message.class);
         try {
@@ -138,16 +180,30 @@ public class JmsMessageProducerTest extends JmsConnectionTestSupport {
         }
 
         try {
+            producer.send(dest, message, completionListener);
+            fail("Expected exception not thrown");
+        } catch (UnsupportedOperationException uoe) {
+            // expected
+        }
+
+        try {
             producer.send(dest, message, Message.DEFAULT_DELIVERY_MODE, Message.DEFAULT_PRIORITY, Message.DEFAULT_TIME_TO_LIVE);
             fail("Expected exception not thrown");
         } catch (UnsupportedOperationException uoe) {
             // expected
         }
+
+        try {
+            producer.send(dest, message, Message.DEFAULT_DELIVERY_MODE, Message.DEFAULT_PRIORITY, Message.DEFAULT_TIME_TO_LIVE, completionListener);
+            fail("Expected exception not thrown");
+        } catch (UnsupportedOperationException uoe) {
+            // expected
+        }
     }
 
     @Test(timeout = 10000)
     public void testAnonymousDestinationProducerThrowsIDEWhenNullDestinationIsProvided() throws Exception {
-        MessageProducer producer = session.createProducer(null);
+        JmsMessageProducer producer = (JmsMessageProducer) session.createProducer(null);
 
         Message message = Mockito.mock(Message.class);
         try {
@@ -158,10 +214,369 @@ public class JmsMessageProducerTest extends JmsConnectionTestSupport {
         }
 
         try {
+            producer.send(null, message, completionListener);
+            fail("Expected exception not thrown");
+        } catch (InvalidDestinationException ide) {
+            // expected
+        }
+
+        try {
             producer.send(null, message, Message.DEFAULT_DELIVERY_MODE, Message.DEFAULT_PRIORITY, Message.DEFAULT_TIME_TO_LIVE);
             fail("Expected exception not thrown");
         } catch (InvalidDestinationException ide) {
             // expected
         }
+
+        try {
+            producer.send(null, message, Message.DEFAULT_DELIVERY_MODE, Message.DEFAULT_PRIORITY, Message.DEFAULT_TIME_TO_LIVE, completionListener);
+            fail("Expected exception not thrown");
+        } catch (InvalidDestinationException ide) {
+            // expected
+        }
+    }
+
+    @Test(timeout = 10000)
+    public void testAnonymousProducerThrowsIAEWhenNullCompletionListenerProvided() throws Exception {
+        JmsMessageProducer producer = (JmsMessageProducer) session.createProducer(null);
+        JmsDestination dest = new JmsQueue("explicitDestination");
+
+        Message message = Mockito.mock(Message.class);
+
+        try {
+            producer.send(dest, message, null);
+            fail("Expected exception not thrown");
+        } catch (IllegalArgumentException iae) {
+            // expected
+        }
+
+        try {
+            producer.send(dest, message, Message.DEFAULT_DELIVERY_MODE, Message.DEFAULT_PRIORITY, Message.DEFAULT_TIME_TO_LIVE, null);
+            fail("Expected exception not thrown");
+        } catch (IllegalArgumentException iae) {
+            // expected
+        }
+    }
+
+    @Test(timeout = 10000)
+    public void testExplicitProducerThrowsIAEWhenNullCompletionListenerIsProvided() throws Exception {
+        JmsDestination dest = new JmsQueue("explicitDestination");
+        JmsMessageProducer producer = (JmsMessageProducer) session.createProducer(dest);
+
+        Message message = Mockito.mock(Message.class);
+        try {
+            producer.send(message, null);
+            fail("Expected exception not thrown");
+        } catch (IllegalArgumentException iae) {
+            // expected
+        }
+
+        try {
+            producer.send(message, Message.DEFAULT_DELIVERY_MODE, Message.DEFAULT_PRIORITY, Message.DEFAULT_TIME_TO_LIVE, null);
+            fail("Expected exception not thrown");
+        } catch (IllegalArgumentException iae) {
+            // expected
+        }
+    }
+
+    @Test(timeout = 10000)
+    public void testInOrderSendAcksCompletionsReturnInOrder() throws Exception {
+        final int MESSAGE_COUNT = 3;
+
+        final MockRemotePeer remotePoor = MockRemotePeer.INSTANCE;
+
+        JmsConnectionFactory factory = new JmsConnectionFactory(
+            "mock://localhost?mock.delayCompletionCalls=true");
+
+        Connection connection = factory.createConnection();
+        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+        final Destination destination = new JmsQueue("explicitDestination");
+        JmsMessageProducer producer = (JmsMessageProducer) session.createProducer(destination);
+        final MyCompletionListener listener = new MyCompletionListener();
+
+        sendMessages(MESSAGE_COUNT, producer, listener);
+
+        assertTrue("Not all sends made it to the remote", Wait.waitFor(new Wait.Condition() {
+
+            @Override
+            public boolean isSatisified() throws Exception {
+                return remotePoor.getPendingCompletions(destination).size() == MESSAGE_COUNT;
+            }
+        }));
+
+        remotePoor.completeAllPendingSends(destination);
+
+        assertTrue("Not all completions triggered", Wait.waitFor(new Wait.Condition() {
+
+            @Override
+            public boolean isSatisified() throws Exception {
+                return listener.getCompletedSends().size() == MESSAGE_COUNT;
+            }
+        }));
+
+        assertMessageCompletedInOrder(MESSAGE_COUNT, listener);
+
+        connection.close();
+    }
+
+    @Test(timeout = 10000)
+    public void testReversedOrderSendAcksCompletionsReturnInOrder() throws Exception {
+        final int MESSAGE_COUNT = 3;
+
+        final MockRemotePeer remotePoor = MockRemotePeer.INSTANCE;
+
+        JmsConnectionFactory factory = new JmsConnectionFactory(
+            "mock://localhost?mock.delayCompletionCalls=true");
+
+        Connection connection = factory.createConnection();
+        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+        final Destination destination = new JmsQueue("explicitDestination");
+        JmsMessageProducer producer = (JmsMessageProducer) session.createProducer(destination);
+        final MyCompletionListener listener = new MyCompletionListener();
+
+        sendMessages(MESSAGE_COUNT, producer, listener);
+
+        assertTrue("Not all sends made it to the remote", Wait.waitFor(new Wait.Condition() {
+
+            @Override
+            public boolean isSatisified() throws Exception {
+                return remotePoor.getPendingCompletions(destination).size() == MESSAGE_COUNT;
+            }
+        }));
+
+        List<JmsOutboundMessageDispatch> pending = remotePoor.getPendingCompletions(destination);
+        assertEquals(MESSAGE_COUNT, pending.size());
+        Collections.reverse(pending);
+
+        for (JmsOutboundMessageDispatch envelope : pending) {
+            LOG.info("Trigger completion of message: {}", envelope.getMessage().getJMSMessageID());
+            remotePoor.completePendingSend(envelope);
+        }
+
+        assertTrue("Not all completions triggered", Wait.waitFor(new Wait.Condition() {
+
+            @Override
+            public boolean isSatisified() throws Exception {
+                return listener.getCompletedSends().size() == MESSAGE_COUNT;
+            }
+        }));
+
+        assertMessageCompletedInOrder(MESSAGE_COUNT, listener);
+
+        connection.close();
+    }
+
+    @Test(timeout = 10000)
+    public void testInOrderSendFailuresCompletionsReturnInOrder() throws Exception {
+        final int MESSAGE_COUNT = 3;
+
+        final MockRemotePeer remotePoor = MockRemotePeer.INSTANCE;
+
+        JmsConnectionFactory factory = new JmsConnectionFactory(
+            "mock://localhost?mock.delayCompletionCalls=true");
+
+        Connection connection = factory.createConnection();
+        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+        final Destination destination = new JmsQueue("explicitDestination");
+        JmsMessageProducer producer = (JmsMessageProducer) session.createProducer(destination);
+        final MyCompletionListener listener = new MyCompletionListener();
+
+        sendMessages(MESSAGE_COUNT, producer, listener);
+        assertTrue("Not all messages sent", Wait.waitFor(new Wait.Condition() {
+
+            @Override
+            public boolean isSatisified() throws Exception {
+                return remotePoor.getPendingCompletions(destination).size() == MESSAGE_COUNT;
+            }
+        }));
+        remotePoor.failAllPendingSends(destination, new JMSException("Could not send message"));
+
+        assertTrue("Not all completions triggered", Wait.waitFor(new Wait.Condition() {
+
+            @Override
+            public boolean isSatisified() throws Exception {
+                return listener.getFailedSends().size() == MESSAGE_COUNT;
+            }
+        }));
+
+        assertMessageFailedInOrder(MESSAGE_COUNT, listener);
+
+        connection.close();
+    }
+
+    @Test(timeout = 10000)
+    public void testReversedOrderSendAcksFailuresReturnInOrder() throws Exception {
+        final int MESSAGE_COUNT = 3;
+
+        final MockRemotePeer remotePoor = MockRemotePeer.INSTANCE;
+
+        JmsConnectionFactory factory = new JmsConnectionFactory(
+            "mock://localhost?mock.delayCompletionCalls=true");
+
+        Connection connection = factory.createConnection();
+        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+        final Destination destination = new JmsQueue("explicitDestination");
+        JmsMessageProducer producer = (JmsMessageProducer) session.createProducer(destination);
+        final MyCompletionListener listener = new MyCompletionListener();
+
+        sendMessages(MESSAGE_COUNT, producer, listener);
+
+        assertTrue("Not all sends made it to the remote", Wait.waitFor(new Wait.Condition() {
+
+            @Override
+            public boolean isSatisified() throws Exception {
+                return remotePoor.getPendingCompletions(destination).size() == MESSAGE_COUNT;
+            }
+        }));
+
+        List<JmsOutboundMessageDispatch> pending = remotePoor.getPendingCompletions(destination);
+        assertEquals(MESSAGE_COUNT, pending.size());
+        Collections.reverse(pending);
+
+        for (JmsOutboundMessageDispatch envelope : pending) {
+            LOG.info("Trigger failure of message: {}", envelope.getMessage().getJMSMessageID());
+            remotePoor.failPendingSend(envelope, new JMSException("Failed to send message"));
+        }
+
+        assertTrue("Not all failures triggered", Wait.waitFor(new Wait.Condition() {
+
+            @Override
+            public boolean isSatisified() throws Exception {
+                return listener.getFailedSends().size() == MESSAGE_COUNT;
+            }
+        }));
+
+        assertMessageFailedInOrder(MESSAGE_COUNT, listener);
+
+        connection.close();
+    }
+
+    @Test(timeout = 10000)
+    public void testInterleavedCompletionsReturnedInOrder() throws Exception {
+        final int MESSAGE_COUNT = 3;
+
+        final MockRemotePeer remotePoor = MockRemotePeer.INSTANCE;
+
+        JmsConnectionFactory factory = new JmsConnectionFactory(
+            "mock://localhost?mock.delayCompletionCalls=true");
+
+        Connection connection = factory.createConnection();
+        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+        final Destination destination = new JmsQueue("explicitDestination");
+        JmsMessageProducer producer = (JmsMessageProducer) session.createProducer(destination);
+        final MyCompletionListener listener = new MyCompletionListener();
+
+        sendMessages(MESSAGE_COUNT, producer, listener);
+
+        assertTrue("Not all sends made it to the remote", Wait.waitFor(new Wait.Condition() {
+
+            @Override
+            public boolean isSatisified() throws Exception {
+                return remotePoor.getPendingCompletions(destination).size() == MESSAGE_COUNT;
+            }
+        }));
+
+        List<JmsOutboundMessageDispatch> pending = remotePoor.getPendingCompletions(destination);
+        assertEquals(MESSAGE_COUNT, pending.size());
+        Collections.reverse(pending);
+
+        for (JmsOutboundMessageDispatch envelope : pending) {
+            int sequence = envelope.getMessage().getIntProperty("sequence");
+            if (sequence % 2 == 0) {
+                LOG.info("Trigger completion of message: {}", envelope.getMessage().getJMSMessageID());
+                remotePoor.completePendingSend(envelope);
+            } else {
+                LOG.info("Trigger failure of message: {}", envelope.getMessage().getJMSMessageID());
+                remotePoor.failPendingSend(envelope, new JMSException("Failed to send message"));
+            }
+        }
+
+        assertTrue("Not all completions triggered", Wait.waitFor(new Wait.Condition() {
+
+            @Override
+            public boolean isSatisified() throws Exception {
+                return listener.getCombinedSends().size() == MESSAGE_COUNT;
+            }
+        }));
+
+        assertTotalCompletionOrder(MESSAGE_COUNT, listener);
+
+        connection.close();
+    }
+
+    private void sendMessages(int count, JmsMessageProducer producer, MyCompletionListener listener) throws Exception {
+        for (int i = 0; i < count; ++i) {
+            Message message = session.createMessage();
+            message.setIntProperty("sequence", i);
+
+            producer.send(message, listener);
+        }
+    }
+
+    private void assertMessageCompletedInOrder(int expected, MyCompletionListener listener) throws Exception {
+        assertEquals("Did not get expected number of completions", expected, listener.completed.size());
+        for (int i = 0; i < listener.completed.size(); ++i) {
+            int sequence = listener.completed.get(i).getIntProperty("sequence");
+            assertEquals("Did not complete expected message: " + i + " got: " + sequence, i, sequence);
+        }
+    }
+
+    private void assertMessageFailedInOrder(int expected, MyCompletionListener listener) throws Exception {
+        assertEquals("Did not get expected number of failures", expected, listener.failed.size());
+        for (int i = 0; i < listener.failed.size(); ++i) {
+            int sequence = listener.failed.get(i).getIntProperty("sequence");
+            assertEquals("Did not fail expected message: " + i + " got: " + sequence, i, sequence);
+        }
+    }
+
+    private void assertTotalCompletionOrder(int expected, MyCompletionListener listener) throws Exception {
+        assertEquals("Did not get expected number of failures", expected, listener.combinedResult.size());
+        for (int i = 0; i < listener.combinedResult.size(); ++i) {
+            int sequence = listener.combinedResult.get(i).getIntProperty("sequence");
+            assertEquals("Did not fail expected message: " + i + " got: " + sequence, i, sequence);
+        }
+    }
+
+    private class MyCompletionListener implements JmsCompletionListener {
+
+        private final List<Message> completed = new ArrayList<Message>();
+        private final List<Message> failed = new ArrayList<Message>();
+        private final List<Message> combinedResult = new ArrayList<Message>();
+
+        @Override
+        public void onCompletion(Message message) {
+            try {
+                LOG.debug("Recording completed send: {}", message.getJMSMessageID());
+            } catch (JMSException e) {
+            }
+            completed.add(message);
+            combinedResult.add(message);
+        }
+
+        @Override
+        public void onException(Message message, Exception exception) {
+            try {
+                LOG.debug("Recording failed send: {} -> error {}", message.getJMSMessageID(), exception.getMessage());
+            } catch (JMSException e) {
+            }
+            failed.add(message);
+            combinedResult.add(message);
+        }
+
+        public List<Message> getCombinedSends() {
+            return combinedResult;
+        }
+
+        public List<Message> getCompletedSends() {
+            return completed;
+        }
+
+        public List<Message> getFailedSends() {
+            return failed;
+        }
     }
 }


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