8 Replies Latest reply on Dec 3, 2007 7:55 AM by arjan

    Problem reading (deserializing) objects from FileCacheLoader

    arjan

      Hi,

      I can't seem to get it working.. I'm running JBossCache on a default JBoss4.0.5 installation (JDK 1.5). I've configured JBossCache as a service (deploy/treecache-service.xml). For each deployed webapp a separate ClassLoader is instantiated, so I have to register a ClassLoader for a section of the TreeCache (see below).

      The problem is that once objects are serialized to the file cache, they can't be de-serialized (unmarshalled).

      I'm getting stack traces like this:

      java.lang.ClassNotFoundException: No ClassLoaders found for: nl.yirdis.monet.chart.ChartMetadata
       at org.jboss.mx.loading.LoadMgr3.beginLoadTask(LoadMgr3.java:212)
       at org.jboss.mx.loading.RepositoryClassLoader.loadClassImpl(RepositoryClassLoader.java:514)
       at org.jboss.mx.loading.RepositoryClassLoader.loadClass(RepositoryClassLoader.java:408)
       at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
       at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)
       at java.lang.Class.forName0(Native Method)
       at java.lang.Class.forName(Class.java:242)
       at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:584)
       at org.jboss.invocation.MarshalledValueInputStream.resolveClass(MarshalledValueInputStream.java:109)
       at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1543)
       at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1465)
       at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1698)
       at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1304)
       at java.io.ObjectInputStream.readObject(ObjectInputStream.java:349)
       at java.util.HashMap.readObject(HashMap.java:1013)
       at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
       at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
       at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
       at java.lang.reflect.Method.invoke(Method.java:585)
       at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:946)
       at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1818)
       at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1718)
       at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1304)
       at java.io.ObjectInputStream.readObject(ObjectInputStream.java:349)
       at org.jboss.cache.loader.FileCacheLoader.loadAttributes(FileCacheLoader.java:486)
       at org.jboss.cache.loader.FileCacheLoader.get(FileCacheLoader.java:136)
       at org.jboss.cache.interceptors.CacheLoaderInterceptor.loadData(CacheLoaderInterceptor.java:443)
       at org.jboss.cache.interceptors.CacheLoaderInterceptor.loadNode(CacheLoaderInterceptor.java:353)
       at org.jboss.cache.interceptors.CacheLoaderInterceptor.invoke(CacheLoaderInterceptor.java:180)
       at org.jboss.cache.interceptors.Interceptor.invoke(Interceptor.java:68)
       at org.jboss.cache.interceptors.UnlockInterceptor.invoke(UnlockInterceptor.java:32)
      


      In my treecache configuration I've enabled RegionBasedMarshalling and InactiveOnStartup, as described in the user guide:

       <attribute name="UseRegionBasedMarshalling">true</attribute>
       <attribute name="InactiveOnStartup">true</attribute>
      


      The region is configured like this:

       <region name="/nl/yirdis/monet/charts">
       <attribute name="maxNodes">1000</attribute>
       <attribute name="timeToLiveSeconds">86400</attribute>
       <attribute name="maxAgeSeconds">86400</attribute>
       </region>
      


      In my code (using Spring), I've set the ClassLoader for that region to the Classloader of the webapp:

       public void afterPropertiesSet() throws Exception {
       treeCache.registerClassLoader("/nl/yirdis/monet/charts/", ChartMetadata.class.getClassLoader());
       treeCache.activateRegion("/nl/yirdis/monet/charts/");
       }
      


      (ChartMetadata is a class containing the serialized stuff. It implements the Serializable interface).

      Deactivation on undeployment of the webapp is done likewise, as stated in the docs.

      Where did I go wrong? What's causing the TreeCache (FileCacheLoader) to cause class loading errors?


      Regards,

      Arjan


        • 1. Re: Problem reading (deserializing) objects from FileCacheLo
          arjan

          Oh,

          The JBossCache version is:

          $ java -jar jboss-cache-jdk50.jar
          Version: 1.4.1.SP3
          Codename: Cayenne
          CVS: $Id: Version.java,v 1.18.2.11 2007/03/06 20:12:53 msurtani Exp $
          History: (see http://jira.jboss.com/jira/browse/JBCACHE for details)
          


          • 2. Re: Problem reading (deserializing) objects from FileCacheLo
            manik

            I presume the ChartMetadata class is in your webapp lib dir? Have you tried using a context class loader instead?

            Thread.currentThread().getContextClassLoader()
            


            • 3. Re: Problem reading (deserializing) objects from FileCacheLo
              arjan

              Yes, ChartMetadata is part of the webapp.

              I checked (I saw the context class loader was used in the MarshalledValueInputStream) and they're both the same class loader.

              • 4. Re: Problem reading (deserializing) objects from FileCacheLo
                arjan

                Okay, I did some extra checks using some (remote) debugging on the JBoss AS.

                I added some breakpoints to org.jboss.invocation.MarshalledValueInputStream. It seems like Thread.currentThread().getContextClassLoader() does return a different classloader than ChartMetadata.getClass().getClassLoader() (and thus the classloader used to load the webapp).

                The first returns a org.jboss.mx.loading.UnifiedClassLoader3 instance, while the second returns a org.jboss.web.tomcat.service.WebAppClassLoader. The ChartMetadata class is packaged as part of a .war archive. The application itself is packaged in an .ear archive. Does this make the difference? Should Tomcat use the unified classloader instead?

                When clecking ChartMetadata.class.getClassLoader() and Thread.currentThread().getContextClassLoader() from within my code (the helper-class used to access the TreeCache) all seems to be fine (both point to the WebAppClassLoader.


                • 5. Re: Problem reading (deserializing) objects from FileCacheLo
                  arjan

                  Indeed, the Classloader seems to be changed in org.jboss.mx.server.AbstractMBeanInvoker:

                  252:ClassLoader mbeanTCL = resourceEntry.getClassLoader();
                   final ClassLoader ccl = TCLAction.UTIL.getContextClassLoader();
                   boolean setCl = ccl != mbeanTCL && mbeanTCL != null;
                   if (setCl)
                   {
                   TCLAction.UTIL.setContextClassLoader(mbeanTCL);
                   }
                  


                  I have to say that I use a JRMPProxyFactory to make the TreeCache accessible from JNDI. I'll change this to see if it makes a difference.




                  • 6. Re: Problem reading (deserializing) objects from FileCacheLo
                    arjan

                    ... No differerence.

                    So, let's summarize:


                    [li]I have a TreeCache deployed as service (deploy/treecache-service.xml)[/li]
                    [li]I access the TreeCache over JMX/JNDI (makes no differerence)[/li]
                    [li]Using the FileCacheLoader fails with ClassNotFoundExceptions[/li]
                    [li]Since TreeCache and my webapp are loaded with different classloaders, the MXBean invocation code switches the classloader to the TreeCache classloader for the duration of the MXBean call.[/li]


                    It looks like the TreeCache is not using my registered class loader at all! (the MarshalledValueInputStream is trying to do it on it's own, without TreeCache's region information).


                    This sounds all pretty plausible to me. But what should I do? Should I provide a special (web-app local) class loader for the region I use in my webapp?

                    Pfffff.

                    Arjan


                    • 7. Re: Problem reading (deserializing) objects from FileCacheLo
                      brian.stansberry

                      This is a workaround -- a hack, but still a workaround:

                      Let's assume you're getting a ref to the TreeCacheMBean via setter dependency injection. Write your setter like this:

                      
                      private TreeCacheMBean treeCache;
                      
                      public void setTreeCache(TreeCacheMBean cache) {
                       this.treeCache = (cache == null ? null : cache.getInstance();
                      }
                      


                      The getInstance() call returns you a direct ref to the cache, replacing the JMX proxy that got passed in. So, later on you are directly invoking on the cache, bypassing the JMX logic that switches out the TCCL.

                      • 8. Re: Problem reading (deserializing) objects from FileCacheLo
                        arjan

                        Hi,

                        Thanks. I had already solved it by serializing and de-serializing the code myself (only storing byte arrays in the cache).

                        Regards,

                        Arjan