blob: 54e96990a7701a11ff8a6bbb6047ac2895f9f059 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.ignite.osgi.classloaders;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.ignite.internal.util.GridConcurrentHashSet;
import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
/**
* A {@link ClassLoader} implementation that first attempts to load the class from the associated {@link Bundle}. As
* a fallback, it sweeps the entire OSGi container to find the requested class, returning the first hit.
* <p>
* It keeps a cache of resolved classes and unresolvable classes, in order to optimize subsequent lookups.
*/
public class ContainerSweepClassLoader extends BundleDelegatingClassLoader {
/** Classes resolved previously. */
private final ConcurrentMap<String, Bundle> resolved = new ConcurrentHashMap<>();
/** Unresolvable classes. */
private final Set<String> nonResolvable = new GridConcurrentHashSet<>();
/**
* Constructor with a {@link Bundle} only.
*
* @param bundle The bundle.
*/
public ContainerSweepClassLoader(Bundle bundle) {
super(bundle);
}
/**
* Constructor with a {@link Bundle} and another {@link ClassLoader} to check.
*
* @param bundle The bundle.
* @param classLoader The other classloader to check.
*/
public ContainerSweepClassLoader(Bundle bundle, ClassLoader classLoader) {
super(bundle, classLoader);
}
/**
* Runs the same logic to find the class as {@link BundleDelegatingClassLoader}, but if not found, sweeps the
* OSGi container to locate the first {@link Bundle} that can load the class, and uses it to do so.
*
* @param name The classname.
* @param resolve Whether to resolve the class or not.
* @return The loaded class.
* @throws ClassNotFoundException
*/
@Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// If we know it's not resolvable, throw the exception immediately.
if (nonResolvable.contains(name))
throw classNotFoundException(name);
Class<?> cls;
// First, delegate to super, and return the class if found.
try {
cls = super.loadClass(name, resolve);
return cls;
}
catch (ClassNotFoundException ignored) {
// Continue.
}
// Else, check the cache.
if (resolved.containsKey(name))
return resolved.get(name).loadClass(name);
// If still unresolved, sweep the container.
cls = sweepContainer(name);
// If still unresolved, throw the exception.
if (cls == null)
throw classNotFoundException(name);
return cls;
}
/**
* Sweeps the OSGi container to find the first {@link Bundle} that can load the class.
*
* @param name The classname.
* @return The loaded class.
*/
protected Class<?> sweepContainer(String name) {
Class<?> cls = null;
Bundle[] bundles = bundle.getBundleContext().getBundles();
int bundleIdx = 0;
for (; bundleIdx < bundles.length; bundleIdx++) {
Bundle b = bundles[bundleIdx];
// Skip bundles that haven't reached RESOLVED state; skip fragments.
if (b.getState() <= Bundle.RESOLVED || b.getHeaders().get(Constants.FRAGMENT_HOST) != null)
continue;
try {
cls = b.loadClass(name);
break;
}
catch (ClassNotFoundException ignored) {
// No-op.
}
}
if (cls == null)
nonResolvable.add(name);
else
resolved.put(name, bundles[bundleIdx]);
return cls;
}
}