001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.request.resource; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.time.Duration; 022import java.time.Instant; 023import javax.servlet.http.HttpServletResponse; 024import org.apache.wicket.Application; 025import org.apache.wicket.util.lang.Bytes; 026import org.apache.wicket.util.lang.Checks; 027import org.apache.wicket.util.resource.IResourceStream; 028import org.apache.wicket.util.resource.IResourceStreamWriter; 029import org.apache.wicket.util.resource.ResourceStreamNotFoundException; 030import org.slf4j.Logger; 031import org.slf4j.LoggerFactory; 032 033 034/** 035 * A {@link AbstractResource resource} that loads its data from {@link IResourceStream} 036 */ 037public class ResourceStreamResource extends AbstractResource 038{ 039 private static final long serialVersionUID = 1L; 040 041 private static final Logger logger = LoggerFactory.getLogger(ResourceStreamResource.class); 042 043 private final IResourceStream stream; 044 private String fileName; 045 private ContentDisposition contentDisposition = ContentDisposition.INLINE; 046 private String textEncoding; 047 048 private Duration cacheDuration; 049 050 /** 051 * Constructor. 052 */ 053 public ResourceStreamResource() 054 { 055 this(null); 056 } 057 058 /** 059 * Constructor. 060 * 061 * @param stream 062 * the resource stream to read from 063 */ 064 public ResourceStreamResource(IResourceStream stream) 065 { 066 this.stream = stream; 067 } 068 069 /** 070 * @param fileName 071 * @return this object, for chaining 072 */ 073 public ResourceStreamResource setFileName(String fileName) 074 { 075 this.fileName = fileName; 076 return this; 077 } 078 079 /** 080 * @param contentDisposition 081 * @return this object, for chaining 082 */ 083 public ResourceStreamResource setContentDisposition(ContentDisposition contentDisposition) 084 { 085 this.contentDisposition = contentDisposition; 086 return this; 087 } 088 089 /** 090 * @param textEncoding 091 * @return this object, for chaining 092 */ 093 public ResourceStreamResource setTextEncoding(String textEncoding) 094 { 095 this.textEncoding = textEncoding; 096 return this; 097 } 098 099 /** 100 * @return the duration for which the resource will be cached by the browser 101 */ 102 public Duration getCacheDuration() 103 { 104 return cacheDuration; 105 } 106 107 /** 108 * @param cacheDuration 109 * the duration for which the resource will be cached by the browser 110 * @return this object, for chaining 111 */ 112 public ResourceStreamResource setCacheDuration(Duration cacheDuration) 113 { 114 this.cacheDuration = cacheDuration; 115 return this; 116 } 117 118 /** 119 * Lazy or dynamic initialization of the wrapped IResourceStream(Writer) 120 * 121 * @param attributes 122 * The request attributes 123 * @return the underlying IResourceStream. May be {@code null}. 124 */ 125 protected IResourceStream getResourceStream(Attributes attributes) 126 { 127 return stream; 128 } 129 130 private IResourceStream internalGetResourceStream(Attributes attributes) 131 { 132 final IResourceStream resourceStream = getResourceStream(attributes); 133 Checks.notNull(resourceStream, "%s#getResourceStream(attributes) should not return null!", getClass().getName()); 134 return resourceStream; 135 } 136 137 @Override 138 protected ResourceResponse newResourceResponse(Attributes attributes) 139 { 140 final IResourceStream resourceStream = internalGetResourceStream(attributes); 141 ResourceResponse data = new ResourceResponse(); 142 Instant lastModifiedTime = resourceStream.lastModifiedTime(); 143 if (lastModifiedTime != null) 144 { 145 data.setLastModified(lastModifiedTime); 146 } 147 148 if (cacheDuration != null) 149 { 150 data.setCacheDuration(cacheDuration); 151 } 152 153 // performance check; don't bother to do anything if the resource is still cached by client 154 if (data.dataNeedsToBeWritten(attributes)) 155 { 156 InputStream inputStream = null; 157 if (resourceStream instanceof IResourceStreamWriter == false) 158 { 159 try 160 { 161 inputStream = resourceStream.getInputStream(); 162 } 163 catch (ResourceStreamNotFoundException e) 164 { 165 data.setError(HttpServletResponse.SC_NOT_FOUND); 166 close(resourceStream); 167 } 168 } 169 170 data.setContentDisposition(contentDisposition); 171 Bytes length = resourceStream.length(); 172 if (length != null) 173 { 174 data.setContentLength(length.bytes()); 175 } 176 data.setFileName(fileName); 177 178 String contentType = resourceStream.getContentType(); 179 if (contentType == null && fileName != null && Application.exists()) 180 { 181 contentType = Application.get().getMimeType(fileName); 182 } 183 data.setContentType(contentType); 184 data.setTextEncoding(textEncoding); 185 186 if (resourceStream instanceof IResourceStreamWriter) 187 { 188 data.setWriteCallback(new WriteCallback() 189 { 190 @Override 191 public void writeData(Attributes attributes) throws IOException 192 { 193 ((IResourceStreamWriter)resourceStream).write(attributes.getResponse().getOutputStream()); 194 close(resourceStream); 195 } 196 }); 197 } 198 else 199 { 200 final InputStream s = inputStream; 201 data.setWriteCallback(new WriteCallback() 202 { 203 @Override 204 public void writeData(Attributes attributes) throws IOException 205 { 206 try 207 { 208 writeStream(attributes, s); 209 } 210 finally 211 { 212 close(resourceStream); 213 } 214 } 215 }); 216 } 217 } 218 219 return data; 220 } 221 222 private void close(IResourceStream stream) 223 { 224 try 225 { 226 stream.close(); 227 } 228 catch (IOException e) 229 { 230 logger.error("Couldn't close ResourceStream", e); 231 } 232 } 233}