You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by lu...@apache.org on 2015/06/17 23:09:34 UTC

[34/57] [partial] struts git commit: Merges xwork packages into struts

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/XWorkList.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/XWorkList.java b/core/src/main/java/com/opensymphony/xwork2/util/XWorkList.java
new file mode 100644
index 0000000..2a50197
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/XWorkList.java
@@ -0,0 +1,224 @@
+/*
+ * 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 com.opensymphony.xwork2.util;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.ObjectFactory;
+import com.opensymphony.xwork2.XWorkException;
+import com.opensymphony.xwork2.conversion.TypeConverter;
+import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+
+
+/**
+ * A simple list that guarantees that inserting and retrieving objects will always work regardless
+ * of the current size of the list.  Upon insertion, type conversion is also performed if necessary.
+ * Empty beans will be created to fill the gap between the current list size and the requested index
+ * using ObjectFactory's {@link ObjectFactory#buildBean(Class,java.util.Map) buildBean} method.
+ *
+ * @author Patrick Lightbody
+ */
+public class XWorkList extends ArrayList {
+    private static final Logger LOG = LogManager.getLogger(XWorkConverter.class);
+
+    private Class clazz;
+
+    public XWorkList(Class clazz) {
+        this.clazz = clazz;
+    }
+
+    public XWorkList(Class clazz, int initialCapacity) {
+        super(initialCapacity);
+        this.clazz = clazz;
+    }
+
+    /**
+     * Inserts the specified element at the specified position in this list. Shifts the element
+     * currently at that position (if any) and any subsequent elements to the right (adds one to
+     * their indices).
+     * <p/>
+     * This method is guaranteed to work since it will create empty beans to fill the gap between
+     * the current list size and the requested index to enable the element to be set.  This method
+     * also performs any necessary type conversion.
+     *
+     * @param index   index at which the specified element is to be inserted.
+     * @param element element to be inserted.
+     */
+    @Override
+    public void add(int index, Object element) {
+        if (index >= this.size()) {
+            get(index);
+        }
+
+        element = convert(element);
+
+        super.add(index, element);
+    }
+
+    /**
+     * Appends the specified element to the end of this list.
+     * <p/>
+     * This method performs any necessary type conversion.
+     *
+     * @param element element to be appended to this list.
+     * @return <tt>true</tt> (as per the general contract of Collection.add).
+     */
+    @Override
+    public boolean add(Object element) {
+        element = convert(element);
+
+        return super.add(element);
+    }
+
+    /**
+     * Appends all of the elements in the specified Collection to the end of this list, in the order
+     * that they are returned by the specified Collection's Iterator.  The behavior of this
+     * operation is undefined if the specified Collection is modified while the operation is in
+     * progress.  (This implies that the behavior of this call is undefined if the specified
+     * Collection is this list, and this list is nonempty.)
+     * <p/>
+     * This method performs any necessary type conversion.
+     *
+     * @param collection the elements to be inserted into this list.
+     * @return <tt>true</tt> if this list changed as a result of the call.
+     * @throws NullPointerException if the specified collection is null.
+     */
+    @Override
+    public boolean addAll(Collection collection) {
+        if (collection == null) {
+            throw new NullPointerException("Collection to add is null");
+        }
+
+        for (Object nextElement : collection) {
+            add(nextElement);
+        }
+
+        return true;
+    }
+
+    /**
+     * Inserts all of the elements in the specified Collection into this list, starting at the
+     * specified position.  Shifts the element currently at that position (if any) and any
+     * subsequent elements to the right (increases their indices).  The new elements will appear in
+     * the list in the order that they are returned by the specified Collection's iterator.
+     * <p/>
+     * This method is guaranteed to work since it will create empty beans to fill the gap between
+     * the current list size and the requested index to enable the element to be set.  This method
+     * also performs any necessary type conversion.
+     *
+     * @param index index at which to insert first element from the specified collection.
+     * @param collection     elements to be inserted into this list.
+     * @return <tt>true</tt> if this list changed as a result of the call.
+     */
+    @Override
+    public boolean addAll(int index, Collection collection) {
+        if (collection == null) {
+            throw new NullPointerException("Collection to add is null");
+        }
+
+        boolean trim = false;
+
+        if (index >= this.size()) {
+            trim = true;
+        }
+
+        for (Iterator it = collection.iterator(); it.hasNext(); index++) {
+            add(index, it.next());
+        }
+
+        if (trim) {
+            remove(this.size() - 1);
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns the element at the specified position in this list.
+     * <p/>
+     * An object is guaranteed to be returned since it will create empty beans to fill the gap
+     * between the current list size and the requested index.
+     *
+     * @param index index of element to return.
+     * @return the element at the specified position in this list.
+     */
+    @Override
+    public synchronized Object get(int index) {
+        while (index >= this.size()) {
+            try {
+                this.add(getObjectFactory().buildBean(clazz, ActionContext.getContext().getContextMap()));
+            } catch (Exception e) {
+                throw new XWorkException(e);
+            }
+        }
+
+        return super.get(index);
+    }
+
+    private ObjectFactory getObjectFactory() {
+        return ActionContext.getContext().getInstance(ObjectFactory.class);
+    }
+
+    /**
+     * Replaces the element at the specified position in this list with the specified element.
+     * <p/>
+     * This method is guaranteed to work since it will create empty beans to fill the gap between
+     * the current list size and the requested index to enable the element to be set.  This method
+     * also performs any necessary type conversion.
+     *
+     * @param index   index of element to replace.
+     * @param element element to be stored at the specified position.
+     * @return the element previously at the specified position.
+     */
+    @Override
+    public Object set(int index, Object element) {
+        if (index >= this.size()) {
+            get(index);
+        }
+
+        element = convert(element);
+
+        return super.set(index, element);
+    }
+
+    private Object convert(Object element) {
+        if ((element != null) && !clazz.isAssignableFrom(element.getClass())) {
+            // convert to correct type
+            LOG.debug("Converting from {} to {}", element.getClass().getName(), clazz.getName());
+            TypeConverter converter = getTypeConverter();
+            Map<String, Object> context = ActionContext.getContext().getContextMap();
+            element = converter.convertValue(context, null, null, null, element, clazz);
+        }
+
+        return element;
+    }
+
+    private TypeConverter getTypeConverter() {
+        return ActionContext.getContext().getContainer().getInstance(XWorkConverter.class);
+    }
+
+    @Override
+    public boolean contains(Object element) {
+        element = convert(element);
+        return super.contains(element);
+    }
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/XWorkTestCaseHelper.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/XWorkTestCaseHelper.java b/core/src/main/java/com/opensymphony/xwork2/util/XWorkTestCaseHelper.java
new file mode 100644
index 0000000..d81b84e
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/XWorkTestCaseHelper.java
@@ -0,0 +1,93 @@
+/*
+ * 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 com.opensymphony.xwork2.util;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.config.*;
+import com.opensymphony.xwork2.config.providers.XWorkConfigurationProvider;
+import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider;
+import com.opensymphony.xwork2.inject.Container;
+import com.opensymphony.xwork2.inject.ContainerBuilder;
+import com.opensymphony.xwork2.util.location.LocatableProperties;
+
+/**
+ * Generic test setup methods to be used with any unit testing framework. 
+ */
+public class XWorkTestCaseHelper {
+
+    public static ConfigurationManager setUp() throws Exception {
+        ConfigurationManager configurationManager = new ConfigurationManager();
+        configurationManager.addContainerProvider(new XWorkConfigurationProvider());
+        Configuration config = configurationManager.getConfiguration();
+        Container container = config.getContainer();
+        
+        // Reset the value stack
+        ValueStack stack = container.getInstance(ValueStackFactory.class).createValueStack();
+        stack.getContext().put(ActionContext.CONTAINER, container);
+        ActionContext.setContext(new ActionContext(stack.getContext()));
+    
+        // clear out localization
+        LocalizedTextUtil.reset();
+        
+    
+        //ObjectFactory.setObjectFactory(container.getInstance(ObjectFactory.class));
+        return configurationManager;
+    }
+
+    public static ConfigurationManager loadConfigurationProviders(ConfigurationManager configurationManager,
+            ConfigurationProvider... providers) {
+        try {
+            tearDown(configurationManager);
+        } catch (Exception e) {
+            throw new RuntimeException("Cannot clean old configuration", e);
+        }
+        configurationManager = new ConfigurationManager();
+        configurationManager.addContainerProvider(new ContainerProvider() {
+            public void destroy() {}
+            public void init(Configuration configuration) throws ConfigurationException {}
+            public boolean needsReload() { return false; }
+
+            public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException {
+                builder.setAllowDuplicates(true);
+            }
+            
+        });
+        configurationManager.addContainerProvider(new XWorkConfigurationProvider());
+        for (ConfigurationProvider prov : providers) {
+            if (prov instanceof XmlConfigurationProvider) {
+                ((XmlConfigurationProvider)prov).setThrowExceptionOnDuplicateBeans(false);
+            }
+            configurationManager.addContainerProvider(prov);
+        }
+        Container container = configurationManager.getConfiguration().getContainer();
+        
+        // Reset the value stack
+        ValueStack stack = container.getInstance(ValueStackFactory.class).createValueStack();
+        stack.getContext().put(ActionContext.CONTAINER, container);
+        ActionContext.setContext(new ActionContext(stack.getContext()));
+        
+        return configurationManager;
+    }
+
+    public static void tearDown(ConfigurationManager configurationManager) throws Exception {
+    
+        //  clear out configuration
+        if (configurationManager != null) {
+            configurationManager.destroyConfiguration();
+        }
+        ActionContext.setContext(null);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/classloader/AbstractResourceStore.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/classloader/AbstractResourceStore.java b/core/src/main/java/com/opensymphony/xwork2/util/classloader/AbstractResourceStore.java
new file mode 100644
index 0000000..9166389
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/classloader/AbstractResourceStore.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2002-2015 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.util.classloader;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+public abstract class AbstractResourceStore implements ResourceStore {
+    private static final Logger log = LogManager.getLogger(JarResourceStore.class);
+    protected final File file;
+
+    public AbstractResourceStore(final File file) {
+        this.file = file;
+    }
+
+    protected void closeQuietly(InputStream is) {
+        try {
+            if (is != null) {
+                is.close();
+            }
+        } catch (IOException e) {
+            log.error("Unable to close file input stream", e);
+        }
+    }
+
+    public void write(String pResourceName, byte[] pResourceData) {
+    }
+
+    public String toString() {
+        return this.getClass().getName() + file.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/classloader/FileResourceStore.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/classloader/FileResourceStore.java b/core/src/main/java/com/opensymphony/xwork2/util/classloader/FileResourceStore.java
new file mode 100644
index 0000000..c2c79ea
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/classloader/FileResourceStore.java
@@ -0,0 +1,57 @@
+/*
+ * 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 com.opensymphony.xwork2.util.classloader;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.File;
+import java.io.FileInputStream;
+
+
+/**
+ * Reads a class from disk
+ *  class taken from Apache JCI
+ */
+public final class FileResourceStore extends AbstractResourceStore {
+    private static final Logger LOG = LogManager.getLogger(FileResourceStore.class);
+
+    public FileResourceStore(final File file) {
+        super(file);
+    }
+
+    public byte[] read(final String pResourceName) {
+        FileInputStream fis = null;
+        try {
+            File file = getFile(pResourceName);
+            byte[] data = new byte[(int) file.length()];
+            fis = new FileInputStream(file);
+            fis.read(data);
+
+            return data;
+        } catch (Exception e) {
+            LOG.debug("Unable to read file [{}]", pResourceName, e);
+            return null;
+        } finally {
+            closeQuietly(fis);
+        }
+    }
+
+    private File getFile(final String pResourceName) {
+        final String fileName = pResourceName.replace('/', File.separatorChar);
+        return new File(file, fileName);
+    }
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/classloader/JarResourceStore.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/classloader/JarResourceStore.java b/core/src/main/java/com/opensymphony/xwork2/util/classloader/JarResourceStore.java
new file mode 100644
index 0000000..c5c1cc7
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/classloader/JarResourceStore.java
@@ -0,0 +1,66 @@
+/*
+ * 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 com.opensymphony.xwork2.util.classloader;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.*;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Read resources from a jar file
+ */
+public class JarResourceStore extends AbstractResourceStore {
+    private static final Logger LOG = LogManager.getLogger(JarResourceStore.class);
+
+    public JarResourceStore(File file) {
+        super(file);
+    }
+
+    public byte[] read(String pResourceName) {
+        InputStream in = null;
+        try {
+            ZipFile jarFile = new ZipFile(file);
+            ZipEntry entry = jarFile.getEntry(pResourceName);
+
+            //read into byte array
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            in = jarFile.getInputStream(entry);
+            copy(in, out);
+
+            return out.toByteArray();
+        } catch (Exception e) {
+            LOG.debug("Unable to read file [{}] from [{}]", pResourceName, file.getName(), e);
+            return null;
+        } finally {
+            closeQuietly(in);
+        }
+    }
+
+    public static long copy(InputStream input, OutputStream output) throws IOException {
+        byte[] buffer = new byte[1024 * 4];
+        long count = 0;
+        int n = 0;
+        while (-1 != (n = input.read(buffer))) {
+            output.write(buffer, 0, n);
+            count += n;
+        }
+        return count;
+    }
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/classloader/ReloadingClassLoader.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/classloader/ReloadingClassLoader.java b/core/src/main/java/com/opensymphony/xwork2/util/classloader/ReloadingClassLoader.java
new file mode 100644
index 0000000..55fe34f
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/classloader/ReloadingClassLoader.java
@@ -0,0 +1,177 @@
+/*
+ * 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 com.opensymphony.xwork2.util.classloader;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.FileManager;
+import com.opensymphony.xwork2.FileManagerFactory;
+import com.opensymphony.xwork2.XWorkException;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.File;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * 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 = LogManager.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("");
+        FileManager fileManager = ActionContext.getContext().getInstance(FileManagerFactory.class).getFileManager();
+        URL root = fileManager.normalizeToFileProtocol(parentRoot);
+        root = 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 [{}]", root.toString(), e);
+            }
+            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() {
+        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;
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/classloader/ResourceStore.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/classloader/ResourceStore.java b/core/src/main/java/com/opensymphony/xwork2/util/classloader/ResourceStore.java
new file mode 100644
index 0000000..80e6ce9
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/classloader/ResourceStore.java
@@ -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 com.opensymphony.xwork2.util.classloader;
+
+/**
+ * *interface taken from Apache JCI
+ */
+public interface ResourceStore {
+
+    void write(final String pResourceName, final byte[] pResourceData);
+
+    byte[] read(final String pResourceName);
+}
+

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/classloader/ResourceStoreClassLoader.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/classloader/ResourceStoreClassLoader.java b/core/src/main/java/com/opensymphony/xwork2/util/classloader/ResourceStoreClassLoader.java
new file mode 100644
index 0000000..8d4a688
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/classloader/ResourceStoreClassLoader.java
@@ -0,0 +1,96 @@
+/*
+ * 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 com.opensymphony.xwork2.util.classloader;
+
+/**
+ * class taken from Apache JCI
+ */
+public final class ResourceStoreClassLoader extends ClassLoader {
+
+    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);
+    }
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinder.java b/core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinder.java
new file mode 100644
index 0000000..50fde4a
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinder.java
@@ -0,0 +1,309 @@
+package com.opensymphony.xwork2.util.finder;
+
+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.util.ArrayList;
+import java.util.List;
+
+/**
+ * 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.
+ */
+public interface ClassFinder {
+
+    boolean isAnnotationPresent(Class<? extends Annotation> annotation);
+
+    /**
+     * 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.
+     */
+    List<String> getClassesNotLoaded();
+
+    List<Package> findAnnotatedPackages(Class<? extends Annotation> annotation);
+
+    List<Class> findAnnotatedClasses(Class<? extends Annotation> annotation);
+
+    List<Method> findAnnotatedMethods(Class<? extends Annotation> annotation);
+
+    List<Constructor> findAnnotatedConstructors(Class<? extends Annotation> annotation);
+
+    List<Field> findAnnotatedFields(Class<? extends Annotation> annotation);
+
+    List<Class> findClassesInPackage(String packageName, boolean recursive);
+
+    List<Class> findClasses(Test<ClassInfo> test);
+
+    List<Class> findClasses();
+
+    ClassLoaderInterface getClassLoaderInterface();
+
+    public static interface Info {
+        String getName();
+
+        List<AnnotationInfo> getAnnotations();
+    }
+
+    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;
+        }
+    }
+
+    public class Annotatable {
+        private final List<AnnotationInfo> annotations = new ArrayList<>();
+
+        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 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, ClassFinder classFinder) {
+            info = new ClassInfo(name, null, classFinder);
+            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<>();
+        private final List<MethodInfo> constructors = new ArrayList<>();
+        private final String superType;
+        private final List<String> interfaces = new ArrayList<>();
+        private final List<String> superInterfaces = new ArrayList<>();
+        private final List<FieldInfo> fields = new ArrayList<>();
+        private Class<?> clazz;
+        private ClassFinder classFinder;
+        private ClassNotFoundException notFound;
+
+        public ClassInfo(Class clazz, ClassFinder classFinder) {
+            super(clazz);
+            this.clazz = clazz;
+            this.classFinder = classFinder;
+            this.name = clazz.getName();
+            Class superclass = clazz.getSuperclass();
+            this.superType = superclass != null ? superclass.getName(): null;
+        }
+
+        public ClassInfo(String name, String superType, ClassFinder classFinder) {
+            this.name = name;
+            this.superType = superType;
+            this.classFinder = classFinder;
+        }
+
+        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 = classFinder.getClassLoaderInterface().loadClass(name);
+                return clazz;
+            } catch (ClassNotFoundException notFound) {
+                classFinder.getClassesNotLoaded().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<>();
+
+        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<>();
+                    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;
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinderFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinderFactory.java b/core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinderFactory.java
new file mode 100644
index 0000000..7998c3c
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinderFactory.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2002-2003,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.util.finder;
+
+import java.net.URL;
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * Allows create different ClassFinders which should help support different Java versions
+ */
+public interface ClassFinderFactory {
+
+    ClassFinder buildClassFinder(ClassLoaderInterface classLoaderInterface, Collection<URL> urls, boolean extractBaseInterfaces, Set<String> protocols, Test<String> classNameFilter);
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/finder/ClassLoaderInterface.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/finder/ClassLoaderInterface.java b/core/src/main/java/com/opensymphony/xwork2/util/finder/ClassLoaderInterface.java
new file mode 100644
index 0000000..f9b4a0f
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/finder/ClassLoaderInterface.java
@@ -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 com.opensymphony.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();
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/finder/ClassLoaderInterfaceDelegate.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/finder/ClassLoaderInterfaceDelegate.java b/core/src/main/java/com/opensymphony/xwork2/util/finder/ClassLoaderInterfaceDelegate.java
new file mode 100644
index 0000000..79fa460
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/finder/ClassLoaderInterfaceDelegate.java
@@ -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 com.opensymphony.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;
+    }
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/finder/DefaultClassFinder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/finder/DefaultClassFinder.java b/core/src/main/java/com/opensymphony/xwork2/util/finder/DefaultClassFinder.java
new file mode 100644
index 0000000..2e140a7
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/finder/DefaultClassFinder.java
@@ -0,0 +1,562 @@
+/*
+ * Copyright 2002-2003,2009 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.util.finder;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.FileManager;
+import com.opensymphony.xwork2.FileManagerFactory;
+import com.opensymphony.xwork2.XWorkException;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+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 java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+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;
+
+public class DefaultClassFinder implements ClassFinder {
+    private static final Logger LOG = LogManager.getLogger(DefaultClassFinder.class);
+
+    private final Map<String, List<Info>> annotated = new HashMap<>();
+    private final Map<String, ClassInfo> classInfos = new LinkedHashMap<>();
+
+    private final List<String> classesNotLoaded = new ArrayList<>();
+
+    private boolean extractBaseInterfaces;
+    private ClassLoaderInterface classLoaderInterface;
+    private FileManager fileManager;
+
+    public DefaultClassFinder(ClassLoaderInterface classLoaderInterface, Collection<URL> urls, boolean extractBaseInterfaces, Set<String> protocols, Test<String> classNameFilter) {
+        this.classLoaderInterface = classLoaderInterface;
+        this.extractBaseInterfaces = extractBaseInterfaces;
+        this.fileManager = ActionContext.getContext().getInstance(FileManagerFactory.class).getFileManager();
+
+        List<String> classNames = new ArrayList<>();
+        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) {
+                LOG.error("Unable to read URL [{}]", location.toExternalForm(), e);
+            }
+        }
+
+        for (String className : classNames) {
+            try {
+                if (classNameFilter.test(className))
+                    readClassDef(className);
+            } catch (Throwable e) {
+                LOG.error("Unable to read class [{}]", className, e);
+            }
+        }
+    }
+
+    public DefaultClassFinder(Class... classes){
+        this(Arrays.asList(classes));
+    }
+
+    public DefaultClassFinder(List<Class> classes){
+        this.classLoaderInterface = null;
+        List<Info> infos = new ArrayList<>();
+        List<Package> packages = new ArrayList<>();
+        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, this);
+            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 ClassLoaderInterface getClassLoaderInterface() {
+        return classLoaderInterface;
+    }
+
+    public boolean isAnnotationPresent(Class<? extends Annotation> annotation) {
+        List<Info> infos = annotated.get(annotation.getName());
+        return infos != null && !infos.isEmpty();
+    }
+
+    public List<String> getClassesNotLoaded() {
+        return Collections.unmodifiableList(classesNotLoaded);
+    }
+
+    public List<Package> findAnnotatedPackages(Class<? extends Annotation> annotation) {
+        classesNotLoaded.clear();
+        List<Package> packages = new ArrayList<>();
+        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<>();
+        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) {
+                    LOG.error("Error loading class [{}]", classInfo.getName(), e);
+                    classesNotLoaded.add(classInfo.getName());
+                }
+            }
+        }
+        return classes;
+    }
+
+    public List<Method> findAnnotatedMethods(Class<? extends Annotation> annotation) {
+        classesNotLoaded.clear();
+        List<ClassInfo> seen = new ArrayList<>();
+        List<Method> methods = new ArrayList<>();
+        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) {
+                    LOG.error("Error loading class [{}]", classInfo.getName(), e);
+                    classesNotLoaded.add(classInfo.getName());
+                }
+            }
+        }
+        return methods;
+    }
+
+    public List<Constructor> findAnnotatedConstructors(Class<? extends Annotation> annotation) {
+        classesNotLoaded.clear();
+        List<ClassInfo> seen = new ArrayList<>();
+        List<Constructor> constructors = new ArrayList<>();
+        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) {
+                    LOG.error("Error loading class [{}]", classInfo.getName(), e);
+                    classesNotLoaded.add(classInfo.getName());
+                }
+            }
+        }
+        return constructors;
+    }
+
+    public List<Field> findAnnotatedFields(Class<? extends Annotation> annotation) {
+        classesNotLoaded.clear();
+        List<ClassInfo> seen = new ArrayList<>();
+        List<Field> fields = new ArrayList<>();
+        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) {
+                    LOG.error("Error loading class [{}]", classInfo.getName(), e);
+                    classesNotLoaded.add(classInfo.getName());
+                }
+            }
+        }
+        return fields;
+    }
+
+    public List<Class> findClassesInPackage(String packageName, boolean recursive) {
+        classesNotLoaded.clear();
+        List<Class> classes = new ArrayList<>();
+        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) {
+                LOG.error("Error loading class [{}]", classInfo.getName(), e);
+                classesNotLoaded.add(classInfo.getName());
+            }
+        }
+        return classes;
+    }
+
+    public List<Class> findClasses(Test<ClassInfo> test) {
+        classesNotLoaded.clear();
+        List<Class> classes = new ArrayList<>();
+        for (ClassInfo classInfo : classInfos.values()) {
+            try {
+                if (test.test(classInfo)) {
+                    classes.add(classInfo.get());
+                }
+            } catch (Throwable e) {
+                LOG.error("Error loading class [{}]", classInfo.getName(), e);
+                classesNotLoaded.add(classInfo.getName());
+            }
+        }
+        return classes;
+    }
+
+    public List<Class> findClasses() {
+        classesNotLoaded.clear();
+        List<Class> classes = new ArrayList<>();
+        for (ClassInfo classInfo : classInfos.values()) {
+            try {
+                classes.add(classInfo.get());
+            } catch (Throwable e) {
+                LOG.error("Error loading class [{}]", classInfo.getName(), e);
+                classesNotLoaded.add(classInfo.getName());
+            }
+        }
+        return classes;
+    }
+
+    private static List<URL> getURLs(ClassLoaderInterface classLoader, String[] dirNames) {
+        List<URL> urls = new ArrayList<>();
+        for (String dirName : dirNames) {
+            try {
+                Enumeration<URL> classLoaderURLs = classLoader.getResources(dirName);
+                while (classLoaderURLs.hasMoreElements()) {
+                    URL url = classLoaderURLs.nextElement();
+                    urls.add(url);
+                }
+            } catch (IOException ioe) {
+                LOG.error("Could not read directory [{}]", dirName, ioe);
+            }
+        }
+
+        return urls;
+    }
+
+    private List<String> file(URL location) {
+        List<String> classNames = new ArrayList<>();
+        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$", "");
+                // Classes packaged in an exploded .war (e.g. in a VFS file system) should not
+                // have WEB-INF.classes in their package name.
+                classNames.add(StringUtils.removeStart(packageName, "WEB-INF.classes.") + name);
+            }
+        }
+    }
+
+    private List<String> jar(URL location) throws IOException {
+        URL url = fileManager.normalizeToFileProtocol(location);
+        if (url != null) {
+            InputStream in = url.openStream();
+            try {
+                JarInputStream jarStream = new JarInputStream(in);
+                return jar(jarStream);
+            } finally {
+                in.close();
+            }
+        } else {
+            LOG.debug("Unable to read [{}]", location.toExternalForm());
+        }
+        return Collections.emptyList();
+    }
+
+    private List<String> jar(JarInputStream jarStream) throws IOException {
+        List<String> classNames = new ArrayList<>();
+
+        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 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, ClassFinder classFinder) {
+            info = new ClassInfo(name, null, classFinder);
+            this.name = name;
+            this.pkg = null;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public Package get() throws ClassNotFoundException {
+            return (pkg != null)?pkg:info.get().getPackage();
+        }
+    }
+
+    private List<Info> getAnnotationInfos(String name) {
+        List<Info> infos = annotated.get(name);
+        if (infos == null) {
+            infos = new ArrayList<>();
+            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) {
+                try (InputStream in = resource.openStream()) {
+                    ClassReader classReader = new ClassReader(in);
+                    classReader.accept(new InfoBuildingVisitor(this), ClassReader.SKIP_DEBUG);
+                }
+            } 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;
+        private ClassFinder classFinder;
+
+        public InfoBuildingVisitor(ClassFinder classFinder) {
+            this.classFinder = classFinder;
+        }
+
+        public InfoBuildingVisitor(Info info, ClassFinder classFinder) {
+            this.info = info;
+            this.classFinder = classFinder;
+        }
+
+        @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), classFinder);
+            } else {
+                ClassInfo classInfo = new ClassInfo(javaName(name), javaName(superName), classFinder);
+
+                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, classFinder);
+        }
+
+        @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, classFinder);
+        }
+
+        @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, classFinder);
+        }
+
+        @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, classFinder);
+        }
+    }
+
+    private static final class DefaultClassnameFilterImpl implements Test<String> {
+        public boolean test(String className) {
+            return true;
+        }
+    }
+}
+