Get rid of DynamicImport-Package in Wicket bundles
--------------------------------------------------

                 Key: WICKET-3737
                 URL: https://issues.apache.org/jira/browse/WICKET-3737
             Project: Wicket
          Issue Type: Improvement
          Components: wicket-core
    Affects Versions: 1.4.17
            Reporter: Daniël van 't Ooster
         Attachments: classloading.patch

Hi,

Wicket can be used in an OSGi container, out of the box it seems to work quite 
well. However, we experience some problems because of having multiple bundles 
which depend on Wicket.

Problem: bundle refreshes caused by DynamicImport-Package
All Wicket bundles have a DynamicImport-Package: * entry in their manifests. 
This makes class loading easy, because the class loaders of the Wicket bundles 
have access to any exported packages from all bundles. This approach has one 
drawback; the Wicket bundles become implicitly dependent of all bundles which 
are used at least one time by a Wicket class loader. According to the OSGi 
specification, when a bundle is refreshed, all bundles which (explicitly or 
implicitly) depend on that bundle will also be refreshed. 

Example case: there is an OSGi container with some Wicket bundles loaded, and 2 
bundles (bundleA and bundleB), both contain some Wicket components, so they 
import Wicket packages, they do not import packages from each other. Now we 
refresh BundleA, we expect only a refresh of only bundleA. In practice all 
Wicket bundles, bundleA and bundleB are refreshed. This is caused by the 
DynamicImport-Package, which makes the Wicket bundles implicitly import 
exported packages from bundleA and bundleB. When bundleA is refreshed, it will 
refresh also the Wicket bundles. Because of bundleB depends on Wicket, bundleB 
will also be refreshed. In a small project with a few bundles, this may not be 
a problem, but it can become a problem when the projects gets larger and you 
refresh bundles frequently (e.g. when using an OSGi container during 
development).

Solution: delegate class loading to another bundle
While deserializing components, Wicket uses its IClassResolver implementation 
to load the classes. The DefaultClassResolver uses the thread's context class 
loader (TCCL) and the classloader of the wicket core bundle (with the 
DynamicImport-Package). My idea is to remove the DynamicImport-Package header 
from all Wicket bundles and delegate class loading to another bundle. This 
bundle exports its OsgiClassResolver (implements IClassResolver) via the OSGi 
service registry (or a service and reference of Spring DM / Blueprint). This 
bundle will refresh when bundleA or bundleB is refreshed. Because of there are 
no bundles which depend on classes from the classResolver bundle, a refresh 
will not refresh other bundles and will be fast.

Issues: A few class loading issues
There are a few spots where we experienced class loading issues. One of the 
problems is a missing implementation of resolveProxyClass in ObjectInputStream 
subclasses. Some work is done to solve class loading issues, e.g. by overriding 
the resolveClass method, but this works for normal classes, proxy classes are 
handled differently. This is a problem on 2 locations in 
org.apache.wicket.util.lang.Objects and in 
org.apache.wicket.util.io.IObjectStreamFactory. Last known issue is in 
org.apache.wicket.proxy.LazyInitProxyFactory (wicket-ioc), but this one is more 
complicated. To be able to create a new proxy instance, it needs a classLoader, 
which can access all interfaces used for the proxy. In a bundle with a 
DynamicImport, it is safe to pass the classloader of any class in that bundle, 
but when the DynamicImport-Package is removed, not all classes are visible, and 
it will throw an exception when one of the classes is not visible. To solve 
this, I had to extend the IClassResolver interface with one method: 
getClassLoader(); this classLoader is used when generating the proxy.

So summarized, the complete solution (based on Spring Dynamic Modules) looks 
like:

applicationContext.xml:
{code}
...
<osgi:reference id="classResolver" 
interface="org.apache.wicket.application.IClassResolver" />
...
<bean id="wicketApplication" class="com.company.WicketApplication"
        p:classResolver-ref="classResolver"
        ...
/>
...
{code}

{code}
// set up application
public class WicketApplication extends Application {
  private IClassResolver classResolver;

  public void setClassResolver(IClassResolver classResolver) {
    this.classResolver = classResolver;
  }

  @Override
  public void init() {
    super.init();

    IApplicationSettings settings = getApplicationSettings();
    settings.setClassResolver(this.classResolver);
  }
}
{code}

ClassResolver bundle:

{code}
<bean id="classResolver" class="com.company.osgi.OSGiClassResolver" />

<osgi:service ref="classResolver">
  <osgi:interfaces>
    <value>org.apache.wicket.application.IClassResolver</value>
  </osgi:interfaces>
</osgi:service>
{code}

{code}
public class OSGiClassResolver extends 
org.apache.wicket.application.DefaultClassResolver {
  @Override
  public ClassLoader getClassLoader() {
    return OSGiClassResolver.class.getClassLoader();
  }
}
{code}

What do you think about this approach? Attached patch is created based on 
1.4.10, seems to be compatible with versions up to 1.4.16. 

best regards,
Daniël

--
This message is automatically generated by JIRA.
For more information on JIRA, see: http://www.atlassian.com/software/jira

Reply via email to