Ok, this is difficult for me because I haven't fully digested the Module
code yet.
An OSGi / Jini client would already know the Service API it wanted to
find. So the Service API Bundle the client depends on would already be
installed in the client node. It would just be a matter of discovering
the correct service and Reggie does discriminate between API with
different versions.
The really tricky part is finding the correct ClassLoader to unmarshall
your classes into and the right ClassLoader to put your reflective
proxy. Hint: It has to go into the Service API interface ClassLoader,
otherwise there are issues with visibility. Java has the Package
version spec, which might be used to assist, since a client could
potentially have multiple versions of a Service API, but different
bundles can have identical packages.
OSGi has some nice features like Permissions required by a bundle being
packaged in the bundle. OSGi's Security Infrastructure predates the Java
1.4 dynamic permission security model, since it supported the various
flavours of embedded java and personal profile, which is now simply Java
CDC.
OSGi's security differs from the Java 1.4 Permission model in it's
BundleClassLoader and PermissionCollectionWrapper, which enables
Permission's to be dynamically added or removed.
PermissionCollectionWrapper is passed to the static (pre Java 1.4)
constructor of the ProtectionDomain. PermissionCollectionWrapper
remains mutable after the ProtectionDomain's construction. It can't be
used in the ProtectionDomain dynamic constructor, since this would cause
it to be replaced by a Permissions instance when performing a policy merge.
It's important to remember that while Permission's can be added and
removed from OSGi's ProtectionDomain's, true revocation of permission's
doesn't occur, client code having obtained security sensitive object
references still have them after permission has been removed, so can
continue performing privileged actions, although it can't perform any
new privileged actions. I believe the dynamic Permission's in OSGi were
made dynamic to allow remote policy updates.
OSGi has a ConditionalPermissionAdmin service which allows permission
checks to be delayed, in the case where a number of checks are performed.
I've done some work on an InternetSecurityManager, I've made sure that
the DelegatePermission's work with OSGi ProtectionDomains as well, so
the OSGi ProtectionDomains can be intermingled with the Dynamic Java 1.4
style ProtectionDomain's that Jini utilises to enable DynamicPolicy's.
The catch is that the ConditionalPermissionAdmin delayed permission
checks won't work, since that requires OSGi's SecurityManager.
I had given this some serious thought, but had concluded that it would
be easier to treat Service API as fixed and non changing, this would not
prevent OSGi from being a Jini client or service, however it wouldn't
force others to adopt OSGi either. I proposed Extended Service API to
enable evolution of Service API similar to the practice of extending
java Interfaces. This is suitable when one party wants to extend the
Service API but doesn't control the spec.
I'd figured that the proxy can install as many dependency's as it wants
in its own ClassLoader, provided we minimise the classes visible in
parent class loaders to the Service API, Jini and Java Platforms.
Since all implementations communicate using the Service API, Jini and
Java platform classes, this makes it possible to isolate all the library
and package versions of all the implementations, each to their own child
ClassLoader visibility.
So clients can still be implemented in OSGi, and updated dynamically,
but OSGi isn't concerned with the classes in proxy ClassLoaders, since
the client and proxy only cooperate using the Service API and can't see
each other. To add Service API in a client node, we would need a way to
add new Service API to the Service API ClassLoader, with potentially
many ProtectionDomains for many CodeSources containing Service API.
I thought the lost codebase problem could be solved by cooperative
CodeSource caching or something similar, based on the jar file message
digest.
Cheers,
Peter.
Michal Kleczek wrote:
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