/*
* 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;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.*;
/**
* This class is extremely useful for loading resources and classes in a fault tolerant manner
* that works across different applications servers.
*
* It has come out of many months of frustrating use of multiple application servers at Atlassian,
* please don't change things unless you're sure they're not going to break in one server or another!
*
* It was brought in from oscore trunk revision 147.
*
* @author $Author$
* @version $Revision$
*/
public class ClassLoaderUtil {
//~ Methods ////////////////////////////////////////////////////////////////
/**
* Load all resources with a given name, potentially aggregating all results
* from the searched classloaders. If no results are found, the resource name
* is prepended by '/' and tried again.
*
* This method will try to load the resources using the following methods (in order):
*
* - From Thread.currentThread().getContextClassLoader()
*
- From ClassLoaderUtil.class.getClassLoader()
*
- callingClass.getClassLoader()
*
*
* @param resourceName The name of the resources to load
* @param callingClass The Class object of the calling object
*/
public static Iterator getResources(String resourceName, Class> callingClass, boolean aggregate) throws IOException {
AggregateIterator iterator = new AggregateIterator<>();
iterator.addEnumeration(Thread.currentThread().getContextClassLoader().getResources(resourceName));
if (!iterator.hasNext() || aggregate) {
iterator.addEnumeration(ClassLoaderUtil.class.getClassLoader().getResources(resourceName));
}
if (!iterator.hasNext() || aggregate) {
ClassLoader cl = callingClass.getClassLoader();
if (cl != null) {
iterator.addEnumeration(cl.getResources(resourceName));
}
}
if (!iterator.hasNext() && resourceName != null && (resourceName.length() == 0 || resourceName.charAt(0) != '/')) {
return getResources('/' + resourceName, callingClass, aggregate);
}
return iterator;
}
/**
* Load a given resource.
*
* This method will try to load the resource using the following methods (in order):
*
* - From Thread.currentThread().getContextClassLoader()
*
- From ClassLoaderUtil.class.getClassLoader()
*
- callingClass.getClassLoader()
*
*
* @param resourceName The name IllegalStateException("Unable to call ")of the resource to load
* @param callingClass The Class object of the calling object
*/
public static URL getResource(String resourceName, Class> callingClass) {
URL url = Thread.currentThread().getContextClassLoader().getResource(resourceName);
if (url == null) {
url = ClassLoaderUtil.class.getClassLoader().getResource(resourceName);
}
if (url == null) {
ClassLoader cl = callingClass.getClassLoader();
if (cl != null) {
url = cl.getResource(resourceName);
}
}
if (url == null && resourceName != null && (resourceName.length() == 0 || resourceName.charAt(0) != '/')) {
return getResource('/' + resourceName, callingClass);
}
return url;
}
/**
* This is a convenience method to load a resource as a stream.
*
* The algorithm used to find the resource is given in getResource()
*
* @param resourceName The name of the resource to load
* @param callingClass The Class object of the calling object
*/
public static InputStream getResourceAsStream(String resourceName, Class> callingClass) {
URL url = getResource(resourceName, callingClass);
try {
return url != null ? url.openStream() : null;
} catch (IOException e) {
return null;
}
}
/**
* Load a class with a given name.
*
* It will try to load the class in the following order:
*
* - From Thread.currentThread().getContextClassLoader()
*
- Using the basic Class.forName()
*
- From ClassLoaderUtil.class.getClassLoader()
*
- From the callingClass.getClassLoader()
*
*
* @param className The name of the class to load
* @param callingClass The Class object of the calling object
* @throws ClassNotFoundException If the class cannot be found anywhere.
*/
public static Class> loadClass(String className, Class> callingClass) throws ClassNotFoundException {
try {
return Thread.currentThread().getContextClassLoader().loadClass(className);
} catch (ClassNotFoundException e) {
try {
return Class.forName(className);
} catch (ClassNotFoundException ex) {
try {
return ClassLoaderUtil.class.getClassLoader().loadClass(className);
} catch (ClassNotFoundException exc) {
return callingClass.getClassLoader().loadClass(className);
}
}
}
}
/**
* Aggregates Enumeration instances into one iterator and filters out duplicates. Always keeps one
* ahead of the enumerator to protect against returning duplicates.
*/
static class AggregateIterator implements Iterator {
LinkedList> enums = new LinkedList<>();
Enumeration cur = null;
E next = null;
Set loaded = new HashSet<>();
public AggregateIterator addEnumeration(Enumeration e) {
if (e.hasMoreElements()) {
if (cur == null) {
cur = e;
next = e.nextElement();
loaded.add(next);
} else {
enums.add(e);
}
}
return this;
}
@Override
public boolean hasNext() {
return next != null;
}
@Override
public E next() {
if (next != null) {
E prev = next;
next = loadNext();
return prev;
} else {
throw new NoSuchElementException();
}
}
private Enumeration determineCurrentEnumeration() {
if (cur != null && !cur.hasMoreElements()) {
if (enums.size() > 0) {
cur = enums.removeLast();
} else {
cur = null;
}
}
return cur;
}
private E loadNext() {
if (determineCurrentEnumeration() != null) {
E tmp = cur.nextElement();
int loadedSize = loaded.size();
while (loaded.contains(tmp)) {
tmp = loadNext();
if (tmp == null || loaded.size() > loadedSize) {
break;
}
}
if (tmp != null) {
loaded.add(tmp);
}
return tmp;
}
return null;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
}