Return-Path: X-Original-To: apmail-struts-commits-archive@minotaur.apache.org Delivered-To: apmail-struts-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id DDDCF18F1A for ; Wed, 17 Jun 2015 21:09:14 +0000 (UTC) Received: (qmail 76617 invoked by uid 500); 17 Jun 2015 21:09:03 -0000 Delivered-To: apmail-struts-commits-archive@struts.apache.org Received: (qmail 76531 invoked by uid 500); 17 Jun 2015 21:09:03 -0000 Mailing-List: contact commits-help@struts.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@struts.apache.org Delivered-To: mailing list commits@struts.apache.org Received: (qmail 75091 invoked by uid 99); 17 Jun 2015 21:09:02 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 17 Jun 2015 21:09:02 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 792D8E3C53; Wed, 17 Jun 2015 21:09:02 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: lukaszlenart@apache.org To: commits@struts.apache.org Date: Wed, 17 Jun 2015 21:09:33 -0000 Message-Id: <55847c887e3e464089f6a4b8ce44de1f@git.apache.org> In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [33/57] [partial] struts git commit: Merges xwork packages into struts http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/finder/ResourceFinder.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/util/finder/ResourceFinder.java b/core/src/main/java/com/opensymphony/xwork2/util/finder/ResourceFinder.java new file mode 100644 index 0000000..c7f9fd1 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/util/finder/ResourceFinder.java @@ -0,0 +1,1124 @@ +/* + * Copyright 2002-2003,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.util.finder; + +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.*; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * @author David Blevins + * @version $Rev$ $Date$ + */ +public class ResourceFinder { + private static final Logger LOG = LogManager.getLogger(ResourceFinder.class); + + private final URL[] urls; + private final String path; + private final ClassLoaderInterface classLoaderInterface; + private final List resourcesNotLoaded = new ArrayList<>(); + + public ResourceFinder(URL... urls) { + this(null, new ClassLoaderInterfaceDelegate(Thread.currentThread().getContextClassLoader()), urls); + } + + public ResourceFinder(String path) { + this(path, new ClassLoaderInterfaceDelegate(Thread.currentThread().getContextClassLoader()), null); + } + + public ResourceFinder(String path, URL... urls) { + this(path, new ClassLoaderInterfaceDelegate(Thread.currentThread().getContextClassLoader()), urls); + } + + public ResourceFinder(String path, ClassLoaderInterface classLoaderInterface) { + this(path, classLoaderInterface, null); + } + + public ResourceFinder(String path, ClassLoaderInterface classLoaderInterface, URL... urls) { + path = StringUtils.trimToEmpty(path); + if (!StringUtils.endsWith(path, "/")) { + path += "/"; + } + this.path = path; + + this.classLoaderInterface = classLoaderInterface == null ? new ClassLoaderInterfaceDelegate(Thread.currentThread().getContextClassLoader()) : classLoaderInterface ; + + for (int i = 0; urls != null && i < urls.length; i++) { + URL url = urls[i]; + if (url == null || isDirectory(url) || "jar".equals(url.getProtocol())) { + continue; + } + try { + urls[i] = new URL("jar", "", -1, url.toString() + "!/"); + } catch (MalformedURLException e) { + } + } + this.urls = (urls == null || urls.length == 0)? null : urls; + } + + private static boolean isDirectory(URL url) { + String file = url.getFile(); + return (file.length() > 0 && file.charAt(file.length() - 1) == '/'); + } + + /** + * Returns a list of resources that could not be loaded in the last invoked findAvailable* or + * mapAvailable* methods. + *

+ * The list will only contain entries of resources that match the requirements + * of the last invoked findAvailable* or mapAvailable* methods, but were unable to be + * loaded and included in their results. + *

+ * The list returned is unmodifiable and the results of this method will change + * after each invocation of a findAvailable* or mapAvailable* methods. + *

+ * This method is not thread safe. + */ + public List getResourcesNotLoaded() { + return Collections.unmodifiableList(resourcesNotLoaded); + } + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // + // Find + // + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + public URL find(String uri) throws IOException { + String fullUri = path + uri; + + return getResource(fullUri); + } + + public List findAll(String uri) throws IOException { + String fullUri = path + uri; + + Enumeration resources = getResources(fullUri); + List list = new ArrayList<>(); + while (resources.hasMoreElements()) { + URL url = resources.nextElement(); + list.add(url); + } + return list; + } + + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // + // Find String + // + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + /** + * Reads the contents of the URL as a {@link String}'s and returns it. + * + * @param uri + * @return a stringified content of a resource + * @throws IOException if a resource pointed out by the uri param could not be find + * @see ClassLoader#getResource(String) + */ + public String findString(String uri) throws IOException { + String fullUri = path + uri; + + URL resource = getResource(fullUri); + if (resource == null) { + throw new IOException("Could not find a resource in : " + fullUri); + } + + return readContents(resource); + } + + /** + * Reads the contents of the found URLs as a list of {@link String}'s and returns them. + * + * @param uri + * @return a list of the content of each resource URL found + * @throws IOException if any of the found URLs are unable to be read. + */ + public List findAllStrings(String uri) throws IOException { + String fulluri = path + uri; + + List strings = new ArrayList<>(); + + Enumeration resources = getResources(fulluri); + while (resources.hasMoreElements()) { + URL url = resources.nextElement(); + String string = readContents(url); + strings.add(string); + } + return strings; + } + + /** + * Reads the contents of the found URLs as a Strings and returns them. + * Individual URLs that cannot be read are skipped and added to the + * list of 'resourcesNotLoaded' + * + * @param uri + * @return a list of the content of each resource URL found + * @throws IOException if classLoader.getResources throws an exception + */ + public List findAvailableStrings(String uri) throws IOException { + resourcesNotLoaded.clear(); + String fulluri = path + uri; + + List strings = new ArrayList<>(); + + Enumeration resources = getResources(fulluri); + while (resources.hasMoreElements()) { + URL url = resources.nextElement(); + try { + String string = readContents(url); + strings.add(string); + } catch (IOException notAvailable) { + resourcesNotLoaded.add(url.toExternalForm()); + } + } + return strings; + } + + /** + * Reads the contents of all non-directory URLs immediately under the specified + * location and returns them in a map keyed by the file name. + *

+ * Any URLs that cannot be read will cause an exception to be thrown. + *

+ * Example classpath: + *

+ * META-INF/serializables/one + * META-INF/serializables/two + * META-INF/serializables/three + * META-INF/serializables/four/foo.txt + *

+ * ResourceFinder finder = new ResourceFinder("META-INF/"); + * Map map = finder.mapAvailableStrings("serializables"); + * map.contains("one"); // true + * map.contains("two"); // true + * map.contains("three"); // true + * map.contains("four"); // false + * + * @param uri + * @return a list of the content of each resource URL found + * @throws IOException if any of the urls cannot be read + */ + public Map mapAllStrings(String uri) throws IOException { + Map strings = new HashMap<>(); + Map resourcesMap = getResourcesMap(uri); + for (Map.Entry entry : resourcesMap.entrySet()) { + String name = entry.getKey(); + URL url = entry.getValue(); + String value = readContents(url); + strings.put(name, value); + } + return strings; + } + + /** + * Reads the contents of all non-directory URLs immediately under the specified + * location and returns them in a map keyed by the file name. + *

+ * Individual URLs that cannot be read are skipped and added to the + * list of 'resourcesNotLoaded' + *

+ * Example classpath: + *

+ * META-INF/serializables/one + * META-INF/serializables/two # not readable + * META-INF/serializables/three + * META-INF/serializables/four/foo.txt + *

+ * ResourceFinder finder = new ResourceFinder("META-INF/"); + * Map map = finder.mapAvailableStrings("serializables"); + * map.contains("one"); // true + * map.contains("two"); // false + * map.contains("three"); // true + * map.contains("four"); // false + * + * @param uri + * @return a list of the content of each resource URL found + * @throws IOException if classLoader.getResources throws an exception + */ + public Map mapAvailableStrings(String uri) throws IOException { + resourcesNotLoaded.clear(); + Map strings = new HashMap<>(); + Map resourcesMap = getResourcesMap(uri); + for (Map.Entry entry : resourcesMap.entrySet()) { + String name = entry.getKey(); + URL url = entry.getValue(); + try { + String value = readContents(url); + strings.put(name, value); + } catch (IOException notAvailable) { + resourcesNotLoaded.add(url.toExternalForm()); + } + } + return strings; + } + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // + // Find Class + // + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + /** + * Executes {@link #findString(String)} assuming the contents URL found is the name of + * a class that should be loaded and returned. + * + * @param uri + * @return + * @throws IOException + * @throws ClassNotFoundException + */ + public Class findClass(String uri) throws IOException, ClassNotFoundException { + String className = findString(uri); + return (Class) classLoaderInterface.loadClass(className); + } + + /** + * Executes findAllStrings assuming the strings are + * the names of a classes that should be loaded and returned. + *

+ * Any URL or class that cannot be loaded will cause an exception to be thrown. + * + * @param uri + * @return + * @throws IOException + * @throws ClassNotFoundException + */ + public List findAllClasses(String uri) throws IOException, ClassNotFoundException { + List classes = new ArrayList<>(); + List strings = findAllStrings(uri); + for (String className : strings) { + Class clazz = classLoaderInterface.loadClass(className); + classes.add(clazz); + } + return classes; + } + + /** + * Executes findAvailableStrings assuming the strings are + * the names of a classes that should be loaded and returned. + *

+ * Any class that cannot be loaded will be skipped and placed in the + * 'resourcesNotLoaded' collection. + * + * @param uri + * @return + * @throws IOException if classLoader.getResources throws an exception + */ + public List findAvailableClasses(String uri) throws IOException { + resourcesNotLoaded.clear(); + List classes = new ArrayList<>(); + List strings = findAvailableStrings(uri); + for (String className : strings) { + try { + Class clazz = classLoaderInterface.loadClass(className); + classes.add(clazz); + } catch (Exception notAvailable) { + resourcesNotLoaded.add(className); + } + } + return classes; + } + + /** + * Executes mapAllStrings assuming the value of each entry in the + * map is the name of a class that should be loaded. + *

+ * Any class that cannot be loaded will be cause an exception to be thrown. + *

+ * Example classpath: + *

+ * META-INF/xmlparsers/xerces + * META-INF/xmlparsers/crimson + *

+ * ResourceFinder finder = new ResourceFinder("META-INF/"); + * Map map = finder.mapAvailableStrings("xmlparsers"); + * map.contains("xerces"); // true + * map.contains("crimson"); // true + * Class xercesClass = map.get("xerces"); + * Class crimsonClass = map.get("crimson"); + * + * @param uri + * @return + * @throws IOException + * @throws ClassNotFoundException + */ + public Map mapAllClasses(String uri) throws IOException, ClassNotFoundException { + Map classes = new HashMap<>(); + Map map = mapAllStrings(uri); + for (Map.Entry entry : map.entrySet()) { + String string = entry.getKey(); + String className = entry.getValue(); + Class clazz = classLoaderInterface.loadClass(className); + classes.put(string, clazz); + } + return classes; + } + + /** + * Executes mapAvailableStrings assuming the value of each entry in the + * map is the name of a class that should be loaded. + *

+ * Any class that cannot be loaded will be skipped and placed in the + * 'resourcesNotLoaded' collection. + *

+ * Example classpath: + *

+ * META-INF/xmlparsers/xerces + * META-INF/xmlparsers/crimson + *

+ * ResourceFinder finder = new ResourceFinder("META-INF/"); + * Map map = finder.mapAvailableStrings("xmlparsers"); + * map.contains("xerces"); // true + * map.contains("crimson"); // true + * Class xercesClass = map.get("xerces"); + * Class crimsonClass = map.get("crimson"); + * + * @param uri + * @return + * @throws IOException if classLoader.getResources throws an exception + */ + public Map mapAvailableClasses(String uri) throws IOException { + resourcesNotLoaded.clear(); + Map classes = new HashMap<>(); + Map map = mapAvailableStrings(uri); + for (Map.Entry entry : map.entrySet()) { + String string = entry.getKey(); + String className = entry.getValue(); + try { + Class clazz = classLoaderInterface.loadClass(className); + classes.put(string, clazz); + } catch (Exception notAvailable) { + resourcesNotLoaded.add(className); + } + } + return classes; + } + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // + // Find Implementation + // + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + /** + * Assumes the class specified points to a file in the classpath that contains + * the name of a class that implements or is a subclass of the specfied class. + *

+ * Any class that cannot be loaded will be cause an exception to be thrown. + *

+ * Example classpath: + *

+ * META-INF/java.io.InputStream # contains the classname org.acme.AcmeInputStream + * META-INF/java.io.OutputStream + *

+ * ResourceFinder finder = new ResourceFinder("META-INF/"); + * Class clazz = finder.findImplementation(java.io.InputStream.class); + * clazz.getName(); // returns "org.acme.AcmeInputStream" + * + * @param interfase a superclass or interface + * @return + * @throws IOException if the URL cannot be read + * @throws ClassNotFoundException if the class found is not loadable + * @throws ClassCastException if the class found is not assignable to the specified superclass or interface + */ + public Class findImplementation(Class interfase) throws IOException, ClassNotFoundException { + String className = findString(interfase.getName()); + Class impl = classLoaderInterface.loadClass(className); + if (!interfase.isAssignableFrom(impl)) { + throw new ClassCastException("Class not of type: " + interfase.getName()); + } + return impl; + } + + /** + * Assumes the class specified points to a file in the classpath that contains + * the name of a class that implements or is a subclass of the specfied class. + *

+ * Any class that cannot be loaded or assigned to the specified interface will be cause + * an exception to be thrown. + *

+ * Example classpath: + *

+ * META-INF/java.io.InputStream # contains the classname org.acme.AcmeInputStream + * META-INF/java.io.InputStream # contains the classname org.widget.NeatoInputStream + * META-INF/java.io.InputStream # contains the classname com.foo.BarInputStream + *

+ * ResourceFinder finder = new ResourceFinder("META-INF/"); + * List classes = finder.findAllImplementations(java.io.InputStream.class); + * classes.contains("org.acme.AcmeInputStream"); // true + * classes.contains("org.widget.NeatoInputStream"); // true + * classes.contains("com.foo.BarInputStream"); // true + * + * @param interfase a superclass or interface + * @return + * @throws IOException if the URL cannot be read + * @throws ClassNotFoundException if the class found is not loadable + * @throws ClassCastException if the class found is not assignable to the specified superclass or interface + */ + public List findAllImplementations(Class interfase) throws IOException, ClassNotFoundException { + List implementations = new ArrayList<>(); + List strings = findAllStrings(interfase.getName()); + for (String className : strings) { + Class impl = classLoaderInterface.loadClass(className); + if (!interfase.isAssignableFrom(impl)) { + throw new ClassCastException("Class not of type: " + interfase.getName()); + } + implementations.add(impl); + } + return implementations; + } + + /** + * Assumes the class specified points to a file in the classpath that contains + * the name of a class that implements or is a subclass of the specfied class. + *

+ * Any class that cannot be loaded or are not assignable to the specified class will be + * skipped and placed in the 'resourcesNotLoaded' collection. + *

+ * Example classpath: + *

+ * META-INF/java.io.InputStream # contains the classname org.acme.AcmeInputStream + * META-INF/java.io.InputStream # contains the classname org.widget.NeatoInputStream + * META-INF/java.io.InputStream # contains the classname com.foo.BarInputStream + *

+ * ResourceFinder finder = new ResourceFinder("META-INF/"); + * List classes = finder.findAllImplementations(java.io.InputStream.class); + * classes.contains("org.acme.AcmeInputStream"); // true + * classes.contains("org.widget.NeatoInputStream"); // true + * classes.contains("com.foo.BarInputStream"); // true + * + * @param interfase a superclass or interface + * @return + * @throws IOException if classLoader.getResources throws an exception + */ + public List findAvailableImplementations(Class interfase) throws IOException { + resourcesNotLoaded.clear(); + List implementations = new ArrayList<>(); + List strings = findAvailableStrings(interfase.getName()); + for (String className : strings) { + try { + Class impl = classLoaderInterface.loadClass(className); + if (interfase.isAssignableFrom(impl)) { + implementations.add(impl); + } else { + resourcesNotLoaded.add(className); + } + } catch (Exception notAvailable) { + resourcesNotLoaded.add(className); + } + } + return implementations; + } + + /** + * Assumes the class specified points to a directory in the classpath that holds files + * containing the name of a class that implements or is a subclass of the specfied class. + *

+ * Any class that cannot be loaded or assigned to the specified interface will be cause + * an exception to be thrown. + *

+ * Example classpath: + *

+ * META-INF/java.net.URLStreamHandler/jar + * META-INF/java.net.URLStreamHandler/file + * META-INF/java.net.URLStreamHandler/http + *

+ * ResourceFinder finder = new ResourceFinder("META-INF/"); + * Map map = finder.mapAllImplementations(java.net.URLStreamHandler.class); + * Class jarUrlHandler = map.get("jar"); + * Class fileUrlHandler = map.get("file"); + * Class httpUrlHandler = map.get("http"); + * + * @param interfase a superclass or interface + * @return + * @throws IOException if the URL cannot be read + * @throws ClassNotFoundException if the class found is not loadable + * @throws ClassCastException if the class found is not assignable to the specified superclass or interface + */ + public Map mapAllImplementations(Class interfase) throws IOException, ClassNotFoundException { + Map implementations = new HashMap<>(); + Map map = mapAllStrings(interfase.getName()); + for (Map.Entry entry : map.entrySet()) { + String string = entry.getKey(); + String className = entry.getValue(); + Class impl = classLoaderInterface.loadClass(className); + if (!interfase.isAssignableFrom(impl)) { + throw new ClassCastException("Class not of type: " + interfase.getName()); + } + implementations.put(string, impl); + } + return implementations; + } + + /** + * Assumes the class specified points to a directory in the classpath that holds files + * containing the name of a class that implements or is a subclass of the specfied class. + *

+ * Any class that cannot be loaded or are not assignable to the specified class will be + * skipped and placed in the 'resourcesNotLoaded' collection. + *

+ * Example classpath: + *

+ * META-INF/java.net.URLStreamHandler/jar + * META-INF/java.net.URLStreamHandler/file + * META-INF/java.net.URLStreamHandler/http + *

+ * ResourceFinder finder = new ResourceFinder("META-INF/"); + * Map map = finder.mapAllImplementations(java.net.URLStreamHandler.class); + * Class jarUrlHandler = map.get("jar"); + * Class fileUrlHandler = map.get("file"); + * Class httpUrlHandler = map.get("http"); + * + * @param interfase a superclass or interface + * @return + * @throws IOException if classLoader.getResources throws an exception + */ + public Map mapAvailableImplementations(Class interfase) throws IOException { + resourcesNotLoaded.clear(); + Map implementations = new HashMap<>(); + Map map = mapAvailableStrings(interfase.getName()); + for (Map.Entry entry : map.entrySet()) { + String string = entry.getKey(); + String className = entry.getValue(); + try { + Class impl = classLoaderInterface.loadClass(className); + if (interfase.isAssignableFrom(impl)) { + implementations.put(string, impl); + } else { + resourcesNotLoaded.add(className); + } + } catch (Exception notAvailable) { + resourcesNotLoaded.add(className); + } + } + return implementations; + } + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // + // Find Properties + // + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + /** + * Finds the corresponding resource and reads it in as a properties file + *

+ * Example classpath: + *

+ * META-INF/widget.properties + *

+ * ResourceFinder finder = new ResourceFinder("META-INF/"); + * Properties widgetProps = finder.findProperties("widget.properties"); + * + * @param uri + * @return + * @throws IOException if the URL cannot be read or is not in properties file format + */ + public Properties findProperties(String uri) throws IOException { + String fulluri = path + uri; + + URL resource = getResource(fulluri); + if (resource == null) { + throw new IOException("Could not find command in : " + fulluri); + } + + return loadProperties(resource); + } + + /** + * Finds the corresponding resources and reads them in as a properties files + *

+ * Any URL that cannot be read in as a properties file will cause an exception to be thrown. + *

+ * Example classpath: + *

+ * META-INF/app.properties + * META-INF/app.properties + * META-INF/app.properties + *

+ * ResourceFinder finder = new ResourceFinder("META-INF/"); + * List appProps = finder.findAllProperties("app.properties"); + * + * @param uri + * @return + * @throws IOException if the URL cannot be read or is not in properties file format + */ + public List findAllProperties(String uri) throws IOException { + String fulluri = path + uri; + + List properties = new ArrayList<>(); + + Enumeration resources = getResources(fulluri); + while (resources.hasMoreElements()) { + URL url = resources.nextElement(); + Properties props = loadProperties(url); + properties.add(props); + } + return properties; + } + + /** + * Finds the corresponding resources and reads them in as a properties files + *

+ * Any URL that cannot be read in as a properties file will be added to the + * 'resourcesNotLoaded' collection. + *

+ * Example classpath: + *

+ * META-INF/app.properties + * META-INF/app.properties + * META-INF/app.properties + *

+ * ResourceFinder finder = new ResourceFinder("META-INF/"); + * List appProps = finder.findAvailableProperties("app.properties"); + * + * @param uri + * @return + * @throws IOException if classLoader.getResources throws an exception + */ + public List findAvailableProperties(String uri) throws IOException { + resourcesNotLoaded.clear(); + String fulluri = path + uri; + + List properties = new ArrayList<>(); + + Enumeration resources = getResources(fulluri); + while (resources.hasMoreElements()) { + URL url = resources.nextElement(); + try { + Properties props = loadProperties(url); + properties.add(props); + } catch (Exception notAvailable) { + resourcesNotLoaded.add(url.toExternalForm()); + } + } + return properties; + } + + /** + * Finds the corresponding resources and reads them in as a properties files + *

+ * Any URL that cannot be read in as a properties file will cause an exception to be thrown. + *

+ * Example classpath: + *

+ * META-INF/jdbcDrivers/oracle.properties + * META-INF/jdbcDrivers/mysql.props + * META-INF/jdbcDrivers/derby + *

+ * ResourceFinder finder = new ResourceFinder("META-INF/"); + * List driversList = finder.findAvailableProperties("jdbcDrivers"); + * Properties oracleProps = driversList.get("oracle.properties"); + * Properties mysqlProps = driversList.get("mysql.props"); + * Properties derbyProps = driversList.get("derby"); + * + * @param uri + * @return + * @throws IOException if the URL cannot be read or is not in properties file format + */ + public Map mapAllProperties(String uri) throws IOException { + Map propertiesMap = new HashMap<>(); + Map map = getResourcesMap(uri); + for (Map.Entry entry : map.entrySet()) { + String string = entry.getKey(); + URL url = entry.getValue(); + Properties properties = loadProperties(url); + propertiesMap.put(string, properties); + } + return propertiesMap; + } + + /** + * Finds the corresponding resources and reads them in as a properties files + *

+ * Any URL that cannot be read in as a properties file will be added to the + * 'resourcesNotLoaded' collection. + *

+ * Example classpath: + *

+ * META-INF/jdbcDrivers/oracle.properties + * META-INF/jdbcDrivers/mysql.props + * META-INF/jdbcDrivers/derby + *

+ * ResourceFinder finder = new ResourceFinder("META-INF/"); + * List driversList = finder.findAvailableProperties("jdbcDrivers"); + * Properties oracleProps = driversList.get("oracle.properties"); + * Properties mysqlProps = driversList.get("mysql.props"); + * Properties derbyProps = driversList.get("derby"); + * + * @param uri + * @return + * @throws IOException if classLoader.getResources throws an exception + */ + public Map mapAvailableProperties(String uri) throws IOException { + resourcesNotLoaded.clear(); + Map propertiesMap = new HashMap<>(); + Map map = getResourcesMap(uri); + for (Map.Entry entry : map.entrySet()) { + String string = entry.getKey(); + URL url = entry.getValue(); + try { + Properties properties = loadProperties(url); + propertiesMap.put(string, properties); + } catch (Exception notAvailable) { + resourcesNotLoaded.add(url.toExternalForm()); + } + } + return propertiesMap; + } + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // + // Map Resources + // + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + public Map getResourcesMap(String uri) throws IOException { + String basePath = path + uri; + + Map resources = new HashMap<>(); + if (!basePath.endsWith("/")) { + basePath += "/"; + } + Enumeration urls = getResources(basePath); + + while (urls.hasMoreElements()) { + URL location = urls.nextElement(); + + try { + if ("jar".equals(location.getProtocol())) { + readJarEntries(location, basePath, resources); + } else if ("file".equals(location.getProtocol())) { + readDirectoryEntries(location, resources); + } + } catch (Exception e) { + LOG.debug("Got exception loading resources for {}", uri, e); + } + } + + return resources; + } + + /** + * Gets a list of subpckages from jars or dirs + */ + public Set findPackages(String uri) throws IOException { + String basePath = path + uri; + + Set resources = new HashSet<>(); + if (!basePath.endsWith("/")) { + basePath += "/"; + } + Enumeration urls = getResources(basePath); + + while (urls.hasMoreElements()) { + URL location = urls.nextElement(); + + try { + if ("jar".equals(location.getProtocol())) { + readJarDirectoryEntries(location, basePath, resources); + } else if ("file".equals(location.getProtocol())) { + readSubDirectories(new File(location.toURI()), uri, resources); + } + } catch (Exception e) { + LOG.debug("Got exception search for subpackages for {}", uri, e); + } + } + + return convertPathsToPackages(resources); + } + + /** + * Gets a list of subpckages from jars or dirs + */ + public Map> findPackagesMap(String uri) throws IOException { + String basePath = path + uri; + + if (!basePath.endsWith("/")) { + basePath += "/"; + } + Enumeration urls = getResources(basePath); + Map> result = new HashMap<>(); + + while (urls.hasMoreElements()) { + URL location = urls.nextElement(); + + try { + if ("jar".equals(location.getProtocol())) { + Set resources = new HashSet<>(); + readJarDirectoryEntries(location, basePath, resources); + result.put(location, convertPathsToPackages(resources)); + } else if ("file".equals(location.getProtocol())) { + Set resources = new HashSet<>(); + readSubDirectories(new File(location.toURI()), uri, resources); + result.put(location, convertPathsToPackages(resources)); + } + } catch (Exception e) { + LOG.debug("Got exception finding subpackages for {}", uri, e); + } + } + + return result; + } + + private Set convertPathsToPackages(Set resources) { + Set packageNames = new HashSet<>(resources.size()); + for(String resource : resources) { + packageNames.add(StringUtils.removeEnd(StringUtils.replace(resource, "/", "."), ".")); + } + + return packageNames; + } + + private static void readDirectoryEntries(URL location, Map resources) throws MalformedURLException { + File dir = new File(URLDecoder.decode(location.getPath())); + if (dir.isDirectory()) { + File[] files = dir.listFiles(); + for (File file : files) { + if (!file.isDirectory()) { + String name = file.getName(); + URL url = file.toURL(); + resources.put(name, url); + } + } + } + } + + /** + * Reads subdirectories of a file. The output is a list of subdirectories, relative to the basepath + */ + private static void readSubDirectories(File dir, String basePath, Set resources) throws MalformedURLException { + if (dir.isDirectory()) { + File[] files = dir.listFiles(); + for (File file : files) { + if (file.isDirectory()) { + String name = file.getName(); + String subName = StringUtils.removeEnd(basePath, "/") + "/" + name; + resources.add(subName); + readSubDirectories(file, subName, resources); + } + } + } + } + + private static void readJarEntries(URL location, String basePath, Map resources) throws IOException { + JarURLConnection conn = (JarURLConnection) location.openConnection(); + JarFile jarfile; + jarfile = conn.getJarFile(); + + Enumeration entries = jarfile.entries(); + while (entries != null && entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String name = entry.getName(); + + if (entry.isDirectory() || !name.startsWith(basePath) || name.length() == basePath.length()) { + continue; + } + + name = name.substring(basePath.length()); + + if (name.contains("/")) { + continue; + } + + URL resource = new URL(location, name); + resources.put(name, resource); + } + } + + //read directories in the jar that start with the basePath + private static void readJarDirectoryEntries(URL location, String basePath, Set resources) throws IOException { + JarURLConnection conn = (JarURLConnection) location.openConnection(); + JarFile jarfile; + jarfile = conn.getJarFile(); + + Enumeration entries = jarfile.entries(); + while (entries != null && entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String name = entry.getName(); + + if (entry.isDirectory() && StringUtils.startsWith(name, basePath)) { + resources.add(name); + } + } + } + + private Properties loadProperties(URL resource) throws IOException { + try (InputStream reader = new BufferedInputStream(resource.openStream())) { + Properties properties = new Properties(); + properties.load(reader); + + return properties; + } + } + + private String readContents(URL resource) throws IOException { + StringBuilder sb = new StringBuilder(); + + try (InputStream reader = new BufferedInputStream(resource.openStream())) { + int b = reader.read(); + while (b != -1) { + sb.append((char) b); + b = reader.read(); + } + + return sb.toString().trim(); + } + } + + private URL getResource(String fullUri) { + if (urls == null){ + return classLoaderInterface.getResource(fullUri); + } + return findResource(fullUri, urls); + } + + private Enumeration getResources(String fulluri) throws IOException { + if (urls == null) { + return classLoaderInterface.getResources(fulluri); + } + Vector resources = new Vector(); + for (URL url : urls) { + URL resource = findResource(fulluri, url); + if (resource != null){ + resources.add(resource); + } + } + return resources.elements(); + } + + private URL findResource(String resourceName, URL... search) { + for (int i = 0; i < search.length; i++) { + URL currentUrl = search[i]; + if (currentUrl == null) { + continue; + } + JarFile jarFile; + try { + String protocol = currentUrl.getProtocol(); + if ("jar".equals(protocol)) { + /* + * If the connection for currentUrl or resURL is + * used, getJarFile() will throw an exception if the + * entry doesn't exist. + */ + URL jarURL = ((JarURLConnection) currentUrl.openConnection()).getJarFileURL(); + try { + JarURLConnection juc = (JarURLConnection) new URL("jar", "", jarURL.toExternalForm() + "!/").openConnection(); + jarFile = juc.getJarFile(); + } catch (IOException e) { + // Don't look for this jar file again + search[i] = null; + throw e; + } + + String entryName; + if (currentUrl.getFile().endsWith("!/")) { + entryName = resourceName; + } else { + String file = currentUrl.getFile(); + int sepIdx = file.lastIndexOf("!/"); + if (sepIdx == -1) { + // Invalid URL, don't look here again + search[i] = null; + continue; + } + sepIdx += 2; + StringBuilder sb = new StringBuilder(file.length() - sepIdx + resourceName.length()); + sb.append(file.substring(sepIdx)); + sb.append(resourceName); + entryName = sb.toString(); + } + if ("META-INF/".equals(entryName) && jarFile.getEntry("META-INF/MANIFEST.MF") != null){ + return targetURL(currentUrl, "META-INF/MANIFEST.MF"); + } + if (jarFile.getEntry(entryName) != null) { + return targetURL(currentUrl, resourceName); + } + } else if ("file".equals(protocol)) { + String baseFile = currentUrl.getFile(); + String host = currentUrl.getHost(); + int hostLength = 0; + if (host != null) { + hostLength = host.length(); + } + StringBuilder buf = new StringBuilder(2 + hostLength + baseFile.length() + resourceName.length()); + + if (hostLength > 0) { + buf.append("//").append(host); + } + // baseFile always ends with '/' + buf.append(baseFile); + String fixedResName = resourceName; + // Do not create a UNC path, i.e. \\host + while (fixedResName.startsWith("/") || fixedResName.startsWith("\\")) { + fixedResName = fixedResName.substring(1); + } + buf.append(fixedResName); + String filename = buf.toString(); + File file = new File(filename); + File file2 = new File(URLDecoder.decode(filename)); + + if (file.exists() || file2.exists()) { + return targetURL(currentUrl, fixedResName); + } + } else { + URL resourceURL = targetURL(currentUrl, resourceName); + URLConnection urlConnection = resourceURL.openConnection(); + + try { + urlConnection.getInputStream().close(); + } catch (SecurityException e) { + return null; + } + // HTTP can return a stream on a non-existent file + // So check for the return code; + if (!"http".equals(resourceURL.getProtocol())) { + return resourceURL; + } + + int code = ((HttpURLConnection) urlConnection).getResponseCode(); + if (code >= 200 && code < 300) { + return resourceURL; + } + } + } catch (IOException | SecurityException e) { + // Keep iterating through the URL list + } + } + return null; + } + + private URL targetURL(URL base, String name) throws MalformedURLException { + StringBuilder sb = new StringBuilder(base.getFile().length() + name.length()); + sb.append(base.getFile()); + sb.append(name); + String file = sb.toString(); + return new URL(base.getProtocol(), base.getHost(), base.getPort(), file, null); + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/finder/Test.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/util/finder/Test.java b/core/src/main/java/com/opensymphony/xwork2/util/finder/Test.java new file mode 100644 index 0000000..b78d6f4 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/util/finder/Test.java @@ -0,0 +1,29 @@ +/* + * Copyright 2002-2003,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.util.finder; + +/** + * This is the testing interface that is used to accept or reject resources. + */ +public interface Test { + /** + * The test method. + * + * @param t The resource object to test. + * @return True if the resource should be accepted, false otherwise. + */ + public boolean test(T t); +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/finder/UrlSet.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/util/finder/UrlSet.java b/core/src/main/java/com/opensymphony/xwork2/util/finder/UrlSet.java new file mode 100644 index 0000000..34e0937 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/util/finder/UrlSet.java @@ -0,0 +1,265 @@ +/* + * Copyright 2002-2003,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.util.finder; + +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.*; + +/** + * Use with ClassFinder to filter the Urls to be scanned, example: + *

+ * UrlSet urlSet = new UrlSet(classLoader);
+ * urlSet = urlSet.exclude(ClassLoader.getSystemClassLoader().getParent());
+ * urlSet = urlSet.excludeJavaExtDirs();
+ * urlSet = urlSet.excludeJavaEndorsedDirs();
+ * urlSet = urlSet.excludeJavaHome();
+ * urlSet = urlSet.excludePaths(System.getProperty("sun.boot.class.path", ""));
+ * urlSet = urlSet.exclude(".*?/JavaVM.framework/.*");
+ * urlSet = urlSet.exclude(".*?/activemq-(core|ra)-[\\d.]+.jar(!/)?");
+ * 
+ * @author David Blevins + * @version $Rev$ $Date$ + */ +public class UrlSet { + + private static final Logger LOG = LogManager.getLogger(UrlSet.class); + + private final Map urls; + private Set protocols; + + private UrlSet() { + this.urls = new HashMap<>(); + } + + public UrlSet(ClassLoaderInterface classLoader) throws IOException { + this(); + load(getUrls(classLoader)); + } + + public UrlSet(ClassLoaderInterface classLoader, Set protocols) throws IOException { + this(); + this.protocols = protocols; + load(getUrls(classLoader, protocols)); + } + + public UrlSet(URL... urls){ + this(Arrays.asList(urls)); + } + /** + * Ignores all URLs that are not "jar" or "file" + * @param urls + */ + public UrlSet(Collection urls){ + this(); + load(urls); + } + + private UrlSet(Map urls) { + this.urls = urls; + } + + private void load(Collection urls){ + for (URL location : urls) { + try { + this.urls.put(location.toExternalForm(), location); + } catch (Exception e) { + LOG.warn("Cannot translate url to external form!", e); + } + } + } + + public UrlSet include(UrlSet urlSet){ + Map urls = new HashMap<>(this.urls); + urls.putAll(urlSet.urls); + return new UrlSet(urls); + } + + public UrlSet exclude(UrlSet urlSet) { + Map urls = new HashMap<>(this.urls); + Map parentUrls = urlSet.urls; + for (String url : parentUrls.keySet()) { + urls.remove(url); + } + return new UrlSet(urls); + } + + public UrlSet exclude(ClassLoaderInterface parent) throws IOException { + return exclude(new UrlSet(parent, this.protocols)); + } + + public UrlSet exclude(File file) throws MalformedURLException { + return exclude(relative(file)); + } + + public UrlSet exclude(String pattern) throws MalformedURLException { + return exclude(matching(pattern)); + } + + /** + * Calls excludePaths(System.getProperty("java.ext.dirs")) + * @return + * @throws MalformedURLException + */ + public UrlSet excludeJavaExtDirs() throws MalformedURLException { + return excludePaths(System.getProperty("java.ext.dirs", "")); + } + + /** + * Calls excludePaths(System.getProperty("java.endorsed.dirs")) + * + * @return + * @throws MalformedURLException + */ + public UrlSet excludeJavaEndorsedDirs() throws MalformedURLException { + return excludePaths(System.getProperty("java.endorsed.dirs", "")); + } + + public UrlSet excludeJavaHome() throws MalformedURLException { + String path = System.getProperty("java.home"); + if (path != null) { + File java = new File(path); + if (path.matches("/System/Library/Frameworks/JavaVM.framework/Versions/[^/]+/Home")){ + java = java.getParentFile(); + } + return exclude(java); + } else { + return this; + } + } + + public UrlSet excludePaths(String pathString) throws MalformedURLException { + String[] paths = pathString.split(File.pathSeparator); + UrlSet urlSet = this; + for (String path : paths) { + if (StringUtils.isNotEmpty(path)) { + File file = new File(path); + urlSet = urlSet.exclude(file); + } + } + return urlSet; + } + + public UrlSet matching(String pattern) { + Map urls = new HashMap<>(); + for (Map.Entry entry : this.urls.entrySet()) { + String url = entry.getKey(); + if (url.matches(pattern)){ + urls.put(url, entry.getValue()); + } + } + return new UrlSet(urls); + } + + /** + * Try to find a classes directory inside a war file add its normalized url to this set + */ + public UrlSet includeClassesUrl(ClassLoaderInterface classLoaderInterface, FileProtocolNormalizer normalizer) throws IOException { + Enumeration rootUrlEnumeration = classLoaderInterface.getResources(""); + while (rootUrlEnumeration.hasMoreElements()) { + URL url = rootUrlEnumeration.nextElement(); + String externalForm = StringUtils.removeEnd(url.toExternalForm(), "/"); + if (externalForm.endsWith(".war/WEB-INF/classes")) { + //if it is inside a war file, get the url to the file + externalForm = StringUtils.substringBefore(externalForm, "/WEB-INF/classes"); + URL warUrl = new URL(externalForm); + URL normalizedUrl = normalizer.normalizeToFileProtocol(warUrl); + URL finalUrl = ObjectUtils.defaultIfNull(normalizedUrl, warUrl); + + Map newUrls = new HashMap<>(this.urls); + if ("jar".equals(finalUrl.getProtocol()) || "file".equals(finalUrl.getProtocol())) { + newUrls.put(finalUrl.toExternalForm(), finalUrl); + } + return new UrlSet(newUrls); + } + } + + return this; + } + + public UrlSet relative(File file) throws MalformedURLException { + String urlPath = file.toURI().toURL().toExternalForm(); + Map urls = new HashMap<>(); + for (Map.Entry entry : this.urls.entrySet()) { + String url = entry.getKey(); + if (url.startsWith(urlPath) || url.startsWith("jar:"+urlPath)){ + urls.put(url, entry.getValue()); + } + } + return new UrlSet(urls); + } + + public List getUrls() { + return new ArrayList<>(urls.values()); + } + + private List getUrls(ClassLoaderInterface classLoader) throws IOException { + List list = new ArrayList<>(); + + //find jars + ArrayList urls = Collections.list(classLoader.getResources("META-INF")); + + for (URL url : urls) { + if ("jar".equalsIgnoreCase(url.getProtocol())) { + String externalForm = url.toExternalForm(); + //build a URL pointing to the jar, instead of the META-INF dir + url = new URL(StringUtils.substringBefore(externalForm, "META-INF")); + list.add(url); + } else { + LOG.debug("Ignoring URL [{}] because it is not a jar", url.toExternalForm()); + } + } + + //usually the "classes" dir + list.addAll(Collections.list(classLoader.getResources(""))); + return list; + } + + private List getUrls(ClassLoaderInterface classLoader, Set protocols) throws IOException { + + if (protocols == null) { + return getUrls(classLoader); + } + + List list = new ArrayList<>(); + + //find jars + ArrayList urls = Collections.list(classLoader.getResources("META-INF")); + + for (URL url : urls) { + if (protocols.contains(url.getProtocol())) { + String externalForm = url.toExternalForm(); + //build a URL pointing to the jar, instead of the META-INF dir + url = new URL(StringUtils.substringBefore(externalForm, "META-INF")); + list.add(url); + } else { + LOG.debug("Ignoring URL [{}] because it is not a valid protocol", url.toExternalForm()); + } + } + return list; + } + + public static interface FileProtocolNormalizer { + URL normalizeToFileProtocol(URL url); + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/fs/DefaultFileManager.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/util/fs/DefaultFileManager.java b/core/src/main/java/com/opensymphony/xwork2/util/fs/DefaultFileManager.java new file mode 100644 index 0000000..86fda9b --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/util/fs/DefaultFileManager.java @@ -0,0 +1,144 @@ +/* + * Copyright 2002-2003,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.util.fs; + +import com.opensymphony.xwork2.FileManager; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Default implementation of {@link FileManager} + */ +public class DefaultFileManager implements FileManager { + + private static Logger LOG = LogManager.getLogger(DefaultFileManager.class); + + private static final Pattern JAR_PATTERN = Pattern.compile("^(jar:|wsjar:|zip:|vfsfile:|code-source:)?(file:)?(.*?)(\\!/|\\.jar/)(.*)"); + private static final int JAR_FILE_PATH = 3; + + protected static Map files = Collections.synchronizedMap(new HashMap()); + + protected boolean reloadingConfigs = false; + + public DefaultFileManager() { + } + + public void setReloadingConfigs(boolean reloadingConfigs) { + this.reloadingConfigs = reloadingConfigs; + } + + public boolean fileNeedsReloading(URL fileUrl) { + return fileUrl != null && fileNeedsReloading(fileUrl.toString()); + } + + public boolean fileNeedsReloading(String fileName) { + Revision revision = files.get(fileName); + if (revision == null) { + // no revision yet and we keep the revision history, so + // return whether the file needs to be loaded for the first time + return reloadingConfigs; + } + return revision.needsReloading(); + } + + public InputStream loadFile(URL fileUrl) { + if (fileUrl == null) { + return null; + } + InputStream is = openFile(fileUrl); + monitorFile(fileUrl); + return is; + } + + private InputStream openFile(URL fileUrl) { + try { + InputStream is = fileUrl.openStream(); + if (is == null) { + throw new IllegalArgumentException("No file '" + fileUrl + "' found as a resource"); + } + return is; + } catch (IOException e) { + throw new IllegalArgumentException("No file '" + fileUrl + "' found as a resource"); + } + } + + public void monitorFile(URL fileUrl) { + String fileName = fileUrl.toString(); + Revision revision; + LOG.debug("Creating revision for URL: {}", fileName); + if (isJarURL(fileUrl)) { + revision = JarEntryRevision.build(fileUrl, this); + } else { + revision = FileRevision.build(fileUrl); + } + if (revision == null) { + files.put(fileName, Revision.build(fileUrl)); + } else { + files.put(fileName, revision); + } + } + + /** + * Check if given URL is matching Jar pattern for different servers + * + * @param fileUrl + * @return + */ + protected boolean isJarURL(URL fileUrl) { + Matcher jarMatcher = JAR_PATTERN.matcher(fileUrl.getPath()); + return jarMatcher.matches(); + } + + public URL normalizeToFileProtocol(URL url) { + String fileName = url.toExternalForm(); + Matcher jarMatcher = JAR_PATTERN.matcher(fileName); + try { + if (jarMatcher.matches()) { + String path = jarMatcher.group(JAR_FILE_PATH); + return new URL("file", "", path); + } else if ("file".equals(url.getProtocol())) { + return url; // it's already a file + } else { + LOG.warn("Could not normalize URL [{}] to file protocol!", url); + return null; + } + } catch (MalformedURLException e) { + LOG.warn("Error normalizing URL [{}] to file protocol!", url, e); + return null; + } + } + + public boolean support() { + return false; // allow other implementation to be used first + } + + public boolean internal() { + return true; + } + + public Collection getAllPhysicalUrls(URL url) throws IOException { + return Arrays.asList(url); + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/fs/DefaultFileManagerFactory.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/util/fs/DefaultFileManagerFactory.java b/core/src/main/java/com/opensymphony/xwork2/util/fs/DefaultFileManagerFactory.java new file mode 100644 index 0000000..c19385f --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/util/fs/DefaultFileManagerFactory.java @@ -0,0 +1,80 @@ +package com.opensymphony.xwork2.util.fs; + +import com.opensymphony.xwork2.FileManager; +import com.opensymphony.xwork2.FileManagerFactory; +import com.opensymphony.xwork2.XWorkConstants; +import com.opensymphony.xwork2.inject.Container; +import com.opensymphony.xwork2.inject.Inject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.HashSet; +import java.util.Set; + +/** + * Default implementation + */ +public class DefaultFileManagerFactory implements FileManagerFactory { + + private static final Logger LOG = LogManager.getLogger(DefaultFileManagerFactory.class); + + private boolean reloadingConfigs; + private FileManager systemFileManager; + private Container container; + + @Inject(value = "system") + public void setFileManager(FileManager fileManager) { + this.systemFileManager = fileManager; + } + + @Inject + public void setContainer(Container container) { + this.container = container; + } + + @Inject(value = XWorkConstants.RELOAD_XML_CONFIGURATION, required = false) + public void setReloadingConfigs(String reloadingConfigs) { + this.reloadingConfigs = Boolean.parseBoolean(reloadingConfigs); + } + + public FileManager getFileManager() { + FileManager fileManager = lookupFileManager(); + if (fileManager != null) { + LOG.debug("Using FileManager implementation [{}]", fileManager.getClass().getSimpleName()); + fileManager.setReloadingConfigs(reloadingConfigs); + return fileManager; + } + LOG.debug("Using default implementation of FileManager provided under name [system]: {}", systemFileManager.getClass().getSimpleName()); + systemFileManager.setReloadingConfigs(reloadingConfigs); + return systemFileManager; + } + + private FileManager lookupFileManager() { + Set names = container.getInstanceNames(FileManager.class); + LOG.debug("Found following implementations of FileManager interface: {}", names); + Set internals = new HashSet<>(); + Set users = new HashSet<>(); + for (String fmName : names) { + FileManager fm = container.getInstance(FileManager.class, fmName); + if (fm.internal()) { + internals.add(fm); + } else { + users.add(fm); + } + } + for (FileManager fm : users) { + if (fm.support()) { + LOG.debug("Using FileManager implementation [{}]", fm.getClass().getSimpleName()); + return fm; + } + } + LOG.debug("No user defined FileManager, looking up for internal implementations!"); + for (FileManager fm : internals) { + if (fm.support()) { + return fm; + } + } + return null; + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/fs/FileRevision.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/util/fs/FileRevision.java b/core/src/main/java/com/opensymphony/xwork2/util/fs/FileRevision.java new file mode 100644 index 0000000..f59d1d4 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/util/fs/FileRevision.java @@ -0,0 +1,52 @@ +package com.opensymphony.xwork2.util.fs; + +import java.io.File; +import java.net.URISyntaxException; +import java.net.URL; + +/** + * Represents file resource revision, used for file://* resources + */ +public class FileRevision extends Revision { + + private File file; + private long lastModified; + + public static Revision build(URL fileUrl) { + File file; + try { + if (fileUrl != null) { + file = new File(fileUrl.toURI()); + } else { + return null; + } + } catch (URISyntaxException e) { + file = new File(fileUrl.getPath()); + } catch (Throwable t) { + return null; + } + if (file.exists() && file.canRead()) { + long lastModified = file.lastModified(); + return new FileRevision(file, lastModified); + } + return null; + } + + private FileRevision(File file, long lastUpdated) { + if (file == null) { + throw new IllegalArgumentException("File cannot be null"); + } + + this.file = file; + this.lastModified = lastUpdated; + } + + public File getFile() { + return file; + } + + public boolean needsReloading() { + return this.lastModified < this.file.lastModified(); + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/fs/JarEntryRevision.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/util/fs/JarEntryRevision.java b/core/src/main/java/com/opensymphony/xwork2/util/fs/JarEntryRevision.java new file mode 100644 index 0000000..4b962af --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/util/fs/JarEntryRevision.java @@ -0,0 +1,82 @@ +package com.opensymphony.xwork2.util.fs; + +import com.opensymphony.xwork2.FileManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.commons.io.FileUtils; + +import java.io.IOException; +import java.net.URL; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; + +/** + * Represents jar resource revision, used for jar://* resource + */ +public class JarEntryRevision extends Revision { + + private static Logger LOG = LogManager.getLogger(JarEntryRevision.class); + + private static final String JAR_FILE_NAME_SEPARATOR = "!/"; + private static final String JAR_FILE_EXTENSION_END = ".jar/"; + + private String jarFileName; + private String fileNameInJar; + private long lastModified; + + public static Revision build(URL fileUrl, FileManager fileManager) { + // File within a Jar + // Find separator index of jar filename and filename within jar + String jarFileName = ""; + try { + String fileName = fileUrl.toString(); + int separatorIndex = fileName.indexOf(JAR_FILE_NAME_SEPARATOR); + if (separatorIndex == -1) { + separatorIndex = fileName.lastIndexOf(JAR_FILE_EXTENSION_END); + } + if (separatorIndex == -1) { + LOG.warn("Could not find end of jar file!"); + return null; + } + + // Split file name + jarFileName = fileName.substring(0, separatorIndex); + int index = separatorIndex + JAR_FILE_NAME_SEPARATOR.length(); + String fileNameInJar = fileName.substring(index).replaceAll("%20", " "); + + URL url = fileManager.normalizeToFileProtocol(fileUrl); + if (url != null) { + JarFile jarFile = new JarFile(FileUtils.toFile(url)); + ZipEntry entry = jarFile.getEntry(fileNameInJar); + return new JarEntryRevision(jarFileName, fileNameInJar, entry.getTime()); + } else { + return null; + } + } catch (Throwable e) { + LOG.warn("Could not create JarEntryRevision for [{}]!", jarFileName, e); + return null; + } + } + + private JarEntryRevision(String jarFileName, String fileNameInJar, long lastModified) { + if ((jarFileName == null) || (fileNameInJar == null)) { + throw new IllegalArgumentException("JarFileName and FileNameInJar cannot be null"); + } + this.jarFileName = jarFileName; + this.fileNameInJar = fileNameInJar; + this.lastModified = lastModified; + } + + public boolean needsReloading() { + ZipEntry entry; + try { + JarFile jarFile = new JarFile(this.jarFileName); + entry = jarFile.getEntry(this.fileNameInJar); + } catch (IOException e) { + entry = null; + } + + return entry != null && (lastModified < entry.getTime()); + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/fs/Revision.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/util/fs/Revision.java b/core/src/main/java/com/opensymphony/xwork2/util/fs/Revision.java new file mode 100644 index 0000000..8da65fc --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/util/fs/Revision.java @@ -0,0 +1,21 @@ +package com.opensymphony.xwork2.util.fs; + +import java.net.URL; + +/** + * Class represents common revision resource, should be used as default class when no other option exists + */ +public class Revision { + + protected Revision() { + } + + public boolean needsReloading() { + return false; + } + + public static Revision build(URL fileUrl) { + return new Revision(); + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/location/Locatable.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/util/location/Locatable.java b/core/src/main/java/com/opensymphony/xwork2/util/location/Locatable.java new file mode 100644 index 0000000..fc6f69c --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/util/location/Locatable.java @@ -0,0 +1,29 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.util.location; + +/** + * A interface that should be implemented by objects knowning their location (i.e. where they + * have been created from). + */ +public interface Locatable { + /** + * Get the location of this object + * + * @return the location + */ + public Location getLocation(); +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/location/LocatableProperties.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/util/location/LocatableProperties.java b/core/src/main/java/com/opensymphony/xwork2/util/location/LocatableProperties.java new file mode 100644 index 0000000..8df44f3 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/util/location/LocatableProperties.java @@ -0,0 +1,81 @@ +package com.opensymphony.xwork2.util.location; + +import com.opensymphony.xwork2.util.PropertiesReader; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +/** + * Properties implementation that remembers the location of each property. When + * loaded, a custom properties file parser is used to remember both the line number + * and preceeding comments for each property entry. + */ +public class LocatableProperties extends Properties implements Locatable { + + Location location; + Map propLocations; + + public LocatableProperties() { + this(Location.UNKNOWN); + } + + public LocatableProperties(Location loc) { + super(); + this.location = loc; + this.propLocations = new HashMap<>(); + } + + @Override + public void load(InputStream in) throws IOException { + Reader reader = new InputStreamReader(in); + PropertiesReader pr = new PropertiesReader(reader); + while (pr.nextProperty()) { + String name = pr.getPropertyName(); + String val = pr.getPropertyValue(); + int line = pr.getLineNumber(); + String desc = convertCommentsToString(pr.getCommentLines()); + + Location loc = new LocationImpl(desc, location.getURI(), line, 0); + setProperty(name, val, loc); + } + } + + String convertCommentsToString(List lines) { + StringBuilder sb = new StringBuilder(); + if (lines != null && !lines.isEmpty()) { + for (String line : lines) { + sb.append(line).append('\n'); + } + } + return sb.toString(); + } + + public Object setProperty(String key, String value, Object locationObj) { + Object obj = super.setProperty(key, value); + if (location != null) { + Location loc = LocationUtils.getLocation(locationObj); + propLocations.put(key, loc); + } + return obj; + } + + public Location getPropertyLocation(String key) { + Location loc = propLocations.get(key); + if (loc != null) { + return loc; + } else { + return Location.UNKNOWN; + } + } + + public Location getLocation() { + return location; + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/location/Located.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/util/location/Located.java b/core/src/main/java/com/opensymphony/xwork2/util/location/Located.java new file mode 100644 index 0000000..7c2c795 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/util/location/Located.java @@ -0,0 +1,42 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.util.location; + +/** + * Base class for location aware objects + */ +public abstract class Located implements Locatable { + + protected Location location; + + /** + * Get the location of this object + * + * @return the location + */ + public Location getLocation() { + return location; + } + + /** + * Set the location of this object + * + * @param loc the location + */ + public void setLocation(Location loc) { + this.location = loc; + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/location/Location.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/util/location/Location.java b/core/src/main/java/com/opensymphony/xwork2/util/location/Location.java new file mode 100644 index 0000000..7791d4f --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/util/location/Location.java @@ -0,0 +1,69 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.util.location; + +import java.util.List; + + +/** + * A location in a resource. The location is composed of the URI of the resource, and + * the line and column numbers within that resource (when available), along with a description. + *

+ * Locations are mostly provided by {@link Locatable}s objects. + */ +public interface Location { + + /** + * Constant for unknown locations. + */ + public static final Location UNKNOWN = LocationImpl.UNKNOWN; + + /** + * Get the description of this location + * + * @return the description (can be null) + */ + String getDescription(); + + /** + * Get the URI of this location + * + * @return the URI (null if unknown). + */ + String getURI(); + + /** + * Get the line number of this location + * + * @return the line number (-1 if unknown) + */ + int getLineNumber(); + + /** + * Get the column number of this location + * + * @return the column number (-1 if unknown) + */ + int getColumnNumber(); + + /** + * Gets a source code snippet with the default padding + * + * @param padding The amount of lines before and after the error to include + * @return A list of source lines + */ + List getSnippet(int padding); +}