Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/classloader/ReloadingClassLoader.java URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/classloader/ReloadingClassLoader.java?rev=1209569&view=auto ============================================================================== --- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/classloader/ReloadingClassLoader.java (added) +++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/classloader/ReloadingClassLoader.java Fri Dec 2 16:33:03 2011 @@ -0,0 +1,174 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.struts2.xwork2.util.classloader; + +import org.apache.struts2.xwork2.util.logging.Logger; +import org.apache.struts2.xwork2.util.logging.LoggerFactory; +import org.apache.struts2.xwork2.util.URLUtil; +import org.apache.struts2.xwork2.XWorkException; + +import java.io.InputStream; +import java.io.File; +import java.net.URL; +import java.net.URISyntaxException; +import java.util.regex.Pattern; +import java.util.regex.Matcher; +import java.util.Set; +import java.util.Collections; + +import org.apache.commons.lang.ObjectUtils; + +/** + * The ReloadingClassLoader uses a delegation mechanism to allow + * classes to be reloaded. That means that loadClass calls may + * return different results if the class was changed in the underlying + * ResourceStore. + * <p/> + * class taken from Apache JCI + */ +public class ReloadingClassLoader extends ClassLoader { + private static final Logger LOG = LoggerFactory.getLogger(ReloadingClassLoader.class); + private final ClassLoader parent; + private ResourceStore[] stores; + private ClassLoader delegate; + + private Set<Pattern> acceptClasses = Collections.emptySet(); + + public ReloadingClassLoader(final ClassLoader pParent) { + super(pParent); + parent = pParent; + URL parentRoot = pParent.getResource(""); + URL root = URLUtil.normalizeToFileProtocol(parentRoot); + root = (URL) ObjectUtils.defaultIfNull(root, parentRoot); + try { + if (root != null) { + stores = new ResourceStore[]{new FileResourceStore(new File(root.toURI()))}; + } else { + throw new XWorkException("Unable to start the reloadable class loader, consider setting 'struts.convention.classes.reload' to false"); + } + } catch (URISyntaxException e) { + throw new XWorkException("Unable to start the reloadable class loader, consider setting 'struts.convention.classes.reload' to false", e); + } catch (RuntimeException e) { + // see WW-3121 + // TODO: Fix this for a reloading mechanism to be marked as stable + if (root != null) + LOG.error("Exception while trying to build the ResourceStore for URL [#0]", e, root.toString()); + else + LOG.error("Exception while trying to get root resource from class loader", e); + LOG.error("Consider setting struts.convention.classes.reload=false"); + throw e; + } + + delegate = new ResourceStoreClassLoader(parent, stores); + } + + public boolean addResourceStore(final ResourceStore pStore) { + try { + final int n = stores.length; + final ResourceStore[] newStores = new ResourceStore[n + 1]; + System.arraycopy(stores, 0, newStores, 1, n); + newStores[0] = pStore; + stores = newStores; + delegate = new ResourceStoreClassLoader(parent, stores); + return true; + } catch (final RuntimeException e) { + LOG.error("Could not add resource store", e); + } + return false; + } + + public boolean removeResourceStore(final ResourceStore pStore) { + + final int n = stores.length; + int i = 0; + + // FIXME: this should be improved with a Map + // find the pStore and index position with var i + while ((i < n) && (stores[i] != pStore)) { + i++; + } + + // pStore was not found + if (i == n) { + return false; + } + + // if stores length > 1 then array copy old values, else create new empty store + final ResourceStore[] newStores = new ResourceStore[n - 1]; + if (i > 0) { + System.arraycopy(stores, 0, newStores, 0, i); + } + if (i < n - 1) { + System.arraycopy(stores, i + 1, newStores, i, (n - i - 1)); + } + + stores = newStores; + delegate = new ResourceStoreClassLoader(parent, stores); + return true; + } + + public void reload() { + if (LOG.isTraceEnabled()) + LOG.trace("Reloading class loader"); + delegate = new ResourceStoreClassLoader(parent, stores); + } + + public void clearAssertionStatus() { + delegate.clearAssertionStatus(); + } + + public URL getResource(String name) { + return delegate.getResource(name); + } + + public InputStream getResourceAsStream(String name) { + return delegate.getResourceAsStream(name); + } + + public Class loadClass(String name) throws ClassNotFoundException { + return isAccepted(name) ? delegate.loadClass(name) : parent.loadClass(name); + } + + public void setClassAssertionStatus(String className, boolean enabled) { + delegate.setClassAssertionStatus(className, enabled); + } + + public void setDefaultAssertionStatus(boolean enabled) { + delegate.setDefaultAssertionStatus(enabled); + } + + public void setPackageAssertionStatus(String packageName, boolean enabled) { + delegate.setPackageAssertionStatus(packageName, enabled); + } + + public void setAccepClasses(Set<Pattern> acceptClasses) { + this.acceptClasses = acceptClasses; + } + + protected boolean isAccepted(String className) { + if (!this.acceptClasses.isEmpty()) { + for (Pattern pattern : acceptClasses) { + Matcher matcher = pattern.matcher(className); + if (matcher.matches()) { + return true; + } + } + return false; + } else + return true; + } +} +
Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/classloader/ResourceStore.java URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/classloader/ResourceStore.java?rev=1209569&view=auto ============================================================================== --- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/classloader/ResourceStore.java (added) +++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/classloader/ResourceStore.java Fri Dec 2 16:33:03 2011 @@ -0,0 +1,27 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.struts2.xwork2.util.classloader; + +/** + * *interface taken from Apache JCI + */ +public interface ResourceStore { + + void write(final String pResourceName, final byte[] pResourceData); + + byte[] read(final String pResourceName); +} + Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/classloader/ResourceStoreClassLoader.java URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/classloader/ResourceStoreClassLoader.java?rev=1209569&view=auto ============================================================================== --- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/classloader/ResourceStoreClassLoader.java (added) +++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/classloader/ResourceStoreClassLoader.java Fri Dec 2 16:33:03 2011 @@ -0,0 +1,102 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.struts2.xwork2.util.classloader; + +import org.apache.struts2.xwork2.util.logging.Logger; +import org.apache.struts2.xwork2.util.logging.LoggerFactory; + +/** + * class taken from Apache JCI + */ +public final class ResourceStoreClassLoader extends ClassLoader { + + private static final Logger LOG = LoggerFactory.getLogger(ResourceStoreClassLoader.class); + + private final ResourceStore[] stores; + + public ResourceStoreClassLoader(final ClassLoader pParent, final ResourceStore[] pStores) { + super(pParent); + + stores = new ResourceStore[pStores.length]; + System.arraycopy(pStores, 0, stores, 0, stores.length); + } + + private Class fastFindClass(final String name) { + + if (stores != null) { + String fileName = name.replace('.', '/') + ".class"; + for (final ResourceStore store : stores) { + final byte[] clazzBytes = store.read(fileName); + if (clazzBytes != null) { + definePackage(name); + return defineClass(name, clazzBytes, 0, clazzBytes.length); + } + } + } + + return null; + } + + protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + Class clazz = findLoadedClass(name); + + if (clazz == null) { + clazz = fastFindClass(name); + + if (clazz == null) { + final ClassLoader parent = getParent(); + if (parent != null) { + clazz = parent.loadClass(name); + } else { + throw new ClassNotFoundException(name); + } + + } + } + + if (resolve) { + resolveClass(clazz); + } + + return clazz; + } + + protected Class findClass(final String name) throws ClassNotFoundException { + final Class clazz = fastFindClass(name); + if (clazz == null) { + throw new ClassNotFoundException(name); + } + return clazz; + } + + /** + * Define the package information associated with a class. + * + * @param className the class name of for which the package information + * is to be determined. + */ + protected void definePackage(String className){ + int classIndex = className.lastIndexOf('.'); + if (classIndex == -1) { + return; + } + String packageName = className.substring(0, classIndex); + if (getPackage(packageName) != null) { + return; + } + definePackage(packageName, null, null, null, null, null, null, null); + } +} Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/finder/ClassFinder.java URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/finder/ClassFinder.java?rev=1209569&view=auto ============================================================================== --- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/finder/ClassFinder.java (added) +++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/finder/ClassFinder.java Fri Dec 2 16:33:03 2011 @@ -0,0 +1,886 @@ +/* + * Copyright 2002-2003,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.struts2.xwork2.util.finder; + +import org.apache.struts2.xwork2.util.logging.Logger; +import org.apache.struts2.xwork2.util.logging.LoggerFactory; +import org.apache.struts2.xwork2.util.URLUtil; +import org.apache.struts2.xwork2.XWorkException; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.commons.EmptyVisitor; +import org.apache.commons.lang.StringUtils; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.JarURLConnection; +import java.net.URL; +import java.net.URLDecoder; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; + +/** + * ClassFinder searches the classpath of the specified ClassLoaderInterface for + * packages, classes, constructors, methods, or fields with specific annotations. + * + * For security reasons ASM is used to find the annotations. Classes are not + * loaded unless they match the requirements of a called findAnnotated* method. + * Once loaded, these classes are cached. + * + * The getClassesNotLoaded() method can be used immediately after any find* + * method to get a list of classes which matched the find requirements (i.e. + * contained the annotation), but were unable to be loaded. + * + * @author David Blevins + * @version $Rev: 1209415 $ $Date: 2011-12-02 12:24:48 +0100 (Fri, 02 Dec 2011) $ + */ +public class ClassFinder { + private static final Logger LOG = LoggerFactory.getLogger(ClassFinder.class); + + private final Map<String, List<Info>> annotated = new HashMap<String, List<Info>>(); + private final Map<String, ClassInfo> classInfos = new LinkedHashMap<String, ClassInfo>(); + + private final List<String> classesNotLoaded = new ArrayList<String>(); + + private boolean extractBaseInterfaces; + private ClassLoaderInterface classLoaderInterface; + + /** + * Creates a ClassFinder that will search the urls in the specified ClassLoaderInterface + * excluding the urls in the ClassLoaderInterface's parent. + * + * To include the parent ClassLoaderInterface, use: + * + * new ClassFinder(ClassLoaderInterface, false); + * + * To exclude the parent's parent, use: + * + * new ClassFinder(ClassLoaderInterface, ClassLoaderInterface.getParent().getParent()); + * + * @param classLoader source of classes to scan + * @throws Exception if something goes wrong + */ + public ClassFinder(ClassLoaderInterface classLoader) throws Exception { + this(classLoader, true); + } + + /** + * Creates a ClassFinder that will search the urls in the specified ClassLoaderInterface. + * + * @param classLoader source of classes to scan + * @param excludeParent Allegedly excludes classes from parent ClassLoaderInterface, whatever that might mean + * @throws Exception if something goes wrong. + */ + public ClassFinder(ClassLoaderInterface classLoader, boolean excludeParent) throws Exception { + this(classLoader, getUrls(classLoader, excludeParent)); + } + + /** + * Creates a ClassFinder that will search the urls in the specified classloader excluding + * the urls in the 'exclude' ClassLoaderInterface. + * + * @param classLoader source of classes to scan + * @param exclude source of classes to exclude from scanning + * @throws Exception if something goes wrong + */ + public ClassFinder(ClassLoaderInterface classLoader, ClassLoaderInterface exclude) throws Exception { + this(classLoader, getUrls(classLoader, exclude)); + } + + public ClassFinder(ClassLoaderInterface classLoader, URL url) { + this(classLoader, Arrays.asList(url)); + } + + public ClassFinder(ClassLoaderInterface classLoader, String... dirNames) { + this(classLoader, getURLs(classLoader, dirNames)); + } + + public ClassFinder(ClassLoaderInterface classLoaderInterface, Collection<URL> urls) { + this(classLoaderInterface, urls, false); + } + + public ClassFinder(ClassLoaderInterface classLoaderInterface, Collection<URL> urls, boolean extractBaseInterfaces) { + this(classLoaderInterface, urls, extractBaseInterfaces, new HashSet(){ + { + add("jar"); + } + }); + } + + public ClassFinder(ClassLoaderInterface classLoaderInterface, Collection<URL> urls, boolean extractBaseInterfaces, Set<String> protocols) { + this(classLoaderInterface,urls,extractBaseInterfaces,protocols,new DefaultClassnameFilterImpl()); + } + + public ClassFinder(ClassLoaderInterface classLoaderInterface, Collection<URL> urls, boolean extractBaseInterfaces, Set<String> protocols, Test<String> classNameFilter) { + this.classLoaderInterface = classLoaderInterface; + this.extractBaseInterfaces = extractBaseInterfaces; + + List<String> classNames = new ArrayList<String>(); + for (URL location : urls) { + try { + if (protocols.contains(location.getProtocol())) { + classNames.addAll(jar(location)); + } else if ("file".equals(location.getProtocol())) { + try { + // See if it's actually a jar + URL jarUrl = new URL("jar", "", location.toExternalForm() + "!/"); + JarURLConnection juc = (JarURLConnection) jarUrl.openConnection(); + juc.getJarFile(); + classNames.addAll(jar(jarUrl)); + } catch (IOException e) { + classNames.addAll(file(location)); + } + } + } catch (Exception e) { + if (LOG.isErrorEnabled()) + LOG.error("Unable to read URL [#0]", e, location.toExternalForm()); + } + } + + for (String className : classNames) { + try { + if (classNameFilter.test(className)) + readClassDef(className); + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Unable to read class [#0]", e, className); + } + } + } + + public ClassFinder(Class... classes){ + this(Arrays.asList(classes)); + } + + public ClassFinder(List<Class> classes){ + this.classLoaderInterface = null; + List<Info> infos = new ArrayList<Info>(); + List<Package> packages = new ArrayList<Package>(); + for (Class clazz : classes) { + + Package aPackage = clazz.getPackage(); + if (aPackage != null && !packages.contains(aPackage)){ + infos.add(new PackageInfo(aPackage)); + packages.add(aPackage); + } + + ClassInfo classInfo = new ClassInfo(clazz); + infos.add(classInfo); + classInfos.put(classInfo.getName(), classInfo); + for (Method method : clazz.getDeclaredMethods()) { + infos.add(new MethodInfo(classInfo, method)); + } + + for (Constructor constructor : clazz.getConstructors()) { + infos.add(new MethodInfo(classInfo, constructor)); + } + + for (Field field : clazz.getDeclaredFields()) { + infos.add(new FieldInfo(classInfo, field)); + } + } + + for (Info info : infos) { + for (AnnotationInfo annotation : info.getAnnotations()) { + List<Info> annotationInfos = getAnnotationInfos(annotation.getName()); + annotationInfos.add(info); + } + } + } + + public boolean isAnnotationPresent(Class<? extends Annotation> annotation) { + List<Info> infos = annotated.get(annotation.getName()); + return infos != null && !infos.isEmpty(); + } + + /** + * Returns a list of classes that could not be loaded in last invoked findAnnotated* method. + * <p/> + * The list will only contain entries of classes whose byte code matched the requirements + * of last invoked find* method, but were unable to be loaded and included in the results. + * <p/> + * The list returned is unmodifiable. Once obtained, the returned list will be a live view of the + * results from the last findAnnotated* method call. + * <p/> + * This method is not thread safe. + * @return an unmodifiable live view of classes that could not be loaded in previous findAnnotated* call. + */ + public List<String> getClassesNotLoaded() { + return Collections.unmodifiableList(classesNotLoaded); + } + + public List<Package> findAnnotatedPackages(Class<? extends Annotation> annotation) { + classesNotLoaded.clear(); + List<Package> packages = new ArrayList<Package>(); + List<Info> infos = getAnnotationInfos(annotation.getName()); + for (Info info : infos) { + if (info instanceof PackageInfo) { + PackageInfo packageInfo = (PackageInfo) info; + try { + Package pkg = packageInfo.get(); + // double check via proper reflection + if (pkg.isAnnotationPresent(annotation)) { + packages.add(pkg); + } + } catch (ClassNotFoundException e) { + classesNotLoaded.add(packageInfo.getName()); + } + } + } + return packages; + } + + public List<Class> findAnnotatedClasses(Class<? extends Annotation> annotation) { + classesNotLoaded.clear(); + List<Class> classes = new ArrayList<Class>(); + List<Info> infos = getAnnotationInfos(annotation.getName()); + for (Info info : infos) { + if (info instanceof ClassInfo) { + ClassInfo classInfo = (ClassInfo) info; + try { + Class clazz = classInfo.get(); + // double check via proper reflection + if (clazz.isAnnotationPresent(annotation)) { + classes.add(clazz); + } + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + } + return classes; + } + + public List<Method> findAnnotatedMethods(Class<? extends Annotation> annotation) { + classesNotLoaded.clear(); + List<ClassInfo> seen = new ArrayList<ClassInfo>(); + List<Method> methods = new ArrayList<Method>(); + List<Info> infos = getAnnotationInfos(annotation.getName()); + for (Info info : infos) { + if (info instanceof MethodInfo && !"<init>".equals(info.getName())) { + MethodInfo methodInfo = (MethodInfo) info; + ClassInfo classInfo = methodInfo.getDeclaringClass(); + + if (seen.contains(classInfo)) continue; + + seen.add(classInfo); + + try { + Class clazz = classInfo.get(); + for (Method method : clazz.getDeclaredMethods()) { + if (method.isAnnotationPresent(annotation)) { + methods.add(method); + } + } + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + } + return methods; + } + + public List<Constructor> findAnnotatedConstructors(Class<? extends Annotation> annotation) { + classesNotLoaded.clear(); + List<ClassInfo> seen = new ArrayList<ClassInfo>(); + List<Constructor> constructors = new ArrayList<Constructor>(); + List<Info> infos = getAnnotationInfos(annotation.getName()); + for (Info info : infos) { + if (info instanceof MethodInfo && "<init>".equals(info.getName())) { + MethodInfo methodInfo = (MethodInfo) info; + ClassInfo classInfo = methodInfo.getDeclaringClass(); + + if (seen.contains(classInfo)) continue; + + seen.add(classInfo); + + try { + Class clazz = classInfo.get(); + for (Constructor constructor : clazz.getConstructors()) { + if (constructor.isAnnotationPresent(annotation)) { + constructors.add(constructor); + } + } + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + } + return constructors; + } + + public List<Field> findAnnotatedFields(Class<? extends Annotation> annotation) { + classesNotLoaded.clear(); + List<ClassInfo> seen = new ArrayList<ClassInfo>(); + List<Field> fields = new ArrayList<Field>(); + List<Info> infos = getAnnotationInfos(annotation.getName()); + for (Info info : infos) { + if (info instanceof FieldInfo) { + FieldInfo fieldInfo = (FieldInfo) info; + ClassInfo classInfo = fieldInfo.getDeclaringClass(); + + if (seen.contains(classInfo)) continue; + + seen.add(classInfo); + + try { + Class clazz = classInfo.get(); + for (Field field : clazz.getDeclaredFields()) { + if (field.isAnnotationPresent(annotation)) { + fields.add(field); + } + } + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + } + return fields; + } + + public List<Class> findClassesInPackage(String packageName, boolean recursive) { + classesNotLoaded.clear(); + List<Class> classes = new ArrayList<Class>(); + for (ClassInfo classInfo : classInfos.values()) { + try { + if (recursive && classInfo.getPackageName().startsWith(packageName)){ + classes.add(classInfo.get()); + } else if (classInfo.getPackageName().equals(packageName)){ + classes.add(classInfo.get()); + } + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + return classes; + } + + public List<Class> findClasses(Test<ClassInfo> test) { + classesNotLoaded.clear(); + List<Class> classes = new ArrayList<Class>(); + for (ClassInfo classInfo : classInfos.values()) { + try { + if (test.test(classInfo)) { + classes.add(classInfo.get()); + } + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + return classes; + } + + public List<Class> findClasses() { + classesNotLoaded.clear(); + List<Class> classes = new ArrayList<Class>(); + for (ClassInfo classInfo : classInfos.values()) { + try { + classes.add(classInfo.get()); + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + return classes; + } + + private static List<URL> getURLs(ClassLoaderInterface classLoader, String[] dirNames) { + List<URL> urls = new ArrayList<URL>(); + for (String dirName : dirNames) { + try { + Enumeration<URL> classLoaderURLs = classLoader.getResources(dirName); + while (classLoaderURLs.hasMoreElements()) { + URL url = classLoaderURLs.nextElement(); + urls.add(url); + } + } catch (IOException ioe) { + if (LOG.isErrorEnabled()) + LOG.error("Could not read driectory [#0]", ioe, dirName); + } + } + + return urls; + } + + private static Collection<URL> getUrls(ClassLoaderInterface classLoaderInterface, boolean excludeParent) throws IOException { + return getUrls(classLoaderInterface, excludeParent? classLoaderInterface.getParent() : null); + } + + private static Collection<URL> getUrls(ClassLoaderInterface classLoader, ClassLoaderInterface excludeParent) throws IOException { + UrlSet urlSet = new UrlSet(classLoader); + if (excludeParent != null){ + urlSet = urlSet.exclude(excludeParent); + } + return urlSet.getUrls(); + } + + private List<String> file(URL location) { + List<String> classNames = new ArrayList<String>(); + File dir = new File(URLDecoder.decode(location.getPath())); + if ("META-INF".equals(dir.getName())) { + dir = dir.getParentFile(); // Scrape "META-INF" off + } + if (dir.isDirectory()) { + scanDir(dir, classNames, ""); + } + return classNames; + } + + private void scanDir(File dir, List<String> classNames, String packageName) { + File[] files = dir.listFiles(); + for (File file : files) { + if (file.isDirectory()) { + scanDir(file, classNames, packageName + file.getName() + "."); + } else if (file.getName().endsWith(".class")) { + String name = file.getName(); + name = name.replaceFirst(".class$", ""); + classNames.add(packageName + name); + } + } + } + + private List<String> jar(URL location) throws IOException { + URL url = URLUtil.normalizeToFileProtocol(location); + if (url != null) { + InputStream in = url.openStream(); + try { + JarInputStream jarStream = new JarInputStream(in); + return jar(jarStream); + } finally { + in.close(); + } + } else if (LOG.isDebugEnabled()) + LOG.debug("Unable to read [#0]", location.toExternalForm()); + + return Collections.emptyList(); + } + + private List<String> jar(JarInputStream jarStream) throws IOException { + List<String> classNames = new ArrayList<String>(); + + JarEntry entry; + while ((entry = jarStream.getNextJarEntry()) != null) { + if (entry.isDirectory() || !entry.getName().endsWith(".class")) { + continue; + } + String className = entry.getName(); + className = className.replaceFirst(".class$", ""); + + //war files are treated as .jar files, so takeout WEB-INF/classes + className = StringUtils.removeStart(className, "WEB-INF/classes/"); + + className = className.replace('/', '.'); + classNames.add(className); + } + + return classNames; + } + + public class Annotatable { + private final List<AnnotationInfo> annotations = new ArrayList<AnnotationInfo>(); + + public Annotatable(AnnotatedElement element) { + for (Annotation annotation : element.getAnnotations()) { + annotations.add(new AnnotationInfo(annotation.annotationType().getName())); + } + } + + public Annotatable() { + } + + public List<AnnotationInfo> getAnnotations() { + return annotations; + } + + } + + public static interface Info { + String getName(); + + List<AnnotationInfo> getAnnotations(); + } + + public class PackageInfo extends Annotatable implements Info { + private final String name; + private final ClassInfo info; + private final Package pkg; + + public PackageInfo(Package pkg){ + super(pkg); + this.pkg = pkg; + this.name = pkg.getName(); + this.info = null; + } + + public PackageInfo(String name) { + info = new ClassInfo(name, null); + this.name = name; + this.pkg = null; + } + + public String getName() { + return name; + } + + public Package get() throws ClassNotFoundException { + return (pkg != null)?pkg:info.get().getPackage(); + } + } + + public class ClassInfo extends Annotatable implements Info { + private final String name; + private final List<MethodInfo> methods = new ArrayList<MethodInfo>(); + private final List<MethodInfo> constructors = new ArrayList<MethodInfo>(); + private final String superType; + private final List<String> interfaces = new ArrayList<String>(); + private final List<String> superInterfaces = new ArrayList<String>(); + private final List<FieldInfo> fields = new ArrayList<FieldInfo>(); + private Class<?> clazz; + private ClassNotFoundException notFound; + + public ClassInfo(Class clazz) { + super(clazz); + this.clazz = clazz; + this.name = clazz.getName(); + Class superclass = clazz.getSuperclass(); + this.superType = superclass != null ? superclass.getName(): null; + } + + public ClassInfo(String name, String superType) { + this.name = name; + this.superType = superType; + } + + public String getPackageName(){ + return name.indexOf(".") > 0 ? name.substring(0, name.lastIndexOf(".")) : "" ; + } + + public List<MethodInfo> getConstructors() { + return constructors; + } + + public List<String> getInterfaces() { + return interfaces; + } + + public List<String> getSuperInterfaces() { + return superInterfaces; + } + + public List<FieldInfo> getFields() { + return fields; + } + + public List<MethodInfo> getMethods() { + return methods; + } + + public String getName() { + return name; + } + + public String getSuperType() { + return superType; + } + + public Class get() throws ClassNotFoundException { + if (clazz != null) return clazz; + if (notFound != null) throw notFound; + try { + this.clazz = classLoaderInterface.loadClass(name); + return clazz; + } catch (ClassNotFoundException notFound) { + classesNotLoaded.add(name); + this.notFound = notFound; + throw notFound; + } + } + + @Override + public String toString() { + return name; + } + } + + public class MethodInfo extends Annotatable implements Info { + private final ClassInfo declaringClass; + private final String returnType; + private final String name; + private final List<List<AnnotationInfo>> parameterAnnotations = new ArrayList<List<AnnotationInfo>>(); + + public MethodInfo(ClassInfo info, Constructor constructor){ + super(constructor); + this.declaringClass = info; + this.name = "<init>"; + this.returnType = Void.TYPE.getName(); + } + + public MethodInfo(ClassInfo info, Method method){ + super(method); + this.declaringClass = info; + this.name = method.getName(); + this.returnType = method.getReturnType().getName(); + } + + public MethodInfo(ClassInfo declarignClass, String name, String returnType) { + this.declaringClass = declarignClass; + this.name = name; + this.returnType = returnType; + } + + public List<List<AnnotationInfo>> getParameterAnnotations() { + return parameterAnnotations; + } + + public List<AnnotationInfo> getParameterAnnotations(int index) { + if (index >= parameterAnnotations.size()) { + for (int i = parameterAnnotations.size(); i <= index; i++) { + List<AnnotationInfo> annotationInfos = new ArrayList<AnnotationInfo>(); + parameterAnnotations.add(i, annotationInfos); + } + } + return parameterAnnotations.get(index); + } + + public String getName() { + return name; + } + + public ClassInfo getDeclaringClass() { + return declaringClass; + } + + public String getReturnType() { + return returnType; + } + + @Override + public String toString() { + return declaringClass + "@" + name; + } + } + + public class FieldInfo extends Annotatable implements Info { + private final String name; + private final String type; + private final ClassInfo declaringClass; + + public FieldInfo(ClassInfo info, Field field){ + super(field); + this.declaringClass = info; + this.name = field.getName(); + this.type = field.getType().getName(); + } + + public FieldInfo(ClassInfo declaringClass, String name, String type) { + this.declaringClass = declaringClass; + this.name = name; + this.type = type; + } + + public String getName() { + return name; + } + + public ClassInfo getDeclaringClass() { + return declaringClass; + } + + public String getType() { + return type; + } + + @Override + public String toString() { + return declaringClass + "#" + name; + } + } + + public class AnnotationInfo extends Annotatable implements Info { + private final String name; + + public AnnotationInfo(Annotation annotation){ + this(annotation.getClass().getName()); + } + + public AnnotationInfo(Class<? extends Annotation> annotation) { + this.name = annotation.getName().intern(); + } + + public AnnotationInfo(String name) { + name = name.replaceAll("^L|;$", ""); + name = name.replace('/', '.'); + this.name = name.intern(); + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return name; + } + } + + private List<Info> getAnnotationInfos(String name) { + List<Info> infos = annotated.get(name); + if (infos == null) { + infos = new ArrayList<Info>(); + annotated.put(name, infos); + } + return infos; + } + + private void readClassDef(String className) { + if (!className.endsWith(".class")) { + className = className.replace('.', '/') + ".class"; + } + try { + URL resource = classLoaderInterface.getResource(className); + if (resource != null) { + InputStream in = resource.openStream(); + try { + ClassReader classReader = new ClassReader(in); + classReader.accept(new InfoBuildingVisitor(), ClassReader.SKIP_DEBUG); + } finally { + in.close(); + } + } else { + throw new XWorkException("Could not load " + className); + } + } catch (IOException e) { + throw new XWorkException("Could not load " + className, e); + } + + } + + public class InfoBuildingVisitor extends EmptyVisitor { + private Info info; + + public InfoBuildingVisitor() { + } + + public InfoBuildingVisitor(Info info) { + this.info = info; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + if (name.endsWith("package-info")) { + info = new PackageInfo(javaName(name)); + } else { + ClassInfo classInfo = new ClassInfo(javaName(name), javaName(superName)); + + for (String interfce : interfaces) { + classInfo.getInterfaces().add(javaName(interfce)); + } + info = classInfo; + classInfos.put(classInfo.getName(), classInfo); + + if (extractBaseInterfaces) + extractSuperInterfaces(classInfo); + } + } + + private void extractSuperInterfaces(ClassInfo classInfo) { + String superType = classInfo.getSuperType(); + + if (superType != null) { + ClassInfo base = classInfos.get(superType); + + if (base == null) { + //try to load base + String resource = superType.replace('.', '/') + ".class"; + readClassDef(resource); + base = classInfos.get(superType); + } + + if (base != null) { + List<String> interfaces = classInfo.getSuperInterfaces(); + interfaces.addAll(base.getSuperInterfaces()); + interfaces.addAll(base.getInterfaces()); + } + } + } + + private String javaName(String name) { + return (name == null)? null:name.replace('/', '.'); + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + AnnotationInfo annotationInfo = new AnnotationInfo(desc); + info.getAnnotations().add(annotationInfo); + getAnnotationInfos(annotationInfo.getName()).add(info); + return new InfoBuildingVisitor(annotationInfo); + } + + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + ClassInfo classInfo = ((ClassInfo) info); + FieldInfo fieldInfo = new FieldInfo(classInfo, name, desc); + classInfo.getFields().add(fieldInfo); + return new InfoBuildingVisitor(fieldInfo); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + ClassInfo classInfo = ((ClassInfo) info); + MethodInfo methodInfo = new MethodInfo(classInfo, name, desc); + classInfo.getMethods().add(methodInfo); + return new InfoBuildingVisitor(methodInfo); + } + + @Override + public AnnotationVisitor visitParameterAnnotation(int param, String desc, boolean visible) { + MethodInfo methodInfo = ((MethodInfo) info); + List<AnnotationInfo> annotationInfos = methodInfo.getParameterAnnotations(param); + AnnotationInfo annotationInfo = new AnnotationInfo(desc); + annotationInfos.add(annotationInfo); + return new InfoBuildingVisitor(annotationInfo); + } + } + + private static final class DefaultClassnameFilterImpl implements Test<String> { + public boolean test(String className) { + return true; + } + } +} + Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/finder/ClassLoaderInterface.java URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/finder/ClassLoaderInterface.java?rev=1209569&view=auto ============================================================================== --- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/finder/ClassLoaderInterface.java (added) +++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/finder/ClassLoaderInterface.java Fri Dec 2 16:33:03 2011 @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2003,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.struts2.xwork2.util.finder; + +import java.net.URL; +import java.util.Enumeration; +import java.io.IOException; +import java.io.InputStream; + +/** + * Classes implementing this interface can find resources and load classes, usually delegating to a class + * loader + */ +public interface ClassLoaderInterface { + + //key used to add the current ClassLoaderInterface to ActionContext + public final String CLASS_LOADER_INTERFACE = "__current_class_loader_interface"; + + Class<?> loadClass(String name) throws ClassNotFoundException; + + URL getResource(String name); + + public Enumeration<URL> getResources(String name) throws IOException; + + public InputStream getResourceAsStream(String name) throws IOException; + + ClassLoaderInterface getParent(); +} Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/finder/ClassLoaderInterfaceDelegate.java URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/finder/ClassLoaderInterfaceDelegate.java?rev=1209569&view=auto ============================================================================== --- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/finder/ClassLoaderInterfaceDelegate.java (added) +++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/finder/ClassLoaderInterfaceDelegate.java Fri Dec 2 16:33:03 2011 @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2003,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.struts2.xwork2.util.finder; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; + +/** + * Default implementation of ClassLoaderInterface, which delegates to an actual ClassLoader + */ +public class ClassLoaderInterfaceDelegate implements ClassLoaderInterface { + private ClassLoader classLoader; + + public ClassLoaderInterfaceDelegate(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + public Class<?> loadClass(String name) throws ClassNotFoundException { + return classLoader.loadClass(name); + } + + public URL getResource(String className) { + return classLoader.getResource(className); + } + + public Enumeration<URL> getResources(String name) throws IOException { + return classLoader.getResources(name); + } + + public InputStream getResourceAsStream(String name) { + return classLoader.getResourceAsStream(name); + } + + public ClassLoaderInterface getParent() { + return classLoader.getParent() != null ? new ClassLoaderInterfaceDelegate(classLoader.getParent()) : null; + } +}