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.net.HttpURLConnection; 023import java.net.URI; 024import java.net.URL; 025import java.net.URLConnection; 026 027import org.springframework.util.ResourceUtils; 028 029/** 030 * Abstract base class for resources which resolve URLs into File references, 031 * such as {@link UrlResource} or {@link ClassPathResource}. 032 * 033 * <p>Detects the "file" protocol as well as the JBoss "vfs" protocol in URLs, 034 * resolving file system references accordingly. 035 * 036 * @author Juergen Hoeller 037 * @since 3.0 038 */ 039public abstract class AbstractFileResolvingResource extends AbstractResource { 040 041 /** 042 * This implementation returns a File reference for the underlying class path 043 * resource, provided that it refers to a file in the file system. 044 * @see org.springframework.util.ResourceUtils#getFile(java.net.URL, String) 045 */ 046 @Override 047 public File getFile() throws IOException { 048 URL url = getURL(); 049 if (url.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) { 050 return VfsResourceDelegate.getResource(url).getFile(); 051 } 052 return ResourceUtils.getFile(url, getDescription()); 053 } 054 055 /** 056 * This implementation determines the underlying File 057 * (or jar file, in case of a resource in a jar/zip). 058 */ 059 @Override 060 protected File getFileForLastModifiedCheck() throws IOException { 061 URL url = getURL(); 062 if (ResourceUtils.isJarURL(url)) { 063 URL actualUrl = ResourceUtils.extractArchiveURL(url); 064 if (actualUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) { 065 return VfsResourceDelegate.getResource(actualUrl).getFile(); 066 } 067 return ResourceUtils.getFile(actualUrl, "Jar URL"); 068 } 069 else { 070 return getFile(); 071 } 072 } 073 074 /** 075 * This implementation returns a File reference for the given URI-identified 076 * resource, provided that it refers to a file in the file system. 077 * @see org.springframework.util.ResourceUtils#getFile(java.net.URI, String) 078 */ 079 protected File getFile(URI uri) throws IOException { 080 if (uri.getScheme().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) { 081 return VfsResourceDelegate.getResource(uri).getFile(); 082 } 083 return ResourceUtils.getFile(uri, getDescription()); 084 } 085 086 087 @Override 088 public boolean exists() { 089 try { 090 URL url = getURL(); 091 if (ResourceUtils.isFileURL(url)) { 092 // Proceed with file system resolution 093 return getFile().exists(); 094 } 095 else { 096 // Try a URL connection content-length header 097 URLConnection con = url.openConnection(); 098 customizeConnection(con); 099 HttpURLConnection httpCon = 100 (con instanceof HttpURLConnection ? (HttpURLConnection) con : null); 101 if (httpCon != null) { 102 int code = httpCon.getResponseCode(); 103 if (code == HttpURLConnection.HTTP_OK) { 104 return true; 105 } 106 else if (code == HttpURLConnection.HTTP_NOT_FOUND) { 107 return false; 108 } 109 } 110 if (con.getContentLength() >= 0) { 111 return true; 112 } 113 if (httpCon != null) { 114 // No HTTP OK status, and no content-length header: give up 115 httpCon.disconnect(); 116 return false; 117 } 118 else { 119 // Fall back to stream existence: can we open the stream? 120 getInputStream().close(); 121 return true; 122 } 123 } 124 } 125 catch (IOException ex) { 126 return false; 127 } 128 } 129 130 @Override 131 public boolean isReadable() { 132 try { 133 URL url = getURL(); 134 if (ResourceUtils.isFileURL(url)) { 135 // Proceed with file system resolution 136 File file = getFile(); 137 return (file.canRead() && !file.isDirectory()); 138 } 139 else { 140 return true; 141 } 142 } 143 catch (IOException ex) { 144 return false; 145 } 146 } 147 148 @Override 149 public long contentLength() throws IOException { 150 URL url = getURL(); 151 if (ResourceUtils.isFileURL(url)) { 152 // Proceed with file system resolution 153 return getFile().length(); 154 } 155 else { 156 // Try a URL connection content-length header 157 URLConnection con = url.openConnection(); 158 customizeConnection(con); 159 return con.getContentLength(); 160 } 161 } 162 163 @Override 164 public long lastModified() throws IOException { 165 URL url = getURL(); 166 if (ResourceUtils.isFileURL(url) || ResourceUtils.isJarURL(url)) { 167 // Proceed with file system resolution 168 try { 169 return super.lastModified(); 170 } 171 catch (FileNotFoundException ex) { 172 // Defensively fall back to URL connection check instead 173 } 174 } 175 // Try a URL connection last-modified header 176 URLConnection con = url.openConnection(); 177 customizeConnection(con); 178 return con.getLastModified(); 179 } 180 181 /** 182 * Customize the given {@link URLConnection}, obtained in the course of an 183 * {@link #exists()}, {@link #contentLength()} or {@link #lastModified()} call. 184 * <p>Calls {@link ResourceUtils#useCachesIfNecessary(URLConnection)} and 185 * delegates to {@link #customizeConnection(HttpURLConnection)} if possible. 186 * Can be overridden in subclasses. 187 * @param con the URLConnection to customize 188 * @throws IOException if thrown from URLConnection methods 189 */ 190 protected void customizeConnection(URLConnection con) throws IOException { 191 ResourceUtils.useCachesIfNecessary(con); 192 if (con instanceof HttpURLConnection) { 193 customizeConnection((HttpURLConnection) con); 194 } 195 } 196 197 /** 198 * Customize the given {@link HttpURLConnection}, obtained in the course of an 199 * {@link #exists()}, {@link #contentLength()} or {@link #lastModified()} call. 200 * <p>Sets request method "HEAD" by default. Can be overridden in subclasses. 201 * @param con the HttpURLConnection to customize 202 * @throws IOException if thrown from HttpURLConnection methods 203 */ 204 protected void customizeConnection(HttpURLConnection con) throws IOException { 205 con.setRequestMethod("HEAD"); 206 } 207 208 209 /** 210 * Inner delegate class, avoiding a hard JBoss VFS API dependency at runtime. 211 */ 212 private static class VfsResourceDelegate { 213 214 public static Resource getResource(URL url) throws IOException { 215 return new VfsResource(VfsUtils.getRoot(url)); 216 } 217 218 public static Resource getResource(URI uri) throws IOException { 219 return new VfsResource(VfsUtils.getRoot(uri)); 220 } 221 } 222 223}