001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.core.io; 018 019import java.io.File; 020import java.io.FileNotFoundException; 021import java.io.IOException; 022import java.io.InputStream; 023import java.net.URI; 024import java.net.URISyntaxException; 025import java.net.URL; 026import java.nio.channels.Channels; 027import java.nio.channels.ReadableByteChannel; 028 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 031 032import org.springframework.core.NestedIOException; 033import org.springframework.lang.Nullable; 034import org.springframework.util.ResourceUtils; 035 036/** 037 * Convenience base class for {@link Resource} implementations, 038 * pre-implementing typical behavior. 039 * 040 * <p>The "exists" method will check whether a File or InputStream can 041 * be opened; "isOpen" will always return false; "getURL" and "getFile" 042 * throw an exception; and "toString" will return the description. 043 * 044 * @author Juergen Hoeller 045 * @author Sam Brannen 046 * @since 28.12.2003 047 */ 048public abstract class AbstractResource implements Resource { 049 050 /** 051 * This implementation checks whether a File can be opened, 052 * falling back to whether an InputStream can be opened. 053 * This will cover both directories and content resources. 054 */ 055 @Override 056 public boolean exists() { 057 // Try file existence: can we find the file in the file system? 058 if (isFile()) { 059 try { 060 return getFile().exists(); 061 } 062 catch (IOException ex) { 063 Log logger = LogFactory.getLog(getClass()); 064 if (logger.isDebugEnabled()) { 065 logger.debug("Could not retrieve File for existence check of " + getDescription(), ex); 066 } 067 } 068 } 069 // Fall back to stream existence: can we open the stream? 070 try { 071 getInputStream().close(); 072 return true; 073 } 074 catch (Throwable ex) { 075 Log logger = LogFactory.getLog(getClass()); 076 if (logger.isDebugEnabled()) { 077 logger.debug("Could not retrieve InputStream for existence check of " + getDescription(), ex); 078 } 079 return false; 080 } 081 } 082 083 /** 084 * This implementation always returns {@code true} for a resource 085 * that {@link #exists() exists} (revised as of 5.1). 086 */ 087 @Override 088 public boolean isReadable() { 089 return exists(); 090 } 091 092 /** 093 * This implementation always returns {@code false}. 094 */ 095 @Override 096 public boolean isOpen() { 097 return false; 098 } 099 100 /** 101 * This implementation always returns {@code false}. 102 */ 103 @Override 104 public boolean isFile() { 105 return false; 106 } 107 108 /** 109 * This implementation throws a FileNotFoundException, assuming 110 * that the resource cannot be resolved to a URL. 111 */ 112 @Override 113 public URL getURL() throws IOException { 114 throw new FileNotFoundException(getDescription() + " cannot be resolved to URL"); 115 } 116 117 /** 118 * This implementation builds a URI based on the URL returned 119 * by {@link #getURL()}. 120 */ 121 @Override 122 public URI getURI() throws IOException { 123 URL url = getURL(); 124 try { 125 return ResourceUtils.toURI(url); 126 } 127 catch (URISyntaxException ex) { 128 throw new NestedIOException("Invalid URI [" + url + "]", ex); 129 } 130 } 131 132 /** 133 * This implementation throws a FileNotFoundException, assuming 134 * that the resource cannot be resolved to an absolute file path. 135 */ 136 @Override 137 public File getFile() throws IOException { 138 throw new FileNotFoundException(getDescription() + " cannot be resolved to absolute file path"); 139 } 140 141 /** 142 * This implementation returns {@link Channels#newChannel(InputStream)} 143 * with the result of {@link #getInputStream()}. 144 * <p>This is the same as in {@link Resource}'s corresponding default method 145 * but mirrored here for efficient JVM-level dispatching in a class hierarchy. 146 */ 147 @Override 148 public ReadableByteChannel readableChannel() throws IOException { 149 return Channels.newChannel(getInputStream()); 150 } 151 152 /** 153 * This method reads the entire InputStream to determine the content length. 154 * <p>For a custom sub-class of {@code InputStreamResource}, we strongly 155 * recommend overriding this method with a more optimal implementation, e.g. 156 * checking File length, or possibly simply returning -1 if the stream can 157 * only be read once. 158 * @see #getInputStream() 159 */ 160 @Override 161 public long contentLength() throws IOException { 162 InputStream is = getInputStream(); 163 try { 164 long size = 0; 165 byte[] buf = new byte[256]; 166 int read; 167 while ((read = is.read(buf)) != -1) { 168 size += read; 169 } 170 return size; 171 } 172 finally { 173 try { 174 is.close(); 175 } 176 catch (IOException ex) { 177 Log logger = LogFactory.getLog(getClass()); 178 if (logger.isDebugEnabled()) { 179 logger.debug("Could not close content-length InputStream for " + getDescription(), ex); 180 } 181 } 182 } 183 } 184 185 /** 186 * This implementation checks the timestamp of the underlying File, 187 * if available. 188 * @see #getFileForLastModifiedCheck() 189 */ 190 @Override 191 public long lastModified() throws IOException { 192 File fileToCheck = getFileForLastModifiedCheck(); 193 long lastModified = fileToCheck.lastModified(); 194 if (lastModified == 0L && !fileToCheck.exists()) { 195 throw new FileNotFoundException(getDescription() + 196 " cannot be resolved in the file system for checking its last-modified timestamp"); 197 } 198 return lastModified; 199 } 200 201 /** 202 * Determine the File to use for timestamp checking. 203 * <p>The default implementation delegates to {@link #getFile()}. 204 * @return the File to use for timestamp checking (never {@code null}) 205 * @throws FileNotFoundException if the resource cannot be resolved as 206 * an absolute file path, i.e. is not available in a file system 207 * @throws IOException in case of general resolution/reading failures 208 */ 209 protected File getFileForLastModifiedCheck() throws IOException { 210 return getFile(); 211 } 212 213 /** 214 * This implementation throws a FileNotFoundException, assuming 215 * that relative resources cannot be created for this resource. 216 */ 217 @Override 218 public Resource createRelative(String relativePath) throws IOException { 219 throw new FileNotFoundException("Cannot create a relative resource for " + getDescription()); 220 } 221 222 /** 223 * This implementation always returns {@code null}, 224 * assuming that this resource type does not have a filename. 225 */ 226 @Override 227 @Nullable 228 public String getFilename() { 229 return null; 230 } 231 232 233 /** 234 * This implementation compares description strings. 235 * @see #getDescription() 236 */ 237 @Override 238 public boolean equals(@Nullable Object other) { 239 return (this == other || (other instanceof Resource && 240 ((Resource) other).getDescription().equals(getDescription()))); 241 } 242 243 /** 244 * This implementation returns the description's hash code. 245 * @see #getDescription() 246 */ 247 @Override 248 public int hashCode() { 249 return getDescription().hashCode(); 250 } 251 252 /** 253 * This implementation returns the description of this resource. 254 * @see #getDescription() 255 */ 256 @Override 257 public String toString() { 258 return getDescription(); 259 } 260 261}