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}