ProxyClassLoader.java
/*
* Copyright 2022 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.keycloak.utils;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* @author <a href="mailto:erik.mulder@docdatapayments.com">Erik Mulder</a>
*
* Classloader implementation to facilitate loading classes and resources from a collection of other classloaders.
* Effectively it forms a proxy to one or more other classloaders.
*
* The way it works:
* - Get list of classloaders, which will be used as "delegates" when loaded classes or resources.
* - Can be retrived from provided classloaders or alternatively from the provided classes where the "delegate classloaders" will be determined from the classloaders of given classes
* - For each class or resource that is 'requested':
* - First try all provided classloaders and if we have a match, return that
* - If no match was found: proceed with 'normal' classloading in 'current classpath' scope
*
* In this particular context: only loadClass and getResource overrides are needed, since those
* are the methods that a classloading and resource loading process will need.
*/
public class ProxyClassLoader extends ClassLoader {
private Set<ClassLoader> classloaders;
/**
* Init classloader with the list of given delegates
* @param delegateClassLoaders
*/
public ProxyClassLoader(ClassLoader... delegateClassLoaders) {
if (delegateClassLoaders == null || delegateClassLoaders.length == 0) {
throw new IllegalStateException("At least one classloader to delegate must be provided");
}
classloaders = new LinkedHashSet<>();
classloaders.addAll(Arrays.asList(delegateClassLoaders));
}
/**
* Get all unique classloaders from the provided classes to be used as "Delegate classloaders"
* @param classes
*/
public ProxyClassLoader(Collection<Class<?>> classes) {
init(classes);
}
private void init(Collection<Class<?>> classes) {
classloaders = new LinkedHashSet<>();
for (Class<?> clazz : classes) {
classloaders.add(clazz.getClassLoader());
}
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
for (ClassLoader classloader : classloaders) {
try {
return classloader.loadClass(name);
} catch (ClassNotFoundException e) {
// This particular class loader did not find the class. It's expected behavior that
// this can happen, so we'll just ignore the exception and let the next one try.
}
}
// We did not find the class in the proxy class loaders, so proceed with 'normal' behavior.
return super.loadClass(name);
}
@Override
public URL getResource(String name) {
for (ClassLoader classloader : classloaders) {
URL resource = classloader.getResource(name);
if (resource != null) {
return resource;
}
// Resource == null means not found, so let the next one try.
}
// We could not get the resource from the proxy class loaders, so proceed with 'normal' behavior.
return super.getResource(name);
}
@Override
public Enumeration<URL> getResources(String name) throws IOException {
final LinkedHashSet<URL> resourceUrls = new LinkedHashSet();
for (ClassLoader classloader : classloaders) {
Enumeration<URL> child = classloader.getResources(name);
while (child.hasMoreElements()) {
resourceUrls.add(child.nextElement());
}
}
return new Enumeration<URL>() {
final Iterator<URL> resourceUrlIterator = resourceUrls.iterator();
public boolean hasMoreElements() {
return this.resourceUrlIterator.hasNext();
}
public URL nextElement() {
return (URL)this.resourceUrlIterator.next();
}
};
}
@Override
public String toString() {
return "ProxyClassLoader: Delegates: " + classloaders;
}
}