0

I'm trying to marshal and unmarshal the following "attrName" and "attrType" XML-Elements into a single class. (At the moment I read the values individually and construct my objects after the unmarshalling in Java.)

<wrapper>
    <someOtherElement>xxx</someOtherElement>
    <attrName ref="a">xxx</attrName>
    <attrName ref="b">xxx</attrName>
    <attrName ref="c">xxx</attrName>
    <attrType attrRef="a">xxx</attrType>
    <attrType attrRef="b">xxx</attrType>
    <someOtherElement>xxx</someOtherElement>
</wrapper>

The "ref" XML-Attribute is used to identify a attribute and as a reference for the "attrType" XML-Element. But the "attrType" XML-Element is optional and must not be there. There can not be a "attrType" XML-Element without a "attrName" XML-Element.

I need to generate a List of "attribute" objects of the class:

package example;

public class Attribute {

    private String name;

    private String ref;

    private String type;

    public String  getName() {
        return name;
    }

    public void setName(String name) {
        this.name= name;
    }

    public String  getRef() {
        return ref;
    }

    public void setRef(String ref) {
        this.ref= ref;
    }

    public String  getType() {
        return type;
    }

    public void setType(String type) {
        this.type= type;
    }
}

I already found following relating question. But It didn't help me in finding a solution for my problem. The problem is to find all related attribute names and types to construct a Java object.

I'd be grateful for any tips or advice in the right direction. If I did not explained anything satisfactory, please don't hesitate to ask as English is not my native tongue.

PS: I know I could use a different XML structure and solve the problem easily. But that is not possible for me.

Community
  • 1
  • 1
Jonas
  • 1
  • 1
  • 1
  • To clarify. What I'd like to know is how to annotate the "Attribute" class (and/or helper classes) to describe the XML Schema for the described case. – Jonas May 10 '16 at 18:26

2 Answers2

0

Your XML file doesn't follow any schema, so you can only rely on its root element name and the fact it is well-formed. The following maps it to a generic Wrapper object and then processes it to produce the list of Attribute objects:

public class JAXBAttribute {

    public static void main(String[] args) throws JAXBException {
        JAXBContext context = JAXBContext.newInstance(new Class[] {Wrapper.class});
        Wrapper wrapper = (Wrapper)context.createUnmarshaller().unmarshal(Thread.currentThread().getContextClassLoader().getResourceAsStream("wrapper.xml"));
        final Map<String,String> attributeTypeMap = new HashMap<String,String>();
        for(final AttributeTypeMapEntry entry : wrapper.getEntryList()) {
            attributeTypeMap.put(entry.getKey(), entry.getValue());
        }
        for(final Attribute a : wrapper.getAttributeObjectList()) {
            a.setType(attributeTypeMap.get(a.getRef()));
        }
        System.out.println(wrapper.getAttributeObjectList());
    }

}

@XmlRootElement(name="wrapper")
@XmlAccessorType(XmlAccessType.NONE)
class Wrapper {

    @XmlElement(name="attrName")
    private List<Attribute> attributeObjectList;

    @XmlElement(name="attrType")
    private List<AttributeTypeMapEntry> entryList;

    public List<Attribute> getAttributeObjectList() {
        return attributeObjectList;
    }

    public List<AttributeTypeMapEntry> getEntryList() {
        return entryList;
    }
}

@XmlAccessorType(XmlAccessType.NONE)  // Only annotated fields will be mapped
class AttributeTypeMapEntry {

    @XmlAttribute(name="attrRef")
    private String key;

    @XmlValue
    private String value;

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "AttributeTypeMapEntry [key=" + key + ", value=" + value + "]";
    }
}

@XmlAccessorType(XmlAccessType.NONE) // Only annotated fields will be mapped
class Attribute {

    @XmlValue
    private String name;

    private String type;

    @XmlAttribute(name="ref")
    private String ref;

    public String  getName() {
        return name;
    }

    public void setName(String name) {
        this.name= name;
    }

    public String getRef() {
        return ref;
    }

    public void setRef(String ref) {
        this.ref= ref;
    }

    public String  getType() {
        return type;
    }

    public void setType(String type) {
        this.type= type;
    }

    @Override
    public String toString() {
        return "Attribute [name=" + name + ", type=" + type + ", ref=" + ref + "]";
    }
}
dimplex
  • 494
  • 5
  • 9
  • Thank you very much for the thorough answer. As stated in the first paragraph in brackets that is similar to what I'm doing at the moment. My real use case of the XML is much more complex. This is only a small snippet for clarity. Is there really no other way to let JAXB do the work? – Jonas May 10 '16 at 15:44
  • In order to utilize the JAXB mapping power you have to know the schema of your XML. Otherwise you do it in a generic way. – dimplex May 10 '16 at 16:10
  • OK, I think I know what you mean. I use Java annotations for XML Schema mapping. And what I'd like to know is how to annotate the class (and/or helper classes) to describe the XML Schema for the described case. – Jonas May 10 '16 at 16:27
  • I updated the example with the best you can achieve with your data structure. You cannot use an XmlAdapter to create the HashMap because there is no wrapper element around collection. You can not fill-in Attribute.type on the fly because you need to collect all types first. Hope this helps. – dimplex May 11 '16 at 14:25
  • Thanks you very much for the effort it is much appreciated! I will definitely have a look at the solution a bit later, as I've got a different task at the moment. – Jonas May 19 '16 at 09:15
0

This is another solution for the same task. The idea is to transform initial document into a straight-to-map XML form, while resolving all references and then map the resulting XML to Java objects.

The XSLT for your data would be:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" indent="yes"/>

    <xsl:template match="/wrapper">
        <xsl:variable name="root" select="."/>
        <xsl:copy>
            <xsl:for-each select="attrName">
                <xsl:variable name="ref" select="@ref"></xsl:variable>
                <attribute>
                    <name><xsl:value-of select="text()"/></name>
                    <ref><xsl:value-of select="$ref"/></ref>
                    <type><xsl:value-of select="$root/attrType[@attrRef=$ref]/text()"/></type>
                </attribute>
            </xsl:for-each>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="text()"/>
</xsl:stylesheet>

The straight-to-map XML would look like:

<?xml version="1.0" encoding="utf-8"?>
<wrapper>
   <attribute>
      <name>xxx</name>
      <ref>a</ref>
      <type>xxx</type>
   </attribute>
   <attribute>
      <name>xxx</name>
      <ref>b</ref>
      <type>xxx</type>
   </attribute>
   <attribute>
      <name>xxx</name>
      <ref>c</ref>
      <type/>
   </attribute>
</wrapper>

The code with annotations would be as follows:

public class JAXBAttribute {

    public static void main(String[] args) throws Exception {
        // Transform initial XML resolving all references, resulting in an straight-to-map XML
        final Transformer t = TransformerFactory.newInstance().newTransformer(
            new StreamSource(Thread.currentThread().getContextClassLoader().getResourceAsStream("wrapper.xsl")));
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        t.transform(
            new StreamSource(Thread.currentThread().getContextClassLoader().getResourceAsStream("wrapper.xml")), 
            new StreamResult(baos));
        // Create Java objects from a straight-to-map XML
        JAXBContext context = JAXBContext.newInstance(new Class[] {Wrapper.class});
        Wrapper wrapper = (Wrapper)context.createUnmarshaller().unmarshal(new ByteArrayInputStream(baos.toByteArray()));
        //
        System.out.println(wrapper.getAttributeObjectList());
    }

}

@XmlRootElement(name="wrapper")
@XmlAccessorType(XmlAccessType.FIELD)
class Wrapper {

    @XmlElement(name="attribute")
    private List<Attribute> attributeObjectList;

    public List<Attribute> getAttributeObjectList() {
        return attributeObjectList;
    }
}

@XmlAccessorType(XmlAccessType.FIELD)
class Attribute {

    private String name;

    private String type;

    private String ref;

    public String  getName() {
        return name;
    }

    public void setName(String name) {
        this.name= name;
    }

    public String getRef() {
        return ref;
    }

    public void setRef(String ref) {
        this.ref= ref;
    }

    public String  getType() {
        return type;
    }

    public void setType(String type) {
        this.type= type;
    }

    @Override
    public String toString() {
        return "Attribute [name=" + name + ", type=" + type + ", ref=" + ref + "]";
    }
}

Note all files for this example are kept in the classpath root.

dimplex
  • 494
  • 5
  • 9
  • Thanks you very much for the effort. It is much appreciated! I will definitely have a look at the solution a bit later, as I've got a different task at the moment. – Jonas May 19 '16 at 09:16