Working with JAX-RS Path Expressions(@Path,@PathParam)

This tutorial shows various forms of path expressions (@Path and @PathParam) supported by JAX-RS and how to map them to REST methods and method parameters in Resource class.

Example Scenario :

We have a rest service which is like a shopping cart and holds some items, say 10 in number.
We want to retrieve one or more items from the cart based on different path expressions in our URI.


Project artifacts:

1. Technologies used –

a. Eclipse IDE for Java EE developer 4.5.0.20150621-1200 (Mars)
b. Preinstalled Maven with Eclipse Mars
c. Tomcat 7.0.64
d. RESTEasy 3.0.4.Final
e. Java 1.8

Jars used –

javax.ws.rs-api-2.0.jar
resteasy-jaxrs-3.0.4.Final.jar
jaxrs-api-3.0.4.Final.jar
slf4j-simple-1.5.8.jar
slf4j-api-1.5.8.jar
scannotation-1.0.3.jar
javassist-3.12.1.GA.jar
jboss-annotations-api_1.1_spec-1.0.1.Final.jar
activation-1.1.jar
httpclient-4.2.1.jar
httpcore-4.2.1.jar
commons-logging-1.1.1.jar
commons-codec-1.6.jar
commons-io-2.1.jar
jcip-annotations-1.0.jar

2. Eclipse project folder structure:

REST Web Project - Maven directory Structure

 

3. Web Project

Create a standard web project in Eclipse by following the below link.

Create a Maven Web Project

4. Maven Dependencies

file:pom.xml. Maven unit of work to declare project dependencies, compile code and build war file for deployment in Tomcat.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.javanbeyond</groupId>
    <artifactId>WorkingWithATPath</artifactId>
    <packaging>war</packaging>
    <version>0.0.1-SNAPSHOT</version>
    <name>Restful Maven Webapp</name>
    <url>http://maven.apache.org</url>
    <dependencies>
        <dependency>
            <groupId>javax.ws.rs</groupId>
            <artifactId>javax.ws.rs-api</artifactId>
            <version>2.0</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-jaxrs</artifactId>
            <version>3.0.4.Final</version>
        </dependency>
    </dependencies>
    <build>
        <finalName>WorkingWithATPath</finalName>
    </build>
</project>

 

5. POJO

file: ShoppingItem.java.A POJO which is stored as cart item in our shopping cart web service and data of which is sent to client based on request URI.

 

package com.javanbeyond.data;
//  Simple POJO
public class ShoppingItem {
  private int id;
  private String name;
  private String price;
  private String quantity;

  public int getId() {
    return id;
  }

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

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getPrice() {
    return price;
  }

  public void setPrice(String price) {
    this.price = price;
  }

  public String getQuantity() {
    return quantity;
  }

  public void setQuantity(String quantity) {
    this.quantity = quantity;
  }

}

 

6. Application class

file: MyApplication.java. The Class that registers the resource class OrderReceiver with JAX-RS runtime. It has two sets.
Our resource class should be added to the singleton set.

package com.javanbeyond.service;

import javax.ws.rs.core.Application;
import java.util.HashSet;
import java.util.Set;

// Regular Application class
public class MyApplication extends Application {
  private Set<Object> singletons = new HashSet<Object>();
  private Set<Class<?>> empty = new HashSet<Class<?>>();

  public MyApplication() {
    singletons.add(new OrderReceiver());
  }

  @Override
  public Set<Class<?>> getClasses() {
    return empty;
  }

  @Override
  public Set<Object> getSingletons() {
    return singletons;
  }
}

7. Resource class

file: OrderReceiver.java . In JAX-RS we use @Path and @PathParam annotations to capture information provided in the URI and pass it to annotated methods as parameters.

The “/orders” @Path value  before the class brings the URI “http://localhost:8080/rest/orders” to this class. The part after it decides which method will be called at runtime. This class has five methods which handle different variations of path elements in the URI.

Case 1: No Path annotation

The getAllItems method handles all URIs which don’t have a path after “/orders” as this is only method which would match this kind of pattern. It returns all the items from the cart.

// No Path Element. This is default when no id is provided
  // We will assume he wants all the created items from cart
  @GET
  // Shows the response will be XML
  @Produces("application/xml")
  public StreamingOutput getAllItems() {
    System.out.println("in getAllItems");
    return new StreamingOutput() {
      public void write(OutputStream outputStream)
          throws IOException, WebApplicationException {
        outputRequestedItem(outputStream, shoppingCart);
      }
    };
  }

Case 2 : Static Path values

getFirstItem method is called when the URI pattern is like “http://localhost:8080/rest/orders/1”. After orders, “/1” in the URI matches the method’s pattern in Path annotation. It returns only the first item from the shopping cart with id value 1.

// This is called only when 1 is mentioned at the end of the path. The below
  // method getItemById
  // also matches this pattern, but this method has more precedence.
  @GET
  @Path("/1")
  // Shows the response will be XML
  @Produces("application/xml")
  public StreamingOutput getFirstItem() {
    System.out.println("in getFirstItem");
    final ShoppingItem item = shoppingCart.get(1);
    System.out.println("Getting cart item " + item.getId());
    return new StreamingOutput() {
      public void write(OutputStream outputStream)
          throws IOException, WebApplicationException {
        outputRequestedItem(outputStream, item);
      }
    };
  }

Case 3: Simple URI with parameters

getItemById method is called when the URI is like “http://localhost:8080/rest/orders/{id}” where id ranges from 1 to 10. When the REST runtime sees this pattern, it gives the variable ‘id’ the value passed after “orders/” and passes it as method parameter which is annotated with  @PathParam. The placeholder value of @Path and @PathParam must match. e.g. “http://localhost:8080/rest/orders/2” URI gives id a value of 2 and the same value is given to the parameter “id”. This method returns the item that corresponds to the id passed in the URI.

// This is called when any particular Item is requested. id can be any value
  // from 1 to 10
  @GET
  @Path("{id}")
  // Shows the response will be XML
  @Produces("application/xml")
  public StreamingOutput getItemById(@PathParam("id"int id) {
    System.out.println("in getItemById");
    final ShoppingItem item = shoppingCart.get(id);
    if (item == null) {
      return new StreamingOutput() {
        public void write(OutputStream outputStream)
            throws IOException, WebApplicationException {
          PrintStream writer = new PrintStream(outputStream);
          writer.println(
              "<item>" + Response.Status.NOT_FOUND + "</item>");
        }
      };
    }
    System.out.println("Getting cart item " + item.getId());
    return new StreamingOutput() {
      public void write(OutputStream outputStream)
          throws IOException, WebApplicationException {
        outputRequestedItem(outputStream, item);
      }

    };
  }

Case 4: Path values with regular expressions

@Path can also take regular expressions as values and the method getItemByBetweenIds is called  if the regex matches the URI pattern. e.g. in the pattern “{ids : \\d-\\d}” , ids is the placeholder which would be mapped to the incoming URI value if it matches with the regex after colon and the same is passed to the PathParam annotated parameters of the method. In regex, \d stands for digits. So values like 3-4, 6-9 etc are valid values. It gets all the items with ids between left of hyphen to right of hyphen.

Sample URIs hitting this method can be : “http://localhost:8080/rest/orders/4-5”, “http://localhost:8080/rest/orders/7-9” etc.

// Regular expression id between min - max. Shows how regular expression can
  // be used with Path expression for easy retrieval
  @GET
  @Path("{ids : \\d-\\d}")
  // Shows the response will be XML
  @Produces("application/xml")
  public StreamingOutput getItemByBetweenIds(@PathParam("ids"String ids) {
    final Map<Integer, ShoppingItem> subCart = new HashMap<Integer, ShoppingItem>();
    String[] idParts = ids.split("-");
    int max = Integer.valueOf(idParts[1]);
    int min = Integer.valueOf(idParts[0]);
    for (int i = min; i <= max; i++) {
      subCart.put(i, shoppingCart.get(i));
    }
    if (subCart.isEmpty()) {
      return new StreamingOutput() {
        public void write(OutputStream outputStream)
            throws IOException, WebApplicationException {
          PrintStream writer = new PrintStream(outputStream);
          writer.println(
              "<item>" + Response.Status.NOT_FOUND + "</item>");
        }
      };
    }
    return new StreamingOutput() {

      public void write(OutputStream outputStream)
          throws IOException, WebApplicationException {
        outputRequestedItem(outputStream, subCart);
      }

    };
  }

Case 5: Multiple path parameters.

Method parameters can be mapped to two or more different values of path in the URI. In the below case, we have two different values id1 and id2. {id1} and {id2} are mapped to the respective same name parameters of the methods. Valid values can be:

“http://localhost:8080/rest/orders/4and5”, “http://localhost:8080/rest/orders/7and9” etc. It fetches items which lies between left and right side of ‘and’ in the path URI.

// More than one path parameter. Show multiple placeholders can be mapped to
  // multiple method arguments
  @GET
  @Path("{id1}and{id2}")
  // Shows the response will be XML
  @Produces("application/xml")
  public StreamingOutput getItemByBetweenIds(@PathParam("id1"int id1,
      @PathParam("id2"int id2) {
    final Map<Integer, ShoppingItem> subCart = new HashMap<Integer, ShoppingItem>();
    subCart.put(1, shoppingCart.get(id1));
    subCart.put(2, shoppingCart.get(id2));
    if (subCart.isEmpty()) {
      return new StreamingOutput() {
        public void write(OutputStream outputStream)
            throws IOException, WebApplicationException {
          PrintStream writer = new PrintStream(outputStream);
          writer.println(
              "<item>" + Response.Status.NOT_FOUND + "</item>");
        }
      };
    }
    return new StreamingOutput() {

      public void write(OutputStream outputStream)
          throws IOException, WebApplicationException {
        outputRequestedItem(outputStream, subCart);
      }

    };
  }

8. Deployment descriptor

file: web.xml . Deployment descriptor file for mapping URIs to servlet. Application class is registered by setting as init param to the servlet.

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
    <display-name>Archetype Created Web Application</display-name>
    <context-param>
        <param-name>resteasy.servlet.mapping.prefix</param-name>
        <param-value>/rest</param-value>
    </context-param>
    <servlet>
        <servlet-name>rest</servlet-name>
        <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
        <init-param>
            <param-name>javax.ws.rs.Application</param-name>
            <param-value>com.javanbeyond.service.MyApplication</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>rest</servlet-name>
        <url-pattern>/rest/*</url-pattern>
    </servlet-mapping>
    <welcome-file-list>
        <welcome-file>client.html</welcome-file>
    </welcome-file-list>
</web-app>

Example Execution:

  1. Deploy the application in Tomcat and type http://localhost:8080/WorkingWithATPath/ in browser.
  2. You should get a client.html like below. This is the client to interact with our web service. Pressing “GET” button with different URI patterns fetches different types of data from different methods.

RESTPath-Client

3. Get all the items by not giving any values after /orders.

REST-NoPathScenario

4. Get only the first item by providing “/1” at the end of URI.

RESTPath-Static Path values

5. Get a specific item by providing the id in the URI.

Path based on parameters

6. Get items based on regular expressions in the URI.

Path with regex expression

7. More than one path parameters mapping.

Multiple Path placeholders

Leave a Reply

Back to Top
%d bloggers like this: