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