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}