Folks,
The discussion about trust and solving deserialization DoS issues brought me
to the idea of annotating classes with Modules.
On the other hand Peter is working on ClassLoader / class identity issues.
I tried to think about it and came up with an idea that a Module can express
that it depends on other Modules so that if there is a dependency that is
shared between two modules classes loaded from this dependency preserve their
ClassLoader:
interface Module {
Module[] getDependencies();
//... class loading methods
}
We would have to implement a ClassLoader structure that is not hierarchical
but allows loading classes from dependencies.
BUT IT IS ALREADY DONE!!! And it is done well. It is called OSGI.
How can we leverage this?
1. Let's annotate classes with objects implementing:
interface BundleSource extends ReferentUuid {
Iterable<? extends BundleSource> getDependencies();
//no more multiple urls - we are a bundle
//we can either provide everything in our bundle
//or require dependencies to be installed
InputStream open();
}
2. As in my original idea - we prepare BundleSources with a ProxyPreparer so
that we know BundleSource is trusted before.
3. Let's implement a JiniBundle:
public class JiniBundle implements BundleActivator {
//our context
private static BundleContext bundleContext;
//we need a package admin instance to find Bundles that
//were used to load classes
//have to check if it is really needed or we can safely assume
//that ClassLoaders implement BundleReference
private PackageAdmin packageAdmin;
//our cache of BundleSources
private static Map<Bundle, BundleSource> bundleSourceCache =
new WeakHashMap();
//used by MarshalOutputStream to annotate classes
//will return null if we don't know the BundleSource
public static BundleSource getSourceOf(Class c) {
return bundleSourceCache.get(
packageAdmin.getExportedPackage(class.getPackage().getName()).getExportingBundle());
}
//now the difficult part :)
//cache of installed BundleSources
private static Map<BundleSource, Reference<? extends Bundle>>
installedBundles = new WeakHashMap();
//used by MarshalInputStream to resolve classes
//
public static Class loadClass(
BundleSource source, String name, ClassLoader defaultLoader) {
if (source == null) {
return defaultLoader.loadClass(name);
}
try {
makeSureInstalled(source).loadClass(name);
}
catch (RuntimeException e) {
throw e;
}
catch (Exception e) {
throw new ClassNotFoundException();
}
}
private static Bundle makeSureInstalled(BundleSource source) {
Bundle b = installedBundles.get(source);
if (b == null) {
//prepare
source = bundleSourcePreparer.prepare(source);
//make sure dependencies are installed
for (final BundleSource dependency : source.getDependencies()) {
makeSureInstalled(dependency);
}
//install
Bundle b = bundleContext.installBundle(
source.getReferentUuid().toString(), source.open());
//cache
bundleSourceCache.put(b, source);
installedBundles.put(source, new WeakReference(b));
}
return b;
}
//osgi activation follows...
}
Further details to be thought out:
1. Especially important is handling of lost codebase problem. OSGI implicitly
imports exported packages so it can happen that the service interface is
loaded from a bundle that was not installed by JiniBundle - so there is no
BundleSource associated with it.
2. Similar to lost codebase problem is that OSGI container can load a class
from a different bundle than the one we would want to so the codebase will not
be lost but will be different than the original one.
3. Sure there are many more - and the question is whether OSGI is the best
choice (maybe classworlds or NetBeans platform is better)
But in the end - would it be a nirvana? :)
Michal