| // Copyright (C) 2012 The Android Open Source Project |
| // |
| // Licensed 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 com.google.gerrit.extensions.restapi; |
| |
| import java.io.Closeable; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.UnsupportedEncodingException; |
| |
| /** |
| * Wrapper around a non-JSON result from a {@link RestView}. |
| * <p> |
| * Views may return this type to signal they want the server glue to write raw |
| * data to the client, instead of attempting automatic conversion to JSON. The |
| * create form is overloaded to handle plain text from a String, or binary data |
| * from a {@code byte[]} or {@code InputSteam}. |
| */ |
| public abstract class BinaryResult implements Closeable { |
| /** Default MIME type for unknown binary data. */ |
| static final String OCTET_STREAM = "application/octet-stream"; |
| |
| /** Produce a UTF-8 encoded result from a string. */ |
| public static BinaryResult create(String data) { |
| try { |
| return create(data.getBytes("UTF-8")) |
| .setContentType("text/plain") |
| .setCharacterEncoding("UTF-8"); |
| } catch (UnsupportedEncodingException e) { |
| throw new RuntimeException("JVM does not support UTF-8", e); |
| } |
| } |
| |
| /** Produce an {@code application/octet-stream} result from a byte array. */ |
| public static BinaryResult create(byte[] data) { |
| return new Array(data); |
| } |
| |
| /** |
| * Produce an {@code application/octet-stream} of unknown length by copying |
| * the InputStream until EOF. The server glue will automatically close this |
| * stream when copying is complete. |
| */ |
| public static BinaryResult create(InputStream data) { |
| return new Stream(data); |
| } |
| |
| private String contentType = OCTET_STREAM; |
| private String characterEncoding; |
| private long contentLength = -1; |
| private boolean gzip = true; |
| private boolean base64 = false; |
| private String attachmentName; |
| |
| /** @return the MIME type of the result, for HTTP clients. */ |
| public String getContentType() { |
| String enc = getCharacterEncoding(); |
| if (enc != null) { |
| return contentType + "; charset=" + enc; |
| } |
| return contentType; |
| } |
| |
| /** Set the MIME type of the result, and return {@code this}. */ |
| public BinaryResult setContentType(String contentType) { |
| this.contentType = contentType != null ? contentType : OCTET_STREAM; |
| return this; |
| } |
| |
| /** Get the character encoding; null if not known. */ |
| public String getCharacterEncoding() { |
| return characterEncoding; |
| } |
| |
| /** Set the character set used to encode text data and return {@code this}. */ |
| public BinaryResult setCharacterEncoding(String encoding) { |
| characterEncoding = encoding; |
| return this; |
| } |
| |
| /** Get the attachment file name; null if not set. */ |
| public String getAttachmentName() { |
| return attachmentName; |
| } |
| |
| /** Set the attachment file name and return {@code this}. */ |
| public BinaryResult setAttachmentName(String attachmentName) { |
| this.attachmentName = attachmentName; |
| return this; |
| } |
| |
| /** @return length in bytes of the result; -1 if not known. */ |
| public long getContentLength() { |
| return contentLength; |
| } |
| |
| /** Set the content length of the result; -1 if not known. */ |
| public BinaryResult setContentLength(long len) { |
| this.contentLength = len; |
| return this; |
| } |
| |
| /** @return true if this result can be gzip compressed to clients. */ |
| public boolean canGzip() { |
| return gzip; |
| } |
| |
| /** Disable gzip compression for already compressed responses. */ |
| public BinaryResult disableGzip() { |
| this.gzip = false; |
| return this; |
| } |
| |
| /** @return true if the result must be base64 encoded. */ |
| public boolean isBase64() { |
| return base64; |
| } |
| |
| /** Wrap the binary data in base64 encoding. */ |
| public BinaryResult base64() { |
| base64 = true; |
| return this; |
| } |
| |
| /** |
| * Write or copy the result onto the specified output stream. |
| * |
| * @param os stream to write result data onto. This stream will be closed by |
| * the caller after this method returns. |
| * @throws IOException if the data cannot be produced, or the OutputStream |
| * {@code os} throws any IOException during a write or flush call. |
| */ |
| public abstract void writeTo(OutputStream os) throws IOException; |
| |
| /** Close the result and release any resources it holds. */ |
| public void close() throws IOException { |
| } |
| |
| @Override |
| public String toString() { |
| if (getContentLength() >= 0) { |
| return String.format( |
| "BinaryResult[Content-Type: %s, Content-Length: %d]", |
| getContentType(), getContentLength()); |
| } |
| return String.format( |
| "BinaryResult[Content-Type: %s, Content-Length: unknown]", |
| getContentType()); |
| } |
| |
| private static class Array extends BinaryResult { |
| private final byte[] data; |
| |
| Array(byte[] data) { |
| this.data = data; |
| setContentLength(data.length); |
| } |
| |
| @Override |
| public void writeTo(OutputStream os) throws IOException { |
| os.write(data); |
| } |
| } |
| |
| private static class Stream extends BinaryResult { |
| private final InputStream src; |
| |
| Stream(InputStream src) { |
| this.src = src; |
| } |
| |
| @Override |
| public void writeTo(OutputStream dst) throws IOException { |
| byte[] tmp = new byte[4096]; |
| int n; |
| while (0 < (n = src.read(tmp))) { |
| dst.write(tmp, 0, n); |
| } |
| } |
| |
| @Override |
| public void close() throws IOException { |
| src.close(); |
| } |
| } |
| } |