@XmlRegistry - how does it work?
Asked Answered
M

2

11

I have found some examples of JAXB2 @XmlRegistry over the internet but no good in-depth tutorials that talk about the concept of using @XmlRegistry with @XmlElementDecl, wonder if its a concept not much explored in general.

Anyways here is my question, first some sample classes that I am using to unmarshall an xml using JAXB:

The main class I am trying to unmarshal using JAXB - Employee.java

package com.test.jaxb;

import java.util.List;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlElementDecl;
import javax.xml.bind.annotation.XmlRegistry;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.namespace.QName;

import com.test.jaxb.dto.Address;

@XmlRootElement
public class Employee {
    private int id;
    private String name;
    private String email;

    private List<Address> addresses;

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }

    public List<Address> getAddresses() {
        return addresses;
    }
    public void setAddresses(List<Address> addresses) {
        this.addresses = addresses;
    }

    @SuppressWarnings("unused")
    @XmlRegistry
    public static class XMLObjectFactory {
        @XmlElementDecl(scope = Employee.class, name= "id")
        JAXBElement<String> createEmployeeId(String value) {
            return new JAXBElement<String>(new QName("id"), String.class, "100");
        }
        @XmlElementDecl(scope = Employee.class, name= "name")
        JAXBElement<String> createName(String value) {
            return new JAXBElement<String>(new QName("name"), String.class, "Fake Name");
        }
        @XmlElementDecl(scope = Employee.class, name= "email")
        JAXBElement<String> createEmail(String value) {
            return new JAXBElement<String>(new QName("email"), String.class, value);
        }

        @XmlElementDecl(scope = Employee.class, name= "addresses")
        JAXBElement<List> createAddresses(List value) {
            return new JAXBElement<List>(new QName("addresses"), List.class, value);
        }
    }
}

The child class - Address.java

package com.test.jaxb.dto;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlElementDecl;
import javax.xml.bind.annotation.XmlRegistry;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.namespace.QName;

import com.test.jaxb.Employee;

@XmlRootElement
public class Address {
    private String addressLine1;
    private String addressLine2;
    private String addressLine3;
    public String getAddressLine1() {
        return addressLine1;
    }
    public void setAddressLine1(String addressLine1) {
        this.addressLine1 = addressLine1;
    }
    public String getAddressLine2() {
        return addressLine2;
    }
    public void setAddressLine2(String addressLine2) {
        this.addressLine2 = addressLine2;
    }
    public String getAddressLine3() {
        return addressLine3;
    }
    public void setAddressLine3(String addressLine3) {
        this.addressLine3 = addressLine3;
    }

    @SuppressWarnings("unused")
    @XmlRegistry
    private static class XMLObjectFactory {
        @XmlElementDecl(scope = Employee.class, name= "addressLine1")
        JAXBElement<String> createAddressLine1(String value) {
            return new JAXBElement<String>(new QName("addressLine1"), String.class, value);
        }
        @XmlElementDecl(scope = Employee.class, name= "addressLine2")
        JAXBElement<String> createAddressLine2(String value) {
            return new JAXBElement<String>(new QName("addressLine2"), String.class, value);
        }
        @XmlElementDecl(scope = Employee.class, name= "addressLine3")
        JAXBElement<String> createAddressLine3(String value) {
            return new JAXBElement<String>(new QName("addressLine3"), String.class, value);
        }
    }
}

The xml to be unmarshalled - employee.xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<employee>
    <id>1</id>
    <name>Vaishali</name>
    <email>[email protected]</email>
    <addresses>
        <address>
            <addressLine1>300</addressLine1>
            <addressLine2>Mumbai</addressLine2>
            <addressLine3>India</addressLine3>
        </address>
        <address>
            <addressLine1>301</addressLine1>
            <addressLine2>Pune</addressLine2>
            <addressLine3>India</addressLine3>
        </address>
    </addresses>
</employee>

Unmarshalling Code :

package com.test.jaxb;

import java.io.FileReader;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;


public class ObjectFactoryTest {
    public static void main(String[] args) throws Exception {
        FileReader reader = new FileReader("resources/employee.xml");
        JAXBContext context = JAXBContext.newInstance(Employee.class);
        Unmarshaller unmarshaller = context.createUnmarshaller();
        Object obj = unmarshaller.unmarshal(reader);
        System.out.println(obj);
    }
}

When I unmarshal the employee xml using above code, the address list does not get populated. The resulting employee object only has a blank list of adresses. Is there anything wrong with my mappings?

To find out what is going on and see if the employee objects are actually being created using the Object Factory (having the @XMLRegistry annotation), I changed the value of id and name in factory methods, however that had no effect in the output, which tells me JAXB is not actually using the ObjectFactory, why?

Am I going aboout this all wrong? Any help would be appreciated.

Manducate answered 18/6, 2012 at 7:54 Comment(0)
R
17

@XmlRegistry - how does it work?

@XmlRegistry is used to mark a class that has @XmlElementDecl annotations. To have your JAXB implementation process this class you need to ensure that it is included in the list of classes used to bootstrap the JAXBContext. It is not enough for it to be a static inner class of one of your domain model classes:

JAXBContext context = JAXBContext.newInstance(Employee.class, Employee.XMLObjectFactory.class);

@XmlElementDecl - how does it work?

If the value of the field/property is going to be a JAXBElement then you need to leverage @XmlElementDecl. A JAXBElement captures information that is can be useful:

  • Element name, this is necessary if you are mapping to a choice structure where multiple elements are of the same type. If the element name does not correspond to a unique type then you would not be able to round-trip the document.
  • JAXBElement can be used to represent an element with xsi:nil="true".

XmlObjectFactory

@XmlElementDecl also allows you to specify a scope. I have modified the model from you post a bit. I have introduced an XmlObjectFactory class that has two @XmlElementDecl. Both specify a name of address. I have leveraged the scope property so that for properties within the Employee class the @XmlElementDecl corresponding to the Address class with be used.

package forum11078850;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlElementDecl;
import javax.xml.bind.annotation.XmlRegistry;
import javax.xml.namespace.QName;

@XmlRegistry
public class XmlObjectFactory {

    @XmlElementDecl(scope = Employee.class, name = "address")
    JAXBElement<Address> createAddress(Address value) {
        return new JAXBElement<Address>(new QName("address"), Address.class, value);
    }

    @XmlElementDecl(name = "address")
    JAXBElement<String> createStringAddress(String value) {
        return new JAXBElement<String>(new QName("address"), String.class, value);
    }

}

Employee

The @XmlElementRef annotation will cause the value of the property to be matched on its root element name. Possible matches will include classes mapped with @XmlRootElement or @XmlElementDecl.

package forum11078850;

import java.util.List;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.*;

@XmlRootElement
@XmlType(propOrder = { "id", "name", "email", "addresses" })
public class Employee {
    private int id;
    private String name;
    private String email;

    private List<JAXBElement<Address>> addresses;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @XmlElementWrapper
     @XmlElementRef(name="address")
    public List<JAXBElement<Address>> getAddresses() {
        return addresses;
    }

    public void setAddresses(List<JAXBElement<Address>> addresses) {
        this.addresses = addresses;
    }

}

ObjectFactoryTest

package forum11078850;

import java.io.FileReader;
import javax.xml.bind.*;

public class ObjectFactoryTest {
    public static void main(String[] args) throws Exception {
        FileReader reader = new FileReader("src/forum11078850/input.xml");
        JAXBContext context = JAXBContext.newInstance(Employee.class, XmlObjectFactory.class);
        Unmarshaller unmarshaller = context.createUnmarshaller();
        Object obj = unmarshaller.unmarshal(reader);

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

The Address class and input.xml from my original answer can be used to run this example.


ORIGINAL ANSWER

I'm not sure how you are attempting to use @XmlRegistry, so I will focus on the following part of your post:

When I unmarshal the employee xml using above code, the address list does not get populated. The resulting employee object only has a blank list of adresses. Is there anything wrong with my mappings?

Your list of Address objects is wrapped in a grouping element (addresses), so you need to use the @XmlElementWrapper annotation to map this use case. Below is a complete example:

Employee

package forum11078850;

import java.util.List;
import javax.xml.bind.annotation.*;

@XmlRootElement
@XmlType(propOrder = { "id", "name", "email", "addresses" })
public class Employee {
    private int id;
    private String name;
    private String email;

    private List<Address> addresses;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @XmlElementWrapper
    @XmlElement(name = "address")
    public List<Address> getAddresses() {
        return addresses;
    }

    public void setAddresses(List<Address> addresses) {
        this.addresses = addresses;
    }

}

Address

package forum11078850;

public class Address {
    private String addressLine1;
    private String addressLine2;
    private String addressLine3;

    public String getAddressLine1() {
        return addressLine1;
    }

    public void setAddressLine1(String addressLine1) {
        this.addressLine1 = addressLine1;
    }

    public String getAddressLine2() {
        return addressLine2;
    }

    public void setAddressLine2(String addressLine2) {
        this.addressLine2 = addressLine2;
    }

    public String getAddressLine3() {
        return addressLine3;
    }

    public void setAddressLine3(String addressLine3) {
        this.addressLine3 = addressLine3;
    }

}

ObjectFactoryTest

package forum11078850;

import java.io.FileReader;
import javax.xml.bind.*;

public class ObjectFactoryTest {
    public static void main(String[] args) throws Exception {
        FileReader reader = new FileReader("src/forum11078850/input.xml");
        JAXBContext context = JAXBContext.newInstance(Employee.class);
        Unmarshaller unmarshaller = context.createUnmarshaller();
        Object obj = unmarshaller.unmarshal(reader);

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

input.xml/Output

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<employee>
    <id>1</id>
    <name>Vaishali</name>
    <email>[email protected]</email>
    <addresses>
        <address>
            <addressLine1>300</addressLine1>
            <addressLine2>Mumbai</addressLine2>
            <addressLine3>India</addressLine3>
        </address>
        <address>
            <addressLine1>301</addressLine1>
            <addressLine2>Pune</addressLine2>
            <addressLine3>India</addressLine3>
        </address>
    </addresses>
</employee>
Rafferty answered 18/6, 2012 at 13:24 Comment(6)
I found several examples of '@XmlRegistry' used the same way was as I am using it, look [here] (codezealot.org/archives/5). I do understand that I can use '@XmlElementWrapper' to do this. My question was more about the understanding of what '@XmlRegistry' is and what I can do with it. AFAIK I should be able to use the objectfactory (annotated with '@XmlRegistry') to unmarshal an xml and convert it into an object. If not, what is it used for? Do I have to register it somehwhere for it to be used (like I demonstrate in my example, my object factory is never really used by JAXB)?Manducate
My understanding might be completely flawed here, but I thought there must be a way of applying something similar to '@XmlElementWrapper' to the '@XmlRegistry', as its an alternate way of unmarshalling XML. No points for guessing I am totally confused how @XmlRegistry works!!!Manducate
@Vaishali - I have updated my answer to include an example leveraging @XmlRegistry and @XmlElementDeclRafferty
+1 for the explanation of the concepts, your blog has in the past given me answers to many of my jaxb issues, however I did not find any tutorials on this topic, may be you could do one on it. Need to try this out before I accept the answer.Manducate
@Vaishali - I'm happy to hear you have found my blog useful. I'll turn this post into an article as well. I want to make sure you get the answer you're looking for, don't let me off the hook if things aren't still clear :).Rafferty
works! your answer explains how and when to use @XmlRegistry well, thanks!Manducate
R
-1

You have to take a List object of Address. In that object, you will have to add the object which contains data like addressline1. addressline2 and so on.

 i.e.
 List addrObjList = new List();
 addrObjList.add(object); // Bind an object containing data and add one by one 
Redfaced answered 18/6, 2012 at 8:44 Comment(1)
Two things, one - that should not be required as Address class itself is annotated with @XmlRootElement and has an object factory of its own, JAXB should be able to do that automatically... and two - like I mentioned in my question JAXB does not seem to be using the Object Factory at all, so any changes I make to factory methods have no effect.Manducate

© 2022 - 2024 — McMap. All rights reserved.