Saturday, January 16, 2016

Jersey RESTful web service tutorial. The basics

REST (Representational State Transfer) - is a software architectural style that is used to develop distributed applications.
Web services created using this approach are called RESTful.

Let's create an application that provides such web service.

This post is available in Russian.

REST principles:
  • Resources are identified by URI
  • Resources can have multiple representations
  • Resources can be retrieved, created, modified or deleted via standard HTTP methods
  • Server does not save any state

Let's design an application according to them. It will work with quotes of famous people. In this context quotes are our resources.
We will have next HTTP methods and URIs for all needed operations:

HTTP method URI Operation
GET /quotes/1 Retrieve the quote
POST /quotes Create a quote
PUT /quotes/1 Modify the quote
DELETE /quotes/1 Delete the quote

We will use JSON as a resource representation.

Also, we will use:
  • Java 8
  • Jersey
  • Maven 3.3.3
  • Tomcat 8.0.30

Let's develop! Add Jersey dependencies into pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>jersey-basics-tutorial</groupId>
    <artifactId>jersey-basics-tutorial</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <jersey.version>2.22.1</jersey.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.glassfish.jersey.containers</groupId>
            <artifactId>jersey-container-servlet</artifactId>
            <version>${jersey.version}</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-moxy</artifactId>
            <version>${jersey.version}</version>
        </dependency>
    </dependencies>

    <build>
        <finalName>tutorial</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.19.1</version>
                <configuration>
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Create QuoteDto class with author and text properties. It will be used to store quote data.
Add @XmlRootElement annotation. Due to it QuoteDto objects can be transformed from/to XML and JSON objects:
package com.jersey.tutorial.dto;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class QuoteDto {

    private String author;
    private String text;

    public QuoteDto() {

    }

    public QuoteDto(String author, String text) {
        this.author = author;
        this.text = text;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }
}

Create QuotesService class with CRUD methods to work with QuoteDto objects.
For simplicity, we won't use a database. Instead of this we will store objects in a synchronized HashMap:
package com.jersey.tutorial.service;

import com.jersey.tutorial.dto.QuoteDto;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class QuotesService {

    private static final QuotesService INSTANCE = new QuotesService();
    private static final Map<Integer, QuoteDto> QUOTES = Collections.synchronizedMap(new HashMap<Integer, QuoteDto>());

    private Integer id = 0;

    public static QuotesService getInstance() {
        return INSTANCE;
    }

    public QuoteDto getQuote(Integer id) {
        return QUOTES.get(id);
    }

    public void addQuote(QuoteDto quote) {
        synchronized(QUOTES) {
            id++;
            QUOTES.put(id, quote);
        }
    }

    public void updateQuote(Integer id, QuoteDto updatedQuoteDto) {
        synchronized(QUOTES) {
            QuoteDto quoteDto = QUOTES.get(id);

            if (quoteDto != null) {
                QUOTES.put(id, updatedQuoteDto);
            }
        }
    }

    public void deleteQuote(Integer id) {
        QUOTES.remove(id);
    }
}

Create QuotesResource class with web service methods, which will be used by clients to work with resources.
Add @Path annotation for the class. It defines URI common for all methods.
Moreover, add next methods:
  • getQuote() to retrieve a quote.
    According to earlier developed architecture it should be invoked by GET request, that's why add @GET annotation for it.
    Add @Produces annotation to return resources in JSON format.
    Add @Path annotation to set method URI.
    Add @PathParam annotation to access id URI parameter in method body.
    Method uses QuotesService object to retrieve QuoteDto object and returns it using Response object
  • addQuote() to create a quote.
    Method should be invoked by POST request, so add @POST annotation.
    Add @Consumes annotation to receive data in JSON format.
    Method returns no data, that's why @Produces annotation is not needed.
    Method URI is the same as common one so that we don't need @Path annotation
  • updateQuote() to modify the quote.
    Method should be invoked by PUT request, so add @PUT annotation.
    Add @Consumes, @Path and @PathParam annotations
  • deleteQuote() to delete the quote.
    Method should be invoked by DELETE request, so add @DELETE annotation.
    Add @Path and @PathParam annotations
package com.jersey.tutorial.ws;

import com.jersey.tutorial.dto.QuoteDto;
import com.jersey.tutorial.service.QuotesService;

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.core.MediaType;
import javax.ws.rs.core.Response;

@Path("/quotes")
public class QuotesResource {

    private QuotesService quotesService = QuotesService.getInstance();

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/{id}")
    public Response getQuote(@PathParam("id") Integer id) {
        QuoteDto quote = quotesService.getQuote(id);
        return Response.ok(quote).build();
    }

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response addQuote(QuoteDto quoteDto) {
        quotesService.addQuote(quoteDto);
        return Response.ok().build();
    }

    @PUT
    @Consumes(MediaType.APPLICATION_JSON)
    @Path("/{id}")
    public Response updateQuote(@PathParam("id") Integer id, QuoteDto quoteDto) {
        quotesService.updateQuote(id, quoteDto);
        return Response.ok().build();
    }

    @DELETE
    @Path("/{id}")
    public Response deleteQuote(@PathParam("id") Integer id) {
        quotesService.deleteQuote(id);
        return Response.ok().build();
    }
}

To deploy our application create QuotesApplication class, which should extend ResourceConfig class.
Add @ApplicationPath annotation to set root application URI.
Class constructor should invoke derived packages() method. Set its argument to application root package name:
package com.jersey.tutorial.ws;

import org.glassfish.jersey.server.ResourceConfig;

import javax.ws.rs.ApplicationPath;

@ApplicationPath("/")
public class QuotesApplication extends ResourceConfig {

    public QuotesApplication() {
        packages("com.jersey.tutorial");
    }
}

Build the project by executing "mvn clean install" command:
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building jersey-basics-tutorial 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:2.4.1:clean (default-clean) @ jersey-basics-tutorial ---
[INFO] Deleting C:\Blog\jersey-basics-tutorial\target
[INFO] 
[INFO] --- maven-resources-plugin:2.5:resources (default-resources) @ jersey-basics-tutorial ---
[debug] execute contextualize
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO] 
[INFO] --- maven-compiler-plugin:2.3.2:compile (default-compile) @ jersey-basics-tutorial ---
[INFO] Compiling 4 source files to C:\Blog\jersey-basics-tutorial\target\classes
[INFO] 
[INFO] --- maven-resources-plugin:2.5:testResources (default-testResources) @ jersey-basics-tutorial ---
[debug] execute contextualize
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory C:\Blog\jersey-basics-tutorial\src\test\resources
[INFO] 
[INFO] --- maven-compiler-plugin:2.3.2:testCompile (default-testCompile) @ jersey-basics-tutorial ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] --- maven-surefire-plugin:2.19.1:test (default-test) @ jersey-basics-tutorial ---
[INFO] Tests are skipped.
[INFO] 
[INFO] --- maven-war-plugin:2.6:war (default-war) @ jersey-basics-tutorial ---
[INFO] Packaging webapp
[INFO] Assembling webapp [jersey-basics-tutorial] in [C:\Blog\jersey-basics-tutorial\target\tutorial]
[INFO] Processing war project
[INFO] Copying webapp resources [C:\Blog\jersey-basics-tutorial\src\main\webapp]
[INFO] Webapp assembled in [361 msecs]
[INFO] Building war: C:\Blog\jersey-basics-tutorial\target\tutorial.war
[INFO] 
[INFO] --- maven-install-plugin:2.3.1:install (default-install) @ jersey-basics-tutorial ---
[INFO] Installing C:\Blog\jersey-basics-tutorial\target\tutorial.war to C:\Users\alex\.m2\repository\jersey-basics-tutorial\jersey-basics-tutorial\1.0-SNAPSHOT\jersey-basics-tutorial-1.0-SNAPSHOT.war
[INFO] Installing C:\Blog\jersey-basics-tutorial\pom.xml to C:\Users\alex\.m2\repository\jersey-basics-tutorial\jersey-basics-tutorial\1.0-SNAPSHOT\jersey-basics-tutorial-1.0-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.545s
[INFO] Finished at: Fri Jan 15 15:40:47 EET 2016
[INFO] Final Memory: 17M/215M
[INFO] ------------------------------------------------------------------------

Copy created war-file into Tomcat webapps directory, start the server and verify how our application works with REST client, like Postman.

Add a quote with POST request:






















Retrieve the quote with GET request:



















Modify the quote with PUT request:






















Verify that quote is modified with GET request:



















Delete the quote with DELETE request:














Verify that quote is deleted with GET request:


















Project structure:



Application source code is available at https://subversion.assembla.com/svn/jersey-basics-tutorial/trunk/.

Recommended posts:

No comments:

Post a Comment