[jboss-dev-forums] [Design the new POJO MicroContainer] - Classloading and versioning

alesj do-not-reply at jboss.com
Mon May 5 02:26:51 EDT 2008


While hacking the demo, I had 'problems' creating this 'expected' behavior:

I have foo.jar which contains FooBar service.
This FooBar service uses two other services - AlesService and ScottService.

  |    <bean name="FooBarService" class="org.jboss.foo.FooBarService">
  |       <property name="alesService"><inject/></property>
  |       <property name="scottService"><inject/></property>
  |    </bean>
  | 

Then I have two similar jars - acme1 and acme2.
Containing AlesService(Impl) and ScottService(Impl).
acme1 has all classes in version 1.
Where acme2 has ales package in version 2 and scott package in version 3.

Then I have the following classloading metadata in foo.jar:

  | <classloading xmlns="urn:jboss:classloading:1.0" export-all="NON_EMPTY">
  |   <capabilities>
  |     <package name="org.jboss.acme.foo" version="1"/>
  |   </capabilities>
  |   <requirements>
  |     <package name="org.jboss.acme.ales" from="1" to="2" from-inclusive="true"/>
  |     <package name="org.jboss.acme.scott" from="3" to="4"  from-inclusive="true"/>
  |   </requirements>
  | </classloading>
  | 

Expecting that FooBar would use AlesService(Impl) from acme1 and ScottService(Impl) from acme2.

But that's not what happened when using the bootstrap configuration similar to what we use in AS5_trunk. :-(

Either I don't know how to properly configure this, or it's broken. ;-)

The problems that I've found:

When ClassLoaderPolicy returns non-null getExported delegate loader, this gets cached in BaseClassLoaderDomain::classLoadersByPackageName

  |          if (packageNames != null && info.getExported() != null)
  |          {
  |             for (String packageName : packageNames)
  |             {
  |                List<ClassLoaderInformation> list = classLoadersByPackageName.get(packageName);
  |                if (list == null)
  |                {
  |                   list = new CopyOnWriteArrayList<ClassLoaderInformation>();
  |                   classLoadersByPackageName.put(packageName, list);
  |                }
  |                list.add(info);
  | 
and this is then used when looking up the class in other bundles as well, but w/o knowing the right version:
BaseClassLoaderDomain::findLoaderInExports

  |       List<ClassLoaderInformation> list = classLoadersByPackageName.get(packageName);
  |       if (trace)
  |          log.trace(this + " trying to load " + name + " from all exports of package " + packageName + " " + list);
  |       if (list != null && list.isEmpty() == false)
  |       {
  |          for (ClassLoaderInformation info : list)
  |          {
  |             BaseDelegateLoader exported = info.getExported();
  |             
  |             // See whether the policies allow caching/blacklisting
  |             BaseClassLoaderPolicy loaderPolicy = exported.getPolicy();
  |             if (loaderPolicy == null || loaderPolicy.isCacheable() == false)
  |                canCache = false;
  |             if (loaderPolicy == null || loaderPolicy.isBlackListable() == false)
  |                canBlackList = false;
  | 
  |             if (exported.getResource(name) != null)
  |             {
  |                if (canCache)
  |                   globalClassCache.put(name, exported);
  |                return exported;
  |             }
  |          }
  |       }
  | 
So, when attempting to load class with the same name from foo.jar's classlaoder, but expecting it in diff version, I still get the previous one.

OK, then I disabled this exported usage - either by returning null on getExported or null getPackageNames.
Still didn't work as expected. :-(
Now the version-unaware delegates were kicking in:
BaseClassLoaderDomain::findLoaderInImports

  |       for (DelegateLoader delegate : delegates)
  |       {
  |          if (trace)
  |             log.trace(this + " trying to load " + name + " from import " + delegate + " for " + info.getClassLoader());
  |          if (delegate.getResource(name) != null)
  |          {
  |             info.cacheLoader(name, delegate);
  |             return delegate;
  |          }
  |       }
  | 
where by default in CLPolicy we return exported, which is just filtered by package names - no version awareness.
So again the previous classloader was picked.

What I had to do then - and I don't see why this isn't default behavior - is the following piece of code:

  |          public DelegateLoader getDelegateLoader(Module module, Requirement requirement)
  |          {
  |             List<Capability> capabilities = getCapabilities();
  |             if (capabilities != null && capabilities.isEmpty() == false)
  |             {
  |                for(Capability capability : capabilities)
  |                {
  |                   if (capability.resolves(this, requirement))
  |                      return new FilteredDelegateLoader(getPolicy(), new CapabilityFilter(capability));
  |                }
  |                // none of the capabilities match - don't put it as delegate
  |                return null;
  |             }
  |             return super.getDelegateLoader(module, requirement);
  |          }
  | 
  | public class CapabilityFilter implements ClassFilter
  | {
  |    private Capability capability;
  | 
  |    public CapabilityFilter(Capability capability)
  |    {
  |       if (capability == null)
  |          throw new IllegalArgumentException("Null capability.");
  |       
  |       this.capability = capability;
  |    }
  | 
  |    public boolean matchesClassName(String className)
  |    {
  |       return matchesPackageName(ClassLoaderUtils.getClassPackageName(className));
  |    }
  | 
  |    public boolean matchesResourcePath(String resourcePath)
  |    {
  |       return matchesPackageName(ClassLoaderUtils.getResourcePackageName(resourcePath));
  |    }
  | 
  |    public boolean matchesPackageName(String packageName)
  |    {
  |       if (capability instanceof ExportPackages)
  |       {
  |          ExportPackages ep = (ExportPackages)capability;
  |          Set<String> packages = ep.getPackageNames(null);
  |          if (packages != null && packages.contains(packageName))
  |             return true;
  |       }
  | 
  |       return false;
  |    }
  | }
  | 
This finally got me to my expected behavior. :-)

All this code can be found here:
 - http://anonsvn.jboss.org/repos/jbossas/projects/demos/osgi/


View the original post : http://www.jboss.com/index.html?module=bb&op=viewtopic&p=4148569#4148569

Reply to the post : http://www.jboss.com/index.html?module=bb&op=posting&mode=reply&p=4148569



More information about the jboss-dev-forums mailing list