In my osgi application I have three bundles, travel.api
, table.api
and utils
. travel.api
depends on table.api
There is no other way how to do that AFAIK.
You have to explicitly state all dependencies the deserialized object tree contains in that bundle where you are trying to do that.
You could try to put all domain object into one bundle, let's say model and then let all other bundles depend on it.
Yes, this is a tricky one. In many cases the problem is even worse, it might not even be known which bundles will be required to deserialize a stream. For these, the compile time dependencies are just not the same as runtime dependencies.
To tackle these situations, I've used either DynamicImports-Package or use the BundleWiring API. Both worked out pretty well, Dynamic imports are easier though.
I'd say isolate the part that needs this class loading as much as you can in a separate bundle, and have that bundle use a DynamicImport.
Good luck, Frank
This actually sounds like a serious shortcoming in deserialization? A decent deserializer should use the class loader of the class that causes the load. The given class loader should only be used for the top level object since the there is no parent object yet.
So in this case the given class loader is used to load PageData. PageData's loader is used to load TableData, and TableData's loader must be used to load TestObject. There is no logical reason why this should fail unless the deserializer you use is really brain damaged since this is the model the VM uses to load classes. I am surprised that the Java deserializer does this, I consider this behavior a serious error since it uses different rules than the VM.
Serialization is a problem in OSGi because modularity is about hiding implementation classes; deserialization has a tendency to want to access these private classes, the antithesis of modularity. However, there are very good solutions to this (which does not include Dynamic-ImportPackage, that is reverting to JAR hell in a more complicated and expensive way than just using plain Java). The basic trick is to have a root object from a public API that has access to the private/transiently needed classes. Hmm, doesn't this sound like a service?
Looking at how negative people are about this, a small example how you can solve the problem with Java Serialization (that is, ObjectInputStream, and ObjectOutputStream). In your question you mention ObjectDecoderInputStream, a class I am not familiar with.
The setup is:
Bundle A: class a.A { B b; } (import b)
Bundle B: class b.B { C c; } (import c)
Bundle C: class c.C { }
So lets first serialize an object:
ByteArrayOutputStream bous = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bous);
oos.writeObject(this);
oos.close();
Now the hard part. We override the resolveObject method, this gives us a chance to to actually do proper class loading ...
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bous.toByteArray())) {
Set<ClassLoader> lhs = new LinkedHashSet<ClassLoader>();
{
// Keep a set if discovered class loaders
lhs.add(getClass().getClassLoader());
}
@Override
protected Class< ? > resolveClass(ObjectStreamClass desc)
throws ClassNotFoundException, IOException {
for (ClassLoader cl : lhs) try {
Class< ? > c = cl.loadClass(name);
// we found the class, so we can use its class loader,
// it is in the proper class space if the uses constraints
// are set properly (and you're using bnd so you should be ok)
lhs.add(c.getClassLoader());
// The paranoid among us would check
// the serial uuid here ...
// long uuid = desc.getSerialVersionUID();
// Field field = c.getField("serialVersionUID");
// assert uuid == field.get(null)
return c;
} catch (Exception e) {
// Ignore
}
// Fallback (for void and primitives)
return super.resolveClass(desc);
}
};
// And now we've successfully read the object ...
A clone = (A) in.readObject();
Please not that this only works as long as the transient graph is properly exported. I.e. if you can do new TableData
then this should also work. An example that does not work is if you for example get an implementation from an interface. The interface class is not connected to the impl. class. I.e. if you had a TableDataImpl that extended TableData you would be screwed. In those cases you need some service to find the "domain" of the implementation.
Good luck.