Repository: incubator-juneau Updated Branches: refs/heads/master 053b7c9ad -> edbba9377
Improvements to @RestResource.stylesheet() and StreamResource. Project: http://git-wip-us.apache.org/repos/asf/incubator-juneau/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-juneau/commit/edbba937 Tree: http://git-wip-us.apache.org/repos/asf/incubator-juneau/tree/edbba937 Diff: http://git-wip-us.apache.org/repos/asf/incubator-juneau/diff/edbba937 Branch: refs/heads/master Commit: edbba93773cb2e13aba02d9a5fe6d58efa2f6f14 Parents: 053b7c9 Author: JamesBognar <[email protected]> Authored: Mon Feb 27 11:14:51 2017 -0500 Committer: JamesBognar <[email protected]> Committed: Mon Feb 27 11:14:51 2017 -0500 ---------------------------------------------------------------------- .../org/apache/juneau/internal/IOUtils.java | 5 +- juneau-core/src/main/javadoc/overview.html | 7 + .../org/apache/juneau/rest/RestServlet.java | 23 ++- .../org/apache/juneau/rest/StreamResource.java | 159 ++++++++++++++++--- .../juneau/rest/annotation/RestResource.java | 6 +- 5 files changed, 159 insertions(+), 41 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/edbba937/juneau-core/src/main/java/org/apache/juneau/internal/IOUtils.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/internal/IOUtils.java b/juneau-core/src/main/java/org/apache/juneau/internal/IOUtils.java index 254fac9..72ece2c 100644 --- a/juneau-core/src/main/java/org/apache/juneau/internal/IOUtils.java +++ b/juneau-core/src/main/java/org/apache/juneau/internal/IOUtils.java @@ -146,10 +146,9 @@ public final class IOUtils { try { while ((nRead = in.read(b, 0, b.length)) != -1) buff.write(b, 0, nRead); + buff.flush(); - buff.flush(); - - return buff.toByteArray(); + return buff.toByteArray(); } finally { in.close(); } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/edbba937/juneau-core/src/main/javadoc/overview.html ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/javadoc/overview.html b/juneau-core/src/main/javadoc/overview.html index d1d3b4a..b7a5b01 100644 --- a/juneau-core/src/main/javadoc/overview.html +++ b/juneau-core/src/main/javadoc/overview.html @@ -5668,6 +5668,13 @@ </ul> </ul> + <h6 class='topic'>org.apache.juneau.rest</h6> + <ul class='spaced-list'> + <li>{@link org.apache.juneau.rest.annotation.RestResource#stylesheet()} can now take in a comma-delimited list of stylesheet paths. + <li>{@link org.apache.juneau.rest.StreamResource} can now contain multiple sources from a variety of source types (e.g. <code><jk>byte</jk>[]</code> arrays, <code>InputStreams</code>, <code>Files</code>, etc...) + and is now immutable. It also includes a new {@link org.apache.juneau.rest.StreamResource.Builder} class. + </ul> + <h6 class='topic'>org.apache.juneau.rest.client</h6> <ul class='spaced-list'> <li>{@link org.apache.juneau.rest.client.RestClient#setRootUrl(Object)} can now take in <code>URI</code> and <code>URL</code> objects. http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/edbba937/juneau-rest/src/main/java/org/apache/juneau/rest/RestServlet.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestServlet.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RestServlet.java index f351088..9bbd5a4 100644 --- a/juneau-rest/src/main/java/org/apache/juneau/rest/RestServlet.java +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestServlet.java @@ -1348,7 +1348,8 @@ public abstract class RestServlet extends HttpServlet { int i = p2.lastIndexOf('/'); String name = (i == -1 ? p2 : p2.substring(i+1)); String mediaType = getMimetypesFileTypeMap().getContentType(name); - staticFilesCache.put(pathInfo, new StreamResource(is, mediaType).setHeader("Cache-Control", "max-age=86400, public")); + ObjectMap headers = new ObjectMap().append("Cache-Control", "max-age=86400, public"); + staticFilesCache.put(pathInfo, new StreamResource(mediaType, headers, is)); return staticFilesCache.get(pathInfo); } finally { is.close(); @@ -1856,19 +1857,15 @@ public abstract class RestServlet extends HttpServlet { * @throws IOException If stylesheet could not be loaded. */ protected StreamResource createStyleSheet() throws IOException { - for (RestResource r : restResourceAnnotationsChildFirst.values()) { + for (RestResource r : restResourceAnnotationsChildFirst.values()) if (! r.stylesheet().isEmpty()) { - String path = getVarResolver().resolve(r.stylesheet()); - InputStream is = getResource(path, null); - if (is != null) { - try { - return new StreamResource(is, "text/css"); - } finally { - is.close(); - } - } + List<InputStream> contents = new ArrayList<InputStream>(); + + for (String path : StringUtils.split(getVarResolver().resolve(r.stylesheet()), ',')) + contents.add(getResource(path, null)); + + return new StreamResource("text/css", contents.toArray()); } - } return null; } @@ -1893,7 +1890,7 @@ public abstract class RestServlet extends HttpServlet { InputStream is = getResource(path, null); if (is != null) { try { - return new StreamResource(is, "image/x-icon"); + return new StreamResource("image/x-icon", is); } finally { is.close(); } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/edbba937/juneau-rest/src/main/java/org/apache/juneau/rest/StreamResource.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/StreamResource.java b/juneau-rest/src/main/java/org/apache/juneau/rest/StreamResource.java index c157c44..31290ae 100644 --- a/juneau-rest/src/main/java/org/apache/juneau/rest/StreamResource.java +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/StreamResource.java @@ -22,56 +22,166 @@ import org.apache.juneau.rest.response.*; /** * Represents the contents of a byte stream file with convenience methods for adding HTTP response headers. * <p> + * The purpose of this class is to maintain an in-memory reusable byte array of a streamed resource for + * the fastest possible streaming. + * Therefore, this object is designed to be reused and thread-safe. + * <p> * This class is handled special by the {@link StreamableHandler} class. + * This allows these objects to be returned as responses by REST methods. */ public class StreamResource implements Streamable { - private byte[] contents; - private String mediaType; - private Map<String,String> headers = new LinkedHashMap<String,String>(); + private final byte[][] contents; + private final String mediaType; + private final Map<String,String> headers; /** * Constructor. - * Create a stream resource from a byte array. * - * @param contents The resource contents. * @param mediaType The resource media type. + * @param contents The resource contents. + * <br>If multiple contents are specified, the results will be concatenated. + * <br>Contents can be any of the following: + * <ul> + * <li><code><jk>byte</jk>[]</code> + * <li><code>InputStream</code> + * <li><code>Reader</code> - Converted to UTF-8 bytes. + * <li><code>File</code> + * <li><code>CharSequence</code> - Converted to UTF-8 bytes. + * </ul> + * @throws IOException */ - public StreamResource(byte[] contents, String mediaType) { - this.contents = contents; - this.mediaType = mediaType; + public StreamResource(String mediaType, Object...contents) throws IOException { + this(mediaType, null, contents); } /** * Constructor. - * Create a stream resource from an <code>InputStream</code>. - * Contents of stream will be loaded into a reusable byte array. * - * @param contents The resource contents. * @param mediaType The resource media type. + * @param headers The HTTP response headers for this streamed resource. + * @param contents The resource contents. + * <br>If multiple contents are specified, the results will be concatenated. + * <br>Contents can be any of the following: + * <ul> + * <li><code><jk>byte</jk>[]</code> + * <li><code>InputStream</code> + * <li><code>Reader</code> - Converted to UTF-8 bytes. + * <li><code>File</code> + * <li><code>CharSequence</code> - Converted to UTF-8 bytes. + * </ul> * @throws IOException */ - public StreamResource(InputStream contents, String mediaType) throws IOException { - this.contents = IOUtils.readBytes(contents, 1024); + public StreamResource(String mediaType, Map<String,Object> headers, Object...contents) throws IOException { this.mediaType = mediaType; + + Map<String,String> m = new LinkedHashMap<String,String>(); + if (headers != null) + for (Map.Entry<String,Object> e : headers.entrySet()) + m.put(e.getKey(), StringUtils.toString(e.getValue())); + this.headers = Collections.unmodifiableMap(m); + + this.contents = new byte[contents.length][]; + for (int i = 0; i < contents.length; i++) { + Object c = contents[i]; + if (c == null) + this.contents[i] = new byte[0]; + else if (c instanceof byte[]) + this.contents[i] = (byte[])c; + else if (c instanceof InputStream) + this.contents[i] = IOUtils.readBytes((InputStream)c, 1024); + else if (c instanceof File) + this.contents[i] = IOUtils.readBytes((File)c); + else if (c instanceof Reader) + this.contents[i] = IOUtils.read((Reader)c).getBytes(IOUtils.UTF8); + else if (c instanceof CharSequence) + this.contents[i] = ((CharSequence)c).toString().getBytes(IOUtils.UTF8); + else + throw new IOException("Invalid class type passed to StreamResource: " + c.getClass().getName()); + } } /** - * Add an HTTP response header. - * - * @param name The header name. - * @param value The header value, converted to a string using {@link Object#toString()}. - * @return This object (for method chaining). + * Builder class for constructing {@link StreamResource} objects. + * <p> */ - public StreamResource setHeader(String name, Object value) { - headers.put(name, value == null ? "" : value.toString()); - return this; + @SuppressWarnings("hiding") + public static class Builder { + ArrayList<Object> contents = new ArrayList<Object>(); + String mediaType; + Map<String,String> headers = new LinkedHashMap<String,String>(); + + /** + * Specifies the resource media type string. + * @param mediaType The resource media type string. + * @return This object (for method chaining). + */ + public Builder mediaType(String mediaType) { + this.mediaType = mediaType; + return this; + } + + /** + * Specifies the contents for this resource. + * <p> + * This method can be called multiple times to add more content. + * + * @param contents The resource contents. + * <br>If multiple contents are specified, the results will be concatenated. + * <br>Contents can be any of the following: + * <ul> + * <li><code><jk>byte</jk>[]</code> + * <li><code>InputStream</code> + * <li><code>Reader</code> - Converted to UTF-8 bytes. + * <li><code>File</code> + * <li><code>CharSequence</code> - Converted to UTF-8 bytes. + * </ul> + * @return This object (for method chaining). + */ + public Builder contents(Object...contents) { + this.contents.addAll(Arrays.asList(contents)); + return this; + } + + /** + * Specifies an HTTP response header value. + * + * @param name The HTTP header name. + * @param value The HTTP header value. Will be converted to a <code>String</code> using {@link Object#toString()}. + * @return This object (for method chaining). + */ + public Builder header(String name, Object value) { + this.headers.put(name, StringUtils.toString(value)); + return this; + } + + /** + * Specifies HTTP response header values. + * + * @param headers The HTTP headers. Values will be converted to <code>Strings</code> using {@link Object#toString()}. + * @return This object (for method chaining). + */ + public Builder headers(Map<String,Object> headers) { + for (Map.Entry<String,Object> e : headers.entrySet()) + header(e.getKey(), e.getValue()); + return this; + } + + /** + * Create a new {@link StreamResource} using values in this builder. + * + * @return A new immutable {@link StreamResource} object. + * @throws IOException + */ + public StreamResource build() throws IOException { + return new StreamResource(mediaType, headers, contents.toArray()); + } } + /** * Get the HTTP response headers. - * - * @return The HTTP response headers. Never <jk>null</jk>. + * @return The HTTP response headers. An unmodifiable map. Never <jk>null</jk>. */ public Map<String,String> getHeaders() { return headers; @@ -79,7 +189,8 @@ public class StreamResource implements Streamable { @Override /* Streamable */ public void streamTo(OutputStream os) throws IOException { - os.write(contents); + for (byte[] b : contents) + os.write(b); } @Override /* Streamable */ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/edbba937/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/RestResource.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/RestResource.java b/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/RestResource.java index 1061f9f..3889b84 100644 --- a/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/RestResource.java +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/RestResource.java @@ -511,8 +511,12 @@ public @interface RestResource { * <ol> * <li><code>com.foo.mypackage.mystyles</code> package. * <li><code>org.apache.juneau.rest.mystyles</code> package (since <code>RestServletDefault</code> is in <code>org.apache.juneau.rest</code>). - * <li><code>[working-dir]/mystyles</code> directory. + * <li><code>[working-dir]/mystyles</code> directory. * </ol> + * <p> + * Multiple stylesheets can be specified as a comma-delimited list. + * When multiple stylesheets are specified, their contents will be concatenated and return in the order specified + * in the list. */ String stylesheet() default "";
