Wednesday, 22 July 2015

JAXB - Java Architecture for XML Bindning


JAXB stands for Java Architecture for XML Binding. It is used to convert XML to java object and java object to XML. JAXB provides an API for reading and writing Java objects to and from XML documents.

JAXB is memory efficient and provides fast and efficient way to bind XML and Java objects.


 

JAXB provides below functionalities,

  • Unmarshalling: Reading XML documents to JAVA content tree
  • Marshalling: Writing Java content tree to XML document
  • Schema Generation: JAXB provide ways to generate XML schema from Java objects
  • Apart from this, it provides way to validate. Validation is the process of verifying that XML document meets all the constraints mentioned in schema. JAXB 2.0 provides way to validate at marshalling and unmarshalling time. Validation at marshalling time is very useful especially in web service where user don’t invalidate XML document when modifying the document in JAXB.

In this tutorial, we will see how to create XML document from Java object (UnMarshal), Java object to XML document (Marshal), creating schema file from Java beans and how to use EventHandler for validation.

First, create model object as below,

Address:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package in.blogspot.ashish4java.userdetails.model;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlType(propOrder = { "address_line_1", "address_line_2", "address_line_3", "address_line_4", "city" })
@XmlRootElement(name="Address")
public class Address {

 public Address() {
  super();
 }

 public Address(String city, String address_line_1, String address_line_2, String address_line_3,
   String address_line_4) {
  super();
  this.city = city;
  this.address_line_1 = address_line_1;
  this.address_line_2 = address_line_2;
  this.address_line_3 = address_line_3;
  this.address_line_4 = address_line_4;
 }

 private String city;
 private String address_line_1;
 private String address_line_2;
 private String address_line_3;
 private String address_line_4;

 @XmlElement(name="City", nillable=false, required=true)
 public String getCity() {
  return city;
 }

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

 @XmlElement(name="AddressLine1", nillable=false, required=true)
 public String getAddress_line_1() {
  return address_line_1;
 }

 public void setAddress_line_1(String address_line_1) {
  this.address_line_1 = address_line_1;
 }

 @XmlElement(name="AddressLine2", nillable=false, required=true)
 public String getAddress_line_2() {
  return address_line_2;
 }

 public void setAddress_line_2(String address_line_2) {
  this.address_line_2 = address_line_2;
 }

 @XmlElement(name="AddressLine3")
 public String getAddress_line_3() {
  return address_line_3;
 }

 public void setAddress_line_3(String address_line_3) {
  this.address_line_3 = address_line_3;
 }

 @XmlElement(name="AddressLine4")
 public String getAddress_line_4() {
  return address_line_4;
 }

 public void setAddress_line_4(String address_line_4) {
  this.address_line_4 = address_line_4;
 }

 @Override
 public String toString() {
  return "Address [city=" + city + ", address_line_1=" + address_line_1 
    + ", address_line_2=" + address_line_2
    + ", address_line_3=" + address_line_3 
    + ", address_line_4=" + address_line_4 + "]";
 }

}


User:

1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
package in.blogspot.ashish4java.userdetails.model;

import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlType(propOrder = { "userId", "firstName", "surName", "addressList" })
@XmlRootElement(name="User")
public class User {

 public User() {
  super();
 }

 public User(String firstName, String surName, List<Address> addressList, int userId) {
  super();
  this.firstName = firstName;
  this.surName = surName;
  this.addressList = addressList;
  this.userId = userId;
 }

 private String firstName;
 private String surName;
 private List<Address> addressList;
 private int userId;

 /**
  * @return the firstName
  */
 @XmlElement(name="FirstName", nillable=false, required=true)
 public String getFirstName() {
  return firstName;
 }

 /**
  * @param firstName
  *            the firstName to set
  */
 public void setFirstName(String firstName) {
  this.firstName = firstName;
 }

 /**
  * @return the surName
  */
 @XmlElement(name="SurName", nillable=false, required=true)
 public String getSurName() {
  return surName;
 }

 /**
  * @param surName
  *            the surName to set
  */
 public void setSurName(String surName) {
  this.surName = surName;
 }

 /**
  * @return the addressList
  */
 @XmlElementWrapper(name="Addresses", required=true, nillable=false)
 @XmlElement(name="Address", type=Address.class, nillable=false, required=true)
 public List<Address> getAddressList() {
  return addressList;
 }

 /**
  * @param addressList
  *            the addressList to set
  */
 public void setAddressList(List<Address> addressList) {
  this.addressList = addressList;
 }

 /**
  * @return the userId
  */
 @XmlElement(name="UserId", nillable=false, required=true)
 public int getUserId() {
  return userId;
 }

 /**
  * @param userId
  *            the userId to set
  */
 public void setUserId(int userId) {
  this.userId = userId;
 }

 @Override
 public String toString() {
  return "User [firstName=" + firstName + ", surName=" + surName 
    + ", addressList=" + addressList + ", userId="
    + userId + "]";
 }

}

Note down use of annotations like @XmlType to define ordering of field in XML document, @XmlElement to define whether property is required or not, nullable or not. @XmlElementWrapper is used to create wrapper around collections(list) of addresses.

Now, generate schema, marshall and unmarshall using these java beans:


1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package in.blogspot.ashish4java.userdetails.helper;

import java.io.File;
import java.io.IOException;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.SchemaOutputResolver;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.ValidationEvent;
import javax.xml.bind.ValidationEventHandler;
import javax.xml.transform.Result;
import javax.xml.transform.stream.StreamResult;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import org.xml.sax.SAXException;

import in.blogspot.ashish4java.userdetails.model.Address;
import in.blogspot.ashish4java.userdetails.model.User;

/**
 * @author admin
 *
 */
public class MarshallerAndUnMarshaller {

 public static final JAXBContext jc = initContext();
 
 //Location of schema file
 private static final String SCHEMA_FILE = "src/in/blogspot/ashish4java/userdetails/data/UserSchema.xsd";
 
 //location of XML document
 public static final String USER_XML_FILE = "src/in/blogspot/ashish4java/userdetails/data/User.xml";

 private static JAXBContext initContext() {
  try {
   return JAXBContext.newInstance(User.class, Address.class);
  } catch (JAXBException e) {
   System.out.println("Exception initialising context " + e.getMessage());
  }
  return null;
 }

 /**
  * Method to write to XML document from Java object. This method sets the
  * schema document first and uses ValidationEventHandler to validate before
  * writing to XML document file as formatted output. However validation
  * fails, unmarshalling continues for rest of document.
  * 
  * @param user
  *            java object which needs to marshall to XML document.
  */
 public final void MarshalToXML(User user) {
  try {
   final Marshaller marshaller = jc.createMarshaller();
   marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
   marshaller.marshal(user, System.out);
   System.out.println();
   final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
   final Schema schema = schemaFactory.newSchema(new File(SCHEMA_FILE));
   marshaller.setSchema(schema);
   marshaller.setEventHandler(new ValidationEventHandler() {
    @Override
    public boolean handleEvent(ValidationEvent event) {
     System.out.println("MarshalToXML - Schema Validation Issue : Severity : " + event.getSeverity());
     System.out.println("MarshalToXML - Schema Validation Issue : Message : " + event.getMessage());
     return true;
    }
   });
   marshaller.marshal(user, new File(USER_XML_FILE));
  } catch (JAXBException | SAXException e) {
   System.out.println("Exception " + e.getMessage());
   System.out.println("Exception " + e.toString());
  }
  System.out.println();
  System.out.println();
 }

 /**
  * It takes XML document file as input and reads it to Java content tree.
  * Method does validation with schema before reading XML document. However
  * validation fails, unmarshalling continues for rest of document.
  * 
  * @param File
  *            to read to Java content tree.
  */
 public final void UnMarshalToJava(File f) {
  try {
   final Unmarshaller unMarshaller = jc.createUnmarshaller();
   final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
   final Schema schema = schemaFactory.newSchema(new File(SCHEMA_FILE));
   unMarshaller.setSchema(schema);
   unMarshaller.setEventHandler(new ValidationEventHandler() {
    @Override
    public boolean handleEvent(ValidationEvent event) {
     System.out.println("UnMarshalToJava - Schema Validation Issue : Severity : " + event.getSeverity());
     System.out.println("UnMarshalToJava - Schema Validation Issue : Message : " + event.getMessage());
     System.out.println("UnMarshalToJava - Schema Validation Issue : LineNumber : "
       + event.getLocator().getLineNumber());
     System.out.println("UnMarshalToJava - Schame Validation Issue : ColumnNumber : "
       + event.getLocator().getColumnNumber());
     return true;
    }
   });
   final User user = (User) unMarshaller.unmarshal(f);
   System.out.println();
   System.out.println("Unmarshlled User object is " + user);
  } catch (JAXBException | SAXException e) {
   System.out.println("Exception in UnMarshalToJava " + e.getMessage());
  }
 }

 /**
  * This method generate the schema at specified location using Java classes
  * as specified in JAXBContext.
  */
 public final void generateSchema() {
  try {
   jc.generateSchema(new SchemaOutputResolver() {

    @Override
    public Result createOutput(String namespaceUri, String suggestedFileName) throws IOException {
     final File file = new File(SCHEMA_FILE);
     final StreamResult result = new StreamResult(file);
     result.setSystemId(file.toURI().toURL().toString());
     return result;
    }
   });
  } catch (IOException e) {
   System.out.println("Exception in generateSchema " + e.getMessage());
  }
 }

}

handleEvent method of ValidationeventHandler returns true which means, unmarshalling or marshalling to continue even if there are validation error occurs.

Now, we just need to call each of above methods,

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package in.blogspot.ashish4java.userdetails;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import in.blogspot.ashish4java.userdetails.helper.MarshallerAndUnMarshaller;
import in.blogspot.ashish4java.userdetails.model.Address;
import in.blogspot.ashish4java.userdetails.model.User;

public class Main {

 public static void main(String[] args) {
  User user = createUser();
  MarshallerAndUnMarshaller marshUnMarsh = new MarshallerAndUnMarshaller();
  marshUnMarsh.generateSchema();
  marshUnMarsh.MarshalToXML(user);
  marshUnMarsh
    .UnMarshalToJava(new File(MarshallerAndUnMarshaller.USER_XML_FILE));
 }

 private static User createUser() {
  User user = new User("Ashish", null, createAddressList(), 1);
  return user;
 }

 private static List<Address> createAddressList() {
  Address address1 = new Address("Sydney", "CBD", "Australia", null, null);
  Address address2 = new Address("Mumbai", null, "India", null, null);
  Address address3 = new Address("London", "Zone1", "United kingdom", null, null);
  List<Address> addressList = new ArrayList<Address>();
  addressList.add(address1);
  addressList.add(address2);
  addressList.add(address3);
  return addressList;
 }

}

With this, on calling executing this program, we get below output,

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<User>
    <UserId>1</UserId>
    <FirstName>Ashish</FirstName>
    <Addresses>
        <Address>
            <AddressLine1>CBD</AddressLine1>
            <AddressLine2>Australia</AddressLine2>
            <City>Sydney</City>
        </Address>
        <Address>
            <AddressLine2>India</AddressLine2>
            <City>Mumbai</City>
        </Address>
        <Address>
            <AddressLine1>Zone1</AddressLine1>
            <AddressLine2>United kingdom</AddressLine2>
            <City>London</City>
        </Address>
    </Addresses>
</User>

MarshalToXML - Schema Validation Issue : Severity : 2
MarshalToXML - Schema Validation Issue : Message : cvc-complex-type.2.4.a: Invalid content was found starting with element 'Addresses'. One of '{SurName}' is expected.
MarshalToXML - Schema Validation Issue : Severity : 2
MarshalToXML - Schema Validation Issue : Message : cvc-complex-type.2.4.a: Invalid content was found starting with element 'AddressLine2'. One of '{AddressLine1}' is expected.


UnMarshalToJava - Schema Validation Issue : Severity : 2
UnMarshalToJava - Schema Validation Issue : Message : cvc-complex-type.2.4.a: Invalid content was found starting with element 'Addresses'. One of '{SurName}' is expected.
UnMarshalToJava - Schema Validation Issue : LineNumber : 5
UnMarshalToJava - Schame Validation Issue : ColumnNumber : 16
UnMarshalToJava - Schema Validation Issue : Severity : 2
UnMarshalToJava - Schema Validation Issue : Message : cvc-complex-type.2.4.a: Invalid content was found starting with element 'AddressLine2'. One of '{AddressLine1}' is expected.
UnMarshalToJava - Schema Validation Issue : LineNumber : 12
UnMarshalToJava - Schame Validation Issue : ColumnNumber : 27

Unmarshlled User object is User [firstName=Ashish, surName=null, addressList=[Address [city=Sydney, address_line_1=CBD, address_line_2=Australia, address_line_3=null, address_line_4=null], Address [city=Mumbai, address_line_1=null, address_line_2=India, address_line_3=null, address_line_4=null], Address [city=London, address_line_1=Zone1, address_line_2=United kingdom, address_line_3=null, address_line_4=null]], userId=1]

There are validation errors thrown as we have not set 'SurName' field in user object and 'AddressLine2' is also null in one of Address object.

We have not covered 'xjc' which is XML to JAVA compiler to generate java classes from schema document. This is mainly used in 'contract-first' approach where schema document contract is defined first and based on that, Java classes are generated.

This is all about JAXB in this tutorial. Thanks and STAY TUNED!

Friday, 17 July 2015

5. JAX-RS WebService : Exception(Fault) handling

We have seen all success scenarios till now. However what if there is exception? how to handle those in RESTful webservices? Handling of exception mainly includes how to return meaningful message to end client.

There are couple of ways to handle exception in RESTful webservices. First we will look into using "ExceptionMapper". javax.ws.rs.ext.ExceptionMapper is interface which have one method to be implemented 'toResponse(E exception)'.

We can implement this interface and create mapper for each type of exception which application can throw. We can have one generic mapper as well to cover all types of exception like mapper for "Throwable".

In last tutorial, we have seen deletion of product method which takes productId and based on that, it deletes the product. But what if there is no product for given productId and application throws 'EntityNotFoundException'?

Let's see how response looks like with this exception,

delete method is below:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
 @DELETE
 @Path("{productId}")
 @Produces(MediaType.TEXT_PLAIN)
 public final Response deleteProduct(@PathParam("productId") final int productId) throws EntityNotFoundException {
  final ProductDao productDao = new ProductDao();
  final boolean isDeleted = productDao.deleteProduct(productId);
  if (isDeleted) {
   return Response.status(Status.OK)
     .header("RESOURCE_DELETED", "TRUE")
     .entity("Resource deleted successfully!").build();
  } else {
   throw new EntityNotFoundException("Product with id " + productId + " not found.");
  }

 }

EntityNotFoundException is custom exception type as below:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package in.blogspot.ashish4java.invoicedetails.exception;

public class EntityNotFoundException extends Exception {

 /**
  * 
  */
 private static final long serialVersionUID = -3227525992494807317L;


 public EntityNotFoundException(final String errorMessage) {
  super(errorMessage);
 }
 
}

With this, if there is deletion request for product which doesn't exists then response looks like below,



What is problem with this response:

  • It doesn't return user friendly error message. It returns standard tomcat error message which doesn't provide additional information on why exception was thrown.
  • Status code returned is 500 which is 'Internal Server Error' however as this is business error we want to set to something like "404 Not Found" for resource not found.

So, let's create ExceptionMapper for this as below,


1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package in.blogspot.ashish4java.invoicedetails.exception;

import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

@Provider
public class EntityNotFoundExceptionMapper implements ExceptionMapper<EntityNotFoundException> {

 @Override
 public Response toResponse(EntityNotFoundException e) {
  return Response.status(Status.NOT_FOUND).header("RESOURCE_FOUND", "FALSE")
    .entity(e.getMessage())
    .build();
 }

}

Note down @Provider annotation which registers this exception mapper in JAX-RS runtime. This declares that the class is of interest to the JAX-RS runtime. toResponse builds the response object by setting status code, response header and error message as response content.

With this, when an application throws an EntityNotFoundException, the toresponse method of the EntityNotFoundExceptionMapper instance will be invoked.

 If there is request to delete Product which does not exists then response will be like below,


Status code is 404 Not found and response content clearly states reason for what went wrong.

This is all about exception mappers.

The other way to handle exception is to throw 'WebApplicationException' or one of subclasses of it. However sometimes this approach is not preferable. This approach either needs to modify existing custom exception type which needs to extend 'WebApplicationException' or have to use existing subclass of WebApplicationException exception. 

In both scenarios, this approach doesn't provide flexibility as first approach of using Exceptionmapper. 

In next tutorial, we will see how to use subresources. Thanks for reading this and STAY TUNED!




Sunday, 5 July 2015

4. JAX-RS WebService : Use of @UPDATE, @DELETE and @POST

In this tutorial, we will see how to update, delete and add new product. As we have seen previously, all these transactions needs to be mapped to HTTP methods. updating existing product will be mapped to "PUT" method of HTTP. Similar way, deleting product maps to "DELETE" method of HTTP and adding new product maps to "POST" method.


First, we will update our dao class to perform these transactions,

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 public final boolean updateProduct(final Product product) {
  boolean isUpdated = false;
  if (productMap.containsKey(product.getProductId())) {
   productMap.put(product.getProductId(), product);
   isUpdated = true;
  }
  return isUpdated;
 }

 public final boolean deleteProduct(final int productId) {
  boolean isDeleted = false;
  if (productMap.containsKey(productId)) {
   productMap.remove(productId);
   isDeleted = true;
  }
  return isDeleted;
 }
 
 public final Product addProduct(final Product product){
  product.setProductId(productMap.size() + 1);
  productMap.put(productMap.size() + 1, product);
  return product;
 }


Now, we will add "deleteProduct" method in our resource class as below,

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
 @DELETE
 @Path("{productId}")
 @Produces(MediaType.TEXT_PLAIN)
 public final Response deleteProduct(@PathParam("productId") final int productId) {
  final ProductDao productDao = new ProductDao();
  final boolean isDeleted = productDao.deleteProduct(productId);
  if (isDeleted) {
   return Response.status(Status.OK)
     .header("RESOURCE_DELETED", "TRUE")
     .entity("Resource deleted successfully!").build();
  } else {
   return Response.status(Status.NOT_FOUND).header("RESOURCE_DELETED", "FALSE")
     .entity("Something went wrong!")
     .build();
  }

 }

Return type of this method is "Response". We build the response based on whether deletion of product is successful or not. HTTP status code is set in response, Custom HTTP response header "RESOURCE_DELETED" is set as well. Entity is set as well to give meaningful text to client.


Request: deletion request to delete product with productId = 3


Success Response returned with our custom response header set to 'TRUE'


Response message also returned successfully.



Similar way, @PUT request can be added to update product details

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
 @PUT
 @Consumes(MediaType.APPLICATION_JSON)
 @Produces(MediaType.TEXT_PLAIN)
 public final Response updateProduct(final Product product) {
  final ProductDao productDao = new ProductDao();
  final boolean isUpdated = productDao.updateProduct(product);
  if (isUpdated) {
   return Response.status(Status.OK).header("RESOURCE_UPDATED", "TRUE")
     .entity("Resource updated successfully!").build();
  } else {
   return Response.status(Status.NOT_FOUND).header("RESOURCE_UPDATED", "FALSE")
     .entity("Something went wrong!")
     .build();
  }

 }

Request:


Body of request :

Response:


Similar way, to add new resource, HTTP POST method can be used as below,

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
  @POST
  @Consumes(MediaType.APPLICATION_JSON)
  @Produces(MediaType.TEXT_PLAIN)
  public final Response addProduct(final @Context UriInfo uriInfo, Product product) { 
   final ProductDao productDao = new ProductDao();
   product = productDao.addProduct(product);
   final URI loc = uriInfo.getAbsolutePathBuilder()
     .path("" + product.getProductId()).build();
   return Response.created(loc)
     .entity("Product Added Successfully").build();   
  }

In this tutorial, we have seen number of concepts including how to use @PUT, @POST, @DELELE HTTP methods, how to send message body in request, how to return Response object in response, how to set Status code in response, how to set custom response header. In "addProduct" method for @POST, we are also setting "Location" header. This is to return resource uri of new product added. So client knows the uri of new resource which will be used for future requests.

I hope this tutorial helped you to understand RESTful webservices better. In next tutorial, we will see how to handle faults in REST. Thanks for reading this and STAY TUNED!