Blue Flower

Introduction

Dans cet article, nous allons voir comment créer un web service de type RESTful utilisant JSON.

Le format JSON a l'avantage d'être plus concis que le format XML. Cette concision le rend moins lisible que le format XML.

Ce type de Web Service permet à partir des méthodes HTTP de manipuler des ressources :

  • GET : pour récupérer une ressource. Par exemple le livre d'id 1
  • POST : pour créer une ressource
  • PUT : pour modifier une ressource
  • DELETE : pour supprimer une ressource.

Pour l'exemple, la ressource sera un projet constitué d'un nom, d'une date de début et d'une date de fin.

Nous utiliserons le serveur Glassfish qui se base sur Jersey concernant les Web services RESTful.

Nous allons donc voir comment créer/modifier/supprimer/rechercher un projet via un Web Service RESTFul XML tout en stockant cette information dans une base MySQL via JPA.

 

Le fichier pom.xml

Il s'agit d'un pom de type war :

<?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>com.mycompany.prj3</groupId>
    <artifactId>PrjWebServiceRestXmlJson</artifactId>
    <version>1.0</version>
    <packaging>war</packaging>
       
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
         <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
    </properties>
        <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>7.0</version>
            <scope>provided</scope>
        </dependency>  
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.16</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
      <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                    <compilerArguments>
                        <endorseddirs>${endorsed.dir}</endorseddirs>
                    </compilerArguments>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.6</version>
                <executions>
                    <execution>
                        <phase>validate</phase>
                        <goals>
                            <goal>copy</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${endorsed.dir}</outputDirectory>
                            <silent>true</silent>
                            <artifactItems>
                                <artifactItem>
                                    <groupId>javax</groupId>
                                    <artifactId>javaee-endorsed-api</artifactId>
                                    <version>7.0</version>
                                    <type>jar</type>
                                </artifactItem>
                            </artifactItems>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

 

Création de l'entity Project avec ces annotations JPA

package com.mycompany.prj3.beans;

import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.xml.bind.annotation.XmlRootElement;

/**
 *
 * @author thierry
 */
@Entity
@Table(name = "PROJECT")

@NamedQueries({
    @NamedQuery(name = Project.PROJECT_FIND_ALL_PROJECTS, query = "SELECT dto FROM Project dto"),
    @NamedQuery(name = Project.PROJECT_FIND_BY_ID, query = "SELECT dto FROM Project dto where dto.projectId=:projectId"),
    @NamedQuery(name = Project.PROJECT_FIND_BY_NAME, query = "SELECT dto FROM Project dto where upper(dto.name) like upper (:pName)")
})

@XmlRootElement
public class Project {

    public static final String PROJECT_FIND_ALL_PROJECTS = "PROJECT_FIND_ALL";
    public static final String PROJECT_FIND_BY_ID = "PROJECT_FIND_BY_ID";
    public static final String PROJECT_FIND_BY_NAME = "PROJECT_FIND_BY_NAME";
    public static final String PROJECT_COUNT = "SELECT count(dto.projectId) FROM Project dto";

    public Project() {
    }
    
      public Project(Long projectId, String name, Date startDate, Date endDate) {
            this.projectId=projectId;
          this.name=name;
          this.startDate=startDate;
          this.endDate=endDate;
    }

    @Id
    @Column(name = "PROJECT_ID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long projectId;

    @Column(name = "NAME", unique = true, nullable = false)
    private String name;

    @Temporal(TemporalType.DATE)
    @Column(nullable = true)
    private Date startDate ;
            
    @Temporal(TemporalType.DATE)
    @Column(nullable = true)
    private Date endDate;

    public Long getProjectId() {
        return projectId;
    }

    public void setProjectId(Long projectId) {
        this.projectId = projectId;
    }

    public String getName() {
        return name;
    }

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

    public String toString() {
        return "Project : name=" + name;
    }

    public Date getStartDate() {
        return startDate;
    }

    public void setStartDate(Date startDate) {
        this.startDate = startDate;
    }

    public Date getEndDate() {
        return endDate;
    }

    public void setEndDate(Date endDate) {
        this.endDate = endDate;
    }

}

Création du Web Service REST

Quelques explications :

  • il s'agit d'un EJB stateteless qui est converti en WebService REST
  • pour chaque méthode, on indique le verbe HTTP associée (GET ou POST ou PUT ou DELETE) et le type de données en entrée/sortie. 
package com.mycompany.prj3.prjwebservicerestxmljson;

import com.mycompany.exception.ExceptionUtil;
import com.mycompany.prj3.beans.Project;
import java.net.URI;
import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
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.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.apache.log4j.Logger;

/**
 *
 * @author thierry
 */
@Stateless
@Path("project")
public class ProjectResource {

    private static final Logger logger = Logger.getLogger(ProjectResource.class);

    @Context
    private UriInfo uriInfo;

    @PersistenceContext(unitName = "puBdProject")
    private EntityManager entityManager;

    public ProjectResource() {
    }

    @GET
    @Produces(MediaType.APPLICATION_XML)
    public Response getProjectAsXml(@QueryParam("id") Long id) {
        logger.info("In getProjectAsXml for id : " + id);
        Project project = null;
        try {
            project = entityManager.find(Project.class, id);
        } catch (Exception e) {
            logger.error("Exception catchee " + ExceptionUtil.displayException(e));
            throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
        }
        logger.info("Project name : " + (project != null ? project.getName() : "Pas de projet"));
        if (project != null) {
            return Response.ok(project,
                    MediaType.APPLICATION_XML).build();
        } else {
            throw new WebApplicationException(Response.Status.NOT_FOUND);
        }
    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response getProjectAsJson(@QueryParam("id") Long id) {
        logger.info("In getProjectAsJson for id : " + id);
        Project project = null;
        try {
            project = entityManager.find(Project.class, id);
        } catch (Exception e) {
            logger.error("Exception catchee " + ExceptionUtil.displayException(e));
            throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
        }
        logger.info("Project name : " + (project != null ? project.getName() : "Pas de projet"));

        if (project != null) {
            return Response.ok(project,
                    MediaType.APPLICATION_JSON).build();
        } else {
            throw new WebApplicationException(Response.Status.NOT_FOUND);
        }
    }

    @GET
    @Produces("text/xml")
    @Path("/all/")
    public List<Project> getProjects() {
        logger.info("In getProjects");
        List<Project> list = null;
        try {
            list = entityManager.createNamedQuery(Project.PROJECT_FIND_ALL_PROJECTS).getResultList();
            logger.info("Nombre de projet : " + (list != null ? list.size() : "0"));
        } catch (Exception e) {
            logger.error("Exception catchee " + ExceptionUtil.displayException(e));
        }

        return list;
    }

    @POST
    @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public Response insertProject(Project project) {
        logger.info("In insertProject");
        try {
            entityManager.persist(project);
            logger.info("projectName=[" + project.getName() + "] id=" + project.getProjectId());
            logger.info("startDate=" + (project.getStartDate() != null ? project.getStartDate() : "Pas de date"));

            URI bookUri = uriInfo.getAbsolutePathBuilder().path(project.getProjectId().toString()).build();
            return Response.created(bookUri).build();
        } catch (Exception e) {
            logger.error("Exception catchee " + ExceptionUtil.displayException(e));
            throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
        }
    }

    @PUT
    @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public void updateProject(Project project) {
        logger.info("In updateProject");
        try {
            entityManager.merge(project);
            logger.info("projectName=[" + project.getName() + "] id=" + project.getProjectId());
        } catch (Exception e) {
            logger.error("Exception catchee " + ExceptionUtil.displayException(e));
        }
    }

    @DELETE
    @Consumes("text/xml")
    public void deleteProject(@QueryParam("id") Long id) {
        logger.info("In deleteProject for id " + id);
        try {
            Project project = entityManager.find(Project.class, id);
            logger.info("projectName=[" + project.getName() + "] id=" + project.getProjectId());
            entityManager.remove(project);
        } catch (Exception e) {
            logger.error("Exception catchee " + ExceptionUtil.displayException(e));
        }
    }
}

 

Configuration de Glassfish pour la prise en charge des URL représentant des web services REST

Il faut indiquer que certaines URL, celles correspondantes aux web services REST que nous voulons exposés, soient prise en compte par Jersey.

Pour cela deux méthodes :

  • soit créer la classe suivante qui indique que toute URL contenant "resources" devra être prise en charge par Jersey
package com.mycompany.prj3.prjwebservicerestxmljson;

/**
 *
 * @author thierry
 */
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("resources")
public class JaxRsConfig extends Application {
}
  • soit paramètre web.xml, mais nous nous limiterons à la méthode précédente.

 

Pour tester le Web Service

Pour tester le GET, entrer l'URL via le navigateur : 

Par contre, pour un POST/PUT/DELETE, nous utilisons curl:

  • Pour récuperer une ressource au format XML via le navigateur : http://localhost:8080/PrjWebServiceRestXmlJson/resources/project?id=1
  • Pour récupérer une ressource au format JSON via cURL: curl -XGET -H "Accept: application/json" http://localhost:8080/PrjWebServiceRestXmlJson/resources/project?id=1
  • Pour récupérer une ressource au format XML via cURL : curl -XGET -H "Accept: application/xml" http://localhost:8080/PrjWebServiceRestXmlJson/resources/project?id=1
  • Pour insérer une ressource au format XML via cURL : curl -XPOST -i -H "Content-type:application/xml" --data "<project><name>prj3</name><startDate>2014-02-25</startDate></project>" http://localhost:8080/PrjWebServiceRestXmlJson/resources/project
  • Pour insérer une ressource au format JSON via cURL: curl -XPOST -i -H "Content-type:application/json" --data "{\"name\":\"prj6\",\"projectId\":251,\"startDate\":\"2014-10-29T00:00:00\"}" http://localhost:8080/PrjWebServiceRestXmlJson/resources/project
  • Pour updater une ressource au format JSON via cURL: curl -XPUT -i -H "Content-type:application/json" --data "{\"name\":\"prj6-update\",\"projectId\":251,\"startDate\":\"2014-10-29T00:00:00\"}" http://localhost:8080/PrjWebServiceRestXmlJson/resources/project
  • Pour updater une ressource au format XML via cURL : curl -XPUT -H "Content-type:application/xml" --data "<project><name>prj3-1</name><projectId>101</projectId></project>" http://localhost:8080/PrjWebServiceRestXmlJson/resources/project
  • Pour supprimer une ressource via cURL : curl -XDELETE http://localhost:8080/PrjWebServiceRestXmlJson/resources/project?id=251

 

Code source

PrjWebServiceRestXmlJson.zip