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.util.*; 020 021import org.apache.juneau.*; 022import org.apache.juneau.http.annotation.*; 023 024/** 025 * Represents the contents of a text file with convenience methods for resolving SVL variables and adding 026 * HTTP response headers. 027 * 028 * <p> 029 * <br>These objects can be returned as responses by REST methods. 030 * 031 * <p> 032 * <l>ReaderResources</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 strings. 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.ReaderResource} 040 * </ul> 041 */ 042@Response 043public class ReaderResource implements Writable { 044 045 private final MediaType mediaType; 046 private final Map<String,Object> headers; 047 048 @SuppressWarnings("javadoc") 049 protected final Object[] contents; 050 051 /** 052 * Constructor. 053 * 054 * @param b Builder containing values to initialize this object with. 055 * @throws IOException Thrown by underlying stream. 056 */ 057 protected ReaderResource(Builder b) throws IOException { 058 this(b.mediaType, b.headers, b.cached, b.contents.toArray()); 059 } 060 061 /** 062 * Constructor. 063 * 064 * @param mediaType The resource media type. 065 * @param headers The HTTP response headers for this streamed resource. 066 * @param cached 067 * Identifies if this resource is cached in memory. 068 * <br>If <jk>true</jk>, the contents will be loaded into a String for fast retrieval. 069 * @param contents 070 * The resource contents. 071 * <br>If multiple contents are specified, the results will be concatenated. 072 * <br>Contents can be any of the following: 073 * <ul> 074 * <li><c>InputStream</c> 075 * <li><c>Reader</c> - Converted to UTF-8 bytes. 076 * <li><c>File</c> 077 * <li><c>CharSequence</c> - Converted to UTF-8 bytes. 078 * </ul> 079 * @throws IOException Thrown by underlying stream. 080 */ 081 public ReaderResource(MediaType mediaType, Map<String,Object> headers, boolean cached, Object...contents) throws IOException { 082 this.mediaType = mediaType; 083 this.headers = immutableMap(headers); 084 this.contents = cached ? new Object[]{readAll(contents)} : contents; 085 } 086 087 //----------------------------------------------------------------------------------------------------------------- 088 // Builder 089 //----------------------------------------------------------------------------------------------------------------- 090 091 /** 092 * Creates a new instance of a {@link Builder} for this class. 093 * 094 * @return A new instance of a {@link Builder}. 095 */ 096 public static Builder create() { 097 return new Builder(); 098 } 099 100 /** 101 * Builder class for constructing {@link ReaderResource} objects. 102 * 103 * <ul class='seealso'> 104 * <li class='link'>{@doc juneau-rest-server.RestMethod.ReaderResource} 105 * </ul> 106 */ 107 @SuppressWarnings("javadoc") 108 public static class Builder { 109 110 public ArrayList<Object> contents = new ArrayList<>(); 111 public MediaType mediaType; 112 public Map<String,Object> headers = new LinkedHashMap<>(); 113 public boolean cached; 114 115 /** 116 * Specifies the resource media type string. 117 * 118 * @param mediaType The resource media type string. 119 * @return This object (for method chaining). 120 */ 121 public Builder mediaType(String mediaType) { 122 this.mediaType = MediaType.forString(mediaType); 123 return this; 124 } 125 126 /** 127 * Specifies the resource media type string. 128 * 129 * @param mediaType The resource media type string. 130 * @return This object (for method chaining). 131 */ 132 public Builder mediaType(MediaType mediaType) { 133 this.mediaType = mediaType; 134 return this; 135 } 136 137 /** 138 * Specifies the contents for this resource. 139 * 140 * <p> 141 * This method can be called multiple times to add more content. 142 * 143 * @param contents 144 * The resource contents. 145 * <br>If multiple contents are specified, the results will be concatenated. 146 * <br>Contents can be any of the following: 147 * <ul> 148 * <li><c>InputStream</c> 149 * <li><c>Reader</c> - Converted to UTF-8 bytes. 150 * <li><c>File</c> 151 * <li><c>CharSequence</c> - Converted to UTF-8 bytes. 152 * </ul> 153 * @return This object (for method chaining). 154 */ 155 public Builder contents(Object...contents) { 156 this.contents.addAll(Arrays.asList(contents)); 157 return this; 158 } 159 160 /** 161 * Specifies an HTTP response header value. 162 * 163 * @param name The HTTP header name. 164 * @param value 165 * The HTTP header value. 166 * <br>Will be converted to a <c>String</c> using {@link Object#toString()}. 167 * @return This object (for method chaining). 168 */ 169 public Builder header(String name, Object value) { 170 this.headers.put(name, value); 171 return this; 172 } 173 174 /** 175 * Specifies HTTP response header values. 176 * 177 * @param headers 178 * The HTTP headers. 179 * <br>Values will be converted to <c>Strings</c> using {@link Object#toString()}. 180 * @return This object (for method chaining). 181 */ 182 public Builder headers(Map<String,Object> headers) { 183 this.headers.putAll(headers); 184 return this; 185 } 186 187 /** 188 * Specifies that this resource is intended to be cached. 189 * 190 * <p> 191 * This will trigger the contents to be loaded into a String for fast serializing. 192 * 193 * @return This object (for method chaining). 194 */ 195 public Builder cached() { 196 this.cached = true; 197 return this; 198 } 199 200 /** 201 * Create a new {@link ReaderResource} using values in this builder. 202 * 203 * @return A new immutable {@link ReaderResource} object. 204 * @throws IOException Thrown by underlying stream. 205 */ 206 public ReaderResource build() throws IOException { 207 return new ReaderResource(this); 208 } 209 } 210 211 //----------------------------------------------------------------------------------------------------------------- 212 // Properties 213 //----------------------------------------------------------------------------------------------------------------- 214 215 /** 216 * Get the HTTP response headers. 217 * 218 * @return 219 * The HTTP response headers. 220 * <br>An unmodifiable map. 221 * <br>Never <jk>null</jk>. 222 */ 223 @ResponseHeader("*") 224 public Map<String,Object> getHeaders() { 225 return headers; 226 } 227 228 @ResponseBody 229 @Override /* Writeable */ 230 public Writer writeTo(Writer w) throws IOException { 231 for (Object o : contents) 232 pipe(o, w); 233 return w; 234 } 235 236 @ResponseHeader("Content-Type") 237 @Override /* Writeable */ 238 public MediaType getMediaType() { 239 return mediaType; 240 } 241 242 @Override /* Object */ 243 public String toString() { 244 try { 245 if (contents.length == 1) 246 return read(contents[0]); 247 return writeTo(new StringWriter()).toString(); 248 } catch (IOException e) { 249 throw new RuntimeException(e); 250 } 251 } 252 253 /** 254 * Same as {@link #toString()} but strips comments from the text before returning it. 255 * 256 * <p> 257 * Supports stripping comments from the following media types: HTML, XHTML, XML, JSON, Javascript, CSS. 258 * 259 * @return The resource contents stripped of any comments. 260 */ 261 public String toCommentStrippedString() { 262 String s = toString(); 263 String subType = mediaType.getSubType(); 264 if ("html".equals(subType) || "xhtml".equals(subType) || "xml".equals(subType)) 265 s = s.replaceAll("(?s)<!--(.*?)-->\\s*", ""); 266 else if ("json".equals(subType) || "javascript".equals(subType) || "css".equals(subType)) 267 s = s.replaceAll("(?s)\\/\\*(.*?)\\*\\/\\s*", ""); 268 return s; 269 } 270 271 /** 272 * Returns the contents of this resource. 273 * 274 * @return The contents of this resource. 275 */ 276 public Reader getContents() { 277 if (contents.length == 1 && contents[0] instanceof Reader) { 278 return (Reader)contents[0]; 279 } 280 return new StringReader(toString()); 281 } 282}