Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Two classes have the same XML type name "objectFactory"

We have been using JAXB 2.1 for a long time in our system. We have a platform that is built with Ant and generates a bunch of bundles that are deployed in an OSGi runtime. We use Java SE 6.

We use JAXB during the build process to generate datatypes from different schemas. Those classes are packaged in the bundles and used in runtime to serialize/deserialize content. In addition we use JAXB in our platform in runtime to generate datatypes from other schemas provided by the user (it a sort of MDA platform).

In the OSGi runtime we have a bundle that has the JAXB jars and exports the necessary packages. We create a JAXBContext instance with the context path of all the object factories generated, so we can marshall/unmarshall all our datatypes.

That has been working so far but right now we are trying to upgrade to the latest stable version of JAXB (2.2.4) and we are having problems trying to create the context in runtime. We get the following exception:

Two classes have the same XML type name "objectFactory". Use @XmlType.name and @XmlType.namespace to assign different names to them.
    this problem is related to the following location:
        at some.package.ObjectFactory
    this problem is related to the following location:
        at some.other.package.ObjectFactory

    at com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException$Builder.check(IllegalAnnotationsException.java:91)
    at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.getTypeInfoSet(JAXBContextImpl.java:436)
    at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.<init>(JAXBContextImpl.java:277)
    at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl$JAXBContextBuilder.build(JAXBContextImpl.java:1100)
    at com.sun.xml.internal.bind.v2.ContextFactory.createContext(ContextFactory.java:143)
    at com.sun.xml.internal.bind.v2.ContextFactory.createContext(ContextFactory.java:110)
    at com.sun.xml.internal.bind.v2.ContextFactory.createContext(ContextFactory.java:191)
    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 javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:187)
    ... 76 more

The error Two classes have the same XML type name "objectFactory" is printed for each of the object factories generated during the build process.

We have seen several posts in SO with the same error but applying to the generated types, not to the object factory. We think that JAXB may not be identifying the ObjectFactory class as an object factory but as a datatype.

One possibility was that we were using the internal version of JAXB in Java 6, so we decided to use the System Property -Djava.endorsed.dirs and put the three jars (jaxb-api-2.2.4.jar, jaxb-impl-2.2.4.jar and jaxb-xjc-2.2.4.jar) in that path, but still not working.

We think that the problem might be that we are using a different version of JAXB in the OSGi runtime and in the build process, so the generated code is not compatible. But maybe we are wrong and there is another problem.

Do you have any ideas?

Thanks in advance.

(Edit: more details on this)

We create the JAXBContext in this way:

    ClassLoader classLoader = new JAXBServiceClassLoader(getParentClassLoader(),
                                                         Collections.unmodifiableMap(objectFactories));
    context = JAXBContext.newInstance(contextPath.toString(), classLoader);

where contextPath is a String that contains all our object factories separated by ':', and the JAXBServiceClassLoader is:

  private static final class JAXBServiceClassLoader extends ClassLoader
  {
    @NotNull
    private final Map<String, Object> objectFactories;

    private JAXBServiceClassLoader(@NotNull ClassLoader parent, @NotNull Map<String, Object> objectFactories)
    {
      super(parent);
      this.objectFactories = objectFactories;
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException
    {
      Class<?> ret;
      try
      {
        ret = super.loadClass(name);
      }
      catch (ClassNotFoundException e)
      {
        Object objectFactory = objectFactories.get(name);
        if (objectFactory != null)
        {
          ret = objectFactory.getClass();
        }
        else
        {
          throw new ClassNotFoundException(name + " class not found");
        }
      }
      return ret;
    }
  }

(Edit: after Aaron's post)

I've been debugging all the internals of JAXBContextImpl and the thing is that the JAXBContextImpl is trying to get the type info from our ObjectFactory classes, which is wrong. In fact, in com.sun.xml.internal.bind.v2.model.impl.ModelBuilder:314, the getClassAnnotation() call returns null but when I see the instance I can see the annotation XmlRegistry.

The thing is that, at that point XmlRegistry.class.getClassLoader() returns null, but if I run ((Class)c).getAnnotations()[0].annotationType().getClassLoader() it returns the classLoader of the OSGi bundle "lib.jaxb" that contains my JAXB jars, which is correct.

So, I guess that we are loading at the same time two different versions of XmlRegistry, one from the JDK and the other one from JAXB 2.2.4 jars. The question is: why?

And, even more, instead of loading all those com.sun.xml.internal.* classes (like JAXBContextImpl), shouldn't be loading and executing com.sun.xml.bind.v2.runtime.JAXBContextImpl from JAXB jars? During the debug process I can see that it's doing some stuff with reflection but I don't understand why is doing that.

like image 506
Denian Avatar asked Sep 09 '11 12:09

Denian


1 Answers

We finally found a solution for this.

From the JAXB documentation (Discovery of JAXB implementation section):

http://jaxb.java.net/nonav/2.2.4-1/docs/api/javax/xml/bind/JAXBContext.html

We tried to add a resource in META-INF/services/javax.xml.bind.JAXBContext in order to enforce the use of com.sun.xml.bind.v2.ContextFactory instead of Sun's internal factory. That didn't work, probably because we are using an OSGi bundle.

Anyway, as we are using our own classloader, we override the getResourceAsStream() method, that is called from ContextFinder:343:

@Override
public InputStream getResourceAsStream(String name)
{
  if (name!=null && name.equals("META-INF/services/javax.xml.bind.JAXBContext"))
  {
    return new ByteArrayInputStream("com.sun.xml.bind.v2.ContextFactory".getBytes());
  }
  return super.getResourceAsStream(name);
}

This is not the prettiest solution but it works for us. And that classloader is used only when we create the JAXBContext, it should be fine.

like image 142
Denian Avatar answered Sep 21 '22 08:09

Denian