Revision: 1336
          http://stripes.svn.sourceforge.net/stripes/?rev=1336&view=rev
Author:   bengunter
Date:     2010-11-12 16:25:10 +0000 (Fri, 12 Nov 2010)

Log Message:
-----------
Applied fix for STS-776, STS-773 and STS-775 from 1.5.x branch.

Modified Paths:
--------------
    
trunk/stripes/src/net/sourceforge/stripes/config/BootstrapPropertyResolver.java
    trunk/stripes/src/net/sourceforge/stripes/util/ResolverUtil.java

Added Paths:
-----------
    trunk/stripes/src/net/sourceforge/stripes/vfs/
    trunk/stripes/src/net/sourceforge/stripes/vfs/DefaultVFS.java
    trunk/stripes/src/net/sourceforge/stripes/vfs/JBoss6VFS.java
    trunk/stripes/src/net/sourceforge/stripes/vfs/VFS.java
    trunk/stripes/src/net/sourceforge/stripes/vfs/package.html

Modified: 
trunk/stripes/src/net/sourceforge/stripes/config/BootstrapPropertyResolver.java
===================================================================
--- 
trunk/stripes/src/net/sourceforge/stripes/config/BootstrapPropertyResolver.java 
    2010-11-12 15:50:31 UTC (rev 1335)
+++ 
trunk/stripes/src/net/sourceforge/stripes/config/BootstrapPropertyResolver.java 
    2010-11-12 16:25:10 UTC (rev 1336)
@@ -28,6 +28,7 @@
 import net.sourceforge.stripes.util.ReflectUtil;
 import net.sourceforge.stripes.util.ResolverUtil;
 import net.sourceforge.stripes.util.StringUtil;
+import net.sourceforge.stripes.vfs.VFS;
 
 /**
  * <p>Resolves configuration properties that are used to bootstrap the system. 
 Essentially this boils
@@ -50,12 +51,16 @@
     
     private FilterConfig filterConfig;
 
+    /** The Configuration Key for looking up the comma separated list of VFS 
classes. */
+    public static final String VFS_CLASSES = "VFS.Classes";
+
     /** The Configuration Key for looking up the comma separated list of 
extension packages. */
     public static final String PACKAGES = "Extension.Packages";
 
     /** Constructs a new BootstrapPropertyResolver with the given 
ServletConfig. */
     public BootstrapPropertyResolver(FilterConfig filterConfig) {
         setFilterConfig(filterConfig);
+        initVFS();
     }
 
     /** Stores a reference to the filter's FilterConfig object. */
@@ -68,6 +73,18 @@
         return this.filterConfig;
     }
 
+    /** Add {...@link VFS} implementations that are specified in the filter 
configuration. */
+    @SuppressWarnings("unchecked")
+    protected void initVFS() {
+        List<Class<?>> vfsImpls = getClassPropertyList(VFS_CLASSES);
+        for (Class<?> clazz : vfsImpls) {
+            if (!VFS.class.isAssignableFrom(clazz))
+                log.warn("Class ", clazz.getName(), " does not extend ", 
VFS.class.getName());
+            else
+                VFS.addImplClass((Class<? extends VFS>) clazz);
+        }
+    }
+
     /**
      * Fetches a configuration property in the manner described in the class 
level javadoc for
      * this class.

Modified: trunk/stripes/src/net/sourceforge/stripes/util/ResolverUtil.java
===================================================================
--- trunk/stripes/src/net/sourceforge/stripes/util/ResolverUtil.java    
2010-11-12 15:50:31 UTC (rev 1335)
+++ trunk/stripes/src/net/sourceforge/stripes/util/ResolverUtil.java    
2010-11-12 16:25:10 UTC (rev 1336)
@@ -14,26 +14,14 @@
  */
 package net.sourceforge.stripes.util;
 
-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.lang.annotation.Annotation;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
-import java.util.jar.JarEntry;
-import java.util.jar.JarInputStream;
-import java.util.regex.Pattern;
 
+import net.sourceforge.stripes.vfs.VFS;
+
 /**
  * <p>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
@@ -70,13 +58,6 @@
     /** An instance of Log to use for logging in this class. */
     private static final Log log = Log.getInstance(ResolverUtil.class);
 
-    /** The magic header that indicates a JAR (ZIP) file. */
-    private static final byte[] JAR_MAGIC = { 'P', 'K', 3, 4 };
-
-    /** Regular expression that matches a Java identifier. */
-    private static final Pattern JAVA_IDENTIFIER_PATTERN = Pattern
-            
.compile("\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*");
-
     /**
      * A simple interface that specifies how to test classes to determine if 
they
      * are to be included in the results produced by the ResolverUtil.
@@ -100,8 +81,7 @@
         public IsA(Class<?> parentType) { this.parent = parentType; }
 
         /** Returns true if type is assignable to the parent type supplied in 
the constructor. */
-        @SuppressWarnings("unchecked")
-               public boolean matches(Class type) {
+               public boolean matches(Class<?> type) {
             return type != null && parent.isAssignableFrom(type);
         }
 
@@ -121,8 +101,7 @@
         public AnnotatedWith(Class<? extends Annotation> annotation) { 
this.annotation = annotation; }
 
         /** Returns true if the type is annotated with the class provided to 
the constructor. */
-        @SuppressWarnings("unchecked")
-               public boolean matches(Class type) {
+               public boolean matches(Class<?> type) {
             return type != null && type.isAnnotationPresent(annotation);
         }
 
@@ -220,12 +199,10 @@
         String path = getPackagePath(packageName);
 
         try {
-            List<URL> urls = 
Collections.list(getClassLoader().getResources(path));
-            for (URL url : urls) {
-                List<String> children = listClassResources(url, path);
-                for (String child : children) {
+            List<String> children = VFS.getInstance().list(path);
+            for (String child : children) {
+                if (child.endsWith(".class"))
                     addIfMatching(test, child);
-                }
             }
         }
         catch (IOException ioe) {
@@ -236,218 +213,6 @@
     }
 
     /**
-     * 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<String> listClassResources(URL url, String path) throws 
IOException {
-        log.debug("Listing classes in ", url);
-
-        InputStream is = null;
-        try {
-            List<String> resources = new ArrayList<String>();
-
-            // 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) {
-                is = jarUrl.openStream();
-                resources = listClassResources(new JarInputStream(is), path);
-            }
-            else {
-                List<String> children = new ArrayList<String>();
-                try {
-                    if (isJar(url)) {
-                        // 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;) {
-                            log.trace("Jar entry: " + entry.getName());
-                            if (isRelevantResource(entry.getName())) {
-                                children.add(entry.getName());
-                            }
-                        }
-                    }
-                    else {
-                        // Some servlet containers allow reading from 
"directory" resources like a
-                        // text file, listing the child resources one per line.
-                        is = url.openStream();
-                        BufferedReader reader = new BufferedReader(new 
InputStreamReader(is));
-                        for (String line; (line = reader.readLine()) != null;) 
{
-                            log.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());
-                        log.trace("Listing directory ", 
file.getAbsolutePath());
-                        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 rethrow it
-                        throw e;
-                    }
-                }
-
-                // 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.endsWith(".class")) {
-                        log.trace("Found class file: ", 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<String> 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<String> resources = new ArrayList<String>();
-        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 file name
-                if (name.endsWith(".class") && name.startsWith(path)) {
-                    log.trace("Found class file: ", name);
-                    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 {
-        log.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());
-                log.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);
-            log.trace("Extracted JAR URL: ", jarUrl);
-        }
-        else {
-            log.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.
-                log.trace("Not a JAR: ", jarUrl);
-                jarUrl.replace(0, jarUrl.length(), testUrl.getFile());
-                File file = new File(jarUrl.toString());
-
-                // File name might be URL-encoded
-                if (!file.exists()) {
-                    file = new File(StringUtil.urlDecode(jarUrl.toString()));
-                }
-
-                if (file.exists()) {
-                    log.trace("Trying real file: ", file.getAbsolutePath());
-                    testUrl = file.toURI().toURL();
-                    if (isJar(testUrl)) {
-                        return testUrl;
-                    }
-                }
-            }
-        }
-        catch (MalformedURLException e) {
-            log.warn("Invalid JAR URL: ", jarUrl);
-        }
-
-        log.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)}.
      * 
@@ -458,60 +223,6 @@
     }
 
     /**
-     * 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.endsWith(".class") || JAVA_IDENTIFIER_PATTERN
-                        .matcher(resourceName).matches());
-    }
-
-    /**
-     * 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)) {
-                log.debug("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.
      *
@@ -525,7 +236,7 @@
             ClassLoader loader = getClassLoader();
             log.trace("Checking to see if class ", externalName, " matches 
criteria [", test, "]");
 
-            Class type = loader.loadClass(externalName);
+            Class<?> type = loader.loadClass(externalName);
             if (test.matches(type) ) {
                 matches.add( (Class<T>) type);
             }

Added: trunk/stripes/src/net/sourceforge/stripes/vfs/DefaultVFS.java
===================================================================
--- trunk/stripes/src/net/sourceforge/stripes/vfs/DefaultVFS.java               
                (rev 0)
+++ trunk/stripes/src/net/sourceforge/stripes/vfs/DefaultVFS.java       
2010-11-12 16:25:10 UTC (rev 1336)
@@ -0,0 +1,306 @@
+/* Copyright 2010 Ben Gunter
+ *
+ * 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 net.sourceforge.stripes.vfs;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+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.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+
+import net.sourceforge.stripes.util.Log;
+import net.sourceforge.stripes.util.StringUtil;
+
+/**
+ * A default implementation of {...@link VFS} that works for most application 
servers.
+ * 
+ * @author Ben Gunter
+ */
+public class DefaultVFS extends VFS {
+    private Log log = Log.getInstance(DefaultVFS.class);
+
+    /** The magic header that indicates a JAR (ZIP) file. */
+    private static final byte[] JAR_MAGIC = { 'P', 'K', 3, 4 };
+
+    @Override
+    public boolean isValid() {
+        return true;
+    }
+
+    @Override
+    public List<String> list(URL url, String path) throws IOException {
+        InputStream is = null;
+        try {
+            List<String> resources = new ArrayList<String>();
+
+            // 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);
+            if (jarUrl != null) {
+                is = jarUrl.openStream();
+                log.debug("Listing ", url);
+                resources = listResources(new JarInputStream(is), path);
+            }
+            else {
+                List<String> children = new ArrayList<String>();
+                try {
+                    if (isJar(url)) {
+                        // 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);
+                        log.debug("Listing ", url);
+                        for (JarEntry entry; (entry = 
jarInput.getNextJarEntry()) != null;) {
+                            log.trace("Jar entry: ", entry.getName());
+                            children.add(entry.getName());
+                        }
+                    }
+                    else {
+                        /*
+                         * Some servlet containers allow reading from 
directory resources like a
+                         * text file, listing the child resources one per 
line. However, there is no
+                         * way to differentiate between directory and file 
resources just by reading
+                         * them. To work around that, as each line is read, 
try to look it up via
+                         * the class loader as a child of the current 
resource. If any line fails
+                         * then we assume the current resource is not a 
directory.
+                         */
+                        is = url.openStream();
+                        BufferedReader reader = new BufferedReader(new 
InputStreamReader(is));
+                        List<String> lines = new ArrayList<String>();
+                        for (String line; (line = reader.readLine()) != null;) 
{
+                            log.trace("Reader entry: ", line);
+                            lines.add(line);
+                            if (getResources(path + "/" + line).isEmpty()) {
+                                lines.clear();
+                                break;
+                            }
+                        }
+
+                        if (!lines.isEmpty()) {
+                            log.debug("Listing ", url);
+                            children.addAll(lines);
+                        }
+                    }
+                }
+                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());
+                        log.trace("Listing directory ", 
file.getAbsolutePath());
+                        if (file.isDirectory()) {
+                            log.debug("Listing ", url);
+                            children = Arrays.asList(file.list());
+                        }
+                    }
+                    else {
+                        // No idea where the exception came from so rethrow it
+                        throw e;
+                    }
+                }
+
+                // The URL prefix to use when recursively listing child 
resources
+                String prefix = url.toExternalForm();
+                if (!prefix.endsWith("/"))
+                    prefix = prefix + "/";
+
+                // Iterate over immediate children, adding files and recursing 
into directories
+                for (String child : children) {
+                    String resourcePath = path + "/" + child;
+                    resources.add(resourcePath);
+                    URL childUrl = new URL(prefix + child);
+                    resources.addAll(list(childUrl, resourcePath));
+                }
+            }
+
+            return resources;
+        }
+        finally {
+            try {
+                if (is != null)
+                    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 If I/O errors occur
+     */
+    protected List<String> listResources(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<String> resources = new ArrayList<String>();
+        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 file name
+                if (name.startsWith(path)) {
+                    log.trace("Found resource: ", name);
+                    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.
+     * @return The URL of the JAR file, if one is found. Null if not.
+     * @throws MalformedURLException
+     */
+    protected URL findJarForResource(URL url) throws MalformedURLException {
+        log.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());
+                log.trace("Inner URL: ", url);
+            }
+        }
+        catch (MalformedURLException e) {
+            // This will happen at some point and serves as 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);
+            log.trace("Extracted JAR URL: ", jarUrl);
+        }
+        else {
+            log.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.
+                log.trace("Not a JAR: ", jarUrl);
+                jarUrl.replace(0, jarUrl.length(), testUrl.getFile());
+                File file = new File(jarUrl.toString());
+
+                // File name might be URL-encoded
+                if (!file.exists()) {
+                    file = new File(StringUtil.urlDecode(jarUrl.toString()));
+                }
+
+                if (file.exists()) {
+                    log.trace("Trying real file: ", file.getAbsolutePath());
+                    testUrl = file.toURI().toURL();
+                    if (isJar(testUrl)) {
+                        return testUrl;
+                    }
+                }
+            }
+        }
+        catch (MalformedURLException e) {
+            log.warn("Invalid JAR URL: ", jarUrl);
+        }
+
+        log.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 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)) {
+                log.debug("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;
+    }
+}

Added: trunk/stripes/src/net/sourceforge/stripes/vfs/JBoss6VFS.java
===================================================================
--- trunk/stripes/src/net/sourceforge/stripes/vfs/JBoss6VFS.java                
                (rev 0)
+++ trunk/stripes/src/net/sourceforge/stripes/vfs/JBoss6VFS.java        
2010-11-12 16:25:10 UTC (rev 1336)
@@ -0,0 +1,169 @@
+/* Copyright 2010 Ben Gunter
+ *
+ * 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 net.sourceforge.stripes.vfs;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import net.sourceforge.stripes.util.Log;
+
+/**
+ * A {...@link VFS} implementation that works with the VFS API provided by 
JBoss 6.
+ * 
+ * @author Ben Gunter
+ */
+public class JBoss6VFS extends net.sourceforge.stripes.vfs.VFS {
+    private static final Log log = Log.getInstance(JBoss6VFS.class);
+
+    /** A class that mimics a tiny subset of the JBoss VirtualFile class. */
+    static class VirtualFile {
+        static Class<?> VirtualFile;
+        static Method getPathNameRelativeTo, getChildrenRecursively;
+
+        Object virtualFile;
+
+        VirtualFile(Object virtualFile) {
+            this.virtualFile = virtualFile;
+        }
+
+        String getPathNameRelativeTo(VirtualFile parent) {
+            try {
+                return invoke(getPathNameRelativeTo, virtualFile, 
parent.virtualFile);
+            }
+            catch (IOException e) {
+                // This exception is not thrown by the called method
+                log.error("This should not be possible. 
VirtualFile.getPathNameRelativeTo() threw IOException.");
+                return null;
+            }
+        }
+
+        List<VirtualFile> getChildren() throws IOException {
+            List<?> objects = invoke(getChildrenRecursively, virtualFile);
+            List<VirtualFile> children = new 
ArrayList<VirtualFile>(objects.size());
+            for (Object object : objects) {
+                children.add(new VirtualFile(object));
+            }
+            return children;
+        }
+    }
+
+    /** A class that mimics a tiny subset of the JBoss VFS class. */
+    static class VFS {
+        static Class<?> VFS;
+        static Method getChild;
+
+        static VirtualFile getChild(URL url) throws IOException {
+            Object o = invoke(getChild, VFS, url);
+            return o == null ? null : new VirtualFile(o);
+        }
+    }
+
+    /** Flag that indicates if this VFS is valid for the current environment. 
*/
+    private static Boolean valid;
+
+    /** Find all the classes and methods that are required to access the JBoss 
6 VFS. */
+    protected static synchronized void initialize() {
+        if (valid == null) {
+            // Assume valid. It will get flipped later if something goes wrong.
+            valid = true;
+
+            // Look up and verify required classes
+            VFS.VFS = checkNotNull(getClass("org.jboss.vfs.VFS"));
+            VirtualFile.VirtualFile = 
checkNotNull(getClass("org.jboss.vfs.VirtualFile"));
+
+            // Look up and verify required methods
+            VFS.getChild = checkNotNull(getMethod(VFS.VFS, "getChild", 
URL.class));
+            VirtualFile.getChildrenRecursively = 
checkNotNull(getMethod(VirtualFile.VirtualFile,
+                    "getChildrenRecursively"));
+            VirtualFile.getPathNameRelativeTo = 
checkNotNull(getMethod(VirtualFile.VirtualFile,
+                    "getPathNameRelativeTo", VirtualFile.VirtualFile));
+
+            // Verify that the API has not changed
+            checkReturnType(VFS.getChild, VirtualFile.VirtualFile);
+            checkReturnType(VirtualFile.getChildrenRecursively, List.class);
+            checkReturnType(VirtualFile.getPathNameRelativeTo, String.class);
+        }
+    }
+
+    /**
+     * Verifies that the provided object reference is null. If it is null, 
then this VFS is marked
+     * as invalid for the current environment.
+     * 
+     * @param objects The object references to check for null.
+     */
+    protected static <T> T checkNotNull(T object) {
+        if (object == null)
+            setInvalid();
+        return object;
+    }
+
+    /**
+     * Verifies that the return type of a method is what it is expected to be. 
If it is not, then
+     * this VFS is marked as invalid for the current environment.
+     * 
+     * @param method The method whose return type is to be checked.
+     * @param expected A type to which the method's return type must be 
assignable.
+     * @see Class#isAssignableFrom(Class)
+     */
+    protected static void checkReturnType(Method method, Class<?> expected) {
+        if (method != null && 
!expected.isAssignableFrom(method.getReturnType())) {
+            log.warn("Method ", method.getClass().getName(), ".", 
method.getName(),
+                    "(..) should return ", expected.getName(), " but returns 
", //
+                    method.getReturnType().getName(), " instead.");
+            setInvalid();
+        }
+    }
+
+    /** Mark this {...@link VFS} as invalid for the current environment. */
+    protected static void setInvalid() {
+        if (JBoss6VFS.valid != null && JBoss6VFS.valid) {
+            log.debug("JBoss 6 VFS API is not available in this environment.");
+            JBoss6VFS.valid = false;
+        }
+    }
+
+    static {
+        initialize();
+    }
+
+    @Override
+    public boolean isValid() {
+        return valid;
+    }
+
+    @Override
+    public List<String> list(URL url, String path) throws IOException {
+        VirtualFile directory;
+        directory = VFS.getChild(url);
+        if (directory == null)
+            return Collections.emptyList();
+
+        if (!path.endsWith("/"))
+            path += "/";
+
+        List<VirtualFile> children = directory.getChildren();
+        List<String> names = new ArrayList<String>(children.size());
+        for (VirtualFile vf : children) {
+            String relative = vf.getPathNameRelativeTo(directory);
+            names.add(path + relative);
+        }
+
+        return names;
+    }
+}

Added: trunk/stripes/src/net/sourceforge/stripes/vfs/VFS.java
===================================================================
--- trunk/stripes/src/net/sourceforge/stripes/vfs/VFS.java                      
        (rev 0)
+++ trunk/stripes/src/net/sourceforge/stripes/vfs/VFS.java      2010-11-12 
16:25:10 UTC (rev 1336)
@@ -0,0 +1,206 @@
+/* Copyright 2010 Ben Gunter
+ *
+ * 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 net.sourceforge.stripes.vfs;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import net.sourceforge.stripes.exception.StripesRuntimeException;
+import net.sourceforge.stripes.util.Log;
+import net.sourceforge.stripes.util.ReflectUtil;
+
+/**
+ * Provides a very simple API for accessing resources within an application 
server.
+ * 
+ * @author Ben Gunter
+ */
+public abstract class VFS {
+    private static final Log log = Log.getInstance(VFS.class);
+
+    /** The built-in implementations. */
+    public static final Class<?>[] IMPLEMENTATIONS = { JBoss6VFS.class, 
DefaultVFS.class };
+
+    /** The list to which implementations are added by {...@link 
#addImplClass(String)}. */
+    public static final List<Class<? extends VFS>> USER_IMPLEMENTATIONS = new 
ArrayList<Class<? extends VFS>>();
+
+    /** Singleton instance. */
+    private static VFS instance;
+
+    /**
+     * Get the singleton {...@link VFS} instance. If no {...@link VFS} 
implementation can be found for the
+     * current environment, then this method returns null.
+     * 
+     * @return
+     */
+    @SuppressWarnings("unchecked")
+    public static VFS getInstance() {
+        if (instance != null)
+            return instance;
+
+        // Try the user implementations first, then the built-ins
+        List<Class<? extends VFS>> impls = new ArrayList<Class<? extends 
VFS>>();
+        impls.addAll(USER_IMPLEMENTATIONS);
+        impls.addAll(Arrays.asList((Class<? extends VFS>[]) IMPLEMENTATIONS));
+
+        // Try each implementation class until a valid one is found
+        VFS vfs = null;
+        for (int i = 0; vfs == null || !vfs.isValid(); i++) {
+            Class<? extends VFS> impl = impls.get(i);
+            try {
+                vfs = impl.newInstance();
+                if (vfs == null || !vfs.isValid()) {
+                    log.debug("VFS implementation ", impl.getName(),
+                            " is not valid in this environment.");
+                }
+            }
+            catch (InstantiationException e) {
+                log.error(e, "Failed to instantiate ", impl);
+                return null;
+            }
+            catch (IllegalAccessException e) {
+                log.error(e, "Failed to instantiate ", impl);
+                return null;
+            }
+        }
+
+        log.info("Using VFS adapter ", vfs.getClass().getName());
+        return VFS.instance = vfs;
+    }
+
+    /**
+     * Adds the specified class to the list of {...@link VFS} implementations. 
Classes added in this
+     * manner are tried in the order they are added and before any of the 
built-in implementations.
+     * 
+     * @param className The name of the {...@link VFS} implementation class to 
add.
+     */
+    public static void addImplClass(Class<? extends VFS> clazz) {
+        if (clazz != null)
+            USER_IMPLEMENTATIONS.add(clazz);
+    }
+
+    /** Get a class by name. If the class is not found then return null. */
+    protected static Class<?> getClass(String className) {
+        try {
+            return ReflectUtil.findClass(className);
+        }
+        catch (ClassNotFoundException e) {
+            log.debug("Class not found: ", className);
+            return null;
+        }
+    }
+
+    /**
+     * Get a method by name and parameter types. If the method is not found 
then return null.
+     * 
+     * @param clazz The class to which the method belongs.
+     * @param methodName The name of the method.
+     * @param parameterTypes The types of the parameters accepted by the 
method.
+     */
+    protected static Method getMethod(Class<?> clazz, String methodName, 
Class<?>... parameterTypes) {
+        try {
+            if (clazz == null)
+                return null;
+            else
+                return clazz.getMethod(methodName, parameterTypes);
+        }
+        catch (SecurityException e) {
+            log.debug(e, "Security exception looking for method ", 
clazz.getName(), ".", methodName);
+            return null;
+        }
+        catch (NoSuchMethodException e) {
+            log.debug(e, "Method not found ", clazz.getName(), ".", 
methodName);
+            return null;
+        }
+    }
+
+    /**
+     * Invoke a method on an object and return whatever it returns.
+     * 
+     * @param method The method to invoke.
+     * @param object The instance or class (for static methods) on which to 
invoke the method.
+     * @param parameters The parameters to pass to the method.
+     * @return Whatever the method returns.
+     * @throws IOException If I/O errors occur
+     * @throws StripesRuntimeException If anything else goes wrong
+     */
+    @SuppressWarnings("unchecked")
+    protected static <T> T invoke(Method method, Object object, Object... 
parameters)
+            throws IOException, StripesRuntimeException {
+        try {
+            return (T) method.invoke(object, parameters);
+        }
+        catch (IllegalArgumentException e) {
+            throw new StripesRuntimeException(e);
+        }
+        catch (IllegalAccessException e) {
+            throw new StripesRuntimeException(e);
+        }
+        catch (InvocationTargetException e) {
+            if (e.getTargetException() instanceof IOException)
+                throw (IOException) e.getTargetException();
+            else
+                throw new StripesRuntimeException(e);
+        }
+    }
+
+    /**
+     * Get a list of {...@link URL}s from the context classloader for all the 
resources found at the
+     * specified path.
+     * 
+     * @param path The resource path.
+     * @return A list of {...@link URL}s, as returned by {...@link 
ClassLoader#getResources(String)}.
+     * @throws IOException If I/O errors occur
+     */
+    protected static List<URL> getResources(String path) throws IOException {
+        return 
Collections.list(Thread.currentThread().getContextClassLoader().getResources(path));
+    }
+
+    /** Return true if the {...@link VFS} implementation is valid for the 
current environment. */
+    public abstract boolean isValid();
+
+    /**
+     * Recursively list the full resource path of all the resources that are 
children of the
+     * resource identified by a URL.
+     * 
+     * @param url The URL that identifies the resource to list.
+     * @param forPath The path to the resource that is identified by the URL. 
Generally, this is the
+     *            value passed to {...@link #getResources(String)} to get the 
resource URL.
+     * @return A list containing the names of the child resources.
+     * @throws IOException If I/O errors occur
+     */
+    protected abstract List<String> list(URL url, String forPath) throws 
IOException;
+
+    /**
+     * Recursively list the full resource path of all the resources that are 
children of all the
+     * resources found at the specified path.
+     * 
+     * @param path The path of the resource(s) to list.
+     * @return A list containing the names of the child resources.
+     * @throws IOException If I/O errors occur
+     */
+    public List<String> list(String path) throws IOException {
+        List<String> names = new ArrayList<String>();
+        for (URL url : getResources(path)) {
+            names.addAll(list(url, path));
+        }
+        return names;
+    }
+}

Added: trunk/stripes/src/net/sourceforge/stripes/vfs/package.html
===================================================================
--- trunk/stripes/src/net/sourceforge/stripes/vfs/package.html                  
        (rev 0)
+++ trunk/stripes/src/net/sourceforge/stripes/vfs/package.html  2010-11-12 
16:25:10 UTC (rev 1336)
@@ -0,0 +1,3 @@
+<body>
+<p>Contains classes that facilitate discovery of resources via the class 
loader.</p>
+</body>
\ No newline at end of file


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.

------------------------------------------------------------------------------
Centralized Desktop Delivery: Dell and VMware Reference Architecture
Simplifying enterprise desktop deployment and management using
Dell EqualLogic storage and VMware View: A highly scalable, end-to-end
client virtualization framework. Read more!
http://p.sf.net/sfu/dell-eql-dev2dev
_______________________________________________
Stripes-development mailing list
Stripes-development@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/stripes-development

Reply via email to