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

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


The following commit(s) were added to refs/heads/master by this push:
     new 9135b45  org.apache.juneau.http tests.
9135b45 is described below

commit 9135b452833e6d7a2dd7254624231ff649f7d675
Author: JamesBognar <[email protected]>
AuthorDate: Wed Jul 8 09:49:41 2020 -0400

    org.apache.juneau.http tests.
---
 .../org/apache/juneau/http/ReaderResource.java     |  18 --
 .../apache/juneau/internal/ReaderInputStream.java  | 280 +++++++++++++++++
 .../apache/juneau/internal/WriterOutputStream.java | 340 +++++++++++++++++++++
 .../org/apache/juneau/rest/client2/RestClient.java |  15 +-
 .../apache/juneau/rest/client2/RestRequest.java    |  12 +-
 .../apache/juneau/rest/mock2/MockRestClient.java   |   9 +-
 .../apache/juneau/rest/BasicRestCallHandler.java   |  12 +-
 .../apache/juneau/rest/BasicRestCallLogger.java    |   4 +
 .../java/org/apache/juneau/rest/RestContext.java   |  58 ++--
 .../java/org/apache/juneau/rest/RestRequest.java   |  30 +-
 .../java/org/apache/juneau/rest/StaticFile.java    |  85 ++++++
 .../java/org/apache/juneau/rest/StaticFiles.java   |  18 +-
 .../java/org/apache/juneau/rest/StatusStats.java   |   6 +-
 .../juneau/rest/reshandlers/DefaultHandler.java    |  24 +-
 .../java/org/apache/juneau/rest/vars/FileVar.java  |  15 +-
 15 files changed, 835 insertions(+), 91 deletions(-)

diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/ReaderResource.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/ReaderResource.java
index 9b16e31..3aef2bb 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/ReaderResource.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/ReaderResource.java
@@ -140,24 +140,6 @@ public class ReaderResource implements Writable {
        }
 
        /**
-        * Same as {@link #toString()} but strips comments from the text before 
returning it.
-        *
-        * <p>
-        * Supports stripping comments from the following media types: HTML, 
XHTML, XML, JSON, Javascript, CSS.
-        *
-        * @return The resource contents stripped of any comments.
-        */
-       public String toCommentStrippedString() {
-               String s = toString();
-               String subType = mediaType.getSubType();
-               if ("html".equals(subType) || "xhtml".equals(subType) || 
"xml".equals(subType))
-                       s = s.replaceAll("(?s)<!--(.*?)-->\\s*", "");
-               else if ("json".equals(subType) || "javascript".equals(subType) 
|| "css".equals(subType))
-                       s = s.replaceAll("(?s)\\/\\*(.*?)\\*\\/\\s*", "");
-               return s;
-       }
-
-       /**
         * Returns the contents of this resource.
         *
         * @return The contents of this resource.
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ReaderInputStream.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ReaderInputStream.java
new file mode 100644
index 0000000..8f597b6
--- /dev/null
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ReaderInputStream.java
@@ -0,0 +1,280 @@
+/*
+ * 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.juneau.internal;
+
+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;
+import java.util.Objects;
+
+/**
+ * {@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 {@code in2} would return the same 
byte
+ * sequence as reading from {@code in} (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 {@code 
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.
+ *
+ * @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(final Reader reader, final 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(final Reader reader, final CharsetEncoder 
encoder, final 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(final Reader reader, final Charset charset, final 
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
+     * {@value 1024} characters.
+     *
+     * @param reader the target {@link Reader}
+     * @param charset the charset encoding
+     */
+    public ReaderInputStream(final Reader reader, final 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(final Reader reader, final String charsetName, 
final int bufferSize) {
+        this(reader, Charset.forName(charsetName), bufferSize);
+    }
+
+    /**
+     * Construct a new {@link ReaderInputStream} with a default input buffer 
size of
+     * {@value 1024} characters.
+     *
+     * @param reader the target {@link Reader}
+     * @param charsetName the name of the charset encoding
+     */
+    public ReaderInputStream(final Reader reader, final String charsetName) {
+        this(reader, charsetName, DEFAULT_BUFFER_SIZE);
+    }
+
+    /**
+     * 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();
+            final 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).
+            final 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 array 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(final byte[] array, int off, int len) throws IOException {
+        Objects.requireNonNull(array, "array");
+        if (len < 0 || off < 0 || (off + len) > array.length) {
+            throw new IndexOutOfBoundsException("Array Size=" + array.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()) {
+                final int c = Math.min(encoderOut.remaining(), len);
+                encoderOut.get(array, 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(final 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;
+            }
+            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/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/WriterOutputStream.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/WriterOutputStream.java
new file mode 100644
index 0000000..e14a4f6
--- /dev/null
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/WriterOutputStream.java
@@ -0,0 +1,340 @@
+/*
+ * 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.juneau.internal;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
+
+/**
+ * {@link OutputStream} implementation that transforms a byte stream to a
+ * character stream using a specified charset encoding and writes the resulting
+ * stream to a {@link Writer}. The stream is transformed using a
+ * {@link CharsetDecoder} object, guaranteeing that all charset
+ * encodings supported by the JRE are handled correctly.
+ * <p>
+ * The output of the {@link CharsetDecoder} is buffered using a fixed size 
buffer.
+ * This implies that the data is written to the underlying {@link Writer} in 
chunks
+ * that are no larger than the size of this buffer. By default, the buffer is
+ * flushed only when it overflows or when {@link #flush()} or {@link #close()}
+ * is called. In general there is therefore no need to wrap the underlying 
{@link Writer}
+ * in a {@link java.io.BufferedWriter}. {@link WriterOutputStream} can also
+ * be instructed to flush the buffer after each write operation. In this case, 
all
+ * available data is written immediately to the underlying {@link Writer}, 
implying that
+ * the current position of the {@link Writer} is correlated to the current 
position
+ * of the {@link WriterOutputStream}.
+ * <p>
+ * {@link WriterOutputStream} implements the inverse transformation of {@link 
java.io.OutputStreamWriter};
+ * in the following example, writing to {@code out2} would have the same 
result as writing to
+ * {@code out} directly (provided that the byte sequence is legal with respect 
to the
+ * charset encoding):
+ * <pre>
+ * OutputStream out = ...
+ * Charset cs = ...
+ * OutputStreamWriter writer = new OutputStreamWriter(out, cs);
+ * WriterOutputStream out2 = new WriterOutputStream(writer, cs);</pre>
+ * {@link WriterOutputStream} implements the same transformation as {@link 
java.io.InputStreamReader},
+ * except that the control flow is reversed: both classes transform a byte 
stream
+ * into a character stream, but {@link java.io.InputStreamReader} pulls data 
from the underlying stream,
+ * while {@link WriterOutputStream} pushes it to 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 OutputStream} object, but where the stream is 
known to represent
+ * character data that must be decoded for further use.
+ * <p>
+ * Instances of {@link WriterOutputStream} are not thread safe.
+ *
+ * @since 2.0
+ */
+public class WriterOutputStream extends OutputStream {
+    private static final int BUFFER_SIZE = 1024;
+
+    private final Writer writer;
+    private final CharsetDecoder decoder;
+    private final boolean writeImmediately;
+
+    /**
+     * ByteBuffer used as input for the decoder. This buffer can be small
+     * as it is used only to transfer the received data to the
+     * decoder.
+     */
+    private final ByteBuffer decoderIn = ByteBuffer.allocate(128);
+
+    /**
+     * CharBuffer used as output for the decoder. It should be
+     * somewhat larger as we write from this buffer to the
+     * underlying Writer.
+     */
+    private final CharBuffer decoderOut;
+
+    /**
+     * Constructs a new {@link WriterOutputStream} with a default output 
buffer size of 1024
+     * characters. The output buffer will only be flushed when it overflows or 
when {@link #flush()} or {@link #close()}
+     * is called.
+     *
+     * @param writer the target {@link Writer}
+     * @param decoder the charset decoder
+     * @since 2.1
+     */
+    public WriterOutputStream(final Writer writer, final CharsetDecoder 
decoder) {
+        this(writer, decoder, BUFFER_SIZE, false);
+    }
+
+    /**
+     * Constructs a new {@link WriterOutputStream}.
+     *
+     * @param writer the target {@link Writer}
+     * @param decoder the charset decoder
+     * @param bufferSize the size of the output buffer in number of characters
+     * @param writeImmediately If {@code true} the output buffer will be 
flushed after each
+     *                         write operation, i.e. all available data will 
be written to the
+     *                         underlying {@link Writer} immediately. If 
{@code false}, the
+     *                         output buffer will only be flushed when it 
overflows or when
+     *                         {@link #flush()} or {@link #close()} is called.
+     * @since 2.1
+     */
+    public WriterOutputStream(final Writer writer, final CharsetDecoder 
decoder, final int bufferSize,
+                              final boolean writeImmediately) {
+        checkIbmJdkWithBrokenUTF16( decoder.charset());
+        this.writer = writer;
+        this.decoder = decoder;
+        this.writeImmediately = writeImmediately;
+        decoderOut = CharBuffer.allocate(bufferSize);
+    }
+
+    /**
+     * Constructs a new {@link WriterOutputStream}.
+     *
+     * @param writer the target {@link Writer}
+     * @param charset the charset encoding
+     * @param bufferSize the size of the output buffer in number of characters
+     * @param writeImmediately If {@code true} the output buffer will be 
flushed after each
+     *                         write operation, i.e. all available data will 
be written to the
+     *                         underlying {@link Writer} immediately. If 
{@code false}, the
+     *                         output buffer will only be flushed when it 
overflows or when
+     *                         {@link #flush()} or {@link #close()} is called.
+     */
+    public WriterOutputStream(final Writer writer, final Charset charset, 
final int bufferSize,
+                              final boolean writeImmediately) {
+        this(writer,
+             charset.newDecoder()
+                    .onMalformedInput(CodingErrorAction.REPLACE)
+                    .onUnmappableCharacter(CodingErrorAction.REPLACE)
+                    .replaceWith("?"),
+             bufferSize,
+             writeImmediately);
+    }
+
+    /**
+     * Constructs a new {@link WriterOutputStream} with a default output 
buffer size of 1024
+     * characters. The output buffer will only be flushed when it overflows or 
when {@link #flush()} or {@link #close()}
+     * is called.
+     *
+     * @param writer the target {@link Writer}
+     * @param charset the charset encoding
+     */
+    public WriterOutputStream(final Writer writer, final Charset charset) {
+        this(writer, charset, BUFFER_SIZE, false);
+    }
+
+    /**
+     * Constructs a new {@link WriterOutputStream}.
+     *
+     * @param writer the target {@link Writer}
+     * @param charsetName the name of the charset encoding
+     * @param bufferSize the size of the output buffer in number of characters
+     * @param writeImmediately If {@code true} the output buffer will be 
flushed after each
+     *                         write operation, i.e. all available data will 
be written to the
+     *                         underlying {@link Writer} immediately. If 
{@code false}, the
+     *                         output buffer will only be flushed when it 
overflows or when
+     *                         {@link #flush()} or {@link #close()} is called.
+     */
+    public WriterOutputStream(final Writer writer, final String charsetName, 
final int bufferSize,
+                              final boolean writeImmediately) {
+        this(writer, Charset.forName(charsetName), bufferSize, 
writeImmediately);
+    }
+
+    /**
+     * Constructs a new {@link WriterOutputStream} with a default output 
buffer size of 1024
+     * characters. The output buffer will only be flushed when it overflows or 
when {@link #flush()} or {@link #close()}
+     * is called.
+     *
+     * @param writer the target {@link Writer}
+     * @param charsetName the name of the charset encoding
+     */
+    public WriterOutputStream(final Writer writer, final String charsetName) {
+        this(writer, charsetName, BUFFER_SIZE, false);
+    }
+
+    /**
+     * Write bytes from the specified byte array to the stream.
+     *
+     * @param b the byte array containing the bytes to write
+     * @param off the start offset in the byte array
+     * @param len the number of bytes to write
+     * @throws IOException if an I/O error occurs
+     */
+    @Override
+    public void write(final byte[] b, int off, int len) throws IOException {
+        while (len > 0) {
+            final int c = Math.min(len, decoderIn.remaining());
+            decoderIn.put(b, off, c);
+            processInput(false);
+            len -= c;
+            off += c;
+        }
+        if (writeImmediately) {
+            flushOutput();
+        }
+    }
+
+    /**
+     * Write bytes from the specified byte array to the stream.
+     *
+     * @param b the byte array containing the bytes to write
+     * @throws IOException if an I/O error occurs
+     */
+    @Override
+    public void write(final byte[] b) throws IOException {
+        write(b, 0, b.length);
+    }
+
+    /**
+     * Write a single byte to the stream.
+     *
+     * @param b the byte to write
+     * @throws IOException if an I/O error occurs
+     */
+    @Override
+    public void write(final int b) throws IOException {
+        write(new byte[] { (byte)b }, 0, 1);
+    }
+
+    /**
+     * Flush the stream. Any remaining content accumulated in the output buffer
+     * will be written to the underlying {@link Writer}. After that
+     * {@link Writer#flush()} will be called.
+     * @throws IOException if an I/O error occurs
+     */
+    @Override
+    public void flush() throws IOException {
+        flushOutput();
+        writer.flush();
+    }
+
+    /**
+     * Close the stream. Any remaining content accumulated in the output buffer
+     * will be written to the underlying {@link Writer}. After that
+     * {@link Writer#close()} will be called.
+     * @throws IOException if an I/O error occurs
+     */
+    @Override
+    public void close() throws IOException {
+        processInput(true);
+        flushOutput();
+        writer.close();
+    }
+
+    /**
+     * Decode the contents of the input ByteBuffer into a CharBuffer.
+     *
+     * @param endOfInput indicates end of input
+     * @throws IOException if an I/O error occurs
+     */
+    private void processInput(final boolean endOfInput) throws IOException {
+        // Prepare decoderIn for reading
+        decoderIn.flip();
+        CoderResult coderResult;
+        while (true) {
+            coderResult = decoder.decode(decoderIn, decoderOut, endOfInput);
+            if (coderResult.isOverflow()) {
+                flushOutput();
+            } else if (coderResult.isUnderflow()) {
+                break;
+            } else {
+                // The decoder is configured to replace malformed input and 
unmappable characters,
+                // so we should not get here.
+                throw new IOException("Unexpected coder result");
+            }
+        }
+        // Discard the bytes that have been read
+        decoderIn.compact();
+    }
+
+    /**
+     * Flush the output.
+     *
+     * @throws IOException if an I/O error occurs
+     */
+    private void flushOutput() throws IOException {
+        if (decoderOut.position() > 0) {
+            writer.write(decoderOut.array(), 0, decoderOut.position());
+            decoderOut.rewind();
+        }
+    }
+
+    /**
+     * Check if the JDK in use properly supports the given charset.
+     *
+     * @param charset the charset to check the support for
+     */
+    private static void checkIbmJdkWithBrokenUTF16(final Charset charset){
+        if (!"UTF-16".equals(charset.name())) {
+            return;
+        }
+        final String TEST_STRING_2 = "v\u00e9s";
+        final byte[] bytes = TEST_STRING_2.getBytes(charset);
+
+        final CharsetDecoder charsetDecoder2 = charset.newDecoder();
+        final ByteBuffer bb2 = ByteBuffer.allocate(16);
+        final CharBuffer cb2 = CharBuffer.allocate(TEST_STRING_2.length());
+        final int len = bytes.length;
+        for (int i = 0; i < len; i++) {
+            bb2.put(bytes[i]);
+            bb2.flip();
+            try {
+                charsetDecoder2.decode(bb2, cb2, i == (len - 1));
+            } catch ( final IllegalArgumentException e){
+                throw new UnsupportedOperationException("UTF-16 requested when 
runninng on an IBM JDK with broken UTF-16 support. " +
+                        "Please find a JDK that supports UTF-16 if you intend 
to use UF-16 with WriterOutputStream");
+            }
+            bb2.compact();
+        }
+        cb2.rewind();
+        if (!TEST_STRING_2.equals(cb2.toString())){
+            throw new UnsupportedOperationException("UTF-16 requested when 
runninng on an IBM JDK with broken UTF-16 support. " +
+                    "Please find a JDK that supports UTF-16 if you intend to 
use UF-16 with WriterOutputStream");
+        }
+
+    }
+}
+
+
+
+
+
+
+
+
diff --git 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestClient.java
 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestClient.java
index 4ba6eff..a97f0eb 100644
--- 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestClient.java
+++ 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestClient.java
@@ -486,9 +486,9 @@ import org.apache.juneau.utils.*;
  *             <li class='jc'>
  *                     {@link InputStream} - Raw contents of {@code 
InputStream} will be serialized to remote resource.
  *             <li class='jc'>
- *                     {@link ReaderResource}/{@link ReaderResourceBuilder} - 
Raw contents of {@code Reader} will be serialized to remote resource.  
Additional headers and media type will be set on request.
+ *                     {@link ReaderResource} - Raw contents of {@code Reader} 
will be serialized to remote resource.  Additional headers and media type will 
be set on request.
  *             <li class='jc'>
- *                     {@link StreamResource}/{@link StreamResourceBuilder} - 
Raw contents of {@code InputStream} will be serialized to remote resource.  
Additional headers and media type will be set on request.
+ *                     {@link StreamResource} - Raw contents of {@code 
InputStream} will be serialized to remote resource.  Additional headers and 
media type will be set on request.
  *             <li class='jc'>
  *                     {@link HttpEntity} - Bypass Juneau serialization and 
pass HttpEntity directly to HttpClient.
  *             <li class='jc'>
@@ -2504,7 +2504,8 @@ public class RestClient extends BeanContext implements 
HttpClient, Closeable, Re
         *              <li>{@link NameValuePair} array - URL-encoded as name 
value pairs.
         *              <li>{@link NameValuePairSupplier} - URL-encoded as name 
value pairs.
         *              <li>{@link Reader}/{@link InputStream}- Streamed 
directly and <l>Content-Type</l> set to 
<js>"application/x-www-form-urlencoded"</js>
-        *              <li>{@link ReaderResource}/{@link 
ReaderResourceBuilder}/{@link StreamResource}/{@link 
StreamResourceBuilder}/{@link HttpEntity}- Streamed directly and 
<l>Content-Type</l> set to <js>"application/x-www-form-urlencoded"</js> if not 
already specified on the entity.
+        *              <li>{@link ReaderResource}/{@link StreamResource}- 
Streamed directly and <l>Content-Type</l> set to 
<js>"application/x-www-form-urlencoded"</js> if not already specified on the 
entity.
+        *              <li>{@link HttpEntity}- Streamed directly and 
<l>Content-Type</l> set to <js>"application/x-www-form-urlencoded"</js> if not 
already specified on the entity.
         *              <li>{@link Object} - Converted to a {@link 
SerializedHttpEntity} using {@link UrlEncodingSerializer} to serialize.
         *              <li>{@link Supplier} - A supplier of anything on this 
list.
         *      </ul>
@@ -2625,9 +2626,9 @@ public class RestClient extends BeanContext implements 
HttpClient, Closeable, Re
         *              <li>
         *                      {@link InputStream} - Raw contents of {@code 
InputStream} will be serialized to remote resource.
         *              <li>
-        *                      {@link ReaderResource}/{@link 
ReaderResourceBuilder} - Raw contents of {@code Reader} will be serialized to 
remote resource.  Additional headers and media type will be set on request.
+        *                      {@link ReaderResource} - Raw contents of {@code 
Reader} will be serialized to remote resource.  Additional headers and media 
type will be set on request.
         *              <li>
-        *                      {@link StreamResource}/{@link 
StreamResourceBuilder} - Raw contents of {@code InputStream} will be serialized 
to remote resource.  Additional headers and media type will be set on request.
+        *                      {@link StreamResource} - Raw contents of {@code 
InputStream} will be serialized to remote resource.  Additional headers and 
media type will be set on request.
         *              <li>
         *                      {@link Object} - POJO to be converted to text 
using the {@link Serializer} registered with the
         *                      {@link RestClient}.
@@ -2811,9 +2812,9 @@ public class RestClient extends BeanContext implements 
HttpClient, Closeable, Re
         *              <li>
         *                      {@link InputStream} - Raw contents of {@code 
InputStream} will be serialized to remote resource.
         *              <li>
-        *                      {@link ReaderResource}/{@link 
ReaderResourceBuilder} - Raw contents of {@code Reader} will be serialized to 
remote resource.  Additional headers and media type will be set on request.
+        *                      {@link ReaderResource} - Raw contents of {@code 
Reader} will be serialized to remote resource.  Additional headers and media 
type will be set on request.
         *              <li>
-        *                      {@link StreamResource}/{@link 
StreamResourceBuilder} - Raw contents of {@code InputStream} will be serialized 
to remote resource.  Additional headers and media type will be set on request.
+        *                      {@link StreamResource} - Raw contents of {@code 
InputStream} will be serialized to remote resource.  Additional headers and 
media type will be set on request.
         *              <li>
         *                      {@link Object} - POJO to be converted to text 
using the {@link Serializer} registered with the
         *                      {@link RestClient}.
diff --git 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestRequest.java
 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestRequest.java
index 8efc233..58a5ab6 100644
--- 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestRequest.java
+++ 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestRequest.java
@@ -1761,9 +1761,9 @@ public class RestRequest extends BeanSession implements 
HttpUriRequest, Configur
         *              <li>
         *                      {@link InputStream} - Raw contents of {@code 
InputStream} will be serialized to remote resource.
         *              <li>
-        *                      {@link ReaderResource}/{@link 
ReaderResourceBuilder} - Raw contents of {@code Reader} will be serialized to 
remote resource.  Additional headers and media type will be set on request.
+        *                      {@link ReaderResource} - Raw contents of {@code 
Reader} will be serialized to remote resource.  Additional headers and media 
type will be set on request.
         *              <li>
-        *                      {@link StreamResource}/{@link 
StreamResourceBuilder} - Raw contents of {@code InputStream} will be serialized 
to remote resource.  Additional headers and media type will be set on request.
+        *                      {@link StreamResource} - Raw contents of {@code 
InputStream} will be serialized to remote resource.  Additional headers and 
media type will be set on request.
         *              <li>
         *                      {@link Object} - POJO to be converted to text 
using the {@link Serializer} registered with the
         *                      {@link RestClient}.
@@ -1853,9 +1853,9 @@ public class RestRequest extends BeanSession implements 
HttpUriRequest, Configur
         *              <li>
         *                      {@link InputStream} - Raw contents of {@code 
InputStream} will be serialized to remote resource.
         *              <li>
-        *                      {@link ReaderResource}/{@link 
ReaderResourceBuilder} - Raw contents of {@code Reader} will be serialized to 
remote resource.  Additional headers and media type will be set on request.
+        *                      {@link ReaderResource} - Raw contents of {@code 
Reader} will be serialized to remote resource.  Additional headers and media 
type will be set on request.
         *              <li>
-        *                      {@link StreamResource}/{@link 
StreamResourceBuilder} - Raw contents of {@code InputStream} will be serialized 
to remote resource.  Additional headers and media type will be set on request.
+        *                      {@link StreamResource} - Raw contents of {@code 
InputStream} will be serialized to remote resource.  Additional headers and 
media type will be set on request.
         *              <li>
         *                      {@link Object} - POJO to be converted to text 
using the {@link Serializer} registered with the
         *                      {@link RestClient}.
@@ -1923,9 +1923,9 @@ public class RestRequest extends BeanSession implements 
HttpUriRequest, Configur
         *              <li>
         *                      {@link InputStream} - Raw contents of {@code 
InputStream} will be serialized to remote resource.
         *              <li>
-        *                      {@link ReaderResource}/{@link 
ReaderResourceBuilder} - Raw contents of {@code Reader} will be serialized to 
remote resource.  Additional headers and media type will be set on request.
+        *                      {@link ReaderResource} - Raw contents of {@code 
Reader} will be serialized to remote resource.  Additional headers and media 
type will be set on request.
         *              <li>
-        *                      {@link StreamResource}/{@link 
StreamResourceBuilder} - Raw contents of {@code InputStream} will be serialized 
to remote resource.  Additional headers and media type will be set on request.
+        *                      {@link StreamResource} - Raw contents of {@code 
InputStream} will be serialized to remote resource.  Additional headers and 
media type will be set on request.
         *              <li>
         *                      {@link Object} - POJO to be converted to text 
using the {@link Serializer} registered with the
         *                      {@link RestClient}.
diff --git 
a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockRestClient.java
 
b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockRestClient.java
index 299c547..2f5fb3b 100644
--- 
a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockRestClient.java
+++ 
b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockRestClient.java
@@ -19,6 +19,7 @@ import java.io.*;
 import java.net.*;
 import java.util.*;
 import java.util.concurrent.*;
+import java.util.zip.*;
 
 import javax.servlet.http.*;
 
@@ -769,9 +770,11 @@ public class MockRestClient extends RestClient implements 
HttpClientConnection {
 
        @Override /* HttpClientConnection */
        public void receiveResponseEntity(HttpResponse response) throws 
HttpException, IOException {
-               BasicHttpEntity e = new BasicHttpEntity();
-               e.setContent(new ByteArrayInputStream(sres.get().getBody()));
-               response.setEntity(e);
+               InputStream is = new ByteArrayInputStream(sres.get().getBody());
+               Header contentEncoding = 
response.getLastHeader("Content-Encoding");
+               if (contentEncoding != null && 
contentEncoding.getValue().equalsIgnoreCase("gzip"))
+                       is = new GZIPInputStream(is);
+               response.setEntity(new InputStreamEntity(is));
        }
 
        @Override /* HttpClientConnection */
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallHandler.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallHandler.java
index 262e868..6888649 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallHandler.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallHandler.java
@@ -25,9 +25,7 @@ import javax.servlet.*;
 import javax.servlet.http.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.http.StreamResource;
 import org.apache.juneau.http.annotation.*;
-import org.apache.juneau.rest.RestContext.*;
 import org.apache.juneau.http.exception.*;
 import org.apache.juneau.parser.*;
 import org.apache.juneau.reflect.*;
@@ -147,13 +145,15 @@ public class BasicRestCallHandler implements 
RestCallHandler {
                        context.setRequest(call.getRestRequest());
                        context.setResponse(call.getRestResponse());
 
-                       StreamResource r = null;
+                       StaticFile r = null;
                        if (call.getPathInfoUndecoded() != null) {
                                String p = 
call.getPathInfoUndecoded().substring(1);
                                if (context.isStaticFile(p)) {
-                                       StaticFile sf = 
context.resolveStaticFile(p);
-                                       r = sf.resource;
-                                       call.responseMeta(sf.meta);
+                                       r = context.resolveStaticFile(p);
+                                       if (! r.exists()) {
+                                               call.output(null);
+                                               r = null;
+                                       }
                                } else if (p.equals("favicon.ico")) {
                                        call.output(null);
                                }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallLogger.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallLogger.java
index 483e6ae..6002781 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallLogger.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallLogger.java
@@ -248,12 +248,16 @@ public class BasicRestCallLogger implements 
RestCallLogger {
        }
 
        private byte[] getRequestBody(HttpServletRequest req) {
+               if (req instanceof RestRequest)
+                       req = ((RestRequest)req).getInner();
                if (req instanceof CachingHttpServletRequest)
                        return ((CachingHttpServletRequest)req).getBody();
                return castOrNull(req.getAttribute("RequestBody"), 
byte[].class);
        }
 
        private byte[] getResponseBody(HttpServletRequest req, 
HttpServletResponse res) {
+               if (res instanceof RestResponse)
+                       res = ((RestResponse)res).getInner();
                if (res instanceof CachingHttpServletResponse)
                        return ((CachingHttpServletResponse)res).getBody();
                return castOrNull(req.getAttribute("ResponseBody"), 
byte[].class);
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
index 75c352f..770d66c 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
@@ -17,6 +17,7 @@ import static org.apache.juneau.internal.CollectionUtils.*;
 import static org.apache.juneau.internal.IOUtils.*;
 import static org.apache.juneau.internal.StringUtils.*;
 import static org.apache.juneau.rest.util.RestUtils.*;
+import static org.apache.juneau.rest.Enablement.*;
 import static org.apache.juneau.rest.HttpRuntimeException.*;
 import static org.apache.juneau.BasicIllegalArgumentException.*;
 
@@ -41,7 +42,6 @@ import org.apache.juneau.encoders.*;
 import org.apache.juneau.html.*;
 import org.apache.juneau.html.annotation.*;
 import org.apache.juneau.http.*;
-import org.apache.juneau.http.StreamResource;
 import org.apache.juneau.http.annotation.*;
 import org.apache.juneau.http.annotation.Body;
 import org.apache.juneau.http.annotation.FormData;
@@ -3628,6 +3628,7 @@ public final class RestContext extends BeanContext {
        private final UriRelativity uriRelativity;
        private final ConcurrentHashMap<String,MethodExecStats> methodExecStats 
= new ConcurrentHashMap<>();
        private final Instant startTime;
+       private final Map<Class<?>,ResponseBeanMeta> responseBeanMetas = new 
ConcurrentHashMap<>();
 
        // Lifecycle methods
        private final MethodInvoker[]
@@ -3794,7 +3795,9 @@ public final class RestContext extends BeanContext {
                        logger = getInstanceProperty(REST_logger, resource, 
RestLogger.class, NoOpRestLogger.class, resourceResolver, this);
 
                        Object clc = getProperty(REST_callLoggerConfig);
-                       if (clc instanceof RestCallLoggerConfig)
+                       if (this.debug == TRUE)
+                               this.callLoggerConfig = 
RestCallLoggerConfig.DEFAULT_DEBUG;
+                       else if (clc instanceof RestCallLoggerConfig)
                                this.callLoggerConfig = 
(RestCallLoggerConfig)clc;
                        else if (clc instanceof OMap)
                                this.callLoggerConfig = 
RestCallLoggerConfig.create().apply((OMap)clc).build();
@@ -4269,13 +4272,14 @@ public final class RestContext extends BeanContext {
                        String p = urlDecode(trimSlashes(pathInfo));
                        if (p.indexOf("..") != -1)
                                throw new NotFound("Invalid path");
-                       StreamResource sr = null;
-                       for (StaticFiles sf : staticFiles) {
-                               sr = sf.resolve(p);
-                               if (sr != null)
+                       StaticFile sf = null;
+                       for (StaticFiles sfs : staticFiles) {
+                               sf = sfs.resolve(p);
+                               if (sf != null)
                                        break;
                        }
-                       StaticFile sf = new StaticFile(sr);
+                       if (sf == null)
+                               sf = new StaticFile(null,null,null);
                        if (useClasspathResourceCaching) {
                                if (staticFilesCache.size() > 100)
                                        staticFilesCache.clear();
@@ -4287,24 +4291,6 @@ public final class RestContext extends BeanContext {
        }
 
        /**
-        * A cached static file instance.
-        */
-       class StaticFile {
-               StreamResource resource;
-               ResponseBeanMeta meta;
-
-               /**
-                * Constructor.
-                *
-                * @param resource The inner resource.
-                */
-               StaticFile(StreamResource resource) {
-                       this.resource = resource;
-                       this.meta = resource == null ? null : 
ResponseBeanMeta.create(resource.getClass(), getPropertyStore());
-               }
-       }
-
-       /**
         * Same as {@link Class#getResourceAsStream(String)} except if it 
doesn't find the resource on this class, searches
         * up the parent hierarchy chain.
         *
@@ -5433,6 +5419,28 @@ public final class RestContext extends BeanContext {
                this.res.set(res);
        }
 
+       /**
+        * If the specified object is annotated with {@link Response}, this 
returns the response metadata about that object.
+        *
+        * @param o The object to check.
+        * @return The response metadata, or <jk>null</jk> if it wasn't 
annotated with {@link Response}.
+        */
+       public ResponseBeanMeta getResponseBeanMeta(Object o) {
+               if (o == null)
+                       return null;
+               Class<?> c = o.getClass();
+               ResponseBeanMeta rbm = responseBeanMetas.get(c);
+               if (rbm == null) {
+                       rbm = ResponseBeanMeta.create(c, 
serializers.getPropertyStore());
+                       if (rbm == null)
+                               rbm = ResponseBeanMeta.NULL;
+                       responseBeanMetas.put(c, rbm);
+               }
+               if (rbm == ResponseBeanMeta.NULL)
+                       return null;
+               return rbm;
+       }
+
        Enablement getDebug() {
                return debug;
        }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
index 6d71272..1f09414 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
@@ -1304,6 +1304,8 @@ public final class RestRequest extends 
HttpServletRequestWrapper {
         * @return The request bean session.
         */
        public BeanSession getBeanSession() {
+               if (beanSession == null)
+                       beanSession = context.createBeanSession();
                return beanSession;
        }
 
@@ -1468,6 +1470,32 @@ public final class RestRequest extends 
HttpServletRequestWrapper {
        }
 
        /**
+        * Returns a classpath resource as a string,
+        *
+        * @param name The resource name.
+        * @return The resource contents, or <jk>null</jk> if they could not be 
found.
+        * @throws IOException If a problem occurred reading the resource.
+        */
+       public String getClasspathResourceAsString(String name) throws 
IOException {
+               return getClasspathResourceAsString(name, false);
+       }
+
+       /**
+        * Returns a classpath resource as a string,
+        *
+        * @param name The resource name.
+        * @param resolveVars Resolve any SVL variables in the string.
+        * @return The resource contents, or <jk>null</jk> if they could not be 
found.
+        * @throws IOException If a problem occurred reading the resource.
+        */
+       public String getClasspathResourceAsString(String name, boolean 
resolveVars) throws IOException {
+               String s = context.getClasspathResourceAsString(name, 
getLocale());
+               if (resolveVars)
+                       return varSession.resolve(s);
+               return s;
+       }
+
+       /**
         * Same as {@link #getClasspathReaderResource(String, boolean, 
MediaType, boolean)} except uses the resource mime-type map
         * constructed using {@link RestContextBuilder#mimeTypes(String...)} to 
determine the media type.
         *
@@ -1816,7 +1844,7 @@ public final class RestRequest extends 
HttpServletRequestWrapper {
         * @return Metadata about the specified response object, or 
<jk>null</jk> if it's not annotated with {@link Response @Response}.
         */
        public ResponseBeanMeta getResponseBeanMeta(Object o) {
-               return restJavaMethod == null ? null : 
restJavaMethod.getResponseBeanMeta(o);
+               return restJavaMethod == null ? 
this.context.getResponseBeanMeta(o) : restJavaMethod.getResponseBeanMeta(o);
        }
 
        /**
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/StaticFile.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/StaticFile.java
new file mode 100644
index 0000000..bd1032e
--- /dev/null
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/StaticFile.java
@@ -0,0 +1,85 @@
+// 
***************************************************************************************************************************
+// * 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.juneau.rest;
+
+import java.io.*;
+import java.util.*;
+
+import org.apache.juneau.http.annotation.*;
+
+/**
+ * Instance of a static file sent as an HTTP response.
+ */
+@Response
+public class StaticFile {
+
+       private final byte[] contents;
+       private final String mediaType;
+       private final Map<String,Object> headers;
+
+       /**
+        * Constructor.
+        *
+        * @param contents Contents of the file, or <jk>null</jk> if file does 
not exist.
+        * @param mediaType The media type of the file.
+        * @param headers Arbitrary response headers to set when sending this 
file as an HTTP response.
+        */
+       public StaticFile(byte[] contents, String mediaType, Map<String,Object> 
headers) {
+               this.contents = contents;
+               this.mediaType = mediaType;
+               this.headers = headers;
+       }
+
+       /**
+        * Does this file exist?
+        *
+        * @return <jk>true</jk> if this file exists.
+        */
+       public boolean exists() {
+               return contents != null;
+       }
+
+       /**
+        * Get the HTTP response headers.
+        *
+        * @return
+        *      The HTTP response headers.
+        *      <br>An unmodifiable map.
+        *      <br>Never <jk>null</jk>.
+        */
+       @ResponseHeader("*")
+       public Map<String,Object> getHeaders() {
+               return headers;
+       }
+
+       /**
+        * Returns the contents of this static file as an input stream.
+        *
+        * @return This file as an input stream.
+        * @throws IOException Should never happen.
+        */
+       @ResponseBody
+       public InputStream getInputStream() throws IOException {
+               return new ByteArrayInputStream(contents);
+       }
+
+       /**
+        * Returns the content type for this static file.
+        *
+        * @return The content type for this static file.
+        */
+       @ResponseHeader("Content-Type")
+       public String getContentType() {
+               return mediaType == null ? null : mediaType.toString();
+       }
+}
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/StaticFiles.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/StaticFiles.java
index e7d859d..35aa3c0 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/StaticFiles.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/StaticFiles.java
@@ -10,18 +10,6 @@
 // * "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.                                              *
 // 
***************************************************************************************************************************
-// 
***************************************************************************************************************************
-// * 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.juneau.rest;
 
 import java.io.*;
@@ -29,7 +17,7 @@ import java.util.*;
 
 import javax.activation.*;
 
-import org.apache.juneau.http.*;
+import org.apache.juneau.internal.*;
 import org.apache.juneau.utils.*;
 
 /**
@@ -56,7 +44,7 @@ class StaticFiles {
                return path;
        }
 
-       StreamResource resolve(String p) throws IOException {
+       StaticFile resolve(String p) throws IOException {
                if (p.startsWith(path)) {
                        String remainder = (p.equals(path) ? "" : 
p.substring(path.length()));
                        if (remainder.isEmpty() || remainder.startsWith("/")) {
@@ -66,7 +54,7 @@ class StaticFiles {
                                                int i = p2.lastIndexOf('/');
                                                String name = (i == -1 ? p2 : 
p2.substring(i+1));
                                                String mediaType = 
mimetypesFileTypeMap.getContentType(name);
-                                               return new 
StreamResource(MediaType.forString(mediaType), responseHeaders, true, is);
+                                               return new 
StaticFile(IOUtils.readBytes(is), mediaType, responseHeaders);
                                        }
                                }
                        }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/StatusStats.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/StatusStats.java
index 58e14c8..d42050a 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/StatusStats.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/StatusStats.java
@@ -57,7 +57,7 @@ public class StatusStats implements Comparable<StatusStats> {
                private java.lang.reflect.Method method;
                private Set<Status> codes = new TreeSet<>();
 
-               private Method(java.lang.reflect.Method method) {
+               Method(java.lang.reflect.Method method) {
                        this.method = method;
                }
 
@@ -86,6 +86,10 @@ public class StatusStats implements Comparable<StatusStats> {
                public int compareTo(Status o) {
                        return Integer.compare(code, o.code);
                }
+
+               public int getCount() {
+                       return count;
+               }
        }
 
        @Override
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/DefaultHandler.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/DefaultHandler.java
index 7f51b6f..73ea7a5 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/DefaultHandler.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/DefaultHandler.java
@@ -28,6 +28,7 @@ import org.apache.juneau.http.exception.*;
 import org.apache.juneau.rest.util.FinishablePrintWriter;
 import org.apache.juneau.rest.util.FinishableServletOutputStream;
 import org.apache.juneau.serializer.*;
+import org.apache.juneau.utils.*;
 
 /**
  * Response handler for POJOs not handled by other handlers.
@@ -191,11 +192,24 @@ public class DefaultHandler implements ResponseHandler {
                        String out = null;
                        if (isEmpty(res.getContentType()))
                                res.setContentType("text/plain");
-                       out = 
req.getBeanSession().getClassMetaForObject(o).toString(o);
-                       FinishablePrintWriter w = res.getNegotiatedWriter();
-                       w.append(out);
-                       w.flush();
-                       w.finish();
+                       if (o instanceof InputStream) {
+                               try (OutputStream os = 
res.getNegotiatedOutputStream()) {
+                                       IOPipe.create(o, os).run();
+                                       os.flush();
+                               }
+                       } else if (o instanceof Reader) {
+                               try (FinishablePrintWriter w = 
res.getNegotiatedWriter()) {
+                                       IOPipe.create(o, w).run();
+                                       w.flush();
+                                       w.finish();
+                               }
+                       } else {
+                               out = 
req.getBeanSession().getClassMetaForObject(o).toString(o);
+                               FinishablePrintWriter w = 
res.getNegotiatedWriter();
+                               w.append(out);
+                               w.flush();
+                               w.finish();
+                       }
                        return true;
                }
 
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/vars/FileVar.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/vars/FileVar.java
index 8623a64..cc3de68 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/vars/FileVar.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/vars/FileVar.java
@@ -12,7 +12,7 @@
 // 
***************************************************************************************************************************
 package org.apache.juneau.rest.vars;
 
-import org.apache.juneau.http.ReaderResource;
+import org.apache.juneau.internal.*;
 import org.apache.juneau.rest.*;
 import org.apache.juneau.svl.*;
 import org.apache.juneau.utils.*;
@@ -25,7 +25,7 @@ import org.apache.juneau.utils.*;
  *
  * <p>
  * File variables resolve to the contents of resource files located on the 
classpath or local JVM directory.
- * They use the {@link RestRequest#getClasspathReaderResource(String)} method 
to retrieve the contents of the file.
+ * They use the {@link RestRequest#getClasspathResourceAsString(String)} 
method to retrieve the contents of the file.
  * That in turn uses the {@link ClasspathResourceFinder} associated with the 
servlet class to find the file.
  *
  * <p>
@@ -84,8 +84,15 @@ public class FileVar extends DefaultingVar {
 
                RestRequest req = session.getSessionObject(RestRequest.class, 
SESSION_req, false);
                if (req != null) {
-                       ReaderResource rr = req.getClasspathReaderResource(key);
-                       return (rr == null ? null : 
rr.toCommentStrippedString());
+                       String s = req.getClasspathResourceAsString(key);
+                       if (s == null)
+                               return null;
+                       String subType = FileUtils.getExtension(key);
+                       if ("html".equals(subType) || "xhtml".equals(subType) 
|| "xml".equals(subType))
+                               s = s.replaceAll("(?s)<!--(.*?)-->\\s*", "");
+                       else if ("json".equals(subType) || 
"javascript".equals(subType) || "css".equals(subType))
+                               s = s.replaceAll("(?s)\\/\\*(.*?)\\*\\/\\s*", 
"");
+                       return s;
                }
 
                ClasspathResourceManager crm = 
session.getSessionObject(ClasspathResourceManager.class, SESSION_crm, false);

Reply via email to