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; + } + } +} +
