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 * <h5 class='section'>See Also:</h5> 039 * <ul> 040 * <li class='link'>{@doc juneau-rest-server.RestMethod.StreamResource} 041 * </ul> 042 */ 043@Response 044public class StreamResource implements Streamable { 045 046 private final MediaType mediaType; 047 private final Object[] contents; 048 private final Map<String,Object> headers; 049 050 StreamResource(Builder b) throws IOException { 051 this(b.mediaType, b.headers, b.cached, b.contents.toArray()); 052 } 053 054 /** 055 * Constructor. 056 * 057 * @param mediaType The resource media type. 058 * @param headers The HTTP response headers for this streamed resource. 059 * @param cached 060 * Identifies if this stream resource is cached in memory. 061 * <br>If <jk>true</jk>, the contents will be loaded into a byte array for fast retrieval. 062 * @param contents 063 * The resource contents. 064 * <br>If multiple contents are specified, the results will be concatenated. 065 * <br>Contents can be any of the following: 066 * <ul> 067 * <li><code><jk>byte</jk>[]</code> 068 * <li><code>InputStream</code> 069 * <li><code>Reader</code> - Converted to UTF-8 bytes. 070 * <li><code>File</code> 071 * <li><code>CharSequence</code> - Converted to UTF-8 bytes. 072 * </ul> 073 * @throws IOException 074 */ 075 public StreamResource(MediaType mediaType, Map<String,Object> headers, boolean cached, Object...contents) throws IOException { 076 this.mediaType = mediaType; 077 this.headers = immutableMap(headers); 078 this.contents = cached ? new Object[]{readBytes(contents)} : contents; 079 } 080 081 //----------------------------------------------------------------------------------------------------------------- 082 // Builder 083 //----------------------------------------------------------------------------------------------------------------- 084 085 /** 086 * Creates a new instance of a {@link Builder} for this class. 087 * 088 * @return A new instance of a {@link Builder}. 089 */ 090 public static Builder create() { 091 return new Builder(); 092 } 093 094 /** 095 * Builder class for constructing {@link StreamResource} objects. 096 * 097 * <h5 class='section'>See Also:</h5> 098 * <ul> 099 * <li class='link'>{@doc juneau-rest-server.RestMethod.StreamResource} 100 * </ul> 101 */ 102 public static class Builder { 103 ArrayList<Object> contents = new ArrayList<>(); 104 MediaType mediaType; 105 Map<String,Object> headers = new LinkedHashMap<>(); 106 boolean cached; 107 108 /** 109 * Specifies the resource media type string. 110 * 111 * @param mediaType The resource media type string. 112 * @return This object (for method chaining). 113 */ 114 public Builder mediaType(String mediaType) { 115 this.mediaType = MediaType.forString(mediaType); 116 return this; 117 } 118 119 /** 120 * Specifies the resource media type string. 121 * 122 * @param mediaType The resource media type string. 123 * @return This object (for method chaining). 124 */ 125 public Builder mediaType(MediaType mediaType) { 126 this.mediaType = mediaType; 127 return this; 128 } 129 130 /** 131 * Specifies the contents for this resource. 132 * 133 * <p> 134 * This method can be called multiple times to add more content. 135 * 136 * @param contents 137 * The resource contents. 138 * <br>If multiple contents are specified, the results will be concatenated. 139 * <br>Contents can be any of the following: 140 * <ul> 141 * <li><code><jk>byte</jk>[]</code> 142 * <li><code>InputStream</code> 143 * <li><code>Reader</code> - Converted to UTF-8 bytes. 144 * <li><code>File</code> 145 * <li><code>CharSequence</code> - Converted to UTF-8 bytes. 146 * </ul> 147 * @return This object (for method chaining). 148 */ 149 public Builder contents(Object...contents) { 150 this.contents.addAll(Arrays.asList(contents)); 151 return this; 152 } 153 154 /** 155 * Specifies an HTTP response header value. 156 * 157 * @param name The HTTP header name. 158 * @param value 159 * The HTTP header value. 160 * <br>Will be converted to a <code>String</code> using {@link Object#toString()}. 161 * @return This object (for method chaining). 162 */ 163 public Builder header(String name, Object value) { 164 this.headers.put(name, value); 165 return this; 166 } 167 168 /** 169 * Specifies HTTP response header values. 170 * 171 * @param headers 172 * The HTTP headers. 173 * <br>Values will be converted to <code>Strings</code> using {@link Object#toString()}. 174 * @return This object (for method chaining). 175 */ 176 public Builder headers(Map<String,Object> headers) { 177 this.headers.putAll(headers); 178 return this; 179 } 180 181 /** 182 * Specifies that this resource is intended to be cached. 183 * 184 * <p> 185 * This will trigger the contents to be loaded into a byte array for fast serializing. 186 * 187 * @return This object (for method chaining). 188 */ 189 public Builder cached() { 190 this.cached = true; 191 return this; 192 } 193 194 /** 195 * Create a new {@link StreamResource} using values in this builder. 196 * 197 * @return A new immutable {@link StreamResource} object. 198 * @throws IOException 199 */ 200 public StreamResource build() throws IOException { 201 return new StreamResource(this); 202 } 203 } 204 205 /** 206 * Get the HTTP response headers. 207 * 208 * @return 209 * The HTTP response headers. 210 * <br>An unmodifiable map. 211 * <br>Never <jk>null</jk>. 212 */ 213 @ResponseHeader("*") 214 public Map<String,Object> getHeaders() { 215 return headers; 216 } 217 218 @ResponseBody 219 @Override /* Streamable */ 220 public void streamTo(OutputStream os) throws IOException { 221 for (Object c : contents) 222 pipe(c, os); 223 os.flush(); 224 } 225 226 @ResponseHeader("Content-Type") 227 @Override /* Streamable */ 228 public MediaType getMediaType() { 229 return mediaType; 230 } 231 232 /** 233 * Returns the contents of this stream resource. 234 * 235 * @return The contents of this stream resource. 236 * @throws IOException 237 */ 238 public InputStream getContents() throws IOException { 239 if (contents.length == 1) { 240 Object c = contents[0]; 241 if (c != null) { 242 if (c instanceof byte[]) 243 return new ByteArrayInputStream((byte[])c); 244 else if (c instanceof InputStream) 245 return (InputStream)c; 246 else if (c instanceof File) 247 return new FileInputStream((File)c); 248 else if (c instanceof CharSequence) 249 return new ByteArrayInputStream((((CharSequence)c).toString().getBytes(UTF8))); 250 } 251 } 252 byte[][] bc = new byte[contents.length][]; 253 int c = 0; 254 for (int i = 0; i < contents.length; i++) { 255 Object o = contents[i]; 256 if (o == null) 257 bc[i] = new byte[0]; 258 else if (o instanceof byte[]) 259 bc[i] = (byte[])o; 260 else if (o instanceof InputStream) 261 bc[i] = readBytes((InputStream)o, 1024); 262 else if (o instanceof Reader) 263 bc[i] = read((Reader)o).getBytes(UTF8); 264 else if (o instanceof File) 265 bc[i] = readBytes((File)o); 266 else if (o instanceof CharSequence) 267 bc[i] = ((CharSequence)o).toString().getBytes(UTF8); 268 c += bc[i].length; 269 } 270 ByteBuffer bb = ByteBuffer.allocate(c); 271 for (byte[] b : bc) 272 bb.put(b); 273 return new ByteArrayInputStream(bb.array()); 274 } 275}