Tagged: jaxb

@XmlTransient on transient fields


public class Child {

    @XmlElement
    private String name;

    @XmlTransient
    private Parent parent;
}

And let’s make it as a Serializable.

public class Child implements Serializable {

    @XmlElement
    private String name;

    @XmlTransient
    private transient Parent parent;
}

Not that parent field marked as transient.
You know what? JAXB complains.

Transient field "parent" cannot have any JAXB annotations.

I believe @XmlTransient should be excluded from this ridiculous rule and issued it.

extensible XmlAdapter for Map bound types


References

Java Sources

When keys are not related to values

When keys can be derived from values

Apache Maven

<dependency>
  <groupId>com.googlecode.jinahya</groupId>
  <artifactId>jinahya-se</artifactId>
  <version>@@?</version>
<dependency>

Example Classes

Here are Employee and Department we gonna use.

public class Employee {

    @XmlElement
    private long id;

    @XmlElement
    private String name;

    @XmlElement
    private int age;
}
@XmlTransient
public abstract class Department {

    public Map<Long, Employee> getEmployees() {
        if (employees == null) {
            employees = new HashMap<>();
        }
        return employees;
    }

    private Map<Long, Employee> employees;
}

Canonical Approach

There are three abstract classes for extension.

@XmlTransient
public abstract class MapEntry<K, V> {

    protected K getKey();

    protected void setKey(K key);

    protected V getValue();

    protected void setValue(V value);
}
@XmlTransient
public abstract class MapEntries<E extends MapEntry<K, V>, K, V> {

    public MapEntries(final Class<E> entryType);

    protected List<E> getEntries();
}
public abstract class MapEntriesAdapter<T extends MapEntries<?, K, V>, K, V>
    extends XmlAdapter<T, Map<K, V>> {

    public MapEntriesAdapter(final Class<T> entriesType);
}

Note that both MapEntry and MapEntries are both annotated with @XmlTransient.

public class EmployeeEntry extends MapEntry<Long, Employee> {

    @XmlAttribute
    public Long getId() {
        return getKey();
    }

    public void setId(final Long id) {
        setKey(id);
    }

    @XmlElement
    public Employee getEmployee() {
        return getValue();
    }

    public void setEmployee(final Employee employee) {
        setValue(employee);
    }
}
public class EmployeeEntries extends MapEntries<EmployeeEntry, Long, Employee> {

    public EmployeeEntries() {
        super(EmployeeEntry.class);
    }

    @XmlElement(name = "employeeEntry")
    public List<EmployeeEntry> getEmployeeEntries() {
        return getEntries();
    }
}
public class EmployeesAdapter0
    extends MapEntriesAdapter<EmployeeEntries, Long, Employee> {

    public EmployeesAdapter0() {
        super(EmployeeEntries.class);
    }
}
@XmlRootElement
public class Department0 extends Department {

    @XmlElement
    @XmlJavaTypeAdapter(EmployeesAdapter0.class)
    @Override
    public Map<Long, Employee> getEmployees() {
        return super.getEmployees();
    }
}
<department0 xmlns="http://jinahya.googlecode.com/xml/bind/test/map">
    <employees>
        <employeeEntry>
            <id>0</id>
            <employee>
                <id>0</id>
                <name>name0</name>
                <age>20</age>
            </employee>
        </employeeEntry>
        <employeeEntry>
            <id>1</id>
            <employee>
                <id>1</id>
                <name>name1</name>
                <age>21</age>
            </employee>
        </employeeEntry>
        <employeeEntry>
            <id>2</id>
            <employee>
                <id>2</id>
                <name>name2</name>
                <age>22</age>
            </employee>
        </employeeEntry>
    </employees>
</department0>

When keys can be derived from values

Why <employeeEntry> elements are there? Because above Canonical Approach assumed that those keys and values are very different types. So what if those keys can be derived from values?
There are another abstract classes for this case.

@XmlTransient
public abstract class MapValues<V> {

    protected List<V> getValues();
}
public abstract class MapValuesAdapter<T extends MapValues<V>, K, V>
    extends XmlAdapter<T, Map<K, V>> {

    public MapValuesAdapter(final Class<T> mapValuesType);

    protected abstract K getKey(V value);
}
public class EmployeeValues extends MapValues<Employee> {

    @XmlElement(name = "employee")
    public List<Employee> getEmployees() {
        return super.getValues();
    }
}

Now we can create another type of XmlAdapter.

public class EmployeesAdapter2
    extends MapValuesAdapter<EmployeeValues, Long, Employee> {

    public EmployeesAdapter2() {
        super(EmployeeValues.class);
    }

    @Override
    protected Long getKey(final Employee value) {
        return value.getId();
    }
}
@XmlRootElement
public class Department2 extends Department {

    @XmlElement
    @XmlJavaTypeAdapter(EmployeesAdapter2.class)
    @Override
    public Map<Long, Employee> getEmployees() {
        return super.getEmployees();
    }
}

Now we got this.

<department2 xmlns="http://jinahya.googlecode.com/xml/bind/test/map">
    <employees>
        <employee>
            <id>0</id>
            <name>name0</name>
            <age>20</age>
        </employee>
        <employee>
            <id>1</id>
            <name>name1</name>
            <age>21</age>
        </employee>
        <employee>
            <id>2</id>
            <name>name2</name>
            <age>22</age>
        </employee>
    </employees>
</department2>

When there are no siblings

Note that those classes explained so far are for general properties with siblings. When a Department doesn’t have any other properties but the Employee list, there is no need to work with XmlAdapter at all.

@XmlRootElement
public class Department7 extends AbstractDepartment {

    @XmlElement(name = "employee")
    private List<Employee> getEmployeeList() {
        final Collection<Employee> employees = getEmployees().values();
        if (employees instanceof List) {
            return (List<Employee>) employees;
        }
        return new ArrayList<>(employees);
    }

    private void setEmployeeList(final List<Employee> employeeList) {
        for (Employee employee : employeeList) {
            getEmployees().put(employee.getId(), employee);
        }
    }

    // not even need to be overridden; just informed.
    @XmlTransient
    @Override
    public Map<Long, Employee> getEmployees() {
        return super.getEmployees();
    }
}

Here comes a little bit more concise version.

<department7 xmlns="http://jinahya.googlecode.com/xml/bind/test/map">
    <employee>
        <id>0</id>
        <name>name0</name>
        <age>20</age>
    </employee>
    <employee>
        <id>1</id>
        <name>name1</name>
        <age>21</age>
    </employee>
    <employee>
        <id>2</id>
        <name>name2</name>
        <age>22</age>
    </employee>
</department7>

Basic RESTful Webapp


Here comes a simple web application for demonstrating the RESTFul web APIs.

Source

svn

$ svn co http://jinahya.googlecode.com/svn/trunk\
> /com.googlecode.jinahya.test/basic-restful-webapp
...
Checked out revision n.

$

mvn

$ cd basic-restful-webapp
$ mvn clean package embedded-glassfish:run
...
Hit ENTER to redeploy, X to exit

Now you can access to resources under http://localhost:58080/basic-restful-webapp/.

Domain Objects

Item.java

@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(namespace = "http://jinahya.googlecode.com/test")
@XmlType(propOrder = {"name", "stock"})
public class Item implements Serializable {


    private static final long serialVersionUID = -3104855615755133457L;


    private static final AtomicLong ATOMIC_ID = new AtomicLong();


    public static Item newInstance(final String name, final int stock) {

        final Item instance = new Item();

        instance.name = name;
        instance.stock = stock;

        return instance;
    }


    public static Item putCreatedAtAndId(final Item item) {

        item.createdAt = new Date();
        item.id = ATOMIC_ID.getAndIncrement();

        return item;
    }


    protected Item() {
        super();
    }


    @XmlAttribute
    public Date getCreatedAt() {
        return createdAt;
    }


    @XmlAttribute
    public Date getUpdatedAt() {
        return updatedAt;
    }


    @XmlAttribute
    public Long getId() {
        return id;
    }


    private Date createdAt;


    protected Date updatedAt;


    private Long id;


    @XmlElement(required = true, nillable = true)
    protected String name;


    @XmlElement(required = true, nillable = true)
    protected int stock;


}

Items.java

@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(namespace = "http://jinahya.googlecode.com/test")
public class Items implements Serializable {


    private static final long serialVersionUID = 5775071328874654134L;


    public Collection<Item> getItems() {

        if (items == null) {
            items = new ArrayList<>();
        }

        return items;
    }


    @XmlElement(name = "item")
    private Collection<Item> items;


}

Business Facade

ItemTable.java

public class ItemFacade {


    private static final long serialVersionUID = 5775071328874654134L;


    private static class InstanceHolder {


        private static final ItemFacade INSTANCE = new ItemFacade();


        private InstanceHolder() {
            super();
        }


    }


    public static ItemFacade getInstance() {
        return InstanceHolder.INSTANCE;
    }


    private ItemFacade() {
        super();
    }


    public void insert(final Item... items) {

        for (Item item : items) {
            Item.putCreatedAtAndId(item);
            tuples.put(item.getId(), item);
        }
    }


    public Item select(final long id) {

        return tuples.get(id);
    }


    public Collection<Item> selectAll() {
        return tuples.values();
    }


    public boolean update(final long id, final Item newItem) {

        if (!tuples.containsKey(id)) {
            return false;
        }

        final Item oldItem = tuples.get(id);
        if (oldItem == null) {
            return false;
        }

        oldItem.updatedAt = new Date();
        oldItem.name = newItem.name;
        oldItem.stock = newItem.stock;

        return true;
    }


    public Item delete(final long id) {
        return tuples.remove(id);
    }


    public void deleteAll() {
        tuples.clear();
    }


    private final Map<Long, Item> tuples = new HashMap<>();


}

Webservice Resource

ItemsResource.java

@Path("/items")
public class ItemsResource {


    @GET
    @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public Items read() {

        final Items items = new Items();

        items.getItems().addAll(ItemFacade.getInstance().selectAll());

        return items;
    }


    @DELETE
    public void delete() {

        ItemFacade.getInstance().deleteAll();
    }


    @POST
    @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public Response createItem(@Context final UriInfo info,
                               final Item item) {

        ItemFacade.getInstance().insert(item);

        UriBuilder builder = info.getAbsolutePathBuilder();
        builder = builder.path(Long.toString(item.getId()));

        final URI uri = builder.build();

        return Response.created(uri).build();
    }


    @Path("/{id: \\d+}")
    @GET
    @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public Item readItem(@PathParam("id") final long id) {

        return ItemFacade.getInstance().select(id);
    }


    @Path("/{id: \\d+}")
    @PUT
    @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public Response updateItem(@PathParam("id") final long id,
                               final Item newItem) {

        final boolean updated = ItemFacade.getInstance().update(id, newItem);

        if (!updated) {
            return Response.status(Status.NOT_FOUND).build();
        }

        return Response.status(Status.NO_CONTENT).build();
    }


    @Path("/{id: \\d+}")
    @DELETE
    public void deleteItem(@PathParam("id") final long id) {

        ItemFacade.getInstance().delete(id);
    }


}
resource POST GET PUT DELETE
/items CREATE
/items/{id: \\d+} READ UPDATE DELETE

Demonstration

CREATE

$ cat src/test/resources/item.xml
<item xmlns="http://jinahya.googlecode.com/test">
  <name>xml</name>
  <stock>30</stock>
</item>

$ curl -i \
> -X POST \
> http://localhost:58080/items \
> -H "Content-Type: application/xml" \
> --data "@src/test/resources/item.xml"
HTTP/1.1 201 Created
X-Powered-By: ...
Server: ...
Location: http://localhost:58080/items/0
Content-Length: 0
Date: ...

$

READ

$ curl -s \
> http://localhost:58080/items/0 \
> -H "Accept: application/xml" \
> | xmllint --format -
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:item xmlns:ns2="http://jinahya.googlecode.com/test"
          createdAt="2013-03-29T17:43:12.413+09:00"
          id="0">
  <name>xml</name>
  <stock>30</stock>
</ns2:item>

$

UPDATE

$ cat src/test/resources/item.json
{
    "name":"json",
    "stock":"40"
}

$ curl -i \
> -X PUT \
> http://localhost:58080/items/0 \
> -H "Content-Type: application/json" \
> --data "@src/test/resources/item.json"
HTTP/1.1 204 No Content
X-Powered-By: ...
Server: ...
Date: ...


$ curl -s \
> http://localhost:58080/items/0 \
> -H "Accept: application/json" \
> | python -m json.tool
{
    "@createdAt": "2013-03-29T17:43:12.413+09:00",
    "@id": "0",
    "@updatedAt": "2013-03-29T17:48:23.238+09:00",
    "name": "json",
    "stock": "40"
}

$

DELETE

$ curl -i \
> -X DELETE \
> http://localhost:58080/items/0
HTTP/1.1 204 No Content
X-Powered-By: ...
Server: ...
Date: ...

$

/schema.xsd

$ curl -s http://localhost:58080/schema.xsd | xmllint --format -
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema xmlns:tns="http://jinahya.googlecode.com/test"
           xmlns:xs="http://www.w3.org/2001/XMLSchema"
           attributeFormDefault="unqualified"
           elementFormDefault="qualified"
           version="1.0"
           targetNamespace="http://jinahya.googlecode.com/test">
  <xs:element name="imageFormat" type="tns:imageFormat"/>
  <xs:element name="imageFormats" type="tns:imageFormats"/>
  <xs:element name="imageSuffix" type="tns:imageSuffix"/>
  <xs:element name="imageSuffixes" type="tns:imageSuffixes"/>
  <xs:element name="item" type="tns:item"/>
  <xs:element name="items" type="tns:items"/>
  <xs:complexType name="imageFormat">
    <xs:simpleContent>
      <xs:extension base="xs:string">
        <xs:attribute name="canRead" type="xs:boolean" use="required"/>
        <xs:attribute name="canWrite" type="xs:boolean" use="required"/>
      </xs:extension>
    </xs:simpleContent>
  </xs:complexType>
  <xs:complexType name="imageFormats">
    <xs:sequence>
      <xs:any processContents="lax" namespace="##other" minOccurs="0" maxOccurs="unbounded"/>
    </xs:sequence>
    <xs:attribute name="empty" type="xs:boolean"/>
  </xs:complexType>
  <xs:complexType name="imageSuffix">
    <xs:simpleContent>
      <xs:extension base="xs:string">
        <xs:attribute name="canRead" type="xs:boolean" use="required"/>
        <xs:attribute name="canWrite" type="xs:boolean" use="required"/>
      </xs:extension>
    </xs:simpleContent>
  </xs:complexType>
  <xs:complexType name="imageSuffixes">
    <xs:sequence>
      <xs:any processContents="lax" namespace="##other" minOccurs="0" maxOccurs="unbounded"/>
    </xs:sequence>
    <xs:attribute name="empty" type="xs:boolean"/>
  </xs:complexType>
  <xs:complexType name="item">
    <xs:sequence>
      <xs:element name="name" type="xs:string"/>
      <xs:element name="stock" type="xs:int"/>
    </xs:sequence>
    <xs:attribute name="createdAt" type="xs:dateTime"/>
    <xs:attribute name="id" type="xs:long"/>
    <xs:attribute name="updatedAt" type="xs:dateTime"/>
  </xs:complexType>
  <xs:complexType name="items">
    <xs:sequence>
      <xs:element ref="tns:item" minOccurs="0" maxOccurs="unbounded"/>
    </xs:sequence>
    <xs:attribute name="empty" type="xs:boolean"/>
  </xs:complexType>
</xs:schema>

/imageFormats

POST GET PUT DELETE
/imageFormats Read All
/imageTypes/{name} Read Single
$ curl -s -H "Accept: application/xml" http://localhost:58080/imageFormats
<imageFormats xmlns="http://jinahya.googlecode.com/test">
  <imageFormat canRead="true" canWrite="true">jpg</imageFormat>
  <imageFormat canRead="true" canWrite="true">bmp</imageFormat>
  <imageFormat canRead="true" canWrite="true">BMP</imageFormat>
  <imageFormat canRead="true" canWrite="true">JPG</imageFormat>
  <imageFormat canRead="true" canWrite="true">wbmp</imageFormat>
  <imageFormat canRead="true" canWrite="true">jpeg</imageFormat>
  <imageFormat canRead="true" canWrite="true">png</imageFormat>
  <imageFormat canRead="true" canWrite="true">PNG</imageFormat>
  <imageFormat canRead="true" canWrite="true">JPEG</imageFormat>
  <imageFormat canRead="true" canWrite="true">WBMP</imageFormat>
  <imageFormat canRead="true" canWrite="true">GIF</imageFormat>
  <imageFormat canRead="true" canWrite="true">gif</imageFormat>
</imageFormats>
$ curl -s -H "Accept: application/json" http://localhost:58080/imageFormats
{
    "imageFormat": [
        {
            "$": "bmp",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "BMP",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "jpg",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "JPG",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "wbmp",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "jpeg",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "png",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "PNG",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "JPEG",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "WBMP",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "GIF",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "gif",
            "@canRead": "true",
            "@canWrite": "true"
        }
    ]
}
$ curl -s -H "Accept: application/xml" http://localhost:58080/imageFormats/png
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<imageFormat xmlns="http://jinahya.googlecode.com/test"
             canRead="true" canWrite="true">png</imageFormat>
$ curl -s -H "Accept: application/json" http://localhost:58080/imageFormats/jpeg
{
    "$": "jpeg",
    "@canRead": "true",
    "@canWrite": "true"
}

/imageTypes

POST GET PUT DELETE
/imageTypes Read All
/imageTypes/{name} Read Single
$ curl -s -H "Accept: application/xml" http://localhost:58080/imageTypes
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<imageTypes xmlns="http://jinahya.googlecode.com/test">
  <imageType canRead="true" canWrite="true">image/png</imageType>
  <imageType canRead="true" canWrite="true">image/jpeg</imageType>
  <imageType canRead="true" canWrite="true">image/x-png</imageType>
  <imageType canRead="true" canWrite="true">image/vnd.wap.wbmp</imageType>
  <imageType canRead="true" canWrite="true">image/bmp</imageType>
  <imageType canRead="true" canWrite="true">image/gif</imageType>
</imageTypes>
$ curl -s -H "Accept: application/json" http://localhost:58080/imageTypes
{
    "imageType": [
        {
            "$": "image/png",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "image/jpeg",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "image/x-png",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "image/vnd.wap.wbmp",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "image/bmp",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "image/gif",
            "@canRead": "true",
            "@canWrite": "true"
        }
    ]
}
$ curl -s -H "Accept: application/xml" http://localhost:58080/imageTypes/image/png
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<imageType xmlns="http://jinahya.googlecode.com/test"
           canRead="true" canWrite="true">image/png</imageType>
$ curl -s -H "Accept: application/json" http://localhost:58080/imageTypes/image/jpeg
{
    "$": "image/jpeg",
    "@canRead": "true",
    "@canWrite": "true"
}

/imageSuffixes

POST GET PUT DELETE
/imageTypes Read All
/imageTypes/{name} Read Single
$ curl -H "Accept: application/xml" http://localhost:58080/imageSuffixes
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<imageSuffixes xmlns="http://jinahya.googlecode.com/test">
  <imageSuffix canRead="true" canWrite="true">bmp</imageSuffix>
  <imageSuffix canRead="true" canWrite="true">jpg</imageSuffix>
  <imageSuffix canRead="true" canWrite="true">jpeg</imageSuffix>
  <imageSuffix canRead="true" canWrite="true">wbmp</imageSuffix>
  <imageSuffix canRead="true" canWrite="true">png</imageSuffix>
  <imageSuffix canRead="true" canWrite="true">gif</imageSuffix>
</imageSuffixes>
$ curl -H "Accept: application/json" http://localhost:58080/imageSuffixes
{
    "imageSuffix": [
        {
            "$": "bmp",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "jpg",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "wbmp",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "jpeg",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "png",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "gif",
            "@canRead": "true",
            "@canWrite": "true"
        }
    ]
}
$ curl -H "Accept: application/xml" http://localhost:58080/imageSuffixes/png
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<imageSuffix xmlns="http://jinahya.googlecode.com/test"
             canRead="true" canWrite="true">png</imageSuffix>
$ curl -s -H "Accept: application/json" http://localhost:58080/imageSuffixes/jpeg
{
    "$": "jpeg",
    "@canRead": "true",
    "@canWrite": "true"
}

Serving XML Schema with JAX-RS


update

@GET
@Path("/some.xsd")
@Produces({MediaType.APPLICATION_XML})
public Response readSomeXsd() throws JAXBException, IOException {

    final JAXBContext context; // get some

    return Response.ok((StreamingOutput) output -> {
        context.generateSchema(new SchemaOutputResolver() {
            @Override
            public Result createOutput(final String namespaceUri,
                                       final String suggestedFileName)
                throws IOException {
                final Result result = new StreamResult(output);
                result.setSystemId(uriInfo.getAbsolutePath().toString());
                return result;
            }
        });
    }).build();
}

@Context
private transient UriInfo uriInfo;

You can serve the XML Schema for your domain objects.

public class SchemaStreamingOutput implements StreamingOutput {

    public SchemaStreamingOutput(final JAXBContext context) {
        super();

        if (context == null) {
            throw new NullPointerException("null context");
        }

        this.context = context;
    }

    @Override
    public void write(final OutputStream output) throws IOException {

        context.generateSchema(new SchemaOutputResolver() {

            @Override
            public Result createOutput(final String namespaceUri,
                                       final String suggestedFileName)
                throws IOException {

                return new StreamResult(output) {

                    @Override
                    public String getSystemId() { // nasty
                        return suggestedFileName;
                    }
                };
            }
        });
    }

    private final JAXBContext context;
}

Now you can serve your XML Schema like this.

@GET
@Path("/items.xsd")
@Produces({MediaType.APPLICATION_XML})
public Response readXsd() throws JAXBException {

    final JAXBContext context = ...;

    return Response.ok(new SchemaStreamingOutput(context)).build();
}

Self-Referencing One-to-Many Bidirectional Relationship


@Entity
public class Person {


    /**
     * Adopts given <code>child</code>.
     *
     * @param child the child to be adopted.
     */
    public void adopt(final Person child) {

        if (child == null) {
            throw new NullPointerException("null child");
        }

        if (child.parent != null) {
            // how cruel
            child.parent.getChildren().remove(child);
            child.parent = null;
        }

        child.parent = this;
        getChildren().add(child);
    }


    /**
     * Returns the children of this person.
     *
     * @return the children of this person
     */
    public List<Person> getChildren() {

        if (children == null) {
            children = new ArrayList<Person>();
        }

        return children;
    }


    /**
     * id.
     */
    @Id
    @XmlAttribute
    @XmlID
    @XmlJavaTypeAdapter(XmlIDAdapter.class) // MOXy doesn't need this
    private Long id;


    @Basic
    @Column
    private String name;


    @Basic
    @Column
    private int age;


    /**
     * parent.
     */
    @JoinColumn
    @ManyToOne
    @XmlAttribute(name = "parentId")
    @XmlIDREF
    private Person parent;


    /**
     * children.
     */
    @JoinColumn
    @OneToMany(mappedBy = "parent")
    @XmlAttribute(name = "childIds")
    @XmlIDREF
    @XmlList
    private List<Person> children;


}
public class XmlIDAdapter extends XmlAdapter<String, Long> {


    @Override
    public String marshal(final Long bound) throws Exception {

        if (bound == null) {
            return null;
        }

        return DatatypeConverter.printLong(bound);
    }


    @Override
    public Long unmarshal(final String value) throws Exception {

        if (value == null) {
            return null;
        }

        return DatatypeConverter.parseLong(value);
    }


}