001/* 002 * Copyright 2002-2018 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; 026 027import org.springframework.core.NestedIOException; 028import org.springframework.util.Assert; 029import org.springframework.util.ResourceUtils; 030 031/** 032 * Convenience base class for {@link Resource} implementations, 033 * pre-implementing typical behavior. 034 * 035 * <p>The "exists" method will check whether a File or InputStream can 036 * be opened; "isOpen" will always return false; "getURL" and "getFile" 037 * throw an exception; and "toString" will return the description. 038 * 039 * @author Juergen Hoeller 040 * @since 28.12.2003 041 */ 042public abstract class AbstractResource implements Resource { 043 044 /** 045 * This implementation checks whether a File can be opened, 046 * falling back to whether an InputStream can be opened. 047 * This will cover both directories and content resources. 048 */ 049 @Override 050 public boolean exists() { 051 // Try file existence: can we find the file in the file system? 052 try { 053 return getFile().exists(); 054 } 055 catch (IOException ex) { 056 // Fall back to stream existence: can we open the stream? 057 try { 058 getInputStream().close(); 059 return true; 060 } 061 catch (Throwable isEx) { 062 return false; 063 } 064 } 065 } 066 067 /** 068 * This implementation always returns {@code true}. 069 */ 070 @Override 071 public boolean isReadable() { 072 return true; 073 } 074 075 /** 076 * This implementation always returns {@code false}. 077 */ 078 @Override 079 public boolean isOpen() { 080 return false; 081 } 082 083 /** 084 * This implementation throws a FileNotFoundException, assuming 085 * that the resource cannot be resolved to a URL. 086 */ 087 @Override 088 public URL getURL() throws IOException { 089 throw new FileNotFoundException(getDescription() + " cannot be resolved to URL"); 090 } 091 092 /** 093 * This implementation builds a URI based on the URL returned 094 * by {@link #getURL()}. 095 */ 096 @Override 097 public URI getURI() throws IOException { 098 URL url = getURL(); 099 try { 100 return ResourceUtils.toURI(url); 101 } 102 catch (URISyntaxException ex) { 103 throw new NestedIOException("Invalid URI [" + url + "]", ex); 104 } 105 } 106 107 /** 108 * This implementation throws a FileNotFoundException, assuming 109 * that the resource cannot be resolved to an absolute file path. 110 */ 111 @Override 112 public File getFile() throws IOException { 113 throw new FileNotFoundException(getDescription() + " cannot be resolved to absolute file path"); 114 } 115 116 /** 117 * This implementation reads the entire InputStream to calculate the 118 * content length. Subclasses will almost always be able to provide 119 * a more optimal version of this, e.g. checking a File length. 120 * @see #getInputStream() 121 */ 122 @Override 123 public long contentLength() throws IOException { 124 InputStream is = getInputStream(); 125 Assert.state(is != null, "Resource InputStream must not be null"); 126 try { 127 long size = 0; 128 byte[] buf = new byte[256]; 129 int read; 130 while ((read = is.read(buf)) != -1) { 131 size += read; 132 } 133 return size; 134 } 135 finally { 136 try { 137 is.close(); 138 } 139 catch (IOException ex) { 140 } 141 } 142 } 143 144 /** 145 * This implementation checks the timestamp of the underlying File, 146 * if available. 147 * @see #getFileForLastModifiedCheck() 148 */ 149 @Override 150 public long lastModified() throws IOException { 151 File fileToCheck = getFileForLastModifiedCheck(); 152 long lastModified = fileToCheck.lastModified(); 153 if (lastModified == 0L && !fileToCheck.exists()) { 154 throw new FileNotFoundException(getDescription() + 155 " cannot be resolved in the file system for checking its last-modified timestamp"); 156 } 157 return lastModified; 158 } 159 160 /** 161 * Determine the File to use for timestamp checking. 162 * <p>The default implementation delegates to {@link #getFile()}. 163 * @return the File to use for timestamp checking (never {@code null}) 164 * @throws FileNotFoundException if the resource cannot be resolved as 165 * an absolute file path, i.e. is not available in a file system 166 * @throws IOException in case of general resolution/reading failures 167 */ 168 protected File getFileForLastModifiedCheck() throws IOException { 169 return getFile(); 170 } 171 172 /** 173 * This implementation throws a FileNotFoundException, assuming 174 * that relative resources cannot be created for this resource. 175 */ 176 @Override 177 public Resource createRelative(String relativePath) throws IOException { 178 throw new FileNotFoundException("Cannot create a relative resource for " + getDescription()); 179 } 180 181 /** 182 * This implementation always returns {@code null}, 183 * assuming that this resource type does not have a filename. 184 */ 185 @Override 186 public String getFilename() { 187 return null; 188 } 189 190 191 /** 192 * This implementation returns the description of this resource. 193 * @see #getDescription() 194 */ 195 @Override 196 public String toString() { 197 return getDescription(); 198 } 199 200 /** 201 * This implementation compares description strings. 202 * @see #getDescription() 203 */ 204 @Override 205 public boolean equals(Object obj) { 206 return (obj == this || 207 (obj instanceof Resource && ((Resource) obj).getDescription().equals(getDescription()))); 208 } 209 210 /** 211 * This implementation returns the description's hash code. 212 * @see #getDescription() 213 */ 214 @Override 215 public int hashCode() { 216 return getDescription().hashCode(); 217 } 218 219}