Return-Path: Delivered-To: apmail-incubator-click-commits-archive@minotaur.apache.org Received: (qmail 32961 invoked from network); 22 Dec 2009 02:59:31 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.3) by minotaur.apache.org with SMTP; 22 Dec 2009 02:59:31 -0000 Received: (qmail 27059 invoked by uid 500); 22 Dec 2009 02:59:28 -0000 Delivered-To: apmail-incubator-click-commits-archive@incubator.apache.org Received: (qmail 26855 invoked by uid 500); 22 Dec 2009 02:59:21 -0000 Mailing-List: contact click-commits-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: click-dev@incubator.apache.org Delivered-To: mailing list click-commits@incubator.apache.org Received: (qmail 26671 invoked by uid 99); 22 Dec 2009 02:59:14 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 22 Dec 2009 02:59:14 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=10.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 22 Dec 2009 02:59:05 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id 824D723889B9; Tue, 22 Dec 2009 02:58:43 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r893062 - /incubator/click/trunk/click/framework/src/org/apache/click/service/DeployUtils.java Date: Tue, 22 Dec 2009 02:58:43 -0000 To: click-commits@incubator.apache.org From: medgar@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20091222025843.824D723889B9@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: medgar Date: Tue Dec 22 02:58:42 2009 New Revision: 893062 URL: http://svn.apache.org/viewvc?rev=893062&view=rev Log: checkstyle updates Modified: incubator/click/trunk/click/framework/src/org/apache/click/service/DeployUtils.java Modified: incubator/click/trunk/click/framework/src/org/apache/click/service/DeployUtils.java URL: http://svn.apache.org/viewvc/incubator/click/trunk/click/framework/src/org/apache/click/service/DeployUtils.java?rev=893062&r1=893061&r2=893062&view=diff ============================================================================== --- incubator/click/trunk/click/framework/src/org/apache/click/service/DeployUtils.java (original) +++ incubator/click/trunk/click/framework/src/org/apache/click/service/DeployUtils.java Tue Dec 22 02:58:42 2009 @@ -1,552 +1,554 @@ -/* Copyright 2005-2006 Tim Fennell - * - * 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 org.apache.click.service; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FilenameFilter; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.jar.JarEntry; -import java.util.jar.JarInputStream; -import org.apache.click.util.ClickUtils; - -/** - *

ResolverUtil is used to locate classes that are available in the/a class path and meet - * arbitrary conditions. The two most common conditions are that a class implements/extends - * another class, or that is it annotated with a specific annotation. However, through the use - * of the {@link Test} class it is possible to search using arbitrary conditions.

- * - *

A ClassLoader is used to locate all locations (directories and jar files) in the class - * path that contain classes within certain packages, and then to load those classes and - * check them. By default the ClassLoader returned by - * {@code Thread.currentThread().getContextClassLoader()} is used, but this can be overridden - * by calling {@link #setClassLoader(ClassLoader)} prior to invoking any of the {@code find()} - * methods.

- * - *

General searches are initiated by calling the - * {@link #find(net.sourceforge.stripes.util.ResolverUtil.Test, String)} ()} method and supplying - * a package name and a Test instance. This will cause the named package and all sub-packages - * to be scanned for classes that meet the test. There are also utility methods for the common - * use cases of scanning multiple packages for extensions of particular classes, or classes - * annotated with a specific annotation.

- * - *

The standard usage pattern for the ResolverUtil class is as follows:

- * - *
- *ResolverUtil<ActionBean> resolver = new ResolverUtil<ActionBean>();
- *resolver.findImplementation(ActionBean.class, pkg1, pkg2);
- *resolver.find(new CustomTest(), pkg1);
- *resolver.find(new CustomTest(), pkg2);
- *Collection<ActionBean> beans = resolver.getClasses();
- *
- * - * This class was copied and adpated from the Stripes Framework - - * Stripes. - * - * @author Tim Fennell - */ -class DeployUtils { - - // -------------------------------------------------------------- Constants - - /** The magic header that indicates a JAR (ZIP) file. */ - private static final byte[] JAR_MAGIC = { 'P', 'K', 3, 4 }; - - // -------------------------------------------------------------- Variables - - /** The set of matches being accumulated. */ - private List matches = new ArrayList(); - - /** The log service to log output to. */ - private LogService logService; - - /** - * The ClassLoader to use when looking for classes. If null then the ClassLoader - * returned by Thread.currentThread().getContextClassLoader() will be used. - */ - private ClassLoader classloader; - - // ----------------------------------------------------------- Constructors - - /** - * Create a new DeployUtils instance. - * - * @param logService the logService to log output to - */ - public DeployUtils(LogService logService) { - this.logService = logService; - } - - // --------------------------------------------------------- Public Methods - - /** - * Provides access to the resources discovered so far. If no calls have been - * made to any of the {@code find()} methods, this list will be empty. - * - * @return the list of resources that have been discovered. - */ - public List getResources() { - return matches; - } - - /** - * Returns the classloader that will be used for scanning for classes. If no explicit - * ClassLoader has been set by the calling, the context class loader will be used. - * - * @return the ClassLoader that will be used to scan for classes - */ - public ClassLoader getClassLoader() { - return classloader == null ? Thread.currentThread().getContextClassLoader() : classloader; - } - - /** - * Sets an explicit ClassLoader that should be used when scanning for classes. If none - * is set then the context classloader will be used. - * - * @param classloader a ClassLoader to use when scanning for classes - */ - public void setClassLoader(ClassLoader classloader) { - this.classloader = classloader; - } - - /** - * Attemp to discover resources inside the given directory. Accumulated - * resources can be accessed by calling {@link #getResources()}. - * - * @param dirs one or more directories to scan (including sub-directories) - * for resources - * @return instance of DeployUtils allows for chaining calls - */ - public DeployUtils findResources(String... dirs) { - if (dirs == null) { - return this; - } - - Test test = new IsDeployable(); - for (String dir : dirs) { - find(test, dir); - } - - return this; - } - - /** - * Scans for classes starting at the package provided and descending into subpackages. - * Each class is offered up to the Test as it is discovered, and if the Test returns - * true the class is retained. Accumulated classes can be fetched by calling - * {@link #getClasses()}. - * - * @param test an instance of {@link Test} that will be used to filter classes - * @param packageName the name of the package from which to start scanning for - * classes, e.g. {@code net.sourceforge.stripes} - */ - public DeployUtils find(Test test, String packageName) { - String path = getPackagePath(packageName); - - try { - List urls = Collections.list(getClassLoader().getResources(path)); - for (URL url : urls) { - List children = listClassResources(url, path); - for (String child : children) { - addIfMatching(test, child); - } - } - } - catch (IOException ioe) { - logService.error("could not read package: " + packageName + " -- " + ioe); - } - - return this; - } - - // ------------------------------------------------------ Protected Methods - - /** - * Recursively list all resources under the given URL that appear to define a Java class. - * Matching resources will have a name that ends in ".class" and have a relative path such that - * each segment of the path is a valid Java identifier. The resource paths returned will be - * relative to the URL and begin with the specified path. - * - * @param url The URL of the parent resource to search. - * @param path The path with which each matching resource path must begin, relative to the URL. - * @return A list of matching resources. The list may be empty. - * @throws IOException - */ - protected List listClassResources(URL url, String path) throws IOException { - if (logService.isDebugEnabled()) { - logService.debug("listing classes in " + url); - } - - InputStream is = null; - try { - List resources = new ArrayList(); - - // First, try to find the URL of a JAR file containing the requested - // resource. If a JAR file is found, then we'll list child resources - // by reading the JAR. - URL jarUrl = findJarForResource(url, path); - if (jarUrl != null) { - // example jarUrl : jar:c:/dev/mylib.jar - is = jarUrl.openStream(); - resources = listClassResources(new JarInputStream(is), path); - - } else { - List children = new ArrayList(); - try { - if (isJar(url)) { - // example url : jar:c:/dev/mylib.jar/META-INF/resources - - // Some versions of JBoss VFS might give a JAR stream even - // if the resource referenced by the URL isn't actually a JAR - is = url.openStream(); - JarInputStream jarInput = new JarInputStream(is); - for (JarEntry entry; (entry = jarInput.getNextJarEntry()) != null;) { - if (logService.isTraceEnabled()) { - logService.trace("jar entry: " + entry.getName()); - } - - if (isRelevantResource(entry.getName())) { - children.add(entry.getName()); - } - } - } else { - // Some servlet containers allow reading from "directory" - // resources like text file, listing the child resources - // one per line. - is = url.openStream(); - - // There is the possibility that a file doesn't have an - // extension and would be seen as a directory. Guard against - // that by checking that the url is a file and adding it - // to the list of resources - File file = new File(url.getFile()); - if (file.isFile()) { - if (isRelevantResource(file.getName())) { - resources.add(path); - } - } else { - BufferedReader reader = new BufferedReader(new InputStreamReader(is)); - for (String line; (line = reader.readLine()) != null;) { - if (logService.isTraceEnabled()) { - logService.trace("reader entry: " + line); - } - if (isRelevantResource(line)) { - children.add(line); - } - } - } - } - } catch (FileNotFoundException e) { - /* - * For file URLs the openStream() call might fail, depending on the servlet - * container, because directories can't be opened for reading. If that happens, - * then list the directory directly instead. - */ - if ("file".equals(url.getProtocol())) { - File file = new File(url.getFile()); - if (file.isDirectory()) { - children = Arrays.asList(file.list(new FilenameFilter() { - public boolean accept(File dir, String name) { - return isRelevantResource(name); - } - })); - } - } else { - // No idea where the exception came from so log it - logService.error("could not deploy the resources from" - + " the url '" + url + "'. You will need to" - + " manually included resources from this url in" - + " your application."); - } - } - - // The URL prefix to use when recursively listing child resources - String prefix = url.toExternalForm(); - if (!prefix.endsWith("/")) - prefix = prefix + "/"; - - // Iterate over each immediate child, adding classes and recursing into directories - for (String child : children) { - String resourcePath = path + "/" + child; - if (child.indexOf(".") != -1) { - if (logService.isTraceEnabled()) { - logService.trace("found deployable resource: " + resourcePath); - } - resources.add(resourcePath); - } - else { - URL childUrl = new URL(prefix + child); - resources.addAll(listClassResources(childUrl, resourcePath)); - } - } - } - - return resources; - } - finally { - try { - is.close(); - } - catch (Exception e) { - } - } - } - - /** - * List the names of the entries in the given {@link JarInputStream} that begin with the - * specified {@code path}. Entries will match with or without a leading slash. - * - * @param jar The JAR input stream - * @param path The leading path to match - * @return The names of all the matching entries - * @throws IOException - */ - protected List listClassResources(JarInputStream jar, String path) throws IOException { - // Include the leading and trailing slash when matching names - if (!path.startsWith("/")) - path = "/" + path; - if (!path.endsWith("/")) - path = path + "/"; - - // Iterate over the entries and collect those that begin with the requested path - List resources = new ArrayList(); - for (JarEntry entry; (entry = jar.getNextJarEntry()) != null;) { - if (!entry.isDirectory()) { - // Add leading slash if it's missing - String name = entry.getName(); - if (!name.startsWith("/")) - name = "/" + name; - - // Check resource name - if (name.startsWith(path)) { - resources.add(name.substring(1)); // Trim leading slash - } - } - } - return resources; - } - - /** - * Attempts to deconstruct the given URL to find a JAR file containing the resource referenced - * by the URL. That is, assuming the URL references a JAR entry, this method will return a URL - * that references the JAR file containing the entry. If the JAR cannot be located, then this - * method returns null. - * - * @param url The URL of the JAR entry. - * @param path The path by which the URL was requested from the class loader. - * @return The URL of the JAR file, if one is found. Null if not. - * @throws MalformedURLException - */ - protected URL findJarForResource(URL url, String path) throws MalformedURLException { - if (logService.isTraceEnabled()) { - logService.trace("find jar url: " + url); - } - - // If the file part of the URL is itself a URL, then that URL probably points to the JAR - try { - for (;;) { - url = new URL(url.getFile()); - if (logService.isTraceEnabled()) { - logService.trace("inner url: " + url); - } - } - } catch (MalformedURLException e) { - // This will happen at some point and serves a break in the loop - } - - // Look for the .jar extension and chop off everything after that - StringBuilder jarUrl = new StringBuilder(url.toExternalForm()); - int index = jarUrl.lastIndexOf(".jar"); - if (index >= 0) { - jarUrl.setLength(index + 4); - if (logService.isTraceEnabled()) { - logService.trace("extracted jar url: " + jarUrl); - } - } else { - if (logService.isTraceEnabled()) { - logService.trace("not a jar: " + jarUrl); - } - return null; - } - - // Try to open and test it - try { - URL testUrl = new URL(jarUrl.toString()); - if (isJar(testUrl)) { - return testUrl; - } else { - // WebLogic fix: check if the URL's file exists in the filesystem. - if (logService.isTraceEnabled()) { - logService.trace("not a jar: " + jarUrl); - } - - jarUrl.replace(0, jarUrl.length(), testUrl.getFile()); - - // File name might be URL-encoded - File file = new File(ClickUtils.decodeURL(jarUrl.toString())); - if (file.exists()) { - if (logService.isTraceEnabled()) { - logService.trace("trying real file: " + file.getAbsolutePath()); - } - testUrl = file.toURI().toURL(); - if (isJar(testUrl)) { - return testUrl; - } - } - } - } - catch (MalformedURLException e) { - logService.warn("invalid jar url: " + e.getMessage()); - } - - if (logService.isTraceEnabled()) { - logService.trace("not a jar: " + jarUrl); - } - return null; - } - - /** - * Converts a Java package name to a path that can be looked up with a call to - * {@link ClassLoader#getResources(String)}. - * - * @param packageName The Java package name to convert to a path - */ - protected String getPackagePath(String packageName) { - return packageName == null ? null : packageName.replace('.', '/'); - } - - /** - * Returns true if the name of a resource (file or directory) is one that matters in the search - * for classes. Relevant resources would be class files themselves (file names that end with - * ".class") and directories that might be a Java package name segment (java identifiers). - * - * @param resourceName The resource name, without path information - */ - protected boolean isRelevantResource(String resourceName) { - return resourceName != null && !resourceName.equals(""); - } - - /** - * Returns true if the resource located at the given URL is a JAR file. - * - * @param url The URL of the resource to test. - */ - protected boolean isJar(URL url) { - return isJar(url, new byte[JAR_MAGIC.length]); - } - - /** - * Returns true if the resource located at the given URL is a JAR file. - * - * @param url The URL of the resource to test. - * @param buffer A buffer into which the first few bytes of the resource are read. The buffer - * must be at least the size of {@link #JAR_MAGIC}. (The same buffer may be reused - * for multiple calls as an optimization.) - */ - protected boolean isJar(URL url, byte[] buffer) { - InputStream is = null; - try { - is = url.openStream(); - is.read(buffer, 0, JAR_MAGIC.length); - if (Arrays.equals(buffer, JAR_MAGIC)) { - if (logService.isInfoEnabled()) { - logService.info("found jar: " + url); - } - return true; - } - } - catch (Exception e) { - // Failure to read the stream means this is not a JAR - } - finally { - try { - is.close(); - } - catch (Exception e) { - } - } - - return false; - } - - /** - * Add the class designated by the fully qualified class name provided to the set of - * resolved classes if and only if it is approved by the Test supplied. - * - * @param test the test used to determine if the class matches - * @param fqn the fully qualified name of a class - */ - @SuppressWarnings("unchecked") - protected void addIfMatching(Test test, String fqn) { - try { - if (test.matches(fqn) ) { - matches.add(fqn); - } - } - catch (Throwable t) { - logService.error("could not examine class '" + fqn + "'" + " due to a " - + t.getClass().getName() + " with message: " + t.getMessage()); - } - } - - // ---------------------------------------------------------- Inner classes - - /** - * A simple interface that specifies how to test classes to determine if they - * are to be included in the results produced by the ResolverUtil. - */ - static interface Test { - /** - * Will be called repeatedly with candidate classes. Must return True if a class - * is to be included in the results, false otherwise. - */ - boolean matches(String resource); - } - - /** - * This test matches deployable resources. - */ - static class IsDeployable implements Test { - - /** - * Default constructor. - */ - IsDeployable() { - } - - /** - * Return true if the given resource should be included in the results, - * false otherwise. - * - * @param resource the resource to be included in the results - * @return true if the resource should be included in the results, false - * otherwise - */ - @SuppressWarnings("unchecked") - public boolean matches(String resource) { - // If a resource is found, it must be deployed - return true; - } - } -} +/* Copyright 2005-2006 Tim Fennell + * + * 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 org.apache.click.service; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; +import org.apache.click.util.ClickUtils; + +/** + *

ResolverUtil is used to locate classes that are available in the/a class path and meet + * arbitrary conditions. The two most common conditions are that a class implements/extends + * another class, or that is it annotated with a specific annotation. However, through the use + * of the {@link Test} class it is possible to search using arbitrary conditions.

+ * + *

A ClassLoader is used to locate all locations (directories and jar files) in the class + * path that contain classes within certain packages, and then to load those classes and + * check them. By default the ClassLoader returned by + * {@code Thread.currentThread().getContextClassLoader()} is used, but this can be overridden + * by calling {@link #setClassLoader(ClassLoader)} prior to invoking any of the {@code find()} + * methods.

+ * + *

General searches are initiated by calling the + * {@link #find(net.sourceforge.stripes.util.ResolverUtil.Test, String)} ()} method and supplying + * a package name and a Test instance. This will cause the named package and all sub-packages + * to be scanned for classes that meet the test. There are also utility methods for the common + * use cases of scanning multiple packages for extensions of particular classes, or classes + * annotated with a specific annotation.

+ * + *

The standard usage pattern for the ResolverUtil class is as follows:

+ * + *
+ *ResolverUtil<ActionBean> resolver = new ResolverUtil<ActionBean>();
+ *resolver.findImplementation(ActionBean.class, pkg1, pkg2);
+ *resolver.find(new CustomTest(), pkg1);
+ *resolver.find(new CustomTest(), pkg2);
+ *Collection<ActionBean> beans = resolver.getClasses();
+ *
+ * + * This class was copied and adpated from the Stripes Framework - + * Stripes. + * + * @author Tim Fennell + */ +class DeployUtils { + + // -------------------------------------------------------------- Constants + + /** The magic header that indicates a JAR (ZIP) file. */ + private static final byte[] JAR_MAGIC = { 'P', 'K', 3, 4 }; + + // -------------------------------------------------------------- Variables + + /** The set of matches being accumulated. */ + private List matches = new ArrayList(); + + /** The log service to log output to. */ + private LogService logService; + + /** + * The ClassLoader to use when looking for classes. If null then the ClassLoader + * returned by Thread.currentThread().getContextClassLoader() will be used. + */ + private ClassLoader classloader; + + // ----------------------------------------------------------- Constructors + + /** + * Create a new DeployUtils instance. + * + * @param logService the logService to log output to + */ + public DeployUtils(LogService logService) { + this.logService = logService; + } + + // --------------------------------------------------------- Public Methods + + /** + * Provides access to the resources discovered so far. If no calls have been + * made to any of the {@code find()} methods, this list will be empty. + * + * @return the list of resources that have been discovered. + */ + public List getResources() { + return matches; + } + + /** + * Returns the classloader that will be used for scanning for classes. If no explicit + * ClassLoader has been set by the calling, the context class loader will be used. + * + * @return the ClassLoader that will be used to scan for classes + */ + public ClassLoader getClassLoader() { + return classloader == null ? Thread.currentThread().getContextClassLoader() : classloader; + } + + /** + * Sets an explicit ClassLoader that should be used when scanning for classes. If none + * is set then the context classloader will be used. + * + * @param classloader a ClassLoader to use when scanning for classes + */ + public void setClassLoader(ClassLoader classloader) { + this.classloader = classloader; + } + + /** + * Attemp to discover resources inside the given directory. Accumulated + * resources can be accessed by calling {@link #getResources()}. + * + * @param dirs one or more directories to scan (including sub-directories) + * for resources + * @return instance of DeployUtils allows for chaining calls + */ + public DeployUtils findResources(String... dirs) { + if (dirs == null) { + return this; + } + + Test test = new IsDeployable(); + for (String dir : dirs) { + find(test, dir); + } + + return this; + } + + /** + * Scans for classes starting at the package provided and descending into subpackages. + * Each class is offered up to the Test as it is discovered, and if the Test returns + * true the class is retained. Accumulated classes can be fetched by calling + * {@link #getClasses()}. + * + * @param test an instance of {@link Test} that will be used to filter classes + * @param packageName the name of the package from which to start scanning for + * classes, e.g. {@code net.sourceforge.stripes} + */ + public DeployUtils find(Test test, String packageName) { + String path = getPackagePath(packageName); + + try { + List urls = Collections.list(getClassLoader().getResources(path)); + for (URL url : urls) { + List children = listClassResources(url, path); + for (String child : children) { + addIfMatching(test, child); + } + } + + } catch (IOException ioe) { + logService.error("could not read package: " + packageName + " -- " + ioe); + } + + return this; + } + + // ------------------------------------------------------ Protected Methods + + /** + * Recursively list all resources under the given URL that appear to define a Java class. + * Matching resources will have a name that ends in ".class" and have a relative path such that + * each segment of the path is a valid Java identifier. The resource paths returned will be + * relative to the URL and begin with the specified path. + * + * @param url The URL of the parent resource to search. + * @param path The path with which each matching resource path must begin, relative to the URL. + * @return A list of matching resources. The list may be empty. + * @throws IOException + */ + protected List listClassResources(URL url, String path) throws IOException { + if (logService.isDebugEnabled()) { + logService.debug("listing classes in " + url); + } + + InputStream is = null; + try { + List resources = new ArrayList(); + + // First, try to find the URL of a JAR file containing the requested + // resource. If a JAR file is found, then we'll list child resources + // by reading the JAR. + URL jarUrl = findJarForResource(url, path); + if (jarUrl != null) { + // example jarUrl : jar:c:/dev/mylib.jar + is = jarUrl.openStream(); + resources = listClassResources(new JarInputStream(is), path); + + } else { + List children = new ArrayList(); + try { + if (isJar(url)) { + // example url : jar:c:/dev/mylib.jar/META-INF/resources + + // Some versions of JBoss VFS might give a JAR stream even + // if the resource referenced by the URL isn't actually a JAR + is = url.openStream(); + JarInputStream jarInput = new JarInputStream(is); + for (JarEntry entry; (entry = jarInput.getNextJarEntry()) != null;) { + if (logService.isTraceEnabled()) { + logService.trace("jar entry: " + entry.getName()); + } + + if (isRelevantResource(entry.getName())) { + children.add(entry.getName()); + } + } + } else { + // Some servlet containers allow reading from "directory" + // resources like text file, listing the child resources + // one per line. + is = url.openStream(); + + // There is the possibility that a file doesn't have an + // extension and would be seen as a directory. Guard against + // that by checking that the url is a file and adding it + // to the list of resources + File file = new File(url.getFile()); + if (file.isFile()) { + if (isRelevantResource(file.getName())) { + resources.add(path); + } + } else { + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + for (String line; (line = reader.readLine()) != null;) { + if (logService.isTraceEnabled()) { + logService.trace("reader entry: " + line); + } + if (isRelevantResource(line)) { + children.add(line); + } + } + } + } + + } catch (FileNotFoundException e) { + /* + * For file URLs the openStream() call might fail, depending on the servlet + * container, because directories can't be opened for reading. If that happens, + * then list the directory directly instead. + */ + if ("file".equals(url.getProtocol())) { + File file = new File(url.getFile()); + if (file.isDirectory()) { + children = Arrays.asList(file.list(new FilenameFilter() { + public boolean accept(File dir, String name) { + return isRelevantResource(name); + } + })); + } + } else { + // No idea where the exception came from so log it + logService.error("could not deploy the resources from" + + " the url '" + url + "'. You will need to" + + " manually included resources from this url in" + + " your application."); + } + } + + // The URL prefix to use when recursively listing child resources + String prefix = url.toExternalForm(); + if (!prefix.endsWith("/")) { + prefix = prefix + "/"; + } + + // Iterate over each immediate child, adding classes and recursing into directories + for (String child : children) { + String resourcePath = path + "/" + child; + if (child.indexOf(".") != -1) { + if (logService.isTraceEnabled()) { + logService.trace("found deployable resource: " + resourcePath); + } + resources.add(resourcePath); + + } else { + URL childUrl = new URL(prefix + child); + resources.addAll(listClassResources(childUrl, resourcePath)); + } + } + } + + return resources; + + } finally { + ClickUtils.close(is); + } + } + + /** + * List the names of the entries in the given {@link JarInputStream} that begin with the + * specified {@code path}. Entries will match with or without a leading slash. + * + * @param jar The JAR input stream + * @param path The leading path to match + * @return The names of all the matching entries + * @throws IOException + */ + protected List listClassResources(JarInputStream jar, String path) throws IOException { + // Include the leading and trailing slash when matching names + if (!path.startsWith("/")) { + path = "/" + path; + } + if (!path.endsWith("/")) { + path = path + "/"; + } + + // Iterate over the entries and collect those that begin with the requested path + List resources = new ArrayList(); + for (JarEntry entry; (entry = jar.getNextJarEntry()) != null;) { + if (!entry.isDirectory()) { + // Add leading slash if it's missing + String name = entry.getName(); + if (!name.startsWith("/")) { + name = "/" + name; + } + + // Check resource name + if (name.startsWith(path)) { + resources.add(name.substring(1)); // Trim leading slash + } + } + } + + return resources; + } + + /** + * Attempts to deconstruct the given URL to find a JAR file containing the resource referenced + * by the URL. That is, assuming the URL references a JAR entry, this method will return a URL + * that references the JAR file containing the entry. If the JAR cannot be located, then this + * method returns null. + * + * @param url The URL of the JAR entry. + * @param path The path by which the URL was requested from the class loader. + * @return The URL of the JAR file, if one is found. Null if not. + * @throws MalformedURLException + */ + protected URL findJarForResource(URL url, String path) throws MalformedURLException { + if (logService.isTraceEnabled()) { + logService.trace("find jar url: " + url); + } + + // If the file part of the URL is itself a URL, then that URL probably points to the JAR + try { + for (;;) { + url = new URL(url.getFile()); + if (logService.isTraceEnabled()) { + logService.trace("inner url: " + url); + } + } + + } catch (MalformedURLException e) { + // This will happen at some point and serves a break in the loop + } + + // Look for the .jar extension and chop off everything after that + StringBuilder jarUrl = new StringBuilder(url.toExternalForm()); + int index = jarUrl.lastIndexOf(".jar"); + if (index >= 0) { + jarUrl.setLength(index + 4); + if (logService.isTraceEnabled()) { + logService.trace("extracted jar url: " + jarUrl); + } + } else { + if (logService.isTraceEnabled()) { + logService.trace("not a jar: " + jarUrl); + } + return null; + } + + // Try to open and test it + try { + URL testUrl = new URL(jarUrl.toString()); + if (isJar(testUrl)) { + return testUrl; + + } else { + // WebLogic fix: check if the URL's file exists in the filesystem. + if (logService.isTraceEnabled()) { + logService.trace("not a jar: " + jarUrl); + } + + jarUrl.replace(0, jarUrl.length(), testUrl.getFile()); + + // File name might be URL-encoded + File file = new File(ClickUtils.decodeURL(jarUrl.toString())); + if (file.exists()) { + if (logService.isTraceEnabled()) { + logService.trace("trying real file: " + file.getAbsolutePath()); + } + testUrl = file.toURI().toURL(); + if (isJar(testUrl)) { + return testUrl; + } + } + } + + } catch (MalformedURLException e) { + logService.warn("invalid jar url: " + e.getMessage()); + } + + if (logService.isTraceEnabled()) { + logService.trace("not a jar: " + jarUrl); + } + return null; + } + + /** + * Converts a Java package name to a path that can be looked up with a call to + * {@link ClassLoader#getResources(String)}. + * + * @param packageName The Java package name to convert to a path + */ + protected String getPackagePath(String packageName) { + return packageName == null ? null : packageName.replace('.', '/'); + } + + /** + * Returns true if the name of a resource (file or directory) is one that matters in the search + * for classes. Relevant resources would be class files themselves (file names that end with + * ".class") and directories that might be a Java package name segment (java identifiers). + * + * @param resourceName The resource name, without path information + */ + protected boolean isRelevantResource(String resourceName) { + return resourceName != null && !resourceName.equals(""); + } + + /** + * Returns true if the resource located at the given URL is a JAR file. + * + * @param url The URL of the resource to test. + */ + protected boolean isJar(URL url) { + return isJar(url, new byte[JAR_MAGIC.length]); + } + + /** + * Returns true if the resource located at the given URL is a JAR file. + * + * @param url The URL of the resource to test. + * @param buffer A buffer into which the first few bytes of the resource are read. The buffer + * must be at least the size of {@link #JAR_MAGIC}. (The same buffer may be reused + * for multiple calls as an optimization.) + */ + protected boolean isJar(URL url, byte[] buffer) { + InputStream is = null; + try { + is = url.openStream(); + is.read(buffer, 0, JAR_MAGIC.length); + if (Arrays.equals(buffer, JAR_MAGIC)) { + if (logService.isInfoEnabled()) { + logService.info("found jar: " + url); + } + return true; + } + + } catch (Exception e) { + // Failure to read the stream means this is not a JAR + + } finally { + try { + is.close(); + } catch (Exception e) { + } + } + + return false; + } + + /** + * Add the class designated by the fully qualified class name provided to the set of + * resolved classes if and only if it is approved by the Test supplied. + * + * @param test the test used to determine if the class matches + * @param fqn the fully qualified name of a class + */ + @SuppressWarnings("unchecked") + protected void addIfMatching(Test test, String fqn) { + try { + if (test.matches(fqn)) { + matches.add(fqn); + } + } catch (Throwable t) { + logService.error("could not examine class '" + fqn + "'" + " due to a " + + t.getClass().getName() + " with message: " + t.getMessage()); + } + } + + // ---------------------------------------------------------- Inner classes + + /** + * A simple interface that specifies how to test classes to determine if they + * are to be included in the results produced by the ResolverUtil. + */ + static interface Test { + /** + * Will be called repeatedly with candidate classes. Must return True if a class + * is to be included in the results, false otherwise. + */ + boolean matches(String resource); + } + + /** + * This test matches deployable resources. + */ + static class IsDeployable implements Test { + + /** + * Default constructor. + */ + IsDeployable() { + } + + /** + * Return true if the given resource should be included in the results, + * false otherwise. + * + * @param resource the resource to be included in the results + * @return true if the resource should be included in the results, false + * otherwise + */ + @SuppressWarnings("unchecked") + public boolean matches(String resource) { + // If a resource is found, it must be deployed + return true; + } + } +}