Skip to content
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

Closed
Tomas-Kraus opened this issue Feb 22, 2012 · 9 comments
Closed

Comments

@Tomas-Kraus
Copy link
Member

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.

@Tomas-Kraus
Copy link
Member Author

@glassfishrobot Commented
Reported by mk0

@Tomas-Kraus
Copy link
Member Author

@glassfishrobot Commented
Was assigned to snajper

@Tomas-Kraus
Copy link
Member Author

@glassfishrobot Commented
laune said:
There is no point in customizing the types of elements and . They can be easily distinguished by their tags. The parent element contains two properties, and after unmarshalling one of them is null, and the other one contains the single child.

Using Thing instead of Object and assuming that Sometype has a property name:

File f = ...;
Thing thing = (Thing)u.unmarshal( f );
Sometype foo = thing.getFoo();
if( foo != null )

{ System.out.println( "foo: " + foo.getName() ); }

Sometype bar = thing.getBar();
if( bar != null )

{ System.out.println( "bar: " + bar.getName() ); }

@Tomas-Kraus
Copy link
Member Author

@glassfishrobot Commented
mk0 said:
Okay, I did a little mistake in my description. The choice is supposed to be an unbounded choice. Please take a look at the Oasis XACML policy schema.

The PolicySetType defines the following unbounded choice:

<xs:choice minOccurs="0" maxOccurs="unbounded">
__<xs:element ref="xacml:PolicySet"/>
__<xs:element ref="xacml:Policy"/>
__<xs:element ref="xacml:PolicySetIdReference"/>
__<xs:element ref="xacml:PolicyIdReference"/>
</xs:choice>

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), }

)
List policySetOrPolicyOrPolicySetIdRef;

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), }

)
List policySetOrPolicyOrPolicySetIdRef;

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.

@Tomas-Kraus
Copy link
Member Author

@glassfishrobot Commented
laune said:
Did you try reading the JAXB Tutorial, especially http://jaxb.java.net/tutorial/section_2_2_12_6-Content-A-Mixed-List-of-Elements.html#Content:%20A%20Mixed%20List%20of%20Elements

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"
xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
version="2.0">

<xs:element name="farm" type="FarmType"/>

<xs:complexType name="FarmType">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element ref="hen"/>
<xs:elemeht ref="duck"/>
<xs:element ref="cow"/>
<xs:element ref="pig"/>
<xs:element ref="cowboy"/>
<xs:element ref="milkmaid"/>
</xs:choice>
<xs:attribute name="Name" type="xs:string"/>
</xs:complexType>

<xs:element name="hen" type="BirdType"/>
<xs:element name="duck" type="BirdType"/>
<xs:element name="cow" type="MammalType"/>
<xs:element name="pig" type="MammalType"/>
<xs:element name="cowboy" type="xs:string"/>
<xs:element name="milkmaid" type="xs:string"/>

<xs:complexType name="BirdType">
xs:sequence
<xs:element name="name" type="xs:string"/>
<xs:element name="color" type="xs:int"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="MammalType">
xs:sequence
<xs:element name="name" type="xs:string"/>
<xs:element name="weight" type="xs:int"/>
</xs:sequence>
</xs:complexType>

</xs:schema>

import java.util.;
import java.io.
;

import javax.xml.bind.*;
import javax.xml.XMLConstants;

import javax.xml.namespace.QName;

import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import org.xml.sax.SAXException;

import generated.*;

public class Main {

private static final String PACKAGE = "generated";
private static final String SCHEMA = "farm.xsd";
private static final String XMLIN = "farm.xml";
private static final String XMLOUT = "farm.xml";

Main(){
}

void unmarshal() throws Exception {
JAXBContext jc = JAXBContext.newInstance( PACKAGE );
Unmarshaller m = jc.createUnmarshaller();

//assign a schema to the unmarshaller
SchemaFactory factory =
SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
try

{ Schema schema = factory.newSchema( new File( SCHEMA )); m.setSchema(schema); }

catch(SAXException e)

{ e.printStackTrace(); }

JAXBElement<?> obj = null;
try

{ obj = (JAXBElement<?>)m.unmarshal( new File( XMLIN ) ); }

catch( Exception e )

{ System.out.println( "EXCEPTION: " + e.getMessage() ); }

FarmType farm = (FarmType)obj.getValue();
System.out.println( farm.getName() );

for( JAXBElement<?> jbe: farm.getHenOrDuckOrCow() ){
String tag = jbe.getName().getLocalPart();
Object value = jbe.getValue();
if( "cow".equals( tag ) ||
"pig".equals( tag ) )

{ MammalType cp = (MammalType)value; System.out.println( tag + ": " + cp.getName() + " " + cp.getWeight() ); }

else if( "cowboy".equals( tag ) ||
"milkmaid".equals( tag ) )

{ System.out.println( tag + ": " + (String)value ); }

}
}

void marshal() throws Exception {
ObjectFactory of = new ObjectFactory();
FarmType farm = of.createFarmType();
JAXBElement jbe = of.createFarm( farm );
farm.setName( "Green River" );

MammalType m1 = of.createMammalType();
m1.setName( "Daisy" );
m1.setWeight( 900 );
farm.getHenOrDuckOrCow().add( of.createCow( m1 ) );
MammalType m2 = of.createMammalType();
m2.setName( "Pinky" );
m2.setWeight( 500 );
farm.getHenOrDuckOrCow().add( of.createPig( m2 ) );

farm.getHenOrDuckOrCow().add( of.createMilkmaid( "Milly" ) );
farm.getHenOrDuckOrCow().add( of.createCowboy( "Jack" ) );

JAXBContext jc = JAXBContext.newInstance( PACKAGE );
Marshaller m = jc.createMarshaller();

m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
// m.setProperty(Marshaller.JAXB_ENCODING, "US-ASCII");

if( XMLOUT == null )

{ m.marshal( jbe, System.out ); }

else

{ m.marshal( jbe, new FileOutputStream( XMLOUT ) ); }

}

public static void main( String[] args ) {
Main main = new Main();
try

{ 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(); }

}
}

@Tomas-Kraus
Copy link
Member Author

@glassfishrobot Commented
snajper said:
Thanks Wolfgang, closing this issue.

@Tomas-Kraus
Copy link
Member Author

@glassfishrobot Commented
Marked as works as designed on Wednesday, February 22nd 2012, 11:56:38 pm

@Tomas-Kraus
Copy link
Member Author

@glassfishrobot Commented
This issue was imported from java.net JIRA JAXB-882

@Tomas-Kraus
Copy link
Member Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants