August 20, 2010

Creating a RESTful Web Service - Part 3/5

Java Architecture for XML Binding (JAXB) is the Java EE standard for mapping POJOs to XML.  In this example we will use JAXB to apply an XML representation to the JPA entities we created in part 2.  Since we are mapping JPA entities to XML we will use the EclipseLink JAXB  (MOXy) implementation and leverage some of its extensions.


Some developers will maintain separate JPA and JAXB models, and perform a copy step to move data between them.  This can be quite a painful and error prone process.  Luckily MOXy has a number of extensions that make this unnecessary:


 jaxb.properties

To use the MOXy implementation of JAXB you need to add a file called jaxb.properties in with your model classes with the following entry:

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory

Customer Entity

JAX-RS requires that we annotate the root object with the JAXB annotation @XmlRootElement. Since Customer will be our root object we will add the annotation.

package org.example;
 
import java.io.Serializable;
import javax.persistence.*;
import javax.xml.bind.annotation.XmlRootElement;
 
import java.util.Set;
 
@Entity
@NamedQuery(name = "findCustomersByCity",
            query = "SELECT c " +
                    "FROM Customer c " +
                    "WHERE c.address.city = :city")
@XmlRootElement
public class Customer implements Serializable {
    private static final long serialVersionUID = 1L;
 
    @Id
    private long id;
 
    @Column(name="FIRST_NAME")
    private String firstName;
 
    @Column(name="LAST_NAME")
    private String lastName;
 
    @OneToOne(mappedBy="customer", cascade={CascadeType.ALL})
    private Address address;
 
    @OneToMany(mappedBy="customer", cascade={CascadeType.ALL})
    private Set<PhoneNumber> phoneNumbers;
 
    public long getId() {
        return this.id;
    }
 
    public void setId(long id) {
        this.id = id;
    }
 
    public String getFirstName() {
        return this.firstName;
    }
 
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
 
    public String getLastName() {
        return this.lastName;
    }
 
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
 
    public Address getAddress() {
        return this.address;
    }
 
    public void setAddress(Address address) {
        this.address = address;
    }
     
    public Set<PhoneNumber> getPhoneNumbers() {
        return this.phoneNumbers;
    }
 
    public void setPhoneNumbers(Set<PhoneNumber> phoneNumbers) {
        this.phoneNumbers = phoneNumbers;
    }

}

Address Entity

The Address entity has a property "customer" that references back to the owning Customer entity. In other words there is a bidirectional relationship. We will use MOXy's @XmlInverseReference annotation to map this relationship. This issue is discussed deeper in the following post JPA Entities to XML - Bidirectional Relationships.

Since relationships in JPA may be "lazy" (not triggered until the property is accessed through the "get" method), we will annotate the property (get/set method), instead of the field (instance variable).

package org.example;

import java.io.Serializable;
import javax.persistence.*;

import org.eclipse.persistence.oxm.annotations.XmlInverseReference;

@Entity
public class Address implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    private long id;

    private String city;

    private String street;

    @OneToOne
    @PrimaryKeyJoinColumn
    private Customer customer;

    public long getId() {
        return this.id;
    }

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

    public String getCity() {
        return this.city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getStreet() {
        return this.street;
    }

    public void setStreet(String street) {
        this.street = street;
    }

    @XmlInverseReference(mappedBy="address")
    public Customer getCustomer() {
        return customer;
    }

    public void setCustomer(Customer customer) {
        this.customer = customer;
    }

}

PhoneNumber Entity

We will also need to address the bidirectional relationship in the PhoneNumber entity.

package org.example;

import java.io.Serializable;
import javax.persistence.*;

import org.eclipse.persistence.oxm.annotations.XmlInverseReference;

@Entity
@Table(name="PHONE_NUMBER")
public class PhoneNumber implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id 
    private long id;
    
    private String num;
    
    private String type;

    @ManyToOne
    @JoinColumn(name="ID_CUSTOMER")
    private Customer customer;

    public long getId() {
        return this.id;
    }

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

    public String getNum() {
        return this.num;
    }

    public void setNum(String num) {
        this.num = num;
    }

    public String getType() {
        return this.type;
    }

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

    @XmlInverseReference(mappedBy="phoneNumbers")
    public Customer getCustomer() {
        return this.customer;
    }

    public void setCustomer(Customer customer) {
        this.customer = customer;
    }
    
}

Server Setup

If your are using GlassFish 3.1.2 or later then the MOXy bundle is already included.  If you are using a previous version of GlassFish then you will need to download a MOXy bundle (org.eclipse.persistence.moxy.jar) that matches the version of the EclipseLink bundles shipped with GlassFish.  The necessary binaries can be downloaded from:

Next Steps

In the next post we will examine how to use the Java API for RESTful Web Services (JAX-RS) to create a RESTful service from an EJB session bean that can perform CRUD operations on our Customer model via XML. 


Further Reading

If you enjoyed this post you may also be interested in:


    6 comments:

    1. I am getting com.sun.istack.SAXException2: A cycle is detected in the object graph. This will cause infinitely deep XML: myPackage.Customer@18bc6a4 -> myPackage.PhoneNumber@19e62cf -> myPackage.Customer@18bc6a4 with @XmlRootElement and @XmlInverseReference but 3.5.1 from http://jaxb.java.net/guide/Mapping_cyclic_references_to_XML.html works for me. I am using Glassfish 3.1.1. But I am having a rather strange problem with @PUT as I do get the application working correctly but I don't get the @JoinColumn(name="ID_CUSTOMER") correctly joined as it always shows NULL when I look at it using Workbench for MySQL 5.5. So if I can't get the changes (adding a new phone number) made to Customer object updated correctly with calling merge method. The application works alright but the database doesn't get updated properly, i.e. once I restart my application server the application doesn't work correctly either. The detached Customer object is not able to get managed by EntityManager with calling merge method. I am not using Address domain object in my application for the sake of simplicity. I tried only the @OneToMany relationship.

      ReplyDelete
    2. I believe your issue is due to EclipseLink JAXB (MOXy) not being configured properly in your environment. When MOXy is not configured property GlassFish defaults back to the JAXB RI.

      My blog posted the incorrect name of the MOXy bundle (it's fixed now). The MOXy bundle starts with "org.eclipse.persistence.moxy". This bundle needs to be the same version as the other eclipselink bundles that ship with GlassFish. In GlassFish 3.1.1 this is EclipseLink 2.3.0.

      This type of issue is hard to handle in the comments section. Feel free to start an email discussion with me via my Contact Me page.

      -Blaise

      ReplyDelete
    3. Its working absolutely fine now. Thanks a heap. Really good article. Thanks for sharing.

      ReplyDelete
    4. Excelente tutorial. Me salvo de un buen problema.

      ReplyDelete
    5. Where exactly to I put my jaxb.properties its a little obscure can you please provide an example of where the "model classes" would be. ta.

      ReplyDelete
      Replies
      1. In part 4 of this series I have a fully expanded view of what the WAR file contains including the location of the java.properties file.
        - Creating a RESTful Web Service - Part 4/5

        Delete