Monday, November 18, 2019

REST Web Services with Java Enterprise Edition done with a Maven project

In this post I'm going to do a server-side REST project to deploy on an application server like WildFly. A standard Enterprise JavaBean does all the heavy lifting in this project. We have three classes to take care of data and make the case more interesting. First the Material class:


package data;

import java.io.Serializable;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;


@XmlAccessorType(XmlAccessType.FIELD)
public class Material implements Serializable {
 private static final long serialVersionUID = 1L;

 @XmlAttribute
 int id;
 
 private String title;
 private String location;
 
 public Material() {}
 
 public Material(int id, String name, String location2) {
  this.id = id;
  this.title = name;
  this.location = location2;
 }
 
 public int getId() {
  return id;
 }

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

 public String getTitle() {
  return title;
 }
 public void setTitle(String title) {
  this.title = title;
 }
 public String getLocation() {
  return location;
 }
 public void setLocation(String location) {
  this.location = location;
 }

 public void update(String name, String location2) {
  this.title = name;
  this.location = location2;
 }

}

Each course has materials. I use JAXB annotations, because I want to serialize data as XML (as well as JSON, but I'm using the defaults for JSON):

package data;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;


@XmlAccessorType(XmlAccessType.FIELD)
public class Course implements Serializable {
 private static final long serialVersionUID = 1L;
 @XmlAttribute
 private int id;
 private String name;
 @XmlElementWrapper(name="materials")
 @XmlElement(name="material")
 private List<Material> materials;

 public Course() {
  super();
 }

 public Course(int id, String name2) {
  this.name = name2;
  this.id = id;
 }
 
 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 void addMaterial(Material material) {
  if (this.materials == null)
   this.materials = new ArrayList<>();
  this.materials.add(material);
 }
 
 public void deleteMaterial(Material material) {
  if (this.materials == null)
   return; //XXX: silent error?
  this.materials.remove(material);
 }

 public List<Material> getMaterials() {
  return this.materials;
 }
   
}

And now a list of courses:

package data;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlRootElement;


@XmlRootElement(name="ListCourses")
public class ListCourses implements Serializable {
 private static final long serialVersionUID = 1L;
 private List<Course> courses;


 public ListCourses() {
  this.courses = new ArrayList<>();
 }

 public List<Course> getCourses() {
  return courses;
 }

 public void setCourses(List<Course> courses) {
  this.courses = courses;
 }

 public void addCourse(Course c) {
  this.courses.add(c);
 }

 public Course get(int id) {
  return this.courses.get(id);
 }

}

I have an Enterprise JavaBean that initializes some data to give back to the REST service. In practice, this EJB would typically go to a database to get the data in need:

package ejb;

import javax.ejb.LocalBean;
import javax.ejb.Stateless;

import data.Course;
import data.ListCourses;
import data.Material;

/**
 * Session Bean implementation class MyBean
 */
@Stateless
@LocalBean
public class MyBean {
 private ListCourses lc;
 
    /**
     * Default constructor. 
     */
    public MyBean() {
  Course courses[] = {new Course(1, "IS"), new Course(2, "ES"), new Course(3, "PPP")};
  Material materials[] = {new Material(1, "book", "/usr"), new Material(2, "slides", "/usr/slides"), new Material(3, "exercises", "/usr/exercises")};
  
  courses[0].addMaterial(materials[0]);
  courses[0].addMaterial(materials[1]);
  courses[1].addMaterial(materials[0]);
  courses[1].addMaterial(materials[2]);
  courses[2].addMaterial(materials[1]);
  courses[2].addMaterial(materials[2]);
  
  lc = new ListCourses();
  for (Course c : courses)
   lc.addCourse(c);
    }
    
    
    public ListCourses getListCourses() {
     return this.lc;
    }
    
    public Course getCourse(int id) {
     return this.lc.get(id);
    }

}

And now the two main classes of the example. First the "Root resource class". Please note the method name (@Get), the path (@Path), the MIME media type of the response (@Produces). I don't have any example with @Consumes, which specifies the MIME media type of the input:

package rest;

import java.util.List;

import javax.enterprise.context.RequestScoped;
//import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

import data.Course;
import data.ListCourses;
import ejb.MyBean;

@Path("/project3webservices")
@RequestScoped
public class WebServicesServer {
 
 @Inject
 MyBean db;
 
 public WebServicesServer() throws NamingException {
  System.out.println("WebServicesServer created. db = " + this.db);
 }
 
 // http://localhost:8080/play-REST-server/webapi/project3webservices/gettext
 @GET
 @Path("gettext")
 @Produces({MediaType.TEXT_PLAIN})
 public String getText() {
  return "Hello World!";
 }
  
 // http://localhost:8080/play-REST-server/webapi/project3webservices/getmaterials
 @GET
 @Path("getmaterials")
 @Produces({MediaType.APPLICATION_XML})
 public ListCourses getAllMaterials() {
  return db.getListCourses();
 }
 
 
 // http://localhost:8080/play-REST-server/webapi/project3webservices/getstudents?id=1
 @GET
 @Path("getstudents")
 @Produces({MediaType.APPLICATION_JSON})
 public Course getAllStudents(@QueryParam("courseid") int id) {
  return db.getCourse(id);
 }
}

And then a class to define the root application path:

package rest;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("webapi")
public class HelloWorldApplication extends Application {
}

Finally, the pom.xml. You may specify the goals (mvn) clean install wildfly:deploy for WildFly. Assuming WildFly is running and configured with the default ports, this project will be immediately deployed.

<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>is</groupId>
<artifactId>project3-REST-server</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>project3 REST web services</name>
<url>http://maven.apache.org</url>


<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.sun.xml.ws/jaxws-rt -->
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>2.3.2</version>
<type>pom</type>
</dependency>
<!-- https://mvnrepository.com/artifact/javax/javaee-api -->
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>8.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>play-REST-server</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>12</source>
<target>12</target>
</configuration>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.3</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>

<plugin>
<groupId>org.wildfly.plugins</groupId>
<artifactId>wildfly-maven-plugin</artifactId>
<version>2.0.1.Final</version>
<configuration>
<hostname>localhost</hostname>
<port>9990</port>
</configuration>
</plugin>
</plugins>
</build>

</project>

Then, you may go to a browser to invoke the three URLs I show in the root resource class to get the following results. One should notice the awesome fact that we are getting XML and JSON as requested using nothing more than a couple of annotations. Java EE makes that for us:




No comments:

Post a Comment