Author: echatellier Date: 2011-05-10 10:06:26 +0200 (Tue, 10 May 2011) New Revision: 2135 Url: http://nuiton.org/repositories/revision/nuiton-utils/2135 Log: #1316: find pattern resources in ClassLoader instead of URLClassLoader Modified: trunk/nuiton-utils/src/main/java/org/nuiton/util/Resource.java trunk/nuiton-utils/src/test/java/org/nuiton/util/ResourceTest.java Modified: trunk/nuiton-utils/src/main/java/org/nuiton/util/Resource.java =================================================================== --- trunk/nuiton-utils/src/main/java/org/nuiton/util/Resource.java 2011-05-04 14:17:42 UTC (rev 2134) +++ trunk/nuiton-utils/src/main/java/org/nuiton/util/Resource.java 2011-05-10 08:06:26 UTC (rev 2135) @@ -25,30 +25,36 @@ package org.nuiton.util; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import static org.nuiton.i18n.I18n._; -import javax.swing.ImageIcon; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.lang.String; import java.lang.reflect.Method; +import java.net.JarURLConnection; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; +import java.net.URLConnection; import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; import java.util.List; import java.util.jar.Attributes; +import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; +import javax.swing.ImageIcon; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + /** * Cette class permet de rechercher un fichier en indiquant son nom avec son * chemin. Cette librairie ira ensuite chercher ce fichier sur le système de @@ -618,4 +624,323 @@ } return false; } + + /** + * Return true is str is a pattern (contains * or ?). + * + * @param str str to test + * @since 2.3 + */ + static protected boolean isPattern(String str) { + return str.indexOf('*') != -1 || str.indexOf('?') != -1; + } + + /** + * Find pattern resouces in {@link ClassLoader#getSystemClassLoader()}. + * + * @param pattern java regex style pattern to find + * @return url list found + * @throws IOException + * @since 2.3 + */ + static public List<URL> getResources(String pattern) throws IOException { + return getResources(pattern, null); + } + + /** + * Find pattern resouces in classloader. + * + * @param pattern java regex style pattern to find + * @param classLoader classLoader + * @return url list found + * @throws IOException + * @since 2.3 + */ + static public List<URL> getResources(String pattern, ClassLoader classLoader) throws IOException { + if (classLoader == null) { + classLoader = ClassLoader.getSystemClassLoader(); + } + + List<URL> urlList = null; + + if (isPattern(pattern)) { + urlList = getPatternRessources(pattern, classLoader); + } + else { + urlList = new HashList<URL>(); + Enumeration<URL> resourceUrls = classLoader.getResources(pattern); + while (resourceUrls.hasMoreElements()) { + URL url = resourceUrls.nextElement(); + urlList.add(url); + } + } + + return urlList; + } + + /** + * + * @param pattern + * @param classLoader + * @return + * @throws IOException + * @since 2.3 + */ + static protected List<URL> getPatternRessources(String pattern, ClassLoader classLoader) throws IOException { + + List<URL> urlList = new HashList<URL>(); + + // get root directory to get URL in classpath + // for example : + // /WEB-INF/*.xml -> /WEB-INF/ + // /META-INF/persistence/*.xml -> /META-INF/persistence/ + int prefixEnd = pattern.indexOf(":") + 1; + int rootDirEnd = pattern.length(); + while (rootDirEnd > prefixEnd && isPattern(pattern.substring(prefixEnd, rootDirEnd))) { + rootDirEnd = pattern.lastIndexOf('/', rootDirEnd - 2) + 1; + } + if (rootDirEnd == 0) { + rootDirEnd = prefixEnd; + } + String rootDirPath = pattern.substring(0, rootDirEnd); + String subPattern = pattern.substring(rootDirPath.length()); + + Enumeration<URL> rootDirResources = classLoader.getResources(rootDirPath); + + while (rootDirResources.hasMoreElements()) { + URL rootDirResource = rootDirResources.nextElement(); + + if (isJarUrl(rootDirResource)) { + // cas ou le ichier du classLoader est un fichier jar + if (log.isDebugEnabled()) { + log.debug("jar to search " + rootDirResource); + } + urlList.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern)); + } + else { + urlList.addAll(doFindMatchingFileSystemResources(rootDirResource, subPattern)); + } + } + + return urlList; + } + + /** + * Test if an url detnoe a jar file. + * + * Code taken from spring source code : + * org.springframework.core.io.support.PathMatchingResourcePatternResolver + * + * @param url url to test + * @return true if url denote a jar file + * @since 2.3 + */ + public static boolean isJarUrl(URL url) { + String protocol = url.getProtocol(); + return "jar".equals(protocol) || + "zip".equals(protocol) || + "wsjar".equals(protocol); + } + + /** + * Find all resources in jar files that match the given location pattern + * via the Java Regex style Matcher. + * + * Code taken from spring source code : + * org.springframework.core.io.support.PathMatchingResourcePatternResolver + * + * @param rootDirResource the root directory as Resource + * @param subPattern the sub pattern to match (below the root directory) + * @return the Set of matching Resource instances + * @throws IOException in case of I/O errors + * @see JarURLConnection + * @since 2.3 + */ + protected static List<URL> doFindPathMatchingJarResources(URL rootDirResource, String subPattern) throws IOException { + + URLConnection con = rootDirResource.openConnection(); + JarFile jarFile; + String jarFileUrl; + String rootEntryPath; + boolean newJarFile = false; + + if (con instanceof JarURLConnection) { + // Should usually be the case for traditional JAR files. + JarURLConnection jarCon = (JarURLConnection) con; + jarCon.setUseCaches(false); + jarFile = jarCon.getJarFile(); + jarFileUrl = jarCon.getJarFileURL().toExternalForm(); + JarEntry jarEntry = jarCon.getJarEntry(); + rootEntryPath = (jarEntry != null ? jarEntry.getName() : ""); + } + else { + // No JarURLConnection -> need to resort to URL file parsing. + // We'll assume URLs of the format "jar:path!/entry", with the protocol + // being arbitrary as long as following the entry format. + // We'll also handle paths with and without leading "file:" prefix. + String urlFile = rootDirResource.getFile(); + int separatorIndex = urlFile.indexOf("!/"); + if (separatorIndex != -1) { + jarFileUrl = urlFile.substring(0, separatorIndex); + rootEntryPath = urlFile.substring(separatorIndex + "!/".length()); + //jarFile = getJarFile(jarFileUrl); + if (jarFileUrl.startsWith("file:")) { + jarFile = new JarFile(jarFileUrl.substring("file:".length())); + } + else { + jarFile = new JarFile(jarFileUrl); + } + } + else { + jarFile = new JarFile(urlFile); + jarFileUrl = urlFile; + rootEntryPath = ""; + } + newJarFile = true; + } + + try { + if (log.isDebugEnabled()) { + log.debug("Looking for matching resources in jar file [" + jarFileUrl + "]"); + } + if (!"".equals(rootEntryPath) && !rootEntryPath.endsWith("/")) { + // Root entry path must end with slash to allow for proper matching. + // The Sun JRE does not return a slash here, but BEA JRockit does. + rootEntryPath = rootEntryPath + "/"; + } + List<URL> result = new HashList<URL>(8); + for (Enumeration entries = jarFile.entries(); entries.hasMoreElements();) { + JarEntry entry = (JarEntry) entries.nextElement(); + String entryPath = entry.getName(); + if (entryPath.startsWith(rootEntryPath)) { + String relativePath = entryPath.substring(rootEntryPath.length()); + if (relativePath.matches(subPattern)) { + URL entryURL = new URL(rootDirResource, relativePath); + result.add(entryURL); + } + } + } + return result; + } + finally { + // Close jar file, but only if freshly obtained - + // not from JarURLConnection, which might cache the file reference. + if (newJarFile) { + jarFile.close(); + } + } + } + + /** + * Find all resources in the file system that match the given location pattern + * via the Java style matcher. + * + * Code taken from spring source code : + * org.springframework.core.io.support.PathMatchingResourcePatternResolver + * + * @param rootDirResource the root directory as Resource + * @param subPattern the sub pattern to match (below the root directory) + * @return the Set of matching Resource instances + * @throws IOException in case of I/O errors + * @see #retrieveMatchingFiles + * @since 2.3 + */ + protected static List<URL> doFindMatchingFileSystemResources(URL rootDirResource, String subPattern) + throws IOException { + + File rootDir; + try { + rootDir = new File(rootDirResource.toURI().getSchemeSpecificPart()); + rootDir = rootDir.getAbsoluteFile(); + } + catch (URISyntaxException ex) { + return Collections.emptyList(); + } + return retrieveMatchingFiles(rootDir, subPattern); + } + + /** + * Retrieve files that match the given path pattern, + * checking the given directory and its subdirectories. + * + * Code taken from spring source code : + * org.springframework.core.io.support.PathMatchingResourcePatternResolver + * + * @param rootDir the directory to start from + * @param pattern the pattern to match against, + * relative to the root directory + * @return the Set of matching File instances + * @throws IOException if directory contents could not be retrieved + * @since 2.3 + */ + protected static List<URL> retrieveMatchingFiles(File rootDir, String pattern) throws IOException { + if (!rootDir.exists()) { + return Collections.emptyList(); + } + if (!rootDir.isDirectory()) { + return Collections.emptyList(); + } + if (!rootDir.canRead()) { + return Collections.emptyList(); + } + //String fullPattern = StringUtils.replace(rootDir.getAbsolutePath(), File.separator, "/"); + String fullPattern = rootDir.getAbsolutePath().replace(File.separator, "/"); + if (!pattern.startsWith("/")) { + fullPattern += "/"; + } + //fullPattern = fullPattern + StringUtils.replace(pattern, File.separator, "/"); + fullPattern = fullPattern + pattern.replace(File.separator, "/"); + List<URL> result = new HashList<URL>(8); + doRetrieveMatchingFiles(fullPattern, rootDir, result); + return result; + } + + /** + * Recursively retrieve files that match the given pattern, + * adding them to the given result list. + * + * Code taken from spring source code : + * org.springframework.core.io.support.PathMatchingResourcePatternResolver + * + * @param fullPattern the pattern to match against, + * with preprended root directory path + * @param dir the current directory + * @param result the Set of matching File instances to add to + * @throws IOException if directory contents could not be retrieved + * @since 2.3 + */ + protected static void doRetrieveMatchingFiles(String fullPattern, File dir, List<URL> result) throws IOException { + if (log.isDebugEnabled()) { + log.debug("Searching directory [" + dir.getAbsolutePath() + + "] for files matching pattern [" + fullPattern + "]"); + } + File[] dirContents = dir.listFiles(); + if (dirContents == null) { + if (log.isWarnEnabled()) { + log.warn("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]"); + } + return; + } + for (File content : dirContents) { + //String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/"); + String currPath = content.getAbsolutePath().replace(File.separator, "/"); + //if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) { + if (content.isDirectory() && (currPath + "/").matches(fullPattern + ".*")) { + if (!content.canRead()) { + if (log.isDebugEnabled()) { + log.debug("Skipping subdirectory [" + dir.getAbsolutePath() + + "] because the application is not allowed to read the directory"); + } + } + else { + doRetrieveMatchingFiles(fullPattern, content, result); + } + } + //if (getPathMatcher().match(fullPattern, currPath)) { + if (currPath.matches(fullPattern)) { + result.add(content.toURI().toURL()); + } + } + } + } // Resource Modified: trunk/nuiton-utils/src/test/java/org/nuiton/util/ResourceTest.java =================================================================== --- trunk/nuiton-utils/src/test/java/org/nuiton/util/ResourceTest.java 2011-05-04 14:17:42 UTC (rev 2134) +++ trunk/nuiton-utils/src/test/java/org/nuiton/util/ResourceTest.java 2011-05-10 08:06:26 UTC (rev 2135) @@ -5,7 +5,7 @@ * $Id$ * $HeadURL$ * %% - * Copyright (C) 2004 - 2010 CodeLutin + * Copyright (C) 2004 - 2011 CodeLutin, Chatellier Eric * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as @@ -23,19 +23,6 @@ * #L% */ -/* * -* ResourceTest.java -* -* Created: Jul 29, 2004 -* -* @author Benjamin Poussin <poussin@codelutin.com> -* Copyright Code Lutin -* @version $Revision$ -* -* Mise a jour: $Date$ -* par : $Author$ -*/ - package org.nuiton.util; import java.io.File; @@ -45,9 +32,17 @@ import java.util.List; import org.junit.Assert; - import org.junit.Test; +/** + * Test class for {@link Resource}. + * + * @author chatellier + * @version $Revision$ + * + * Last update : $Date$ + * By : $Author$ + */ public class ResourceTest { // ResourceTest @Test @@ -132,6 +127,58 @@ Assert.assertFalse(Resource.isZip("")); Assert.assertFalse(Resource.isZip(null)); } + + @Test + public void testIsPattern() { + Assert.assertTrue(Resource.isPattern(".*.zip")); + Assert.assertTrue(Resource.isPattern(".?.zip")); + Assert.assertFalse(Resource.isPattern("toto.zip")); + } + /** + * Test de recherche de resource dans le classpath (jar). + * + * @throws Exception + */ + @Test + public void testGetResourcesJarClassPath() throws Exception { + // ce test peut echoué a chaque changement dans les dépendances + List<URL> urlsPattern = Resource.getResources("org/nuiton/util/Version.*Test\\.class"); + List<URL> urlsClass1 = Resource.getResources("org/nuiton/util/VersionTest.class"); + List<URL> urlsClass2 = Resource.getResources("org/nuiton/util/VersionUtilTest.class"); + Assert.assertEquals(2, urlsPattern.size()); + Assert.assertEquals(urlsPattern.size(), urlsClass1.size() + urlsClass2.size()); + + // test sans dossier de recherche (directement un pattern) + List<URL> urlsAllPattern = Resource.getResources("log4j\\..*"); + Assert.assertEquals(1, urlsAllPattern.size()); + } + + /** + * Test de recherche de resource dans le classpath (dossier hors jar). + * + * @throws Exception + */ + @Test + public void testGetResourcesFileSystemClassPath() throws Exception { + List<URL> urls = Resource.getResources("META-INF/services/.*apache.*"); + // i18n, resources, test resources + Assert.assertEquals(3, urls.size()); + } + + /** + * Test de recherche de resource dans le classpath (dossier hors jar) en + * utiliant le classloader de test. + * + * @throws Exception + */ + @Test + public void testGetTestResourcesClassPath() throws Exception { + List<URL> urls = Resource.getResources("org/nuiton/util/.*\\.txt"); + Assert.assertEquals(1, urls.size()); + + urls = Resource.getResources("org/nuiton/util/.*\\.txt", ResourceTest.class.getClassLoader()); + Assert.assertEquals(1, urls.size()); + } + } // ResourceTest -