diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/encoders/Finishable.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/encoders/Finishable.java new file mode 100644 index 0000000..c86c116 --- /dev/null +++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/encoders/Finishable.java @@ -0,0 +1,31 @@ +// *************************************************************************************************************************** +// * 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.encoders; + +import java.io.*; + +/** + * Interface that identifies an output stream has having a finish() method. + */ +public interface Finishable { + + /** + * Finishes writing compressed data to the output stream without closing the underlying stream. + * + *

+ * Use this method when applying multiple filters in succession to the same output stream. + * + * @throws IOException + */ + void finish() throws IOException; +} diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/encoders/GzipEncoder.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/encoders/GzipEncoder.java index 8ebad4c..1596af5 100644 --- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/encoders/GzipEncoder.java +++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/encoders/GzipEncoder.java @@ -22,13 +22,7 @@ @Override /* Encoder */ public OutputStream getOutputStream(OutputStream os) throws IOException { - return new GZIPOutputStream(os) { - @Override /* OutputStream */ - public final void close() throws IOException { - finish(); - super.close(); - } - }; + return new FinishableGZIPOutputStream(os); } @Override /* Encoder */ @@ -43,4 +37,10 @@ public String[] getCodings() { return new String[]{"gzip"}; } + + private static class FinishableGZIPOutputStream extends GZIPOutputStream implements Finishable { + FinishableGZIPOutputStream(OutputStream out) throws IOException { + super(out); + } + } } diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/FinishablePrintWriter.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/FinishablePrintWriter.java new file mode 100644 index 0000000..4af84f4 --- /dev/null +++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/FinishablePrintWriter.java @@ -0,0 +1,43 @@ +// *************************************************************************************************************************** +// * 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 org.apache.juneau.encoders.*; + +/** + * A wrapped {@link PrintWriter} with an added finish() method. + */ +public class FinishablePrintWriter extends PrintWriter implements Finishable { + + final Finishable f; + + FinishablePrintWriter(OutputStream out, String characterEncoding) throws IOException { + super(new OutputStreamWriter(out, characterEncoding)); + f = (out instanceof Finishable ? (Finishable)out : null); + } + + + /** + * Calls {@link Finishable#finish()} on the underlying output stream. + * + *

+ * A no-op if the underlying output stream does not implement the {@link Finishable} interface. + */ + @Override /* Finishable */ + public void finish() throws IOException { + if (f != null) + f.finish(); + } +} diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/FinishableServletOutputStream.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/FinishableServletOutputStream.java new file mode 100644 index 0000000..87e2044 --- /dev/null +++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/FinishableServletOutputStream.java @@ -0,0 +1,78 @@ +// *************************************************************************************************************************** +// * 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 javax.servlet.*; + +import org.apache.juneau.encoders.*; + +/** + * A wrapped {@link ServletOutputStream} with an added finish() method. + */ +public class FinishableServletOutputStream extends ServletOutputStream implements Finishable { + + final OutputStream os; + final ServletOutputStream sos; + final Finishable f; + + FinishableServletOutputStream(OutputStream os) { + this.os = os; + this.sos = (os instanceof ServletOutputStream ? (ServletOutputStream)os : null); + this.f = (os instanceof Finishable ? (Finishable)os : null); + } + + @Override /* OutputStream */ + public final void write(byte[] b, int off, int len) throws IOException { + os.write(b, off, len); + } + + @Override /* OutputStream */ + public final void write(int b) throws IOException { + os.write(b); + } + + @Override /* OutputStream */ + public final void flush() throws IOException { + os.flush(); + } + + @Override /* OutputStream */ + public final void close() throws IOException { + os.close(); + } + + @Override /* ServletOutputStream */ + public boolean isReady() { + return sos == null ? true : sos.isReady(); + } + + @Override /* ServletOutputStream */ + public void setWriteListener(WriteListener arg0) { + if (sos != null) + sos.setWriteListener(arg0); + } + + /** + * Calls {@link Finishable#finish()} on the underlying output stream. + * + *

+ * A no-op if the underlying output stream does not implement the {@link Finishable} interface. + */ + @Override /* Finishable */ + public void finish() throws IOException { + if (f != null) + f.finish(); + } +} \ No newline at end of file diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java index 21f196b..1228145 100644 --- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java +++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java @@ -58,8 +58,9 @@ private Object output; // The POJO being sent to the output. private boolean isNullOutput; // The output is null (as opposed to not being set at all) private RequestProperties properties; // Response properties - private ServletOutputStream os; - private PrintWriter w; + private ServletOutputStream sos; + private FinishableServletOutputStream os; + private FinishablePrintWriter w; private HtmlDocBuilder htmlDocBuilder; /** @@ -347,7 +348,7 @@ * @return A negotiated output stream. * @throws IOException */ - public ServletOutputStream getNegotiatedOutputStream() throws IOException { + public FinishableServletOutputStream getNegotiatedOutputStream() throws IOException { if (os == null) { Encoder encoder = null; EncoderGroup encoders = restJavaMethod.encoders; @@ -372,46 +373,18 @@ setHeader("content-encoding", encoding); } } - os = getOutputStream(); - if (encoder != null) { - @SuppressWarnings("resource") - final OutputStream os2 = encoder.getOutputStream(os); - os = new ServletOutputStream(){ - @Override /* OutputStream */ - public final void write(byte[] b, int off, int len) throws IOException { - os2.write(b, off, len); - } - @Override /* OutputStream */ - public final void write(int b) throws IOException { - os2.write(b); - } - @Override /* OutputStream */ - public final void flush() throws IOException { - os2.flush(); - } - @Override /* OutputStream */ - public final void close() throws IOException { - os2.close(); - } - @Override /* ServletOutputStream */ - public boolean isReady() { - return true; - } - @Override /* ServletOutputStream */ - public void setWriteListener(WriteListener arg0) { - throw new NoSuchMethodError(); - } - }; - } + @SuppressWarnings("resource") + ServletOutputStream sos = getOutputStream(); + os = new FinishableServletOutputStream(encoder == null ? sos : encoder.getOutputStream(sos)); } return os; } @Override /* ServletResponse */ public ServletOutputStream getOutputStream() throws IOException { - if (os == null) - os = super.getOutputStream(); - return os; + if (sos == null) + sos = super.getOutputStream(); + return sos; } /** @@ -420,7 +393,7 @@ * @return true if {@link #getOutputStream()} has been called. */ public boolean getOutputStreamCalled() { - return os != null; + return sos != null; } /** @@ -463,11 +436,12 @@ * @return The negotiated writer. * @throws IOException */ - public PrintWriter getNegotiatedWriter() throws IOException { + public FinishablePrintWriter getNegotiatedWriter() throws IOException { return getWriter(false); } - private PrintWriter getWriter(boolean raw) throws IOException { + @SuppressWarnings("resource") + private FinishablePrintWriter getWriter(boolean raw) throws IOException { if (w != null) return w; @@ -477,7 +451,7 @@ try { OutputStream out = (raw ? getOutputStream() : getNegotiatedOutputStream()); - w = new PrintWriter(new OutputStreamWriter(out, getCharacterEncoding())); + w = new FinishablePrintWriter(out, getCharacterEncoding()); return w; } catch (UnsupportedEncodingException e) { String ce = getCharacterEncoding(); diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/StreamResource.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/StreamResource.java index e8ba790..a62cb17 100644 --- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/StreamResource.java +++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/StreamResource.java @@ -114,6 +114,7 @@ public void streamTo(OutputStream os) throws IOException { for (byte[] b : contents) os.write(b); + os.flush(); } @Override /* Streamable */ diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/DefaultHandler.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/DefaultHandler.java index 609a862..96a5ba1 100644 --- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/DefaultHandler.java +++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/DefaultHandler.java @@ -69,20 +69,23 @@ if (! session.isWriterSerializer()) { if (req.isPlainText()) { - Writer w = res.getNegotiatedWriter(); + FinishablePrintWriter w = res.getNegotiatedWriter(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); session.serialize(output, baos); w.write(StringUtils.toSpacedHex(baos.toByteArray())); - w.close(); // Leave open if exception occurs. + w.flush(); + w.finish(); } else { - OutputStream os = res.getNegotiatedOutputStream(); + FinishableServletOutputStream os = res.getNegotiatedOutputStream(); session.serialize(output, os); - os.close(); // Leave open if exception occurs. + os.flush(); + os.finish(); } } else { - Writer w = res.getNegotiatedWriter(); + FinishablePrintWriter w = res.getNegotiatedWriter(); session.serialize(output, w); - w.close(); // Leave open if exception occurs. + w.flush(); + w.finish(); } } catch (SerializeException e) { throw new RestException(SC_INTERNAL_SERVER_ERROR, e);