Repository: incubator-tamaya Updated Branches: refs/heads/master 987c5a1d9 -> af21ac43e
TAMAYA-43: Added support for JBoss VFS. Project: http://git-wip-us.apache.org/repos/asf/incubator-tamaya/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-tamaya/commit/af21ac43 Tree: http://git-wip-us.apache.org/repos/asf/incubator-tamaya/tree/af21ac43 Diff: http://git-wip-us.apache.org/repos/asf/incubator-tamaya/diff/af21ac43 Branch: refs/heads/master Commit: af21ac43e7c00aeabc5a2ba617c5ca8737b1a290 Parents: 987c5a1 Author: anatole <[email protected]> Authored: Fri Jan 16 15:06:13 2015 +0100 Committer: anatole <[email protected]> Committed: Fri Jan 16 15:06:30 2015 +0100 ---------------------------------------------------------------------- .../resource/internal/ClasspathCollector.java | 115 ++++++++- .../tamaya/resource/internal/VfsSupport.java | 252 +++++++++++++++++++ 2 files changed, 365 insertions(+), 2 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/af21ac43/modules/resources/src/main/java/org/apache/tamaya/resource/internal/ClasspathCollector.java ---------------------------------------------------------------------- diff --git a/modules/resources/src/main/java/org/apache/tamaya/resource/internal/ClasspathCollector.java b/modules/resources/src/main/java/org/apache/tamaya/resource/internal/ClasspathCollector.java index 8688ce8..c47bd10 100644 --- a/modules/resources/src/main/java/org/apache/tamaya/resource/internal/ClasspathCollector.java +++ b/modules/resources/src/main/java/org/apache/tamaya/resource/internal/ClasspathCollector.java @@ -20,6 +20,8 @@ package org.apache.tamaya.resource.internal; import java.io.File; import java.io.IOException; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; import java.net.JarURLConnection; import java.net.MalformedURLException; import java.net.URISyntaxException; @@ -28,6 +30,7 @@ import java.net.URLConnection; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; +import java.util.LinkedList; import java.util.List; import java.util.Objects; import java.util.jar.JarEntry; @@ -121,6 +124,8 @@ public class ClasspathCollector { try { if (isJarFile(resource)) { result.addAll(doFindPathMatchingJarResources(resource, locator.getSubPath())); + } else if (resource.getProtocol().startsWith(PROTOCOL_VFSZIP)) { + result.addAll(findMatchingVfsResources(resource, locator.getSubPath())); } else { result.addAll(FileCollector.traverseAndSelectFromChildren(getFile(resource), locator.getSubPathTokens(), 0)); @@ -194,7 +199,7 @@ public class ClasspathCollector { String entryPath = entry.getName(); if (entryPath.startsWith(rootEntryPath)) { String relativePath = entryPath.substring(rootEntryPath.length()); - if(relativePath.contains("/") && isFileExpression){ + if (relativePath.contains("/") && isFileExpression) { continue; } if (relativePath.matches(subPattern)) { @@ -245,7 +250,7 @@ public class ClasspathCollector { PROTOCOL_ZIP.equals(protocol) || PROTOCOL_VFSZIP.equals(protocol) || PROTOCOL_WSJAR.equals(protocol) || - (PROTOCOL_CODE_SOURCE.equals(protocol) && url.getPath().indexOf(JAR_URL_SEPARATOR) != -1)); + (PROTOCOL_CODE_SOURCE.equals(protocol) && url.getPath().contains(JAR_URL_SEPARATOR))); } /** @@ -268,4 +273,110 @@ public class ClasspathCollector { } } + /** + * Method that collects resources from a JBoss classloading system using Vfs. + * @param rootResource the root resource for evaluating its children. + * @param locationPattern the sub pattern that all children must mach, so they are selected. + * @return the resources found, never null. + * @throws IOException + */ + private static Collection<URL> findMatchingVfsResources( + URL rootResource, String locationPattern) throws IOException { + Object root = VfsSupport.getRoot(rootResource); + PatternVfsVisitor visitor = + new PatternVfsVisitor(VfsSupport.getPath(root), locationPattern); + VfsSupport.visit(root, visitor); + return visitor.getResources(); + } + + /** + * Simple dynamic visitor implementation used for evaluating paths from a JBoss Vfs system. + */ + private static class PatternVfsVisitor implements InvocationHandler { + /** + * The regex pattern to match agains all child resources of the root path against. + */ + private final String subPattern; + /** + * The resource path before yny placeholders/whitespaces are occurring. + */ + private final String rootPath; + /** + * THe resources found so far. + */ + private final List<URL> resources = new LinkedList<>(); + + /** + * Creates a new visitor for cfs resources. + * + * @param rootPath the root path, until any patterns are occurring. + * @param subPattern the sub pattern for looking for. + */ + public PatternVfsVisitor(String rootPath, String subPattern) { + this.subPattern = subPattern; + this.rootPath = (rootPath.length() == 0 || rootPath.endsWith("/") ? rootPath : rootPath + "/"); + } + + /** + * Method called by visitor proxy. + * + * @param proxy the proxy instance. + * @param method the method called. + * @param args any arguments. + * @return the result. + * @throws Throwable in case something goes wrong. + */ + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + String methodName = method.getName(); + if (Object.class.equals(method.getDeclaringClass())) { + if (methodName.equals("equals")) { + // Only consider equal when proxies are identical. + return (proxy == args[0]); + } else if (methodName.equals("hashCode")) { + return System.identityHashCode(proxy); + } + } else if ("getAttributes".equals(methodName)) { + return VfsSupport.getVisitorAttributes(); + } else if ("visit".equals(methodName)) { + visit(args[0]); + return null; + } else if ("toString".equals(methodName)) { + return toString(); + } + + throw new IllegalStateException("Unexpected method invocation: " + method); + } + + /** + * Visitor method. + * + * @param vfsResource the vfsResource object. + */ + public void visit(Object vfsResource) { + String subPath = VfsSupport.getPath(vfsResource).substring(this.rootPath.length()); + if (subPath.matches(this.subPattern)) { + try { + this.resources.add(VfsSupport.getURL(vfsResource)); + } catch (Exception e) { + LOG.log(Level.WARNING, "Failed to convert vfs resource to URL: " + vfsResource, e); + } + } + } + + /** + * Access the resources found from Vfs during last visit. + * + * @return the resources found, not null. + */ + public Collection<URL> getResources() { + return this.resources; + } + + @Override + public String toString() { + return "sub-pattern: " + this.subPattern + ", resources: " + this.resources; + } + } + } http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/af21ac43/modules/resources/src/main/java/org/apache/tamaya/resource/internal/VfsSupport.java ---------------------------------------------------------------------- diff --git a/modules/resources/src/main/java/org/apache/tamaya/resource/internal/VfsSupport.java b/modules/resources/src/main/java/org/apache/tamaya/resource/internal/VfsSupport.java new file mode 100644 index 0000000..22b4f52 --- /dev/null +++ b/modules/resources/src/main/java/org/apache/tamaya/resource/internal/VfsSupport.java @@ -0,0 +1,252 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.tamaya.resource.internal; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.net.URL; +import java.util.Arrays; +import java.util.Objects; + +/** + * Internal support class dealing with JBoss VFS in the classpath. + * <p> + * This code is compatible with JBoss AS 6+ and JBoss AS 7 and + * WildFly 8. + */ +class VfsSupport { + + private static final String VFS3_PKG = "org.jboss.vfs."; + private static final String VFS_PROTOCOL = "VFS"; + + private static Method methodGetRootUrl = null; + private static Method methodToUrl; + private static Method methodGetPathName; + private static Class<?> fileVisitorInterface; + private static Method methodVisit; + private static Field visitorAttributesField = null; + private static Method methodGetPhysicalFile = null; + + /** + * Private constructor. + */ + private VfsSupport(){} + + /** + * Initialize glue reflection code for communicating with VFS systems. + */ + static { + ClassLoader loader = VfsSupport.class.getClassLoader(); + try { + Class<?> vfsClass = loader.loadClass(VFS3_PKG + VFS_PROTOCOL); + methodGetRootUrl = findMethod(vfsClass, "getChild", URL.class); + Class<?> virtualFile = loader.loadClass(VFS3_PKG + "VirtualFile"); + methodToUrl = findMethod(virtualFile, "toURL"); + methodGetPathName = findMethod(virtualFile, "getPathName"); + methodGetPhysicalFile = findMethod(virtualFile, "getPhysicalFile"); + fileVisitorInterface = loader.loadClass(VFS3_PKG + "VirtualFileVisitor"); + methodVisit = findMethod(virtualFile, "visit", fileVisitorInterface); + Class<?> visitorAttributesClass = loader.loadClass(VFS3_PKG + "VisitorAttributes"); + visitorAttributesField = findField(visitorAttributesClass, "RECURSE"); + } catch (ClassNotFoundException ex) { + throw new IllegalStateException("JBoss VFS not available.", ex); + } + } + + /** + * Visit a VFS resource with the given visitor, modeled as dynamic {@link java.lang.reflect.InvocationHandler}. + * + * @param resource the resource + * @param visitor the visitor. + * @throws IOException + */ + static void visit(Object resource, InvocationHandler visitor) throws IOException { + Object visitorProxy = Proxy.newProxyInstance( + fileVisitorInterface.getClassLoader(), + new Class<?>[]{fileVisitorInterface}, visitor); + invokeVfsMethod(methodVisit, resource, visitorProxy); + } + + /** + * Helper method to invoke an operation on VFS. + * + * @param method the method to invoke + * @param target the target instance + * @param args any arguments + * @return the result + * @throws IOException if something fails. + */ + private static Object invokeVfsMethod(Method method, Object target, Object... args) throws IOException { + try { + return method.invoke(target, args); + } catch (Exception ex) { + throw new IOException("Failed to evaluated method: " + method, ex); + } + + } + + /** + * Transform a VFS resource into an URL. + * + * @param vfsResource the cfw resource, not null + * @return the corresponding URL + * @throws IOException + */ + static URL getURL(Object vfsResource) throws IOException { + return (URL) invokeVfsMethod(methodToUrl, vfsResource); + } + + /** + * Get a to root VFS resource for the given URL. + * + * @param url the url + * @return the corresponding VFS resource. + * @throws IOException + */ + static Object getRelative(URL url) throws IOException { + return invokeVfsMethod(methodGetRootUrl, null, url); + } + + /** + * Transform the given VFS resource of a file. + * + * @param vfsResource the VFS resource + * @return the file. + * @throws IOException + */ + static File getFile(Object vfsResource) throws IOException { + return (File) invokeVfsMethod(methodGetPhysicalFile, vfsResource); + } + + /** + * Convert the given URL to the correspinoding root URL. + * + * @param url the url + * @return the root resource. + * @throws IOException + */ + static Object getRoot(URL url) throws IOException { + return invokeVfsMethod(methodGetRootUrl, null, url); + } + + /** + * Access the attributes from the current visitor context. + * + * @return the attributes. + */ + static Object getVisitorAttributes() { + return readField(visitorAttributesField, null); + } + + /** + * Access the corresponding path to the given VFS resource. + * + * @param resource the VFS resource + * @return the corresponding path. + */ + static String getPath(Object resource) { + try { + return (String) methodGetPathName.invoke(resource); + } catch (Exception e) { + throw new IllegalStateException("Failed to get path name - " + resource, e); + } + } + + + /** + * Attempt to find a {@link Method} on the supplied class with the supplied name + * and parameter types. Searches all superclasses up to {@code Object}. + * <p>Returns {@code null} if no {@link Method} can be found. + * + * @param clazz the class to introspect + * @param name the name of the method + * @param paramTypes the parameter types of the method + * (may be {@code null} to indicate any signature) + * @return the Method object, or {@code null} if none found + */ + private static Method findMethod(Class<?> clazz, String name, Class<?>... paramTypes) { + Objects.requireNonNull(clazz, "Class must not be null"); + Objects.requireNonNull(name, "Method name must not be null"); + Class<?> searchType = clazz; + while (searchType != null) { + Method[] methods = (searchType.isInterface() ? searchType.getMethods() : searchType.getDeclaredMethods()); + for (Method method : methods) { + if (name.equals(method.getName()) && + (paramTypes == null || Arrays.equals(paramTypes, method.getParameterTypes()))) { + return method; + } + } + searchType = searchType.getSuperclass(); + } + return null; + } + + + /** + * Get the field represented by the supplied {@link Field field object} on the + * specified {@link Object target object}. In accordance with {@link Field#get(Object)} + * semantics, the returned value is automatically wrapped if the underlying field + * has a primitive type. + * <p>Thrown exceptions are rethrown as {@link IllegalStateException}. + * + * @param field the field to get + * @param target the target object from which to get the field + * @return the field's current value + */ + private static Object readField(Field field, Object target) { + try { + if (!field.isAccessible()) { + field.setAccessible(true); + } + return field.get(target); + } catch (Exception e) { + throw new IllegalStateException( + "Failed to read field: " + field.toGenericString(), e); + } + } + + /** + * Attempt to find a {@link Field field} on the supplied {@link Class} with the + * supplied {@code name}. Searches all superclasses up to {@link Object}. + * + * @param clazz the class to introspect + * @param name the name of the field + * @return the corresponding Field object, or {@code null} if not found + */ + private static Field findField(Class<?> clazz, String name) { + Objects.requireNonNull(clazz, "Class must not be null"); + Objects.requireNonNull(name, "Name must not be null."); + Class<?> searchType = clazz; + while (!Object.class.equals(searchType) && searchType != null) { + Field[] fields = searchType.getDeclaredFields(); + for (Field field : fields) { + if (name.equals(field.getName())) { + return field; + } + } + searchType = searchType.getSuperclass(); + } + return null; + } + +}
