users@jaxb.java.net

Issue with types that refer to themselves, and arrays

From: Reif, Benjamin <benjamin.reif_at_cgi.com>
Date: Mon, 15 Sep 2008 14:30:20 -0700

All,

 

Sorry for the long email, but I just wanted to include the necessary
detail for clarity. I am using JAXB annotations (2.1.6) in my Java
classes to generate Schema / WSDL.

I have many methods that use Collections, which I would like to be
wrapped array types in the schema. However, I would like the schema to
have seperate, named,

complexTypes for the arrays so that they can be reused
(@XmlElementWrapper seems to make an anonymous complexType inside an
element,

which can't be reused).

 

I have found that simply specifying the Array class on the @XmlElement
type attribute works in most cases, and creates a reusable Array
complexType in the Schema:

 

@XmlElement(name="myObjects", type=MyObject[].class,
namespace="http://security.myProject.myCompany.com/", nillable=true,
required=false)

public List<MyObject> getMyObjects() {

        return objs;

}

 

Assuming that this method is in another class called Foo, this gives
you:

 

<xs:complexType name="Foo">

    <xs:sequence>

      <xs:element form="qualified" minOccurs="0" name="myObjects"
nillable="true" type="tns:MyObjectArray"/>

    </xs:sequence>

  </xs:complexType>

 

<xs:complexType final="#all" name="MyObjectArray">

    <xs:sequence>

      <xs:element maxOccurs="unbounded" minOccurs="0" name="item"
nillable="true" type="tns:MyObject"/>

    </xs:sequence>

  </xs:complexType>

 

The problem I have found however, is that this does not work when you
have circular dependencies, where an object references

another object, which then refers to itself. The example I'm using is
that a User can belong to many Groups. In addition, a Group can be made
up

of one or more Groups as well. So I have:

 

@XmlType(name="User",
namespace="http://security.myProject.myCompany.com/", propOrder={""})

public class User {

 

    @XmlElement(name="userGroups", type=Group[].class,
namespace="http://security.myProject.myCompany.com/", nillable=true,
required=false)

    public List<Group> getUserGroups() {

        return groups;

    }

}

 

@XmlType(name="Group",
namespace="http://security.myProject.myCompany.com/", propOrder={""})

public class Group {

 

    @XmlElement(name="groups", type=Group[].class,
namespace="http://security.myProject.myCompany.com/", nillable=true,
required=false)

    public List<Group> getGroups() {

        return groups;

    }

}

 

But when I try to annotate it this way I get:

 

"2 counts of IllegalAnnotationExceptions Two classes have the same XML
type name "{http://security.myProject.myCompany.com/}GroupArray". Use
@XmlType.name and

@XmlType.namespace to assign different names to them.

   this problem is related to the following location:

           at com.myCompany.myProject.security.Group[]

           at public java.util.List
com.myCompany.myProject.security.Group.getGroups()

           at com.myCompany.myProject.security.Group

           at com.myCompany.myProject.security.User"

 

I think the problem is from
com.sun.xml.bind.v2.model.impl.ModelBuilder.addTypeName(NonElement<T, C>
r), where it checks if 'old' is not equal to null:

 

  private void addTypeName(NonElement<T, C> r) {

        QName t = r.getTypeName();

        if(t==null) return;

 

        TypeInfo old = typeNames.put(t,r);

        if(old!=null) {

            // collision

            reportError(new IllegalAnnotationException(

 
Messages.CONFLICTING_XML_TYPE_MAPPING.format(r.getTypeName()),

                    old, r ));

        }

    }

 

I think this is incorrect, since it is possible that the type can be
referenced multiple times from multiple classes in the same package, or
even referenced by itself;

which in this case is why it's not null and is throwing the error. I
think the correct logic would be to implement TypeInfo.equals() method,
something like:

 

 public boolean equals(Object obj){

        if(this.getClass.isAssignableFrom(obj.getClass())){

                TypeInfo ti = (TypeInfo)obj;

                if(this.getType().equals(ti.getType()) &&
this.getTypeName().equals(ti.getTypeName())){

                        return true;

                }

        }

        return false;

 }

 

 Then addTypeName() should check:

 

if(old!=null && !old.equals(r)){

      // collision

      reportError(new IllegalAnnotationException(

 
Messages.CONFLICTING_XML_TYPE_MAPPING.format(r.getTypeName()),

                    old, r ));

}

 

I'm wondering if this seems like a legitimate defect and if the JAXB
team thinks this is a reasonable solution, or if anyone else has found
another

way around it without having to change the source code.

 

Thanks,

Ben