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.rest.helper; 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.*; 023import org.apache.juneau.http.annotation.*; 024import org.apache.juneau.svl.*; 025 026/** 027 * Represents the contents of a text file with convenience methods for resolving SVL variables and adding 028 * HTTP response headers. 029 * 030 * <p> 031 * <br>These objects can be returned as responses by REST methods. 032 * 033 * <p> 034 * <l>ReaderResources</l> are meant to be thread-safe and reusable objects. 035 * <br>The contents of the request passed into the constructor are immediately converted to read-only strings. 036 * 037 * <p> 038 * Instances of this class can be built using {@link ReaderResourceBuilder}. 039 * 040 * <h5 class='section'>See Also:</h5> 041 * <ul> 042 * <li class='link'>{@doc juneau-rest-server.RestMethod.ReaderResource} 043 * </ul> 044 */ 045@Response 046public class ReaderResource implements Writable { 047 048 private final MediaType mediaType; 049 private final String[] contents; 050 private final VarResolverSession varSession; 051 private final Map<String,Object> headers; 052 053 /** 054 * Creates a new instance of a {@link ReaderResourceBuilder} 055 * 056 * @return A new instance of a {@link ReaderResourceBuilder} 057 */ 058 public static ReaderResourceBuilder create() { 059 return new ReaderResourceBuilder(); 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 varSession Optional variable resolver for resolving variables in the string. 068 * @param contents 069 * The resource contents. 070 * <br>If multiple contents are specified, the results will be concatenated. 071 * <br>Contents can be any of the following: 072 * <ul> 073 * <li><code>InputStream</code> 074 * <li><code>Reader</code> - Converted to UTF-8 bytes. 075 * <li><code>File</code> 076 * <li><code>CharSequence</code> - Converted to UTF-8 bytes. 077 * </ul> 078 * @throws IOException 079 */ 080 public ReaderResource(MediaType mediaType, Map<String,Object> headers, VarResolverSession varSession, Object...contents) throws IOException { 081 this.mediaType = mediaType; 082 this.varSession = varSession; 083 084 this.headers = immutableMap(headers); 085 086 this.contents = new String[contents.length]; 087 for (int i = 0; i < contents.length; i++) { 088 Object c = contents[i]; 089 if (c == null) 090 this.contents[i] = ""; 091 else if (c instanceof InputStream) 092 this.contents[i] = read((InputStream)c); 093 else if (c instanceof File) 094 this.contents[i] = read((File)c); 095 else if (c instanceof Reader) 096 this.contents[i] = read((Reader)c); 097 else if (c instanceof CharSequence) 098 this.contents[i] = ((CharSequence)c).toString(); 099 else 100 throw new IOException("Invalid class type passed to ReaderResource: " + c.getClass().getName()); 101 } 102 } 103 104 /** 105 * Get the HTTP response headers. 106 * 107 * @return 108 * The HTTP response headers. 109 * <br>An unmodifiable map. 110 * <br>Never <jk>null</jk>. 111 */ 112 @ResponseHeader("*") 113 public Map<String,Object> getHeaders() { 114 return headers; 115 } 116 117 @ResponseBody 118 @Override /* Writeable */ 119 public Writer writeTo(Writer w) throws IOException { 120 for (String s : contents) { 121 if (varSession != null) 122 varSession.resolveTo(s, w); 123 else 124 w.write(s); 125 } 126 return w; 127 } 128 129 @ResponseHeader("Content-Type") 130 @Override /* Writeable */ 131 public MediaType getMediaType() { 132 return mediaType; 133 } 134 135 @Override /* Object */ 136 public String toString() { 137 if (contents.length == 1 && varSession == null) 138 return contents[0]; 139 StringWriter sw = new StringWriter(); 140 for (String s : contents) { 141 if (varSession != null) 142 return varSession.resolve(s); 143 sw.write(s); 144 } 145 return sw.toString(); 146 } 147 148 /** 149 * Same as {@link #toString()} but strips comments from the text before returning it. 150 * 151 * <p> 152 * Supports stripping comments from the following media types: HTML, XHTML, XML, JSON, Javascript, CSS. 153 * 154 * @return The resource contents stripped of any comments. 155 */ 156 public String toCommentStrippedString() { 157 String s = toString(); 158 String subType = mediaType.getSubType(); 159 if ("html".equals(subType) || "xhtml".equals(subType) || "xml".equals(subType)) 160 s = s.replaceAll("(?s)<!--(.*?)-->\\s*", ""); 161 else if ("json".equals(subType) || "javascript".equals(subType) || "css".equals(subType)) 162 s = s.replaceAll("(?s)\\/\\*(.*?)\\*\\/\\s*", ""); 163 return s; 164 } 165}