RESTful web services are built to work best on the Web. Representational State Transfer (REST) is an architectural style that specifies constraints, such as the uniform interface, that if applied to a web service induce desirable properties, such as performance, scalability, and modifiability, that enable services to work best on the Web. In the REST architectural style, data and functionality are considered resources and are accessed using Uniform Resource Identifiers (URIs), typically links on the Web.
In simple words, REST stands for Representational State Transfer and it’s an architecture style for creating network based applications. JAX-RS is the Java API specification and Jersey is its reference implementation.
Our goal is to create a RESTful service that can retrieve product and price details by ID. Our app (myRetail RESTful service) should be able to do the following:-
- Responds to an HTTP GET request at
/products/{id}
and delivers product data as JSON (where {id} will be a number. - Reads pricing information from a NoSQL data store and combines it with the product id and name from the HTTP request into a single response.
- Accepts an HTTP PUT request at the same path (
/products/{id}
), containing a JSON request body similar to the GET response, and updates the product’s price in the data store. - Delete a product price entry from db.
Example response: {"id":13860428,"name":"The Big Lebowski (Blu-ray) (Widescreen)","current_price":{"value": 13.49,"currency_code":"USD"}}
Okay, lets start with this simple CRUD Restful apis.
Step 1: We will create a dynamic web project
in Eclipse.
Step 2: Add web.xml
through Generate Deployment Descriptor Stub
in Java EE tools.
Step 3: Convert it to a Maven
Project.
Step 4: In pom.xml
add the following dependencies:-
<!-- https://mvnrepository.com/artifact/asm/asm -->
<dependency>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
<version>3.3.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.sun.jersey/jersey-bundle -->
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-bundle</artifactId>
<version>1.19</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.json/json -->
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20160810</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.sun.jersey/jersey-server -->
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
<version>1.19.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.sun.jersey/jersey-core -->
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-core</artifactId>
<version>1.19.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mongodb/mongo-java-driver -->
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>3.4.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.sun.jersey/jersey-json -->
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>1.19</version>
</dependency>
Step 5: Update web.xml
with the following mapping:-
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Jersey Web Application</servlet-name>
<url-pattern>/myRetail/*</url-pattern>
</servlet-mapping>
Step 6: Creating our POJO classes with @XmlRootElement
to define the root element for an XML tree.
@XmlRootElement
public class Product{
private int id;
private String name;
private Price current_price;
...
}
Step 7: We will create a class named MyRetailService
with /products
as its @Path
as per the requirements. Read this for guidelines on resource URI naming conventions. This class will have all our api calls.
a) To create pricing information in Product, we will do a POST
request consuming a json/xml of Price type.
@POST @Path("/create")
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response createPriceData(Price price){
String success = dao.createPriceData(price);
return Response.status(201).entity(success).build();
}
b) To retrieve all the details of products in myRetail app, we will go for a GET
call.
@GET
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response findAll() {
List<Product> product = dao.createDummyProducts();
GenericEntity<List<Product>> entity = new GenericEntity<List<Product>>(product) {};
return Response.ok(entity).build();
}
This is the case when we need to return parameterized types from a JAXRS resource method in the Response
. Due to type erasure, it requires special handling in Jersey runtime to determine the generic type that is required to select a suitable MessageBodyWriter
. GenericEntity
can be used to represent a response entity of a generic type.
c) To update a price value, we will go for PUT
@PUT @Path("{id}")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response updatePrice(@PathParam("id") String id,Product product) {
Product updatedProduct = dao.update(Integer.parseInt(id),product);
return Response.status(200).entity(updatedProduct).build();
}
d) Delete
a price entry from product.
@DELETE @Path("{id}")
public Response delete(@PathParam("id") String id) {
String response = dao.delete(Integer.parseInt(id));
return Response.status(202).entity(response).build();
}
We have handled the exceptions by throwing GenericException
. However, we still need to create a Handler object to convert this exception into an actual JSON response so we get a nice friendly error message, which is implemented through GenericExceptionMapper
.
@Provider
public class GenericExceptionMapper implements ExceptionMapper<Throwable>{
public Response toResponse(Throwable ex){
if(ex instanceof ProductSearchException){
return Response.status(Status.NOT_FOUND)
.entity(new ErrorProps("404", ex.getMessage()))
.build();
}else{
return Response.status(Status.INTERNAL_SERVER_ERROR)
.entity(new ErrorProps("Some error code, 500 or somthing", ex.getMessage()))
.build();
}
}
}
Step 8: To test, we can use postman or jersey.api.client
private static final String webServiceURI = "http://localhost:8080/myRetail-0.0.1-SNAPSHOT/myRetail/products/";
public static void main(String[] args) {
Client client = Client.create();
WebResource webResource = client.resource(webServiceURI);
createDummyPriceTest(webResource);
}
private static void createDummyPriceTest(WebResource webResource){
Price price = new Price();
price.set_id(16483589);
price.setCurrency_code("USD");
price.setValue(13.49);
//or String input = "{\"currency_code\":\"USD\",\"_id\":\"16483589\",\"value\":\"13.49\"}";
ClientResponse response = webResource.path("create").type(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON).post(ClientResponse.class,price);
if (response.getStatus() != 201) {
throw new RuntimeException("Failed : HTTP error code : " + response.getStatus());
}
String output = response.getEntity(String.class);
System.out.println(output);
}
Summary of JAX-RS Annotations used
Annotations | Usage |
Path | It identifies the URI path |
PathParam | Represents the parameter of the URI path. |
GET | Specifies method responds to GET request. |
POST | Specifies method responds to POST request. |
PUT | Specifies method responds to PUT request. |
DELETE | Specifies method responds to DELETE request. |
Produces | Defines media type for the response such as XML, PLAIN, JSON etc. It defines the media type that the methods of a resource class or MessageBodyWriter can produce. |
Consumes | It defines the media type that the methods of a resource class or MessageBodyReader can produce. |
Provider | The @Provider annotation is used for anything that is of interest to the JAX-RS runtime, such as MessageBodyReader and MessageBodyWriter. For HTTP requests, the MessageBodyReader is used to map an HTTP request entity body to method parameters. On the response side, a return value is mapped to an HTTP response entity body by using a MessageBodyWriter. If the application needs to supply additional metadata, such as HTTP headers or a different status code, a method can return a Response that wraps the entity and that can be built using Response.ResponseBuilder. |
This is a very simple example of a RESTful service that uses JAX-RS annotations. You can check the repo for the DAO layer implementation with the usage of MongoClient
. Find the same repo in Kotlin.