JAX-RS is a Java™ API for RESTful web services. The JAX-RS API is a collection of interfaces and Java annotations that simplifies development of server-side REST applications. JAX-RS enables developers to specify RESTful web service behavior by annotating classes to respond to specific HTTP methods (such as GET or POST) and URI patterns. JAX-RS is an official feature of Java EE 6.
The most recent versions of IBM WebSphere Application Server provide support for JAX-RS. WebSphere Application Server V8.5 has support for JAX-RS built in; no extra installation is required. For WebSphere Application Server V8.0, JAX-RS features are provided in the Feature Pack for Web 2.0 and Mobile (Version 1.1.0), which an optional product extension installed separately.
Whichever of these configurations you use, WebSphere Application Server provides these features for JAX-RS:
- JAX-RS 1.1 server runtime based on Apache Wink 1.1
- Built-in POJO entity provider support provided by the Jackson JSON processor
- Built-in IBM JSON4J entity provider support.
This article describes the design and implemention of a sample application scenario to help you understand how you can apply JAX-RS to a WebSphere Application Server application. A partial implementation of the design as a set of Eclipse projects, including a web module, EJB module, and a Derby database is included with this article. After completing this article and working with the sample application, you should be able to create a JAX-RS application with response providers that enable you to tailor the responses from your application.
The example we will use in this discussion is a sample Student Registration Application (SRA). The application's components and their high-level interactions are shown in Figure 1.
Figure 1. Student Registration Application example
The student registration management functions provided in this sample SRA application enable you to:
- Register new students
- Edit registered student information
- List registered students
- Remove student registrations.
SRA consists of a web module that provides the RESTful services, an EJB module that provides the basic Create-Read-Update-Delete (CRUD) database logic, and the database where student records are stored.
The application configuration class shown in Figure 1 is used by the JAX-RS runtime to determine the classes in the application that are resource classes and provider classes. The
StudentRegistrationApplication
class in which resource and provider classes are registered is shown in Listing 1.Listing 1. StudentRegistrationApplication class
public class StudentRegistrationApplication extends Application { public Set<Class<?>> getClasses() { Set<Class<?>> classes = new HashSet<Class<?>>(); // Resources classes.add( com.dw.demo.rest.resources.StudentResource.class ); // Providers return classes; } } |
The
StudentRegistrationApplication
class is a subclass of the JAX-RS provided Application
class and provides a list of the REST resources and providers in SRA.
The
web.xml
file for the application is shown in Listing 2.Listing 2. Student Registration Application web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <display-name>demo.war</display-name> <servlet> <servlet-name>com.dw.demo.rest.StudentRegistrationApplication</servlet-name> </servlet> <servlet-mapping> <servlet-name>com.dw.demo.rest.StudentRegistrationApplication</servlet-name> <url-pattern>/rest/*</url-pattern> </servlet-mapping> </web-app> |
WebSphere Application Server provides a JAX-RS aware servlet container so the
StudentRegistrationApplication
class can be provided as the servlet name. The configuration in the web.xml
file maps the "/rest/*
" URL pattern to theStudentRegistrationApplication
class, and the WebSphere Application Server servlet container automatically connects the dots so that references to the "/rest/*
" URL pattern invoke the appropriate resource class.
The
StudentRegistrationApplication
class declares a single resource, the StudentResource
class. The StudentResource
class represents the entire collection of registered students. The initial implementation of the class is shown in Listing 3.Listing 3. StudentResource class
@Path( "/students" ) @Stateless public class StudentResource { @EJB IStudent studentService; @POST @Consumes( MediaType.APPLICATION_JSON ) public Response create( @Context UriInfo uriInfo, Student student ) { Student createdStudent = studentService.createStudent( student ); URI uri = uriInfo.getBaseUriBuilder().path( StudentResource.class ) .path( createdStudent.getId() ).build(); return Response.created( uri ).build(); } @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() { List<Student> students = studentService.getAll(); return Response.ok( students ).build(); } @GET @Path( "/{id}" ) @Produces( MediaType.APPLICATION_JSON ) public Response getById( @PathParam( "id" ) String id ) { Student student = studentService.findById( id ); if ( student != null ) { return Response.ok( student ).build(); } else { return Response.status( Status.NOT_FOUND ).build(); } } @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 requests to /demo/rest/students
will be handled by the Student Resource class.
The resource class defines five methods:
- create: create a new student record using HTTP POST.
- delete: delete a particular student record using HTTP DELETE.
- getAll: get all students using HTTP GET.
- getById: get a particular student using their student ID using HTTP GET
- update: update a student record using HTTP PUT.
A collection-wide delete method is specifically not implemented on the collection of students because you don't want to inadvertently delete all of the registered students.
The implementation of the
StudentResource
class is also annotated to either consume or produce JSON content depending on the method; for the purpose of this article, the supported media type is restricted to "application/json.
"
The
StudentResource
class uses an EJB to perform the required database operations; the @Stateless
annotation at the class level tells WebSphere Application Server to treat the StudentResource
class as an EJB so that the EJBs the class depends on – in this case, the StudentService
EJB — will be injected automatically.
The methods shown in Listing 3 either accept or return instances (or a list of instances) of the
Student
class. The Student
class is a POJO declared and implemented in the EJB module. The Student
class is annotated as a JPA entity and is shown in Listing 4.Listing 4. Student Entity class
@Entity @Table( name = "STUDENT" ) public class Student implements Serializable { private static final long serialVersionUID = 1L; private String id; private String firstName; private String lastName; private String email; private String studentNumber; private Calendar registeredOn; private Gender gender; // Getters and setters omitted for brevity } |
Since the
Student
class is a POJO with well-defined getters and setters, the WebSphere Application Server JAX-RS runtime will use the Jackson JSON processer to automatically serialize and de-serialize the Student
class, either a single instance of the Student class or a collection of instances of the Student
class. No extra coding is required. Even the Gender
enumeration used by the Student
class is handled correctly by the Jackson JSON processor.
If you test the application by issuing a
GET
against /demo/rest/students
, you will see that the application returns the JSON code shown in Listing 5.Listing 5. JSON code returned by application
[ { "id" : "0b031e9b-6933-4a10-9ffc-c7fd2c604331", "firstName" : "Aaron", "lastName" : "Aaronson", "gender" : "MALE", "email" : "aaaronson@domain.net", "studentNumber" : "823-934", "registeredOn" : 1356480000000 }, … { "id" : "864eb556-053a-42d7-a757-e24928fb19a3", "firstName" : "Pamela", "lastName" : "Peterson", "gender" : "FEMALE", "email" : "ppeterson@domain.net", "studentNumber" : "826-660", "registeredOn" : 1364256000000 } ] |
By default, the Jackson JSON Processor serializes date objects (
java.util.Date
, java.util.Calendar
, and so on) as numeric timestamps (milliseconds since January 1, 1970, 00:00:00 GMT). For this sample application, you want the registeredOn
value of Student entity serialized as an ISO8601 string.
In order to control serialization, you will create a JAX-RS provider class named
JsonStudentProvider
, as shown in Listing 6.Listing 6. JsonStudentProvider
@Provider @Produces( MediaType.APPLICATION_JSON ) public class JsonStudentProvider implements MessageBodyWriter<Object> { public long getSize( Object object, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType ) { return -1; } public boolean isWriteable( Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType ) { boolean isWritable = false; if ( List.class.isAssignableFrom( type ) && genericType instanceof ParameterizedType ) { ParameterizedType parameterizedType = (ParameterizedType) genericType; Type[] actualTypeArgs = ( parameterizedType.getActualTypeArguments() ); isWritable = ( actualTypeArgs.length == 1 && actualTypeArgs[0].equals( Student.class ) ); } else if ( type == Student.class ) { isWritable = true; } return isWritable; } public void writeTo( Object object, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream ) throws IOException { // Explicitly use the Jackson ObjectMapper to write dates in ISO8601 format ObjectMapper mapper = new ObjectMapper(); mapper.configure( SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false ); mapper.writeValue( entityStream, object ); } } |
The
JsonStudentProvider
class must be annotated with @Provider
and must implement these methods declared by the JAX-RSMessageBodyWriter
interface:getSize
: get the size of the serialized object, return -1 if the size is not known.isWritable
: if this method returns true, the JAX-RS framework will use this class to serialize the object.writeTo
: this method is invoked to serialize the supplied object to the supplied OutputStream instance.
The
isWritable
method tests if the supplied object is an instance of Student
or a list of Student instances. If it is, then theJsonStudentProvider
will be used to serialize the object.
The
writeTo
method creates a Jackson ObjectMapper
instance, configures it to not write dates as timestamps, but instead to write dates as strings, and finally uses the ObjectMapper
instance to serialize the supplied object to the supplied OutputStream
.
Once the
JsonStudentProvider
class is created, it must be registered in the Student Registration Application class, shown in Listing 7.Listing 7. Updated StudentRegistrationApplication class
public class StudentRegistrationApplication extends Application { public Set<Class<?>> getClasses() { Set<Class<?>> classes = new HashSet<Class<?>>(); // Resources classes.add( dw.demo.rest.resources.StudentResource.class ); // Providers classes.add( dw.demo.rest.providers.JsonStudentProvider.class ); return classes; } } |
You need to make one small change to the
getAll
method in the Student Resource class to ensure that type information is correctly passed to the JsonStudentProvider
when the user asks for a list of all students (Listing 8).Listing 8. Updated Student Resource Class "getAll" Method
@GET @Produces( MediaType.APPLICATION_JSON ) public Response getAll() { List<Student> students = studentService.get(); // This preserves type information for the provider GenericEntity<List<Student>> entity = new GenericEntity<List<Student>>( students ) {}; return Response.ok( entity ).build(); } |
The JAX-RS
GenericEntity
class enables type information for the generic List interface to be available to theJsonStudentProvider
at run time. Retesting the application by issuing a GET
against /demo/rest/students
, you will see that the application now returns the JSON code shown in Listing 9.Listing 9. JSON code returned by application
[ { "id" : "0b031e9b-6933-4a10-9ffc-c7fd2c604331", "firstName" : "Aaron", "lastName" : "Aaronson", "gender" : "MALE", "email" : "aaaronson@domain.net", "studentNumber" : "823-934", "registeredOn" : "2012-12-26T00:00:00.000+0000" }, … { "id" : "864eb556-053a-42d7-a757-e24928fb19a3", "firstName" : "Pamela", "lastName" : "Peterson", "gender" : "FEMALE", "email" : "ppeterson@domain.net", "studentNumber" : "826-660", "registeredOn" : "2013-03-26T00:00:00.000+0000" } ] |
The values in the
registeredOn
property are now presented in ISO8601 format.
If invalid JSON is supplied to your application, a server error (HTTP 500) will result. Invalid JSON might be:
- Badly formed JSON, such as missing property name/value delimiters, unbalanced braces, and so on.
- JSON containing properties with no corresponding property in the Student class.
For example, if the request body of a
POST
to the /demo/rest/students
URL is:{ "bad":"property" }
The response shown in Listing 10 is returned by the server.
Listing 10. Server response
HTTP/1.1 500 Internal Server Error X-Powered-By: Servlet/3.0 Content-Type: text/html;charset=ISO-8859-1 $WSEP: Content-Language: en-CA Content-Length: 377 Connection: Close Date: Tue, 02 Apr 2013 07:40:06 GMT Server: WebSphere Application Server/8.0 Error 500: javax.servlet.ServletException: org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Unrecognized field "bad" (Class com.dw.demo.entities.Student), not marked as ignorable at [Source: com.ibm.ws.webcontainer.srt.http.HttpInputStream@5370a0; line: 1, column: 10] (through reference chain: com.dw.demo.entities.Student ["bad"]) |
The exception is issued because the
Student
class has no property named "bad.
"
What you would like is for the server to return an HTTP 400 (Bad Request) response with some diagnostic text if the application is provided with invalid JSON. To do this, create a new provider class, as shown in Listing 11.
Listing 11. JsonProcessingExceptionMapper class
@Provider public class JsonProcessingExceptionMapper implements ExceptionMapper<JsonProcessingException> { @Override public Response toResponse( JsonProcessingException e ) { String message = e.getMessage(); return Response .status( Status.BAD_REQUEST ) .type( MediaType.TEXT_PLAIN ) .entity( "The supplied JSON was not well formed: " + message ) .build(); } } |
The
JsonProcessingExceptionMapper
class implements the JAX-RS ExceptionMapper
interface, which has one method:toResponse
. The toResponse
method takes a Jackson JsonProcessingException
, and creates an HTTP 400 response with a plain text payload.
Once the
JsonProcessingExceptionMapper
class is created, it must be registered in the StudentRegistrationApplication class, as shown in Listing 12.Listing 12. Updated StudentRegistrationApplication class
public class StudentRegistrationApplication extends Application { public Set<Class<?>> getClasses() { Set<Class<?>> classes = new HashSet<Class<?>>(); // Resources classes.add( dw.demo.rest.resources.StudentResource.class ); // Providers classes.add( dw.demo.rest.providers.JsonStudentProvider.class ); classes.add( dw.demo.rest.providers.JsonProcessingExceptionMapper.class ); return classes; } } |
Now, if the request body of a
POST
to the /demo/rest/students
URL is: { "bad":"property" }
the response in Listing 13 is returned by the server.Listing 13. Server response
HTTP/1.1 400 Bad Request X-Powered-By: Servlet/3.0 Content-Type: text/plain Content-Language: en-CA Transfer-Encoding: chunked Connection: Close Date: Tue, 02 Apr 2013 07:35:30 GMT Server: WebSphere Application Server/8.0 10f The supplied JSON was not well formed: Unrecognized field "bad" (Class com.dw.demo.entities.Student), not marked as ignorable at [Source: com.ibm.ws.webcontainer.srt.http.HttpInputStream@5370a0; line: 1, column: 10] (through reference chain: com.dw.demo.entities.Student["bad"]) 0 |
This article described how to create a RESTful service to manage student registrations. The service uses a custom serializer to ensure student records represented in JSON contain dates in the correct format, and a custom exception mapper to report invalid input JSON with an HTTP 400 error code instead of an HTTP 500 error code.
Part 2 will show how you can integrate your RESTful service with a client-side UI framework, Dojo.
Download
Description | Name | Size | Download method |
---|---|---|---|
Code sample | demo.zip | 41 KB | HTTP |
Learn
- Architectural Styles and the Design of Network-based Software Architectures (Roy Thomas Fielding, University of California at Irvine, 2000): Fielding's doctoral dissertation describing REST.
- JSR-000311 Java API for RESTful Web Services 1.1 Maintenance Release (Java Community Process, November, 2009)
- Create RESTful Web services with Java technology (developerWorks, February, 2010) by Dustin Amrhein and Nick Gallardo
- Jackson JSON Processor (FasterXML, LLC), tutorials, FAQ and more
- Christopher Hunt on Software Development
- WebSphere Application Server product information
- IBM developerWorks WebSphere
Get products and technologies
댓글 없음:
댓글 쓰기