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.util;
018
019import java.io.File;
020import java.io.FileNotFoundException;
021import java.net.MalformedURLException;
022import java.net.URI;
023import java.net.URISyntaxException;
024import java.net.URL;
025import java.net.URLConnection;
026
027import org.springframework.lang.Nullable;
028
029/**
030 * Utility methods for resolving resource locations to files in the
031 * file system. Mainly for internal use within the framework.
032 *
033 * <p>Consider using Spring's Resource abstraction in the core package
034 * for handling all kinds of file resources in a uniform manner.
035 * {@link org.springframework.core.io.ResourceLoader}'s {@code getResource()}
036 * method can resolve any location to a {@link org.springframework.core.io.Resource}
037 * object, which in turn allows one to obtain a {@code java.io.File} in the
038 * file system through its {@code getFile()} method.
039 *
040 * @author Juergen Hoeller
041 * @since 1.1.5
042 * @see org.springframework.core.io.Resource
043 * @see org.springframework.core.io.ClassPathResource
044 * @see org.springframework.core.io.FileSystemResource
045 * @see org.springframework.core.io.UrlResource
046 * @see org.springframework.core.io.ResourceLoader
047 */
048public abstract class ResourceUtils {
049
050        /** Pseudo URL prefix for loading from the class path: "classpath:". */
051        public static final String CLASSPATH_URL_PREFIX = "classpath:";
052
053        /** URL prefix for loading from the file system: "file:". */
054        public static final String FILE_URL_PREFIX = "file:";
055
056        /** URL prefix for loading from a jar file: "jar:". */
057        public static final String JAR_URL_PREFIX = "jar:";
058
059        /** URL prefix for loading from a war file on Tomcat: "war:". */
060        public static final String WAR_URL_PREFIX = "war:";
061
062        /** URL protocol for a file in the file system: "file". */
063        public static final String URL_PROTOCOL_FILE = "file";
064
065        /** URL protocol for an entry from a jar file: "jar". */
066        public static final String URL_PROTOCOL_JAR = "jar";
067
068        /** URL protocol for an entry from a war file: "war". */
069        public static final String URL_PROTOCOL_WAR = "war";
070
071        /** URL protocol for an entry from a zip file: "zip". */
072        public static final String URL_PROTOCOL_ZIP = "zip";
073
074        /** URL protocol for an entry from a WebSphere jar file: "wsjar". */
075        public static final String URL_PROTOCOL_WSJAR = "wsjar";
076
077        /** URL protocol for an entry from a JBoss jar file: "vfszip". */
078        public static final String URL_PROTOCOL_VFSZIP = "vfszip";
079
080        /** URL protocol for a JBoss file system resource: "vfsfile". */
081        public static final String URL_PROTOCOL_VFSFILE = "vfsfile";
082
083        /** URL protocol for a general JBoss VFS resource: "vfs". */
084        public static final String URL_PROTOCOL_VFS = "vfs";
085
086        /** File extension for a regular jar file: ".jar". */
087        public static final String JAR_FILE_EXTENSION = ".jar";
088
089        /** Separator between JAR URL and file path within the JAR: "!/". */
090        public static final String JAR_URL_SEPARATOR = "!/";
091
092        /** Special separator between WAR URL and jar part on Tomcat. */
093        public static final String WAR_URL_SEPARATOR = "*/";
094
095
096        /**
097         * Return whether the given resource location is a URL:
098         * either a special "classpath" pseudo URL or a standard URL.
099         * @param resourceLocation the location String to check
100         * @return whether the location qualifies as a URL
101         * @see #CLASSPATH_URL_PREFIX
102         * @see java.net.URL
103         */
104        public static boolean isUrl(@Nullable String resourceLocation) {
105                if (resourceLocation == null) {
106                        return false;
107                }
108                if (resourceLocation.startsWith(CLASSPATH_URL_PREFIX)) {
109                        return true;
110                }
111                try {
112                        new URL(resourceLocation);
113                        return true;
114                }
115                catch (MalformedURLException ex) {
116                        return false;
117                }
118        }
119
120        /**
121         * Resolve the given resource location to a {@code java.net.URL}.
122         * <p>Does not check whether the URL actually exists; simply returns
123         * the URL that the given location would correspond to.
124         * @param resourceLocation the resource location to resolve: either a
125         * "classpath:" pseudo URL, a "file:" URL, or a plain file path
126         * @return a corresponding URL object
127         * @throws FileNotFoundException if the resource cannot be resolved to a URL
128         */
129        public static URL getURL(String resourceLocation) throws FileNotFoundException {
130                Assert.notNull(resourceLocation, "Resource location must not be null");
131                if (resourceLocation.startsWith(CLASSPATH_URL_PREFIX)) {
132                        String path = resourceLocation.substring(CLASSPATH_URL_PREFIX.length());
133                        ClassLoader cl = ClassUtils.getDefaultClassLoader();
134                        URL url = (cl != null ? cl.getResource(path) : ClassLoader.getSystemResource(path));
135                        if (url == null) {
136                                String description = "class path resource [" + path + "]";
137                                throw new FileNotFoundException(description +
138                                                " cannot be resolved to URL because it does not exist");
139                        }
140                        return url;
141                }
142                try {
143                        // try URL
144                        return new URL(resourceLocation);
145                }
146                catch (MalformedURLException ex) {
147                        // no URL -> treat as file path
148                        try {
149                                return new File(resourceLocation).toURI().toURL();
150                        }
151                        catch (MalformedURLException ex2) {
152                                throw new FileNotFoundException("Resource location [" + resourceLocation +
153                                                "] is neither a URL not a well-formed file path");
154                        }
155                }
156        }
157
158        /**
159         * Resolve the given resource location to a {@code java.io.File},
160         * i.e. to a file in the file system.
161         * <p>Does not check whether the file actually exists; simply returns
162         * the File that the given location would correspond to.
163         * @param resourceLocation the resource location to resolve: either a
164         * "classpath:" pseudo URL, a "file:" URL, or a plain file path
165         * @return a corresponding File object
166         * @throws FileNotFoundException if the resource cannot be resolved to
167         * a file in the file system
168         */
169        public static File getFile(String resourceLocation) throws FileNotFoundException {
170                Assert.notNull(resourceLocation, "Resource location must not be null");
171                if (resourceLocation.startsWith(CLASSPATH_URL_PREFIX)) {
172                        String path = resourceLocation.substring(CLASSPATH_URL_PREFIX.length());
173                        String description = "class path resource [" + path + "]";
174                        ClassLoader cl = ClassUtils.getDefaultClassLoader();
175                        URL url = (cl != null ? cl.getResource(path) : ClassLoader.getSystemResource(path));
176                        if (url == null) {
177                                throw new FileNotFoundException(description +
178                                                " cannot be resolved to absolute file path because it does not exist");
179                        }
180                        return getFile(url, description);
181                }
182                try {
183                        // try URL
184                        return getFile(new URL(resourceLocation));
185                }
186                catch (MalformedURLException ex) {
187                        // no URL -> treat as file path
188                        return new File(resourceLocation);
189                }
190        }
191
192        /**
193         * Resolve the given resource URL to a {@code java.io.File},
194         * i.e. to a file in the file system.
195         * @param resourceUrl the resource URL to resolve
196         * @return a corresponding File object
197         * @throws FileNotFoundException if the URL cannot be resolved to
198         * a file in the file system
199         */
200        public static File getFile(URL resourceUrl) throws FileNotFoundException {
201                return getFile(resourceUrl, "URL");
202        }
203
204        /**
205         * Resolve the given resource URL to a {@code java.io.File},
206         * i.e. to a file in the file system.
207         * @param resourceUrl the resource URL to resolve
208         * @param description a description of the original resource that
209         * the URL was created for (for example, a class path location)
210         * @return a corresponding File object
211         * @throws FileNotFoundException if the URL cannot be resolved to
212         * a file in the file system
213         */
214        public static File getFile(URL resourceUrl, String description) throws FileNotFoundException {
215                Assert.notNull(resourceUrl, "Resource URL must not be null");
216                if (!URL_PROTOCOL_FILE.equals(resourceUrl.getProtocol())) {
217                        throw new FileNotFoundException(
218                                        description + " cannot be resolved to absolute file path " +
219                                        "because it does not reside in the file system: " + resourceUrl);
220                }
221                try {
222                        return new File(toURI(resourceUrl).getSchemeSpecificPart());
223                }
224                catch (URISyntaxException ex) {
225                        // Fallback for URLs that are not valid URIs (should hardly ever happen).
226                        return new File(resourceUrl.getFile());
227                }
228        }
229
230        /**
231         * Resolve the given resource URI to a {@code java.io.File},
232         * i.e. to a file in the file system.
233         * @param resourceUri the resource URI to resolve
234         * @return a corresponding File object
235         * @throws FileNotFoundException if the URL cannot be resolved to
236         * a file in the file system
237         * @since 2.5
238         */
239        public static File getFile(URI resourceUri) throws FileNotFoundException {
240                return getFile(resourceUri, "URI");
241        }
242
243        /**
244         * Resolve the given resource URI to a {@code java.io.File},
245         * i.e. to a file in the file system.
246         * @param resourceUri the resource URI to resolve
247         * @param description a description of the original resource that
248         * the URI was created for (for example, a class path location)
249         * @return a corresponding File object
250         * @throws FileNotFoundException if the URL cannot be resolved to
251         * a file in the file system
252         * @since 2.5
253         */
254        public static File getFile(URI resourceUri, String description) throws FileNotFoundException {
255                Assert.notNull(resourceUri, "Resource URI must not be null");
256                if (!URL_PROTOCOL_FILE.equals(resourceUri.getScheme())) {
257                        throw new FileNotFoundException(
258                                        description + " cannot be resolved to absolute file path " +
259                                        "because it does not reside in the file system: " + resourceUri);
260                }
261                return new File(resourceUri.getSchemeSpecificPart());
262        }
263
264        /**
265         * Determine whether the given URL points to a resource in the file system,
266         * i.e. has protocol "file", "vfsfile" or "vfs".
267         * @param url the URL to check
268         * @return whether the URL has been identified as a file system URL
269         */
270        public static boolean isFileURL(URL url) {
271                String protocol = url.getProtocol();
272                return (URL_PROTOCOL_FILE.equals(protocol) || URL_PROTOCOL_VFSFILE.equals(protocol) ||
273                                URL_PROTOCOL_VFS.equals(protocol));
274        }
275
276        /**
277         * Determine whether the given URL points to a resource in a jar file.
278         * i.e. has protocol "jar", "war, ""zip", "vfszip" or "wsjar".
279         * @param url the URL to check
280         * @return whether the URL has been identified as a JAR URL
281         */
282        public static boolean isJarURL(URL url) {
283                String protocol = url.getProtocol();
284                return (URL_PROTOCOL_JAR.equals(protocol) || URL_PROTOCOL_WAR.equals(protocol) ||
285                                URL_PROTOCOL_ZIP.equals(protocol) || URL_PROTOCOL_VFSZIP.equals(protocol) ||
286                                URL_PROTOCOL_WSJAR.equals(protocol));
287        }
288
289        /**
290         * Determine whether the given URL points to a jar file itself,
291         * that is, has protocol "file" and ends with the ".jar" extension.
292         * @param url the URL to check
293         * @return whether the URL has been identified as a JAR file URL
294         * @since 4.1
295         */
296        public static boolean isJarFileURL(URL url) {
297                return (URL_PROTOCOL_FILE.equals(url.getProtocol()) &&
298                                url.getPath().toLowerCase().endsWith(JAR_FILE_EXTENSION));
299        }
300
301        /**
302         * Extract the URL for the actual jar file from the given URL
303         * (which may point to a resource in a jar file or to a jar file itself).
304         * @param jarUrl the original URL
305         * @return the URL for the actual jar file
306         * @throws MalformedURLException if no valid jar file URL could be extracted
307         */
308        public static URL extractJarFileURL(URL jarUrl) throws MalformedURLException {
309                String urlFile = jarUrl.getFile();
310                int separatorIndex = urlFile.indexOf(JAR_URL_SEPARATOR);
311                if (separatorIndex != -1) {
312                        String jarFile = urlFile.substring(0, separatorIndex);
313                        try {
314                                return new URL(jarFile);
315                        }
316                        catch (MalformedURLException ex) {
317                                // Probably no protocol in original jar URL, like "jar:C:/mypath/myjar.jar".
318                                // This usually indicates that the jar file resides in the file system.
319                                if (!jarFile.startsWith("/")) {
320                                        jarFile = "/" + jarFile;
321                                }
322                                return new URL(FILE_URL_PREFIX + jarFile);
323                        }
324                }
325                else {
326                        return jarUrl;
327                }
328        }
329
330        /**
331         * Extract the URL for the outermost archive from the given jar/war URL
332         * (which may point to a resource in a jar file or to a jar file itself).
333         * <p>In the case of a jar file nested within a war file, this will return
334         * a URL to the war file since that is the one resolvable in the file system.
335         * @param jarUrl the original URL
336         * @return the URL for the actual jar file
337         * @throws MalformedURLException if no valid jar file URL could be extracted
338         * @since 4.1.8
339         * @see #extractJarFileURL(URL)
340         */
341        public static URL extractArchiveURL(URL jarUrl) throws MalformedURLException {
342                String urlFile = jarUrl.getFile();
343
344                int endIndex = urlFile.indexOf(WAR_URL_SEPARATOR);
345                if (endIndex != -1) {
346                        // Tomcat's "war:file:...mywar.war*/WEB-INF/lib/myjar.jar!/myentry.txt"
347                        String warFile = urlFile.substring(0, endIndex);
348                        if (URL_PROTOCOL_WAR.equals(jarUrl.getProtocol())) {
349                                return new URL(warFile);
350                        }
351                        int startIndex = warFile.indexOf(WAR_URL_PREFIX);
352                        if (startIndex != -1) {
353                                return new URL(warFile.substring(startIndex + WAR_URL_PREFIX.length()));
354                        }
355                }
356
357                // Regular "jar:file:...myjar.jar!/myentry.txt"
358                return extractJarFileURL(jarUrl);
359        }
360
361        /**
362         * Create a URI instance for the given URL,
363         * replacing spaces with "%20" URI encoding first.
364         * @param url the URL to convert into a URI instance
365         * @return the URI instance
366         * @throws URISyntaxException if the URL wasn't a valid URI
367         * @see java.net.URL#toURI()
368         */
369        public static URI toURI(URL url) throws URISyntaxException {
370                return toURI(url.toString());
371        }
372
373        /**
374         * Create a URI instance for the given location String,
375         * replacing spaces with "%20" URI encoding first.
376         * @param location the location String to convert into a URI instance
377         * @return the URI instance
378         * @throws URISyntaxException if the location wasn't a valid URI
379         */
380        public static URI toURI(String location) throws URISyntaxException {
381                return new URI(StringUtils.replace(location, " ", "%20"));
382        }
383
384        /**
385         * Set the {@link URLConnection#setUseCaches "useCaches"} flag on the
386         * given connection, preferring {@code false} but leaving the
387         * flag at {@code true} for JNLP based resources.
388         * @param con the URLConnection to set the flag on
389         */
390        public static void useCachesIfNecessary(URLConnection con) {
391                con.setUseCaches(con.getClass().getSimpleName().startsWith("JNLP"));
392        }
393
394}