Uploaded image for project: 'Wicket'
  1. Wicket
  2. WICKET-3737

Remove DynamicImport-Package header from Wicket bundles

    XMLWordPrintableJSON

Details

    • Improvement
    • Status: Resolved
    • Major
    • Resolution: Won't Fix
    • 1.4.17
    • None
    • wicket

    Description

      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:
      ...
      <osgi:reference id="classResolver" interface="org.apache.wicket.application.IClassResolver" />
      ...
      <bean id="wicketApplication" class="com.company.WicketApplication"
      p:classResolver-ref="classResolver"
      ...
      />
      ...

      // 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); }

      }

      // ClassResolver bundle:

      <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>

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

      { return OSGiClassResolver.class.getClassLoader(); }

      }

      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

      Attachments

        1. WICKET-3737.patch
          7 kB
          Daniël van 't Ooster
        2. WICKET-3737_bundles.zip
          45 kB
          Daniël van 't Ooster
        3. classloading.patch
          11 kB
          Daniël van 't Ooster
        4. 0001-Removed-DynamicImport-Package-header-from-manifest.patch
          2 kB
          Daniël van 't Ooster

        Activity

          People

            ivaynberg Igor Vaynberg
            dvantooster Daniël van 't Ooster
            Votes:
            4 Vote for this issue
            Watchers:
            8 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved: