You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by ff...@apache.org on 2017/10/19 03:16:10 UTC

[cxf] branch master updated: [CXF-7532]REST on JMS transport can't handle the request message with text messageType

This is an automated email from the ASF dual-hosted git repository.

ffang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cxf.git


The following commit(s) were added to refs/heads/master by this push:
     new 1d66475  [CXF-7532]REST on JMS transport can't handle the request message with text messageType
1d66475 is described below

commit 1d66475b4dea2a0031dcb1765e8e9fd08430ec9b
Author: Freeman Fang <fr...@gmail.com>
AuthorDate: Thu Oct 19 11:15:50 2017 +0800

    [CXF-7532]REST on JMS transport can't handle the request message with text messageType
---
 .../org/apache/cxf/jaxrs/impl/ResponseImpl.java    |   5 +
 .../org/apache/cxf/jaxrs/utils/JAXRSUtils.java     |   6 +-
 .../apache/cxf/jaxrs/utils/ReaderInputStream.java  | 296 +++++++++++++++++++++
 .../apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java |  90 +++++++
 4 files changed, 396 insertions(+), 1 deletion(-)

diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java
index e3a3acf..82f183a 100644
--- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java
+++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java
@@ -58,6 +58,7 @@ import org.apache.cxf.jaxrs.provider.ProviderFactory;
 import org.apache.cxf.jaxrs.utils.HttpUtils;
 import org.apache.cxf.jaxrs.utils.InjectionUtils;
 import org.apache.cxf.jaxrs.utils.JAXRSUtils;
+import org.apache.cxf.jaxrs.utils.ReaderInputStream;
 import org.apache.cxf.message.Message;
 import org.apache.cxf.message.MessageUtils;
 
@@ -342,6 +343,10 @@ public final class ResponseImpl extends Response {
             entityStreamAvailable = entityStream != null;
         } else if (entity instanceof InputStream) {
             entityStream = InputStream.class.cast(entity);
+        } else {
+            Message inMessage = getResponseMessage();
+            Reader reader = inMessage.getContent(Reader.class);
+            entityStream = InputStream.class.cast(new ReaderInputStream(reader));
         }
 
         // we need to check for readers even if no IS is set - the readers may still do it
diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
index 0ef6565..509f76d 100644
--- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
+++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
@@ -832,7 +832,11 @@ public final class JAXRSUtils {
                                            OperationResourceInfo ori)
         throws IOException, WebApplicationException {
         InputStream is = message.getContent(InputStream.class);
-
+        if (is == null) {
+            //may use the jms transport so check the Reader;
+            Reader reader = message.getContent(Reader.class);
+            is = new ReaderInputStream(reader);
+        }
         if (parameter.getType() == ParameterType.REQUEST_BODY) {
 
             if (parameterClass == AsyncResponse.class) {
diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java
new file mode 100644
index 0000000..c7b142e
--- /dev/null
+++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java
@@ -0,0 +1,296 @@
+/**
+ * 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.cxf.jaxrs.utils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
+
+/**
+ * {@link InputStream} implementation that reads a character stream from a {@link Reader}
+ * and transforms it to a byte stream using a specified charset encoding. The stream
+ * is transformed using a {@link CharsetEncoder} object, guaranteeing that all charset
+ * encodings supported by the JRE are handled correctly. In particular for charsets such as
+ * UTF-16, the implementation ensures that one and only one byte order marker
+ * is produced.
+ * <p>
+ * Since in general it is not possible to predict the number of characters to be read from the
+ * {@link Reader} to satisfy a read request on the {@link ReaderInputStream}, all reads from
+ * the {@link Reader} are buffered. There is therefore no well defined correlation
+ * between the current position of the {@link Reader} and that of the {@link ReaderInputStream}.
+ * This also implies that in general there is no need to wrap the underlying {@link Reader}
+ * in a {@link java.io.BufferedReader}.
+ * <p>
+ * {@link ReaderInputStream} implements the inverse transformation of {@link java.io.InputStreamReader};
+ * in the following example, reading from <tt>in2</tt> would return the same byte
+ * sequence as reading from <tt>in</tt> (provided that the initial byte sequence is legal
+ * with respect to the charset encoding):
+ * <pre>
+ * InputStream in = ...
+ * Charset cs = ...
+ * InputStreamReader reader = new InputStreamReader(in, cs);
+ * ReaderInputStream in2 = new ReaderInputStream(reader, cs);</pre>
+ * {@link ReaderInputStream} implements the same transformation as {@link java.io.OutputStreamWriter},
+ * except that the control flow is reversed: both classes transform a character stream
+ * into a byte stream, but {@link java.io.OutputStreamWriter} pushes data to the underlying stream,
+ * while {@link ReaderInputStream} pulls it from the underlying stream.
+ * <p>
+ * Note that while there are use cases where there is no alternative to using
+ * this class, very often the need to use this class is an indication of a flaw
+ * in the design of the code. This class is typically used in situations where an existing
+ * API only accepts an {@link InputStream}, but where the most natural way to produce the data
+ * is as a character stream, i.e. by providing a {@link Reader} instance. An example of a situation
+ * where this problem may appear is when implementing the {@link javax.activation.DataSource}
+ * interface from the Java Activation Framework.
+ * <p>
+ * Given the fact that the {@link Reader} class doesn't provide any way to predict whether the next
+ * read operation will block or not, it is not possible to provide a meaningful
+ * implementation of the {@link InputStream#available()} method. A call to this method
+ * will always return 0. Also, this class doesn't support {@link InputStream#mark(int)}.
+ * <p>
+ * Instances of {@link ReaderInputStream} are not thread safe.
+ * 
+ * @see org.apache.commons.io.output.WriterOutputStream
+ * 
+ * @since 2.0
+ */
+public class ReaderInputStream extends InputStream {
+    private static final int DEFAULT_BUFFER_SIZE = 1024;
+
+    private final Reader reader;
+    private final CharsetEncoder encoder;
+
+    /**
+     * CharBuffer used as input for the decoder. It should be reasonably
+     * large as we read data from the underlying Reader into this buffer.
+     */
+    private final CharBuffer encoderIn;
+
+    /**
+     * ByteBuffer used as output for the decoder. This buffer can be small
+     * as it is only used to transfer data from the decoder to the
+     * buffer provided by the caller.
+     */
+    private final ByteBuffer encoderOut;
+
+    private CoderResult lastCoderResult;
+    private boolean endOfInput;
+
+    /**
+     * Construct a new {@link ReaderInputStream}.
+     * 
+     * @param reader the target {@link Reader}
+     * @param encoder the charset encoder
+     * @since 2.1
+     */
+    public ReaderInputStream(Reader reader, CharsetEncoder encoder) {
+        this(reader, encoder, DEFAULT_BUFFER_SIZE);
+    }
+
+    /**
+     * Construct a new {@link ReaderInputStream}.
+     * 
+     * @param reader the target {@link Reader}
+     * @param encoder the charset encoder
+     * @param bufferSize the size of the input buffer in number of characters
+     * @since 2.1
+     */
+    public ReaderInputStream(Reader reader, CharsetEncoder encoder, int bufferSize) {
+        this.reader = reader;
+        this.encoder = encoder;
+        this.encoderIn = CharBuffer.allocate(bufferSize);
+        this.encoderIn.flip();
+        this.encoderOut = ByteBuffer.allocate(128);
+        this.encoderOut.flip();
+    }
+
+    /**
+     * Construct a new {@link ReaderInputStream}.
+     * 
+     * @param reader the target {@link Reader}
+     * @param charset the charset encoding
+     * @param bufferSize the size of the input buffer in number of characters
+     */
+    public ReaderInputStream(Reader reader, Charset charset, int bufferSize) {
+        this(reader,
+             charset.newEncoder()
+                    .onMalformedInput(CodingErrorAction.REPLACE)
+                    .onUnmappableCharacter(CodingErrorAction.REPLACE),
+             bufferSize);
+    }
+
+    /**
+     * Construct a new {@link ReaderInputStream} with a default input buffer size of
+     * 1024 characters.
+     * 
+     * @param reader the target {@link Reader}
+     * @param charset the charset encoding
+     */
+    public ReaderInputStream(Reader reader, Charset charset) {
+        this(reader, charset, DEFAULT_BUFFER_SIZE);
+    }
+
+    /**
+     * Construct a new {@link ReaderInputStream}.
+     * 
+     * @param reader the target {@link Reader}
+     * @param charsetName the name of the charset encoding
+     * @param bufferSize the size of the input buffer in number of characters
+     */
+    public ReaderInputStream(Reader reader, String charsetName, int bufferSize) {
+        this(reader, Charset.forName(charsetName), bufferSize);
+    }
+
+    /**
+     * Construct a new {@link ReaderInputStream} with a default input buffer size of
+     * 1024 characters.
+     * 
+     * @param reader the target {@link Reader}
+     * @param charsetName the name of the charset encoding
+     */
+    public ReaderInputStream(Reader reader, String charsetName) {
+        this(reader, charsetName, DEFAULT_BUFFER_SIZE);
+    }
+
+    /**
+     * Construct a new {@link ReaderInputStream} that uses the default character encoding
+     * with a default input buffer size of 1024 characters.
+     * 
+     * @param reader the target {@link Reader}
+     */
+    public ReaderInputStream(Reader reader) {
+        this(reader, Charset.defaultCharset());
+    }
+
+    /**
+     * Fills the internal char buffer from the reader.
+     * 
+     * @throws IOException
+     *             If an I/O error occurs
+     */
+    private void fillBuffer() throws IOException {
+        if (!endOfInput && (lastCoderResult == null || lastCoderResult.isUnderflow())) {
+            encoderIn.compact();
+            int position = encoderIn.position();
+            // We don't use Reader#read(CharBuffer) here because it is more efficient
+            // to write directly to the underlying char array (the default implementation
+            // copies data to a temporary char array).
+            int c = reader.read(encoderIn.array(), position, encoderIn.remaining());
+            if (c == -1) {
+                endOfInput = true;
+            } else {
+                encoderIn.position(position + c);
+            }
+            encoderIn.flip();
+        }
+        encoderOut.compact();
+        lastCoderResult = encoder.encode(encoderIn, encoderOut, endOfInput);
+        encoderOut.flip();
+    }
+    
+    /**
+     * Read the specified number of bytes into an array.
+     * 
+     * @param b the byte array to read into
+     * @param off the offset to start reading bytes into
+     * @param len the number of bytes to read
+     * @return the number of bytes read or <code>-1</code>
+     *         if the end of the stream has been reached
+     * @throws IOException if an I/O error occurs
+     */
+    @Override
+    public int read(byte[] b, int off, int len) throws IOException {
+        if (b == null) {
+            throw new NullPointerException("Byte array must not be null");
+        }
+        if (len < 0 || off < 0 || (off + len) > b.length) {
+            throw new IndexOutOfBoundsException("Array Size=" + b.length 
+                    + ", offset=" + off + ", length=" + len);
+        }
+        int read = 0;
+        if (len == 0) {
+            return 0; // Always return 0 if len == 0
+        }
+        while (len > 0) {
+            if (encoderOut.hasRemaining()) {
+                int c = Math.min(encoderOut.remaining(), len);
+                encoderOut.get(b, off, c);
+                off += c;
+                len -= c;
+                read += c;
+            } else {
+                fillBuffer();
+                if (endOfInput && !encoderOut.hasRemaining()) {
+                    break;
+                }
+            }
+        }
+        return read == 0 && endOfInput ? -1 : read;
+    }
+
+    /**
+     * Read the specified number of bytes into an array.
+     * 
+     * @param b the byte array to read into
+     * @return the number of bytes read or <code>-1</code>
+     *         if the end of the stream has been reached
+     * @throws IOException if an I/O error occurs
+     */
+    @Override
+    public int read(byte[] b) throws IOException {
+        return read(b, 0, b.length);
+    }
+
+    /**
+     * Read a single byte.
+     *
+     * @return either the byte read or <code>-1</code> if the end of the stream
+     *         has been reached
+     * @throws IOException if an I/O error occurs
+     */
+    @Override
+    public int read() throws IOException {
+        for (;;) {
+            if (encoderOut.hasRemaining()) {
+                return encoderOut.get() & 0xFF;
+            } else {
+                fillBuffer();
+                if (endOfInput && !encoderOut.hasRemaining()) {
+                    return -1;
+                }
+            }
+        }
+    }
+
+    /**
+     * Close the stream. This method will cause the underlying {@link Reader}
+     * to be closed.
+     * @throws IOException if an I/O error occurs
+     */
+    @Override
+    public void close() throws IOException {
+        reader.close();
+    }
+}
diff --git a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java
index f77b5ed..b957f36 100644
--- a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java
+++ b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java
@@ -96,6 +96,25 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
         assertEquals("Get a wrong response code.", 200, client.getResponse().getStatus());
         assertEquals("Get a wrong book id.", 123, book.getId());
     }
+    
+    @Test
+    public void testGetBookFromWebClientWithTextJMSMessage() throws Exception {
+        // setup the the client
+        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
+             + "?replyToName=dynamicQueues/test.jmstransport.response"
+             + "&jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory"
+             + "&jndiURL=tcp://localhost:" + JMS_PORT
+             + "&messageType=text";
+
+        WebClient client = WebClient.create(endpointAddressUrlEncoded);
+        WebClient.getConfig(client).getInInterceptors().add(new LoggingInInterceptor());
+        WebClient.getConfig(client).getRequestContext()
+            .put(org.apache.cxf.message.Message.REQUEST_URI, "/bookstore/books/123");
+
+        Book book = client.get(Book.class);
+        assertEquals("Get a wrong response code.", 200, client.getResponse().getStatus());
+        assertEquals("Get a wrong book id.", 123, book.getId());
+    }
 
     @Test
     public void testPutBookOneWayWithWebClient() throws Exception {
@@ -130,6 +149,7 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
         }
     }
 
+       
     @Test
     public void testGetBookFromWebClientWithPath() throws Exception {
         // setup the the client
@@ -148,6 +168,25 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
     }
 
     @Test
+    public void testGetBookFromWebClientWithPathWithTextJMSMessage() throws Exception {
+        // setup the the client
+        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
+             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory"
+             + "&replyToName=dynamicQueues/test.jmstransport.response"
+             + "&jndiURL=tcp://localhost:" + JMS_PORT
+             + "&jndiConnectionFactoryName=ConnectionFactory"
+             + "&messageType=text";
+
+        WebClient client = WebClient.create(endpointAddressUrlEncoded);
+        client.path("bookstore").path("books").path("123");
+
+        Book book = client.get(Book.class);
+        assertEquals("Get a wrong response code.", 200, client.getResponse().getStatus());
+        assertEquals("Get a wrong book id.", 123, book.getId());
+    }
+
+    
+    @Test
     public void testGetBookFromProxyClient() throws Exception {
         // setup the the client
         String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
@@ -161,6 +200,22 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
         assertEquals("Get a wrong response code.", 200, WebClient.client(client).getResponse().getStatus());
         assertEquals("Get a wrong book id.", 123, book.getId());
     }
+    
+    @Test
+    public void testGetBookFromProxyClientWithTextJMSMessage() throws Exception {
+        // setup the the client
+        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
+             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory"
+             + "&replyToName=dynamicQueues/test.jmstransport.response"
+             + "&jndiURL=tcp://localhost:" + JMS_PORT
+             + "&jndiConnectionFactoryName=ConnectionFactory" 
+             + "&messageType=text";
+
+        JMSBookStore client = JAXRSClientFactory.create(endpointAddressUrlEncoded, JMSBookStore.class);
+        Book book = client.getBook("123");
+        assertEquals("Get a wrong response code.", 200, WebClient.client(client).getResponse().getStatus());
+        assertEquals("Get a wrong book id.", 123, book.getId());
+    }
 
     @Test
     public void testGetBookFromSubresourceProxyClient() throws Exception {
@@ -177,6 +232,23 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
         assertEquals("Get a wrong response code.", 200, WebClient.client(bookProxy).getResponse().getStatus());
         assertEquals("Get a wrong book id.", 123, book.getId());
     }
+    
+    @Test
+    public void testGetBookFromSubresourceProxyClientWithTextJMSMessage() throws Exception {
+        // setup the the client
+        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
+             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory"
+             + "&replyToName=dynamicQueues/test.jmstransport.response"
+             + "&jndiURL=tcp://localhost:" + JMS_PORT
+             + "&jndiConnectionFactoryName=ConnectionFactory"
+             + "&messageType=text";
+
+        JMSBookStore client = JAXRSClientFactory.create(endpointAddressUrlEncoded, JMSBookStore.class);
+        Book bookProxy = client.getBookSubResource("123");
+        Book book = bookProxy.retrieveState();
+        assertEquals("Get a wrong response code.", 200, WebClient.client(bookProxy).getResponse().getStatus());
+        assertEquals("Get a wrong book id.", 123, book.getId());
+    }
 
     @Test
     public void testGetBookFromProxyClientWithQuery() throws Exception {
@@ -193,6 +265,24 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
         assertEquals("Get a wrong book id.", 123, book.getId());
     }
 
+    
+    @Test
+    public void testGetBookFromProxyClientWithQueryWithTextJMSMessage() throws Exception {
+        // setup the the client
+        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
+             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory"
+             + "&replyToName=dynamicQueues/test.jmstransport.response"
+             + "&jndiURL=tcp://localhost:" + JMS_PORT
+             + "&jndiConnectionFactoryName=ConnectionFactory"
+             + "&messageType=text";
+
+        JMSBookStore client = JAXRSClientFactory.create(endpointAddressUrlEncoded, JMSBookStore.class);
+        Book book = client.getBookByURLQuery(new String[] {"1", "2", "3"});
+        assertEquals("Get a wrong response code.", 200, WebClient.client(client).getResponse().getStatus());
+        assertEquals("Get a wrong book id.", 123, book.getId());
+    }
+
+    
     @Test
     public void testGetBook() throws Exception {
         Context ctx = getContext();

-- 
To stop receiving notification emails like this one, please contact
['"commits@cxf.apache.org" <co...@cxf.apache.org>'].

Re: [cxf] branch master updated: [CXF-7532]REST on JMS transport can't handle the request message with text messageType

Posted by ffang <fr...@gmail.com>.
Sure, will do!
-------------
Freeman(Yue) Fang

Red Hat, Inc. 
FuseSource is now part of Red Hat



> On Oct 19, 2017, at 5:05 PM, Sergey Beryozkin <sb...@gmail.com> wrote:
> 
> Hi Freeman
> 
> Reader may likely need to be set too for SOAP, and I recall checking Reader somewhere else too (will double check now), but I thought may be the transport can set both Reader and InputStream, hmm, that is an extra instance though, may be lets start with moving ReaderInputStream to the core first ?
> 
> Sergey
> On 19/10/17 09:59, ffang wrote:
>> Yeah, set ReaderInputStream instead of Reader for the JMS transport is a better solution. If no objection I will go this way.
>> -------------
>> Freeman(Yue) Fang
>> Red Hat, Inc.
>> FuseSource is now part of Red Hat
>>> On Oct 19, 2017, at 4:50 PM, Sergey Beryozkin <sberyozkin@gmail.com <ma...@gmail.com>> wrote:
>>> 
>>> The other thing is, should JMS transport itself set ReaderInputStream as InputStream on the message, as opposed to patching the JAX-RS code in various places ?
>>> 
>>> Sergey
>>> On 19/10/17 09:44, Sergey Beryozkin wrote:
>>>> Hi Freeman
>>>> Can you move ReaderInputStream to the CXF core where CachedInputStream and other IO utility code is located ?
>>>> Please also update the code which gets Reader.class and check it for null first, and also drop a comment re the JMS transport, checking the reader can make sense in all the cases...
>>>> Thanks, Sergey
>>>> On 19/10/17 04:16, ffang@apache.org <ma...@apache.org> wrote:
>>>>> This is an automated email from the ASF dual-hosted git repository.
>>>>> 
>>>>> ffang pushed a commit to branch master
>>>>> in repository https://gitbox.apache.org/repos/asf/cxf.git
>>>>> 
>>>>> 
>>>>> The following commit(s) were added to refs/heads/master by this push:
>>>>>       new 1d66475  [CXF-7532]REST on JMS transport can't handle the request message with text messageType
>>>>> 1d66475 is described below
>>>>> 
>>>>> commit 1d66475b4dea2a0031dcb1765e8e9fd08430ec9b
>>>>> Author: Freeman Fang <freeman.fang@gmail.com <ma...@gmail.com>>
>>>>> AuthorDate: Thu Oct 19 11:15:50 2017 +0800
>>>>> 
>>>>>      [CXF-7532]REST on JMS transport can't handle the request message with text messageType
>>>>> ---
>>>>>   .../org/apache/cxf/jaxrs/impl/ResponseImpl.java    |   5 +
>>>>>   .../org/apache/cxf/jaxrs/utils/JAXRSUtils.java     |   6 +-
>>>>>   .../apache/cxf/jaxrs/utils/ReaderInputStream.java  | 296 +++++++++++++++++++++
>>>>>   .../apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java |  90 +++++++
>>>>>   4 files changed, 396 insertions(+), 1 deletion(-)
>>>>> 
>>>>> diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java 
>>>>> index e3a3acf..82f183a 100644
>>>>> --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java 
>>>>> +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java 
>>>>> @@ -58,6 +58,7 @@ import org.apache.cxf.jaxrs.provider.ProviderFactory;
>>>>>   import org.apache.cxf.jaxrs.utils.HttpUtils;
>>>>>   import org.apache.cxf.jaxrs.utils.InjectionUtils;
>>>>>   import org.apache.cxf.jaxrs.utils.JAXRSUtils;
>>>>> +import org.apache.cxf.jaxrs.utils.ReaderInputStream;
>>>>>   import org.apache.cxf.message.Message;
>>>>>   import org.apache.cxf.message.MessageUtils;
>>>>> @@ -342,6 +343,10 @@ public final class ResponseImpl extends Response {
>>>>>               entityStreamAvailable = entityStream != null;
>>>>>           } else if (entity instanceof InputStream) {
>>>>>               entityStream = InputStream.class.cast(entity);
>>>>> +        } else {
>>>>> +            Message inMessage = getResponseMessage();
>>>>> +            Reader reader = inMessage.getContent(Reader.class);
>>>>> +            entityStream = InputStream.class.cast(new ReaderInputStream(reader));
>>>>>           }
>>>>>           // we need to check for readers even if no IS is set - the readers may still do it
>>>>> diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java 
>>>>> index 0ef6565..509f76d 100644
>>>>> --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java 
>>>>> +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java 
>>>>> @@ -832,7 +832,11 @@ public final class JAXRSUtils {
>>>>>                                              OperationResourceInfo ori)
>>>>>           throws IOException, WebApplicationException {
>>>>>           InputStream is = message.getContent(InputStream.class);
>>>>> -
>>>>> +        if (is == null) {
>>>>> +            //may use the jms transport so check the Reader;
>>>>> +            Reader reader = message.getContent(Reader.class);
>>>>> +            is = new ReaderInputStream(reader);
>>>>> +        }
>>>>>           if (parameter.getType() == ParameterType.REQUEST_BODY) {
>>>>>               if (parameterClass == AsyncResponse.class) {
>>>>> diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java 
>>>>> new file mode 100644
>>>>> index 0000000..c7b142e
>>>>> --- /dev/null
>>>>> +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java 
>>>>> @@ -0,0 +1,296 @@
>>>>> +/**
>>>>> + * 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.cxf.jaxrs.utils;
>>>>> +
>>>>> +import java.io.IOException;
>>>>> +import java.io.InputStream;
>>>>> +import java.io.Reader;
>>>>> +import java.nio.ByteBuffer;
>>>>> +import java.nio.CharBuffer;
>>>>> +import java.nio.charset.Charset;
>>>>> +import java.nio.charset.CharsetEncoder;
>>>>> +import java.nio.charset.CoderResult;
>>>>> +import java.nio.charset.CodingErrorAction;
>>>>> +
>>>>> +/**
>>>>> + * {@link InputStream} implementation that reads a character stream from a {@link Reader}
>>>>> + * and transforms it to a byte stream using a specified charset encoding. The stream
>>>>> + * is transformed using a {@link CharsetEncoder} object, guaranteeing that all charset
>>>>> + * encodings supported by the JRE are handled correctly. In particular for charsets such as
>>>>> + * UTF-16, the implementation ensures that one and only one byte order marker
>>>>> + * is produced.
>>>>> + * <p>
>>>>> + * Since in general it is not possible to predict the number of characters to be read from the
>>>>> + * {@link Reader} to satisfy a read request on the {@link ReaderInputStream}, all reads from
>>>>> + * the {@link Reader} are buffered. There is therefore no well defined correlation
>>>>> + * between the current position of the {@link Reader} and that of the {@link ReaderInputStream}.
>>>>> + * This also implies that in general there is no need to wrap the underlying {@link Reader}
>>>>> + * in a {@link java.io.BufferedReader}.
>>>>> + * <p>
>>>>> + * {@link ReaderInputStream} implements the inverse transformation of {@link java.io.InputStreamReader};
>>>>> + * in the following example, reading from <tt>in2</tt> would return the same byte
>>>>> + * sequence as reading from <tt>in</tt> (provided that the initial byte sequence is legal
>>>>> + * with respect to the charset encoding):
>>>>> + * <pre>
>>>>> + * InputStream in = ...
>>>>> + * Charset cs = ...
>>>>> + * InputStreamReader reader = new InputStreamReader(in, cs);
>>>>> + * ReaderInputStream in2 = new ReaderInputStream(reader, cs);</pre>
>>>>> + * {@link ReaderInputStream} implements the same transformation as {@link java.io.OutputStreamWriter},
>>>>> + * except that the control flow is reversed: both classes transform a character stream
>>>>> + * into a byte stream, but {@link java.io.OutputStreamWriter} pushes data to the underlying stream,
>>>>> + * while {@link ReaderInputStream} pulls it from the underlying stream.
>>>>> + * <p>
>>>>> + * Note that while there are use cases where there is no alternative to using
>>>>> + * this class, very often the need to use this class is an indication of a flaw
>>>>> + * in the design of the code. This class is typically used in situations where an existing
>>>>> + * API only accepts an {@link InputStream}, but where the most natural way to produce the data
>>>>> + * is as a character stream, i.e. by providing a {@link Reader} instance. An example of a situation
>>>>> + * where this problem may appear is when implementing the {@link javax.activation.DataSource}
>>>>> + * interface from the Java Activation Framework.
>>>>> + * <p>
>>>>> + * Given the fact that the {@link Reader} class doesn't provide any way to predict whether the next
>>>>> + * read operation will block or not, it is not possible to provide a meaningful
>>>>> + * implementation of the {@link InputStream#available()} method. A call to this method
>>>>> + * will always return 0. Also, this class doesn't support {@link InputStream#mark(int)}.
>>>>> + * <p>
>>>>> + * Instances of {@link ReaderInputStream} are not thread safe.
>>>>> + *
>>>>> + * @see org.apache.commons.io.output.WriterOutputStream
>>>>> + *
>>>>> + * @since 2.0
>>>>> + */
>>>>> +public class ReaderInputStream extends InputStream {
>>>>> +    private static final int DEFAULT_BUFFER_SIZE = 1024;
>>>>> +
>>>>> +    private final Reader reader;
>>>>> +    private final CharsetEncoder encoder;
>>>>> +
>>>>> +    /**
>>>>> +     * CharBuffer used as input for the decoder. It should be reasonably
>>>>> +     * large as we read data from the underlying Reader into this buffer.
>>>>> +     */
>>>>> +    private final CharBuffer encoderIn;
>>>>> +
>>>>> +    /**
>>>>> +     * ByteBuffer used as output for the decoder. This buffer can be small
>>>>> +     * as it is only used to transfer data from the decoder to the
>>>>> +     * buffer provided by the caller.
>>>>> +     */
>>>>> +    private final ByteBuffer encoderOut;
>>>>> +
>>>>> +    private CoderResult lastCoderResult;
>>>>> +    private boolean endOfInput;
>>>>> +
>>>>> +    /**
>>>>> +     * Construct a new {@link ReaderInputStream}.
>>>>> +     *
>>>>> +     * @param reader the target {@link Reader}
>>>>> +     * @param encoder the charset encoder
>>>>> +     * @since 2.1
>>>>> +     */
>>>>> +    public ReaderInputStream(Reader reader, CharsetEncoder encoder) {
>>>>> +        this(reader, encoder, DEFAULT_BUFFER_SIZE);
>>>>> +    }
>>>>> +
>>>>> +    /**
>>>>> +     * Construct a new {@link ReaderInputStream}.
>>>>> +     *
>>>>> +     * @param reader the target {@link Reader}
>>>>> +     * @param encoder the charset encoder
>>>>> +     * @param bufferSize the size of the input buffer in number of characters
>>>>> +     * @since 2.1
>>>>> +     */
>>>>> +    public ReaderInputStream(Reader reader, CharsetEncoder encoder, int bufferSize) {
>>>>> +        this.reader = reader;
>>>>> +        this.encoder = encoder;
>>>>> +        this.encoderIn = CharBuffer.allocate(bufferSize);
>>>>> +        this.encoderIn.flip();
>>>>> +        this.encoderOut = ByteBuffer.allocate(128);
>>>>> +        this.encoderOut.flip();
>>>>> +    }
>>>>> +
>>>>> +    /**
>>>>> +     * Construct a new {@link ReaderInputStream}.
>>>>> +     *
>>>>> +     * @param reader the target {@link Reader}
>>>>> +     * @param charset the charset encoding
>>>>> +     * @param bufferSize the size of the input buffer in number of characters
>>>>> +     */
>>>>> +    public ReaderInputStream(Reader reader, Charset charset, int bufferSize) {
>>>>> +        this(reader,
>>>>> +             charset.newEncoder()
>>>>> +                    .onMalformedInput(CodingErrorAction.REPLACE)
>>>>> +                    .onUnmappableCharacter(CodingErrorAction.REPLACE),
>>>>> +             bufferSize);
>>>>> +    }
>>>>> +
>>>>> +    /**
>>>>> +     * Construct a new {@link ReaderInputStream} with a default input buffer size of
>>>>> +     * 1024 characters.
>>>>> +     *
>>>>> +     * @param reader the target {@link Reader}
>>>>> +     * @param charset the charset encoding
>>>>> +     */
>>>>> +    public ReaderInputStream(Reader reader, Charset charset) {
>>>>> +        this(reader, charset, DEFAULT_BUFFER_SIZE);
>>>>> +    }
>>>>> +
>>>>> +    /**
>>>>> +     * Construct a new {@link ReaderInputStream}.
>>>>> +     *
>>>>> +     * @param reader the target {@link Reader}
>>>>> +     * @param charsetName the name of the charset encoding
>>>>> +     * @param bufferSize the size of the input buffer in number of characters
>>>>> +     */
>>>>> +    public ReaderInputStream(Reader reader, String charsetName, int bufferSize) {
>>>>> +        this(reader, Charset.forName(charsetName), bufferSize);
>>>>> +    }
>>>>> +
>>>>> +    /**
>>>>> +     * Construct a new {@link ReaderInputStream} with a default input buffer size of
>>>>> +     * 1024 characters.
>>>>> +     *
>>>>> +     * @param reader the target {@link Reader}
>>>>> +     * @param charsetName the name of the charset encoding
>>>>> +     */
>>>>> +    public ReaderInputStream(Reader reader, String charsetName) {
>>>>> +        this(reader, charsetName, DEFAULT_BUFFER_SIZE);
>>>>> +    }
>>>>> +
>>>>> +    /**
>>>>> +     * Construct a new {@link ReaderInputStream} that uses the default character encoding
>>>>> +     * with a default input buffer size of 1024 characters.
>>>>> +     *
>>>>> +     * @param reader the target {@link Reader}
>>>>> +     */
>>>>> +    public ReaderInputStream(Reader reader) {
>>>>> +        this(reader, Charset.defaultCharset());
>>>>> +    }
>>>>> +
>>>>> +    /**
>>>>> +     * Fills the internal char buffer from the reader.
>>>>> +     *
>>>>> +     * @throws IOException
>>>>> +     *             If an I/O error occurs
>>>>> +     */
>>>>> +    private void fillBuffer() throws IOException {
>>>>> +        if (!endOfInput && (lastCoderResult == null || lastCoderResult.isUnderflow())) {
>>>>> +            encoderIn.compact();
>>>>> +            int position = encoderIn.position();
>>>>> +            // We don't use Reader#read(CharBuffer) here because it is more efficient
>>>>> +            // to write directly to the underlying char array (the default implementation
>>>>> +            // copies data to a temporary char array).
>>>>> +            int c = reader.read(encoderIn.array(), position, encoderIn.remaining());
>>>>> +            if (c == -1) {
>>>>> +                endOfInput = true;
>>>>> +            } else {
>>>>> +                encoderIn.position(position + c);
>>>>> +            }
>>>>> +            encoderIn.flip();
>>>>> +        }
>>>>> +        encoderOut.compact();
>>>>> +        lastCoderResult = encoder.encode(encoderIn, encoderOut, endOfInput);
>>>>> +        encoderOut.flip();
>>>>> +    }
>>>>> +
>>>>> +    /**
>>>>> +     * Read the specified number of bytes into an array.
>>>>> +     *
>>>>> +     * @param b the byte array to read into
>>>>> +     * @param off the offset to start reading bytes into
>>>>> +     * @param len the number of bytes to read
>>>>> +     * @return the number of bytes read or <code>-1</code>
>>>>> +     *         if the end of the stream has been reached
>>>>> +     * @throws IOException if an I/O error occurs
>>>>> +     */
>>>>> +    @Override
>>>>> +    public int read(byte[] b, int off, int len) throws IOException {
>>>>> +        if (b == null) {
>>>>> +            throw new NullPointerException("Byte array must not be null");
>>>>> +        }
>>>>> +        if (len < 0 || off < 0 || (off + len) > b.length) {
>>>>> +            throw new IndexOutOfBoundsException("Array Size=" + b.length
>>>>> +                    + ", offset=" + off + ", length=" + len);
>>>>> +        }
>>>>> +        int read = 0;
>>>>> +        if (len == 0) {
>>>>> +            return 0; // Always return 0 if len == 0
>>>>> +        }
>>>>> +        while (len > 0) {
>>>>> +            if (encoderOut.hasRemaining()) {
>>>>> +                int c = Math.min(encoderOut.remaining(), len);
>>>>> +                encoderOut.get(b, off, c);
>>>>> +                off += c;
>>>>> +                len -= c;
>>>>> +                read += c;
>>>>> +            } else {
>>>>> +                fillBuffer();
>>>>> +                if (endOfInput && !encoderOut.hasRemaining()) {
>>>>> +                    break;
>>>>> +                }
>>>>> +            }
>>>>> +        }
>>>>> +        return read == 0 && endOfInput ? -1 : read;
>>>>> +    }
>>>>> +
>>>>> +    /**
>>>>> +     * Read the specified number of bytes into an array.
>>>>> +     *
>>>>> +     * @param b the byte array to read into
>>>>> +     * @return the number of bytes read or <code>-1</code>
>>>>> +     *         if the end of the stream has been reached
>>>>> +     * @throws IOException if an I/O error occurs
>>>>> +     */
>>>>> +    @Override
>>>>> +    public int read(byte[] b) throws IOException {
>>>>> +        return read(b, 0, b.length);
>>>>> +    }
>>>>> +
>>>>> +    /**
>>>>> +     * Read a single byte.
>>>>> +     *
>>>>> +     * @return either the byte read or <code>-1</code> if the end of the stream
>>>>> +     *         has been reached
>>>>> +     * @throws IOException if an I/O error occurs
>>>>> +     */
>>>>> +    @Override
>>>>> +    public int read() throws IOException {
>>>>> +        for (;;) {
>>>>> +            if (encoderOut.hasRemaining()) {
>>>>> +                return encoderOut.get() & 0xFF;
>>>>> +            } else {
>>>>> +                fillBuffer();
>>>>> +                if (endOfInput && !encoderOut.hasRemaining()) {
>>>>> +                    return -1;
>>>>> +                }
>>>>> +            }
>>>>> +        }
>>>>> +    }
>>>>> +
>>>>> +    /**
>>>>> +     * Close the stream. This method will cause the underlying {@link Reader}
>>>>> +     * to be closed.
>>>>> +     * @throws IOException if an I/O error occurs
>>>>> +     */
>>>>> +    @Override
>>>>> +    public void close() throws IOException {
>>>>> +        reader.close();
>>>>> +    }
>>>>> +}
>>>>> diff --git a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java 
>>>>> index f77b5ed..b957f36 100644
>>>>> --- a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java 
>>>>> +++ b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java 
>>>>> @@ -96,6 +96,25 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>>>           assertEquals("Get a wrong response code.", 200, client.getResponse().getStatus());
>>>>>           assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>       }
>>>>> +
>>>>> +    @Test
>>>>> +    public void testGetBookFromWebClientWithTextJMSMessage() throws Exception {
>>>>> +        // setup the the client
>>>>> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>> +             + "?replyToName=dynamicQueues/test.jmstransport.response"
>>>>> +             + "&jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>> +             + "&messageType=text";
>>>>> +
>>>>> +        WebClient client = WebClient.create(endpointAddressUrlEncoded);
>>>>> +        WebClient.getConfig(client).getInInterceptors().add(new LoggingInInterceptor());
>>>>> +        WebClient.getConfig(client).getRequestContext()
>>>>> +            .put(org.apache.cxf.message.Message.REQUEST_URI, "/bookstore/books/123");
>>>>> +
>>>>> +        Book book = client.get(Book.class);
>>>>> +        assertEquals("Get a wrong response code.", 200, client.getResponse().getStatus());
>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>> +    }
>>>>>       @Test
>>>>>       public void testPutBookOneWayWithWebClient() throws Exception {
>>>>> @@ -130,6 +149,7 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>>>           }
>>>>>       }
>>>>> +
>>>>>       @Test
>>>>>       public void testGetBookFromWebClientWithPath() throws Exception {
>>>>>           // setup the the client
>>>>> @@ -148,6 +168,25 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>>>       }
>>>>>       @Test
>>>>> +    public void testGetBookFromWebClientWithPathWithTextJMSMessage() throws Exception {
>>>>> +        // setup the the client
>>>>> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>> +             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>>>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>>> +             + "&messageType=text";
>>>>> +
>>>>> +        WebClient client = WebClient.create(endpointAddressUrlEncoded);
>>>>> +        client.path("bookstore").path("books").path("123");
>>>>> +
>>>>> +        Book book = client.get(Book.class);
>>>>> +        assertEquals("Get a wrong response code.", 200, client.getResponse().getStatus());
>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>> +    }
>>>>> +
>>>>> +
>>>>> +    @Test
>>>>>       public void testGetBookFromProxyClient() throws Exception {
>>>>>           // setup the the client
>>>>>           String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>> @@ -161,6 +200,22 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>>>           assertEquals("Get a wrong response code.", 200, WebClient.client(client).getResponse().getStatus());
>>>>>           assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>       }
>>>>> +
>>>>> +    @Test
>>>>> +    public void testGetBookFromProxyClientWithTextJMSMessage() throws Exception {
>>>>> +        // setup the the client
>>>>> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>> +             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>>>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>>> +             + "&messageType=text";
>>>>> +
>>>>> +        JMSBookStore client = JAXRSClientFactory.create(endpointAddressUrlEncoded, JMSBookStore.class);
>>>>> +        Book book = client.getBook("123");
>>>>> +        assertEquals("Get a wrong response code.", 200, WebClient.client(client).getResponse().getStatus());
>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>> +    }
>>>>>       @Test
>>>>>       public void testGetBookFromSubresourceProxyClient() throws Exception {
>>>>> @@ -177,6 +232,23 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>>>           assertEquals("Get a wrong response code.", 200, WebClient.client(bookProxy).getResponse().getStatus());
>>>>>           assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>       }
>>>>> +
>>>>> +    @Test
>>>>> +    public void testGetBookFromSubresourceProxyClientWithTextJMSMessage() throws Exception {
>>>>> +        // setup the the client
>>>>> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>> +             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>>>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>>> +             + "&messageType=text";
>>>>> +
>>>>> +        JMSBookStore client = JAXRSClientFactory.create(endpointAddressUrlEncoded, JMSBookStore.class);
>>>>> +        Book bookProxy = client.getBookSubResource("123");
>>>>> +        Book book = bookProxy.retrieveState();
>>>>> +        assertEquals("Get a wrong response code.", 200, WebClient.client(bookProxy).getResponse().getStatus());
>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>> +    }
>>>>>       @Test
>>>>>       public void testGetBookFromProxyClientWithQuery() throws Exception {
>>>>> @@ -193,6 +265,24 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>>>           assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>       }
>>>>> +
>>>>> +    @Test
>>>>> +    public void testGetBookFromProxyClientWithQueryWithTextJMSMessage() throws Exception {
>>>>> +        // setup the the client
>>>>> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>> +             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>>>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>>> +             + "&messageType=text";
>>>>> +
>>>>> +        JMSBookStore client = JAXRSClientFactory.create(endpointAddressUrlEncoded, JMSBookStore.class);
>>>>> +        Book book = client.getBookByURLQuery(new String[] {"1", "2", "3"});
>>>>> +        assertEquals("Get a wrong response code.", 200, WebClient.client(client).getResponse().getStatus());
>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>> +    }
>>>>> +
>>>>> +
>>>>>       @Test
>>>>>       public void testGetBook() throws Exception {
>>>>>           Context ctx = getContext();
>>>>> 
>>> 


Re: [cxf] branch master updated: [CXF-7532]REST on JMS transport can't handle the request message with text messageType

Posted by Sergey Beryozkin <sb...@gmail.com>.
Thanks Freeman, it looks fine, yes, it's a bit tricky to make sure it 
works across all of JAX-RS providers, especially with custom MBRs 
expecting InputStream to be not null...

Sergey

On 20/10/17 03:08, Freeman Fang wrote:
> Hi Sergey,
> 
> Cleaned up accordingly.
> 
> Cheers
> -------------
> Freeman(Yue) Fang
> 
> Red Hat, Inc.
> FuseSource is now part of Red Hat
> 
> 
> 
>> On Oct 20, 2017, at 5:59 AM, Sergey Beryozkin <sberyozkin@gmail.com 
>> <ma...@gmail.com>> wrote:
>>
>> Some JAX-RS providers check Reader or XMLStreamReader if InputStream 
>> is null.
>> But at the moment, Freeman, please do a bit of cleanup as agreed, and 
>> then we can check if the use ReaderInputStream can be avoided at all.
>>
>> Sergey
>>
>> On 19/10/17 19:47, Daniel Kulp wrote:
>>> If using the TEXT stuff for JMS, it should remain as a Reader on the 
>>> message if at all possible.  Converting from Reader to InputStream is 
>>> not a trivial operation and does enact a performance penalty.   All 
>>> of the SOAP pathways for JMS should be OK for use of a Reader so that 
>>> we can avoid the entire String->UTF-8 bytes->chars round tripping.
>>> Dan
>>> On Oct 19, 2017, at 5:21 AM, ffang <freeman.fang@gmail.com 
>>> <ma...@gmail.com>> wrote:
>>>>
>>>> I modified the JMS transport part and set ReaderInputStream instead 
>>>> of Reader, running all tests now, will see what I can get.
>>>>
>>>> If I run into any test failure, I will keep things as is, just clean up.
>>>> -------------
>>>> Freeman(Yue) Fang
>>>>
>>>> Red Hat, Inc.
>>>> FuseSource is now part of Red Hat
>>>>
>>>>
>>>>
>>>>> On Oct 19, 2017, at 5:16 PM, Sergey Beryozkin <sberyozkin@gmail.com 
>>>>> <ma...@gmail.com>> wrote:
>>>>>
>>>>> If we assume that checking Reader can be useful in the general 
>>>>> sense, then may be lets keep things as is, with the minor cleanup 
>>>>> only...
>>>>>
>>>>> Sergey
>>>>> On 19/10/17 10:05, Sergey Beryozkin wrote:
>>>>>> Hi Freeman
>>>>>> Reader may likely need to be set too for SOAP, and I recall 
>>>>>> checking Reader somewhere else too (will double check now), but I 
>>>>>> thought may be the transport can set both Reader and InputStream, 
>>>>>> hmm, that is an extra instance though, may be lets start with 
>>>>>> moving ReaderInputStream to the core first ?
>>>>>> Sergey
>>>>>> On 19/10/17 09:59, ffang wrote:
>>>>>>> Yeah, set ReaderInputStream instead of Reader for the JMS 
>>>>>>> transport is a better solution. If no objection I will go this way.
>>>>>>> -------------
>>>>>>> Freeman(Yue) Fang
>>>>>>>
>>>>>>> Red Hat, Inc.
>>>>>>> FuseSource is now part of Red Hat
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>>> On Oct 19, 2017, at 4:50 PM, Sergey Beryozkin 
>>>>>>>> <sberyozkin@gmail.com <ma...@gmail.com> 
>>>>>>>> <ma...@gmail.com>> wrote:
>>>>>>>>
>>>>>>>> The other thing is, should JMS transport itself set 
>>>>>>>> ReaderInputStream as InputStream on the message, as opposed to 
>>>>>>>> patching the JAX-RS code in various places ?
>>>>>>>>
>>>>>>>> Sergey
>>>>>>>> On 19/10/17 09:44, Sergey Beryozkin wrote:
>>>>>>>>> Hi Freeman
>>>>>>>>> Can you move ReaderInputStream to the CXF core where 
>>>>>>>>> CachedInputStream and other IO utility code is located ?
>>>>>>>>> Please also update the code which gets Reader.class and check 
>>>>>>>>> it for null first, and also drop a comment re the JMS 
>>>>>>>>> transport, checking the reader can make sense in all the cases...
>>>>>>>>> Thanks, Sergey
>>>>>>>>> On 19/10/17 04:16, ffang@apache.org <ma...@apache.org> 
>>>>>>>>> <ma...@apache.org> wrote:
>>>>>>>>>> This is an automated email from the ASF dual-hosted git 
>>>>>>>>>> repository.
>>>>>>>>>>
>>>>>>>>>> ffang pushed a commit to branch master
>>>>>>>>>> in repository https://gitbox.apache.org/repos/asf/cxf.git
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> The following commit(s) were added to refs/heads/master by 
>>>>>>>>>> this push:
>>>>>>>>>>      new 1d66475  [CXF-7532]REST on JMS transport can't handle 
>>>>>>>>>> the request message with text messageType
>>>>>>>>>> 1d66475 is described below
>>>>>>>>>>
>>>>>>>>>> commit 1d66475b4dea2a0031dcb1765e8e9fd08430ec9b
>>>>>>>>>> Author: Freeman Fang <freeman.fang@gmail.com 
>>>>>>>>>> <ma...@gmail.com> <ma...@gmail.com>>
>>>>>>>>>> AuthorDate: Thu Oct 19 11:15:50 2017 +0800
>>>>>>>>>>
>>>>>>>>>>     [CXF-7532]REST on JMS transport can't handle the request 
>>>>>>>>>> message with text messageType
>>>>>>>>>> ---
>>>>>>>>>>  .../org/apache/cxf/jaxrs/impl/ResponseImpl.java    |   5 +
>>>>>>>>>>  .../org/apache/cxf/jaxrs/utils/JAXRSUtils.java     |   6 +-
>>>>>>>>>>  .../apache/cxf/jaxrs/utils/ReaderInputStream.java  | 296 
>>>>>>>>>> +++++++++++++++++++++
>>>>>>>>>>  .../apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java |  90 +++++++
>>>>>>>>>>  4 files changed, 396 insertions(+), 1 deletion(-)
>>>>>>>>>>
>>>>>>>>>> diff --git 
>>>>>>>>>> a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java 
>>>>>>>>>> b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java
>>>>>>>>>> index e3a3acf..82f183a 100644
>>>>>>>>>> --- 
>>>>>>>>>> a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java
>>>>>>>>>> +++ 
>>>>>>>>>> b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java
>>>>>>>>>> @@ -58,6 +58,7 @@ import 
>>>>>>>>>> org.apache.cxf.jaxrs.provider.ProviderFactory;
>>>>>>>>>>  import org.apache.cxf.jaxrs.utils.HttpUtils;
>>>>>>>>>>  import org.apache.cxf.jaxrs.utils.InjectionUtils;
>>>>>>>>>>  import org.apache.cxf.jaxrs.utils.JAXRSUtils;
>>>>>>>>>> +import org.apache.cxf.jaxrs.utils.ReaderInputStream;
>>>>>>>>>>  import org.apache.cxf.message.Message;
>>>>>>>>>>  import org.apache.cxf.message.MessageUtils;
>>>>>>>>>> @@ -342,6 +343,10 @@ public final class ResponseImpl extends 
>>>>>>>>>> Response {
>>>>>>>>>>              entityStreamAvailable = entityStream != null;
>>>>>>>>>>          } else if (entity instanceof InputStream) {
>>>>>>>>>>              entityStream = InputStream.class.cast(entity);
>>>>>>>>>> +        } else {
>>>>>>>>>> +            Message inMessage = getResponseMessage();
>>>>>>>>>> +            Reader reader = inMessage.getContent(Reader.class);
>>>>>>>>>> +            entityStream = InputStream.class.cast(new 
>>>>>>>>>> ReaderInputStream(reader));
>>>>>>>>>>          }
>>>>>>>>>>          // we need to check for readers even if no IS is set 
>>>>>>>>>> - the readers may still do it
>>>>>>>>>> diff --git 
>>>>>>>>>> a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java 
>>>>>>>>>> b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
>>>>>>>>>> index 0ef6565..509f76d 100644
>>>>>>>>>> --- 
>>>>>>>>>> a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
>>>>>>>>>> +++ 
>>>>>>>>>> b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
>>>>>>>>>> @@ -832,7 +832,11 @@ public final class JAXRSUtils {
>>>>>>>>>>                                             OperationResourceInfo 
>>>>>>>>>> ori)
>>>>>>>>>>          throws IOException, WebApplicationException {
>>>>>>>>>>          InputStream is = message.getContent(InputStream.class);
>>>>>>>>>> -
>>>>>>>>>> +        if (is == null) {
>>>>>>>>>> +            //may use the jms transport so check the Reader;
>>>>>>>>>> +            Reader reader = message.getContent(Reader.class);
>>>>>>>>>> +            is = new ReaderInputStream(reader);
>>>>>>>>>> +        }
>>>>>>>>>>          if (parameter.getType() == ParameterType.REQUEST_BODY) {
>>>>>>>>>>              if (parameterClass == AsyncResponse.class) {
>>>>>>>>>> diff --git 
>>>>>>>>>> a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java 
>>>>>>>>>> b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java
>>>>>>>>>> new file mode 100644
>>>>>>>>>> index 0000000..c7b142e
>>>>>>>>>> --- /dev/null
>>>>>>>>>> +++ 
>>>>>>>>>> b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java
>>>>>>>>>> @@ -0,0 +1,296 @@
>>>>>>>>>> +/**
>>>>>>>>>> + * 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.cxf.jaxrs.utils;
>>>>>>>>>> +
>>>>>>>>>> +import java.io.IOException;
>>>>>>>>>> +import java.io.InputStream;
>>>>>>>>>> +import java.io.Reader;
>>>>>>>>>> +import java.nio.ByteBuffer;
>>>>>>>>>> +import java.nio.CharBuffer;
>>>>>>>>>> +import java.nio.charset.Charset;
>>>>>>>>>> +import java.nio.charset.CharsetEncoder;
>>>>>>>>>> +import java.nio.charset.CoderResult;
>>>>>>>>>> +import java.nio.charset.CodingErrorAction;
>>>>>>>>>> +
>>>>>>>>>> +/**
>>>>>>>>>> + * {@link InputStream} implementation that reads a character 
>>>>>>>>>> stream from a {@link Reader}
>>>>>>>>>> + * and transforms it to a byte stream using a specified 
>>>>>>>>>> charset encoding. The stream
>>>>>>>>>> + * is transformed using a {@link CharsetEncoder} object, 
>>>>>>>>>> guaranteeing that all charset
>>>>>>>>>> + * encodings supported by the JRE are handled correctly. In 
>>>>>>>>>> particular for charsets such as
>>>>>>>>>> + * UTF-16, the implementation ensures that one and only one 
>>>>>>>>>> byte order marker
>>>>>>>>>> + * is produced.
>>>>>>>>>> + * <p>
>>>>>>>>>> + * Since in general it is not possible to predict the number 
>>>>>>>>>> of characters to be read from the
>>>>>>>>>> + * {@link Reader} to satisfy a read request on the {@link 
>>>>>>>>>> ReaderInputStream}, all reads from
>>>>>>>>>> + * the {@link Reader} are buffered. There is therefore no 
>>>>>>>>>> well defined correlation
>>>>>>>>>> + * between the current position of the {@link Reader} and 
>>>>>>>>>> that of the {@link ReaderInputStream}.
>>>>>>>>>> + * This also implies that in general there is no need to wrap 
>>>>>>>>>> the underlying {@link Reader}
>>>>>>>>>> + * in a {@link java.io.BufferedReader}.
>>>>>>>>>> + * <p>
>>>>>>>>>> + * {@link ReaderInputStream} implements the inverse 
>>>>>>>>>> transformation of {@link java.io.InputStreamReader};
>>>>>>>>>> + * in the following example, reading from <tt>in2</tt> would 
>>>>>>>>>> return the same byte
>>>>>>>>>> + * sequence as reading from <tt>in</tt> (provided that the 
>>>>>>>>>> initial byte sequence is legal
>>>>>>>>>> + * with respect to the charset encoding):
>>>>>>>>>> + * <pre>
>>>>>>>>>> + * InputStream in = ...
>>>>>>>>>> + * Charset cs = ...
>>>>>>>>>> + * InputStreamReader reader = new InputStreamReader(in, cs);
>>>>>>>>>> + * ReaderInputStream in2 = new ReaderInputStream(reader, 
>>>>>>>>>> cs);</pre>
>>>>>>>>>> + * {@link ReaderInputStream} implements the same 
>>>>>>>>>> transformation as {@link java.io.OutputStreamWriter},
>>>>>>>>>> + * except that the control flow is reversed: both classes 
>>>>>>>>>> transform a character stream
>>>>>>>>>> + * into a byte stream, but {@link java.io.OutputStreamWriter} 
>>>>>>>>>> pushes data to the underlying stream,
>>>>>>>>>> + * while {@link ReaderInputStream} pulls it from the 
>>>>>>>>>> underlying stream.
>>>>>>>>>> + * <p>
>>>>>>>>>> + * Note that while there are use cases where there is no 
>>>>>>>>>> alternative to using
>>>>>>>>>> + * this class, very often the need to use this class is an 
>>>>>>>>>> indication of a flaw
>>>>>>>>>> + * in the design of the code. This class is typically used in 
>>>>>>>>>> situations where an existing
>>>>>>>>>> + * API only accepts an {@link InputStream}, but where the 
>>>>>>>>>> most natural way to produce the data
>>>>>>>>>> + * is as a character stream, i.e. by providing a {@link 
>>>>>>>>>> Reader} instance. An example of a situation
>>>>>>>>>> + * where this problem may appear is when implementing the 
>>>>>>>>>> {@link javax.activation.DataSource}
>>>>>>>>>> + * interface from the Java Activation Framework.
>>>>>>>>>> + * <p>
>>>>>>>>>> + * Given the fact that the {@link Reader} class doesn't 
>>>>>>>>>> provide any way to predict whether the next
>>>>>>>>>> + * read operation will block or not, it is not possible to 
>>>>>>>>>> provide a meaningful
>>>>>>>>>> + * implementation of the {@link InputStream#available()} 
>>>>>>>>>> method. A call to this method
>>>>>>>>>> + * will always return 0. Also, this class doesn't support 
>>>>>>>>>> {@link InputStream#mark(int)}.
>>>>>>>>>> + * <p>
>>>>>>>>>> + * Instances of {@link ReaderInputStream} are not thread safe.
>>>>>>>>>> + *
>>>>>>>>>> + * @see org.apache.commons.io.output.WriterOutputStream
>>>>>>>>>> + *
>>>>>>>>>> + * @since 2.0
>>>>>>>>>> + */
>>>>>>>>>> +public class ReaderInputStream extends InputStream {
>>>>>>>>>> +    private static final int DEFAULT_BUFFER_SIZE = 1024;
>>>>>>>>>> +
>>>>>>>>>> +    private final Reader reader;
>>>>>>>>>> +    private final CharsetEncoder encoder;
>>>>>>>>>> +
>>>>>>>>>> +    /**
>>>>>>>>>> +     * CharBuffer used as input for the decoder. It should be 
>>>>>>>>>> reasonably
>>>>>>>>>> +     * large as we read data from the underlying Reader into 
>>>>>>>>>> this buffer.
>>>>>>>>>> +     */
>>>>>>>>>> +    private final CharBuffer encoderIn;
>>>>>>>>>> +
>>>>>>>>>> +    /**
>>>>>>>>>> +     * ByteBuffer used as output for the decoder. This buffer 
>>>>>>>>>> can be small
>>>>>>>>>> +     * as it is only used to transfer data from the decoder 
>>>>>>>>>> to the
>>>>>>>>>> +     * buffer provided by the caller.
>>>>>>>>>> +     */
>>>>>>>>>> +    private final ByteBuffer encoderOut;
>>>>>>>>>> +
>>>>>>>>>> +    private CoderResult lastCoderResult;
>>>>>>>>>> +    private boolean endOfInput;
>>>>>>>>>> +
>>>>>>>>>> +    /**
>>>>>>>>>> +     * Construct a new {@link ReaderInputStream}.
>>>>>>>>>> +     *
>>>>>>>>>> +     * @param reader the target {@link Reader}
>>>>>>>>>> +     * @param encoder the charset encoder
>>>>>>>>>> +     * @since 2.1
>>>>>>>>>> +     */
>>>>>>>>>> +    public ReaderInputStream(Reader reader, CharsetEncoder 
>>>>>>>>>> encoder) {
>>>>>>>>>> +        this(reader, encoder, DEFAULT_BUFFER_SIZE);
>>>>>>>>>> +    }
>>>>>>>>>> +
>>>>>>>>>> +    /**
>>>>>>>>>> +     * Construct a new {@link ReaderInputStream}.
>>>>>>>>>> +     *
>>>>>>>>>> +     * @param reader the target {@link Reader}
>>>>>>>>>> +     * @param encoder the charset encoder
>>>>>>>>>> +     * @param bufferSize the size of the input buffer in 
>>>>>>>>>> number of characters
>>>>>>>>>> +     * @since 2.1
>>>>>>>>>> +     */
>>>>>>>>>> +    public ReaderInputStream(Reader reader, CharsetEncoder 
>>>>>>>>>> encoder, int bufferSize) {
>>>>>>>>>> +        this.reader = reader;
>>>>>>>>>> +        this.encoder = encoder;
>>>>>>>>>> +        this.encoderIn = CharBuffer.allocate(bufferSize);
>>>>>>>>>> +        this.encoderIn.flip();
>>>>>>>>>> +        this.encoderOut = ByteBuffer.allocate(128);
>>>>>>>>>> +        this.encoderOut.flip();
>>>>>>>>>> +    }
>>>>>>>>>> +
>>>>>>>>>> +    /**
>>>>>>>>>> +     * Construct a new {@link ReaderInputStream}.
>>>>>>>>>> +     *
>>>>>>>>>> +     * @param reader the target {@link Reader}
>>>>>>>>>> +     * @param charset the charset encoding
>>>>>>>>>> +     * @param bufferSize the size of the input buffer in 
>>>>>>>>>> number of characters
>>>>>>>>>> +     */
>>>>>>>>>> +    public ReaderInputStream(Reader reader, Charset charset, 
>>>>>>>>>> int bufferSize) {
>>>>>>>>>> +        this(reader,
>>>>>>>>>> +             charset.newEncoder()
>>>>>>>>>> +                    .onMalformedInput(CodingErrorAction.REPLACE)
>>>>>>>>>> + 
>>>>>>>>>>                    .onUnmappableCharacter(CodingErrorAction.REPLACE),
>>>>>>>>>> +             bufferSize);
>>>>>>>>>> +    }
>>>>>>>>>> +
>>>>>>>>>> +    /**
>>>>>>>>>> +     * Construct a new {@link ReaderInputStream} with a 
>>>>>>>>>> default input buffer size of
>>>>>>>>>> +     * 1024 characters.
>>>>>>>>>> +     *
>>>>>>>>>> +     * @param reader the target {@link Reader}
>>>>>>>>>> +     * @param charset the charset encoding
>>>>>>>>>> +     */
>>>>>>>>>> +    public ReaderInputStream(Reader reader, Charset charset) {
>>>>>>>>>> +        this(reader, charset, DEFAULT_BUFFER_SIZE);
>>>>>>>>>> +    }
>>>>>>>>>> +
>>>>>>>>>> +    /**
>>>>>>>>>> +     * Construct a new {@link ReaderInputStream}.
>>>>>>>>>> +     *
>>>>>>>>>> +     * @param reader the target {@link Reader}
>>>>>>>>>> +     * @param charsetName the name of the charset encoding
>>>>>>>>>> +     * @param bufferSize the size of the input buffer in 
>>>>>>>>>> number of characters
>>>>>>>>>> +     */
>>>>>>>>>> +    public ReaderInputStream(Reader reader, String 
>>>>>>>>>> charsetName, int bufferSize) {
>>>>>>>>>> +        this(reader, Charset.forName(charsetName), bufferSize);
>>>>>>>>>> +    }
>>>>>>>>>> +
>>>>>>>>>> +    /**
>>>>>>>>>> +     * Construct a new {@link ReaderInputStream} with a 
>>>>>>>>>> default input buffer size of
>>>>>>>>>> +     * 1024 characters.
>>>>>>>>>> +     *
>>>>>>>>>> +     * @param reader the target {@link Reader}
>>>>>>>>>> +     * @param charsetName the name of the charset encoding
>>>>>>>>>> +     */
>>>>>>>>>> +    public ReaderInputStream(Reader reader, String charsetName) {
>>>>>>>>>> +        this(reader, charsetName, DEFAULT_BUFFER_SIZE);
>>>>>>>>>> +    }
>>>>>>>>>> +
>>>>>>>>>> +    /**
>>>>>>>>>> +     * Construct a new {@link ReaderInputStream} that uses 
>>>>>>>>>> the default character encoding
>>>>>>>>>> +     * with a default input buffer size of 1024 characters.
>>>>>>>>>> +     *
>>>>>>>>>> +     * @param reader the target {@link Reader}
>>>>>>>>>> +     */
>>>>>>>>>> +    public ReaderInputStream(Reader reader) {
>>>>>>>>>> +        this(reader, Charset.defaultCharset());
>>>>>>>>>> +    }
>>>>>>>>>> +
>>>>>>>>>> +    /**
>>>>>>>>>> +     * Fills the internal char buffer from the reader.
>>>>>>>>>> +     *
>>>>>>>>>> +     * @throws IOException
>>>>>>>>>> +     *             If an I/O error occurs
>>>>>>>>>> +     */
>>>>>>>>>> +    private void fillBuffer() throws IOException {
>>>>>>>>>> +        if (!endOfInput && (lastCoderResult == null || 
>>>>>>>>>> lastCoderResult.isUnderflow())) {
>>>>>>>>>> +            encoderIn.compact();
>>>>>>>>>> +            int position = encoderIn.position();
>>>>>>>>>> +            // We don't use Reader#read(CharBuffer) here 
>>>>>>>>>> because it is more efficient
>>>>>>>>>> +            // to write directly to the underlying char array 
>>>>>>>>>> (the default implementation
>>>>>>>>>> +            // copies data to a temporary char array).
>>>>>>>>>> +            int c = reader.read(encoderIn.array(), position, 
>>>>>>>>>> encoderIn.remaining());
>>>>>>>>>> +            if (c == -1) {
>>>>>>>>>> +                endOfInput = true;
>>>>>>>>>> +            } else {
>>>>>>>>>> +                encoderIn.position(position + c);
>>>>>>>>>> +            }
>>>>>>>>>> +            encoderIn.flip();
>>>>>>>>>> +        }
>>>>>>>>>> +        encoderOut.compact();
>>>>>>>>>> +        lastCoderResult = encoder.encode(encoderIn, 
>>>>>>>>>> encoderOut, endOfInput);
>>>>>>>>>> +        encoderOut.flip();
>>>>>>>>>> +    }
>>>>>>>>>> +
>>>>>>>>>> +    /**
>>>>>>>>>> +     * Read the specified number of bytes into an array.
>>>>>>>>>> +     *
>>>>>>>>>> +     * @param b the byte array to read into
>>>>>>>>>> +     * @param off the offset to start reading bytes into
>>>>>>>>>> +     * @param len the number of bytes to read
>>>>>>>>>> +     * @return the number of bytes read or <code>-1</code>
>>>>>>>>>> +     *         if the end of the stream has been reached
>>>>>>>>>> +     * @throws IOException if an I/O error occurs
>>>>>>>>>> +     */
>>>>>>>>>> +    @Override
>>>>>>>>>> +    public int read(byte[] b, int off, int len) throws 
>>>>>>>>>> IOException {
>>>>>>>>>> +        if (b == null) {
>>>>>>>>>> +            throw new NullPointerException("Byte array must 
>>>>>>>>>> not be null");
>>>>>>>>>> +        }
>>>>>>>>>> +        if (len < 0 || off < 0 || (off + len) > b.length) {
>>>>>>>>>> +            throw new IndexOutOfBoundsException("Array Size=" 
>>>>>>>>>> + b.length
>>>>>>>>>> +                    + ", offset=" + off + ", length=" + len);
>>>>>>>>>> +        }
>>>>>>>>>> +        int read = 0;
>>>>>>>>>> +        if (len == 0) {
>>>>>>>>>> +            return 0; // Always return 0 if len == 0
>>>>>>>>>> +        }
>>>>>>>>>> +        while (len > 0) {
>>>>>>>>>> +            if (encoderOut.hasRemaining()) {
>>>>>>>>>> +                int c = Math.min(encoderOut.remaining(), len);
>>>>>>>>>> +                encoderOut.get(b, off, c);
>>>>>>>>>> +                off += c;
>>>>>>>>>> +                len -= c;
>>>>>>>>>> +                read += c;
>>>>>>>>>> +            } else {
>>>>>>>>>> +                fillBuffer();
>>>>>>>>>> +                if (endOfInput && !encoderOut.hasRemaining()) {
>>>>>>>>>> +                    break;
>>>>>>>>>> +                }
>>>>>>>>>> +            }
>>>>>>>>>> +        }
>>>>>>>>>> +        return read == 0 && endOfInput ? -1 : read;
>>>>>>>>>> +    }
>>>>>>>>>> +
>>>>>>>>>> +    /**
>>>>>>>>>> +     * Read the specified number of bytes into an array.
>>>>>>>>>> +     *
>>>>>>>>>> +     * @param b the byte array to read into
>>>>>>>>>> +     * @return the number of bytes read or <code>-1</code>
>>>>>>>>>> +     *         if the end of the stream has been reached
>>>>>>>>>> +     * @throws IOException if an I/O error occurs
>>>>>>>>>> +     */
>>>>>>>>>> +    @Override
>>>>>>>>>> +    public int read(byte[] b) throws IOException {
>>>>>>>>>> +        return read(b, 0, b.length);
>>>>>>>>>> +    }
>>>>>>>>>> +
>>>>>>>>>> +    /**
>>>>>>>>>> +     * Read a single byte.
>>>>>>>>>> +     *
>>>>>>>>>> +     * @return either the byte read or <code>-1</code> if the 
>>>>>>>>>> end of the stream
>>>>>>>>>> +     *         has been reached
>>>>>>>>>> +     * @throws IOException if an I/O error occurs
>>>>>>>>>> +     */
>>>>>>>>>> +    @Override
>>>>>>>>>> +    public int read() throws IOException {
>>>>>>>>>> +        for (;;) {
>>>>>>>>>> +            if (encoderOut.hasRemaining()) {
>>>>>>>>>> +                return encoderOut.get() & 0xFF;
>>>>>>>>>> +            } else {
>>>>>>>>>> +                fillBuffer();
>>>>>>>>>> +                if (endOfInput && !encoderOut.hasRemaining()) {
>>>>>>>>>> +                    return -1;
>>>>>>>>>> +                }
>>>>>>>>>> +            }
>>>>>>>>>> +        }
>>>>>>>>>> +    }
>>>>>>>>>> +
>>>>>>>>>> +    /**
>>>>>>>>>> +     * Close the stream. This method will cause the 
>>>>>>>>>> underlying {@link Reader}
>>>>>>>>>> +     * to be closed.
>>>>>>>>>> +     * @throws IOException if an I/O error occurs
>>>>>>>>>> +     */
>>>>>>>>>> +    @Override
>>>>>>>>>> +    public void close() throws IOException {
>>>>>>>>>> +        reader.close();
>>>>>>>>>> +    }
>>>>>>>>>> +}
>>>>>>>>>> diff --git 
>>>>>>>>>> a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java 
>>>>>>>>>> b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java
>>>>>>>>>> index f77b5ed..b957f36 100644
>>>>>>>>>> --- 
>>>>>>>>>> a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java
>>>>>>>>>> +++ 
>>>>>>>>>> b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java
>>>>>>>>>> @@ -96,6 +96,25 @@ public class JAXRSJmsTest extends 
>>>>>>>>>> AbstractBusClientServerTestBase {
>>>>>>>>>>          assertEquals("Get a wrong response code.", 200, 
>>>>>>>>>> client.getResponse().getStatus());
>>>>>>>>>>          assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>>>>      }
>>>>>>>>>> +
>>>>>>>>>> +    @Test
>>>>>>>>>> +    public void testGetBookFromWebClientWithTextJMSMessage() 
>>>>>>>>>> throws Exception {
>>>>>>>>>> +        // setup the the client
>>>>>>>>>> +        String endpointAddressUrlEncoded = 
>>>>>>>>>> "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>>>>>>> +             + 
>>>>>>>>>> "?replyToName=dynamicQueues/test.jmstransport.response"
>>>>>>>>>> +             + 
>>>>>>>>>> "&jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory"
>>>>>>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>>>>>>> +             + "&messageType=text";
>>>>>>>>>> +
>>>>>>>>>> +        WebClient client = 
>>>>>>>>>> WebClient.create(endpointAddressUrlEncoded);
>>>>>>>>>> + 
>>>>>>>>>>        WebClient.getConfig(client).getInInterceptors().add(new 
>>>>>>>>>> LoggingInInterceptor());
>>>>>>>>>> +        WebClient.getConfig(client).getRequestContext()
>>>>>>>>>> +            .put(org.apache.cxf.message.Message.REQUEST_URI, 
>>>>>>>>>> "/bookstore/books/123");
>>>>>>>>>> +
>>>>>>>>>> +        Book book = client.get(Book.class);
>>>>>>>>>> +        assertEquals("Get a wrong response code.", 200, 
>>>>>>>>>> client.getResponse().getStatus());
>>>>>>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>>>> +    }
>>>>>>>>>>      @Test
>>>>>>>>>>      public void testPutBookOneWayWithWebClient() throws 
>>>>>>>>>> Exception {
>>>>>>>>>> @@ -130,6 +149,7 @@ public class JAXRSJmsTest extends 
>>>>>>>>>> AbstractBusClientServerTestBase {
>>>>>>>>>>          }
>>>>>>>>>>      }
>>>>>>>>>> +
>>>>>>>>>>      @Test
>>>>>>>>>>      public void testGetBookFromWebClientWithPath() throws 
>>>>>>>>>> Exception {
>>>>>>>>>>          // setup the the client
>>>>>>>>>> @@ -148,6 +168,25 @@ public class JAXRSJmsTest extends 
>>>>>>>>>> AbstractBusClientServerTestBase {
>>>>>>>>>>      }
>>>>>>>>>>      @Test
>>>>>>>>>> +    public void 
>>>>>>>>>> testGetBookFromWebClientWithPathWithTextJMSMessage() throws 
>>>>>>>>>> Exception {
>>>>>>>>>> +        // setup the the client
>>>>>>>>>> +        String endpointAddressUrlEncoded = 
>>>>>>>>>> "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>>>>>>> +             + 
>>>>>>>>>> "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory"
>>>>>>>>>> +             + 
>>>>>>>>>> "&replyToName=dynamicQueues/test.jmstransport.response"
>>>>>>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>>>>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>>>>>>>> +             + "&messageType=text";
>>>>>>>>>> +
>>>>>>>>>> +        WebClient client = 
>>>>>>>>>> WebClient.create(endpointAddressUrlEncoded);
>>>>>>>>>> +        client.path("bookstore").path("books").path("123");
>>>>>>>>>> +
>>>>>>>>>> +        Book book = client.get(Book.class);
>>>>>>>>>> +        assertEquals("Get a wrong response code.", 200, 
>>>>>>>>>> client.getResponse().getStatus());
>>>>>>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>>>> +    }
>>>>>>>>>> +
>>>>>>>>>> +
>>>>>>>>>> +    @Test
>>>>>>>>>>      public void testGetBookFromProxyClient() throws Exception {
>>>>>>>>>>          // setup the the client
>>>>>>>>>>          String endpointAddressUrlEncoded = 
>>>>>>>>>> "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>>>>>>> @@ -161,6 +200,22 @@ public class JAXRSJmsTest extends 
>>>>>>>>>> AbstractBusClientServerTestBase {
>>>>>>>>>>          assertEquals("Get a wrong response code.", 200, 
>>>>>>>>>> WebClient.client(client).getResponse().getStatus());
>>>>>>>>>>          assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>>>>      }
>>>>>>>>>> +
>>>>>>>>>> +    @Test
>>>>>>>>>> +    public void 
>>>>>>>>>> testGetBookFromProxyClientWithTextJMSMessage() throws Exception {
>>>>>>>>>> +        // setup the the client
>>>>>>>>>> +        String endpointAddressUrlEncoded = 
>>>>>>>>>> "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>>>>>>> +             + 
>>>>>>>>>> "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory"
>>>>>>>>>> +             + 
>>>>>>>>>> "&replyToName=dynamicQueues/test.jmstransport.response"
>>>>>>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>>>>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>>>>>>>> +             + "&messageType=text";
>>>>>>>>>> +
>>>>>>>>>> +        JMSBookStore client = 
>>>>>>>>>> JAXRSClientFactory.create(endpointAddressUrlEncoded, 
>>>>>>>>>> JMSBookStore.class);
>>>>>>>>>> +        Book book = client.getBook("123");
>>>>>>>>>> +        assertEquals("Get a wrong response code.", 200, 
>>>>>>>>>> WebClient.client(client).getResponse().getStatus());
>>>>>>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>>>> +    }
>>>>>>>>>>      @Test
>>>>>>>>>>      public void testGetBookFromSubresourceProxyClient() 
>>>>>>>>>> throws Exception {
>>>>>>>>>> @@ -177,6 +232,23 @@ public class JAXRSJmsTest extends 
>>>>>>>>>> AbstractBusClientServerTestBase {
>>>>>>>>>>          assertEquals("Get a wrong response code.", 200, 
>>>>>>>>>> WebClient.client(bookProxy).getResponse().getStatus());
>>>>>>>>>>          assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>>>>      }
>>>>>>>>>> +
>>>>>>>>>> +    @Test
>>>>>>>>>> +    public void 
>>>>>>>>>> testGetBookFromSubresourceProxyClientWithTextJMSMessage() 
>>>>>>>>>> throws Exception {
>>>>>>>>>> +        // setup the the client
>>>>>>>>>> +        String endpointAddressUrlEncoded = 
>>>>>>>>>> "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>>>>>>> +             + 
>>>>>>>>>> "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory"
>>>>>>>>>> +             + 
>>>>>>>>>> "&replyToName=dynamicQueues/test.jmstransport.response"
>>>>>>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>>>>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>>>>>>>> +             + "&messageType=text";
>>>>>>>>>> +
>>>>>>>>>> +        JMSBookStore client = 
>>>>>>>>>> JAXRSClientFactory.create(endpointAddressUrlEncoded, 
>>>>>>>>>> JMSBookStore.class);
>>>>>>>>>> +        Book bookProxy = client.getBookSubResource("123");
>>>>>>>>>> +        Book book = bookProxy.retrieveState();
>>>>>>>>>> +        assertEquals("Get a wrong response code.", 200, 
>>>>>>>>>> WebClient.client(bookProxy).getResponse().getStatus());
>>>>>>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>>>> +    }
>>>>>>>>>>      @Test
>>>>>>>>>>      public void testGetBookFromProxyClientWithQuery() throws 
>>>>>>>>>> Exception {
>>>>>>>>>> @@ -193,6 +265,24 @@ public class JAXRSJmsTest extends 
>>>>>>>>>> AbstractBusClientServerTestBase {
>>>>>>>>>>          assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>>>>      }
>>>>>>>>>> +
>>>>>>>>>> +    @Test
>>>>>>>>>> +    public void 
>>>>>>>>>> testGetBookFromProxyClientWithQueryWithTextJMSMessage() throws 
>>>>>>>>>> Exception {
>>>>>>>>>> +        // setup the the client
>>>>>>>>>> +        String endpointAddressUrlEncoded = 
>>>>>>>>>> "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>>>>>>> +             + 
>>>>>>>>>> "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory"
>>>>>>>>>> +             + 
>>>>>>>>>> "&replyToName=dynamicQueues/test.jmstransport.response"
>>>>>>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>>>>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>>>>>>>> +             + "&messageType=text";
>>>>>>>>>> +
>>>>>>>>>> +        JMSBookStore client = 
>>>>>>>>>> JAXRSClientFactory.create(endpointAddressUrlEncoded, 
>>>>>>>>>> JMSBookStore.class);
>>>>>>>>>> +        Book book = client.getBookByURLQuery(new String[] 
>>>>>>>>>> {"1", "2", "3"});
>>>>>>>>>> +        assertEquals("Get a wrong response code.", 200, 
>>>>>>>>>> WebClient.client(client).getResponse().getStatus());
>>>>>>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>>>> +    }
>>>>>>>>>> +
>>>>>>>>>> +
>>>>>>>>>>      @Test
>>>>>>>>>>      public void testGetBook() throws Exception {
>>>>>>>>>>          Context ctx = getContext();
>>>>>>>>>>
>>>>>>>>
>>>>>>>
>>>>>
>>>>>
>>>>> -- 
>>>>> Sergey Beryozkin
>>>>>
>>>>> Talend Community Coders
>>>>> http://coders.talend.com/
>>>>
> 


Re: [cxf] branch master updated: [CXF-7532]REST on JMS transport can't handle the request message with text messageType

Posted by Freeman Fang <fr...@gmail.com>.
Hi Sergey,

Cleaned up accordingly.

Cheers
-------------
Freeman(Yue) Fang

Red Hat, Inc. 
FuseSource is now part of Red Hat



> On Oct 20, 2017, at 5:59 AM, Sergey Beryozkin <sb...@gmail.com> wrote:
> 
> Some JAX-RS providers check Reader or XMLStreamReader if InputStream is null.
> But at the moment, Freeman, please do a bit of cleanup as agreed, and then we can check if the use ReaderInputStream can be avoided at all.
> 
> Sergey
> 
> On 19/10/17 19:47, Daniel Kulp wrote:
>> If using the TEXT stuff for JMS, it should remain as a Reader on the message if at all possible.  Converting from Reader to InputStream is not a trivial operation and does enact a performance penalty.   All of the SOAP pathways for JMS should be OK for use of a Reader so that we can avoid the entire String->UTF-8 bytes->chars round tripping.
>> Dan
>> On Oct 19, 2017, at 5:21 AM, ffang <fr...@gmail.com> wrote:
>>> 
>>> I modified the JMS transport part and set ReaderInputStream instead of Reader, running all tests now, will see what I can get.
>>> 
>>> If I run into any test failure, I will keep things as is, just clean up.
>>> -------------
>>> Freeman(Yue) Fang
>>> 
>>> Red Hat, Inc.
>>> FuseSource is now part of Red Hat
>>> 
>>> 
>>> 
>>>> On Oct 19, 2017, at 5:16 PM, Sergey Beryozkin <sb...@gmail.com> wrote:
>>>> 
>>>> If we assume that checking Reader can be useful in the general sense, then may be lets keep things as is, with the minor cleanup only...
>>>> 
>>>> Sergey
>>>> On 19/10/17 10:05, Sergey Beryozkin wrote:
>>>>> Hi Freeman
>>>>> Reader may likely need to be set too for SOAP, and I recall checking Reader somewhere else too (will double check now), but I thought may be the transport can set both Reader and InputStream, hmm, that is an extra instance though, may be lets start with moving ReaderInputStream to the core first ?
>>>>> Sergey
>>>>> On 19/10/17 09:59, ffang wrote:
>>>>>> Yeah, set ReaderInputStream instead of Reader for the JMS transport is a better solution. If no objection I will go this way.
>>>>>> -------------
>>>>>> Freeman(Yue) Fang
>>>>>> 
>>>>>> Red Hat, Inc.
>>>>>> FuseSource is now part of Red Hat
>>>>>> 
>>>>>> 
>>>>>> 
>>>>>>> On Oct 19, 2017, at 4:50 PM, Sergey Beryozkin <sberyozkin@gmail.com <ma...@gmail.com>> wrote:
>>>>>>> 
>>>>>>> The other thing is, should JMS transport itself set ReaderInputStream as InputStream on the message, as opposed to patching the JAX-RS code in various places ?
>>>>>>> 
>>>>>>> Sergey
>>>>>>> On 19/10/17 09:44, Sergey Beryozkin wrote:
>>>>>>>> Hi Freeman
>>>>>>>> Can you move ReaderInputStream to the CXF core where CachedInputStream and other IO utility code is located ?
>>>>>>>> Please also update the code which gets Reader.class and check it for null first, and also drop a comment re the JMS transport, checking the reader can make sense in all the cases...
>>>>>>>> Thanks, Sergey
>>>>>>>> On 19/10/17 04:16, ffang@apache.org <ma...@apache.org> wrote:
>>>>>>>>> This is an automated email from the ASF dual-hosted git repository.
>>>>>>>>> 
>>>>>>>>> ffang pushed a commit to branch master
>>>>>>>>> in repository https://gitbox.apache.org/repos/asf/cxf.git
>>>>>>>>> 
>>>>>>>>> 
>>>>>>>>> The following commit(s) were added to refs/heads/master by this push:
>>>>>>>>>      new 1d66475  [CXF-7532]REST on JMS transport can't handle the request message with text messageType
>>>>>>>>> 1d66475 is described below
>>>>>>>>> 
>>>>>>>>> commit 1d66475b4dea2a0031dcb1765e8e9fd08430ec9b
>>>>>>>>> Author: Freeman Fang <freeman.fang@gmail.com <ma...@gmail.com>>
>>>>>>>>> AuthorDate: Thu Oct 19 11:15:50 2017 +0800
>>>>>>>>> 
>>>>>>>>>     [CXF-7532]REST on JMS transport can't handle the request message with text messageType
>>>>>>>>> ---
>>>>>>>>>  .../org/apache/cxf/jaxrs/impl/ResponseImpl.java    |   5 +
>>>>>>>>>  .../org/apache/cxf/jaxrs/utils/JAXRSUtils.java     |   6 +-
>>>>>>>>>  .../apache/cxf/jaxrs/utils/ReaderInputStream.java  | 296 +++++++++++++++++++++
>>>>>>>>>  .../apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java |  90 +++++++
>>>>>>>>>  4 files changed, 396 insertions(+), 1 deletion(-)
>>>>>>>>> 
>>>>>>>>> diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java
>>>>>>>>> index e3a3acf..82f183a 100644
>>>>>>>>> --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java
>>>>>>>>> +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java
>>>>>>>>> @@ -58,6 +58,7 @@ import org.apache.cxf.jaxrs.provider.ProviderFactory;
>>>>>>>>>  import org.apache.cxf.jaxrs.utils.HttpUtils;
>>>>>>>>>  import org.apache.cxf.jaxrs.utils.InjectionUtils;
>>>>>>>>>  import org.apache.cxf.jaxrs.utils.JAXRSUtils;
>>>>>>>>> +import org.apache.cxf.jaxrs.utils.ReaderInputStream;
>>>>>>>>>  import org.apache.cxf.message.Message;
>>>>>>>>>  import org.apache.cxf.message.MessageUtils;
>>>>>>>>> @@ -342,6 +343,10 @@ public final class ResponseImpl extends Response {
>>>>>>>>>              entityStreamAvailable = entityStream != null;
>>>>>>>>>          } else if (entity instanceof InputStream) {
>>>>>>>>>              entityStream = InputStream.class.cast(entity);
>>>>>>>>> +        } else {
>>>>>>>>> +            Message inMessage = getResponseMessage();
>>>>>>>>> +            Reader reader = inMessage.getContent(Reader.class);
>>>>>>>>> +            entityStream = InputStream.class.cast(new ReaderInputStream(reader));
>>>>>>>>>          }
>>>>>>>>>          // we need to check for readers even if no IS is set - the readers may still do it
>>>>>>>>> diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
>>>>>>>>> index 0ef6565..509f76d 100644
>>>>>>>>> --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
>>>>>>>>> +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
>>>>>>>>> @@ -832,7 +832,11 @@ public final class JAXRSUtils {
>>>>>>>>>                                             OperationResourceInfo ori)
>>>>>>>>>          throws IOException, WebApplicationException {
>>>>>>>>>          InputStream is = message.getContent(InputStream.class);
>>>>>>>>> -
>>>>>>>>> +        if (is == null) {
>>>>>>>>> +            //may use the jms transport so check the Reader;
>>>>>>>>> +            Reader reader = message.getContent(Reader.class);
>>>>>>>>> +            is = new ReaderInputStream(reader);
>>>>>>>>> +        }
>>>>>>>>>          if (parameter.getType() == ParameterType.REQUEST_BODY) {
>>>>>>>>>              if (parameterClass == AsyncResponse.class) {
>>>>>>>>> diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java
>>>>>>>>> new file mode 100644
>>>>>>>>> index 0000000..c7b142e
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java
>>>>>>>>> @@ -0,0 +1,296 @@
>>>>>>>>> +/**
>>>>>>>>> + * 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.cxf.jaxrs.utils;
>>>>>>>>> +
>>>>>>>>> +import java.io.IOException;
>>>>>>>>> +import java.io.InputStream;
>>>>>>>>> +import java.io.Reader;
>>>>>>>>> +import java.nio.ByteBuffer;
>>>>>>>>> +import java.nio.CharBuffer;
>>>>>>>>> +import java.nio.charset.Charset;
>>>>>>>>> +import java.nio.charset.CharsetEncoder;
>>>>>>>>> +import java.nio.charset.CoderResult;
>>>>>>>>> +import java.nio.charset.CodingErrorAction;
>>>>>>>>> +
>>>>>>>>> +/**
>>>>>>>>> + * {@link InputStream} implementation that reads a character stream from a {@link Reader}
>>>>>>>>> + * and transforms it to a byte stream using a specified charset encoding. The stream
>>>>>>>>> + * is transformed using a {@link CharsetEncoder} object, guaranteeing that all charset
>>>>>>>>> + * encodings supported by the JRE are handled correctly. In particular for charsets such as
>>>>>>>>> + * UTF-16, the implementation ensures that one and only one byte order marker
>>>>>>>>> + * is produced.
>>>>>>>>> + * <p>
>>>>>>>>> + * Since in general it is not possible to predict the number of characters to be read from the
>>>>>>>>> + * {@link Reader} to satisfy a read request on the {@link ReaderInputStream}, all reads from
>>>>>>>>> + * the {@link Reader} are buffered. There is therefore no well defined correlation
>>>>>>>>> + * between the current position of the {@link Reader} and that of the {@link ReaderInputStream}.
>>>>>>>>> + * This also implies that in general there is no need to wrap the underlying {@link Reader}
>>>>>>>>> + * in a {@link java.io.BufferedReader}.
>>>>>>>>> + * <p>
>>>>>>>>> + * {@link ReaderInputStream} implements the inverse transformation of {@link java.io.InputStreamReader};
>>>>>>>>> + * in the following example, reading from <tt>in2</tt> would return the same byte
>>>>>>>>> + * sequence as reading from <tt>in</tt> (provided that the initial byte sequence is legal
>>>>>>>>> + * with respect to the charset encoding):
>>>>>>>>> + * <pre>
>>>>>>>>> + * InputStream in = ...
>>>>>>>>> + * Charset cs = ...
>>>>>>>>> + * InputStreamReader reader = new InputStreamReader(in, cs);
>>>>>>>>> + * ReaderInputStream in2 = new ReaderInputStream(reader, cs);</pre>
>>>>>>>>> + * {@link ReaderInputStream} implements the same transformation as {@link java.io.OutputStreamWriter},
>>>>>>>>> + * except that the control flow is reversed: both classes transform a character stream
>>>>>>>>> + * into a byte stream, but {@link java.io.OutputStreamWriter} pushes data to the underlying stream,
>>>>>>>>> + * while {@link ReaderInputStream} pulls it from the underlying stream.
>>>>>>>>> + * <p>
>>>>>>>>> + * Note that while there are use cases where there is no alternative to using
>>>>>>>>> + * this class, very often the need to use this class is an indication of a flaw
>>>>>>>>> + * in the design of the code. This class is typically used in situations where an existing
>>>>>>>>> + * API only accepts an {@link InputStream}, but where the most natural way to produce the data
>>>>>>>>> + * is as a character stream, i.e. by providing a {@link Reader} instance. An example of a situation
>>>>>>>>> + * where this problem may appear is when implementing the {@link javax.activation.DataSource}
>>>>>>>>> + * interface from the Java Activation Framework.
>>>>>>>>> + * <p>
>>>>>>>>> + * Given the fact that the {@link Reader} class doesn't provide any way to predict whether the next
>>>>>>>>> + * read operation will block or not, it is not possible to provide a meaningful
>>>>>>>>> + * implementation of the {@link InputStream#available()} method. A call to this method
>>>>>>>>> + * will always return 0. Also, this class doesn't support {@link InputStream#mark(int)}.
>>>>>>>>> + * <p>
>>>>>>>>> + * Instances of {@link ReaderInputStream} are not thread safe.
>>>>>>>>> + *
>>>>>>>>> + * @see org.apache.commons.io.output.WriterOutputStream
>>>>>>>>> + *
>>>>>>>>> + * @since 2.0
>>>>>>>>> + */
>>>>>>>>> +public class ReaderInputStream extends InputStream {
>>>>>>>>> +    private static final int DEFAULT_BUFFER_SIZE = 1024;
>>>>>>>>> +
>>>>>>>>> +    private final Reader reader;
>>>>>>>>> +    private final CharsetEncoder encoder;
>>>>>>>>> +
>>>>>>>>> +    /**
>>>>>>>>> +     * CharBuffer used as input for the decoder. It should be reasonably
>>>>>>>>> +     * large as we read data from the underlying Reader into this buffer.
>>>>>>>>> +     */
>>>>>>>>> +    private final CharBuffer encoderIn;
>>>>>>>>> +
>>>>>>>>> +    /**
>>>>>>>>> +     * ByteBuffer used as output for the decoder. This buffer can be small
>>>>>>>>> +     * as it is only used to transfer data from the decoder to the
>>>>>>>>> +     * buffer provided by the caller.
>>>>>>>>> +     */
>>>>>>>>> +    private final ByteBuffer encoderOut;
>>>>>>>>> +
>>>>>>>>> +    private CoderResult lastCoderResult;
>>>>>>>>> +    private boolean endOfInput;
>>>>>>>>> +
>>>>>>>>> +    /**
>>>>>>>>> +     * Construct a new {@link ReaderInputStream}.
>>>>>>>>> +     *
>>>>>>>>> +     * @param reader the target {@link Reader}
>>>>>>>>> +     * @param encoder the charset encoder
>>>>>>>>> +     * @since 2.1
>>>>>>>>> +     */
>>>>>>>>> +    public ReaderInputStream(Reader reader, CharsetEncoder encoder) {
>>>>>>>>> +        this(reader, encoder, DEFAULT_BUFFER_SIZE);
>>>>>>>>> +    }
>>>>>>>>> +
>>>>>>>>> +    /**
>>>>>>>>> +     * Construct a new {@link ReaderInputStream}.
>>>>>>>>> +     *
>>>>>>>>> +     * @param reader the target {@link Reader}
>>>>>>>>> +     * @param encoder the charset encoder
>>>>>>>>> +     * @param bufferSize the size of the input buffer in number of characters
>>>>>>>>> +     * @since 2.1
>>>>>>>>> +     */
>>>>>>>>> +    public ReaderInputStream(Reader reader, CharsetEncoder encoder, int bufferSize) {
>>>>>>>>> +        this.reader = reader;
>>>>>>>>> +        this.encoder = encoder;
>>>>>>>>> +        this.encoderIn = CharBuffer.allocate(bufferSize);
>>>>>>>>> +        this.encoderIn.flip();
>>>>>>>>> +        this.encoderOut = ByteBuffer.allocate(128);
>>>>>>>>> +        this.encoderOut.flip();
>>>>>>>>> +    }
>>>>>>>>> +
>>>>>>>>> +    /**
>>>>>>>>> +     * Construct a new {@link ReaderInputStream}.
>>>>>>>>> +     *
>>>>>>>>> +     * @param reader the target {@link Reader}
>>>>>>>>> +     * @param charset the charset encoding
>>>>>>>>> +     * @param bufferSize the size of the input buffer in number of characters
>>>>>>>>> +     */
>>>>>>>>> +    public ReaderInputStream(Reader reader, Charset charset, int bufferSize) {
>>>>>>>>> +        this(reader,
>>>>>>>>> +             charset.newEncoder()
>>>>>>>>> +                    .onMalformedInput(CodingErrorAction.REPLACE)
>>>>>>>>> +                    .onUnmappableCharacter(CodingErrorAction.REPLACE),
>>>>>>>>> +             bufferSize);
>>>>>>>>> +    }
>>>>>>>>> +
>>>>>>>>> +    /**
>>>>>>>>> +     * Construct a new {@link ReaderInputStream} with a default input buffer size of
>>>>>>>>> +     * 1024 characters.
>>>>>>>>> +     *
>>>>>>>>> +     * @param reader the target {@link Reader}
>>>>>>>>> +     * @param charset the charset encoding
>>>>>>>>> +     */
>>>>>>>>> +    public ReaderInputStream(Reader reader, Charset charset) {
>>>>>>>>> +        this(reader, charset, DEFAULT_BUFFER_SIZE);
>>>>>>>>> +    }
>>>>>>>>> +
>>>>>>>>> +    /**
>>>>>>>>> +     * Construct a new {@link ReaderInputStream}.
>>>>>>>>> +     *
>>>>>>>>> +     * @param reader the target {@link Reader}
>>>>>>>>> +     * @param charsetName the name of the charset encoding
>>>>>>>>> +     * @param bufferSize the size of the input buffer in number of characters
>>>>>>>>> +     */
>>>>>>>>> +    public ReaderInputStream(Reader reader, String charsetName, int bufferSize) {
>>>>>>>>> +        this(reader, Charset.forName(charsetName), bufferSize);
>>>>>>>>> +    }
>>>>>>>>> +
>>>>>>>>> +    /**
>>>>>>>>> +     * Construct a new {@link ReaderInputStream} with a default input buffer size of
>>>>>>>>> +     * 1024 characters.
>>>>>>>>> +     *
>>>>>>>>> +     * @param reader the target {@link Reader}
>>>>>>>>> +     * @param charsetName the name of the charset encoding
>>>>>>>>> +     */
>>>>>>>>> +    public ReaderInputStream(Reader reader, String charsetName) {
>>>>>>>>> +        this(reader, charsetName, DEFAULT_BUFFER_SIZE);
>>>>>>>>> +    }
>>>>>>>>> +
>>>>>>>>> +    /**
>>>>>>>>> +     * Construct a new {@link ReaderInputStream} that uses the default character encoding
>>>>>>>>> +     * with a default input buffer size of 1024 characters.
>>>>>>>>> +     *
>>>>>>>>> +     * @param reader the target {@link Reader}
>>>>>>>>> +     */
>>>>>>>>> +    public ReaderInputStream(Reader reader) {
>>>>>>>>> +        this(reader, Charset.defaultCharset());
>>>>>>>>> +    }
>>>>>>>>> +
>>>>>>>>> +    /**
>>>>>>>>> +     * Fills the internal char buffer from the reader.
>>>>>>>>> +     *
>>>>>>>>> +     * @throws IOException
>>>>>>>>> +     *             If an I/O error occurs
>>>>>>>>> +     */
>>>>>>>>> +    private void fillBuffer() throws IOException {
>>>>>>>>> +        if (!endOfInput && (lastCoderResult == null || lastCoderResult.isUnderflow())) {
>>>>>>>>> +            encoderIn.compact();
>>>>>>>>> +            int position = encoderIn.position();
>>>>>>>>> +            // We don't use Reader#read(CharBuffer) here because it is more efficient
>>>>>>>>> +            // to write directly to the underlying char array (the default implementation
>>>>>>>>> +            // copies data to a temporary char array).
>>>>>>>>> +            int c = reader.read(encoderIn.array(), position, encoderIn.remaining());
>>>>>>>>> +            if (c == -1) {
>>>>>>>>> +                endOfInput = true;
>>>>>>>>> +            } else {
>>>>>>>>> +                encoderIn.position(position + c);
>>>>>>>>> +            }
>>>>>>>>> +            encoderIn.flip();
>>>>>>>>> +        }
>>>>>>>>> +        encoderOut.compact();
>>>>>>>>> +        lastCoderResult = encoder.encode(encoderIn, encoderOut, endOfInput);
>>>>>>>>> +        encoderOut.flip();
>>>>>>>>> +    }
>>>>>>>>> +
>>>>>>>>> +    /**
>>>>>>>>> +     * Read the specified number of bytes into an array.
>>>>>>>>> +     *
>>>>>>>>> +     * @param b the byte array to read into
>>>>>>>>> +     * @param off the offset to start reading bytes into
>>>>>>>>> +     * @param len the number of bytes to read
>>>>>>>>> +     * @return the number of bytes read or <code>-1</code>
>>>>>>>>> +     *         if the end of the stream has been reached
>>>>>>>>> +     * @throws IOException if an I/O error occurs
>>>>>>>>> +     */
>>>>>>>>> +    @Override
>>>>>>>>> +    public int read(byte[] b, int off, int len) throws IOException {
>>>>>>>>> +        if (b == null) {
>>>>>>>>> +            throw new NullPointerException("Byte array must not be null");
>>>>>>>>> +        }
>>>>>>>>> +        if (len < 0 || off < 0 || (off + len) > b.length) {
>>>>>>>>> +            throw new IndexOutOfBoundsException("Array Size=" + b.length
>>>>>>>>> +                    + ", offset=" + off + ", length=" + len);
>>>>>>>>> +        }
>>>>>>>>> +        int read = 0;
>>>>>>>>> +        if (len == 0) {
>>>>>>>>> +            return 0; // Always return 0 if len == 0
>>>>>>>>> +        }
>>>>>>>>> +        while (len > 0) {
>>>>>>>>> +            if (encoderOut.hasRemaining()) {
>>>>>>>>> +                int c = Math.min(encoderOut.remaining(), len);
>>>>>>>>> +                encoderOut.get(b, off, c);
>>>>>>>>> +                off += c;
>>>>>>>>> +                len -= c;
>>>>>>>>> +                read += c;
>>>>>>>>> +            } else {
>>>>>>>>> +                fillBuffer();
>>>>>>>>> +                if (endOfInput && !encoderOut.hasRemaining()) {
>>>>>>>>> +                    break;
>>>>>>>>> +                }
>>>>>>>>> +            }
>>>>>>>>> +        }
>>>>>>>>> +        return read == 0 && endOfInput ? -1 : read;
>>>>>>>>> +    }
>>>>>>>>> +
>>>>>>>>> +    /**
>>>>>>>>> +     * Read the specified number of bytes into an array.
>>>>>>>>> +     *
>>>>>>>>> +     * @param b the byte array to read into
>>>>>>>>> +     * @return the number of bytes read or <code>-1</code>
>>>>>>>>> +     *         if the end of the stream has been reached
>>>>>>>>> +     * @throws IOException if an I/O error occurs
>>>>>>>>> +     */
>>>>>>>>> +    @Override
>>>>>>>>> +    public int read(byte[] b) throws IOException {
>>>>>>>>> +        return read(b, 0, b.length);
>>>>>>>>> +    }
>>>>>>>>> +
>>>>>>>>> +    /**
>>>>>>>>> +     * Read a single byte.
>>>>>>>>> +     *
>>>>>>>>> +     * @return either the byte read or <code>-1</code> if the end of the stream
>>>>>>>>> +     *         has been reached
>>>>>>>>> +     * @throws IOException if an I/O error occurs
>>>>>>>>> +     */
>>>>>>>>> +    @Override
>>>>>>>>> +    public int read() throws IOException {
>>>>>>>>> +        for (;;) {
>>>>>>>>> +            if (encoderOut.hasRemaining()) {
>>>>>>>>> +                return encoderOut.get() & 0xFF;
>>>>>>>>> +            } else {
>>>>>>>>> +                fillBuffer();
>>>>>>>>> +                if (endOfInput && !encoderOut.hasRemaining()) {
>>>>>>>>> +                    return -1;
>>>>>>>>> +                }
>>>>>>>>> +            }
>>>>>>>>> +        }
>>>>>>>>> +    }
>>>>>>>>> +
>>>>>>>>> +    /**
>>>>>>>>> +     * Close the stream. This method will cause the underlying {@link Reader}
>>>>>>>>> +     * to be closed.
>>>>>>>>> +     * @throws IOException if an I/O error occurs
>>>>>>>>> +     */
>>>>>>>>> +    @Override
>>>>>>>>> +    public void close() throws IOException {
>>>>>>>>> +        reader.close();
>>>>>>>>> +    }
>>>>>>>>> +}
>>>>>>>>> diff --git a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java
>>>>>>>>> index f77b5ed..b957f36 100644
>>>>>>>>> --- a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java
>>>>>>>>> +++ b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java
>>>>>>>>> @@ -96,6 +96,25 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>>>>>>>          assertEquals("Get a wrong response code.", 200, client.getResponse().getStatus());
>>>>>>>>>          assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>>>      }
>>>>>>>>> +
>>>>>>>>> +    @Test
>>>>>>>>> +    public void testGetBookFromWebClientWithTextJMSMessage() throws Exception {
>>>>>>>>> +        // setup the the client
>>>>>>>>> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>>>>>> +             + "?replyToName=dynamicQueues/test.jmstransport.response"
>>>>>>>>> +             + "&jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory"
>>>>>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>>>>>> +             + "&messageType=text";
>>>>>>>>> +
>>>>>>>>> +        WebClient client = WebClient.create(endpointAddressUrlEncoded);
>>>>>>>>> +        WebClient.getConfig(client).getInInterceptors().add(new LoggingInInterceptor());
>>>>>>>>> +        WebClient.getConfig(client).getRequestContext()
>>>>>>>>> +            .put(org.apache.cxf.message.Message.REQUEST_URI, "/bookstore/books/123");
>>>>>>>>> +
>>>>>>>>> +        Book book = client.get(Book.class);
>>>>>>>>> +        assertEquals("Get a wrong response code.", 200, client.getResponse().getStatus());
>>>>>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>>> +    }
>>>>>>>>>      @Test
>>>>>>>>>      public void testPutBookOneWayWithWebClient() throws Exception {
>>>>>>>>> @@ -130,6 +149,7 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>>>>>>>          }
>>>>>>>>>      }
>>>>>>>>> +
>>>>>>>>>      @Test
>>>>>>>>>      public void testGetBookFromWebClientWithPath() throws Exception {
>>>>>>>>>          // setup the the client
>>>>>>>>> @@ -148,6 +168,25 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>>>>>>>      }
>>>>>>>>>      @Test
>>>>>>>>> +    public void testGetBookFromWebClientWithPathWithTextJMSMessage() throws Exception {
>>>>>>>>> +        // setup the the client
>>>>>>>>> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>>>>>> +             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory"
>>>>>>>>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>>>>>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>>>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>>>>>>> +             + "&messageType=text";
>>>>>>>>> +
>>>>>>>>> +        WebClient client = WebClient.create(endpointAddressUrlEncoded);
>>>>>>>>> +        client.path("bookstore").path("books").path("123");
>>>>>>>>> +
>>>>>>>>> +        Book book = client.get(Book.class);
>>>>>>>>> +        assertEquals("Get a wrong response code.", 200, client.getResponse().getStatus());
>>>>>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>>> +    }
>>>>>>>>> +
>>>>>>>>> +
>>>>>>>>> +    @Test
>>>>>>>>>      public void testGetBookFromProxyClient() throws Exception {
>>>>>>>>>          // setup the the client
>>>>>>>>>          String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>>>>>> @@ -161,6 +200,22 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>>>>>>>          assertEquals("Get a wrong response code.", 200, WebClient.client(client).getResponse().getStatus());
>>>>>>>>>          assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>>>      }
>>>>>>>>> +
>>>>>>>>> +    @Test
>>>>>>>>> +    public void testGetBookFromProxyClientWithTextJMSMessage() throws Exception {
>>>>>>>>> +        // setup the the client
>>>>>>>>> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>>>>>> +             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory"
>>>>>>>>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>>>>>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>>>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>>>>>>> +             + "&messageType=text";
>>>>>>>>> +
>>>>>>>>> +        JMSBookStore client = JAXRSClientFactory.create(endpointAddressUrlEncoded, JMSBookStore.class);
>>>>>>>>> +        Book book = client.getBook("123");
>>>>>>>>> +        assertEquals("Get a wrong response code.", 200, WebClient.client(client).getResponse().getStatus());
>>>>>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>>> +    }
>>>>>>>>>      @Test
>>>>>>>>>      public void testGetBookFromSubresourceProxyClient() throws Exception {
>>>>>>>>> @@ -177,6 +232,23 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>>>>>>>          assertEquals("Get a wrong response code.", 200, WebClient.client(bookProxy).getResponse().getStatus());
>>>>>>>>>          assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>>>      }
>>>>>>>>> +
>>>>>>>>> +    @Test
>>>>>>>>> +    public void testGetBookFromSubresourceProxyClientWithTextJMSMessage() throws Exception {
>>>>>>>>> +        // setup the the client
>>>>>>>>> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>>>>>> +             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory"
>>>>>>>>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>>>>>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>>>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>>>>>>> +             + "&messageType=text";
>>>>>>>>> +
>>>>>>>>> +        JMSBookStore client = JAXRSClientFactory.create(endpointAddressUrlEncoded, JMSBookStore.class);
>>>>>>>>> +        Book bookProxy = client.getBookSubResource("123");
>>>>>>>>> +        Book book = bookProxy.retrieveState();
>>>>>>>>> +        assertEquals("Get a wrong response code.", 200, WebClient.client(bookProxy).getResponse().getStatus());
>>>>>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>>> +    }
>>>>>>>>>      @Test
>>>>>>>>>      public void testGetBookFromProxyClientWithQuery() throws Exception {
>>>>>>>>> @@ -193,6 +265,24 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>>>>>>>          assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>>>      }
>>>>>>>>> +
>>>>>>>>> +    @Test
>>>>>>>>> +    public void testGetBookFromProxyClientWithQueryWithTextJMSMessage() throws Exception {
>>>>>>>>> +        // setup the the client
>>>>>>>>> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>>>>>> +             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory"
>>>>>>>>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>>>>>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>>>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>>>>>>> +             + "&messageType=text";
>>>>>>>>> +
>>>>>>>>> +        JMSBookStore client = JAXRSClientFactory.create(endpointAddressUrlEncoded, JMSBookStore.class);
>>>>>>>>> +        Book book = client.getBookByURLQuery(new String[] {"1", "2", "3"});
>>>>>>>>> +        assertEquals("Get a wrong response code.", 200, WebClient.client(client).getResponse().getStatus());
>>>>>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>>> +    }
>>>>>>>>> +
>>>>>>>>> +
>>>>>>>>>      @Test
>>>>>>>>>      public void testGetBook() throws Exception {
>>>>>>>>>          Context ctx = getContext();
>>>>>>>>> 
>>>>>>> 
>>>>>> 
>>>> 
>>>> 
>>>> -- 
>>>> Sergey Beryozkin
>>>> 
>>>> Talend Community Coders
>>>> http://coders.talend.com/
>>> 


Re: [cxf] branch master updated: [CXF-7532]REST on JMS transport can't handle the request message with text messageType

Posted by Sergey Beryozkin <sb...@gmail.com>.
Some JAX-RS providers check Reader or XMLStreamReader if InputStream is 
null.
But at the moment, Freeman, please do a bit of cleanup as agreed, and 
then we can check if the use ReaderInputStream can be avoided at all.

Sergey

On 19/10/17 19:47, Daniel Kulp wrote:
> If using the TEXT stuff for JMS, it should remain as a Reader on the message if at all possible.  Converting from Reader to InputStream is not a trivial operation and does enact a performance penalty.   All of the SOAP pathways for JMS should be OK for use of a Reader so that we can avoid the entire String->UTF-8 bytes->chars round tripping.
> 
> Dan
> 
> 
> On Oct 19, 2017, at 5:21 AM, ffang <fr...@gmail.com> wrote:
>>
>> I modified the JMS transport part and set ReaderInputStream instead of Reader, running all tests now, will see what I can get.
>>
>> If I run into any test failure, I will keep things as is, just clean up.
>> -------------
>> Freeman(Yue) Fang
>>
>> Red Hat, Inc.
>> FuseSource is now part of Red Hat
>>
>>
>>
>>> On Oct 19, 2017, at 5:16 PM, Sergey Beryozkin <sb...@gmail.com> wrote:
>>>
>>> If we assume that checking Reader can be useful in the general sense, then may be lets keep things as is, with the minor cleanup only...
>>>
>>> Sergey
>>> On 19/10/17 10:05, Sergey Beryozkin wrote:
>>>> Hi Freeman
>>>> Reader may likely need to be set too for SOAP, and I recall checking Reader somewhere else too (will double check now), but I thought may be the transport can set both Reader and InputStream, hmm, that is an extra instance though, may be lets start with moving ReaderInputStream to the core first ?
>>>> Sergey
>>>> On 19/10/17 09:59, ffang wrote:
>>>>> Yeah, set ReaderInputStream instead of Reader for the JMS transport is a better solution. If no objection I will go this way.
>>>>> -------------
>>>>> Freeman(Yue) Fang
>>>>>
>>>>> Red Hat, Inc.
>>>>> FuseSource is now part of Red Hat
>>>>>
>>>>>
>>>>>
>>>>>> On Oct 19, 2017, at 4:50 PM, Sergey Beryozkin <sberyozkin@gmail.com <ma...@gmail.com>> wrote:
>>>>>>
>>>>>> The other thing is, should JMS transport itself set ReaderInputStream as InputStream on the message, as opposed to patching the JAX-RS code in various places ?
>>>>>>
>>>>>> Sergey
>>>>>> On 19/10/17 09:44, Sergey Beryozkin wrote:
>>>>>>> Hi Freeman
>>>>>>> Can you move ReaderInputStream to the CXF core where CachedInputStream and other IO utility code is located ?
>>>>>>> Please also update the code which gets Reader.class and check it for null first, and also drop a comment re the JMS transport, checking the reader can make sense in all the cases...
>>>>>>> Thanks, Sergey
>>>>>>> On 19/10/17 04:16, ffang@apache.org <ma...@apache.org> wrote:
>>>>>>>> This is an automated email from the ASF dual-hosted git repository.
>>>>>>>>
>>>>>>>> ffang pushed a commit to branch master
>>>>>>>> in repository https://gitbox.apache.org/repos/asf/cxf.git
>>>>>>>>
>>>>>>>>
>>>>>>>> The following commit(s) were added to refs/heads/master by this push:
>>>>>>>>       new 1d66475  [CXF-7532]REST on JMS transport can't handle the request message with text messageType
>>>>>>>> 1d66475 is described below
>>>>>>>>
>>>>>>>> commit 1d66475b4dea2a0031dcb1765e8e9fd08430ec9b
>>>>>>>> Author: Freeman Fang <freeman.fang@gmail.com <ma...@gmail.com>>
>>>>>>>> AuthorDate: Thu Oct 19 11:15:50 2017 +0800
>>>>>>>>
>>>>>>>>      [CXF-7532]REST on JMS transport can't handle the request message with text messageType
>>>>>>>> ---
>>>>>>>>   .../org/apache/cxf/jaxrs/impl/ResponseImpl.java    |   5 +
>>>>>>>>   .../org/apache/cxf/jaxrs/utils/JAXRSUtils.java     |   6 +-
>>>>>>>>   .../apache/cxf/jaxrs/utils/ReaderInputStream.java  | 296 +++++++++++++++++++++
>>>>>>>>   .../apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java |  90 +++++++
>>>>>>>>   4 files changed, 396 insertions(+), 1 deletion(-)
>>>>>>>>
>>>>>>>> diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java
>>>>>>>> index e3a3acf..82f183a 100644
>>>>>>>> --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java
>>>>>>>> +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java
>>>>>>>> @@ -58,6 +58,7 @@ import org.apache.cxf.jaxrs.provider.ProviderFactory;
>>>>>>>>   import org.apache.cxf.jaxrs.utils.HttpUtils;
>>>>>>>>   import org.apache.cxf.jaxrs.utils.InjectionUtils;
>>>>>>>>   import org.apache.cxf.jaxrs.utils.JAXRSUtils;
>>>>>>>> +import org.apache.cxf.jaxrs.utils.ReaderInputStream;
>>>>>>>>   import org.apache.cxf.message.Message;
>>>>>>>>   import org.apache.cxf.message.MessageUtils;
>>>>>>>> @@ -342,6 +343,10 @@ public final class ResponseImpl extends Response {
>>>>>>>>               entityStreamAvailable = entityStream != null;
>>>>>>>>           } else if (entity instanceof InputStream) {
>>>>>>>>               entityStream = InputStream.class.cast(entity);
>>>>>>>> +        } else {
>>>>>>>> +            Message inMessage = getResponseMessage();
>>>>>>>> +            Reader reader = inMessage.getContent(Reader.class);
>>>>>>>> +            entityStream = InputStream.class.cast(new ReaderInputStream(reader));
>>>>>>>>           }
>>>>>>>>           // we need to check for readers even if no IS is set - the readers may still do it
>>>>>>>> diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
>>>>>>>> index 0ef6565..509f76d 100644
>>>>>>>> --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
>>>>>>>> +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
>>>>>>>> @@ -832,7 +832,11 @@ public final class JAXRSUtils {
>>>>>>>>                                              OperationResourceInfo ori)
>>>>>>>>           throws IOException, WebApplicationException {
>>>>>>>>           InputStream is = message.getContent(InputStream.class);
>>>>>>>> -
>>>>>>>> +        if (is == null) {
>>>>>>>> +            //may use the jms transport so check the Reader;
>>>>>>>> +            Reader reader = message.getContent(Reader.class);
>>>>>>>> +            is = new ReaderInputStream(reader);
>>>>>>>> +        }
>>>>>>>>           if (parameter.getType() == ParameterType.REQUEST_BODY) {
>>>>>>>>               if (parameterClass == AsyncResponse.class) {
>>>>>>>> diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java
>>>>>>>> new file mode 100644
>>>>>>>> index 0000000..c7b142e
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java
>>>>>>>> @@ -0,0 +1,296 @@
>>>>>>>> +/**
>>>>>>>> + * 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.cxf.jaxrs.utils;
>>>>>>>> +
>>>>>>>> +import java.io.IOException;
>>>>>>>> +import java.io.InputStream;
>>>>>>>> +import java.io.Reader;
>>>>>>>> +import java.nio.ByteBuffer;
>>>>>>>> +import java.nio.CharBuffer;
>>>>>>>> +import java.nio.charset.Charset;
>>>>>>>> +import java.nio.charset.CharsetEncoder;
>>>>>>>> +import java.nio.charset.CoderResult;
>>>>>>>> +import java.nio.charset.CodingErrorAction;
>>>>>>>> +
>>>>>>>> +/**
>>>>>>>> + * {@link InputStream} implementation that reads a character stream from a {@link Reader}
>>>>>>>> + * and transforms it to a byte stream using a specified charset encoding. The stream
>>>>>>>> + * is transformed using a {@link CharsetEncoder} object, guaranteeing that all charset
>>>>>>>> + * encodings supported by the JRE are handled correctly. In particular for charsets such as
>>>>>>>> + * UTF-16, the implementation ensures that one and only one byte order marker
>>>>>>>> + * is produced.
>>>>>>>> + * <p>
>>>>>>>> + * Since in general it is not possible to predict the number of characters to be read from the
>>>>>>>> + * {@link Reader} to satisfy a read request on the {@link ReaderInputStream}, all reads from
>>>>>>>> + * the {@link Reader} are buffered. There is therefore no well defined correlation
>>>>>>>> + * between the current position of the {@link Reader} and that of the {@link ReaderInputStream}.
>>>>>>>> + * This also implies that in general there is no need to wrap the underlying {@link Reader}
>>>>>>>> + * in a {@link java.io.BufferedReader}.
>>>>>>>> + * <p>
>>>>>>>> + * {@link ReaderInputStream} implements the inverse transformation of {@link java.io.InputStreamReader};
>>>>>>>> + * in the following example, reading from <tt>in2</tt> would return the same byte
>>>>>>>> + * sequence as reading from <tt>in</tt> (provided that the initial byte sequence is legal
>>>>>>>> + * with respect to the charset encoding):
>>>>>>>> + * <pre>
>>>>>>>> + * InputStream in = ...
>>>>>>>> + * Charset cs = ...
>>>>>>>> + * InputStreamReader reader = new InputStreamReader(in, cs);
>>>>>>>> + * ReaderInputStream in2 = new ReaderInputStream(reader, cs);</pre>
>>>>>>>> + * {@link ReaderInputStream} implements the same transformation as {@link java.io.OutputStreamWriter},
>>>>>>>> + * except that the control flow is reversed: both classes transform a character stream
>>>>>>>> + * into a byte stream, but {@link java.io.OutputStreamWriter} pushes data to the underlying stream,
>>>>>>>> + * while {@link ReaderInputStream} pulls it from the underlying stream.
>>>>>>>> + * <p>
>>>>>>>> + * Note that while there are use cases where there is no alternative to using
>>>>>>>> + * this class, very often the need to use this class is an indication of a flaw
>>>>>>>> + * in the design of the code. This class is typically used in situations where an existing
>>>>>>>> + * API only accepts an {@link InputStream}, but where the most natural way to produce the data
>>>>>>>> + * is as a character stream, i.e. by providing a {@link Reader} instance. An example of a situation
>>>>>>>> + * where this problem may appear is when implementing the {@link javax.activation.DataSource}
>>>>>>>> + * interface from the Java Activation Framework.
>>>>>>>> + * <p>
>>>>>>>> + * Given the fact that the {@link Reader} class doesn't provide any way to predict whether the next
>>>>>>>> + * read operation will block or not, it is not possible to provide a meaningful
>>>>>>>> + * implementation of the {@link InputStream#available()} method. A call to this method
>>>>>>>> + * will always return 0. Also, this class doesn't support {@link InputStream#mark(int)}.
>>>>>>>> + * <p>
>>>>>>>> + * Instances of {@link ReaderInputStream} are not thread safe.
>>>>>>>> + *
>>>>>>>> + * @see org.apache.commons.io.output.WriterOutputStream
>>>>>>>> + *
>>>>>>>> + * @since 2.0
>>>>>>>> + */
>>>>>>>> +public class ReaderInputStream extends InputStream {
>>>>>>>> +    private static final int DEFAULT_BUFFER_SIZE = 1024;
>>>>>>>> +
>>>>>>>> +    private final Reader reader;
>>>>>>>> +    private final CharsetEncoder encoder;
>>>>>>>> +
>>>>>>>> +    /**
>>>>>>>> +     * CharBuffer used as input for the decoder. It should be reasonably
>>>>>>>> +     * large as we read data from the underlying Reader into this buffer.
>>>>>>>> +     */
>>>>>>>> +    private final CharBuffer encoderIn;
>>>>>>>> +
>>>>>>>> +    /**
>>>>>>>> +     * ByteBuffer used as output for the decoder. This buffer can be small
>>>>>>>> +     * as it is only used to transfer data from the decoder to the
>>>>>>>> +     * buffer provided by the caller.
>>>>>>>> +     */
>>>>>>>> +    private final ByteBuffer encoderOut;
>>>>>>>> +
>>>>>>>> +    private CoderResult lastCoderResult;
>>>>>>>> +    private boolean endOfInput;
>>>>>>>> +
>>>>>>>> +    /**
>>>>>>>> +     * Construct a new {@link ReaderInputStream}.
>>>>>>>> +     *
>>>>>>>> +     * @param reader the target {@link Reader}
>>>>>>>> +     * @param encoder the charset encoder
>>>>>>>> +     * @since 2.1
>>>>>>>> +     */
>>>>>>>> +    public ReaderInputStream(Reader reader, CharsetEncoder encoder) {
>>>>>>>> +        this(reader, encoder, DEFAULT_BUFFER_SIZE);
>>>>>>>> +    }
>>>>>>>> +
>>>>>>>> +    /**
>>>>>>>> +     * Construct a new {@link ReaderInputStream}.
>>>>>>>> +     *
>>>>>>>> +     * @param reader the target {@link Reader}
>>>>>>>> +     * @param encoder the charset encoder
>>>>>>>> +     * @param bufferSize the size of the input buffer in number of characters
>>>>>>>> +     * @since 2.1
>>>>>>>> +     */
>>>>>>>> +    public ReaderInputStream(Reader reader, CharsetEncoder encoder, int bufferSize) {
>>>>>>>> +        this.reader = reader;
>>>>>>>> +        this.encoder = encoder;
>>>>>>>> +        this.encoderIn = CharBuffer.allocate(bufferSize);
>>>>>>>> +        this.encoderIn.flip();
>>>>>>>> +        this.encoderOut = ByteBuffer.allocate(128);
>>>>>>>> +        this.encoderOut.flip();
>>>>>>>> +    }
>>>>>>>> +
>>>>>>>> +    /**
>>>>>>>> +     * Construct a new {@link ReaderInputStream}.
>>>>>>>> +     *
>>>>>>>> +     * @param reader the target {@link Reader}
>>>>>>>> +     * @param charset the charset encoding
>>>>>>>> +     * @param bufferSize the size of the input buffer in number of characters
>>>>>>>> +     */
>>>>>>>> +    public ReaderInputStream(Reader reader, Charset charset, int bufferSize) {
>>>>>>>> +        this(reader,
>>>>>>>> +             charset.newEncoder()
>>>>>>>> +                    .onMalformedInput(CodingErrorAction.REPLACE)
>>>>>>>> +                    .onUnmappableCharacter(CodingErrorAction.REPLACE),
>>>>>>>> +             bufferSize);
>>>>>>>> +    }
>>>>>>>> +
>>>>>>>> +    /**
>>>>>>>> +     * Construct a new {@link ReaderInputStream} with a default input buffer size of
>>>>>>>> +     * 1024 characters.
>>>>>>>> +     *
>>>>>>>> +     * @param reader the target {@link Reader}
>>>>>>>> +     * @param charset the charset encoding
>>>>>>>> +     */
>>>>>>>> +    public ReaderInputStream(Reader reader, Charset charset) {
>>>>>>>> +        this(reader, charset, DEFAULT_BUFFER_SIZE);
>>>>>>>> +    }
>>>>>>>> +
>>>>>>>> +    /**
>>>>>>>> +     * Construct a new {@link ReaderInputStream}.
>>>>>>>> +     *
>>>>>>>> +     * @param reader the target {@link Reader}
>>>>>>>> +     * @param charsetName the name of the charset encoding
>>>>>>>> +     * @param bufferSize the size of the input buffer in number of characters
>>>>>>>> +     */
>>>>>>>> +    public ReaderInputStream(Reader reader, String charsetName, int bufferSize) {
>>>>>>>> +        this(reader, Charset.forName(charsetName), bufferSize);
>>>>>>>> +    }
>>>>>>>> +
>>>>>>>> +    /**
>>>>>>>> +     * Construct a new {@link ReaderInputStream} with a default input buffer size of
>>>>>>>> +     * 1024 characters.
>>>>>>>> +     *
>>>>>>>> +     * @param reader the target {@link Reader}
>>>>>>>> +     * @param charsetName the name of the charset encoding
>>>>>>>> +     */
>>>>>>>> +    public ReaderInputStream(Reader reader, String charsetName) {
>>>>>>>> +        this(reader, charsetName, DEFAULT_BUFFER_SIZE);
>>>>>>>> +    }
>>>>>>>> +
>>>>>>>> +    /**
>>>>>>>> +     * Construct a new {@link ReaderInputStream} that uses the default character encoding
>>>>>>>> +     * with a default input buffer size of 1024 characters.
>>>>>>>> +     *
>>>>>>>> +     * @param reader the target {@link Reader}
>>>>>>>> +     */
>>>>>>>> +    public ReaderInputStream(Reader reader) {
>>>>>>>> +        this(reader, Charset.defaultCharset());
>>>>>>>> +    }
>>>>>>>> +
>>>>>>>> +    /**
>>>>>>>> +     * Fills the internal char buffer from the reader.
>>>>>>>> +     *
>>>>>>>> +     * @throws IOException
>>>>>>>> +     *             If an I/O error occurs
>>>>>>>> +     */
>>>>>>>> +    private void fillBuffer() throws IOException {
>>>>>>>> +        if (!endOfInput && (lastCoderResult == null || lastCoderResult.isUnderflow())) {
>>>>>>>> +            encoderIn.compact();
>>>>>>>> +            int position = encoderIn.position();
>>>>>>>> +            // We don't use Reader#read(CharBuffer) here because it is more efficient
>>>>>>>> +            // to write directly to the underlying char array (the default implementation
>>>>>>>> +            // copies data to a temporary char array).
>>>>>>>> +            int c = reader.read(encoderIn.array(), position, encoderIn.remaining());
>>>>>>>> +            if (c == -1) {
>>>>>>>> +                endOfInput = true;
>>>>>>>> +            } else {
>>>>>>>> +                encoderIn.position(position + c);
>>>>>>>> +            }
>>>>>>>> +            encoderIn.flip();
>>>>>>>> +        }
>>>>>>>> +        encoderOut.compact();
>>>>>>>> +        lastCoderResult = encoder.encode(encoderIn, encoderOut, endOfInput);
>>>>>>>> +        encoderOut.flip();
>>>>>>>> +    }
>>>>>>>> +
>>>>>>>> +    /**
>>>>>>>> +     * Read the specified number of bytes into an array.
>>>>>>>> +     *
>>>>>>>> +     * @param b the byte array to read into
>>>>>>>> +     * @param off the offset to start reading bytes into
>>>>>>>> +     * @param len the number of bytes to read
>>>>>>>> +     * @return the number of bytes read or <code>-1</code>
>>>>>>>> +     *         if the end of the stream has been reached
>>>>>>>> +     * @throws IOException if an I/O error occurs
>>>>>>>> +     */
>>>>>>>> +    @Override
>>>>>>>> +    public int read(byte[] b, int off, int len) throws IOException {
>>>>>>>> +        if (b == null) {
>>>>>>>> +            throw new NullPointerException("Byte array must not be null");
>>>>>>>> +        }
>>>>>>>> +        if (len < 0 || off < 0 || (off + len) > b.length) {
>>>>>>>> +            throw new IndexOutOfBoundsException("Array Size=" + b.length
>>>>>>>> +                    + ", offset=" + off + ", length=" + len);
>>>>>>>> +        }
>>>>>>>> +        int read = 0;
>>>>>>>> +        if (len == 0) {
>>>>>>>> +            return 0; // Always return 0 if len == 0
>>>>>>>> +        }
>>>>>>>> +        while (len > 0) {
>>>>>>>> +            if (encoderOut.hasRemaining()) {
>>>>>>>> +                int c = Math.min(encoderOut.remaining(), len);
>>>>>>>> +                encoderOut.get(b, off, c);
>>>>>>>> +                off += c;
>>>>>>>> +                len -= c;
>>>>>>>> +                read += c;
>>>>>>>> +            } else {
>>>>>>>> +                fillBuffer();
>>>>>>>> +                if (endOfInput && !encoderOut.hasRemaining()) {
>>>>>>>> +                    break;
>>>>>>>> +                }
>>>>>>>> +            }
>>>>>>>> +        }
>>>>>>>> +        return read == 0 && endOfInput ? -1 : read;
>>>>>>>> +    }
>>>>>>>> +
>>>>>>>> +    /**
>>>>>>>> +     * Read the specified number of bytes into an array.
>>>>>>>> +     *
>>>>>>>> +     * @param b the byte array to read into
>>>>>>>> +     * @return the number of bytes read or <code>-1</code>
>>>>>>>> +     *         if the end of the stream has been reached
>>>>>>>> +     * @throws IOException if an I/O error occurs
>>>>>>>> +     */
>>>>>>>> +    @Override
>>>>>>>> +    public int read(byte[] b) throws IOException {
>>>>>>>> +        return read(b, 0, b.length);
>>>>>>>> +    }
>>>>>>>> +
>>>>>>>> +    /**
>>>>>>>> +     * Read a single byte.
>>>>>>>> +     *
>>>>>>>> +     * @return either the byte read or <code>-1</code> if the end of the stream
>>>>>>>> +     *         has been reached
>>>>>>>> +     * @throws IOException if an I/O error occurs
>>>>>>>> +     */
>>>>>>>> +    @Override
>>>>>>>> +    public int read() throws IOException {
>>>>>>>> +        for (;;) {
>>>>>>>> +            if (encoderOut.hasRemaining()) {
>>>>>>>> +                return encoderOut.get() & 0xFF;
>>>>>>>> +            } else {
>>>>>>>> +                fillBuffer();
>>>>>>>> +                if (endOfInput && !encoderOut.hasRemaining()) {
>>>>>>>> +                    return -1;
>>>>>>>> +                }
>>>>>>>> +            }
>>>>>>>> +        }
>>>>>>>> +    }
>>>>>>>> +
>>>>>>>> +    /**
>>>>>>>> +     * Close the stream. This method will cause the underlying {@link Reader}
>>>>>>>> +     * to be closed.
>>>>>>>> +     * @throws IOException if an I/O error occurs
>>>>>>>> +     */
>>>>>>>> +    @Override
>>>>>>>> +    public void close() throws IOException {
>>>>>>>> +        reader.close();
>>>>>>>> +    }
>>>>>>>> +}
>>>>>>>> diff --git a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java
>>>>>>>> index f77b5ed..b957f36 100644
>>>>>>>> --- a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java
>>>>>>>> +++ b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java
>>>>>>>> @@ -96,6 +96,25 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>>>>>>           assertEquals("Get a wrong response code.", 200, client.getResponse().getStatus());
>>>>>>>>           assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>>       }
>>>>>>>> +
>>>>>>>> +    @Test
>>>>>>>> +    public void testGetBookFromWebClientWithTextJMSMessage() throws Exception {
>>>>>>>> +        // setup the the client
>>>>>>>> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>>>>> +             + "?replyToName=dynamicQueues/test.jmstransport.response"
>>>>>>>> +             + "&jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory"
>>>>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>>>>> +             + "&messageType=text";
>>>>>>>> +
>>>>>>>> +        WebClient client = WebClient.create(endpointAddressUrlEncoded);
>>>>>>>> +        WebClient.getConfig(client).getInInterceptors().add(new LoggingInInterceptor());
>>>>>>>> +        WebClient.getConfig(client).getRequestContext()
>>>>>>>> +            .put(org.apache.cxf.message.Message.REQUEST_URI, "/bookstore/books/123");
>>>>>>>> +
>>>>>>>> +        Book book = client.get(Book.class);
>>>>>>>> +        assertEquals("Get a wrong response code.", 200, client.getResponse().getStatus());
>>>>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>> +    }
>>>>>>>>       @Test
>>>>>>>>       public void testPutBookOneWayWithWebClient() throws Exception {
>>>>>>>> @@ -130,6 +149,7 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>>>>>>           }
>>>>>>>>       }
>>>>>>>> +
>>>>>>>>       @Test
>>>>>>>>       public void testGetBookFromWebClientWithPath() throws Exception {
>>>>>>>>           // setup the the client
>>>>>>>> @@ -148,6 +168,25 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>>>>>>       }
>>>>>>>>       @Test
>>>>>>>> +    public void testGetBookFromWebClientWithPathWithTextJMSMessage() throws Exception {
>>>>>>>> +        // setup the the client
>>>>>>>> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>>>>> +             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory"
>>>>>>>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>>>>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>>>>>> +             + "&messageType=text";
>>>>>>>> +
>>>>>>>> +        WebClient client = WebClient.create(endpointAddressUrlEncoded);
>>>>>>>> +        client.path("bookstore").path("books").path("123");
>>>>>>>> +
>>>>>>>> +        Book book = client.get(Book.class);
>>>>>>>> +        assertEquals("Get a wrong response code.", 200, client.getResponse().getStatus());
>>>>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>> +    }
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +    @Test
>>>>>>>>       public void testGetBookFromProxyClient() throws Exception {
>>>>>>>>           // setup the the client
>>>>>>>>           String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>>>>> @@ -161,6 +200,22 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>>>>>>           assertEquals("Get a wrong response code.", 200, WebClient.client(client).getResponse().getStatus());
>>>>>>>>           assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>>       }
>>>>>>>> +
>>>>>>>> +    @Test
>>>>>>>> +    public void testGetBookFromProxyClientWithTextJMSMessage() throws Exception {
>>>>>>>> +        // setup the the client
>>>>>>>> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>>>>> +             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory"
>>>>>>>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>>>>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>>>>>> +             + "&messageType=text";
>>>>>>>> +
>>>>>>>> +        JMSBookStore client = JAXRSClientFactory.create(endpointAddressUrlEncoded, JMSBookStore.class);
>>>>>>>> +        Book book = client.getBook("123");
>>>>>>>> +        assertEquals("Get a wrong response code.", 200, WebClient.client(client).getResponse().getStatus());
>>>>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>> +    }
>>>>>>>>       @Test
>>>>>>>>       public void testGetBookFromSubresourceProxyClient() throws Exception {
>>>>>>>> @@ -177,6 +232,23 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>>>>>>           assertEquals("Get a wrong response code.", 200, WebClient.client(bookProxy).getResponse().getStatus());
>>>>>>>>           assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>>       }
>>>>>>>> +
>>>>>>>> +    @Test
>>>>>>>> +    public void testGetBookFromSubresourceProxyClientWithTextJMSMessage() throws Exception {
>>>>>>>> +        // setup the the client
>>>>>>>> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>>>>> +             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory"
>>>>>>>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>>>>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>>>>>> +             + "&messageType=text";
>>>>>>>> +
>>>>>>>> +        JMSBookStore client = JAXRSClientFactory.create(endpointAddressUrlEncoded, JMSBookStore.class);
>>>>>>>> +        Book bookProxy = client.getBookSubResource("123");
>>>>>>>> +        Book book = bookProxy.retrieveState();
>>>>>>>> +        assertEquals("Get a wrong response code.", 200, WebClient.client(bookProxy).getResponse().getStatus());
>>>>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>> +    }
>>>>>>>>       @Test
>>>>>>>>       public void testGetBookFromProxyClientWithQuery() throws Exception {
>>>>>>>> @@ -193,6 +265,24 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>>>>>>           assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>>       }
>>>>>>>> +
>>>>>>>> +    @Test
>>>>>>>> +    public void testGetBookFromProxyClientWithQueryWithTextJMSMessage() throws Exception {
>>>>>>>> +        // setup the the client
>>>>>>>> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>>>>> +             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory"
>>>>>>>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>>>>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>>>>>> +             + "&messageType=text";
>>>>>>>> +
>>>>>>>> +        JMSBookStore client = JAXRSClientFactory.create(endpointAddressUrlEncoded, JMSBookStore.class);
>>>>>>>> +        Book book = client.getBookByURLQuery(new String[] {"1", "2", "3"});
>>>>>>>> +        assertEquals("Get a wrong response code.", 200, WebClient.client(client).getResponse().getStatus());
>>>>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>> +    }
>>>>>>>> +
>>>>>>>> +
>>>>>>>>       @Test
>>>>>>>>       public void testGetBook() throws Exception {
>>>>>>>>           Context ctx = getContext();
>>>>>>>>
>>>>>>
>>>>>
>>>
>>>
>>> -- 
>>> Sergey Beryozkin
>>>
>>> Talend Community Coders
>>> http://coders.talend.com/
>>
> 

Re: [cxf] branch master updated: [CXF-7532]REST on JMS transport can't handle the request message with text messageType

Posted by Daniel Kulp <dk...@apache.org>.
If using the TEXT stuff for JMS, it should remain as a Reader on the message if at all possible.  Converting from Reader to InputStream is not a trivial operation and does enact a performance penalty.   All of the SOAP pathways for JMS should be OK for use of a Reader so that we can avoid the entire String->UTF-8 bytes->chars round tripping.  

Dan


On Oct 19, 2017, at 5:21 AM, ffang <fr...@gmail.com> wrote:
> 
> I modified the JMS transport part and set ReaderInputStream instead of Reader, running all tests now, will see what I can get.
> 
> If I run into any test failure, I will keep things as is, just clean up.
> -------------
> Freeman(Yue) Fang
> 
> Red Hat, Inc. 
> FuseSource is now part of Red Hat
> 
> 
> 
>> On Oct 19, 2017, at 5:16 PM, Sergey Beryozkin <sb...@gmail.com> wrote:
>> 
>> If we assume that checking Reader can be useful in the general sense, then may be lets keep things as is, with the minor cleanup only...
>> 
>> Sergey
>> On 19/10/17 10:05, Sergey Beryozkin wrote:
>>> Hi Freeman
>>> Reader may likely need to be set too for SOAP, and I recall checking Reader somewhere else too (will double check now), but I thought may be the transport can set both Reader and InputStream, hmm, that is an extra instance though, may be lets start with moving ReaderInputStream to the core first ?
>>> Sergey
>>> On 19/10/17 09:59, ffang wrote:
>>>> Yeah, set ReaderInputStream instead of Reader for the JMS transport is a better solution. If no objection I will go this way.
>>>> -------------
>>>> Freeman(Yue) Fang
>>>> 
>>>> Red Hat, Inc.
>>>> FuseSource is now part of Red Hat
>>>> 
>>>> 
>>>> 
>>>>> On Oct 19, 2017, at 4:50 PM, Sergey Beryozkin <sberyozkin@gmail.com <ma...@gmail.com>> wrote:
>>>>> 
>>>>> The other thing is, should JMS transport itself set ReaderInputStream as InputStream on the message, as opposed to patching the JAX-RS code in various places ?
>>>>> 
>>>>> Sergey
>>>>> On 19/10/17 09:44, Sergey Beryozkin wrote:
>>>>>> Hi Freeman
>>>>>> Can you move ReaderInputStream to the CXF core where CachedInputStream and other IO utility code is located ?
>>>>>> Please also update the code which gets Reader.class and check it for null first, and also drop a comment re the JMS transport, checking the reader can make sense in all the cases...
>>>>>> Thanks, Sergey
>>>>>> On 19/10/17 04:16, ffang@apache.org <ma...@apache.org> wrote:
>>>>>>> This is an automated email from the ASF dual-hosted git repository.
>>>>>>> 
>>>>>>> ffang pushed a commit to branch master
>>>>>>> in repository https://gitbox.apache.org/repos/asf/cxf.git
>>>>>>> 
>>>>>>> 
>>>>>>> The following commit(s) were added to refs/heads/master by this push:
>>>>>>>      new 1d66475  [CXF-7532]REST on JMS transport can't handle the request message with text messageType
>>>>>>> 1d66475 is described below
>>>>>>> 
>>>>>>> commit 1d66475b4dea2a0031dcb1765e8e9fd08430ec9b
>>>>>>> Author: Freeman Fang <freeman.fang@gmail.com <ma...@gmail.com>>
>>>>>>> AuthorDate: Thu Oct 19 11:15:50 2017 +0800
>>>>>>> 
>>>>>>>     [CXF-7532]REST on JMS transport can't handle the request message with text messageType
>>>>>>> ---
>>>>>>>  .../org/apache/cxf/jaxrs/impl/ResponseImpl.java    |   5 +
>>>>>>>  .../org/apache/cxf/jaxrs/utils/JAXRSUtils.java     |   6 +-
>>>>>>>  .../apache/cxf/jaxrs/utils/ReaderInputStream.java  | 296 +++++++++++++++++++++
>>>>>>>  .../apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java |  90 +++++++
>>>>>>>  4 files changed, 396 insertions(+), 1 deletion(-)
>>>>>>> 
>>>>>>> diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java 
>>>>>>> index e3a3acf..82f183a 100644
>>>>>>> --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java 
>>>>>>> +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java 
>>>>>>> @@ -58,6 +58,7 @@ import org.apache.cxf.jaxrs.provider.ProviderFactory;
>>>>>>>  import org.apache.cxf.jaxrs.utils.HttpUtils;
>>>>>>>  import org.apache.cxf.jaxrs.utils.InjectionUtils;
>>>>>>>  import org.apache.cxf.jaxrs.utils.JAXRSUtils;
>>>>>>> +import org.apache.cxf.jaxrs.utils.ReaderInputStream;
>>>>>>>  import org.apache.cxf.message.Message;
>>>>>>>  import org.apache.cxf.message.MessageUtils;
>>>>>>> @@ -342,6 +343,10 @@ public final class ResponseImpl extends Response {
>>>>>>>              entityStreamAvailable = entityStream != null;
>>>>>>>          } else if (entity instanceof InputStream) {
>>>>>>>              entityStream = InputStream.class.cast(entity);
>>>>>>> +        } else {
>>>>>>> +            Message inMessage = getResponseMessage();
>>>>>>> +            Reader reader = inMessage.getContent(Reader.class);
>>>>>>> +            entityStream = InputStream.class.cast(new ReaderInputStream(reader));
>>>>>>>          }
>>>>>>>          // we need to check for readers even if no IS is set - the readers may still do it
>>>>>>> diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java 
>>>>>>> index 0ef6565..509f76d 100644
>>>>>>> --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java 
>>>>>>> +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java 
>>>>>>> @@ -832,7 +832,11 @@ public final class JAXRSUtils {
>>>>>>>                                             OperationResourceInfo ori)
>>>>>>>          throws IOException, WebApplicationException {
>>>>>>>          InputStream is = message.getContent(InputStream.class);
>>>>>>> -
>>>>>>> +        if (is == null) {
>>>>>>> +            //may use the jms transport so check the Reader;
>>>>>>> +            Reader reader = message.getContent(Reader.class);
>>>>>>> +            is = new ReaderInputStream(reader);
>>>>>>> +        }
>>>>>>>          if (parameter.getType() == ParameterType.REQUEST_BODY) {
>>>>>>>              if (parameterClass == AsyncResponse.class) {
>>>>>>> diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java 
>>>>>>> new file mode 100644
>>>>>>> index 0000000..c7b142e
>>>>>>> --- /dev/null
>>>>>>> +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java 
>>>>>>> @@ -0,0 +1,296 @@
>>>>>>> +/**
>>>>>>> + * 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.cxf.jaxrs.utils;
>>>>>>> +
>>>>>>> +import java.io.IOException;
>>>>>>> +import java.io.InputStream;
>>>>>>> +import java.io.Reader;
>>>>>>> +import java.nio.ByteBuffer;
>>>>>>> +import java.nio.CharBuffer;
>>>>>>> +import java.nio.charset.Charset;
>>>>>>> +import java.nio.charset.CharsetEncoder;
>>>>>>> +import java.nio.charset.CoderResult;
>>>>>>> +import java.nio.charset.CodingErrorAction;
>>>>>>> +
>>>>>>> +/**
>>>>>>> + * {@link InputStream} implementation that reads a character stream from a {@link Reader}
>>>>>>> + * and transforms it to a byte stream using a specified charset encoding. The stream
>>>>>>> + * is transformed using a {@link CharsetEncoder} object, guaranteeing that all charset
>>>>>>> + * encodings supported by the JRE are handled correctly. In particular for charsets such as
>>>>>>> + * UTF-16, the implementation ensures that one and only one byte order marker
>>>>>>> + * is produced.
>>>>>>> + * <p>
>>>>>>> + * Since in general it is not possible to predict the number of characters to be read from the
>>>>>>> + * {@link Reader} to satisfy a read request on the {@link ReaderInputStream}, all reads from
>>>>>>> + * the {@link Reader} are buffered. There is therefore no well defined correlation
>>>>>>> + * between the current position of the {@link Reader} and that of the {@link ReaderInputStream}.
>>>>>>> + * This also implies that in general there is no need to wrap the underlying {@link Reader}
>>>>>>> + * in a {@link java.io.BufferedReader}.
>>>>>>> + * <p>
>>>>>>> + * {@link ReaderInputStream} implements the inverse transformation of {@link java.io.InputStreamReader};
>>>>>>> + * in the following example, reading from <tt>in2</tt> would return the same byte
>>>>>>> + * sequence as reading from <tt>in</tt> (provided that the initial byte sequence is legal
>>>>>>> + * with respect to the charset encoding):
>>>>>>> + * <pre>
>>>>>>> + * InputStream in = ...
>>>>>>> + * Charset cs = ...
>>>>>>> + * InputStreamReader reader = new InputStreamReader(in, cs);
>>>>>>> + * ReaderInputStream in2 = new ReaderInputStream(reader, cs);</pre>
>>>>>>> + * {@link ReaderInputStream} implements the same transformation as {@link java.io.OutputStreamWriter},
>>>>>>> + * except that the control flow is reversed: both classes transform a character stream
>>>>>>> + * into a byte stream, but {@link java.io.OutputStreamWriter} pushes data to the underlying stream,
>>>>>>> + * while {@link ReaderInputStream} pulls it from the underlying stream.
>>>>>>> + * <p>
>>>>>>> + * Note that while there are use cases where there is no alternative to using
>>>>>>> + * this class, very often the need to use this class is an indication of a flaw
>>>>>>> + * in the design of the code. This class is typically used in situations where an existing
>>>>>>> + * API only accepts an {@link InputStream}, but where the most natural way to produce the data
>>>>>>> + * is as a character stream, i.e. by providing a {@link Reader} instance. An example of a situation
>>>>>>> + * where this problem may appear is when implementing the {@link javax.activation.DataSource}
>>>>>>> + * interface from the Java Activation Framework.
>>>>>>> + * <p>
>>>>>>> + * Given the fact that the {@link Reader} class doesn't provide any way to predict whether the next
>>>>>>> + * read operation will block or not, it is not possible to provide a meaningful
>>>>>>> + * implementation of the {@link InputStream#available()} method. A call to this method
>>>>>>> + * will always return 0. Also, this class doesn't support {@link InputStream#mark(int)}.
>>>>>>> + * <p>
>>>>>>> + * Instances of {@link ReaderInputStream} are not thread safe.
>>>>>>> + *
>>>>>>> + * @see org.apache.commons.io.output.WriterOutputStream
>>>>>>> + *
>>>>>>> + * @since 2.0
>>>>>>> + */
>>>>>>> +public class ReaderInputStream extends InputStream {
>>>>>>> +    private static final int DEFAULT_BUFFER_SIZE = 1024;
>>>>>>> +
>>>>>>> +    private final Reader reader;
>>>>>>> +    private final CharsetEncoder encoder;
>>>>>>> +
>>>>>>> +    /**
>>>>>>> +     * CharBuffer used as input for the decoder. It should be reasonably
>>>>>>> +     * large as we read data from the underlying Reader into this buffer.
>>>>>>> +     */
>>>>>>> +    private final CharBuffer encoderIn;
>>>>>>> +
>>>>>>> +    /**
>>>>>>> +     * ByteBuffer used as output for the decoder. This buffer can be small
>>>>>>> +     * as it is only used to transfer data from the decoder to the
>>>>>>> +     * buffer provided by the caller.
>>>>>>> +     */
>>>>>>> +    private final ByteBuffer encoderOut;
>>>>>>> +
>>>>>>> +    private CoderResult lastCoderResult;
>>>>>>> +    private boolean endOfInput;
>>>>>>> +
>>>>>>> +    /**
>>>>>>> +     * Construct a new {@link ReaderInputStream}.
>>>>>>> +     *
>>>>>>> +     * @param reader the target {@link Reader}
>>>>>>> +     * @param encoder the charset encoder
>>>>>>> +     * @since 2.1
>>>>>>> +     */
>>>>>>> +    public ReaderInputStream(Reader reader, CharsetEncoder encoder) {
>>>>>>> +        this(reader, encoder, DEFAULT_BUFFER_SIZE);
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    /**
>>>>>>> +     * Construct a new {@link ReaderInputStream}.
>>>>>>> +     *
>>>>>>> +     * @param reader the target {@link Reader}
>>>>>>> +     * @param encoder the charset encoder
>>>>>>> +     * @param bufferSize the size of the input buffer in number of characters
>>>>>>> +     * @since 2.1
>>>>>>> +     */
>>>>>>> +    public ReaderInputStream(Reader reader, CharsetEncoder encoder, int bufferSize) {
>>>>>>> +        this.reader = reader;
>>>>>>> +        this.encoder = encoder;
>>>>>>> +        this.encoderIn = CharBuffer.allocate(bufferSize);
>>>>>>> +        this.encoderIn.flip();
>>>>>>> +        this.encoderOut = ByteBuffer.allocate(128);
>>>>>>> +        this.encoderOut.flip();
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    /**
>>>>>>> +     * Construct a new {@link ReaderInputStream}.
>>>>>>> +     *
>>>>>>> +     * @param reader the target {@link Reader}
>>>>>>> +     * @param charset the charset encoding
>>>>>>> +     * @param bufferSize the size of the input buffer in number of characters
>>>>>>> +     */
>>>>>>> +    public ReaderInputStream(Reader reader, Charset charset, int bufferSize) {
>>>>>>> +        this(reader,
>>>>>>> +             charset.newEncoder()
>>>>>>> +                    .onMalformedInput(CodingErrorAction.REPLACE)
>>>>>>> +                    .onUnmappableCharacter(CodingErrorAction.REPLACE),
>>>>>>> +             bufferSize);
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    /**
>>>>>>> +     * Construct a new {@link ReaderInputStream} with a default input buffer size of
>>>>>>> +     * 1024 characters.
>>>>>>> +     *
>>>>>>> +     * @param reader the target {@link Reader}
>>>>>>> +     * @param charset the charset encoding
>>>>>>> +     */
>>>>>>> +    public ReaderInputStream(Reader reader, Charset charset) {
>>>>>>> +        this(reader, charset, DEFAULT_BUFFER_SIZE);
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    /**
>>>>>>> +     * Construct a new {@link ReaderInputStream}.
>>>>>>> +     *
>>>>>>> +     * @param reader the target {@link Reader}
>>>>>>> +     * @param charsetName the name of the charset encoding
>>>>>>> +     * @param bufferSize the size of the input buffer in number of characters
>>>>>>> +     */
>>>>>>> +    public ReaderInputStream(Reader reader, String charsetName, int bufferSize) {
>>>>>>> +        this(reader, Charset.forName(charsetName), bufferSize);
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    /**
>>>>>>> +     * Construct a new {@link ReaderInputStream} with a default input buffer size of
>>>>>>> +     * 1024 characters.
>>>>>>> +     *
>>>>>>> +     * @param reader the target {@link Reader}
>>>>>>> +     * @param charsetName the name of the charset encoding
>>>>>>> +     */
>>>>>>> +    public ReaderInputStream(Reader reader, String charsetName) {
>>>>>>> +        this(reader, charsetName, DEFAULT_BUFFER_SIZE);
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    /**
>>>>>>> +     * Construct a new {@link ReaderInputStream} that uses the default character encoding
>>>>>>> +     * with a default input buffer size of 1024 characters.
>>>>>>> +     *
>>>>>>> +     * @param reader the target {@link Reader}
>>>>>>> +     */
>>>>>>> +    public ReaderInputStream(Reader reader) {
>>>>>>> +        this(reader, Charset.defaultCharset());
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    /**
>>>>>>> +     * Fills the internal char buffer from the reader.
>>>>>>> +     *
>>>>>>> +     * @throws IOException
>>>>>>> +     *             If an I/O error occurs
>>>>>>> +     */
>>>>>>> +    private void fillBuffer() throws IOException {
>>>>>>> +        if (!endOfInput && (lastCoderResult == null || lastCoderResult.isUnderflow())) {
>>>>>>> +            encoderIn.compact();
>>>>>>> +            int position = encoderIn.position();
>>>>>>> +            // We don't use Reader#read(CharBuffer) here because it is more efficient
>>>>>>> +            // to write directly to the underlying char array (the default implementation
>>>>>>> +            // copies data to a temporary char array).
>>>>>>> +            int c = reader.read(encoderIn.array(), position, encoderIn.remaining());
>>>>>>> +            if (c == -1) {
>>>>>>> +                endOfInput = true;
>>>>>>> +            } else {
>>>>>>> +                encoderIn.position(position + c);
>>>>>>> +            }
>>>>>>> +            encoderIn.flip();
>>>>>>> +        }
>>>>>>> +        encoderOut.compact();
>>>>>>> +        lastCoderResult = encoder.encode(encoderIn, encoderOut, endOfInput);
>>>>>>> +        encoderOut.flip();
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    /**
>>>>>>> +     * Read the specified number of bytes into an array.
>>>>>>> +     *
>>>>>>> +     * @param b the byte array to read into
>>>>>>> +     * @param off the offset to start reading bytes into
>>>>>>> +     * @param len the number of bytes to read
>>>>>>> +     * @return the number of bytes read or <code>-1</code>
>>>>>>> +     *         if the end of the stream has been reached
>>>>>>> +     * @throws IOException if an I/O error occurs
>>>>>>> +     */
>>>>>>> +    @Override
>>>>>>> +    public int read(byte[] b, int off, int len) throws IOException {
>>>>>>> +        if (b == null) {
>>>>>>> +            throw new NullPointerException("Byte array must not be null");
>>>>>>> +        }
>>>>>>> +        if (len < 0 || off < 0 || (off + len) > b.length) {
>>>>>>> +            throw new IndexOutOfBoundsException("Array Size=" + b.length
>>>>>>> +                    + ", offset=" + off + ", length=" + len);
>>>>>>> +        }
>>>>>>> +        int read = 0;
>>>>>>> +        if (len == 0) {
>>>>>>> +            return 0; // Always return 0 if len == 0
>>>>>>> +        }
>>>>>>> +        while (len > 0) {
>>>>>>> +            if (encoderOut.hasRemaining()) {
>>>>>>> +                int c = Math.min(encoderOut.remaining(), len);
>>>>>>> +                encoderOut.get(b, off, c);
>>>>>>> +                off += c;
>>>>>>> +                len -= c;
>>>>>>> +                read += c;
>>>>>>> +            } else {
>>>>>>> +                fillBuffer();
>>>>>>> +                if (endOfInput && !encoderOut.hasRemaining()) {
>>>>>>> +                    break;
>>>>>>> +                }
>>>>>>> +            }
>>>>>>> +        }
>>>>>>> +        return read == 0 && endOfInput ? -1 : read;
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    /**
>>>>>>> +     * Read the specified number of bytes into an array.
>>>>>>> +     *
>>>>>>> +     * @param b the byte array to read into
>>>>>>> +     * @return the number of bytes read or <code>-1</code>
>>>>>>> +     *         if the end of the stream has been reached
>>>>>>> +     * @throws IOException if an I/O error occurs
>>>>>>> +     */
>>>>>>> +    @Override
>>>>>>> +    public int read(byte[] b) throws IOException {
>>>>>>> +        return read(b, 0, b.length);
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    /**
>>>>>>> +     * Read a single byte.
>>>>>>> +     *
>>>>>>> +     * @return either the byte read or <code>-1</code> if the end of the stream
>>>>>>> +     *         has been reached
>>>>>>> +     * @throws IOException if an I/O error occurs
>>>>>>> +     */
>>>>>>> +    @Override
>>>>>>> +    public int read() throws IOException {
>>>>>>> +        for (;;) {
>>>>>>> +            if (encoderOut.hasRemaining()) {
>>>>>>> +                return encoderOut.get() & 0xFF;
>>>>>>> +            } else {
>>>>>>> +                fillBuffer();
>>>>>>> +                if (endOfInput && !encoderOut.hasRemaining()) {
>>>>>>> +                    return -1;
>>>>>>> +                }
>>>>>>> +            }
>>>>>>> +        }
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    /**
>>>>>>> +     * Close the stream. This method will cause the underlying {@link Reader}
>>>>>>> +     * to be closed.
>>>>>>> +     * @throws IOException if an I/O error occurs
>>>>>>> +     */
>>>>>>> +    @Override
>>>>>>> +    public void close() throws IOException {
>>>>>>> +        reader.close();
>>>>>>> +    }
>>>>>>> +}
>>>>>>> diff --git a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java 
>>>>>>> index f77b5ed..b957f36 100644
>>>>>>> --- a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java 
>>>>>>> +++ b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java 
>>>>>>> @@ -96,6 +96,25 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>>>>>          assertEquals("Get a wrong response code.", 200, client.getResponse().getStatus());
>>>>>>>          assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>      }
>>>>>>> +
>>>>>>> +    @Test
>>>>>>> +    public void testGetBookFromWebClientWithTextJMSMessage() throws Exception {
>>>>>>> +        // setup the the client
>>>>>>> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>>>> +             + "?replyToName=dynamicQueues/test.jmstransport.response"
>>>>>>> +             + "&jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>>>> +             + "&messageType=text";
>>>>>>> +
>>>>>>> +        WebClient client = WebClient.create(endpointAddressUrlEncoded);
>>>>>>> +        WebClient.getConfig(client).getInInterceptors().add(new LoggingInInterceptor());
>>>>>>> +        WebClient.getConfig(client).getRequestContext()
>>>>>>> +            .put(org.apache.cxf.message.Message.REQUEST_URI, "/bookstore/books/123");
>>>>>>> +
>>>>>>> +        Book book = client.get(Book.class);
>>>>>>> +        assertEquals("Get a wrong response code.", 200, client.getResponse().getStatus());
>>>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>> +    }
>>>>>>>      @Test
>>>>>>>      public void testPutBookOneWayWithWebClient() throws Exception {
>>>>>>> @@ -130,6 +149,7 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>>>>>          }
>>>>>>>      }
>>>>>>> +
>>>>>>>      @Test
>>>>>>>      public void testGetBookFromWebClientWithPath() throws Exception {
>>>>>>>          // setup the the client
>>>>>>> @@ -148,6 +168,25 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>>>>>      }
>>>>>>>      @Test
>>>>>>> +    public void testGetBookFromWebClientWithPathWithTextJMSMessage() throws Exception {
>>>>>>> +        // setup the the client
>>>>>>> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>>>> +             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>>>>>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>>>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>>>>> +             + "&messageType=text";
>>>>>>> +
>>>>>>> +        WebClient client = WebClient.create(endpointAddressUrlEncoded);
>>>>>>> +        client.path("bookstore").path("books").path("123");
>>>>>>> +
>>>>>>> +        Book book = client.get(Book.class);
>>>>>>> +        assertEquals("Get a wrong response code.", 200, client.getResponse().getStatus());
>>>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>> +    }
>>>>>>> +
>>>>>>> +
>>>>>>> +    @Test
>>>>>>>      public void testGetBookFromProxyClient() throws Exception {
>>>>>>>          // setup the the client
>>>>>>>          String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>>>> @@ -161,6 +200,22 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>>>>>          assertEquals("Get a wrong response code.", 200, WebClient.client(client).getResponse().getStatus());
>>>>>>>          assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>      }
>>>>>>> +
>>>>>>> +    @Test
>>>>>>> +    public void testGetBookFromProxyClientWithTextJMSMessage() throws Exception {
>>>>>>> +        // setup the the client
>>>>>>> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>>>> +             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>>>>>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>>>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>>>>> +             + "&messageType=text";
>>>>>>> +
>>>>>>> +        JMSBookStore client = JAXRSClientFactory.create(endpointAddressUrlEncoded, JMSBookStore.class);
>>>>>>> +        Book book = client.getBook("123");
>>>>>>> +        assertEquals("Get a wrong response code.", 200, WebClient.client(client).getResponse().getStatus());
>>>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>> +    }
>>>>>>>      @Test
>>>>>>>      public void testGetBookFromSubresourceProxyClient() throws Exception {
>>>>>>> @@ -177,6 +232,23 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>>>>>          assertEquals("Get a wrong response code.", 200, WebClient.client(bookProxy).getResponse().getStatus());
>>>>>>>          assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>      }
>>>>>>> +
>>>>>>> +    @Test
>>>>>>> +    public void testGetBookFromSubresourceProxyClientWithTextJMSMessage() throws Exception {
>>>>>>> +        // setup the the client
>>>>>>> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>>>> +             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>>>>>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>>>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>>>>> +             + "&messageType=text";
>>>>>>> +
>>>>>>> +        JMSBookStore client = JAXRSClientFactory.create(endpointAddressUrlEncoded, JMSBookStore.class);
>>>>>>> +        Book bookProxy = client.getBookSubResource("123");
>>>>>>> +        Book book = bookProxy.retrieveState();
>>>>>>> +        assertEquals("Get a wrong response code.", 200, WebClient.client(bookProxy).getResponse().getStatus());
>>>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>> +    }
>>>>>>>      @Test
>>>>>>>      public void testGetBookFromProxyClientWithQuery() throws Exception {
>>>>>>> @@ -193,6 +265,24 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>>>>>          assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>>      }
>>>>>>> +
>>>>>>> +    @Test
>>>>>>> +    public void testGetBookFromProxyClientWithQueryWithTextJMSMessage() throws Exception {
>>>>>>> +        // setup the the client
>>>>>>> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>>>> +             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>>>>>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>>>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>>>>> +             + "&messageType=text";
>>>>>>> +
>>>>>>> +        JMSBookStore client = JAXRSClientFactory.create(endpointAddressUrlEncoded, JMSBookStore.class);
>>>>>>> +        Book book = client.getBookByURLQuery(new String[] {"1", "2", "3"});
>>>>>>> +        assertEquals("Get a wrong response code.", 200, WebClient.client(client).getResponse().getStatus());
>>>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>> +    }
>>>>>>> +
>>>>>>> +
>>>>>>>      @Test
>>>>>>>      public void testGetBook() throws Exception {
>>>>>>>          Context ctx = getContext();
>>>>>>> 
>>>>> 
>>>> 
>> 
>> 
>> -- 
>> Sergey Beryozkin
>> 
>> Talend Community Coders
>> http://coders.talend.com/
> 

-- 
Daniel Kulp
dkulp@apache.org - http://dankulp.com/blog
Talend Community Coder - http://coders.talend.com


Re: [cxf] branch master updated: [CXF-7532]REST on JMS transport can't handle the request message with text messageType

Posted by ffang <fr...@gmail.com>.
I modified the JMS transport part and set ReaderInputStream instead of Reader, running all tests now, will see what I can get.

If I run into any test failure, I will keep things as is, just clean up.
-------------
Freeman(Yue) Fang

Red Hat, Inc. 
FuseSource is now part of Red Hat



> On Oct 19, 2017, at 5:16 PM, Sergey Beryozkin <sb...@gmail.com> wrote:
> 
> If we assume that checking Reader can be useful in the general sense, then may be lets keep things as is, with the minor cleanup only...
> 
> Sergey
> On 19/10/17 10:05, Sergey Beryozkin wrote:
>> Hi Freeman
>> Reader may likely need to be set too for SOAP, and I recall checking Reader somewhere else too (will double check now), but I thought may be the transport can set both Reader and InputStream, hmm, that is an extra instance though, may be lets start with moving ReaderInputStream to the core first ?
>> Sergey
>> On 19/10/17 09:59, ffang wrote:
>>> Yeah, set ReaderInputStream instead of Reader for the JMS transport is a better solution. If no objection I will go this way.
>>> -------------
>>> Freeman(Yue) Fang
>>> 
>>> Red Hat, Inc.
>>> FuseSource is now part of Red Hat
>>> 
>>> 
>>> 
>>>> On Oct 19, 2017, at 4:50 PM, Sergey Beryozkin <sberyozkin@gmail.com <ma...@gmail.com>> wrote:
>>>> 
>>>> The other thing is, should JMS transport itself set ReaderInputStream as InputStream on the message, as opposed to patching the JAX-RS code in various places ?
>>>> 
>>>> Sergey
>>>> On 19/10/17 09:44, Sergey Beryozkin wrote:
>>>>> Hi Freeman
>>>>> Can you move ReaderInputStream to the CXF core where CachedInputStream and other IO utility code is located ?
>>>>> Please also update the code which gets Reader.class and check it for null first, and also drop a comment re the JMS transport, checking the reader can make sense in all the cases...
>>>>> Thanks, Sergey
>>>>> On 19/10/17 04:16, ffang@apache.org <ma...@apache.org> wrote:
>>>>>> This is an automated email from the ASF dual-hosted git repository.
>>>>>> 
>>>>>> ffang pushed a commit to branch master
>>>>>> in repository https://gitbox.apache.org/repos/asf/cxf.git
>>>>>> 
>>>>>> 
>>>>>> The following commit(s) were added to refs/heads/master by this push:
>>>>>>       new 1d66475  [CXF-7532]REST on JMS transport can't handle the request message with text messageType
>>>>>> 1d66475 is described below
>>>>>> 
>>>>>> commit 1d66475b4dea2a0031dcb1765e8e9fd08430ec9b
>>>>>> Author: Freeman Fang <freeman.fang@gmail.com <ma...@gmail.com>>
>>>>>> AuthorDate: Thu Oct 19 11:15:50 2017 +0800
>>>>>> 
>>>>>>      [CXF-7532]REST on JMS transport can't handle the request message with text messageType
>>>>>> ---
>>>>>>   .../org/apache/cxf/jaxrs/impl/ResponseImpl.java    |   5 +
>>>>>>   .../org/apache/cxf/jaxrs/utils/JAXRSUtils.java     |   6 +-
>>>>>>   .../apache/cxf/jaxrs/utils/ReaderInputStream.java  | 296 +++++++++++++++++++++
>>>>>>   .../apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java |  90 +++++++
>>>>>>   4 files changed, 396 insertions(+), 1 deletion(-)
>>>>>> 
>>>>>> diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java 
>>>>>> index e3a3acf..82f183a 100644
>>>>>> --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java 
>>>>>> +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java 
>>>>>> @@ -58,6 +58,7 @@ import org.apache.cxf.jaxrs.provider.ProviderFactory;
>>>>>>   import org.apache.cxf.jaxrs.utils.HttpUtils;
>>>>>>   import org.apache.cxf.jaxrs.utils.InjectionUtils;
>>>>>>   import org.apache.cxf.jaxrs.utils.JAXRSUtils;
>>>>>> +import org.apache.cxf.jaxrs.utils.ReaderInputStream;
>>>>>>   import org.apache.cxf.message.Message;
>>>>>>   import org.apache.cxf.message.MessageUtils;
>>>>>> @@ -342,6 +343,10 @@ public final class ResponseImpl extends Response {
>>>>>>               entityStreamAvailable = entityStream != null;
>>>>>>           } else if (entity instanceof InputStream) {
>>>>>>               entityStream = InputStream.class.cast(entity);
>>>>>> +        } else {
>>>>>> +            Message inMessage = getResponseMessage();
>>>>>> +            Reader reader = inMessage.getContent(Reader.class);
>>>>>> +            entityStream = InputStream.class.cast(new ReaderInputStream(reader));
>>>>>>           }
>>>>>>           // we need to check for readers even if no IS is set - the readers may still do it
>>>>>> diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java 
>>>>>> index 0ef6565..509f76d 100644
>>>>>> --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java 
>>>>>> +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java 
>>>>>> @@ -832,7 +832,11 @@ public final class JAXRSUtils {
>>>>>>                                              OperationResourceInfo ori)
>>>>>>           throws IOException, WebApplicationException {
>>>>>>           InputStream is = message.getContent(InputStream.class);
>>>>>> -
>>>>>> +        if (is == null) {
>>>>>> +            //may use the jms transport so check the Reader;
>>>>>> +            Reader reader = message.getContent(Reader.class);
>>>>>> +            is = new ReaderInputStream(reader);
>>>>>> +        }
>>>>>>           if (parameter.getType() == ParameterType.REQUEST_BODY) {
>>>>>>               if (parameterClass == AsyncResponse.class) {
>>>>>> diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java 
>>>>>> new file mode 100644
>>>>>> index 0000000..c7b142e
>>>>>> --- /dev/null
>>>>>> +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java 
>>>>>> @@ -0,0 +1,296 @@
>>>>>> +/**
>>>>>> + * 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.cxf.jaxrs.utils;
>>>>>> +
>>>>>> +import java.io.IOException;
>>>>>> +import java.io.InputStream;
>>>>>> +import java.io.Reader;
>>>>>> +import java.nio.ByteBuffer;
>>>>>> +import java.nio.CharBuffer;
>>>>>> +import java.nio.charset.Charset;
>>>>>> +import java.nio.charset.CharsetEncoder;
>>>>>> +import java.nio.charset.CoderResult;
>>>>>> +import java.nio.charset.CodingErrorAction;
>>>>>> +
>>>>>> +/**
>>>>>> + * {@link InputStream} implementation that reads a character stream from a {@link Reader}
>>>>>> + * and transforms it to a byte stream using a specified charset encoding. The stream
>>>>>> + * is transformed using a {@link CharsetEncoder} object, guaranteeing that all charset
>>>>>> + * encodings supported by the JRE are handled correctly. In particular for charsets such as
>>>>>> + * UTF-16, the implementation ensures that one and only one byte order marker
>>>>>> + * is produced.
>>>>>> + * <p>
>>>>>> + * Since in general it is not possible to predict the number of characters to be read from the
>>>>>> + * {@link Reader} to satisfy a read request on the {@link ReaderInputStream}, all reads from
>>>>>> + * the {@link Reader} are buffered. There is therefore no well defined correlation
>>>>>> + * between the current position of the {@link Reader} and that of the {@link ReaderInputStream}.
>>>>>> + * This also implies that in general there is no need to wrap the underlying {@link Reader}
>>>>>> + * in a {@link java.io.BufferedReader}.
>>>>>> + * <p>
>>>>>> + * {@link ReaderInputStream} implements the inverse transformation of {@link java.io.InputStreamReader};
>>>>>> + * in the following example, reading from <tt>in2</tt> would return the same byte
>>>>>> + * sequence as reading from <tt>in</tt> (provided that the initial byte sequence is legal
>>>>>> + * with respect to the charset encoding):
>>>>>> + * <pre>
>>>>>> + * InputStream in = ...
>>>>>> + * Charset cs = ...
>>>>>> + * InputStreamReader reader = new InputStreamReader(in, cs);
>>>>>> + * ReaderInputStream in2 = new ReaderInputStream(reader, cs);</pre>
>>>>>> + * {@link ReaderInputStream} implements the same transformation as {@link java.io.OutputStreamWriter},
>>>>>> + * except that the control flow is reversed: both classes transform a character stream
>>>>>> + * into a byte stream, but {@link java.io.OutputStreamWriter} pushes data to the underlying stream,
>>>>>> + * while {@link ReaderInputStream} pulls it from the underlying stream.
>>>>>> + * <p>
>>>>>> + * Note that while there are use cases where there is no alternative to using
>>>>>> + * this class, very often the need to use this class is an indication of a flaw
>>>>>> + * in the design of the code. This class is typically used in situations where an existing
>>>>>> + * API only accepts an {@link InputStream}, but where the most natural way to produce the data
>>>>>> + * is as a character stream, i.e. by providing a {@link Reader} instance. An example of a situation
>>>>>> + * where this problem may appear is when implementing the {@link javax.activation.DataSource}
>>>>>> + * interface from the Java Activation Framework.
>>>>>> + * <p>
>>>>>> + * Given the fact that the {@link Reader} class doesn't provide any way to predict whether the next
>>>>>> + * read operation will block or not, it is not possible to provide a meaningful
>>>>>> + * implementation of the {@link InputStream#available()} method. A call to this method
>>>>>> + * will always return 0. Also, this class doesn't support {@link InputStream#mark(int)}.
>>>>>> + * <p>
>>>>>> + * Instances of {@link ReaderInputStream} are not thread safe.
>>>>>> + *
>>>>>> + * @see org.apache.commons.io.output.WriterOutputStream
>>>>>> + *
>>>>>> + * @since 2.0
>>>>>> + */
>>>>>> +public class ReaderInputStream extends InputStream {
>>>>>> +    private static final int DEFAULT_BUFFER_SIZE = 1024;
>>>>>> +
>>>>>> +    private final Reader reader;
>>>>>> +    private final CharsetEncoder encoder;
>>>>>> +
>>>>>> +    /**
>>>>>> +     * CharBuffer used as input for the decoder. It should be reasonably
>>>>>> +     * large as we read data from the underlying Reader into this buffer.
>>>>>> +     */
>>>>>> +    private final CharBuffer encoderIn;
>>>>>> +
>>>>>> +    /**
>>>>>> +     * ByteBuffer used as output for the decoder. This buffer can be small
>>>>>> +     * as it is only used to transfer data from the decoder to the
>>>>>> +     * buffer provided by the caller.
>>>>>> +     */
>>>>>> +    private final ByteBuffer encoderOut;
>>>>>> +
>>>>>> +    private CoderResult lastCoderResult;
>>>>>> +    private boolean endOfInput;
>>>>>> +
>>>>>> +    /**
>>>>>> +     * Construct a new {@link ReaderInputStream}.
>>>>>> +     *
>>>>>> +     * @param reader the target {@link Reader}
>>>>>> +     * @param encoder the charset encoder
>>>>>> +     * @since 2.1
>>>>>> +     */
>>>>>> +    public ReaderInputStream(Reader reader, CharsetEncoder encoder) {
>>>>>> +        this(reader, encoder, DEFAULT_BUFFER_SIZE);
>>>>>> +    }
>>>>>> +
>>>>>> +    /**
>>>>>> +     * Construct a new {@link ReaderInputStream}.
>>>>>> +     *
>>>>>> +     * @param reader the target {@link Reader}
>>>>>> +     * @param encoder the charset encoder
>>>>>> +     * @param bufferSize the size of the input buffer in number of characters
>>>>>> +     * @since 2.1
>>>>>> +     */
>>>>>> +    public ReaderInputStream(Reader reader, CharsetEncoder encoder, int bufferSize) {
>>>>>> +        this.reader = reader;
>>>>>> +        this.encoder = encoder;
>>>>>> +        this.encoderIn = CharBuffer.allocate(bufferSize);
>>>>>> +        this.encoderIn.flip();
>>>>>> +        this.encoderOut = ByteBuffer.allocate(128);
>>>>>> +        this.encoderOut.flip();
>>>>>> +    }
>>>>>> +
>>>>>> +    /**
>>>>>> +     * Construct a new {@link ReaderInputStream}.
>>>>>> +     *
>>>>>> +     * @param reader the target {@link Reader}
>>>>>> +     * @param charset the charset encoding
>>>>>> +     * @param bufferSize the size of the input buffer in number of characters
>>>>>> +     */
>>>>>> +    public ReaderInputStream(Reader reader, Charset charset, int bufferSize) {
>>>>>> +        this(reader,
>>>>>> +             charset.newEncoder()
>>>>>> +                    .onMalformedInput(CodingErrorAction.REPLACE)
>>>>>> +                    .onUnmappableCharacter(CodingErrorAction.REPLACE),
>>>>>> +             bufferSize);
>>>>>> +    }
>>>>>> +
>>>>>> +    /**
>>>>>> +     * Construct a new {@link ReaderInputStream} with a default input buffer size of
>>>>>> +     * 1024 characters.
>>>>>> +     *
>>>>>> +     * @param reader the target {@link Reader}
>>>>>> +     * @param charset the charset encoding
>>>>>> +     */
>>>>>> +    public ReaderInputStream(Reader reader, Charset charset) {
>>>>>> +        this(reader, charset, DEFAULT_BUFFER_SIZE);
>>>>>> +    }
>>>>>> +
>>>>>> +    /**
>>>>>> +     * Construct a new {@link ReaderInputStream}.
>>>>>> +     *
>>>>>> +     * @param reader the target {@link Reader}
>>>>>> +     * @param charsetName the name of the charset encoding
>>>>>> +     * @param bufferSize the size of the input buffer in number of characters
>>>>>> +     */
>>>>>> +    public ReaderInputStream(Reader reader, String charsetName, int bufferSize) {
>>>>>> +        this(reader, Charset.forName(charsetName), bufferSize);
>>>>>> +    }
>>>>>> +
>>>>>> +    /**
>>>>>> +     * Construct a new {@link ReaderInputStream} with a default input buffer size of
>>>>>> +     * 1024 characters.
>>>>>> +     *
>>>>>> +     * @param reader the target {@link Reader}
>>>>>> +     * @param charsetName the name of the charset encoding
>>>>>> +     */
>>>>>> +    public ReaderInputStream(Reader reader, String charsetName) {
>>>>>> +        this(reader, charsetName, DEFAULT_BUFFER_SIZE);
>>>>>> +    }
>>>>>> +
>>>>>> +    /**
>>>>>> +     * Construct a new {@link ReaderInputStream} that uses the default character encoding
>>>>>> +     * with a default input buffer size of 1024 characters.
>>>>>> +     *
>>>>>> +     * @param reader the target {@link Reader}
>>>>>> +     */
>>>>>> +    public ReaderInputStream(Reader reader) {
>>>>>> +        this(reader, Charset.defaultCharset());
>>>>>> +    }
>>>>>> +
>>>>>> +    /**
>>>>>> +     * Fills the internal char buffer from the reader.
>>>>>> +     *
>>>>>> +     * @throws IOException
>>>>>> +     *             If an I/O error occurs
>>>>>> +     */
>>>>>> +    private void fillBuffer() throws IOException {
>>>>>> +        if (!endOfInput && (lastCoderResult == null || lastCoderResult.isUnderflow())) {
>>>>>> +            encoderIn.compact();
>>>>>> +            int position = encoderIn.position();
>>>>>> +            // We don't use Reader#read(CharBuffer) here because it is more efficient
>>>>>> +            // to write directly to the underlying char array (the default implementation
>>>>>> +            // copies data to a temporary char array).
>>>>>> +            int c = reader.read(encoderIn.array(), position, encoderIn.remaining());
>>>>>> +            if (c == -1) {
>>>>>> +                endOfInput = true;
>>>>>> +            } else {
>>>>>> +                encoderIn.position(position + c);
>>>>>> +            }
>>>>>> +            encoderIn.flip();
>>>>>> +        }
>>>>>> +        encoderOut.compact();
>>>>>> +        lastCoderResult = encoder.encode(encoderIn, encoderOut, endOfInput);
>>>>>> +        encoderOut.flip();
>>>>>> +    }
>>>>>> +
>>>>>> +    /**
>>>>>> +     * Read the specified number of bytes into an array.
>>>>>> +     *
>>>>>> +     * @param b the byte array to read into
>>>>>> +     * @param off the offset to start reading bytes into
>>>>>> +     * @param len the number of bytes to read
>>>>>> +     * @return the number of bytes read or <code>-1</code>
>>>>>> +     *         if the end of the stream has been reached
>>>>>> +     * @throws IOException if an I/O error occurs
>>>>>> +     */
>>>>>> +    @Override
>>>>>> +    public int read(byte[] b, int off, int len) throws IOException {
>>>>>> +        if (b == null) {
>>>>>> +            throw new NullPointerException("Byte array must not be null");
>>>>>> +        }
>>>>>> +        if (len < 0 || off < 0 || (off + len) > b.length) {
>>>>>> +            throw new IndexOutOfBoundsException("Array Size=" + b.length
>>>>>> +                    + ", offset=" + off + ", length=" + len);
>>>>>> +        }
>>>>>> +        int read = 0;
>>>>>> +        if (len == 0) {
>>>>>> +            return 0; // Always return 0 if len == 0
>>>>>> +        }
>>>>>> +        while (len > 0) {
>>>>>> +            if (encoderOut.hasRemaining()) {
>>>>>> +                int c = Math.min(encoderOut.remaining(), len);
>>>>>> +                encoderOut.get(b, off, c);
>>>>>> +                off += c;
>>>>>> +                len -= c;
>>>>>> +                read += c;
>>>>>> +            } else {
>>>>>> +                fillBuffer();
>>>>>> +                if (endOfInput && !encoderOut.hasRemaining()) {
>>>>>> +                    break;
>>>>>> +                }
>>>>>> +            }
>>>>>> +        }
>>>>>> +        return read == 0 && endOfInput ? -1 : read;
>>>>>> +    }
>>>>>> +
>>>>>> +    /**
>>>>>> +     * Read the specified number of bytes into an array.
>>>>>> +     *
>>>>>> +     * @param b the byte array to read into
>>>>>> +     * @return the number of bytes read or <code>-1</code>
>>>>>> +     *         if the end of the stream has been reached
>>>>>> +     * @throws IOException if an I/O error occurs
>>>>>> +     */
>>>>>> +    @Override
>>>>>> +    public int read(byte[] b) throws IOException {
>>>>>> +        return read(b, 0, b.length);
>>>>>> +    }
>>>>>> +
>>>>>> +    /**
>>>>>> +     * Read a single byte.
>>>>>> +     *
>>>>>> +     * @return either the byte read or <code>-1</code> if the end of the stream
>>>>>> +     *         has been reached
>>>>>> +     * @throws IOException if an I/O error occurs
>>>>>> +     */
>>>>>> +    @Override
>>>>>> +    public int read() throws IOException {
>>>>>> +        for (;;) {
>>>>>> +            if (encoderOut.hasRemaining()) {
>>>>>> +                return encoderOut.get() & 0xFF;
>>>>>> +            } else {
>>>>>> +                fillBuffer();
>>>>>> +                if (endOfInput && !encoderOut.hasRemaining()) {
>>>>>> +                    return -1;
>>>>>> +                }
>>>>>> +            }
>>>>>> +        }
>>>>>> +    }
>>>>>> +
>>>>>> +    /**
>>>>>> +     * Close the stream. This method will cause the underlying {@link Reader}
>>>>>> +     * to be closed.
>>>>>> +     * @throws IOException if an I/O error occurs
>>>>>> +     */
>>>>>> +    @Override
>>>>>> +    public void close() throws IOException {
>>>>>> +        reader.close();
>>>>>> +    }
>>>>>> +}
>>>>>> diff --git a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java 
>>>>>> index f77b5ed..b957f36 100644
>>>>>> --- a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java 
>>>>>> +++ b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java 
>>>>>> @@ -96,6 +96,25 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>>>>           assertEquals("Get a wrong response code.", 200, client.getResponse().getStatus());
>>>>>>           assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>       }
>>>>>> +
>>>>>> +    @Test
>>>>>> +    public void testGetBookFromWebClientWithTextJMSMessage() throws Exception {
>>>>>> +        // setup the the client
>>>>>> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>>> +             + "?replyToName=dynamicQueues/test.jmstransport.response"
>>>>>> +             + "&jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>>> +             + "&messageType=text";
>>>>>> +
>>>>>> +        WebClient client = WebClient.create(endpointAddressUrlEncoded);
>>>>>> +        WebClient.getConfig(client).getInInterceptors().add(new LoggingInInterceptor());
>>>>>> +        WebClient.getConfig(client).getRequestContext()
>>>>>> +            .put(org.apache.cxf.message.Message.REQUEST_URI, "/bookstore/books/123");
>>>>>> +
>>>>>> +        Book book = client.get(Book.class);
>>>>>> +        assertEquals("Get a wrong response code.", 200, client.getResponse().getStatus());
>>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>> +    }
>>>>>>       @Test
>>>>>>       public void testPutBookOneWayWithWebClient() throws Exception {
>>>>>> @@ -130,6 +149,7 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>>>>           }
>>>>>>       }
>>>>>> +
>>>>>>       @Test
>>>>>>       public void testGetBookFromWebClientWithPath() throws Exception {
>>>>>>           // setup the the client
>>>>>> @@ -148,6 +168,25 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>>>>       }
>>>>>>       @Test
>>>>>> +    public void testGetBookFromWebClientWithPathWithTextJMSMessage() throws Exception {
>>>>>> +        // setup the the client
>>>>>> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>>> +             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>>>>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>>>> +             + "&messageType=text";
>>>>>> +
>>>>>> +        WebClient client = WebClient.create(endpointAddressUrlEncoded);
>>>>>> +        client.path("bookstore").path("books").path("123");
>>>>>> +
>>>>>> +        Book book = client.get(Book.class);
>>>>>> +        assertEquals("Get a wrong response code.", 200, client.getResponse().getStatus());
>>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>> +    }
>>>>>> +
>>>>>> +
>>>>>> +    @Test
>>>>>>       public void testGetBookFromProxyClient() throws Exception {
>>>>>>           // setup the the client
>>>>>>           String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>>> @@ -161,6 +200,22 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>>>>           assertEquals("Get a wrong response code.", 200, WebClient.client(client).getResponse().getStatus());
>>>>>>           assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>       }
>>>>>> +
>>>>>> +    @Test
>>>>>> +    public void testGetBookFromProxyClientWithTextJMSMessage() throws Exception {
>>>>>> +        // setup the the client
>>>>>> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>>> +             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>>>>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>>>> +             + "&messageType=text";
>>>>>> +
>>>>>> +        JMSBookStore client = JAXRSClientFactory.create(endpointAddressUrlEncoded, JMSBookStore.class);
>>>>>> +        Book book = client.getBook("123");
>>>>>> +        assertEquals("Get a wrong response code.", 200, WebClient.client(client).getResponse().getStatus());
>>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>> +    }
>>>>>>       @Test
>>>>>>       public void testGetBookFromSubresourceProxyClient() throws Exception {
>>>>>> @@ -177,6 +232,23 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>>>>           assertEquals("Get a wrong response code.", 200, WebClient.client(bookProxy).getResponse().getStatus());
>>>>>>           assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>       }
>>>>>> +
>>>>>> +    @Test
>>>>>> +    public void testGetBookFromSubresourceProxyClientWithTextJMSMessage() throws Exception {
>>>>>> +        // setup the the client
>>>>>> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>>> +             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>>>>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>>>> +             + "&messageType=text";
>>>>>> +
>>>>>> +        JMSBookStore client = JAXRSClientFactory.create(endpointAddressUrlEncoded, JMSBookStore.class);
>>>>>> +        Book bookProxy = client.getBookSubResource("123");
>>>>>> +        Book book = bookProxy.retrieveState();
>>>>>> +        assertEquals("Get a wrong response code.", 200, WebClient.client(bookProxy).getResponse().getStatus());
>>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>> +    }
>>>>>>       @Test
>>>>>>       public void testGetBookFromProxyClientWithQuery() throws Exception {
>>>>>> @@ -193,6 +265,24 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>>>>           assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>>       }
>>>>>> +
>>>>>> +    @Test
>>>>>> +    public void testGetBookFromProxyClientWithQueryWithTextJMSMessage() throws Exception {
>>>>>> +        // setup the the client
>>>>>> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>>> +             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>>>>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>>>> +             + "&messageType=text";
>>>>>> +
>>>>>> +        JMSBookStore client = JAXRSClientFactory.create(endpointAddressUrlEncoded, JMSBookStore.class);
>>>>>> +        Book book = client.getBookByURLQuery(new String[] {"1", "2", "3"});
>>>>>> +        assertEquals("Get a wrong response code.", 200, WebClient.client(client).getResponse().getStatus());
>>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>> +    }
>>>>>> +
>>>>>> +
>>>>>>       @Test
>>>>>>       public void testGetBook() throws Exception {
>>>>>>           Context ctx = getContext();
>>>>>> 
>>>> 
>>> 
> 
> 
> -- 
> Sergey Beryozkin
> 
> Talend Community Coders
> http://coders.talend.com/


Re: [cxf] branch master updated: [CXF-7532]REST on JMS transport can't handle the request message with text messageType

Posted by Sergey Beryozkin <sb...@gmail.com>.
If we assume that checking Reader can be useful in the general sense, 
then may be lets keep things as is, with the minor cleanup only...

Sergey
On 19/10/17 10:05, Sergey Beryozkin wrote:
> Hi Freeman
> 
> Reader may likely need to be set too for SOAP, and I recall checking 
> Reader somewhere else too (will double check now), but I thought may be 
> the transport can set both Reader and InputStream, hmm, that is an extra 
> instance though, may be lets start with moving ReaderInputStream to the 
> core first ?
> 
> Sergey
> On 19/10/17 09:59, ffang wrote:
>> Yeah, set ReaderInputStream instead of Reader for the JMS transport is 
>> a better solution. If no objection I will go this way.
>> -------------
>> Freeman(Yue) Fang
>>
>> Red Hat, Inc.
>> FuseSource is now part of Red Hat
>>
>>
>>
>>> On Oct 19, 2017, at 4:50 PM, Sergey Beryozkin <sberyozkin@gmail.com 
>>> <ma...@gmail.com>> wrote:
>>>
>>> The other thing is, should JMS transport itself set ReaderInputStream 
>>> as InputStream on the message, as opposed to patching the JAX-RS code 
>>> in various places ?
>>>
>>> Sergey
>>> On 19/10/17 09:44, Sergey Beryozkin wrote:
>>>> Hi Freeman
>>>> Can you move ReaderInputStream to the CXF core where 
>>>> CachedInputStream and other IO utility code is located ?
>>>> Please also update the code which gets Reader.class and check it for 
>>>> null first, and also drop a comment re the JMS transport, checking 
>>>> the reader can make sense in all the cases...
>>>> Thanks, Sergey
>>>> On 19/10/17 04:16, ffang@apache.org <ma...@apache.org> wrote:
>>>>> This is an automated email from the ASF dual-hosted git repository.
>>>>>
>>>>> ffang pushed a commit to branch master
>>>>> in repository https://gitbox.apache.org/repos/asf/cxf.git
>>>>>
>>>>>
>>>>> The following commit(s) were added to refs/heads/master by this push:
>>>>>       new 1d66475  [CXF-7532]REST on JMS transport can't handle the 
>>>>> request message with text messageType
>>>>> 1d66475 is described below
>>>>>
>>>>> commit 1d66475b4dea2a0031dcb1765e8e9fd08430ec9b
>>>>> Author: Freeman Fang <freeman.fang@gmail.com 
>>>>> <ma...@gmail.com>>
>>>>> AuthorDate: Thu Oct 19 11:15:50 2017 +0800
>>>>>
>>>>>      [CXF-7532]REST on JMS transport can't handle the request 
>>>>> message with text messageType
>>>>> ---
>>>>>   .../org/apache/cxf/jaxrs/impl/ResponseImpl.java    |   5 +
>>>>>   .../org/apache/cxf/jaxrs/utils/JAXRSUtils.java     |   6 +-
>>>>>   .../apache/cxf/jaxrs/utils/ReaderInputStream.java  | 296 
>>>>> +++++++++++++++++++++
>>>>>   .../apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java |  90 +++++++
>>>>>   4 files changed, 396 insertions(+), 1 deletion(-)
>>>>>
>>>>> diff --git 
>>>>> a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java 
>>>>> b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java 
>>>>>
>>>>> index e3a3acf..82f183a 100644
>>>>> --- 
>>>>> a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java 
>>>>>
>>>>> +++ 
>>>>> b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java 
>>>>>
>>>>> @@ -58,6 +58,7 @@ import 
>>>>> org.apache.cxf.jaxrs.provider.ProviderFactory;
>>>>>   import org.apache.cxf.jaxrs.utils.HttpUtils;
>>>>>   import org.apache.cxf.jaxrs.utils.InjectionUtils;
>>>>>   import org.apache.cxf.jaxrs.utils.JAXRSUtils;
>>>>> +import org.apache.cxf.jaxrs.utils.ReaderInputStream;
>>>>>   import org.apache.cxf.message.Message;
>>>>>   import org.apache.cxf.message.MessageUtils;
>>>>> @@ -342,6 +343,10 @@ public final class ResponseImpl extends 
>>>>> Response {
>>>>>               entityStreamAvailable = entityStream != null;
>>>>>           } else if (entity instanceof InputStream) {
>>>>>               entityStream = InputStream.class.cast(entity);
>>>>> +        } else {
>>>>> +            Message inMessage = getResponseMessage();
>>>>> +            Reader reader = inMessage.getContent(Reader.class);
>>>>> +            entityStream = InputStream.class.cast(new 
>>>>> ReaderInputStream(reader));
>>>>>           }
>>>>>           // we need to check for readers even if no IS is set - 
>>>>> the readers may still do it
>>>>> diff --git 
>>>>> a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java 
>>>>> b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java 
>>>>>
>>>>> index 0ef6565..509f76d 100644
>>>>> --- 
>>>>> a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java 
>>>>>
>>>>> +++ 
>>>>> b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java 
>>>>>
>>>>> @@ -832,7 +832,11 @@ public final class JAXRSUtils {
>>>>>                                              OperationResourceInfo 
>>>>> ori)
>>>>>           throws IOException, WebApplicationException {
>>>>>           InputStream is = message.getContent(InputStream.class);
>>>>> -
>>>>> +        if (is == null) {
>>>>> +            //may use the jms transport so check the Reader;
>>>>> +            Reader reader = message.getContent(Reader.class);
>>>>> +            is = new ReaderInputStream(reader);
>>>>> +        }
>>>>>           if (parameter.getType() == ParameterType.REQUEST_BODY) {
>>>>>               if (parameterClass == AsyncResponse.class) {
>>>>> diff --git 
>>>>> a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java 
>>>>> b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java 
>>>>>
>>>>> new file mode 100644
>>>>> index 0000000..c7b142e
>>>>> --- /dev/null
>>>>> +++ 
>>>>> b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java 
>>>>>
>>>>> @@ -0,0 +1,296 @@
>>>>> +/**
>>>>> + * 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.cxf.jaxrs.utils;
>>>>> +
>>>>> +import java.io.IOException;
>>>>> +import java.io.InputStream;
>>>>> +import java.io.Reader;
>>>>> +import java.nio.ByteBuffer;
>>>>> +import java.nio.CharBuffer;
>>>>> +import java.nio.charset.Charset;
>>>>> +import java.nio.charset.CharsetEncoder;
>>>>> +import java.nio.charset.CoderResult;
>>>>> +import java.nio.charset.CodingErrorAction;
>>>>> +
>>>>> +/**
>>>>> + * {@link InputStream} implementation that reads a character 
>>>>> stream from a {@link Reader}
>>>>> + * and transforms it to a byte stream using a specified charset 
>>>>> encoding. The stream
>>>>> + * is transformed using a {@link CharsetEncoder} object, 
>>>>> guaranteeing that all charset
>>>>> + * encodings supported by the JRE are handled correctly. In 
>>>>> particular for charsets such as
>>>>> + * UTF-16, the implementation ensures that one and only one byte 
>>>>> order marker
>>>>> + * is produced.
>>>>> + * <p>
>>>>> + * Since in general it is not possible to predict the number of 
>>>>> characters to be read from the
>>>>> + * {@link Reader} to satisfy a read request on the {@link 
>>>>> ReaderInputStream}, all reads from
>>>>> + * the {@link Reader} are buffered. There is therefore no well 
>>>>> defined correlation
>>>>> + * between the current position of the {@link Reader} and that of 
>>>>> the {@link ReaderInputStream}.
>>>>> + * This also implies that in general there is no need to wrap the 
>>>>> underlying {@link Reader}
>>>>> + * in a {@link java.io.BufferedReader}.
>>>>> + * <p>
>>>>> + * {@link ReaderInputStream} implements the inverse transformation 
>>>>> of {@link java.io.InputStreamReader};
>>>>> + * in the following example, reading from <tt>in2</tt> would 
>>>>> return the same byte
>>>>> + * sequence as reading from <tt>in</tt> (provided that the initial 
>>>>> byte sequence is legal
>>>>> + * with respect to the charset encoding):
>>>>> + * <pre>
>>>>> + * InputStream in = ...
>>>>> + * Charset cs = ...
>>>>> + * InputStreamReader reader = new InputStreamReader(in, cs);
>>>>> + * ReaderInputStream in2 = new ReaderInputStream(reader, cs);</pre>
>>>>> + * {@link ReaderInputStream} implements the same transformation as 
>>>>> {@link java.io.OutputStreamWriter},
>>>>> + * except that the control flow is reversed: both classes 
>>>>> transform a character stream
>>>>> + * into a byte stream, but {@link java.io.OutputStreamWriter} 
>>>>> pushes data to the underlying stream,
>>>>> + * while {@link ReaderInputStream} pulls it from the underlying 
>>>>> stream.
>>>>> + * <p>
>>>>> + * Note that while there are use cases where there is no 
>>>>> alternative to using
>>>>> + * this class, very often the need to use this class is an 
>>>>> indication of a flaw
>>>>> + * in the design of the code. This class is typically used in 
>>>>> situations where an existing
>>>>> + * API only accepts an {@link InputStream}, but where the most 
>>>>> natural way to produce the data
>>>>> + * is as a character stream, i.e. by providing a {@link Reader} 
>>>>> instance. An example of a situation
>>>>> + * where this problem may appear is when implementing the {@link 
>>>>> javax.activation.DataSource}
>>>>> + * interface from the Java Activation Framework.
>>>>> + * <p>
>>>>> + * Given the fact that the {@link Reader} class doesn't provide 
>>>>> any way to predict whether the next
>>>>> + * read operation will block or not, it is not possible to provide 
>>>>> a meaningful
>>>>> + * implementation of the {@link InputStream#available()} method. A 
>>>>> call to this method
>>>>> + * will always return 0. Also, this class doesn't support {@link 
>>>>> InputStream#mark(int)}.
>>>>> + * <p>
>>>>> + * Instances of {@link ReaderInputStream} are not thread safe.
>>>>> + *
>>>>> + * @see org.apache.commons.io.output.WriterOutputStream
>>>>> + *
>>>>> + * @since 2.0
>>>>> + */
>>>>> +public class ReaderInputStream extends InputStream {
>>>>> +    private static final int DEFAULT_BUFFER_SIZE = 1024;
>>>>> +
>>>>> +    private final Reader reader;
>>>>> +    private final CharsetEncoder encoder;
>>>>> +
>>>>> +    /**
>>>>> +     * CharBuffer used as input for the decoder. It should be 
>>>>> reasonably
>>>>> +     * large as we read data from the underlying Reader into this 
>>>>> buffer.
>>>>> +     */
>>>>> +    private final CharBuffer encoderIn;
>>>>> +
>>>>> +    /**
>>>>> +     * ByteBuffer used as output for the decoder. This buffer can 
>>>>> be small
>>>>> +     * as it is only used to transfer data from the decoder to the
>>>>> +     * buffer provided by the caller.
>>>>> +     */
>>>>> +    private final ByteBuffer encoderOut;
>>>>> +
>>>>> +    private CoderResult lastCoderResult;
>>>>> +    private boolean endOfInput;
>>>>> +
>>>>> +    /**
>>>>> +     * Construct a new {@link ReaderInputStream}.
>>>>> +     *
>>>>> +     * @param reader the target {@link Reader}
>>>>> +     * @param encoder the charset encoder
>>>>> +     * @since 2.1
>>>>> +     */
>>>>> +    public ReaderInputStream(Reader reader, CharsetEncoder encoder) {
>>>>> +        this(reader, encoder, DEFAULT_BUFFER_SIZE);
>>>>> +    }
>>>>> +
>>>>> +    /**
>>>>> +     * Construct a new {@link ReaderInputStream}.
>>>>> +     *
>>>>> +     * @param reader the target {@link Reader}
>>>>> +     * @param encoder the charset encoder
>>>>> +     * @param bufferSize the size of the input buffer in number of 
>>>>> characters
>>>>> +     * @since 2.1
>>>>> +     */
>>>>> +    public ReaderInputStream(Reader reader, CharsetEncoder 
>>>>> encoder, int bufferSize) {
>>>>> +        this.reader = reader;
>>>>> +        this.encoder = encoder;
>>>>> +        this.encoderIn = CharBuffer.allocate(bufferSize);
>>>>> +        this.encoderIn.flip();
>>>>> +        this.encoderOut = ByteBuffer.allocate(128);
>>>>> +        this.encoderOut.flip();
>>>>> +    }
>>>>> +
>>>>> +    /**
>>>>> +     * Construct a new {@link ReaderInputStream}.
>>>>> +     *
>>>>> +     * @param reader the target {@link Reader}
>>>>> +     * @param charset the charset encoding
>>>>> +     * @param bufferSize the size of the input buffer in number of 
>>>>> characters
>>>>> +     */
>>>>> +    public ReaderInputStream(Reader reader, Charset charset, int 
>>>>> bufferSize) {
>>>>> +        this(reader,
>>>>> +             charset.newEncoder()
>>>>> +                    .onMalformedInput(CodingErrorAction.REPLACE)
>>>>> +                    
>>>>> .onUnmappableCharacter(CodingErrorAction.REPLACE),
>>>>> +             bufferSize);
>>>>> +    }
>>>>> +
>>>>> +    /**
>>>>> +     * Construct a new {@link ReaderInputStream} with a default 
>>>>> input buffer size of
>>>>> +     * 1024 characters.
>>>>> +     *
>>>>> +     * @param reader the target {@link Reader}
>>>>> +     * @param charset the charset encoding
>>>>> +     */
>>>>> +    public ReaderInputStream(Reader reader, Charset charset) {
>>>>> +        this(reader, charset, DEFAULT_BUFFER_SIZE);
>>>>> +    }
>>>>> +
>>>>> +    /**
>>>>> +     * Construct a new {@link ReaderInputStream}.
>>>>> +     *
>>>>> +     * @param reader the target {@link Reader}
>>>>> +     * @param charsetName the name of the charset encoding
>>>>> +     * @param bufferSize the size of the input buffer in number of 
>>>>> characters
>>>>> +     */
>>>>> +    public ReaderInputStream(Reader reader, String charsetName, 
>>>>> int bufferSize) {
>>>>> +        this(reader, Charset.forName(charsetName), bufferSize);
>>>>> +    }
>>>>> +
>>>>> +    /**
>>>>> +     * Construct a new {@link ReaderInputStream} with a default 
>>>>> input buffer size of
>>>>> +     * 1024 characters.
>>>>> +     *
>>>>> +     * @param reader the target {@link Reader}
>>>>> +     * @param charsetName the name of the charset encoding
>>>>> +     */
>>>>> +    public ReaderInputStream(Reader reader, String charsetName) {
>>>>> +        this(reader, charsetName, DEFAULT_BUFFER_SIZE);
>>>>> +    }
>>>>> +
>>>>> +    /**
>>>>> +     * Construct a new {@link ReaderInputStream} that uses the 
>>>>> default character encoding
>>>>> +     * with a default input buffer size of 1024 characters.
>>>>> +     *
>>>>> +     * @param reader the target {@link Reader}
>>>>> +     */
>>>>> +    public ReaderInputStream(Reader reader) {
>>>>> +        this(reader, Charset.defaultCharset());
>>>>> +    }
>>>>> +
>>>>> +    /**
>>>>> +     * Fills the internal char buffer from the reader.
>>>>> +     *
>>>>> +     * @throws IOException
>>>>> +     *             If an I/O error occurs
>>>>> +     */
>>>>> +    private void fillBuffer() throws IOException {
>>>>> +        if (!endOfInput && (lastCoderResult == null || 
>>>>> lastCoderResult.isUnderflow())) {
>>>>> +            encoderIn.compact();
>>>>> +            int position = encoderIn.position();
>>>>> +            // We don't use Reader#read(CharBuffer) here because 
>>>>> it is more efficient
>>>>> +            // to write directly to the underlying char array (the 
>>>>> default implementation
>>>>> +            // copies data to a temporary char array).
>>>>> +            int c = reader.read(encoderIn.array(), position, 
>>>>> encoderIn.remaining());
>>>>> +            if (c == -1) {
>>>>> +                endOfInput = true;
>>>>> +            } else {
>>>>> +                encoderIn.position(position + c);
>>>>> +            }
>>>>> +            encoderIn.flip();
>>>>> +        }
>>>>> +        encoderOut.compact();
>>>>> +        lastCoderResult = encoder.encode(encoderIn, encoderOut, 
>>>>> endOfInput);
>>>>> +        encoderOut.flip();
>>>>> +    }
>>>>> +
>>>>> +    /**
>>>>> +     * Read the specified number of bytes into an array.
>>>>> +     *
>>>>> +     * @param b the byte array to read into
>>>>> +     * @param off the offset to start reading bytes into
>>>>> +     * @param len the number of bytes to read
>>>>> +     * @return the number of bytes read or <code>-1</code>
>>>>> +     *         if the end of the stream has been reached
>>>>> +     * @throws IOException if an I/O error occurs
>>>>> +     */
>>>>> +    @Override
>>>>> +    public int read(byte[] b, int off, int len) throws IOException {
>>>>> +        if (b == null) {
>>>>> +            throw new NullPointerException("Byte array must not be 
>>>>> null");
>>>>> +        }
>>>>> +        if (len < 0 || off < 0 || (off + len) > b.length) {
>>>>> +            throw new IndexOutOfBoundsException("Array Size=" + 
>>>>> b.length
>>>>> +                    + ", offset=" + off + ", length=" + len);
>>>>> +        }
>>>>> +        int read = 0;
>>>>> +        if (len == 0) {
>>>>> +            return 0; // Always return 0 if len == 0
>>>>> +        }
>>>>> +        while (len > 0) {
>>>>> +            if (encoderOut.hasRemaining()) {
>>>>> +                int c = Math.min(encoderOut.remaining(), len);
>>>>> +                encoderOut.get(b, off, c);
>>>>> +                off += c;
>>>>> +                len -= c;
>>>>> +                read += c;
>>>>> +            } else {
>>>>> +                fillBuffer();
>>>>> +                if (endOfInput && !encoderOut.hasRemaining()) {
>>>>> +                    break;
>>>>> +                }
>>>>> +            }
>>>>> +        }
>>>>> +        return read == 0 && endOfInput ? -1 : read;
>>>>> +    }
>>>>> +
>>>>> +    /**
>>>>> +     * Read the specified number of bytes into an array.
>>>>> +     *
>>>>> +     * @param b the byte array to read into
>>>>> +     * @return the number of bytes read or <code>-1</code>
>>>>> +     *         if the end of the stream has been reached
>>>>> +     * @throws IOException if an I/O error occurs
>>>>> +     */
>>>>> +    @Override
>>>>> +    public int read(byte[] b) throws IOException {
>>>>> +        return read(b, 0, b.length);
>>>>> +    }
>>>>> +
>>>>> +    /**
>>>>> +     * Read a single byte.
>>>>> +     *
>>>>> +     * @return either the byte read or <code>-1</code> if the end 
>>>>> of the stream
>>>>> +     *         has been reached
>>>>> +     * @throws IOException if an I/O error occurs
>>>>> +     */
>>>>> +    @Override
>>>>> +    public int read() throws IOException {
>>>>> +        for (;;) {
>>>>> +            if (encoderOut.hasRemaining()) {
>>>>> +                return encoderOut.get() & 0xFF;
>>>>> +            } else {
>>>>> +                fillBuffer();
>>>>> +                if (endOfInput && !encoderOut.hasRemaining()) {
>>>>> +                    return -1;
>>>>> +                }
>>>>> +            }
>>>>> +        }
>>>>> +    }
>>>>> +
>>>>> +    /**
>>>>> +     * Close the stream. This method will cause the underlying 
>>>>> {@link Reader}
>>>>> +     * to be closed.
>>>>> +     * @throws IOException if an I/O error occurs
>>>>> +     */
>>>>> +    @Override
>>>>> +    public void close() throws IOException {
>>>>> +        reader.close();
>>>>> +    }
>>>>> +}
>>>>> diff --git 
>>>>> a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java 
>>>>> b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java 
>>>>>
>>>>> index f77b5ed..b957f36 100644
>>>>> --- 
>>>>> a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java 
>>>>>
>>>>> +++ 
>>>>> b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java 
>>>>>
>>>>> @@ -96,6 +96,25 @@ public class JAXRSJmsTest extends 
>>>>> AbstractBusClientServerTestBase {
>>>>>           assertEquals("Get a wrong response code.", 200, 
>>>>> client.getResponse().getStatus());
>>>>>           assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>       }
>>>>> +
>>>>> +    @Test
>>>>> +    public void testGetBookFromWebClientWithTextJMSMessage() 
>>>>> throws Exception {
>>>>> +        // setup the the client
>>>>> +        String endpointAddressUrlEncoded = 
>>>>> "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>> +             + 
>>>>> "?replyToName=dynamicQueues/test.jmstransport.response"
>>>>> +             + 
>>>>> "&jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>>>>
>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>> +             + "&messageType=text";
>>>>> +
>>>>> +        WebClient client = 
>>>>> WebClient.create(endpointAddressUrlEncoded);
>>>>> +        WebClient.getConfig(client).getInInterceptors().add(new 
>>>>> LoggingInInterceptor());
>>>>> +        WebClient.getConfig(client).getRequestContext()
>>>>> +            .put(org.apache.cxf.message.Message.REQUEST_URI, 
>>>>> "/bookstore/books/123");
>>>>> +
>>>>> +        Book book = client.get(Book.class);
>>>>> +        assertEquals("Get a wrong response code.", 200, 
>>>>> client.getResponse().getStatus());
>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>> +    }
>>>>>       @Test
>>>>>       public void testPutBookOneWayWithWebClient() throws Exception {
>>>>> @@ -130,6 +149,7 @@ public class JAXRSJmsTest extends 
>>>>> AbstractBusClientServerTestBase {
>>>>>           }
>>>>>       }
>>>>> +
>>>>>       @Test
>>>>>       public void testGetBookFromWebClientWithPath() throws 
>>>>> Exception {
>>>>>           // setup the the client
>>>>> @@ -148,6 +168,25 @@ public class JAXRSJmsTest extends 
>>>>> AbstractBusClientServerTestBase {
>>>>>       }
>>>>>       @Test
>>>>> +    public void 
>>>>> testGetBookFromWebClientWithPathWithTextJMSMessage() throws 
>>>>> Exception {
>>>>> +        // setup the the client
>>>>> +        String endpointAddressUrlEncoded = 
>>>>> "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>> +             + 
>>>>> "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>>>>
>>>>> +             + 
>>>>> "&replyToName=dynamicQueues/test.jmstransport.response"
>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>>> +             + "&messageType=text";
>>>>> +
>>>>> +        WebClient client = 
>>>>> WebClient.create(endpointAddressUrlEncoded);
>>>>> +        client.path("bookstore").path("books").path("123");
>>>>> +
>>>>> +        Book book = client.get(Book.class);
>>>>> +        assertEquals("Get a wrong response code.", 200, 
>>>>> client.getResponse().getStatus());
>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>> +    }
>>>>> +
>>>>> +
>>>>> +    @Test
>>>>>       public void testGetBookFromProxyClient() throws Exception {
>>>>>           // setup the the client
>>>>>           String endpointAddressUrlEncoded = 
>>>>> "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>> @@ -161,6 +200,22 @@ public class JAXRSJmsTest extends 
>>>>> AbstractBusClientServerTestBase {
>>>>>           assertEquals("Get a wrong response code.", 200, 
>>>>> WebClient.client(client).getResponse().getStatus());
>>>>>           assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>       }
>>>>> +
>>>>> +    @Test
>>>>> +    public void testGetBookFromProxyClientWithTextJMSMessage() 
>>>>> throws Exception {
>>>>> +        // setup the the client
>>>>> +        String endpointAddressUrlEncoded = 
>>>>> "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>> +             + 
>>>>> "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>>>>
>>>>> +             + 
>>>>> "&replyToName=dynamicQueues/test.jmstransport.response"
>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>>> +             + "&messageType=text";
>>>>> +
>>>>> +        JMSBookStore client = 
>>>>> JAXRSClientFactory.create(endpointAddressUrlEncoded, 
>>>>> JMSBookStore.class);
>>>>> +        Book book = client.getBook("123");
>>>>> +        assertEquals("Get a wrong response code.", 200, 
>>>>> WebClient.client(client).getResponse().getStatus());
>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>> +    }
>>>>>       @Test
>>>>>       public void testGetBookFromSubresourceProxyClient() throws 
>>>>> Exception {
>>>>> @@ -177,6 +232,23 @@ public class JAXRSJmsTest extends 
>>>>> AbstractBusClientServerTestBase {
>>>>>           assertEquals("Get a wrong response code.", 200, 
>>>>> WebClient.client(bookProxy).getResponse().getStatus());
>>>>>           assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>       }
>>>>> +
>>>>> +    @Test
>>>>> +    public void 
>>>>> testGetBookFromSubresourceProxyClientWithTextJMSMessage() throws 
>>>>> Exception {
>>>>> +        // setup the the client
>>>>> +        String endpointAddressUrlEncoded = 
>>>>> "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>> +             + 
>>>>> "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>>>>
>>>>> +             + 
>>>>> "&replyToName=dynamicQueues/test.jmstransport.response"
>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>>> +             + "&messageType=text";
>>>>> +
>>>>> +        JMSBookStore client = 
>>>>> JAXRSClientFactory.create(endpointAddressUrlEncoded, 
>>>>> JMSBookStore.class);
>>>>> +        Book bookProxy = client.getBookSubResource("123");
>>>>> +        Book book = bookProxy.retrieveState();
>>>>> +        assertEquals("Get a wrong response code.", 200, 
>>>>> WebClient.client(bookProxy).getResponse().getStatus());
>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>> +    }
>>>>>       @Test
>>>>>       public void testGetBookFromProxyClientWithQuery() throws 
>>>>> Exception {
>>>>> @@ -193,6 +265,24 @@ public class JAXRSJmsTest extends 
>>>>> AbstractBusClientServerTestBase {
>>>>>           assertEquals("Get a wrong book id.", 123, book.getId());
>>>>>       }
>>>>> +
>>>>> +    @Test
>>>>> +    public void 
>>>>> testGetBookFromProxyClientWithQueryWithTextJMSMessage() throws 
>>>>> Exception {
>>>>> +        // setup the the client
>>>>> +        String endpointAddressUrlEncoded = 
>>>>> "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>>> +             + 
>>>>> "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>>>>
>>>>> +             + 
>>>>> "&replyToName=dynamicQueues/test.jmstransport.response"
>>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>>> +             + "&messageType=text";
>>>>> +
>>>>> +        JMSBookStore client = 
>>>>> JAXRSClientFactory.create(endpointAddressUrlEncoded, 
>>>>> JMSBookStore.class);
>>>>> +        Book book = client.getBookByURLQuery(new String[] {"1", 
>>>>> "2", "3"});
>>>>> +        assertEquals("Get a wrong response code.", 200, 
>>>>> WebClient.client(client).getResponse().getStatus());
>>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>>> +    }
>>>>> +
>>>>> +
>>>>>       @Test
>>>>>       public void testGetBook() throws Exception {
>>>>>           Context ctx = getContext();
>>>>>
>>>
>>


-- 
Sergey Beryozkin

Talend Community Coders
http://coders.talend.com/

Re: [cxf] branch master updated: [CXF-7532]REST on JMS transport can't handle the request message with text messageType

Posted by Sergey Beryozkin <sb...@gmail.com>.
Hi Freeman

Reader may likely need to be set too for SOAP, and I recall checking 
Reader somewhere else too (will double check now), but I thought may be 
the transport can set both Reader and InputStream, hmm, that is an extra 
instance though, may be lets start with moving ReaderInputStream to the 
core first ?

Sergey
On 19/10/17 09:59, ffang wrote:
> Yeah, set ReaderInputStream instead of Reader for the JMS transport is a 
> better solution. If no objection I will go this way.
> -------------
> Freeman(Yue) Fang
> 
> Red Hat, Inc.
> FuseSource is now part of Red Hat
> 
> 
> 
>> On Oct 19, 2017, at 4:50 PM, Sergey Beryozkin <sberyozkin@gmail.com 
>> <ma...@gmail.com>> wrote:
>>
>> The other thing is, should JMS transport itself set ReaderInputStream 
>> as InputStream on the message, as opposed to patching the JAX-RS code 
>> in various places ?
>>
>> Sergey
>> On 19/10/17 09:44, Sergey Beryozkin wrote:
>>> Hi Freeman
>>> Can you move ReaderInputStream to the CXF core where 
>>> CachedInputStream and other IO utility code is located ?
>>> Please also update the code which gets Reader.class and check it for 
>>> null first, and also drop a comment re the JMS transport, checking 
>>> the reader can make sense in all the cases...
>>> Thanks, Sergey
>>> On 19/10/17 04:16, ffang@apache.org <ma...@apache.org> wrote:
>>>> This is an automated email from the ASF dual-hosted git repository.
>>>>
>>>> ffang pushed a commit to branch master
>>>> in repository https://gitbox.apache.org/repos/asf/cxf.git
>>>>
>>>>
>>>> The following commit(s) were added to refs/heads/master by this push:
>>>>       new 1d66475  [CXF-7532]REST on JMS transport can't handle the 
>>>> request message with text messageType
>>>> 1d66475 is described below
>>>>
>>>> commit 1d66475b4dea2a0031dcb1765e8e9fd08430ec9b
>>>> Author: Freeman Fang <freeman.fang@gmail.com 
>>>> <ma...@gmail.com>>
>>>> AuthorDate: Thu Oct 19 11:15:50 2017 +0800
>>>>
>>>>      [CXF-7532]REST on JMS transport can't handle the request 
>>>> message with text messageType
>>>> ---
>>>>   .../org/apache/cxf/jaxrs/impl/ResponseImpl.java    |   5 +
>>>>   .../org/apache/cxf/jaxrs/utils/JAXRSUtils.java     |   6 +-
>>>>   .../apache/cxf/jaxrs/utils/ReaderInputStream.java  | 296 
>>>> +++++++++++++++++++++
>>>>   .../apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java |  90 +++++++
>>>>   4 files changed, 396 insertions(+), 1 deletion(-)
>>>>
>>>> diff --git 
>>>> a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java 
>>>> b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java 
>>>>
>>>> index e3a3acf..82f183a 100644
>>>> --- 
>>>> a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java 
>>>>
>>>> +++ 
>>>> b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java 
>>>>
>>>> @@ -58,6 +58,7 @@ import org.apache.cxf.jaxrs.provider.ProviderFactory;
>>>>   import org.apache.cxf.jaxrs.utils.HttpUtils;
>>>>   import org.apache.cxf.jaxrs.utils.InjectionUtils;
>>>>   import org.apache.cxf.jaxrs.utils.JAXRSUtils;
>>>> +import org.apache.cxf.jaxrs.utils.ReaderInputStream;
>>>>   import org.apache.cxf.message.Message;
>>>>   import org.apache.cxf.message.MessageUtils;
>>>> @@ -342,6 +343,10 @@ public final class ResponseImpl extends Response {
>>>>               entityStreamAvailable = entityStream != null;
>>>>           } else if (entity instanceof InputStream) {
>>>>               entityStream = InputStream.class.cast(entity);
>>>> +        } else {
>>>> +            Message inMessage = getResponseMessage();
>>>> +            Reader reader = inMessage.getContent(Reader.class);
>>>> +            entityStream = InputStream.class.cast(new 
>>>> ReaderInputStream(reader));
>>>>           }
>>>>           // we need to check for readers even if no IS is set - the 
>>>> readers may still do it
>>>> diff --git 
>>>> a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java 
>>>> b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java 
>>>>
>>>> index 0ef6565..509f76d 100644
>>>> --- 
>>>> a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java 
>>>>
>>>> +++ 
>>>> b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java 
>>>>
>>>> @@ -832,7 +832,11 @@ public final class JAXRSUtils {
>>>>                                              OperationResourceInfo ori)
>>>>           throws IOException, WebApplicationException {
>>>>           InputStream is = message.getContent(InputStream.class);
>>>> -
>>>> +        if (is == null) {
>>>> +            //may use the jms transport so check the Reader;
>>>> +            Reader reader = message.getContent(Reader.class);
>>>> +            is = new ReaderInputStream(reader);
>>>> +        }
>>>>           if (parameter.getType() == ParameterType.REQUEST_BODY) {
>>>>               if (parameterClass == AsyncResponse.class) {
>>>> diff --git 
>>>> a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java 
>>>> b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java 
>>>>
>>>> new file mode 100644
>>>> index 0000000..c7b142e
>>>> --- /dev/null
>>>> +++ 
>>>> b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java 
>>>>
>>>> @@ -0,0 +1,296 @@
>>>> +/**
>>>> + * 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.cxf.jaxrs.utils;
>>>> +
>>>> +import java.io.IOException;
>>>> +import java.io.InputStream;
>>>> +import java.io.Reader;
>>>> +import java.nio.ByteBuffer;
>>>> +import java.nio.CharBuffer;
>>>> +import java.nio.charset.Charset;
>>>> +import java.nio.charset.CharsetEncoder;
>>>> +import java.nio.charset.CoderResult;
>>>> +import java.nio.charset.CodingErrorAction;
>>>> +
>>>> +/**
>>>> + * {@link InputStream} implementation that reads a character stream 
>>>> from a {@link Reader}
>>>> + * and transforms it to a byte stream using a specified charset 
>>>> encoding. The stream
>>>> + * is transformed using a {@link CharsetEncoder} object, 
>>>> guaranteeing that all charset
>>>> + * encodings supported by the JRE are handled correctly. In 
>>>> particular for charsets such as
>>>> + * UTF-16, the implementation ensures that one and only one byte 
>>>> order marker
>>>> + * is produced.
>>>> + * <p>
>>>> + * Since in general it is not possible to predict the number of 
>>>> characters to be read from the
>>>> + * {@link Reader} to satisfy a read request on the {@link 
>>>> ReaderInputStream}, all reads from
>>>> + * the {@link Reader} are buffered. There is therefore no well 
>>>> defined correlation
>>>> + * between the current position of the {@link Reader} and that of 
>>>> the {@link ReaderInputStream}.
>>>> + * This also implies that in general there is no need to wrap the 
>>>> underlying {@link Reader}
>>>> + * in a {@link java.io.BufferedReader}.
>>>> + * <p>
>>>> + * {@link ReaderInputStream} implements the inverse transformation 
>>>> of {@link java.io.InputStreamReader};
>>>> + * in the following example, reading from <tt>in2</tt> would return 
>>>> the same byte
>>>> + * sequence as reading from <tt>in</tt> (provided that the initial 
>>>> byte sequence is legal
>>>> + * with respect to the charset encoding):
>>>> + * <pre>
>>>> + * InputStream in = ...
>>>> + * Charset cs = ...
>>>> + * InputStreamReader reader = new InputStreamReader(in, cs);
>>>> + * ReaderInputStream in2 = new ReaderInputStream(reader, cs);</pre>
>>>> + * {@link ReaderInputStream} implements the same transformation as 
>>>> {@link java.io.OutputStreamWriter},
>>>> + * except that the control flow is reversed: both classes transform 
>>>> a character stream
>>>> + * into a byte stream, but {@link java.io.OutputStreamWriter} 
>>>> pushes data to the underlying stream,
>>>> + * while {@link ReaderInputStream} pulls it from the underlying stream.
>>>> + * <p>
>>>> + * Note that while there are use cases where there is no 
>>>> alternative to using
>>>> + * this class, very often the need to use this class is an 
>>>> indication of a flaw
>>>> + * in the design of the code. This class is typically used in 
>>>> situations where an existing
>>>> + * API only accepts an {@link InputStream}, but where the most 
>>>> natural way to produce the data
>>>> + * is as a character stream, i.e. by providing a {@link Reader} 
>>>> instance. An example of a situation
>>>> + * where this problem may appear is when implementing the {@link 
>>>> javax.activation.DataSource}
>>>> + * interface from the Java Activation Framework.
>>>> + * <p>
>>>> + * Given the fact that the {@link Reader} class doesn't provide any 
>>>> way to predict whether the next
>>>> + * read operation will block or not, it is not possible to provide 
>>>> a meaningful
>>>> + * implementation of the {@link InputStream#available()} method. A 
>>>> call to this method
>>>> + * will always return 0. Also, this class doesn't support {@link 
>>>> InputStream#mark(int)}.
>>>> + * <p>
>>>> + * Instances of {@link ReaderInputStream} are not thread safe.
>>>> + *
>>>> + * @see org.apache.commons.io.output.WriterOutputStream
>>>> + *
>>>> + * @since 2.0
>>>> + */
>>>> +public class ReaderInputStream extends InputStream {
>>>> +    private static final int DEFAULT_BUFFER_SIZE = 1024;
>>>> +
>>>> +    private final Reader reader;
>>>> +    private final CharsetEncoder encoder;
>>>> +
>>>> +    /**
>>>> +     * CharBuffer used as input for the decoder. It should be 
>>>> reasonably
>>>> +     * large as we read data from the underlying Reader into this 
>>>> buffer.
>>>> +     */
>>>> +    private final CharBuffer encoderIn;
>>>> +
>>>> +    /**
>>>> +     * ByteBuffer used as output for the decoder. This buffer can 
>>>> be small
>>>> +     * as it is only used to transfer data from the decoder to the
>>>> +     * buffer provided by the caller.
>>>> +     */
>>>> +    private final ByteBuffer encoderOut;
>>>> +
>>>> +    private CoderResult lastCoderResult;
>>>> +    private boolean endOfInput;
>>>> +
>>>> +    /**
>>>> +     * Construct a new {@link ReaderInputStream}.
>>>> +     *
>>>> +     * @param reader the target {@link Reader}
>>>> +     * @param encoder the charset encoder
>>>> +     * @since 2.1
>>>> +     */
>>>> +    public ReaderInputStream(Reader reader, CharsetEncoder encoder) {
>>>> +        this(reader, encoder, DEFAULT_BUFFER_SIZE);
>>>> +    }
>>>> +
>>>> +    /**
>>>> +     * Construct a new {@link ReaderInputStream}.
>>>> +     *
>>>> +     * @param reader the target {@link Reader}
>>>> +     * @param encoder the charset encoder
>>>> +     * @param bufferSize the size of the input buffer in number of 
>>>> characters
>>>> +     * @since 2.1
>>>> +     */
>>>> +    public ReaderInputStream(Reader reader, CharsetEncoder encoder, 
>>>> int bufferSize) {
>>>> +        this.reader = reader;
>>>> +        this.encoder = encoder;
>>>> +        this.encoderIn = CharBuffer.allocate(bufferSize);
>>>> +        this.encoderIn.flip();
>>>> +        this.encoderOut = ByteBuffer.allocate(128);
>>>> +        this.encoderOut.flip();
>>>> +    }
>>>> +
>>>> +    /**
>>>> +     * Construct a new {@link ReaderInputStream}.
>>>> +     *
>>>> +     * @param reader the target {@link Reader}
>>>> +     * @param charset the charset encoding
>>>> +     * @param bufferSize the size of the input buffer in number of 
>>>> characters
>>>> +     */
>>>> +    public ReaderInputStream(Reader reader, Charset charset, int 
>>>> bufferSize) {
>>>> +        this(reader,
>>>> +             charset.newEncoder()
>>>> +                    .onMalformedInput(CodingErrorAction.REPLACE)
>>>> +                    .onUnmappableCharacter(CodingErrorAction.REPLACE),
>>>> +             bufferSize);
>>>> +    }
>>>> +
>>>> +    /**
>>>> +     * Construct a new {@link ReaderInputStream} with a default 
>>>> input buffer size of
>>>> +     * 1024 characters.
>>>> +     *
>>>> +     * @param reader the target {@link Reader}
>>>> +     * @param charset the charset encoding
>>>> +     */
>>>> +    public ReaderInputStream(Reader reader, Charset charset) {
>>>> +        this(reader, charset, DEFAULT_BUFFER_SIZE);
>>>> +    }
>>>> +
>>>> +    /**
>>>> +     * Construct a new {@link ReaderInputStream}.
>>>> +     *
>>>> +     * @param reader the target {@link Reader}
>>>> +     * @param charsetName the name of the charset encoding
>>>> +     * @param bufferSize the size of the input buffer in number of 
>>>> characters
>>>> +     */
>>>> +    public ReaderInputStream(Reader reader, String charsetName, int 
>>>> bufferSize) {
>>>> +        this(reader, Charset.forName(charsetName), bufferSize);
>>>> +    }
>>>> +
>>>> +    /**
>>>> +     * Construct a new {@link ReaderInputStream} with a default 
>>>> input buffer size of
>>>> +     * 1024 characters.
>>>> +     *
>>>> +     * @param reader the target {@link Reader}
>>>> +     * @param charsetName the name of the charset encoding
>>>> +     */
>>>> +    public ReaderInputStream(Reader reader, String charsetName) {
>>>> +        this(reader, charsetName, DEFAULT_BUFFER_SIZE);
>>>> +    }
>>>> +
>>>> +    /**
>>>> +     * Construct a new {@link ReaderInputStream} that uses the 
>>>> default character encoding
>>>> +     * with a default input buffer size of 1024 characters.
>>>> +     *
>>>> +     * @param reader the target {@link Reader}
>>>> +     */
>>>> +    public ReaderInputStream(Reader reader) {
>>>> +        this(reader, Charset.defaultCharset());
>>>> +    }
>>>> +
>>>> +    /**
>>>> +     * Fills the internal char buffer from the reader.
>>>> +     *
>>>> +     * @throws IOException
>>>> +     *             If an I/O error occurs
>>>> +     */
>>>> +    private void fillBuffer() throws IOException {
>>>> +        if (!endOfInput && (lastCoderResult == null || 
>>>> lastCoderResult.isUnderflow())) {
>>>> +            encoderIn.compact();
>>>> +            int position = encoderIn.position();
>>>> +            // We don't use Reader#read(CharBuffer) here because it 
>>>> is more efficient
>>>> +            // to write directly to the underlying char array (the 
>>>> default implementation
>>>> +            // copies data to a temporary char array).
>>>> +            int c = reader.read(encoderIn.array(), position, 
>>>> encoderIn.remaining());
>>>> +            if (c == -1) {
>>>> +                endOfInput = true;
>>>> +            } else {
>>>> +                encoderIn.position(position + c);
>>>> +            }
>>>> +            encoderIn.flip();
>>>> +        }
>>>> +        encoderOut.compact();
>>>> +        lastCoderResult = encoder.encode(encoderIn, encoderOut, 
>>>> endOfInput);
>>>> +        encoderOut.flip();
>>>> +    }
>>>> +
>>>> +    /**
>>>> +     * Read the specified number of bytes into an array.
>>>> +     *
>>>> +     * @param b the byte array to read into
>>>> +     * @param off the offset to start reading bytes into
>>>> +     * @param len the number of bytes to read
>>>> +     * @return the number of bytes read or <code>-1</code>
>>>> +     *         if the end of the stream has been reached
>>>> +     * @throws IOException if an I/O error occurs
>>>> +     */
>>>> +    @Override
>>>> +    public int read(byte[] b, int off, int len) throws IOException {
>>>> +        if (b == null) {
>>>> +            throw new NullPointerException("Byte array must not be 
>>>> null");
>>>> +        }
>>>> +        if (len < 0 || off < 0 || (off + len) > b.length) {
>>>> +            throw new IndexOutOfBoundsException("Array Size=" + 
>>>> b.length
>>>> +                    + ", offset=" + off + ", length=" + len);
>>>> +        }
>>>> +        int read = 0;
>>>> +        if (len == 0) {
>>>> +            return 0; // Always return 0 if len == 0
>>>> +        }
>>>> +        while (len > 0) {
>>>> +            if (encoderOut.hasRemaining()) {
>>>> +                int c = Math.min(encoderOut.remaining(), len);
>>>> +                encoderOut.get(b, off, c);
>>>> +                off += c;
>>>> +                len -= c;
>>>> +                read += c;
>>>> +            } else {
>>>> +                fillBuffer();
>>>> +                if (endOfInput && !encoderOut.hasRemaining()) {
>>>> +                    break;
>>>> +                }
>>>> +            }
>>>> +        }
>>>> +        return read == 0 && endOfInput ? -1 : read;
>>>> +    }
>>>> +
>>>> +    /**
>>>> +     * Read the specified number of bytes into an array.
>>>> +     *
>>>> +     * @param b the byte array to read into
>>>> +     * @return the number of bytes read or <code>-1</code>
>>>> +     *         if the end of the stream has been reached
>>>> +     * @throws IOException if an I/O error occurs
>>>> +     */
>>>> +    @Override
>>>> +    public int read(byte[] b) throws IOException {
>>>> +        return read(b, 0, b.length);
>>>> +    }
>>>> +
>>>> +    /**
>>>> +     * Read a single byte.
>>>> +     *
>>>> +     * @return either the byte read or <code>-1</code> if the end 
>>>> of the stream
>>>> +     *         has been reached
>>>> +     * @throws IOException if an I/O error occurs
>>>> +     */
>>>> +    @Override
>>>> +    public int read() throws IOException {
>>>> +        for (;;) {
>>>> +            if (encoderOut.hasRemaining()) {
>>>> +                return encoderOut.get() & 0xFF;
>>>> +            } else {
>>>> +                fillBuffer();
>>>> +                if (endOfInput && !encoderOut.hasRemaining()) {
>>>> +                    return -1;
>>>> +                }
>>>> +            }
>>>> +        }
>>>> +    }
>>>> +
>>>> +    /**
>>>> +     * Close the stream. This method will cause the underlying 
>>>> {@link Reader}
>>>> +     * to be closed.
>>>> +     * @throws IOException if an I/O error occurs
>>>> +     */
>>>> +    @Override
>>>> +    public void close() throws IOException {
>>>> +        reader.close();
>>>> +    }
>>>> +}
>>>> diff --git 
>>>> a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java 
>>>> b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java 
>>>>
>>>> index f77b5ed..b957f36 100644
>>>> --- 
>>>> a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java 
>>>>
>>>> +++ 
>>>> b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java 
>>>>
>>>> @@ -96,6 +96,25 @@ public class JAXRSJmsTest extends 
>>>> AbstractBusClientServerTestBase {
>>>>           assertEquals("Get a wrong response code.", 200, 
>>>> client.getResponse().getStatus());
>>>>           assertEquals("Get a wrong book id.", 123, book.getId());
>>>>       }
>>>> +
>>>> +    @Test
>>>> +    public void testGetBookFromWebClientWithTextJMSMessage() throws 
>>>> Exception {
>>>> +        // setup the the client
>>>> +        String endpointAddressUrlEncoded = 
>>>> "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>> +             + "?replyToName=dynamicQueues/test.jmstransport.response"
>>>> +             + 
>>>> "&jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>>>
>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>> +             + "&messageType=text";
>>>> +
>>>> +        WebClient client = WebClient.create(endpointAddressUrlEncoded);
>>>> +        WebClient.getConfig(client).getInInterceptors().add(new 
>>>> LoggingInInterceptor());
>>>> +        WebClient.getConfig(client).getRequestContext()
>>>> +            .put(org.apache.cxf.message.Message.REQUEST_URI, 
>>>> "/bookstore/books/123");
>>>> +
>>>> +        Book book = client.get(Book.class);
>>>> +        assertEquals("Get a wrong response code.", 200, 
>>>> client.getResponse().getStatus());
>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>> +    }
>>>>       @Test
>>>>       public void testPutBookOneWayWithWebClient() throws Exception {
>>>> @@ -130,6 +149,7 @@ public class JAXRSJmsTest extends 
>>>> AbstractBusClientServerTestBase {
>>>>           }
>>>>       }
>>>> +
>>>>       @Test
>>>>       public void testGetBookFromWebClientWithPath() throws Exception {
>>>>           // setup the the client
>>>> @@ -148,6 +168,25 @@ public class JAXRSJmsTest extends 
>>>> AbstractBusClientServerTestBase {
>>>>       }
>>>>       @Test
>>>> +    public void 
>>>> testGetBookFromWebClientWithPathWithTextJMSMessage() throws Exception {
>>>> +        // setup the the client
>>>> +        String endpointAddressUrlEncoded = 
>>>> "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>> +             + 
>>>> "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>>>
>>>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>> +             + "&messageType=text";
>>>> +
>>>> +        WebClient client = WebClient.create(endpointAddressUrlEncoded);
>>>> +        client.path("bookstore").path("books").path("123");
>>>> +
>>>> +        Book book = client.get(Book.class);
>>>> +        assertEquals("Get a wrong response code.", 200, 
>>>> client.getResponse().getStatus());
>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>> +    }
>>>> +
>>>> +
>>>> +    @Test
>>>>       public void testGetBookFromProxyClient() throws Exception {
>>>>           // setup the the client
>>>>           String endpointAddressUrlEncoded = 
>>>> "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>> @@ -161,6 +200,22 @@ public class JAXRSJmsTest extends 
>>>> AbstractBusClientServerTestBase {
>>>>           assertEquals("Get a wrong response code.", 200, 
>>>> WebClient.client(client).getResponse().getStatus());
>>>>           assertEquals("Get a wrong book id.", 123, book.getId());
>>>>       }
>>>> +
>>>> +    @Test
>>>> +    public void testGetBookFromProxyClientWithTextJMSMessage() 
>>>> throws Exception {
>>>> +        // setup the the client
>>>> +        String endpointAddressUrlEncoded = 
>>>> "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>> +             + 
>>>> "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>>>
>>>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>> +             + "&messageType=text";
>>>> +
>>>> +        JMSBookStore client = 
>>>> JAXRSClientFactory.create(endpointAddressUrlEncoded, 
>>>> JMSBookStore.class);
>>>> +        Book book = client.getBook("123");
>>>> +        assertEquals("Get a wrong response code.", 200, 
>>>> WebClient.client(client).getResponse().getStatus());
>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>> +    }
>>>>       @Test
>>>>       public void testGetBookFromSubresourceProxyClient() throws 
>>>> Exception {
>>>> @@ -177,6 +232,23 @@ public class JAXRSJmsTest extends 
>>>> AbstractBusClientServerTestBase {
>>>>           assertEquals("Get a wrong response code.", 200, 
>>>> WebClient.client(bookProxy).getResponse().getStatus());
>>>>           assertEquals("Get a wrong book id.", 123, book.getId());
>>>>       }
>>>> +
>>>> +    @Test
>>>> +    public void 
>>>> testGetBookFromSubresourceProxyClientWithTextJMSMessage() throws 
>>>> Exception {
>>>> +        // setup the the client
>>>> +        String endpointAddressUrlEncoded = 
>>>> "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>> +             + 
>>>> "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>>>
>>>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>> +             + "&messageType=text";
>>>> +
>>>> +        JMSBookStore client = 
>>>> JAXRSClientFactory.create(endpointAddressUrlEncoded, 
>>>> JMSBookStore.class);
>>>> +        Book bookProxy = client.getBookSubResource("123");
>>>> +        Book book = bookProxy.retrieveState();
>>>> +        assertEquals("Get a wrong response code.", 200, 
>>>> WebClient.client(bookProxy).getResponse().getStatus());
>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>> +    }
>>>>       @Test
>>>>       public void testGetBookFromProxyClientWithQuery() throws 
>>>> Exception {
>>>> @@ -193,6 +265,24 @@ public class JAXRSJmsTest extends 
>>>> AbstractBusClientServerTestBase {
>>>>           assertEquals("Get a wrong book id.", 123, book.getId());
>>>>       }
>>>> +
>>>> +    @Test
>>>> +    public void 
>>>> testGetBookFromProxyClientWithQueryWithTextJMSMessage() throws 
>>>> Exception {
>>>> +        // setup the the client
>>>> +        String endpointAddressUrlEncoded = 
>>>> "jms:jndi:dynamicQueues/test.jmstransport.text"
>>>> +             + 
>>>> "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>>>
>>>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>>> +             + "&messageType=text";
>>>> +
>>>> +        JMSBookStore client = 
>>>> JAXRSClientFactory.create(endpointAddressUrlEncoded, 
>>>> JMSBookStore.class);
>>>> +        Book book = client.getBookByURLQuery(new String[] {"1", 
>>>> "2", "3"});
>>>> +        assertEquals("Get a wrong response code.", 200, 
>>>> WebClient.client(client).getResponse().getStatus());
>>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>>> +    }
>>>> +
>>>> +
>>>>       @Test
>>>>       public void testGetBook() throws Exception {
>>>>           Context ctx = getContext();
>>>>
>>
> 

Re: [cxf] branch master updated: [CXF-7532]REST on JMS transport can't handle the request message with text messageType

Posted by ffang <fr...@gmail.com>.
Yeah, set ReaderInputStream instead of Reader for the JMS transport is a better solution. If no objection I will go this way.
-------------
Freeman(Yue) Fang

Red Hat, Inc. 
FuseSource is now part of Red Hat



> On Oct 19, 2017, at 4:50 PM, Sergey Beryozkin <sb...@gmail.com> wrote:
> 
> The other thing is, should JMS transport itself set ReaderInputStream as InputStream on the message, as opposed to patching the JAX-RS code in various places ?
> 
> Sergey
> On 19/10/17 09:44, Sergey Beryozkin wrote:
>> Hi Freeman
>> Can you move ReaderInputStream to the CXF core where CachedInputStream and other IO utility code is located ?
>> Please also update the code which gets Reader.class and check it for null first, and also drop a comment re the JMS transport, checking the reader can make sense in all the cases...
>> Thanks, Sergey
>> On 19/10/17 04:16, ffang@apache.org wrote:
>>> This is an automated email from the ASF dual-hosted git repository.
>>> 
>>> ffang pushed a commit to branch master
>>> in repository https://gitbox.apache.org/repos/asf/cxf.git
>>> 
>>> 
>>> The following commit(s) were added to refs/heads/master by this push:
>>>       new 1d66475  [CXF-7532]REST on JMS transport can't handle the request message with text messageType
>>> 1d66475 is described below
>>> 
>>> commit 1d66475b4dea2a0031dcb1765e8e9fd08430ec9b
>>> Author: Freeman Fang <fr...@gmail.com>
>>> AuthorDate: Thu Oct 19 11:15:50 2017 +0800
>>> 
>>>      [CXF-7532]REST on JMS transport can't handle the request message with text messageType
>>> ---
>>>   .../org/apache/cxf/jaxrs/impl/ResponseImpl.java    |   5 +
>>>   .../org/apache/cxf/jaxrs/utils/JAXRSUtils.java     |   6 +-
>>>   .../apache/cxf/jaxrs/utils/ReaderInputStream.java  | 296 +++++++++++++++++++++
>>>   .../apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java |  90 +++++++
>>>   4 files changed, 396 insertions(+), 1 deletion(-)
>>> 
>>> diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java 
>>> index e3a3acf..82f183a 100644
>>> --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java 
>>> +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java 
>>> @@ -58,6 +58,7 @@ import org.apache.cxf.jaxrs.provider.ProviderFactory;
>>>   import org.apache.cxf.jaxrs.utils.HttpUtils;
>>>   import org.apache.cxf.jaxrs.utils.InjectionUtils;
>>>   import org.apache.cxf.jaxrs.utils.JAXRSUtils;
>>> +import org.apache.cxf.jaxrs.utils.ReaderInputStream;
>>>   import org.apache.cxf.message.Message;
>>>   import org.apache.cxf.message.MessageUtils;
>>> @@ -342,6 +343,10 @@ public final class ResponseImpl extends Response {
>>>               entityStreamAvailable = entityStream != null;
>>>           } else if (entity instanceof InputStream) {
>>>               entityStream = InputStream.class.cast(entity);
>>> +        } else {
>>> +            Message inMessage = getResponseMessage();
>>> +            Reader reader = inMessage.getContent(Reader.class);
>>> +            entityStream = InputStream.class.cast(new ReaderInputStream(reader));
>>>           }
>>>           // we need to check for readers even if no IS is set - the readers may still do it
>>> diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java 
>>> index 0ef6565..509f76d 100644
>>> --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java 
>>> +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java 
>>> @@ -832,7 +832,11 @@ public final class JAXRSUtils {
>>>                                              OperationResourceInfo ori)
>>>           throws IOException, WebApplicationException {
>>>           InputStream is = message.getContent(InputStream.class);
>>> -
>>> +        if (is == null) {
>>> +            //may use the jms transport so check the Reader;
>>> +            Reader reader = message.getContent(Reader.class);
>>> +            is = new ReaderInputStream(reader);
>>> +        }
>>>           if (parameter.getType() == ParameterType.REQUEST_BODY) {
>>>               if (parameterClass == AsyncResponse.class) {
>>> diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java 
>>> new file mode 100644
>>> index 0000000..c7b142e
>>> --- /dev/null
>>> +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java 
>>> @@ -0,0 +1,296 @@
>>> +/**
>>> + * 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.cxf.jaxrs.utils;
>>> +
>>> +import java.io.IOException;
>>> +import java.io.InputStream;
>>> +import java.io.Reader;
>>> +import java.nio.ByteBuffer;
>>> +import java.nio.CharBuffer;
>>> +import java.nio.charset.Charset;
>>> +import java.nio.charset.CharsetEncoder;
>>> +import java.nio.charset.CoderResult;
>>> +import java.nio.charset.CodingErrorAction;
>>> +
>>> +/**
>>> + * {@link InputStream} implementation that reads a character stream from a {@link Reader}
>>> + * and transforms it to a byte stream using a specified charset encoding. The stream
>>> + * is transformed using a {@link CharsetEncoder} object, guaranteeing that all charset
>>> + * encodings supported by the JRE are handled correctly. In particular for charsets such as
>>> + * UTF-16, the implementation ensures that one and only one byte order marker
>>> + * is produced.
>>> + * <p>
>>> + * Since in general it is not possible to predict the number of characters to be read from the
>>> + * {@link Reader} to satisfy a read request on the {@link ReaderInputStream}, all reads from
>>> + * the {@link Reader} are buffered. There is therefore no well defined correlation
>>> + * between the current position of the {@link Reader} and that of the {@link ReaderInputStream}.
>>> + * This also implies that in general there is no need to wrap the underlying {@link Reader}
>>> + * in a {@link java.io.BufferedReader}.
>>> + * <p>
>>> + * {@link ReaderInputStream} implements the inverse transformation of {@link java.io.InputStreamReader};
>>> + * in the following example, reading from <tt>in2</tt> would return the same byte
>>> + * sequence as reading from <tt>in</tt> (provided that the initial byte sequence is legal
>>> + * with respect to the charset encoding):
>>> + * <pre>
>>> + * InputStream in = ...
>>> + * Charset cs = ...
>>> + * InputStreamReader reader = new InputStreamReader(in, cs);
>>> + * ReaderInputStream in2 = new ReaderInputStream(reader, cs);</pre>
>>> + * {@link ReaderInputStream} implements the same transformation as {@link java.io.OutputStreamWriter},
>>> + * except that the control flow is reversed: both classes transform a character stream
>>> + * into a byte stream, but {@link java.io.OutputStreamWriter} pushes data to the underlying stream,
>>> + * while {@link ReaderInputStream} pulls it from the underlying stream.
>>> + * <p>
>>> + * Note that while there are use cases where there is no alternative to using
>>> + * this class, very often the need to use this class is an indication of a flaw
>>> + * in the design of the code. This class is typically used in situations where an existing
>>> + * API only accepts an {@link InputStream}, but where the most natural way to produce the data
>>> + * is as a character stream, i.e. by providing a {@link Reader} instance. An example of a situation
>>> + * where this problem may appear is when implementing the {@link javax.activation.DataSource}
>>> + * interface from the Java Activation Framework.
>>> + * <p>
>>> + * Given the fact that the {@link Reader} class doesn't provide any way to predict whether the next
>>> + * read operation will block or not, it is not possible to provide a meaningful
>>> + * implementation of the {@link InputStream#available()} method. A call to this method
>>> + * will always return 0. Also, this class doesn't support {@link InputStream#mark(int)}.
>>> + * <p>
>>> + * Instances of {@link ReaderInputStream} are not thread safe.
>>> + *
>>> + * @see org.apache.commons.io.output.WriterOutputStream
>>> + *
>>> + * @since 2.0
>>> + */
>>> +public class ReaderInputStream extends InputStream {
>>> +    private static final int DEFAULT_BUFFER_SIZE = 1024;
>>> +
>>> +    private final Reader reader;
>>> +    private final CharsetEncoder encoder;
>>> +
>>> +    /**
>>> +     * CharBuffer used as input for the decoder. It should be reasonably
>>> +     * large as we read data from the underlying Reader into this buffer.
>>> +     */
>>> +    private final CharBuffer encoderIn;
>>> +
>>> +    /**
>>> +     * ByteBuffer used as output for the decoder. This buffer can be small
>>> +     * as it is only used to transfer data from the decoder to the
>>> +     * buffer provided by the caller.
>>> +     */
>>> +    private final ByteBuffer encoderOut;
>>> +
>>> +    private CoderResult lastCoderResult;
>>> +    private boolean endOfInput;
>>> +
>>> +    /**
>>> +     * Construct a new {@link ReaderInputStream}.
>>> +     *
>>> +     * @param reader the target {@link Reader}
>>> +     * @param encoder the charset encoder
>>> +     * @since 2.1
>>> +     */
>>> +    public ReaderInputStream(Reader reader, CharsetEncoder encoder) {
>>> +        this(reader, encoder, DEFAULT_BUFFER_SIZE);
>>> +    }
>>> +
>>> +    /**
>>> +     * Construct a new {@link ReaderInputStream}.
>>> +     *
>>> +     * @param reader the target {@link Reader}
>>> +     * @param encoder the charset encoder
>>> +     * @param bufferSize the size of the input buffer in number of characters
>>> +     * @since 2.1
>>> +     */
>>> +    public ReaderInputStream(Reader reader, CharsetEncoder encoder, int bufferSize) {
>>> +        this.reader = reader;
>>> +        this.encoder = encoder;
>>> +        this.encoderIn = CharBuffer.allocate(bufferSize);
>>> +        this.encoderIn.flip();
>>> +        this.encoderOut = ByteBuffer.allocate(128);
>>> +        this.encoderOut.flip();
>>> +    }
>>> +
>>> +    /**
>>> +     * Construct a new {@link ReaderInputStream}.
>>> +     *
>>> +     * @param reader the target {@link Reader}
>>> +     * @param charset the charset encoding
>>> +     * @param bufferSize the size of the input buffer in number of characters
>>> +     */
>>> +    public ReaderInputStream(Reader reader, Charset charset, int bufferSize) {
>>> +        this(reader,
>>> +             charset.newEncoder()
>>> +                    .onMalformedInput(CodingErrorAction.REPLACE)
>>> +                    .onUnmappableCharacter(CodingErrorAction.REPLACE),
>>> +             bufferSize);
>>> +    }
>>> +
>>> +    /**
>>> +     * Construct a new {@link ReaderInputStream} with a default input buffer size of
>>> +     * 1024 characters.
>>> +     *
>>> +     * @param reader the target {@link Reader}
>>> +     * @param charset the charset encoding
>>> +     */
>>> +    public ReaderInputStream(Reader reader, Charset charset) {
>>> +        this(reader, charset, DEFAULT_BUFFER_SIZE);
>>> +    }
>>> +
>>> +    /**
>>> +     * Construct a new {@link ReaderInputStream}.
>>> +     *
>>> +     * @param reader the target {@link Reader}
>>> +     * @param charsetName the name of the charset encoding
>>> +     * @param bufferSize the size of the input buffer in number of characters
>>> +     */
>>> +    public ReaderInputStream(Reader reader, String charsetName, int bufferSize) {
>>> +        this(reader, Charset.forName(charsetName), bufferSize);
>>> +    }
>>> +
>>> +    /**
>>> +     * Construct a new {@link ReaderInputStream} with a default input buffer size of
>>> +     * 1024 characters.
>>> +     *
>>> +     * @param reader the target {@link Reader}
>>> +     * @param charsetName the name of the charset encoding
>>> +     */
>>> +    public ReaderInputStream(Reader reader, String charsetName) {
>>> +        this(reader, charsetName, DEFAULT_BUFFER_SIZE);
>>> +    }
>>> +
>>> +    /**
>>> +     * Construct a new {@link ReaderInputStream} that uses the default character encoding
>>> +     * with a default input buffer size of 1024 characters.
>>> +     *
>>> +     * @param reader the target {@link Reader}
>>> +     */
>>> +    public ReaderInputStream(Reader reader) {
>>> +        this(reader, Charset.defaultCharset());
>>> +    }
>>> +
>>> +    /**
>>> +     * Fills the internal char buffer from the reader.
>>> +     *
>>> +     * @throws IOException
>>> +     *             If an I/O error occurs
>>> +     */
>>> +    private void fillBuffer() throws IOException {
>>> +        if (!endOfInput && (lastCoderResult == null || lastCoderResult.isUnderflow())) {
>>> +            encoderIn.compact();
>>> +            int position = encoderIn.position();
>>> +            // We don't use Reader#read(CharBuffer) here because it is more efficient
>>> +            // to write directly to the underlying char array (the default implementation
>>> +            // copies data to a temporary char array).
>>> +            int c = reader.read(encoderIn.array(), position, encoderIn.remaining());
>>> +            if (c == -1) {
>>> +                endOfInput = true;
>>> +            } else {
>>> +                encoderIn.position(position + c);
>>> +            }
>>> +            encoderIn.flip();
>>> +        }
>>> +        encoderOut.compact();
>>> +        lastCoderResult = encoder.encode(encoderIn, encoderOut, endOfInput);
>>> +        encoderOut.flip();
>>> +    }
>>> +
>>> +    /**
>>> +     * Read the specified number of bytes into an array.
>>> +     *
>>> +     * @param b the byte array to read into
>>> +     * @param off the offset to start reading bytes into
>>> +     * @param len the number of bytes to read
>>> +     * @return the number of bytes read or <code>-1</code>
>>> +     *         if the end of the stream has been reached
>>> +     * @throws IOException if an I/O error occurs
>>> +     */
>>> +    @Override
>>> +    public int read(byte[] b, int off, int len) throws IOException {
>>> +        if (b == null) {
>>> +            throw new NullPointerException("Byte array must not be null");
>>> +        }
>>> +        if (len < 0 || off < 0 || (off + len) > b.length) {
>>> +            throw new IndexOutOfBoundsException("Array Size=" + b.length
>>> +                    + ", offset=" + off + ", length=" + len);
>>> +        }
>>> +        int read = 0;
>>> +        if (len == 0) {
>>> +            return 0; // Always return 0 if len == 0
>>> +        }
>>> +        while (len > 0) {
>>> +            if (encoderOut.hasRemaining()) {
>>> +                int c = Math.min(encoderOut.remaining(), len);
>>> +                encoderOut.get(b, off, c);
>>> +                off += c;
>>> +                len -= c;
>>> +                read += c;
>>> +            } else {
>>> +                fillBuffer();
>>> +                if (endOfInput && !encoderOut.hasRemaining()) {
>>> +                    break;
>>> +                }
>>> +            }
>>> +        }
>>> +        return read == 0 && endOfInput ? -1 : read;
>>> +    }
>>> +
>>> +    /**
>>> +     * Read the specified number of bytes into an array.
>>> +     *
>>> +     * @param b the byte array to read into
>>> +     * @return the number of bytes read or <code>-1</code>
>>> +     *         if the end of the stream has been reached
>>> +     * @throws IOException if an I/O error occurs
>>> +     */
>>> +    @Override
>>> +    public int read(byte[] b) throws IOException {
>>> +        return read(b, 0, b.length);
>>> +    }
>>> +
>>> +    /**
>>> +     * Read a single byte.
>>> +     *
>>> +     * @return either the byte read or <code>-1</code> if the end of the stream
>>> +     *         has been reached
>>> +     * @throws IOException if an I/O error occurs
>>> +     */
>>> +    @Override
>>> +    public int read() throws IOException {
>>> +        for (;;) {
>>> +            if (encoderOut.hasRemaining()) {
>>> +                return encoderOut.get() & 0xFF;
>>> +            } else {
>>> +                fillBuffer();
>>> +                if (endOfInput && !encoderOut.hasRemaining()) {
>>> +                    return -1;
>>> +                }
>>> +            }
>>> +        }
>>> +    }
>>> +
>>> +    /**
>>> +     * Close the stream. This method will cause the underlying {@link Reader}
>>> +     * to be closed.
>>> +     * @throws IOException if an I/O error occurs
>>> +     */
>>> +    @Override
>>> +    public void close() throws IOException {
>>> +        reader.close();
>>> +    }
>>> +}
>>> diff --git a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java 
>>> index f77b5ed..b957f36 100644
>>> --- a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java 
>>> +++ b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java 
>>> @@ -96,6 +96,25 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>           assertEquals("Get a wrong response code.", 200, client.getResponse().getStatus());
>>>           assertEquals("Get a wrong book id.", 123, book.getId());
>>>       }
>>> +
>>> +    @Test
>>> +    public void testGetBookFromWebClientWithTextJMSMessage() throws Exception {
>>> +        // setup the the client
>>> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>> +             + "?replyToName=dynamicQueues/test.jmstransport.response"
>>> +             + "&jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>> +             + "&messageType=text";
>>> +
>>> +        WebClient client = WebClient.create(endpointAddressUrlEncoded);
>>> +        WebClient.getConfig(client).getInInterceptors().add(new LoggingInInterceptor());
>>> +        WebClient.getConfig(client).getRequestContext()
>>> +            .put(org.apache.cxf.message.Message.REQUEST_URI, "/bookstore/books/123");
>>> +
>>> +        Book book = client.get(Book.class);
>>> +        assertEquals("Get a wrong response code.", 200, client.getResponse().getStatus());
>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>> +    }
>>>       @Test
>>>       public void testPutBookOneWayWithWebClient() throws Exception {
>>> @@ -130,6 +149,7 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>           }
>>>       }
>>> +
>>>       @Test
>>>       public void testGetBookFromWebClientWithPath() throws Exception {
>>>           // setup the the client
>>> @@ -148,6 +168,25 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>       }
>>>       @Test
>>> +    public void testGetBookFromWebClientWithPathWithTextJMSMessage() throws Exception {
>>> +        // setup the the client
>>> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>> +             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>> +             + "&messageType=text";
>>> +
>>> +        WebClient client = WebClient.create(endpointAddressUrlEncoded);
>>> +        client.path("bookstore").path("books").path("123");
>>> +
>>> +        Book book = client.get(Book.class);
>>> +        assertEquals("Get a wrong response code.", 200, client.getResponse().getStatus());
>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>> +    }
>>> +
>>> +
>>> +    @Test
>>>       public void testGetBookFromProxyClient() throws Exception {
>>>           // setup the the client
>>>           String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>> @@ -161,6 +200,22 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>           assertEquals("Get a wrong response code.", 200, WebClient.client(client).getResponse().getStatus());
>>>           assertEquals("Get a wrong book id.", 123, book.getId());
>>>       }
>>> +
>>> +    @Test
>>> +    public void testGetBookFromProxyClientWithTextJMSMessage() throws Exception {
>>> +        // setup the the client
>>> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>> +             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>> +             + "&messageType=text";
>>> +
>>> +        JMSBookStore client = JAXRSClientFactory.create(endpointAddressUrlEncoded, JMSBookStore.class);
>>> +        Book book = client.getBook("123");
>>> +        assertEquals("Get a wrong response code.", 200, WebClient.client(client).getResponse().getStatus());
>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>> +    }
>>>       @Test
>>>       public void testGetBookFromSubresourceProxyClient() throws Exception {
>>> @@ -177,6 +232,23 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>           assertEquals("Get a wrong response code.", 200, WebClient.client(bookProxy).getResponse().getStatus());
>>>           assertEquals("Get a wrong book id.", 123, book.getId());
>>>       }
>>> +
>>> +    @Test
>>> +    public void testGetBookFromSubresourceProxyClientWithTextJMSMessage() throws Exception {
>>> +        // setup the the client
>>> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>> +             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>> +             + "&messageType=text";
>>> +
>>> +        JMSBookStore client = JAXRSClientFactory.create(endpointAddressUrlEncoded, JMSBookStore.class);
>>> +        Book bookProxy = client.getBookSubResource("123");
>>> +        Book book = bookProxy.retrieveState();
>>> +        assertEquals("Get a wrong response code.", 200, WebClient.client(bookProxy).getResponse().getStatus());
>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>> +    }
>>>       @Test
>>>       public void testGetBookFromProxyClientWithQuery() throws Exception {
>>> @@ -193,6 +265,24 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>>>           assertEquals("Get a wrong book id.", 123, book.getId());
>>>       }
>>> +
>>> +    @Test
>>> +    public void testGetBookFromProxyClientWithQueryWithTextJMSMessage() throws Exception {
>>> +        // setup the the client
>>> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
>>> +             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>>> +             + "&messageType=text";
>>> +
>>> +        JMSBookStore client = JAXRSClientFactory.create(endpointAddressUrlEncoded, JMSBookStore.class);
>>> +        Book book = client.getBookByURLQuery(new String[] {"1", "2", "3"});
>>> +        assertEquals("Get a wrong response code.", 200, WebClient.client(client).getResponse().getStatus());
>>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>>> +    }
>>> +
>>> +
>>>       @Test
>>>       public void testGetBook() throws Exception {
>>>           Context ctx = getContext();
>>> 
> 


Re: [cxf] branch master updated: [CXF-7532]REST on JMS transport can't handle the request message with text messageType

Posted by Sergey Beryozkin <sb...@gmail.com>.
The other thing is, should JMS transport itself set ReaderInputStream as 
InputStream on the message, as opposed to patching the JAX-RS code in 
various places ?

Sergey
On 19/10/17 09:44, Sergey Beryozkin wrote:
> Hi Freeman
> 
> Can you move ReaderInputStream to the CXF core where CachedInputStream 
> and other IO utility code is located ?
> 
> Please also update the code which gets Reader.class and check it for 
> null first, and also drop a comment re the JMS transport, checking the 
> reader can make sense in all the cases...
> 
> Thanks, Sergey
> On 19/10/17 04:16, ffang@apache.org wrote:
>> This is an automated email from the ASF dual-hosted git repository.
>>
>> ffang pushed a commit to branch master
>> in repository https://gitbox.apache.org/repos/asf/cxf.git
>>
>>
>> The following commit(s) were added to refs/heads/master by this push:
>>       new 1d66475  [CXF-7532]REST on JMS transport can't handle the 
>> request message with text messageType
>> 1d66475 is described below
>>
>> commit 1d66475b4dea2a0031dcb1765e8e9fd08430ec9b
>> Author: Freeman Fang <fr...@gmail.com>
>> AuthorDate: Thu Oct 19 11:15:50 2017 +0800
>>
>>      [CXF-7532]REST on JMS transport can't handle the request message 
>> with text messageType
>> ---
>>   .../org/apache/cxf/jaxrs/impl/ResponseImpl.java    |   5 +
>>   .../org/apache/cxf/jaxrs/utils/JAXRSUtils.java     |   6 +-
>>   .../apache/cxf/jaxrs/utils/ReaderInputStream.java  | 296 
>> +++++++++++++++++++++
>>   .../apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java |  90 +++++++
>>   4 files changed, 396 insertions(+), 1 deletion(-)
>>
>> diff --git 
>> a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java 
>> b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java 
>>
>> index e3a3acf..82f183a 100644
>> --- 
>> a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java 
>>
>> +++ 
>> b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java 
>>
>> @@ -58,6 +58,7 @@ import org.apache.cxf.jaxrs.provider.ProviderFactory;
>>   import org.apache.cxf.jaxrs.utils.HttpUtils;
>>   import org.apache.cxf.jaxrs.utils.InjectionUtils;
>>   import org.apache.cxf.jaxrs.utils.JAXRSUtils;
>> +import org.apache.cxf.jaxrs.utils.ReaderInputStream;
>>   import org.apache.cxf.message.Message;
>>   import org.apache.cxf.message.MessageUtils;
>> @@ -342,6 +343,10 @@ public final class ResponseImpl extends Response {
>>               entityStreamAvailable = entityStream != null;
>>           } else if (entity instanceof InputStream) {
>>               entityStream = InputStream.class.cast(entity);
>> +        } else {
>> +            Message inMessage = getResponseMessage();
>> +            Reader reader = inMessage.getContent(Reader.class);
>> +            entityStream = InputStream.class.cast(new 
>> ReaderInputStream(reader));
>>           }
>>           // we need to check for readers even if no IS is set - the 
>> readers may still do it
>> diff --git 
>> a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java 
>> b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java 
>>
>> index 0ef6565..509f76d 100644
>> --- 
>> a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java 
>>
>> +++ 
>> b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java 
>>
>> @@ -832,7 +832,11 @@ public final class JAXRSUtils {
>>                                              OperationResourceInfo ori)
>>           throws IOException, WebApplicationException {
>>           InputStream is = message.getContent(InputStream.class);
>> -
>> +        if (is == null) {
>> +            //may use the jms transport so check the Reader;
>> +            Reader reader = message.getContent(Reader.class);
>> +            is = new ReaderInputStream(reader);
>> +        }
>>           if (parameter.getType() == ParameterType.REQUEST_BODY) {
>>               if (parameterClass == AsyncResponse.class) {
>> diff --git 
>> a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java 
>> b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java 
>>
>> new file mode 100644
>> index 0000000..c7b142e
>> --- /dev/null
>> +++ 
>> b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java 
>>
>> @@ -0,0 +1,296 @@
>> +/**
>> + * 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.cxf.jaxrs.utils;
>> +
>> +import java.io.IOException;
>> +import java.io.InputStream;
>> +import java.io.Reader;
>> +import java.nio.ByteBuffer;
>> +import java.nio.CharBuffer;
>> +import java.nio.charset.Charset;
>> +import java.nio.charset.CharsetEncoder;
>> +import java.nio.charset.CoderResult;
>> +import java.nio.charset.CodingErrorAction;
>> +
>> +/**
>> + * {@link InputStream} implementation that reads a character stream 
>> from a {@link Reader}
>> + * and transforms it to a byte stream using a specified charset 
>> encoding. The stream
>> + * is transformed using a {@link CharsetEncoder} object, guaranteeing 
>> that all charset
>> + * encodings supported by the JRE are handled correctly. In 
>> particular for charsets such as
>> + * UTF-16, the implementation ensures that one and only one byte 
>> order marker
>> + * is produced.
>> + * <p>
>> + * Since in general it is not possible to predict the number of 
>> characters to be read from the
>> + * {@link Reader} to satisfy a read request on the {@link 
>> ReaderInputStream}, all reads from
>> + * the {@link Reader} are buffered. There is therefore no well 
>> defined correlation
>> + * between the current position of the {@link Reader} and that of the 
>> {@link ReaderInputStream}.
>> + * This also implies that in general there is no need to wrap the 
>> underlying {@link Reader}
>> + * in a {@link java.io.BufferedReader}.
>> + * <p>
>> + * {@link ReaderInputStream} implements the inverse transformation of 
>> {@link java.io.InputStreamReader};
>> + * in the following example, reading from <tt>in2</tt> would return 
>> the same byte
>> + * sequence as reading from <tt>in</tt> (provided that the initial 
>> byte sequence is legal
>> + * with respect to the charset encoding):
>> + * <pre>
>> + * InputStream in = ...
>> + * Charset cs = ...
>> + * InputStreamReader reader = new InputStreamReader(in, cs);
>> + * ReaderInputStream in2 = new ReaderInputStream(reader, cs);</pre>
>> + * {@link ReaderInputStream} implements the same transformation as 
>> {@link java.io.OutputStreamWriter},
>> + * except that the control flow is reversed: both classes transform a 
>> character stream
>> + * into a byte stream, but {@link java.io.OutputStreamWriter} pushes 
>> data to the underlying stream,
>> + * while {@link ReaderInputStream} pulls it from the underlying stream.
>> + * <p>
>> + * Note that while there are use cases where there is no alternative 
>> to using
>> + * this class, very often the need to use this class is an indication 
>> of a flaw
>> + * in the design of the code. This class is typically used in 
>> situations where an existing
>> + * API only accepts an {@link InputStream}, but where the most 
>> natural way to produce the data
>> + * is as a character stream, i.e. by providing a {@link Reader} 
>> instance. An example of a situation
>> + * where this problem may appear is when implementing the {@link 
>> javax.activation.DataSource}
>> + * interface from the Java Activation Framework.
>> + * <p>
>> + * Given the fact that the {@link Reader} class doesn't provide any 
>> way to predict whether the next
>> + * read operation will block or not, it is not possible to provide a 
>> meaningful
>> + * implementation of the {@link InputStream#available()} method. A 
>> call to this method
>> + * will always return 0. Also, this class doesn't support {@link 
>> InputStream#mark(int)}.
>> + * <p>
>> + * Instances of {@link ReaderInputStream} are not thread safe.
>> + *
>> + * @see org.apache.commons.io.output.WriterOutputStream
>> + *
>> + * @since 2.0
>> + */
>> +public class ReaderInputStream extends InputStream {
>> +    private static final int DEFAULT_BUFFER_SIZE = 1024;
>> +
>> +    private final Reader reader;
>> +    private final CharsetEncoder encoder;
>> +
>> +    /**
>> +     * CharBuffer used as input for the decoder. It should be reasonably
>> +     * large as we read data from the underlying Reader into this 
>> buffer.
>> +     */
>> +    private final CharBuffer encoderIn;
>> +
>> +    /**
>> +     * ByteBuffer used as output for the decoder. This buffer can be 
>> small
>> +     * as it is only used to transfer data from the decoder to the
>> +     * buffer provided by the caller.
>> +     */
>> +    private final ByteBuffer encoderOut;
>> +
>> +    private CoderResult lastCoderResult;
>> +    private boolean endOfInput;
>> +
>> +    /**
>> +     * Construct a new {@link ReaderInputStream}.
>> +     *
>> +     * @param reader the target {@link Reader}
>> +     * @param encoder the charset encoder
>> +     * @since 2.1
>> +     */
>> +    public ReaderInputStream(Reader reader, CharsetEncoder encoder) {
>> +        this(reader, encoder, DEFAULT_BUFFER_SIZE);
>> +    }
>> +
>> +    /**
>> +     * Construct a new {@link ReaderInputStream}.
>> +     *
>> +     * @param reader the target {@link Reader}
>> +     * @param encoder the charset encoder
>> +     * @param bufferSize the size of the input buffer in number of 
>> characters
>> +     * @since 2.1
>> +     */
>> +    public ReaderInputStream(Reader reader, CharsetEncoder encoder, 
>> int bufferSize) {
>> +        this.reader = reader;
>> +        this.encoder = encoder;
>> +        this.encoderIn = CharBuffer.allocate(bufferSize);
>> +        this.encoderIn.flip();
>> +        this.encoderOut = ByteBuffer.allocate(128);
>> +        this.encoderOut.flip();
>> +    }
>> +
>> +    /**
>> +     * Construct a new {@link ReaderInputStream}.
>> +     *
>> +     * @param reader the target {@link Reader}
>> +     * @param charset the charset encoding
>> +     * @param bufferSize the size of the input buffer in number of 
>> characters
>> +     */
>> +    public ReaderInputStream(Reader reader, Charset charset, int 
>> bufferSize) {
>> +        this(reader,
>> +             charset.newEncoder()
>> +                    .onMalformedInput(CodingErrorAction.REPLACE)
>> +                    .onUnmappableCharacter(CodingErrorAction.REPLACE),
>> +             bufferSize);
>> +    }
>> +
>> +    /**
>> +     * Construct a new {@link ReaderInputStream} with a default input 
>> buffer size of
>> +     * 1024 characters.
>> +     *
>> +     * @param reader the target {@link Reader}
>> +     * @param charset the charset encoding
>> +     */
>> +    public ReaderInputStream(Reader reader, Charset charset) {
>> +        this(reader, charset, DEFAULT_BUFFER_SIZE);
>> +    }
>> +
>> +    /**
>> +     * Construct a new {@link ReaderInputStream}.
>> +     *
>> +     * @param reader the target {@link Reader}
>> +     * @param charsetName the name of the charset encoding
>> +     * @param bufferSize the size of the input buffer in number of 
>> characters
>> +     */
>> +    public ReaderInputStream(Reader reader, String charsetName, int 
>> bufferSize) {
>> +        this(reader, Charset.forName(charsetName), bufferSize);
>> +    }
>> +
>> +    /**
>> +     * Construct a new {@link ReaderInputStream} with a default input 
>> buffer size of
>> +     * 1024 characters.
>> +     *
>> +     * @param reader the target {@link Reader}
>> +     * @param charsetName the name of the charset encoding
>> +     */
>> +    public ReaderInputStream(Reader reader, String charsetName) {
>> +        this(reader, charsetName, DEFAULT_BUFFER_SIZE);
>> +    }
>> +
>> +    /**
>> +     * Construct a new {@link ReaderInputStream} that uses the 
>> default character encoding
>> +     * with a default input buffer size of 1024 characters.
>> +     *
>> +     * @param reader the target {@link Reader}
>> +     */
>> +    public ReaderInputStream(Reader reader) {
>> +        this(reader, Charset.defaultCharset());
>> +    }
>> +
>> +    /**
>> +     * Fills the internal char buffer from the reader.
>> +     *
>> +     * @throws IOException
>> +     *             If an I/O error occurs
>> +     */
>> +    private void fillBuffer() throws IOException {
>> +        if (!endOfInput && (lastCoderResult == null || 
>> lastCoderResult.isUnderflow())) {
>> +            encoderIn.compact();
>> +            int position = encoderIn.position();
>> +            // We don't use Reader#read(CharBuffer) here because it 
>> is more efficient
>> +            // to write directly to the underlying char array (the 
>> default implementation
>> +            // copies data to a temporary char array).
>> +            int c = reader.read(encoderIn.array(), position, 
>> encoderIn.remaining());
>> +            if (c == -1) {
>> +                endOfInput = true;
>> +            } else {
>> +                encoderIn.position(position + c);
>> +            }
>> +            encoderIn.flip();
>> +        }
>> +        encoderOut.compact();
>> +        lastCoderResult = encoder.encode(encoderIn, encoderOut, 
>> endOfInput);
>> +        encoderOut.flip();
>> +    }
>> +
>> +    /**
>> +     * Read the specified number of bytes into an array.
>> +     *
>> +     * @param b the byte array to read into
>> +     * @param off the offset to start reading bytes into
>> +     * @param len the number of bytes to read
>> +     * @return the number of bytes read or <code>-1</code>
>> +     *         if the end of the stream has been reached
>> +     * @throws IOException if an I/O error occurs
>> +     */
>> +    @Override
>> +    public int read(byte[] b, int off, int len) throws IOException {
>> +        if (b == null) {
>> +            throw new NullPointerException("Byte array must not be 
>> null");
>> +        }
>> +        if (len < 0 || off < 0 || (off + len) > b.length) {
>> +            throw new IndexOutOfBoundsException("Array Size=" + b.length
>> +                    + ", offset=" + off + ", length=" + len);
>> +        }
>> +        int read = 0;
>> +        if (len == 0) {
>> +            return 0; // Always return 0 if len == 0
>> +        }
>> +        while (len > 0) {
>> +            if (encoderOut.hasRemaining()) {
>> +                int c = Math.min(encoderOut.remaining(), len);
>> +                encoderOut.get(b, off, c);
>> +                off += c;
>> +                len -= c;
>> +                read += c;
>> +            } else {
>> +                fillBuffer();
>> +                if (endOfInput && !encoderOut.hasRemaining()) {
>> +                    break;
>> +                }
>> +            }
>> +        }
>> +        return read == 0 && endOfInput ? -1 : read;
>> +    }
>> +
>> +    /**
>> +     * Read the specified number of bytes into an array.
>> +     *
>> +     * @param b the byte array to read into
>> +     * @return the number of bytes read or <code>-1</code>
>> +     *         if the end of the stream has been reached
>> +     * @throws IOException if an I/O error occurs
>> +     */
>> +    @Override
>> +    public int read(byte[] b) throws IOException {
>> +        return read(b, 0, b.length);
>> +    }
>> +
>> +    /**
>> +     * Read a single byte.
>> +     *
>> +     * @return either the byte read or <code>-1</code> if the end of 
>> the stream
>> +     *         has been reached
>> +     * @throws IOException if an I/O error occurs
>> +     */
>> +    @Override
>> +    public int read() throws IOException {
>> +        for (;;) {
>> +            if (encoderOut.hasRemaining()) {
>> +                return encoderOut.get() & 0xFF;
>> +            } else {
>> +                fillBuffer();
>> +                if (endOfInput && !encoderOut.hasRemaining()) {
>> +                    return -1;
>> +                }
>> +            }
>> +        }
>> +    }
>> +
>> +    /**
>> +     * Close the stream. This method will cause the underlying {@link 
>> Reader}
>> +     * to be closed.
>> +     * @throws IOException if an I/O error occurs
>> +     */
>> +    @Override
>> +    public void close() throws IOException {
>> +        reader.close();
>> +    }
>> +}
>> diff --git 
>> a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java 
>> b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java 
>>
>> index f77b5ed..b957f36 100644
>> --- 
>> a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java 
>>
>> +++ 
>> b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java 
>>
>> @@ -96,6 +96,25 @@ public class JAXRSJmsTest extends 
>> AbstractBusClientServerTestBase {
>>           assertEquals("Get a wrong response code.", 200, 
>> client.getResponse().getStatus());
>>           assertEquals("Get a wrong book id.", 123, book.getId());
>>       }
>> +
>> +    @Test
>> +    public void testGetBookFromWebClientWithTextJMSMessage() throws 
>> Exception {
>> +        // setup the the client
>> +        String endpointAddressUrlEncoded = 
>> "jms:jndi:dynamicQueues/test.jmstransport.text"
>> +             + "?replyToName=dynamicQueues/test.jmstransport.response"
>> +             + 
>> "&jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>
>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>> +             + "&messageType=text";
>> +
>> +        WebClient client = WebClient.create(endpointAddressUrlEncoded);
>> +        WebClient.getConfig(client).getInInterceptors().add(new 
>> LoggingInInterceptor());
>> +        WebClient.getConfig(client).getRequestContext()
>> +            .put(org.apache.cxf.message.Message.REQUEST_URI, 
>> "/bookstore/books/123");
>> +
>> +        Book book = client.get(Book.class);
>> +        assertEquals("Get a wrong response code.", 200, 
>> client.getResponse().getStatus());
>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>> +    }
>>       @Test
>>       public void testPutBookOneWayWithWebClient() throws Exception {
>> @@ -130,6 +149,7 @@ public class JAXRSJmsTest extends 
>> AbstractBusClientServerTestBase {
>>           }
>>       }
>> +
>>       @Test
>>       public void testGetBookFromWebClientWithPath() throws Exception {
>>           // setup the the client
>> @@ -148,6 +168,25 @@ public class JAXRSJmsTest extends 
>> AbstractBusClientServerTestBase {
>>       }
>>       @Test
>> +    public void testGetBookFromWebClientWithPathWithTextJMSMessage() 
>> throws Exception {
>> +        // setup the the client
>> +        String endpointAddressUrlEncoded = 
>> "jms:jndi:dynamicQueues/test.jmstransport.text"
>> +             + 
>> "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>
>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>> +             + "&messageType=text";
>> +
>> +        WebClient client = WebClient.create(endpointAddressUrlEncoded);
>> +        client.path("bookstore").path("books").path("123");
>> +
>> +        Book book = client.get(Book.class);
>> +        assertEquals("Get a wrong response code.", 200, 
>> client.getResponse().getStatus());
>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>> +    }
>> +
>> +
>> +    @Test
>>       public void testGetBookFromProxyClient() throws Exception {
>>           // setup the the client
>>           String endpointAddressUrlEncoded = 
>> "jms:jndi:dynamicQueues/test.jmstransport.text"
>> @@ -161,6 +200,22 @@ public class JAXRSJmsTest extends 
>> AbstractBusClientServerTestBase {
>>           assertEquals("Get a wrong response code.", 200, 
>> WebClient.client(client).getResponse().getStatus());
>>           assertEquals("Get a wrong book id.", 123, book.getId());
>>       }
>> +
>> +    @Test
>> +    public void testGetBookFromProxyClientWithTextJMSMessage() throws 
>> Exception {
>> +        // setup the the client
>> +        String endpointAddressUrlEncoded = 
>> "jms:jndi:dynamicQueues/test.jmstransport.text"
>> +             + 
>> "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>
>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>> +             + "&messageType=text";
>> +
>> +        JMSBookStore client = 
>> JAXRSClientFactory.create(endpointAddressUrlEncoded, JMSBookStore.class);
>> +        Book book = client.getBook("123");
>> +        assertEquals("Get a wrong response code.", 200, 
>> WebClient.client(client).getResponse().getStatus());
>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>> +    }
>>       @Test
>>       public void testGetBookFromSubresourceProxyClient() throws 
>> Exception {
>> @@ -177,6 +232,23 @@ public class JAXRSJmsTest extends 
>> AbstractBusClientServerTestBase {
>>           assertEquals("Get a wrong response code.", 200, 
>> WebClient.client(bookProxy).getResponse().getStatus());
>>           assertEquals("Get a wrong book id.", 123, book.getId());
>>       }
>> +
>> +    @Test
>> +    public void 
>> testGetBookFromSubresourceProxyClientWithTextJMSMessage() throws 
>> Exception {
>> +        // setup the the client
>> +        String endpointAddressUrlEncoded = 
>> "jms:jndi:dynamicQueues/test.jmstransport.text"
>> +             + 
>> "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>
>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>> +             + "&messageType=text";
>> +
>> +        JMSBookStore client = 
>> JAXRSClientFactory.create(endpointAddressUrlEncoded, JMSBookStore.class);
>> +        Book bookProxy = client.getBookSubResource("123");
>> +        Book book = bookProxy.retrieveState();
>> +        assertEquals("Get a wrong response code.", 200, 
>> WebClient.client(bookProxy).getResponse().getStatus());
>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>> +    }
>>       @Test
>>       public void testGetBookFromProxyClientWithQuery() throws 
>> Exception {
>> @@ -193,6 +265,24 @@ public class JAXRSJmsTest extends 
>> AbstractBusClientServerTestBase {
>>           assertEquals("Get a wrong book id.", 123, book.getId());
>>       }
>> +
>> +    @Test
>> +    public void 
>> testGetBookFromProxyClientWithQueryWithTextJMSMessage() throws 
>> Exception {
>> +        // setup the the client
>> +        String endpointAddressUrlEncoded = 
>> "jms:jndi:dynamicQueues/test.jmstransport.text"
>> +             + 
>> "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory" 
>>
>> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
>> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
>> +             + "&jndiConnectionFactoryName=ConnectionFactory"
>> +             + "&messageType=text";
>> +
>> +        JMSBookStore client = 
>> JAXRSClientFactory.create(endpointAddressUrlEncoded, JMSBookStore.class);
>> +        Book book = client.getBookByURLQuery(new String[] {"1", "2", 
>> "3"});
>> +        assertEquals("Get a wrong response code.", 200, 
>> WebClient.client(client).getResponse().getStatus());
>> +        assertEquals("Get a wrong book id.", 123, book.getId());
>> +    }
>> +
>> +
>>       @Test
>>       public void testGetBook() throws Exception {
>>           Context ctx = getContext();
>>
> 
> 


Re: [cxf] branch master updated: [CXF-7532]REST on JMS transport can't handle the request message with text messageType

Posted by Sergey Beryozkin <sb...@gmail.com>.
Hi Freeman

Can you move ReaderInputStream to the CXF core where CachedInputStream 
and other IO utility code is located ?

Please also update the code which gets Reader.class and check it for 
null first, and also drop a comment re the JMS transport, checking the 
reader can make sense in all the cases...

Thanks, Sergey
On 19/10/17 04:16, ffang@apache.org wrote:
> This is an automated email from the ASF dual-hosted git repository.
> 
> ffang pushed a commit to branch master
> in repository https://gitbox.apache.org/repos/asf/cxf.git
> 
> 
> The following commit(s) were added to refs/heads/master by this push:
>       new 1d66475  [CXF-7532]REST on JMS transport can't handle the request message with text messageType
> 1d66475 is described below
> 
> commit 1d66475b4dea2a0031dcb1765e8e9fd08430ec9b
> Author: Freeman Fang <fr...@gmail.com>
> AuthorDate: Thu Oct 19 11:15:50 2017 +0800
> 
>      [CXF-7532]REST on JMS transport can't handle the request message with text messageType
> ---
>   .../org/apache/cxf/jaxrs/impl/ResponseImpl.java    |   5 +
>   .../org/apache/cxf/jaxrs/utils/JAXRSUtils.java     |   6 +-
>   .../apache/cxf/jaxrs/utils/ReaderInputStream.java  | 296 +++++++++++++++++++++
>   .../apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java |  90 +++++++
>   4 files changed, 396 insertions(+), 1 deletion(-)
> 
> diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java
> index e3a3acf..82f183a 100644
> --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java
> +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseImpl.java
> @@ -58,6 +58,7 @@ import org.apache.cxf.jaxrs.provider.ProviderFactory;
>   import org.apache.cxf.jaxrs.utils.HttpUtils;
>   import org.apache.cxf.jaxrs.utils.InjectionUtils;
>   import org.apache.cxf.jaxrs.utils.JAXRSUtils;
> +import org.apache.cxf.jaxrs.utils.ReaderInputStream;
>   import org.apache.cxf.message.Message;
>   import org.apache.cxf.message.MessageUtils;
>   
> @@ -342,6 +343,10 @@ public final class ResponseImpl extends Response {
>               entityStreamAvailable = entityStream != null;
>           } else if (entity instanceof InputStream) {
>               entityStream = InputStream.class.cast(entity);
> +        } else {
> +            Message inMessage = getResponseMessage();
> +            Reader reader = inMessage.getContent(Reader.class);
> +            entityStream = InputStream.class.cast(new ReaderInputStream(reader));
>           }
>   
>           // we need to check for readers even if no IS is set - the readers may still do it
> diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
> index 0ef6565..509f76d 100644
> --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
> +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
> @@ -832,7 +832,11 @@ public final class JAXRSUtils {
>                                              OperationResourceInfo ori)
>           throws IOException, WebApplicationException {
>           InputStream is = message.getContent(InputStream.class);
> -
> +        if (is == null) {
> +            //may use the jms transport so check the Reader;
> +            Reader reader = message.getContent(Reader.class);
> +            is = new ReaderInputStream(reader);
> +        }
>           if (parameter.getType() == ParameterType.REQUEST_BODY) {
>   
>               if (parameterClass == AsyncResponse.class) {
> diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java
> new file mode 100644
> index 0000000..c7b142e
> --- /dev/null
> +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ReaderInputStream.java
> @@ -0,0 +1,296 @@
> +/**
> + * 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.cxf.jaxrs.utils;
> +
> +import java.io.IOException;
> +import java.io.InputStream;
> +import java.io.Reader;
> +import java.nio.ByteBuffer;
> +import java.nio.CharBuffer;
> +import java.nio.charset.Charset;
> +import java.nio.charset.CharsetEncoder;
> +import java.nio.charset.CoderResult;
> +import java.nio.charset.CodingErrorAction;
> +
> +/**
> + * {@link InputStream} implementation that reads a character stream from a {@link Reader}
> + * and transforms it to a byte stream using a specified charset encoding. The stream
> + * is transformed using a {@link CharsetEncoder} object, guaranteeing that all charset
> + * encodings supported by the JRE are handled correctly. In particular for charsets such as
> + * UTF-16, the implementation ensures that one and only one byte order marker
> + * is produced.
> + * <p>
> + * Since in general it is not possible to predict the number of characters to be read from the
> + * {@link Reader} to satisfy a read request on the {@link ReaderInputStream}, all reads from
> + * the {@link Reader} are buffered. There is therefore no well defined correlation
> + * between the current position of the {@link Reader} and that of the {@link ReaderInputStream}.
> + * This also implies that in general there is no need to wrap the underlying {@link Reader}
> + * in a {@link java.io.BufferedReader}.
> + * <p>
> + * {@link ReaderInputStream} implements the inverse transformation of {@link java.io.InputStreamReader};
> + * in the following example, reading from <tt>in2</tt> would return the same byte
> + * sequence as reading from <tt>in</tt> (provided that the initial byte sequence is legal
> + * with respect to the charset encoding):
> + * <pre>
> + * InputStream in = ...
> + * Charset cs = ...
> + * InputStreamReader reader = new InputStreamReader(in, cs);
> + * ReaderInputStream in2 = new ReaderInputStream(reader, cs);</pre>
> + * {@link ReaderInputStream} implements the same transformation as {@link java.io.OutputStreamWriter},
> + * except that the control flow is reversed: both classes transform a character stream
> + * into a byte stream, but {@link java.io.OutputStreamWriter} pushes data to the underlying stream,
> + * while {@link ReaderInputStream} pulls it from the underlying stream.
> + * <p>
> + * Note that while there are use cases where there is no alternative to using
> + * this class, very often the need to use this class is an indication of a flaw
> + * in the design of the code. This class is typically used in situations where an existing
> + * API only accepts an {@link InputStream}, but where the most natural way to produce the data
> + * is as a character stream, i.e. by providing a {@link Reader} instance. An example of a situation
> + * where this problem may appear is when implementing the {@link javax.activation.DataSource}
> + * interface from the Java Activation Framework.
> + * <p>
> + * Given the fact that the {@link Reader} class doesn't provide any way to predict whether the next
> + * read operation will block or not, it is not possible to provide a meaningful
> + * implementation of the {@link InputStream#available()} method. A call to this method
> + * will always return 0. Also, this class doesn't support {@link InputStream#mark(int)}.
> + * <p>
> + * Instances of {@link ReaderInputStream} are not thread safe.
> + *
> + * @see org.apache.commons.io.output.WriterOutputStream
> + *
> + * @since 2.0
> + */
> +public class ReaderInputStream extends InputStream {
> +    private static final int DEFAULT_BUFFER_SIZE = 1024;
> +
> +    private final Reader reader;
> +    private final CharsetEncoder encoder;
> +
> +    /**
> +     * CharBuffer used as input for the decoder. It should be reasonably
> +     * large as we read data from the underlying Reader into this buffer.
> +     */
> +    private final CharBuffer encoderIn;
> +
> +    /**
> +     * ByteBuffer used as output for the decoder. This buffer can be small
> +     * as it is only used to transfer data from the decoder to the
> +     * buffer provided by the caller.
> +     */
> +    private final ByteBuffer encoderOut;
> +
> +    private CoderResult lastCoderResult;
> +    private boolean endOfInput;
> +
> +    /**
> +     * Construct a new {@link ReaderInputStream}.
> +     *
> +     * @param reader the target {@link Reader}
> +     * @param encoder the charset encoder
> +     * @since 2.1
> +     */
> +    public ReaderInputStream(Reader reader, CharsetEncoder encoder) {
> +        this(reader, encoder, DEFAULT_BUFFER_SIZE);
> +    }
> +
> +    /**
> +     * Construct a new {@link ReaderInputStream}.
> +     *
> +     * @param reader the target {@link Reader}
> +     * @param encoder the charset encoder
> +     * @param bufferSize the size of the input buffer in number of characters
> +     * @since 2.1
> +     */
> +    public ReaderInputStream(Reader reader, CharsetEncoder encoder, int bufferSize) {
> +        this.reader = reader;
> +        this.encoder = encoder;
> +        this.encoderIn = CharBuffer.allocate(bufferSize);
> +        this.encoderIn.flip();
> +        this.encoderOut = ByteBuffer.allocate(128);
> +        this.encoderOut.flip();
> +    }
> +
> +    /**
> +     * Construct a new {@link ReaderInputStream}.
> +     *
> +     * @param reader the target {@link Reader}
> +     * @param charset the charset encoding
> +     * @param bufferSize the size of the input buffer in number of characters
> +     */
> +    public ReaderInputStream(Reader reader, Charset charset, int bufferSize) {
> +        this(reader,
> +             charset.newEncoder()
> +                    .onMalformedInput(CodingErrorAction.REPLACE)
> +                    .onUnmappableCharacter(CodingErrorAction.REPLACE),
> +             bufferSize);
> +    }
> +
> +    /**
> +     * Construct a new {@link ReaderInputStream} with a default input buffer size of
> +     * 1024 characters.
> +     *
> +     * @param reader the target {@link Reader}
> +     * @param charset the charset encoding
> +     */
> +    public ReaderInputStream(Reader reader, Charset charset) {
> +        this(reader, charset, DEFAULT_BUFFER_SIZE);
> +    }
> +
> +    /**
> +     * Construct a new {@link ReaderInputStream}.
> +     *
> +     * @param reader the target {@link Reader}
> +     * @param charsetName the name of the charset encoding
> +     * @param bufferSize the size of the input buffer in number of characters
> +     */
> +    public ReaderInputStream(Reader reader, String charsetName, int bufferSize) {
> +        this(reader, Charset.forName(charsetName), bufferSize);
> +    }
> +
> +    /**
> +     * Construct a new {@link ReaderInputStream} with a default input buffer size of
> +     * 1024 characters.
> +     *
> +     * @param reader the target {@link Reader}
> +     * @param charsetName the name of the charset encoding
> +     */
> +    public ReaderInputStream(Reader reader, String charsetName) {
> +        this(reader, charsetName, DEFAULT_BUFFER_SIZE);
> +    }
> +
> +    /**
> +     * Construct a new {@link ReaderInputStream} that uses the default character encoding
> +     * with a default input buffer size of 1024 characters.
> +     *
> +     * @param reader the target {@link Reader}
> +     */
> +    public ReaderInputStream(Reader reader) {
> +        this(reader, Charset.defaultCharset());
> +    }
> +
> +    /**
> +     * Fills the internal char buffer from the reader.
> +     *
> +     * @throws IOException
> +     *             If an I/O error occurs
> +     */
> +    private void fillBuffer() throws IOException {
> +        if (!endOfInput && (lastCoderResult == null || lastCoderResult.isUnderflow())) {
> +            encoderIn.compact();
> +            int position = encoderIn.position();
> +            // We don't use Reader#read(CharBuffer) here because it is more efficient
> +            // to write directly to the underlying char array (the default implementation
> +            // copies data to a temporary char array).
> +            int c = reader.read(encoderIn.array(), position, encoderIn.remaining());
> +            if (c == -1) {
> +                endOfInput = true;
> +            } else {
> +                encoderIn.position(position + c);
> +            }
> +            encoderIn.flip();
> +        }
> +        encoderOut.compact();
> +        lastCoderResult = encoder.encode(encoderIn, encoderOut, endOfInput);
> +        encoderOut.flip();
> +    }
> +
> +    /**
> +     * Read the specified number of bytes into an array.
> +     *
> +     * @param b the byte array to read into
> +     * @param off the offset to start reading bytes into
> +     * @param len the number of bytes to read
> +     * @return the number of bytes read or <code>-1</code>
> +     *         if the end of the stream has been reached
> +     * @throws IOException if an I/O error occurs
> +     */
> +    @Override
> +    public int read(byte[] b, int off, int len) throws IOException {
> +        if (b == null) {
> +            throw new NullPointerException("Byte array must not be null");
> +        }
> +        if (len < 0 || off < 0 || (off + len) > b.length) {
> +            throw new IndexOutOfBoundsException("Array Size=" + b.length
> +                    + ", offset=" + off + ", length=" + len);
> +        }
> +        int read = 0;
> +        if (len == 0) {
> +            return 0; // Always return 0 if len == 0
> +        }
> +        while (len > 0) {
> +            if (encoderOut.hasRemaining()) {
> +                int c = Math.min(encoderOut.remaining(), len);
> +                encoderOut.get(b, off, c);
> +                off += c;
> +                len -= c;
> +                read += c;
> +            } else {
> +                fillBuffer();
> +                if (endOfInput && !encoderOut.hasRemaining()) {
> +                    break;
> +                }
> +            }
> +        }
> +        return read == 0 && endOfInput ? -1 : read;
> +    }
> +
> +    /**
> +     * Read the specified number of bytes into an array.
> +     *
> +     * @param b the byte array to read into
> +     * @return the number of bytes read or <code>-1</code>
> +     *         if the end of the stream has been reached
> +     * @throws IOException if an I/O error occurs
> +     */
> +    @Override
> +    public int read(byte[] b) throws IOException {
> +        return read(b, 0, b.length);
> +    }
> +
> +    /**
> +     * Read a single byte.
> +     *
> +     * @return either the byte read or <code>-1</code> if the end of the stream
> +     *         has been reached
> +     * @throws IOException if an I/O error occurs
> +     */
> +    @Override
> +    public int read() throws IOException {
> +        for (;;) {
> +            if (encoderOut.hasRemaining()) {
> +                return encoderOut.get() & 0xFF;
> +            } else {
> +                fillBuffer();
> +                if (endOfInput && !encoderOut.hasRemaining()) {
> +                    return -1;
> +                }
> +            }
> +        }
> +    }
> +
> +    /**
> +     * Close the stream. This method will cause the underlying {@link Reader}
> +     * to be closed.
> +     * @throws IOException if an I/O error occurs
> +     */
> +    @Override
> +    public void close() throws IOException {
> +        reader.close();
> +    }
> +}
> diff --git a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java
> index f77b5ed..b957f36 100644
> --- a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java
> +++ b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/jms/JAXRSJmsTest.java
> @@ -96,6 +96,25 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>           assertEquals("Get a wrong response code.", 200, client.getResponse().getStatus());
>           assertEquals("Get a wrong book id.", 123, book.getId());
>       }
> +
> +    @Test
> +    public void testGetBookFromWebClientWithTextJMSMessage() throws Exception {
> +        // setup the the client
> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
> +             + "?replyToName=dynamicQueues/test.jmstransport.response"
> +             + "&jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory"
> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
> +             + "&messageType=text";
> +
> +        WebClient client = WebClient.create(endpointAddressUrlEncoded);
> +        WebClient.getConfig(client).getInInterceptors().add(new LoggingInInterceptor());
> +        WebClient.getConfig(client).getRequestContext()
> +            .put(org.apache.cxf.message.Message.REQUEST_URI, "/bookstore/books/123");
> +
> +        Book book = client.get(Book.class);
> +        assertEquals("Get a wrong response code.", 200, client.getResponse().getStatus());
> +        assertEquals("Get a wrong book id.", 123, book.getId());
> +    }
>   
>       @Test
>       public void testPutBookOneWayWithWebClient() throws Exception {
> @@ -130,6 +149,7 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>           }
>       }
>   
> +
>       @Test
>       public void testGetBookFromWebClientWithPath() throws Exception {
>           // setup the the client
> @@ -148,6 +168,25 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>       }
>   
>       @Test
> +    public void testGetBookFromWebClientWithPathWithTextJMSMessage() throws Exception {
> +        // setup the the client
> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
> +             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory"
> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
> +             + "&jndiConnectionFactoryName=ConnectionFactory"
> +             + "&messageType=text";
> +
> +        WebClient client = WebClient.create(endpointAddressUrlEncoded);
> +        client.path("bookstore").path("books").path("123");
> +
> +        Book book = client.get(Book.class);
> +        assertEquals("Get a wrong response code.", 200, client.getResponse().getStatus());
> +        assertEquals("Get a wrong book id.", 123, book.getId());
> +    }
> +
> +
> +    @Test
>       public void testGetBookFromProxyClient() throws Exception {
>           // setup the the client
>           String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
> @@ -161,6 +200,22 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>           assertEquals("Get a wrong response code.", 200, WebClient.client(client).getResponse().getStatus());
>           assertEquals("Get a wrong book id.", 123, book.getId());
>       }
> +
> +    @Test
> +    public void testGetBookFromProxyClientWithTextJMSMessage() throws Exception {
> +        // setup the the client
> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
> +             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory"
> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
> +             + "&jndiConnectionFactoryName=ConnectionFactory"
> +             + "&messageType=text";
> +
> +        JMSBookStore client = JAXRSClientFactory.create(endpointAddressUrlEncoded, JMSBookStore.class);
> +        Book book = client.getBook("123");
> +        assertEquals("Get a wrong response code.", 200, WebClient.client(client).getResponse().getStatus());
> +        assertEquals("Get a wrong book id.", 123, book.getId());
> +    }
>   
>       @Test
>       public void testGetBookFromSubresourceProxyClient() throws Exception {
> @@ -177,6 +232,23 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>           assertEquals("Get a wrong response code.", 200, WebClient.client(bookProxy).getResponse().getStatus());
>           assertEquals("Get a wrong book id.", 123, book.getId());
>       }
> +
> +    @Test
> +    public void testGetBookFromSubresourceProxyClientWithTextJMSMessage() throws Exception {
> +        // setup the the client
> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
> +             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory"
> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
> +             + "&jndiConnectionFactoryName=ConnectionFactory"
> +             + "&messageType=text";
> +
> +        JMSBookStore client = JAXRSClientFactory.create(endpointAddressUrlEncoded, JMSBookStore.class);
> +        Book bookProxy = client.getBookSubResource("123");
> +        Book book = bookProxy.retrieveState();
> +        assertEquals("Get a wrong response code.", 200, WebClient.client(bookProxy).getResponse().getStatus());
> +        assertEquals("Get a wrong book id.", 123, book.getId());
> +    }
>   
>       @Test
>       public void testGetBookFromProxyClientWithQuery() throws Exception {
> @@ -193,6 +265,24 @@ public class JAXRSJmsTest extends AbstractBusClientServerTestBase {
>           assertEquals("Get a wrong book id.", 123, book.getId());
>       }
>   
> +
> +    @Test
> +    public void testGetBookFromProxyClientWithQueryWithTextJMSMessage() throws Exception {
> +        // setup the the client
> +        String endpointAddressUrlEncoded = "jms:jndi:dynamicQueues/test.jmstransport.text"
> +             + "?jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory"
> +             + "&replyToName=dynamicQueues/test.jmstransport.response"
> +             + "&jndiURL=tcp://localhost:" + JMS_PORT
> +             + "&jndiConnectionFactoryName=ConnectionFactory"
> +             + "&messageType=text";
> +
> +        JMSBookStore client = JAXRSClientFactory.create(endpointAddressUrlEncoded, JMSBookStore.class);
> +        Book book = client.getBookByURLQuery(new String[] {"1", "2", "3"});
> +        assertEquals("Get a wrong response code.", 200, WebClient.client(client).getResponse().getStatus());
> +        assertEquals("Get a wrong book id.", 123, book.getId());
> +    }
> +
> +
>       @Test
>       public void testGetBook() throws Exception {
>           Context ctx = getContext();
> 


-- 
Sergey Beryozkin

Talend Community Coders
http://coders.talend.com/