Search code examples
javamavencachingclassloadermybatis

ClassNotFoundException when deserializing data from cache


I have an EAR application with EJB for web services, deployed at Glassfish 3.0.1 server. All dependencies are loaded by maven, regarding to pom.xml. I am using wsimport for generate classes from WSDL files. Everything worked perfectly.

Then I had to implement caching for mybatis queries because of some performance issues. After I turn caching on, I realize, that my classes need to be serializable. This wasn't a problem.

<xs:annotation>
    <xs:appinfo>
        <jaxb:globalBindings>
            <xjc:serializable uid="1" />
        </jaxb:globalBindings>
    </xs:appinfo>
</xs:annotation>

Application is compiled and deployed, but when i call web service operation for the second time (first time ok, cache is empty) from soapUI, i get following error.

org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: org.apache.ibatis.cache.CacheException: Error     deserializing object. Cause: java.lang.ClassNotFoundException: cz.cpost.esb.cenik.schema.CountryType
### Cause: org.apache.ibatis.cache.CacheException: Error deserializing object. Cause: java.lang.ClassNotFoundException: cz.cpost.esb.cenik.schema.CountryType
... some code ommited ...
Caused by: org.apache.ibatis.cache.CacheException: Error deserializing object.  Cause: java.lang.ClassNotFoundException: cz.cpost.esb.cenik.schema.CountryType
at org.apache.ibatis.cache.decorators.SerializedCache.deserialize(SerializedCache.java:79)
at org.apache.ibatis.cache.decorators.SerializedCache.getObject(SerializedCache.java:35)
at org.apache.ibatis.cache.decorators.LoggingCache.getObject(LoggingCache.java:35)
at org.apache.ibatis.cache.decorators.SynchronizedCache.getObject(SynchronizedCache.java:40)
at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:56)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:78)
... 83 more
Caused by: java.lang.ClassNotFoundException: cz.cpost.esb.cenik.schema.CountryType
at com.sun.enterprise.loader.ASURLClassLoader.findClassData(ASURLClassLoader.java:713)
at com.sun.enterprise.loader.ASURLClassLoader.findClass(ASURLClassLoader.java:626)
at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:247)
at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:604)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1575)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1496)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1732)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1329)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:351)
at java.util.ArrayList.readObject(ArrayList.java:593)
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:597)
at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:974)
at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1849)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1753)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1329)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:351)
at org.apache.ibatis.cache.decorators.SerializedCache.deserialize(SerializedCache.java:76)
... 88 more

It seems like when app is trying to get data from cache, cannot find object CountryType. I don't know how this is possible, I am pretty new in maven and caching.

maven build element:

<build>
    <resources>
        <resource>
            <targetPath>META-INF/wsdl</targetPath>
            <directory>src/wsdl</directory>
            <includes/>
            <filtering>true</filtering>
        </resource>
        <resource>
            <directory>src/main/resources</directory>
            <includes/>
        </resource>
    </resources>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>2.3.2</version>
            <configuration>
                <source>1.6</source>
                <target>1.6</target>
                <compilerArguments>
                    <endorseddirs>${endorsed.dir}</endorseddirs>
                </compilerArguments>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-ejb-plugin</artifactId>
            <version>2.3</version>
            <configuration>
                <ejbVersion>3.1</ejbVersion>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>jaxws-maven-plugin</artifactId>
            <version>1.12</version>
            <executions>
                <execution>
                    <goals>
                        <goal>wsimport</goal>
                    </goals>
                    <configuration>
                        <keep>true</keep>
                        <wsdlFiles>
                            <wsdlFile>CenikServices-v2.0.wsdl</wsdlFile>
                        </wsdlFiles>
                        <packageName>cz.cpost.esb.cenik.schema</packageName>
                        <staleFile>${project.build.directory}/jaxws/stale/cenik.stale</staleFile>
                        <bindingFiles>
                            <bindingFile>${basedir}/src/main/resources/jaxb-bindings.xml</bindingFile>
                        </bindingFiles>
                    </configuration>
                    <id>wsimport-generate-cenik</id>
                    <phase>generate-sources</phase>
                </execution>
            </executions>
            <dependencies>
                <dependency>
                    <groupId>javax.xml</groupId>
                    <artifactId>webservices-api</artifactId>
                    <version>1.4</version>
                </dependency>
            </dependencies>
            <configuration>
                <sourceDestDir>${project.build.directory}/generated-sources/jaxws-wsimport</sourceDestDir>
                <destDir>${project.build.directory}/classes</destDir>
                <xnocompile>true</xnocompile>
                <verbose>true</verbose>
                <extension>true</extension>
                <catalog>${basedir}/src/jax-ws-catalog.xml</catalog>
                <target>2.0</target>
            </configuration>
        </plugin>
    </plugins>
</build>

cache config in mapper configuration:

<cache type="org.mybatis.caches.ehcache.EhcacheCache">
    <property name="timeToIdleSeconds" value="7200"/>
    <property name="timeToLiveSeconds" value="28800"/>
    <property name="maxElementsInMemory" value="5000"/>
    <property name="maxElementsOnDisk" value="10000"/>
    <property name="memoryStoreEvictionPolicy" value="LRU"/>
 </cache>

I though my sources is not in classpath and I put archive element to ear pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
    <groupId>cz.cpost.esb</groupId>
    <artifactId>cenikservices-ear</artifactId>
    <version>2.0</version>
    <relativePath>../pom.xml</relativePath>
</parent>

<modelVersion>4.0.0</modelVersion>
<artifactId>ear</artifactId>
<packaging>ear</packaging>

<dependencies>
    <dependency>
        <groupId>${project.groupId}</groupId>
        <artifactId>cenikservices-ejb</artifactId>
        <version>${project.version}</version>
        <type>ejb</type>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-ear-plugin</artifactId>
            <version>2.5</version>
            <configuration>
                <defaultLibBundleDir>lib</defaultLibBundleDir>
                <modules>
                    <ejbModule>
                        <groupId>${project.groupId}</groupId>
                        <artifactId>cenikservices-ejb</artifactId>
                    </ejbModule>
                </modules>
                <archive>
                    <manifest>
                        <addClasspath>true</addClasspath>
                    </manifest>
                </archive>
            </configuration>
        </plugin>
    </plugins>
</build>

EAR file structure:

structure of deployed EAR file

But problem is not solved. Why application could not find generated sources packaged in cenikservices-ejb-2.0.jar?


UPDATE

I added lines to my EJB class into its constructor

import org.apache.ibatis.io.Resources;
...
public CenikEJB() {
    Resources.setDefaultClassLoader(this.getClass().getClassLoader());        // added
    this.sqlSessionFactory = MyBatisConnectionFactory.getSqlSessionFactory();
}

but still get the same error on ASURLClassLoader :-( I tested, if class CountryType is available in EJB method and it is. I see SUCCESS in log.

@Override
public List<CountryType> listCountry(Integer codeTask, String langAlfaCode) throws CenikFault {
    methodName = "Cenik.listCountry";

    SqlSession session = this.sqlSessionFactory.openSession(); 
    try {
        this.getClass().getClassLoader().loadClass("cz.cpost.esb.cenik.schema.CountryType");
        ccpLogger.info( "SUCCESS    - Pokus o nalezeni tridy CountryType vysel");
    } catch (ClassNotFoundException ex) {
        ccpLogger.info("FAIL        - Pokus o nalezeni tridy CountryType nevysel",ex);
    }

    try
    {   
        ListCountryType param = this.of.createListCountryType();
        param.setCodeTask(codeTask);
        param.setLangAlfaCode(langAlfaCode);

        // ziskat seznam zemi pomoci SQL procedury
        List seznamZemi = session.selectList("Cenik.getListCountry", param);

        return seznamZemi; 
    } catch (IllegalArgumentException ex) {
        ccpLogger.error(methodName,ex);
                    ...

    } catch (Exception ex) {
        ccpLogger.error(methodName,ex);
                    ...
    } finally { 
        session.close(); 
    } 
}

Solution

  • The problem is that the server side can't load the class cz.cpost.esb.cenik.schema.CountryType.

    The most likely cause is that the JAR file that defines that class can not been included in the EAR file that you deployed. And the most likely explanation for that is that the POM file for the EAR project doesn't have the required dependency in the required form.

    Note that the dependencies should be listed in a <dependencies> element at the top level on the POM file; i.e. as a child element of the <project> element.


    The first step should be to check whether the JAR file that contains that class is actually in the deployed webapp. (It it is, then the problem is something else ...)

    Then you need to add the requisite dependencies to the EAR module's POM file.


    UPDATE

    Based on the additional information you provided, I think that it is a problem with Ibatis' class loading. It appears that Ibatis is attempting to load the class using a class loader that doesn't include your classes. (My guess is that Ibatis is using the "common classes" class loader ... which doesn't include your application JARs.)

    Apparently, the fix is to do this in your servlet class:

        import com.ibatis.common.resources.Resources;
        ...
        Resources.setDefaultClassLoader(this.getClass().getClassLoader());
    

    References:

    UPDATE #2

    I'm guessing at this point ... but this bug report (http://code.google.com/p/mybatis/issues/detail?id=622) seems to be describing a bug in MyBatis 3.x where setDefaultClassLoader is not working. They seem to be saying "fixed in 3.2.2". I notice that you are using 3.0.5. See if updating your POM's dependency to a later version helps.

    If not, the best I can suggest is to attach a debugger to your Glassfish instance and see if you can dig out what classloader it is using ... and why. It seems pretty clear that it is a classloader issue.