Search code examples
javatomcatherokuclasscastexceptionxmlbeans

Inconsistent ClassCastException for XmlBeans.Factory parse method


I know this is a very long shot, but I've been trying to figure it out for like two weeks already, so any idea pointing to the right direction could be priceless.

So, I have a very old application which uses XmlBeans. My task is to migrate from Tomcat 7.0.67 to Tomcat 8.5.11 introducing Spring Sessions and Spring Security instead of Realm-based authentication. Prior the migration everything was working fine both locally (MacOS, Oracle JDK 8) and on Heroku (Ubuntu, OpenJDK 8), but after the migration everything works on my local environment, but on Heroku, sometimes, when the app tries to parse a string to appropriate XmlBean, this ClassCastException occurs:

java.lang.ClassCastException: foo.bar.2.impl.PreferencesDocumentImpl cannot be cast to foo.bar.1.PreferencesDocument
    at foo.bar.1.PreferencesDocument$Factory.parse(Unknown Source)

I have two auto-generated by XmlBeans classes, which were generated from two xsd-schemas without any namespace set. Classes share the name, but are located in different packages (parse method where the exception occurs is located in the Factory inner class, other methods are omitted):

/*
 * An XML document type.
 * Localname: Preferences
 * Namespace: 
 * Java type: foo.bar.1.PreferencesDocument
 *
 * Automatically generated - do not modify.
 */
package foo.bar.1;

public interface PreferencesDocument extends org.apache.xmlbeans.XmlObject {
    public static final org.apache.xmlbeans.SchemaType type = (org.apache.xmlbeans.SchemaType)
        org.apache.xmlbeans.XmlBeans.typeSystemForClassLoader(PreferencesDocument.class.getClassLoader(), "schemaorg_apache_xmlbeans.system.s2D5798E4F4AFDA8394735C8512CDCBC7").resolveHandle("preferencesa8bfdoctype");

    public static final class Factory {
        public static foo.bar.1.PreferencesDocument parse(java.lang.String xmlAsString) throws org.apache.xmlbeans.XmlException {
          return (foo.bar.PreferencesDocument) org.apache.xmlbeans.XmlBeans.getContextTypeLoader().parse( xmlAsString, type, null ); 
        }   
    }
}

/*
 * An XML document type.
 * Localname: Preferences
 * Namespace: 
 * Java type: foo.bar.1.PreferencesDocument
 *
 * Automatically generated - do not modify.
 */
package foo.bar.1.impl;

public class PreferencesDocumentImpl extends org.apache.xmlbeans.impl.values.XmlComplexContentImpl implements foo.bar.1.PreferencesDocument {
    public PreferencesDocumentImpl(org.apache.xmlbeans.SchemaType sType) {
        super(sType);
    }

    private static final javax.xml.namespace.QName PREFERENCES$0 = new javax.xml.namespace.QName("", "Preferences");
}

/*
 * An XML document type.
 * Localname: Preferences
 * Namespace: 
 * Java type: foo.bar.2.PreferencesDocument
 *
 * Automatically generated - do not modify.
 */
package foo.bar.2;  

public interface PreferencesDocument extends org.apache.xmlbeans.XmlObject {
    public static final org.apache.xmlbeans.SchemaType type = (org.apache.xmlbeans.SchemaType)
        org.apache.xmlbeans.XmlBeans.typeSystemForClassLoader(PreferencesDocument.class.getClassLoader(), "schemaorg_apache_xmlbeans.system.sC8953008EC716AA258D3951B84AB1CB7").resolveHandle("preferencesa8bfdoctype");

    public static final class Factory {
        public static foo.bar.2.PreferencesDocument parse(java.lang.String xmlAsString) throws org.apache.xmlbeans.XmlException {
          return (foo.bar.2.PreferencesDocument) org.apache.xmlbeans.XmlBeans.getContextTypeLoader().parse( xmlAsString, type, null ); }

    }
}

/*
 * An XML document type.
 * Localname: Preferences
 * Namespace: 
 * Java type: foo.bar.2.PreferencesDocument
 *
 * Automatically generated - do not modify.
 */
package foo.bar.2.impl;

public class PreferencesDocumentImpl extends org.apache.xmlbeans.impl.values.XmlComplexContentImpl implements foo.bar.2.PreferencesDocument {

    public PreferencesDocumentImpl(org.apache.xmlbeans.SchemaType sType) {
        super(sType);
    }

    private static final javax.xml.namespace.QName PREFERENCES$0 = 
        new javax.xml.namespace.QName("", "Preferences");
    }
}

Sometimes, when the app deployed to Heroku is restarted, the problem is gone, but after another restart it's back again.

According to this, the root cause is the absence of namespaces which leads to collision. But due to our requirements I can't add or change namespace of the xsds. So do you have any ideas why does it work locally with Tomcat 7, locally with Tomcat 8 and on Heroku with Tomcat 7, but doesn't work on Heroku with Tomcat 8?

Thanks in advance.


Solution

  • It works on Tomcat 7 because of this.

    Prior Tomcat 8 a Tomcat's ClassLoader was loading resources in alphabetical order. Our app was working only because of that.

    It works locally on Tomcat 8 + MacOS, because Tomcat 8's classloader loads resources in an order provided by OS, which, in case of OSX, seems to be ordered.