001/*
002 * Copyright 2002-2017 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.IOException;
021import java.io.InputStream;
022import java.net.HttpURLConnection;
023import java.net.MalformedURLException;
024import java.net.URI;
025import java.net.URISyntaxException;
026import java.net.URL;
027import java.net.URLConnection;
028
029import org.springframework.util.Assert;
030import org.springframework.util.ResourceUtils;
031import org.springframework.util.StringUtils;
032
033/**
034 * {@link Resource} implementation for {@code java.net.URL} locators.
035 * Supports resolution as a {@code URL} and also as a {@code File} in
036 * case of the {@code "file:"} protocol.
037 *
038 * @author Juergen Hoeller
039 * @since 28.12.2003
040 * @see java.net.URL
041 */
042public class UrlResource extends AbstractFileResolvingResource {
043
044        /**
045         * Original URI, if available; used for URI and File access.
046         */
047        private final URI uri;
048
049        /**
050         * Original URL, used for actual access.
051         */
052        private final URL url;
053
054        /**
055         * Cleaned URL (with normalized path), used for comparisons.
056         */
057        private final URL cleanedUrl;
058
059
060        /**
061         * Create a new {@code UrlResource} based on the given URI object.
062         * @param uri a URI
063         * @throws MalformedURLException if the given URL path is not valid
064         * @since 2.5
065         */
066        public UrlResource(URI uri) throws MalformedURLException {
067                Assert.notNull(uri, "URI must not be null");
068                this.uri = uri;
069                this.url = uri.toURL();
070                this.cleanedUrl = getCleanedUrl(this.url, uri.toString());
071        }
072
073        /**
074         * Create a new {@code UrlResource} based on the given URL object.
075         * @param url a URL
076         */
077        public UrlResource(URL url) {
078                Assert.notNull(url, "URL must not be null");
079                this.url = url;
080                this.cleanedUrl = getCleanedUrl(this.url, url.toString());
081                this.uri = null;
082        }
083
084        /**
085         * Create a new {@code UrlResource} based on a URL path.
086         * <p>Note: The given path needs to be pre-encoded if necessary.
087         * @param path a URL path
088         * @throws MalformedURLException if the given URL path is not valid
089         * @see java.net.URL#URL(String)
090         */
091        public UrlResource(String path) throws MalformedURLException {
092                Assert.notNull(path, "Path must not be null");
093                this.uri = null;
094                this.url = new URL(path);
095                this.cleanedUrl = getCleanedUrl(this.url, path);
096        }
097
098        /**
099         * Create a new {@code UrlResource} based on a URI specification.
100         * <p>The given parts will automatically get encoded if necessary.
101         * @param protocol the URL protocol to use (e.g. "jar" or "file" - without colon);
102         * also known as "scheme"
103         * @param location the location (e.g. the file path within that protocol);
104         * also known as "scheme-specific part"
105         * @throws MalformedURLException if the given URL specification is not valid
106         * @see java.net.URI#URI(String, String, String)
107         */
108        public UrlResource(String protocol, String location) throws MalformedURLException  {
109                this(protocol, location, null);
110        }
111
112        /**
113         * Create a new {@code UrlResource} based on a URI specification.
114         * <p>The given parts will automatically get encoded if necessary.
115         * @param protocol the URL protocol to use (e.g. "jar" or "file" - without colon);
116         * also known as "scheme"
117         * @param location the location (e.g. the file path within that protocol);
118         * also known as "scheme-specific part"
119         * @param fragment the fragment within that location (e.g. anchor on an HTML page,
120         * as following after a "#" separator)
121         * @throws MalformedURLException if the given URL specification is not valid
122         * @see java.net.URI#URI(String, String, String)
123         */
124        public UrlResource(String protocol, String location, String fragment) throws MalformedURLException  {
125                try {
126                        this.uri = new URI(protocol, location, fragment);
127                        this.url = this.uri.toURL();
128                        this.cleanedUrl = getCleanedUrl(this.url, this.uri.toString());
129                }
130                catch (URISyntaxException ex) {
131                        MalformedURLException exToThrow = new MalformedURLException(ex.getMessage());
132                        exToThrow.initCause(ex);
133                        throw exToThrow;
134                }
135        }
136
137
138        /**
139         * Determine a cleaned URL for the given original URL.
140         * @param originalUrl the original URL
141         * @param originalPath the original URL path
142         * @return the cleaned URL
143         * @see org.springframework.util.StringUtils#cleanPath
144         */
145        private URL getCleanedUrl(URL originalUrl, String originalPath) {
146                try {
147                        return new URL(StringUtils.cleanPath(originalPath));
148                }
149                catch (MalformedURLException ex) {
150                        // Cleaned URL path cannot be converted to URL
151                        // -> take original URL.
152                        return originalUrl;
153                }
154        }
155
156        /**
157         * This implementation opens an InputStream for the given URL.
158         * <p>It sets the {@code useCaches} flag to {@code false},
159         * mainly to avoid jar file locking on Windows.
160         * @see java.net.URL#openConnection()
161         * @see java.net.URLConnection#setUseCaches(boolean)
162         * @see java.net.URLConnection#getInputStream()
163         */
164        @Override
165        public InputStream getInputStream() throws IOException {
166                URLConnection con = this.url.openConnection();
167                ResourceUtils.useCachesIfNecessary(con);
168                try {
169                        return con.getInputStream();
170                }
171                catch (IOException ex) {
172                        // Close the HTTP connection (if applicable).
173                        if (con instanceof HttpURLConnection) {
174                                ((HttpURLConnection) con).disconnect();
175                        }
176                        throw ex;
177                }
178        }
179
180        /**
181         * This implementation returns the underlying URL reference.
182         */
183        @Override
184        public URL getURL() throws IOException {
185                return this.url;
186        }
187
188        /**
189         * This implementation returns the underlying URI directly,
190         * if possible.
191         */
192        @Override
193        public URI getURI() throws IOException {
194                if (this.uri != null) {
195                        return this.uri;
196                }
197                else {
198                        return super.getURI();
199                }
200        }
201
202        /**
203         * This implementation returns a File reference for the underlying URL/URI,
204         * provided that it refers to a file in the file system.
205         * @see org.springframework.util.ResourceUtils#getFile(java.net.URL, String)
206         */
207        @Override
208        public File getFile() throws IOException {
209                if (this.uri != null) {
210                        return super.getFile(this.uri);
211                }
212                else {
213                        return super.getFile();
214                }
215        }
216
217        /**
218         * This implementation creates a {@code UrlResource}, applying the given path
219         * relative to the path of the underlying URL of this resource descriptor.
220         * @see java.net.URL#URL(java.net.URL, String)
221         */
222        @Override
223        public Resource createRelative(String relativePath) throws MalformedURLException {
224                if (relativePath.startsWith("/")) {
225                        relativePath = relativePath.substring(1);
226                }
227                return new UrlResource(new URL(this.url, relativePath));
228        }
229
230        /**
231         * This implementation returns the name of the file that this URL refers to.
232         * @see java.net.URL#getPath()
233         */
234        @Override
235        public String getFilename() {
236                return StringUtils.getFilename(this.cleanedUrl.getPath());
237        }
238
239        /**
240         * This implementation returns a description that includes the URL.
241         */
242        @Override
243        public String getDescription() {
244                return "URL [" + this.url + "]";
245        }
246
247
248        /**
249         * This implementation compares the underlying URL references.
250         */
251        @Override
252        public boolean equals(Object obj) {
253                return (obj == this ||
254                        (obj instanceof UrlResource && this.cleanedUrl.equals(((UrlResource) obj).cleanedUrl)));
255        }
256
257        /**
258         * This implementation returns the hash code of the underlying URL reference.
259         */
260        @Override
261        public int hashCode() {
262                return this.cleanedUrl.hashCode();
263        }
264
265}