@Path annotation: Binding urls to service methods

In the earlier customer service example you might have noticed that the service is annotated with @Path("/customers"). This annotation plays a key role in routing incoming request urls to appropriate methods. We will build the ProductService and through that we will learn more about @Path.

ProductService.java


 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
package com.example.service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;

import com.example.Product;

/**
 * @author Santosh
 *
 */
@Path("/products")
public class ProductService {

 private Map<Integer, Product> productDb = new ConcurrentHashMap<Integer, Product>();

 private AtomicInteger idCounter = new AtomicInteger();

 public ProductService() {
  Product p1 = new Product();
  p1.setId(idCounter.getAndIncrement());
  p1.setName("Pen");
  p1.setCost(10.0);

  Product p2 = new Product();
  p2.setId(idCounter.getAndIncrement());
  p2.setName("Book");
  p2.setCost(20.0);

  productDb.put(p1.getId(), p1);
  productDb.put(p2.getId(), p2);
 }

 @GET
 @Produces("application/json")
 public List<Product> getAllProducts() throws WebApplicationException {

  List<Product> products = new ArrayList<>();
  for (Product product : productDb.values()) {
   products.add(product);
  }
  return products;

 }

 @POST
 @Produces("application/json")
 @Consumes("application/json")
 public Product addProduct(Product product) throws WebApplicationException {

  if (product == null) {
   throw new WebApplicationException(Response.Status.BAD_REQUEST);
  } else {
   int id = idCounter.getAndIncrement();
   product.setId(id);
   product.setName(product.getName());
   product.setCost(product.getCost());
   productDb.put(id, product);
   return productDb.get(id);
  }

 }

 @PUT
 @Produces("application/json")
 @Consumes("application/json")
 public Product updateProduct(Product product)
   throws WebApplicationException {

  if (productDb.get(product.getId()) == null) {
   throw new WebApplicationException(Response.Status.NOT_FOUND);
  } else {
   productDb.put(product.getId(), product);
   return productDb.get(product.getId());
  }

 }

 @DELETE
 @Produces("application/json")
 @Consumes("application/json")
 public Product cancelProduct(Product product)
   throws WebApplicationException {

  if (productDb.get(product.getId()) == null) {
   throw new WebApplicationException(Response.Status.NOT_FOUND);
  } else {
   productDb.remove(product.getId());
   return productDb.get(product.getId());
  }

 }

 @GET
 @Path("/{id}")
 public Product getProducts(@PathParam("id") int id)
   throws WebApplicationException {

  if (productDb.get(id) == null) {
   // no such customer exists
   throw new WebApplicationException(Response.Status.NOT_FOUND);
  } else {
   return productDb.get(id);

  }

 }

}


Just like we defined the methods to add,update, delete, fetch customers we have added methods for products now. Notice the new value for @produces, @consumes annotation. The value we gave is application/json which means we product service communicates via JSON.


Root Resources
For a moment suppose that the path annotation for ProductService is just @Path("/") then the ProductService is called Root Resource as the string immediately after the root of the web application will be matched with this service. Lets say your webapp is MyWebApp and the url is http://localhost:8090/MyWebApp/xyz then part of the url appearing after http://localhost:8090/MyWebApp is matched with the resource path.

@Path annotations on methods

Methods in service can also be annotated with @Path. Take a look at getProduct() method in ProductService. However it is not mandatory for a method to have @Path annotation to be invoked. Please note that If the method has to be invoked it should have one of the http method annotations @GET,@POST,@PUT,@DELETE.

Once the method has @Path only if the url matches the resource path concatenated with method path the method would be invoked. Lets say we have new method under ProductService called getDiscontinuedProducts with @Path("discontinued").

1
2
3
4
5
@GET
@Produces("application/json")
@Path("discontinued")
public Product getDiscountinuedProducts()
   throws WebApplicationException {...}

To call the above method the url should be like
GET http://localhost:8090/MyWebApp/products/discontinued


@Path expressions
We can have templates in the @Path expressions. These templates will be populated based on the incoming url. Take for example the below customer service


1
2
3
4
5
6
7
8
@Path("/customers")
public class CustomerResource {
 @GET
 @Path("{id}")
 public String getCustomer(@PathParam("id") int id) {
 ...
 }

If the url is http://localhost:8090/MyWebApp/customers/99 the value for id will be 99.

We can  have multiple templates in the @path.  Lets say if we have a method like below then the url to match this method would be GET/customers/santosh-srinivas

1
2
3
4
5
6
7
8
9
@Path("/")
public class CustomerResource {
 @GET
 @Path("customers/{firstname}-{lastname}")
 public String getCustomer(@PathParam("firstname") String first,
 @PathParam("lastname") String last) {
 ...
 }
}
Similarly you can have regular expressions in @Path. The below method will be matched only for GET /customers/1 and not for GET /customers/xyz
1
2
3
4
5
6
7
8
@Path("/customers")
public class CustomerResource {
 @GET
 @Path("{id : \\d+}")
 public String getCustomer(@PathParam("id") int id) {
 ...
 }
}


Testing the ProductService
We will test the product service we developed almost the same way we tested CustomerService.

Fetching the products


Observe the Accept header value passed as application/json. This is a way to tell the server that the client is expecting json response.  The response for this call would be


The body of the response is listing the products in the system in JSON format. Notice the difference between JSON and xml. JSON is more concise.


Adding a product
Let us say we want to add a product Optical Mouse priced at 150 Rs to the system. The http operation would be post and the body should be JSON as the service is annotated with @consumes("application/json"). Sending xml data wont succeed.

{"name":"Optical Mouse","cost":150.0}

The response for this call would be creation of a new product and the product is returned with id field set.


The response is the new product in JSON format.


We are not mentioning the Update and Delete cases as they are exactly similar to POST except that the data passed would have the correct product id populated. You can try that as an exercise

No comments:

Post a Comment