New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Marshalling Objects extending JAXBElement linked by @XmlElementRef #882
Comments
@glassfishrobot Commented |
@glassfishrobot Commented |
@glassfishrobot Commented Using Thing instead of Object and assuming that Sometype has a property name: File f = ...; { System.out.println( "foo: " + foo.getName() ); } Sometype bar = thing.getBar(); { System.out.println( "bar: " + bar.getName() ); } |
@glassfishrobot Commented The PolicySetType defines the following unbounded choice: <xs:choice minOccurs="0" maxOccurs="unbounded"> PolicySetIdReference and PolicyIdReference are both of the type xs:anyURI. The generated List of the object PolicySetType looks like this (I am at home right now not having this in front of me, so this may be a little wrong) (assuming that as:anyURI gets mapped to String): @XmlElementRefs( { __@XmlElementRef(name="PolicySet", ns="...", type=PolicySet.class), __@XmlElementRef(name="Policy", ns="...", type=Policy.class), __@XmlElementRef(name="PolicySetIdReference", ns="...", type=String.class), __@XmlElementRef(name="PolicyIdReference", ns="...", type=String.class), } ) As you can see, the type String appears twice here. So how is JAXB supposed to know, if this is a PolicySetIdReference or a PolicyIdReference? This is the first bug here. However, I tried to get around this by customizing PolicySetIdReference and PolicyIdReference, so that JAXB generates classes for them as well. Now, the List in PolicySetType will look like this: @XmlElementRefs( { __@XmlElementRef(name="PolicySet", ns="...", type=PolicySet.class), __@XmlElementRef(name="Policy", ns="...", type=Policy.class), __@XmlElementRef(name="PolicySetIdReference", ns="...", type=MyPolicySetIdReference.class), __@XmlElementRef(name="PolicyIdReference", ns="...", type=MyPolicyIdReference.class), } ) But now you will get IllegalArgumentsExceptions because @XmlElementRef does not support classes, which extends JAXBElement instead of having a @XmlRootElement annotation on them and all the other things they need to become part of the JAXBContext. MyPolicySetIdReference and MyPolicyIdReference are not known to the JAXBContext. And this results in a blocker, because one is not able to handle this case in any way. I tried everything... I hope, this is more clear now. |
@glassfishrobot Commented JAXB inspects the DOM tree and it is the XML elements' tag names that distinguish between elements - not the classes. And you'll have to do likewise when you inspect the list of choices. (Java 1.7 lets you use switch based on String values.) Below is a full example for you, marshalling and unmarshalling. (If there's still something you don't understand: please use the JAXB users list.) <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" <xs:element name="farm" type="FarmType"/> <xs:complexType name="FarmType"> <xs:element name="hen" type="BirdType"/> <xs:complexType name="BirdType"> <xs:complexType name="MammalType"> </xs:schema> import java.util.; import javax.xml.bind.*; import javax.xml.namespace.QName; import javax.xml.validation.Schema; import generated.*; public class Main { private static final String PACKAGE = "generated"; Main(){ void unmarshal() throws Exception { //assign a schema to the unmarshaller { Schema schema = factory.newSchema( new File( SCHEMA )); m.setSchema(schema); } catch(SAXException e) { e.printStackTrace(); } JAXBElement<?> obj = null; { obj = (JAXBElement<?>)m.unmarshal( new File( XMLIN ) ); } catch( Exception e ) { System.out.println( "EXCEPTION: " + e.getMessage() ); } FarmType farm = (FarmType)obj.getValue(); for( JAXBElement<?> jbe: farm.getHenOrDuckOrCow() ){ { MammalType cp = (MammalType)value; System.out.println( tag + ": " + cp.getName() + " " + cp.getWeight() ); } else if( "cowboy".equals( tag ) || { System.out.println( tag + ": " + (String)value ); } } void marshal() throws Exception { MammalType m1 = of.createMammalType(); farm.getHenOrDuckOrCow().add( of.createMilkmaid( "Milly" ) ); JAXBContext jc = JAXBContext.newInstance( PACKAGE ); m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); if( XMLOUT == null ) { m.marshal( jbe, System.out ); } else { m.marshal( jbe, new FileOutputStream( XMLOUT ) ); } } public static void main( String[] args ) { { main.marshal(); } catch( Exception e ) { System.err.println( "marshal fails: " ); e.printStackTrace(); } try { main.unmarshal(); } catch( Exception e ) { System.err.println( "unmarshal fails: " ); e.printStackTrace(); } } |
@glassfishrobot Commented |
@glassfishrobot Commented |
@glassfishrobot Commented |
|
As described here, there is a difference, how JAXB will generate classes:
http://weblogs.java.net/blog/kohsuke/archive/2006/03/why_does_jaxb_p.html
We have the following schema files:
Schema "references.xsd" with reusable elements:
Schema "object.xsd" with some real constructs like:
JAXB will generate a class for the element object and may name it "Object" with an @XmlRootElement annotation on it, while the property for "foo" and "bar" will get optimized the the type "common:sometype". So we will loose the information about the element "foo" and "bar", which causes problems, because this is a choice and JAXB will no longer be able to differ between them. The List accepts the generated class for "common:sometype" twice.
@XmlElementRefs({
@XmlElementRef(name = "foo", namespace = "...", type = CommonSomeType.class),
@XmlElementRef(name = "bar", namespace = "...", type = CommonSomeType.class),
})
protected List<JAXBElement<? extends Serializable>> fooOrBar;
As you can see, JAXB will not be able to know, which CommonSomeType is a "bar" or "foo".
Because of this, I added the following bindings to the binding.xjb:
Now, JAXB will generate these two classes "Foo" and "Bar" instead of directly using the "common:sometype"-class for these properties and the choice can be marshalled. This issue is not mainly about the "xs:choice", btw.
The problem is, that the JAXBContext cannot find these two classes in the contextPath, because they look like this:
public class Foo extends JAXBElement {
private QName qname = ....
}
When analyzing JAXBContext.toString() I also do not see them in the list of the known classes.
In this example, the generated Object class looks like this:
@XmlRootElement(...)
public class Object {
@XmlElementRef(name="foo", namespace="...", type=Foo.class)
private Foo foo;
}
How is one supposed to be able to marshal classes, that have properties annotated with @XmlElementRef, which link to classes like Foo?
If you want a real world example, try generating classes using the Sun XACML 1.0 policy schema:
http://www.oasis-open.org/committees/tc_home.php?wg_abbrev=xacml#XACML10
The PolicySet element uses such a choice leading to these problems.
The text was updated successfully, but these errors were encountered: