Contents


Construct a simple JAX-RS 2.0-compliant REST service with WebSphere Application Server

Comments

Java™ API for RESTful Web Services (JAX-RS) is a collection of interfaces and Java annotations that simplifies the development of server-side REST applications. Thanks to JAX-RS, you can specify RESTful web service behavior by annotating classes to respond to specific HTTP methods (such as GET or POST) and Uniform Resource Identifier (URI) patterns. The current release of JAX-RS is version 2.0, and it is a required API in Java Platform, Enterprise Edition (Java EE) 7.

This article demonstrates how you can create a simple JAX-RS 2.0-compliant REST service by using the capabilities that are built in to IBM® WebSphere® Application Server, such as object deserialization, custom serialization, and exception handling. This article also includes three Eclipse projects that are available for download from GitHub that you can follow along as you read the article.

Downloadable project files

Before reading this article, download the three project files from GitHub. The three projects are the EAR project, EJB project, and Web project. By downloading these projects, you can follow along in them as you read the article. These project files are exported from an Eclipse 4.5.2 (Mars) IDE with the traditional WebSphere Application Server V9.x Developer Tools for Mars (available from the Eclipse Marketplace).

The persistence.xml file in the EJB project is configured to use the built-in Derby JTA data source that is provided by WebSphere Application Server V9. If you want to configure and use a different JTA data source, configure the JTA data source in the WebSphere Application Server V9 administrative console, and edit the persistence.xml file to specify the new JTA data source.

Java EE 7 JAX-RS features in WebSphere Application Server

Starting in the WebSphere Liberty Profile v8.5.5.6 and traditional WebSphere Application Server V9, support is provided for JAX-RS 2.0. These WebSphere Application Server runtimes provide the following features:

  • JAX-RS 2.0 server run time based on Apache CXF 3.0
  • Built-in plain old Java object (POJO) entity provider support that is provided by the Jackson JSON processor
  • Built-in IBM JSON4J entity provider support

JAX-RS 2.0 introduces the following new features:

  • Client API
  • Asynchronous request processing
  • Hypermedia support (Hypermedia As The Engine Of State (HATEOS))
  • New annotations (improved support for Contexts and Dependency Injection (CDI))
  • Bean validation
  • Filters and handlers
  • Richer content negotiation

Designing and implementing a simple RESTful application

In this article, you learn how to design and implement the basis of a student information system, which is the student registration application (SRA). The SRA demonstrates some of the new features that are provided by JAX-RS 2.0, specifically HATEOAS support and improved injection and bean validation.

The following diagram illustrates the components of the application and their high-level interactions.

Figure 1. Student registration application components
Student registration application components
Student registration application components

The SRA provides the following functions to manage student registrations:

  • Registration of new students
  • Edit registered student information
  • List registered students
  • Remove student registrations

The SRA consists of the following components:

  • A web module that provides the RESTful services
  • An EJB module that provides the database create, retrieve, update, and delete logic
  • The database where student records are kept

The JAX-RS run time uses the StudentRegistrationApplication configuration class to determine the classes in the SRA that are resource classes and provider classes. The following listing shows the StudentRegistrationApplication class in which the resource and provider classes are registered.

Listing 1. StudentRegistrationApplication configuration class
@ApplicationPath("rest")
public class StudentRegistrationApplication extends Application {
}

WebSphere Application Server V9 provides a JAX-RS 2.0 aware web container so that the @ApplicationPath annotation on the StudentRegistrationApplication class will be used to map the /rest/* URL pattern to the StudentRegistrationApplication class. The WebSphere Application Server web container then automatically ensures that references to the /rest/* URL pattern start the appropriate resource class.

No other configuration is required, the WebSphere Application Server web container scans for resource (@Path-annotated) and provider (@Provider-annotated) classes during startup.

The SRA has a single resource class, the StudentResource class. The StudentResource class represents the entire collection of registered students. The following listing shows the initial implementation of the class.

Listing 2. Student resource class
@Path( "/students" )
@Stateless
public class StudentResource {

    private static final String LINK_SELF = "self";

    @EJB
    IStudent studentService;

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response create(@Context UriInfo uriInfo, BaseStudentDto student) {
        Student newStudent = createNewStudent(student);
        URI uri = uriInfo.getBaseUriBuilder()
            .path(StudentResource.class)
            .path(newStudent.getId())
            .build();
        return Response.created(uri).build();
    }

    @POST
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public Response createFromForm(@Context UriInfo uriInfo, @Valid @BeanParam BaseStudentDto student) {
        Student newStudent = createNewStudent(student);
        URI uri = uriInfo.getBaseUriBuilder()
            .path(StudentResource.class)
            .path(newStudent.getId())
            .build();
        return Response.created(uri).build();
	}

    private Student createNewStudent(BaseStudentDto student) {
        Student newStudent = new Student();
        newStudent.setEmail(student.getEmail());
        newStudent.setFirstName(student.getFirstName());
        newStudent.setLastName(student.getLastName());
        newStudent.setGender(student.getGender());
        newStudent = studentService.create(newStudent);
        return newStudent;
    }

    @DELETE
    @Path( "/{id}")
    public Response delete( @PathParam( "id" ) String id ) {
        studentService.delete( id );
        return Response.status( Status.NO_CONTENT ).build();
    }

    @GET
    @Produces( MediaType.APPLICATION_JSON )
    public Response getAll(@Context UriInfo uriInfo) {
        List<Student> students = studentService.get();
        List<HateoasStudentDto> wrappedStudents = new ArrayList<HateoasStudentDto>();
        for (Student student : students) {
            HateoasStudentDto wrappedStudent = new HateoasStudentDto(student);
            Link link = buildSelfLink(uriInfo, wrappedStudent);
            wrappedStudent.addLink(link);
            wrappedStudents.add(wrappedStudent);
        }
        return Response.ok(wrappedStudents).build();
    }

    @GET
    @Path( "/{id}" )
    @Produces( MediaType.APPLICATION_JSON )
    public Response getById(@PathParam("id") String id) {
        Student student = studentService.findById(id);
        if (student != null) {
            HateoasStudentDto wrappedStudent = new HateoasStudentDto(student);
            Link link = buildSelfLink(uriInfo, wrappedStudent);
            wrappedStudent.addLink(link);
            return Response.ok(wrappedStudent).build();
        } else {
            return Response.status(Status.NOT_FOUND).build();
        }
    }

    private Link buildSelfLink(UriInfo uriInfo, HateoasStudentDto student) {
        URI uri = uriInfo.getBaseUriBuilder().path(StudentResource.class).path(student.getId()).build();
        Link link = Link.fromUri(uri).rel(LINK_SELF).type(MediaType.APPLICATION_JSON).build();
        return link;
    }

    @PUT
    @Consumes( MediaType.APPLICATION_JSON )
    public Response update( @Context UriInfo uriInfo, Student student ) {
        Student updatedStudent = studentService.update( student );
        return Response.ok( updatedStudent ).build();
    }
}

The @Path( "/students" ) annotation means that student resource class will handle requests to context_root/rest/students.

The resource class defines the following methods:

  • create. You create a new student record by using an HTTP POST of a new student's information in JSON.
  • createFromForm. You create a new student record by using an HTTP POST of a new student's information in a form.
  • delete. You delete a particular student record by using HTTP DELETE.
  • getAll. You get all students by using HTTP GET.
  • getById. You get a particular student by their student ID by using HTTP GET.
  • update. You update a student record by using HTTP PUT.

Do not implement a collection-wide delete method on the collection of students because you might inadvertently delete all of the registered students.

The methods of the StudentResource class are annotated to consume or produce JSON content, which is enforced by setting the media type to application/json. The exception is the createFromForm method, which accepts application/x-www-form-urlencoded content.

The StudentResource class uses an EJB to perform the required database operations. The @Stateless annotation at the class level signals for WebSphere Application Server to treat the StudentResource class as an EJB. This way, the EJBs that the class depends on, which is the StudentService EJB, are injected automatically by WebSphere Application Server.

Supporting Java objects in the REST service

The methods that are shown in the previous listing either accept or return instances (or a list of instances) of the BaseStudentDto or HateoasStudentDto classes. The BaseStudentDto class is used as input for creating new student records.

Listing 3. BaseStudentDto entity class
public class BaseStudentDto {
    @NotNull
    @FormParam("firstName")
    String firstName;

    @NotNull
    @FormParam("lastName")
    String lastName;

    @NotNull
    @FormParam("email")
    String email;

    @NotNull
    @FormParam("gender")
    Gender gender;

    // Getters and setters omitted for brevity
}

The HateoasStudentDto entity class is used to output student records.

Listing 4. HateoasStudentDto entity class
@JsonPropertyOrder({ "id", "firstName", "lastName", "gender", "email", "registeredOn", "studentNumber", "_links" })
@JsonTypeName("student")
public class HateoasStudentDto extends BaseStudentDto {

    private String id;
    private Calendar registeredOn;
    private String studentNumber;
    private List<Link> links;

    // Some getters and setters omitted for brevity

    @JsonProperty("_links")
    @JsonSerialize(using = LinksSerializer.class)
    public List<Link> getLinks() {
        return links;
    }
}

HATEOAS support in the REST service

The StudentResource#getById and StudentResource#getAll methods convert the contents of the Student entity beans that are returned from the StudentService EJB to HateoasStudentDto objects before they return to the REST client. A HateoasStudentDto object contains a collection of JAX-RS 2.0 Link objects in addition to the properties that are provided by a Student entity bean. The JAX-RS 2.0 Link object provides a means for an application to programmatically define links to REST resources so that the application can more readily implement HATEOAS support.

The HateoasStudentDto class is a POJO with well-defined getters. Therefore, the WebSphere Application Server JAX-RS 2.0 run time uses the Jackson JSON processor to automatically serialize the HateoasStudentDto class, either a single instance of the HateoasStudentDto class or a collection of instances of the HateoasStudentDto class. Even the Gender enumeration that is used by the Student entity bean is handled correctly by the Jackson JSON processor.

The @JsonProperty and @JsonSerialize annotations on the HateoasStudentDto#getLinks method tell the Jackson JSON processor how to serialize the collection of Link objects. Serialization of Link objects is done by using a custom serialization class, called LinksSerializer.

When you test the application by entering a GET against /demo/rest/students, the application returns the following JSON.

Listing 5. JSON returned when testing the application
[ {
    "id": "0b031e9b-6933-4a10-9ffc-c7fd2c604331",
    "firstName": "Aaron",
    "lastName": "Aaronson",
    "gender": "MALE",
    "email": "aaaronson@domain.net",
    "registeredOn": 1359417600000,
    "studentNumber": "823-934",
    "_links": [
      {
        "rel": "self",
        "href": "http://localhost:9080/demo/rest/students/0b031e9b-6933-4a10-9ffc-c7fd2c604331",
        "type": "application/json"
      }
    ]
},  …
{
    "id": "864eb556-053a-42d7-a757-e24928fb19a3",
    "firstName": "Pamela",
    "lastName": "Peterson",
    "gender": "FEMALE",
    "email": "ppeterson@domain.net",
    "registeredOn": 1358467200000,
    "studentNumber": "826-660",
    "_links": [
      {
        "rel": "self",
        "href": "http://localhost:9080/demo/rest/students/864eb556-053a-42d7-a757-e24928fb19a3",
        "type": "application/json"
      }
    ]
} ]

The _links collection that is included with each JSON student record provides a single link object so that the REST client can determine the URI to use to access a particular student record.

Input validation and parameter aggregation

The StudentResource#createFromForm method allows a client to directly submit a form to create a new student record rather than submitting a JSON Blob that contains new student information. The createFromForm method has two important annotations on the BaseStudentDto parameter: @Valid and @BeanParam.

The @Valid parameter part of the Java Validation API) directs the JAX-RS 2.0-aware web container to perform bean validation on the supplied BaseStudentDto parameter.

The JAX-RS 2.0 @BeanParam annotation directs the container to create an instance of the BaseStudentDto class and inject the new object with the values that are supplied by the REST client's form submission. The new @BeanParam annotation means that developers can aggregate submitted values to a POJO rather than having to list all of the values in their input resource methods.

The BaseStudentDto class fields were previously annotated with JAX-RS @FormParam annotations and @NotNull validation annotations (see Listing 3). Consider when a REST client submits the following POST to the /demo/rest/students resource.

Listing 6. POST request to the /demo/rest/students URL
POST /demo/rest/students HTTP/1.1
Host: localhost:9080
Content-Type: application/x-www-form-urlencoded
Cache-Control: no-cache

firstName=Norm&lastName=Smith&email=nsmith%40nowhere.com&gender=MALE

In this case, the container uses the URL-encoded form values to create and inject a BaseStudentDto instance, perform validation on the new object (no fields can be null). Then, start the createFromForm method with the new BaseStudentDto object if validation passes. If validation doesn't pass, meaning that the REST client did not supply one or more values, a ConstraintViolationException is thrown and a Server 500 error results.

Providing enhanced serialization

By default, the Jackson JSON processor serializes java.util.Calendar objects as numeric time stamps (milliseconds since January 1, 1970, 00:00:00 GMT). For this application, we want the registeredOn value of a student record to be serialized as an ISO8601-compliant date string (that is, YYYY-MM-DD).

To control serialization, the SRA contains a Jackson serialization class named CalendarSerializer as shown in the following listing.

Listing 7. CalendarSerializer class
public class CalendarSerializer extends SerializerBase<Calendar> {
    protected CalendarSerializer() {
        super(Calendar.class, true);
    }

    @Override
    public JsonNode getSchema(SerializerProvider arg0, Type arg1) throws JsonMappingException {
        return null;
    }

    @Override
public void serialize(Calendar cal, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
    jgen.writeString(new SimpleDateFormat("yyyy-MM-dd").format(cal.getTime()));
    }
}

After the CalendarSerializer class is created, annotate HatesoasStudentDto#getRegisterdOn method with @JsonSerialize(using=CalendarSerializer.class). This way, when a HateoasStudentDto object is serialized, the registeredOn date is output as a string in the YYYY-MM-DD format as shown in the following listing.

Listing 8. registeredOn date output as a string
[ {
    "id": "0b031e9b-6933-4a10-9ffc-c7fd2c604331",
    "firstName": "Aaron",
    "lastName": "Aaronson",
    "gender": "MALE",
    "email": "aaaronson@domain.net",
    "registeredOn": "2013-01-28",
    "studentNumber": "823-934",
    "_links": [
      {
        "rel": "self",
        "href": "http://localhost:9080/demo/rest/students/0b031e9b-6933-4a10-9ffc-c7fd2c604331",
        "type": "application/json"
      }
    ]
},  …
{
    "id": "864eb556-053a-42d7-a757-e24928fb19a3",
    "firstName": "Pamela",
    "lastName": "Peterson",
    "gender": "FEMALE",
    "email": "ppeterson@domain.net",
    "registeredOn": "2013-01-17",
    "studentNumber": "826-660",
    "_links": [
      {
        "rel": "self",
        "href": "http://localhost:9080/demo/rest/students/864eb556-053a-42d7-a757-e24928fb19a3",
        "type": "application/json"
      }
    ]
} ]

Error handling

When an invalid JSON Blob is supplied or a POST form has one or missing values, a server error can results. The following sections take a look at what happens when these errors occur and how to handle them.

Invalid JSON

If an invalid JSON Blob is supplied to the application, a server error (HTTP 500) results. An invalid JSON might result for the following reasons:

  • The JSON is badly formed. For example, it has a missing property name or value delimiters, or unbalanced braces.
  • The JSON contains properties that have no corresponding properties in the BaseStudentDto class.

For example, if the request body of a POST method to the /demo/rest/students URL is:
{ "bad":"property" }

In this case, the following response is returned by the server.

Listing 9. Server response for bad property
HTTP/1.1 500 Internal Server Error
X-Powered-By: Servlet/3.1
Content-Length: 0
Date: Mon, 26 Sep 2016 18:10:31 GMT
Content-Language: en-US
Connection: Close

The SystemOut.log file in WebSphere Application Server shows that the HTTP 500 is issued because the Student class does not have a property named bad.

We want the server to return an HTTP 400 (Bad Request) response with a diagnostic text if the application is provided with an invalid JSON. In this case, you create a new provider class as shown in the following listing.

Listing 10. JsonMappingExceptionMapper class
@Provider
public class JsonMappingExceptionMapper implements
    ExceptionMapper<JsonMappingException> {

    @Override
    public Response toResponse( JsonMappingException e ) {
	// Get the mapping path at which the object mapper detected the error
	List<Reference> references = e.getPath();
	// Get the LAST reference from the list
	String message = references.get(references.size() - 1).getFieldName();
	return Response.status(Status.BAD_REQUEST).type(MediaType.TEXT_PLAIN)
	    .entity("The supplied JSON contained an invalid property: " + message).build();
    }
}

The JsonMappingExceptionMapper class implements the JAX-RS ExceptionMapper interface, which has one method, toResponse. The toResponse method takes a JacksonJsonMappingException and creates an HTTP 400 response with a plain text payload.

Now, consider that a POST to the /demo/rest/students URL has the following request body:
{ "bad":"property" }

The following text is returned in the HTTP 400 response from the server:
The supplied JSON contained an invalid property: bad

Validation errors

If a form with one or more missing values is POSTed to the /demo/rest/students URL, a server error (HTTP 500) results.

For example, if the form POSTed to the /demo/rest/students URL is missing a firstName parameter the following response is returned by the server by default.

Listing 11. Server response for missing firstName parameter
HTTP/1.1 500 Internal Server Error
X-Powered-By: Servlet/3.1
Content-Type: text/html;charset=ISO-8859-1
$WSEP:
Content-Language: en-US
Content-Length: 405
Connection: Close
Date: Mon, 12 Dec 2016 02:43:22 GMT

Error 500: java.lang.RuntimeException: org.apache.cxf.interceptor.Fault: null while invoking public javax.ws.rs.core.Response com.dw.demo.rest.resources.StudentResource.createFromForm&#40;javax.ws.rs.core.UriInfo,com.dw.demo.rest.dto.BaseStudentDto&#41; with params [org.apache.cxf.jaxrs.impl.UriInfoImpl@8b20c7ca, BaseStudentDto [firstName=null, lastName=Smith, email=nsmith@nowhere.com, gender=MALE]].

We want the server to return an HTTP 400 (Bad Request) response with some diagnostic text if the application is provided with an invalid form. To do this, we create a new provider class as shown in the following listing.

Listing 12. ValidationExceptionMapper class
@Provider
public class ValidationExceptionMapper implements ExceptionMapper<ValidationException> {

    @Override
    public Response toResponse(ValidationException ex) {
        if (ex instanceof ConstraintViolationException) {
            return handleConstraintViolationException((ConstraintViolationException) ex);
        }

        return Response.status(Status.BAD_REQUEST).type(MediaType.TEXT_PLAIN).entity("bad: " + unwrapException(ex)).build();
    }

    private Response handleConstraintViolationException(ConstraintViolationException ex) {
        StringBuilder sb = new StringBuilder();
        for (ConstraintViolation<?> v : ex.getConstraintViolations()) {
            String lastName = "";
            for (Node n : v.getPropertyPath()) {
                lastName = n.getName();
            }
            sb.append("Error: '" + lastName + "' " + v.getMessage() + "\n");
        }
	return Response.status(Status.BAD_REQUEST)
            .type(MediaType.TEXT_PLAIN).entity(sb.toString()).build();
    }

    protected String unwrapException(Throwable t) {
        StringBuffer sb = new StringBuffer();
        unwrapException(sb, t);
        return sb.toString();
    }

    private void unwrapException(StringBuffer sb, Throwable t) {
        if (t == null) {
            return;
        }
        sb.append(t.getMessage());
        if (t.getCause() != null && t != t.getCause()) {
            sb.append('[');
            unwrapException(sb, t.getCause());
            sb.append(']');
        }
    }
}

The ValidationExceptionMapping class implements the JAX-RS ExceptionMapper interface and implements the toResponse method to create an HTTP 400 response with a plain text payload that identifies the form fields that failed validation.

Now, consider that a POST to the /demo/rest/students URL has the following request.

Listing 13. POST request to the /demo/rest/students URL
POST /demo/rest/students HTTP/1.1
Host: localhost:9080
Content-Type: application/x-www-form-urlencoded
Cache-Control: no-cache
Postman-Token: 6237ff3d-0eab-4e44-3b4f-ea49b3985547

lastName=Smith&email=nsmith%40nowhere.com&gender=MALE

The following text is returned in the HTTP 400 response from the server:
Error: 'firstName' may not be null

Conclusion

This tutorial explained how to create a RESTful service by using the JAX-RS 2.0 features of Java EE 7 versions of WebSphere Application Server to manage student registrations. The service uses custom serializers to ensure that the student records that are represented in JSON contain dates in the correct format and that each student record contains a HATEOAS-compliant link for access to and manipulation of the record. Custom exception mappers are used to report invalid inputs with an HTTP 400 error code instead of an HTTP 500 error code.

Acknowledgments

The author thanks Tom Alcott and Erin Schnabel for their time and assistance in reviewing this article.


Downloadable resources


Related topics


Comments

Sign in or register to add and subscribe to comments.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Middleware
ArticleID=1041257
ArticleTitle=Construct a simple JAX-RS 2.0-compliant REST service with WebSphere Application Server
publish-date=12202016