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}