Hi,
The following mail is about an issue we have and we have already discussed
this with Peter Kriens on the JavaPolis
in december and he suggested posted this on the osgi-dev, it is a bit
belated, but here goes.
To sum it all up, we did not find a way to get the bundle information for an
arbitray class. The long version, see below.
We are using a project that uses wicket as a front end and we have our
components separated using bundles. The
classes that comprise the wicket components are not know forehand, but are
configured by user action and the
classnames are stored in a database, together with their bundle symbolic
name and version (there can be multiple
versions of the same bundle running in 1 environment, so versioning is
crucial). Our problem is that wicket
serializes these objects using ObjectOutputStream and then reloads them at a
later date. The problem is that
these classes are not wired, but dynamically loaded using exact
bundle/version matching, which ObjectInput/OutputStream
has no notion of. What we could do is plug in our own subclasses of
ObjectInput/OutputStream. What we are
doing is writing extra version/bundle information into the byte stream and
reading it back in. For reading
in the class we can just iterate through the bundles until we have a match,
however finding the bundle
and version on an arbitrary class, now that's a whole different story.
There is a lot of information here, so I hope you bear with me.
The outputstream class just writes out a flag that we have a versioned class
and after that the information.
public class MultiVersionObjectOutputStream extends ObjectOutputStream {
private final IClassVersionStringProvider classVersionStringProvider;
public MultiVersionObjectOutputStream(final OutputStream out, final
IClassVersionStringProvider classVersionStringProvider) throws IOException {
super(out);
this.classVersionStringProvider = classVersionStringProvider;
}
@Override
protected void writeClassDescriptor(final ObjectStreamClass desc) throws
IOException {
final String versionString =
classVersionStringProvider.getClassVersionString(desc.forClass());
writeBoolean(versionString != null);
if (versionString != null) {
writeUTF(versionString);
}
super.writeClassDescriptor(desc);
}
}
The information is delivered by this interface
public interface IClassVersionStringProvider {
String getClassVersionString(Class<?> clazz);
}
Now here is the problem, how do we get the specific bundle for a class?
First thoughts, just get skip
through the bundles and do a reference check on the class object.
Our specific implementation looks like this
public String getClassVersionString(Class<?> clazz) {
// locate correct bundle, reads our database
Bundle foundBundle = Activator.getBundleForClass(clazz);
String symName = foundBundle.getSymbolicName();
String version = (String) foundBundle.getHeaders().get(
Constants.BUNDLE_VERSION);
return symName + "%" + version;
}
in activator:
public static Bundle getBundleForClass(Class clazz) {
// find bundle
Bundle[] bundles = instance.context.getBundles();
// locate correct bundle
for (Bundle bundle : bundles) {
try {
Class clazz2 = bundle.loadClass(clazz.getName());
if (clazz2 == clazz){
return bundle;
}
} catch (ClassNotFoundException e) {
continue;
}
}
return null;
}
Problem is, this doesn't work for us. In our specific case, we get two hits,
one of the real bundle and one
on the spring-aop bundle, so other bundles that dynamically load classes and
incidentally have loaded
our class, also match the criteria, but the information got from that bundle
is no good, as when loading
the class from that bundle, which it does so dynamically, will give the
class from the bundle with the
highest version number, but we need exact version matching.
Up until know we did not find an good solution for this, however we managed
to hack an equinox specific
solution to our problem:
@Override
public String getClassVersionString(Class<?> clazz) {
// locate correct bundle
ClassLoader cl = clazz.getClassLoader();
if (cl != null && cl.getClass().getName().equals("
org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader")) {
Bundle foundBundle = null;
try {
Method m = cl.getClass().getDeclaredMethod("getDelegate");
Object classLoaderDelegate = m.invoke(cl);
Method m2 = classLoaderDelegate.getClass
().getDeclaredMethod("getBundle");
m2.setAccessible(true);
foundBundle = (Bundle) m2.invoke(classLoaderDelegate);
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
String symName = foundBundle.getSymbolicName();
String version = (String) foundBundle.getHeaders().get(
Constants.BUNDLE_VERSION);
return symName + "%" + version;
}
return null;
}
<sarcasm>nice eh?</sarcasm>. What we really would like to see and perhaps
this is a good addition for the r5 specs, is
a generic way to get bundle information when you only have a Class object.
For completeness, here follows the input part of the process.
public class MultiVersionObjectInputStream extends ObjectInputStream {
private final IMultiVersionClassResolver classResolver;
private String versionString;
public MultiVersionObjectInputStream(final InputStream in, final
IMultiVersionClassResolver classResolver) throws IOException {
super(in);
this.classResolver = classResolver;
}
@Override
protected ObjectStreamClass readClassDescriptor() throws IOException,
ClassNotFoundException {
final boolean hasVersionString = readBoolean();
versionString = hasVersionString ? readUTF() : null;
return super.readClassDescriptor();
}
@Override
protected Class<?> resolveClass(final ObjectStreamClass desc) throws
IOException, ClassNotFoundException {
if (versionString != null) {
return classResolver.resolveClass(desc.getName(),
versionString);
}
return super.resolveClass(desc);
}
}
public interface IMultiVersionClassResolver {
Class<?> resolveClass(String className, String versionString) throws
ClassNotFoundException;
}
and our implementation
public class LayoutBundleClassResolver implements
IMultiVersionClassResolver{
@Override
public Class<?> resolveClass(final String className, final String
versionString) throws ClassNotFoundException {
int sepIndex = versionString.indexOf('%');
String bundleSymname = versionString.substring(0, sepIndex);
String bundleVersion = versionString.substring(sepIndex + 1);
Class<?> clazz = Activator.loadClass(className, bundleSymname,
bundleVersion);
if (clazz == null) {
throw new ClassNotFoundException("Could not find bundle for
bundle symbolic name " + bundleSymname + " and version "
+ bundleVersion + " for class " + className);
}
return clazz;
}
}
and in activator (here we use a utility class from Spring DM):
public static Class<?> loadClass(String className, String
bundleSymbolicName, String version) throws ClassNotFoundException{
// find bundle
Bundle[] bundles = instance.context.getBundles();
// locate correct bundle
for (Bundle bundle : bundles) {
String symName = foundBundle.getSymbolicName();
String foundVersion = (String) bundle.getHeaders().get(
Constants.BUNDLE_VERSION);
if (symName.startsWith(bundleSymbolicName) &&
version.equals(foundVersion))
{
// got the bundle, load the class
BundleDelegatingClassLoader cl =
BundleDelegatingClassLoader.createBundleClassLoaderFor(bundle);
return cl.loadClass(className);
}
}
return null;
}
Regards,
Wouter de Vaal
_______________________________________________
OSGi Developer Mail List
[email protected]
http://www2.osgi.org/mailman/listinfo/osgi-dev