/*
* Copyright 2011-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.xd.module.support;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import org.springframework.beans.factory.xml.PluggableSchemaResolver;
import org.springframework.core.io.support.PropertiesLoaderUtils;
/**
* Extension for {@link URLClassLoader} that uses a parent-last (or child first) delegation.
*
* @author Costin Leau
*/
public class ParentLastURLClassLoader extends URLClassLoader {
private final ClassLoader system;
private static final String[] SPECIAL_CASES = new String[] { "META-INF/spring.handlers", "META-INF/spring.schemas" };
private boolean byPassSystemClassLoaderForResources = false;
public ParentLastURLClassLoader(URL[] classpath, ClassLoader parent) {
this(classpath, parent, false);
}
public ParentLastURLClassLoader(URL[] classpath, ClassLoader parent, boolean byPassSystemClassLoaderForResources) {
super(wrapNull(classpath), parent);
ClassLoader sys = getSystemClassLoader();
while (sys.getParent() != null) {
sys = sys.getParent();
}
system = sys;
this.byPassSystemClassLoaderForResources = byPassSystemClassLoaderForResources;
}
private static URL[] wrapNull(URL[] classpath) {
return classpath == null ? new URL[0] : classpath;
}
@Override
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
// always check system class loader (for jvm classes & co)
if (system != null) {
try {
c = system.loadClass(name);
}
catch (ClassNotFoundException ignored) {
}
}
if (c == null) {
try {
// load local
c = findClass(name);
}
catch (ClassNotFoundException e) {
// fall back to parent
c = super.loadClass(name, resolve);
}
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
@Override
public URL getResource(String name) {
// same delegation as with load class
URL url = null;
if (system != null && !byPassSystemClassLoaderForResources) {
url = system.getResource(name);
}
if (url == null) {
url = findResource(name);
if (url == null) {
url = super.getResource(name);
}
}
return url;
}
@Override
public Enumeration<URL> getResources(String name) throws IOException {
List<URL> urls = new ArrayList<URL>();
if (system != null) {
urls.addAll(Collections.list(system.getResources(name)));
}
ArrayList<URL> fromSelf = Collections.list(findResources(name));
ClassLoader parent = getParent();
List<URL> fromParent = Collections.emptyList();
if (parent != null) {
fromParent = Collections.list(parent.getResources(name));
}
if (!isSpecialCase(name)) {
urls.addAll(fromSelf);
urls.addAll(fromParent);
}
else {
urls.addAll(fromParent);
urls.addAll(fromSelf);
}
return Collections.enumeration(urls);
}
public void setByPassSystemClassLoaderForResources(boolean byPassSystemClassLoaderForResources) {
this.byPassSystemClassLoaderForResources = byPassSystemClassLoaderForResources;
}
/**
* When loading spring schema related files, multiples resources will be loaded in turn in a {@link Properties}
* object, with later mappings overriding previous ones. Hence, we actually want our directly loaded properties to
* appear *last*. This is especially useful for version-less xsd mappings, where the one from our jar should be
* considered the *current* one, not the one that was current in a jar with a different version.
*
* @see PropertiesLoaderUtils
* @see PluggableSchemaResolver
*/
private boolean isSpecialCase(String name) {
for (String c : SPECIAL_CASES) {
if (c.equals(name)) {
return true;
}
}
return false;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("ParentLastURLCL\r\nURLs: ");
sb.append(Arrays.asList(getURLs()));
sb.append("\nParent CL: ");
sb.append(getParent());
sb.append("\nSystem CL: ");
sb.append(system);
sb.append("\n");
return (sb.toString());
}
}