001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.http; 014 015import static org.apache.juneau.internal.CollectionUtils.*; 016import static org.apache.juneau.internal.IOUtils.*; 017 018import java.io.*; 019import java.nio.*; 020import java.util.*; 021 022import org.apache.juneau.*; 023import org.apache.juneau.http.annotation.*; 024 025/** 026 * Represents the contents of a byte stream file with convenience methods for adding HTTP response headers. 027 * 028 * <p> 029 * <br>These objects can to be returned as responses by REST methods. 030 * 031 * <p> 032 * <l>StreamResources</l> are meant to be thread-safe and reusable objects. 033 * <br>The contents of the request passed into the constructor are immediately converted to read-only byte arrays. 034 * 035 * <p> 036 * Instances of this class can be built using {@link Builder}. 037 * 038 * <ul class='seealso'> 039 * <li class='link'>{@doc juneau-rest-server.RestMethod.StreamResource} 040 * </ul> 041 */ 042@Response 043public class StreamResource implements Streamable { 044 045 private final MediaType mediaType; 046 private final Object[] contents; 047 private final Map<String,Object> headers; 048 049 StreamResource(Builder b) throws IOException { 050 this(b.mediaType, b.headers, b.cached, b.contents.toArray()); 051 } 052 053 /** 054 * Constructor. 055 * 056 * @param mediaType The resource media type. 057 * @param headers The HTTP response headers for this streamed resource. 058 * @param cached 059 * Identifies if this stream resource is cached in memory. 060 * <br>If <jk>true</jk>, the contents will be loaded into a byte array for fast retrieval. 061 * @param contents 062 * The resource contents. 063 * <br>If multiple contents are specified, the results will be concatenated. 064 * <br>Contents can be any of the following: 065 * <ul> 066 * <li><code><jk>byte</jk>[]</code> 067 * <li><c>InputStream</c> 068 * <li><c>Reader</c> - Converted to UTF-8 bytes. 069 * <li><c>File</c> 070 * <li><c>CharSequence</c> - Converted to UTF-8 bytes. 071 * </ul> 072 * @throws IOException Thrown by underlying stream. 073 */ 074 public StreamResource(MediaType mediaType, Map<String,Object> headers, boolean cached, Object...contents) throws IOException { 075 this.mediaType = mediaType; 076 this.headers = immutableMap(headers); 077 this.contents = cached ? new Object[]{readBytes(contents)} : contents; 078 } 079 080 //----------------------------------------------------------------------------------------------------------------- 081 // Builder 082 //----------------------------------------------------------------------------------------------------------------- 083 084 /** 085 * Creates a new instance of a {@link Builder} for this class. 086 * 087 * @return A new instance of a {@link Builder}. 088 */ 089 public static Builder create() { 090 return new Builder(); 091 } 092 093 /** 094 * Builder class for constructing {@link StreamResource} objects. 095 * 096 * <ul class='seealso'> 097 * <li class='link'>{@doc juneau-rest-server.RestMethod.StreamResource} 098 * </ul> 099 */ 100 public static class Builder { 101 ArrayList<Object> contents = new ArrayList<>(); 102 MediaType mediaType; 103 Map<String,Object> headers = new LinkedHashMap<>(); 104 boolean cached; 105 106 /** 107 * Specifies the resource media type string. 108 * 109 * @param mediaType The resource media type string. 110 * @return This object (for method chaining). 111 */ 112 public Builder mediaType(String mediaType) { 113 this.mediaType = MediaType.forString(mediaType); 114 return this; 115 } 116 117 /** 118 * Specifies the resource media type string. 119 * 120 * @param mediaType The resource media type string. 121 * @return This object (for method chaining). 122 */ 123 public Builder mediaType(MediaType mediaType) { 124 this.mediaType = mediaType; 125 return this; 126 } 127 128 /** 129 * Specifies the contents for this resource. 130 * 131 * <p> 132 * This method can be called multiple times to add more content. 133 * 134 * @param contents 135 * The resource contents. 136 * <br>If multiple contents are specified, the results will be concatenated. 137 * <br>Contents can be any of the following: 138 * <ul> 139 * <li><code><jk>byte</jk>[]</code> 140 * <li><c>InputStream</c> 141 * <li><c>Reader</c> - Converted to UTF-8 bytes. 142 * <li><c>File</c> 143 * <li><c>CharSequence</c> - Converted to UTF-8 bytes. 144 * </ul> 145 * @return This object (for method chaining). 146 */ 147 public Builder contents(Object...contents) { 148 this.contents.addAll(Arrays.asList(contents)); 149 return this; 150 } 151 152 /** 153 * Specifies an HTTP response header value. 154 * 155 * @param name The HTTP header name. 156 * @param value 157 * The HTTP header value. 158 * <br>Will be converted to a <c>String</c> using {@link Object#toString()}. 159 * @return This object (for method chaining). 160 */ 161 public Builder header(String name, Object value) { 162 this.headers.put(name, value); 163 return this; 164 } 165 166 /** 167 * Specifies HTTP response header values. 168 * 169 * @param headers 170 * The HTTP headers. 171 * <br>Values will be converted to <c>Strings</c> using {@link Object#toString()}. 172 * @return This object (for method chaining). 173 */ 174 public Builder headers(Map<String,Object> headers) { 175 this.headers.putAll(headers); 176 return this; 177 } 178 179 /** 180 * Specifies that this resource is intended to be cached. 181 * 182 * <p> 183 * This will trigger the contents to be loaded into a byte array for fast serializing. 184 * 185 * @return This object (for method chaining). 186 */ 187 public Builder cached() { 188 this.cached = true; 189 return this; 190 } 191 192 /** 193 * Create a new {@link StreamResource} using values in this builder. 194 * 195 * @return A new immutable {@link StreamResource} object. 196 * @throws IOException Thrown by underlying stream. 197 */ 198 public StreamResource build() throws IOException { 199 return new StreamResource(this); 200 } 201 } 202 203 /** 204 * Get the HTTP response headers. 205 * 206 * @return 207 * The HTTP response headers. 208 * <br>An unmodifiable map. 209 * <br>Never <jk>null</jk>. 210 */ 211 @ResponseHeader("*") 212 public Map<String,Object> getHeaders() { 213 return headers; 214 } 215 216 @ResponseBody 217 @Override /* Streamable */ 218 public void streamTo(OutputStream os) throws IOException { 219 for (Object c : contents) 220 pipe(c, os); 221 os.flush(); 222 } 223 224 @ResponseHeader("Content-Type") 225 @Override /* Streamable */ 226 public MediaType getMediaType() { 227 return mediaType; 228 } 229 230 /** 231 * Returns the contents of this stream resource. 232 * 233 * @return The contents of this stream resource. 234 * @throws IOException Thrown by underlying stream. 235 */ 236 public InputStream getContents() throws IOException { 237 if (contents.length == 1) { 238 Object c = contents[0]; 239 if (c != null) { 240 if (c instanceof byte[]) 241 return new ByteArrayInputStream((byte[])c); 242 else if (c instanceof InputStream) 243 return (InputStream)c; 244 else if (c instanceof File) 245 return new FileInputStream((File)c); 246 else if (c instanceof CharSequence) 247 return new ByteArrayInputStream((((CharSequence)c).toString().getBytes(UTF8))); 248 } 249 } 250 byte[][] bc = new byte[contents.length][]; 251 int c = 0; 252 for (int i = 0; i < contents.length; i++) { 253 Object o = contents[i]; 254 if (o == null) 255 bc[i] = new byte[0]; 256 else if (o instanceof byte[]) 257 bc[i] = (byte[])o; 258 else if (o instanceof InputStream) 259 bc[i] = readBytes((InputStream)o, 1024); 260 else if (o instanceof Reader) 261 bc[i] = read((Reader)o).getBytes(UTF8); 262 else if (o instanceof File) 263 bc[i] = readBytes((File)o); 264 else if (o instanceof CharSequence) 265 bc[i] = ((CharSequence)o).toString().getBytes(UTF8); 266 c += bc[i].length; 267 } 268 ByteBuffer bb = ByteBuffer.allocate(c); 269 for (byte[] b : bc) 270 bb.put(b); 271 return new ByteArrayInputStream(bb.array()); 272 } 273}