001/* 002 * Copyright 2002-2015 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.web.context.support; 018 019import java.io.IOException; 020import java.util.Enumeration; 021import java.util.LinkedHashSet; 022import java.util.Set; 023import java.util.jar.JarEntry; 024import java.util.jar.JarFile; 025import javax.servlet.ServletContext; 026 027import org.apache.commons.logging.Log; 028import org.apache.commons.logging.LogFactory; 029 030import org.springframework.core.io.Resource; 031import org.springframework.core.io.ResourceLoader; 032import org.springframework.core.io.UrlResource; 033import org.springframework.core.io.support.PathMatchingResourcePatternResolver; 034import org.springframework.util.ResourceUtils; 035import org.springframework.util.StringUtils; 036 037/** 038 * ServletContext-aware subclass of {@link PathMatchingResourcePatternResolver}, 039 * able to find matching resources below the web application root directory 040 * via {@link ServletContext#getResourcePaths}. Falls back to the superclass' 041 * file system checking for other resources. 042 * 043 * @author Juergen Hoeller 044 * @since 1.1.2 045 */ 046public class ServletContextResourcePatternResolver extends PathMatchingResourcePatternResolver { 047 048 private static final Log logger = LogFactory.getLog(ServletContextResourcePatternResolver.class); 049 050 051 /** 052 * Create a new ServletContextResourcePatternResolver. 053 * @param servletContext the ServletContext to load resources with 054 * @see ServletContextResourceLoader#ServletContextResourceLoader(javax.servlet.ServletContext) 055 */ 056 public ServletContextResourcePatternResolver(ServletContext servletContext) { 057 super(new ServletContextResourceLoader(servletContext)); 058 } 059 060 /** 061 * Create a new ServletContextResourcePatternResolver. 062 * @param resourceLoader the ResourceLoader to load root directories and 063 * actual resources with 064 */ 065 public ServletContextResourcePatternResolver(ResourceLoader resourceLoader) { 066 super(resourceLoader); 067 } 068 069 070 /** 071 * Overridden version which checks for ServletContextResource 072 * and uses {@code ServletContext.getResourcePaths} to find 073 * matching resources below the web application root directory. 074 * In case of other resources, delegates to the superclass version. 075 * @see #doRetrieveMatchingServletContextResources 076 * @see ServletContextResource 077 * @see javax.servlet.ServletContext#getResourcePaths 078 */ 079 @Override 080 protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern) 081 throws IOException { 082 083 if (rootDirResource instanceof ServletContextResource) { 084 ServletContextResource scResource = (ServletContextResource) rootDirResource; 085 ServletContext sc = scResource.getServletContext(); 086 String fullPattern = scResource.getPath() + subPattern; 087 Set<Resource> result = new LinkedHashSet<Resource>(8); 088 doRetrieveMatchingServletContextResources(sc, fullPattern, scResource.getPath(), result); 089 return result; 090 } 091 else { 092 return super.doFindPathMatchingFileResources(rootDirResource, subPattern); 093 } 094 } 095 096 /** 097 * Recursively retrieve ServletContextResources that match the given pattern, 098 * adding them to the given result set. 099 * @param servletContext the ServletContext to work on 100 * @param fullPattern the pattern to match against, 101 * with preprended root directory path 102 * @param dir the current directory 103 * @param result the Set of matching Resources to add to 104 * @throws IOException if directory contents could not be retrieved 105 * @see ServletContextResource 106 * @see javax.servlet.ServletContext#getResourcePaths 107 */ 108 protected void doRetrieveMatchingServletContextResources( 109 ServletContext servletContext, String fullPattern, String dir, Set<Resource> result) 110 throws IOException { 111 112 Set<String> candidates = servletContext.getResourcePaths(dir); 113 if (candidates != null) { 114 boolean dirDepthNotFixed = fullPattern.contains("**"); 115 int jarFileSep = fullPattern.indexOf(ResourceUtils.JAR_URL_SEPARATOR); 116 String jarFilePath = null; 117 String pathInJarFile = null; 118 if (jarFileSep > 0 && jarFileSep + ResourceUtils.JAR_URL_SEPARATOR.length() < fullPattern.length()) { 119 jarFilePath = fullPattern.substring(0, jarFileSep); 120 pathInJarFile = fullPattern.substring(jarFileSep + ResourceUtils.JAR_URL_SEPARATOR.length()); 121 } 122 for (String currPath : candidates) { 123 if (!currPath.startsWith(dir)) { 124 // Returned resource path does not start with relative directory: 125 // assuming absolute path returned -> strip absolute path. 126 int dirIndex = currPath.indexOf(dir); 127 if (dirIndex != -1) { 128 currPath = currPath.substring(dirIndex); 129 } 130 } 131 if (currPath.endsWith("/") && (dirDepthNotFixed || StringUtils.countOccurrencesOf(currPath, "/") <= 132 StringUtils.countOccurrencesOf(fullPattern, "/"))) { 133 // Search subdirectories recursively: ServletContext.getResourcePaths 134 // only returns entries for one directory level. 135 doRetrieveMatchingServletContextResources(servletContext, fullPattern, currPath, result); 136 } 137 if (jarFilePath != null && getPathMatcher().match(jarFilePath, currPath)) { 138 // Base pattern matches a jar file - search for matching entries within. 139 String absoluteJarPath = servletContext.getRealPath(currPath); 140 if (absoluteJarPath != null) { 141 doRetrieveMatchingJarEntries(absoluteJarPath, pathInJarFile, result); 142 } 143 } 144 if (getPathMatcher().match(fullPattern, currPath)) { 145 result.add(new ServletContextResource(servletContext, currPath)); 146 } 147 } 148 } 149 } 150 151 /** 152 * Extract entries from the given jar by pattern. 153 * @param jarFilePath the path to the jar file 154 * @param entryPattern the pattern for jar entries to match 155 * @param result the Set of matching Resources to add to 156 */ 157 private void doRetrieveMatchingJarEntries(String jarFilePath, String entryPattern, Set<Resource> result) { 158 if (logger.isDebugEnabled()) { 159 logger.debug("Searching jar file [" + jarFilePath + "] for entries matching [" + entryPattern + "]"); 160 } 161 try { 162 JarFile jarFile = new JarFile(jarFilePath); 163 try { 164 for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) { 165 JarEntry entry = entries.nextElement(); 166 String entryPath = entry.getName(); 167 if (getPathMatcher().match(entryPattern, entryPath)) { 168 result.add(new UrlResource( 169 ResourceUtils.URL_PROTOCOL_JAR, 170 ResourceUtils.FILE_URL_PREFIX + jarFilePath + ResourceUtils.JAR_URL_SEPARATOR + entryPath)); 171 } 172 } 173 } 174 finally { 175 jarFile.close(); 176 } 177 } 178 catch (IOException ex) { 179 if (logger.isWarnEnabled()) { 180 logger.warn("Cannot search for matching resources in jar file [" + jarFilePath + 181 "] because the jar cannot be opened through the file system", ex); 182 } 183 } 184 } 185 186}