hansi
hansi

Reputation: 2298

EclipseLink MOXy: Binding working and not working with same XPath

Input:

<?xml version="1.0" encoding="UTF-8"?>
<foo:root xmlns:foo="http://www.domain.org/foo" 
      xmlns="http://www.domain.org/foo"
      xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <a xsi:type="foo:someType">
   <b  text="some text" />
  </a>
</foo:root>

Bindings:

<?xml version="1.0"?>
<xml-bindings
   xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
   package-name="test">

<xml-schema element-form-default="QUALIFIED" namespace="http://www.domain.org/foo">
    <xml-ns prefix="foo" namespace-uri="http://www.domain.org/foo" />
</xml-schema>

 <java-types>        
    <java-type name="Root">
        <xml-root-element name="root"/>
        <java-attributes>
            <xml-element java-attribute="contentRoot" xml-path="." type="test.ContentRoot" />
        </java-attributes>
    </java-type>

    <java-type name="ContentRoot">
        <java-attributes>
            <xml-element java-attribute="text" xml-path="a/b/@text" />
            <xml-element java-attribute="contents" xml-path="a/b" type="test.Content" container-type="java.util.List" />
        </java-attributes>
    </java-type>

    <java-type name="Content">
        <java-attributes>
            <xml-element java-attribute="text" xml-path="@text" />
        </java-attributes>
    </java-type>
</java-types>

</xml-bindings>

Classes in package test:

public class Root {
 private List<ContentRoot> contentRoots = new LinkedList<ContentRoot>();
 public List<ContentRoot> getContentRoots() {
    return contentRoots;
 } 
 public void setContentRoots(List<ContentRoot> contentRoots) {
    this.contentRoots = contentRoots;
 }
 public void setContentRoot(ContentRoot contentRoot) {
    this.contentRoots.add(contentRoot);
 }
}

public class ContentRoot {
 private List<Content> contents;
 private String text;
 public List<Content> getContents() {
    return contents;
 }
 public void setContents(List<Content> contents) {
    this.contents = contents;
 }
 public String getText() {
    return text;
 }
 public void setText(String text) {
    this.text = text;
 }  
}

public class Content  {
 private String text;
 public String getText() {
    return text;
 }
 public void setText(String text) {
    this.text = text;
 }
}

Running Code:

Map<String, Object> jaxbContextProperties = new HashMap<String, Object>(1);
jaxbContextProperties.put(JAXBContextProperties.OXM_METADATA_SOURCE, "bindings.xml");
JAXBContext jaxbContext = JAXBContextFactory.createContext(new Class[] {Root.class}, jaxbContextProperties);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
Root root = (Root)unmarshaller.unmarshal(new File("input.xml"));
System.out.println(root.getContentRoots().get(0).getText());    
System.out.println(root.getContentRoots().get(0).getContents().get(0).getText());

The result is, that text is set in ContentRoot but not in Content (I get a NullPointerException from the last System.out.println()). Can somebody tell me why?

Upvotes: 1

Views: 835

Answers (1)

bdoughan
bdoughan

Reputation: 148977

There are a few items that are tripping you up:

ISSUE #1 - ContentRoot Contains Invalid Mappings

EclipseLink JAXB (MOXy) does not allow the following combination of mappings. With the second xml-element you are telling MOXy to map the contents properties to the repeating element b that occur within the element a. With the first xml-element you are trying to map the text attribute of one of the potentially many b elements to the String property text.

<java-type name="ContentRoot">
    <java-attributes>
        <xml-element java-attribute="text" xml-path="a/b/@text" />
        <xml-element java-attribute="contents" xml-path="a/b" type="test.Content" container-type="java.util.List" />
    </java-attributes>
</java-type>

The correct place to map the text attribute is on the Content class which you already had. Instead of using the xml-element mapping with the xml-path pointing at the attribute (which would work), I would recomment using the xml-attribute mapping and specifying a name.

<java-type name="Content">
    <java-attributes>
        <xml-attribute java-attribute="text"/>
    </java-attributes>
</java-type>

ISSUE #2 - xml-path Is Not Properly Namespace Qualified

Since in bindings.xml you have associated the foo prefix with the http://www.domain.org/foo namespace URI.

<xml-schema element-form-default="QUALIFIED" namespace="http://www.domain.org/foo">
    <xml-ns prefix="foo" namespace-uri="http://www.domain.org/foo" />
</xml-schema>

When you specify the xml-path you need to include the prefix in order to get the correct namespace qualification.

<java-type name="ContentRoot">
    <java-attributes>
        <xml-element java-attribute="contents" xml-path="foo:a/foo:b"/>
    </java-attributes>
</java-type>

For More Information

Alternatively you could have mapped it with an xml-element-wrapper as follows:

<java-type name="ContentRoot">
    <java-attributes>
        <xml-element java-attribute="contents" name="b">
            <xml-element-wrapper name="a"/>
        </xml-element>
    </java-attributes>
</java-type>

ISSUE #3 - xml-path="." Can Not Be Used on a Collection Property

Currently MOXy requires that each item in a collection correspond to its own element. This means that at this time you can not specify the self XPath . for collection properties.


Full Example

Your demo code didn't seem to fully match your domain model. Here is a complete example that pulls everything together:

Root

package test;

public class Root {

    private ContentRoot contentRoot;

    public ContentRoot getContentRoot() {
        return contentRoot;
    }

    public void setContentRoot(ContentRoot contentRoot) {
        this.contentRoot = contentRoot;
    }

}

ContentRoot

package test;

import java.util.List;

public class ContentRoot {

    private List<Content> contents;

    public List<Content> getContents() {
        return contents;
    }

    public void setContents(List<Content> contents) {
        this.contents = contents;
    }

}

Content

package test;

public class Content {

    private String text;

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

}

bindings.xml

<?xml version="1.0"?>
<xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    package-name="test"
    xml-accessor-type="FIELD">

    <xml-schema element-form-default="QUALIFIED" namespace="http://www.domain.org/foo">
        <xml-ns prefix="foo" namespace-uri="http://www.domain.org/foo" />
    </xml-schema>

    <java-types>
        <java-type name="Root">
            <xml-root-element/>
            <java-attributes>
                <xml-element java-attribute="contentRoot" xml-path="."/>
            </java-attributes>
        </java-type>

        <java-type name="ContentRoot">
            <java-attributes>
                <xml-element java-attribute="contents" xml-path="foo:a/foo:b"/>
            </java-attributes>
        </java-type>

        <java-type name="Content">
            <java-attributes>
                <xml-attribute java-attribute="text"/>
            </java-attributes>
        </java-type>
    </java-types>

</xml-bindings>

Demo

package test;

import java.io.File;
import java.util.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.JAXBContextFactory;
import org.eclipse.persistence.jaxb.JAXBContextProperties;

public class Demo {

    public static void main(String[] args) throws Exception {
        Map<String, Object> jaxbContextProperties = new HashMap<String, Object>(1);
        jaxbContextProperties.put(JAXBContextProperties.OXM_METADATA_SOURCE, "test/bindings.xml");
        JAXBContext jaxbContext = JAXBContextFactory.createContext(new Class[] {Root.class}, jaxbContextProperties);

        Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
        Root root = (Root)unmarshaller.unmarshal(new File("src/test/input.xml"));
        System.out.println(root.getContentRoot().getContents().get(0).getText());

        Marshaller marshaller = jaxbContext.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(root, System.out);
    }

}

Upvotes: 1

Related Questions