Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot cast to type though type

I get the following exception when I want to cast one type to another.

java.lang.ClassCastException: org.paston.certification.data.impl.BRL6000 
cannot be cast to org.paston.certification.data.Certification

BRL6000 extends the Certification. So in my understanding I should be able to cast a object of the type BRL6000 to the Certification type.

This is the code where the exception occurs.

Object certification = ch.getCertificationData(process, version);
Certification c = (Certification)certification;

Deploying

The application is deployed from Eclipse to a Tomcat 7 server. My application uses a few JARs from the Tomcat environment (e.g. Bonita_Server.jar).

My application is (in Eclipse) a dynamic web projects which references an other project (Certificationnl) which contains the classes Certification and BRL6000. Project Certificationnl is added to the webproject's WAR when I deploy the application to Tomcat.

Classes

The BRL6000 class

package org.paston.certification.data.impl;

import org.paston.certification.data.Certification;
import org.paston.certification.data.CertificationStep;

public class BRL6000 extends Certification{

    /**
     * 
     */
    public static final long serialVersionUID = -8215555386637513536L;
    public static final String processName = "BRL6000";

}

Certification class

package org.paston.certification.data;

import java.util.ArrayList;
import java.util.List;

import org.ow2.bonita.facade.runtime.impl.AttachmentInstanceImpl;

public class Certification implements java.io.Serializable{

    public enum Section{
        ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN
    }
    /**
     * SerializationVersionUID
     */
    private static final long serialVersionUID = -5158236308772958478L;


}

getCertificationData

public Object getCertificationData(String process, String version) {
    if (loginContext == null)
        login();

    System.out.println("Process: "+ process + " Version: "+ version);
    ProcessDefinitionUUID pdu = new ProcessDefinitionUUID(process, version);

    QueryRuntimeAPI queryRuntimeAPI = AccessorUtil
            .getQueryRuntimeAPI();

    try {
        Set<ProcessInstance> processInstances = queryRuntimeAPI
                .getProcessInstances(pdu);

        if (processInstances.size() != 1)
            System.out.println("Best number of instances is 1. Found: "
                    + processInstances.size());

        for (ProcessInstance processInstance : processInstances) {
            Map<String, Object> variables = processInstance
                    .getLastKnownVariableValues();
            if (((Boolean) variables.get("active")) == true) {              
                return variables.get("certification");
            }
        }
    } catch (ProcessNotFoundException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    return null;
}

Update with code as a Servlet

package org.paston.certification.servlet;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.paston.certification.CertificationHandler;
import org.paston.certification.data.Certification;
import org.paston.certification.data.CertificationI;

/**
 * Servlet implementation class SomeServlet
 */
public class SomeServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public SomeServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        CertificationHandler ch = new CertificationHandler();
        String process = request.getParameter("p");
        String version = request.getParameter("v");
        Object certification = ch.getCertificationData(process, version);
        Class<?> clazz = certification.getClass();
        while (clazz != null) {
            System.out.println(clazz.getName());
            clazz = clazz.getSuperclass();
        }

        Class c1 = certification.getClass().getSuperclass();
        Class c2 = Certification.class;

        System.out.println("c1 is " + c1 + ", c2 is " + c2);
        System.out.println("c1 == c2 is " + (c1 == c2));
        System.out.println("c1.equals(c2) is " + c1.equals(c2));
        System.out.println("c1.getName().equals(c2.getName()) is "
                + c1.getName().equals(c2.getName()));
        System.out.println("c1.getClassLoader() == c2.getClassLoader() is "
                + (c1.getClassLoader() == c2.getClassLoader()));

        CertificationI c = (CertificationI) certification;

        PrintWriter out = response.getWriter();
        out.println("Hello World");
    }

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
    }

}

Console output of the Servlet:

Process: BRL6000 Version: 1.0 org.paston.certification.data.impl.BRL6000 org.paston.certification.data.Certification java.lang.Object c1 is class org.paston.certification.data.Certification, c2 is class org.paston.certification.data.Certification c1 == c2 is false c1.equals(c2) is false c1.getName().equals(c2.getName()) is true c1.getClassLoader() == c2.getClassLoader() is false

There is also one other issue that maybe hinting for the problem. Every 10 seconds I see the following Exception in the console:

May 07, 2013 2:09:45 PM org.apache.catalina.loader.WebappClassLoader loadClass
INFO: Illegal access: this web application instance has been stopped already.  Could not load org.ow2.bonita.runtime.tx.StandardTransaction.  The eventual following stack trace is caused by an error thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access, and has no functional impact.
java.lang.IllegalStateException
    at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1566)
    at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1526)
    at org.ow2.bonita.util.ReflectUtil.loadClass(ReflectUtil.java:68)
    at org.ow2.bonita.env.descriptor.ObjectDescriptor.construct(ObjectDescriptor.java:195)
    at org.ow2.bonita.env.WireContext.construct(WireContext.java:521)
    at org.ow2.bonita.env.WireContext.create(WireContext.java:498)
    at org.ow2.bonita.env.WireContext.create(WireContext.java:484)
    at org.ow2.bonita.env.WireContext.get(WireContext.java:456)
    at org.ow2.bonita.env.WireContext.get(WireContext.java:343)
    at org.ow2.bonita.env.WireContext.get(WireContext.java:746)
    at org.ow2.bonita.env.BasicEnvironment.get(BasicEnvironment.java:151)
    at org.ow2.bonita.env.BasicEnvironment.get(BasicEnvironment.java:142)
    at org.ow2.bonita.util.EnvTool.getEnvClass(EnvTool.java:175)
    at org.ow2.bonita.util.EnvTool.getTransaction(EnvTool.java:84)
    at org.ow2.bonita.runtime.tx.StandardTransactionInterceptor.execute(StandardTransactionInterceptor.java:42)
    at org.ow2.bonita.services.impl.EnvironmentInterceptor.execute(EnvironmentInterceptor.java:40)
    at org.ow2.bonita.services.impl.RetryInterceptor.execute(RetryInterceptor.java:59)
    at org.ow2.bonita.runtime.event.EventExecutorThread.run(EventExecutorThread.java:61)

Update 2

I came to understand the problem a little better. The Bonitaserver ( AccessorUtil ) does load the Certification object as well. It somewhere loads the classes from Certification.jar1620768823629427276.tmp which the Bonitaserver created when a process was uploaded to the server.

Also, I found a class ReflectUtil (link) which is probably used to load those classes.

What I tried is to load the classes at the start of the doGet for both this (the servlet) as the ClassLoader of the AccessorUtil. Both with the same old result.

    ArrayList<String> classesNames = new ArrayList<String>();
    classesNames.add("org.paston.certification.data.Certification");
    classesNames.add("org.paston.certification.data.CertificationI");
    classesNames.add("org.paston.certification.data.impl.BRL6000");
    ClassLoader cl = this.getClass().getClassLoader();
    Class<?>[] classes =  ReflectUtil.loadClasses(cl, classesNames);

Update 3

Results of the following code proposed by @GaborSch. The code as I used it:

    System.out.println("--- Test ClassLoader certification object---");
    ClassLoader cl1 = certification.getClass().getSuperclass().getClassLoader();
    while (cl1 != null) {
        System.out.println(cl1.getClass().getCanonicalName() + " " + cl1.hashCode() + " " + cl1);
        cl1 = cl1.getParent();
    }
    System.out.println("--- Test ClassLoader Certification class---");
    ClassLoader cl2 = Certification.class.getClassLoader();
    while (cl2 != null) {
        System.out.println(cl2.getClass().getCanonicalName() + " " + cl2.hashCode() + " " + cl2);
        cl2 = cl2.getParent();
    }

Result of the code:

--- Test ClassLoader certification object---

org.ow2.bonita.runtime.ProcessClassLoader 451656
org.ow2.bonita.runtime.ProcessClassLoader@6e448
org.ow2.bonita.runtime.VirtualCommonClassloader 1182018350
org.ow2.bonita.runtime.VirtualCommonClassloader@46742b2e
org.apache.catalina.loader.StandardClassLoader 318536939
org.apache.catalina.loader.StandardClassLoader@12fc7ceb
sun.misc.Launcher.AppClassLoader 1667514408
sun.misc.Launcher$AppClassLoader@63644028
sun.misc.Launcher.ExtClassLoader 1253061906
sun.misc.Launcher$ExtClassLoader@4ab03512

--- Test ClassLoader Certification class--- 

org.apache.catalina.loader.WebappClassLoader 2136824254
WebappClassLoader   context: /Certification   delegate: false  
repositories:
    /WEB-INF/classes/
----------> Parent Classloader: org.apache.catalina.loader.StandardClassLoader@12fc7ceb

org.apache.catalina.loader.StandardClassLoader 318536939
org.apache.catalina.loader.StandardClassLoader@12fc7ceb
sun.misc.Launcher.AppClassLoader 1667514408
sun.misc.Launcher$AppClassLoader@63644028
sun.misc.Launcher.ExtClassLoader 1253061906
sun.misc.Launcher$ExtClassLoader@4ab03512
like image 838
Roel Veldhuizen Avatar asked May 06 '13 13:05

Roel Veldhuizen


2 Answers

I suspect that the root of the problem lies in the runtime environment.

Your Certification data ultimately come from AccessorUtil.getQueryRuntimeAPI(), which is a static method run by Tomcat, so all object instances coming from it are likely to be loaded by the Tomcat classloader.

If you copy the jar file under Tomcat, it will load it with its own classloader. Your eclipse-run code although uses a different classloader, and if they have no common ancestor classloader where this Certification class is loaded, they will be considered as a different Class.

I suggest to review your runtime class paths, remove libs from you Tomcat, or (worst case) run your code within Tomcat (e.g. as a Servlet in the same app).

Update:

Based on the project deployment description I believe that

  • The AccessorUtil class is loaded by a common class loader
  • The lists inside are populated by Certification instances from your Certificationnl classes from your Tomcat deployment
  • You can access those objects through the AccessorUtil class, but the class definitions are not accessible from your Eclipse code.

It's a bad idea to access Tomcat-defined objects from outside on JVM-level. Either put your code into the same container, or use a properly-defined interface (e.g. web services).

If you want to stay with the current project setup, you could also use interfaces that are put into the common classloader (next to AccessorUtil), and cast to the interface.

Update 2:

To check the class loader hierarchy, execute a code like this:

Class c1 = certification.getClass().getSuperclass().getClassLoader();
while (c1 != null) {
    System.out.println(c1.getClass().getCanonicalName() + " " + c1.hashCode() + " " + c1);
    c1 = c1.getParent();
}

Class c2 = Certification.class.getClassLoader();
while (c2 != null) {
    System.out.println(c2.getClass().getCanonicalName() + " " + c2.hashCode() + " " + c2);
    c2 = c2.getParent();
}

You can find the common ancestor by comparing the stack. The best way to go on is still debugging.

Update 3:

It is visible that your common classloader is org.apache.catalina.loader.StandardClassLoader. On top of that

  • org.apache.catalina.loader.WebappClassLoader is present for your webapp (this is where you want to cast to)
  • org.ow2.bonita.runtime.VirtualCommonClassloader and org.ow2.bonita.runtime.ProcessClassLoader are present where the objects come from (that's where they're instantiated)

It seems that Bonita is using some kind of classloading mechanism.

The solution is that you make the StandardClassloader (or any other classloader) to load your classes. Since Certification (and thus BRL6000) are dependent to Bonita classes, you must put Bonita_Server.jar into endorsed. See the Tomcat Classloader HOWTO, it may give you more insight.

Update 4:

To do the serializing/deserializing recommended in my comments, you can use this piece of code:

Certification certification = null;
try {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(baos);
    oos.writeObject(ch.getCertificationData(process, version));
    oos.flush();
    certification = (Certification) new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray())).readObject();
} catch (IOException | ClassNotFoundException ex) {
}

Note that the (Certification) cast is made to the currently loaded class.

Update 5:

The final resolution was not using direct Java class-level access, but rather using a proper API to do the same trick.

like image 79
gaborsch Avatar answered Nov 02 '22 06:11

gaborsch


I think there are two possible explanations:

  • You have managed to load the Certification class in two different classloaders.
  • You have two Certification classes that have names that just look the same.

Here's what I suggest you do to figure out what is going on.

  • Before the statement that does (Certification) certification insert the following:

    // Get the classes that are being compared in the typecast.
    Class c1 = certification.getClass().getSuperclass();
    Class c2 = Certification.class;
    
    System.out.println("c1 is " + c1 + ", c2 is " + c2);
    System.out.println("c1 == c2 is " + c1 == c2);
    System.out.println("c1.equals(c2) is " + c1 == c1.equals(c2));
    System.out.println("c1.getName().equals(c2.getName()) is " + 
                       c1.getName().equals(c2.getName()));
    System.out.println("c1.getClassLoader() == c2.getClassLoader() is " +
                       c1.getClassLoader() == c2.getClassLoader());
    
  • Check that the first line gives (what looks to be) the same names for c1 and c2. (If not, we've selected the wrong classes to compare. Tweak my code to get the right ones.)

  • The c1 == c2 and c2.equals(c2) tests should give the same answer, and I predict it will be false.

  • Comparing the names is the tests that distinguishes the two alternative explanations:

    • If the names are equal, it is indicative of a classloader problem.
    • If the names are not equal, then you have two different classes whose names look the same but actually aren't. (How can that be? Well Java uses Unicode, and some of the European charactersets use the same "glyph" for different characters. Depending on the fonts you are using, you get pairs of characters that look the same when you view them in an editor / IDE, but in fact aren't.
  • The final test will confirm that c1 and c2 do or do not have the same classloader.


This won't explain why your web application is in this state, but it will clearly tell you whether the problem is classloaders, funky class names ... or something else.

like image 33
Stephen C Avatar answered Nov 02 '22 08:11

Stephen C