Application Overview
This sample application demonstrates a simple order system which can get, delete, and edit ordering data using RESTful Web services. This RESTful Web services application works at XML message level. When a user create a new order or edit an existing one, the resource data is converted into XML schema for processing. This web service is exposed as a Servlet in the Geronimo application server. See RESTful Web services for more information on the concept.
Application contents
The restfulorder-javaee6 application is deployed as a WAR to the application server. The overview of the structural content of this WAR file is given as follows.
|-restfulorder-javaee6.war +-rest +- restfulorder +- Support.js +-WEB-INF +- web.xml +- classes +- org.apache.geronimo.samples.javaee6.restfulorder +- META-INF +-META-INF
The org.apache.geronimo.samples.javaee6.restfulorder
package consists of the following files.
+- org.apache.geronimo.samples.javaee6.restfulorder +-service +- RestfulordersResource.class +- RestfulorderResource.class +-config +- ApplicationConfig.class +-converter +- RestfulorderConverter.class +- RestfulordersConverter.class +-entities +-JPASessionBeans
where,
- The service folder contains the resource classes of the RESTful application.
- The config folder contains the servlet class that extends
javax.ws.rs.core.Application
to configure the application classes within a JAX-RS runtime. Thejavax.ws.rs.core.Application
class provides methods that can specify a set of root resources and providers accessible from a specified application path. This way users can specify several groups of resources in a single application. - The converter folder contains Java Architecture for XML Binding (JAXB) annotated classes to support the processing of XML in requests and responses.
Application implementation
RestfulordersResource is a resource class that uses JAX-RS annotations to implement the corresponding Web resource.
package org.apache.geronimo.samples.javaee6.restfulorder.service; import java.util.Collection; import javax.ws.rs.Path; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Produces; import javax.ws.rs.Consumes; import javax.ws.rs.PathParam; import javax.ws.rs.QueryParam; import javax.ws.rs.DefaultValue; import javax.ws.rs.core.Response; import javax.ws.rs.core.Context; import javax.ws.rs.core.UriInfo; import javax.persistence.EntityManager; import org.apache.geronimo.samples.javaee6.restfulorder.converter.RestfulordersConverter; import org.apache.geronimo.samples.javaee6.restfulorder.converter.RestfulorderConverter; import javax.persistence.PersistenceContext; import javax.ejb.Stateless; import org.apache.geronimo.samples.javaee6.restfulorder.entities.Restfulorder; @Path("/restfulorders/") @Stateless public class RestfulordersResource { @javax.ejb.EJB private RestfulorderResource restfulorderResource; @Context protected UriInfo uriInfo; @PersistenceContext(unitName = "RestfulProduct2PU") protected EntityManager em; /** Creates a new instance of RestfulordersResource */ public RestfulordersResource() { } /** * Get method for retrieving a collection of Restfulorder instance in XML format. * * @return an instance of RestfulordersConverter */ @GET @Produces({"application/xml", "application/json"}) public RestfulordersConverter get(@QueryParam("start") @DefaultValue("0") int start, @QueryParam("max") @DefaultValue("10") int max, @QueryParam("expandLevel") @DefaultValue("1") int expandLevel, @QueryParam("query") @DefaultValue("SELECT e FROM Restfulorder e") String query) { return new RestfulordersConverter(getEntities(start, max, query), uriInfo.getAbsolutePath(), expandLevel); } /** * Post method for creating an instance of Restfulorder using XML as the input format. * * @param data an RestfulorderConverter entity that is deserialized from an XML stream * @return an instance of RestfulorderConverter */ @POST @Consumes({"application/xml", "application/json"}) public Response post(RestfulorderConverter data) { System.out.println("before resolve,in post"); Restfulorder entity = data.resolveEntity(em); createEntity(data.resolveEntity(em)); return Response.created(uriInfo.getAbsolutePath().resolve(entity.getId() + "/")).build(); } /** * Returns a dynamic instance of RestfulorderResource used for entity navigation. * * @return an instance of RestfulorderResource */ @Path("{id}/") public RestfulorderResource getRestfulorderResource(@PathParam("id") Integer id) { restfulorderResource.setId(id); restfulorderResource.setEm(em); return restfulorderResource; } /** * Returns all the entities associated with this resource. * * @return a collection of Restfulorder instances */ protected Collection<Restfulorder> getEntities(int start, int max, String query) { return em.createQuery(query).setFirstResult(start).setMaxResults(max).getResultList(); } /** * Persist the given entity. * * @param entity the entity to persist */ protected void createEntity(Restfulorder entity) { em.persist(entity); } }
where,
@Path("/restfulorders/")
is the annotation that defines the relative URI to your resource. The base URI is provided by the combination of your hostname, port, application context root, and any URI pattern mappings in the application'sweb.xml
file. For example, the URI for the resource presented by this resource class can be http://localhost:8080/restfulorder-javaee6/resources/restfulorders/.
@Path("{id}/")
annotates a sub-resource locator which returns aRestfulorderResource
object to handle HTTP requests. Theid
parameter in this annotation defines the relative URI under/restfulorders/id
to the resource.@Context
is used in this resource class to inject an instance ofUriInfo
into the class field.UriInfo
instances can provide information on the components of a request URI.@GET
and@POST
are request method designators for annotating resource methods in a resource class. Other common request method designators include@PUT
,@DELETE
, and@HEAD
.@Consumes
and@Produces
annotations are used to declare the supported request and response media types. In this resource class, thepost
andget
methods both supportapplication/xml
andapplication/json
.
ApplicationConfig is a servlet that extends the javax.ws.rs.core.Application
class to configure the application classes within a JAX-RS runtime.
package org.apache.geronimo.samples.javaee6.restfulorder.config; @javax.ws.rs.ApplicationPath("resources") public class ApplicationConfig extends javax.ws.rs.core.Application { }
where,
@javax.ws.rs.ApplicationPath
is an annotation that helps to specify the resource path of the application. The value of this annotation is used as the servlet URL pattern. For example, the URI for the resource presented in this application can be http://localhost:8080/restfulorder-javaee6/resources/restfulorders/, where /resources is the servlet URL. You can also specify this URL patterns in your
web.xml
file if this annotation is absent.javax.ws.rs.core.Application
is a class that provides thegetClasses
andgetSingletons
methods that can specify a set of root resources and providers accessible from a specified application path respectively. The ApplicationConfig class uses the default implementations ofjavax.ws.rs.core.Application
subclassgetClasses
andgetSingletons
methods that return empty sets. In this case, when both methods return empty sets, all root resource classes and providers packaged in the application are included in the published JAX-RS application.
RestfulordersConverter is a Java Architecture for XML Binding (JAXB) annotated class that support the processing of XML in requests and responses.
package org.apache.geronimo.samples.javaee6.restfulorder.converter; import java.net.URI; import java.util.Collection; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.XmlAttribute; import java.util.ArrayList; import org.apache.geronimo.samples.javaee6.restfulorder.entities.Restfulorder; @XmlRootElement(name = "restfulorders") public class RestfulordersConverter { private Collection<Restfulorder> entities; private Collection<RestfulorderConverter> items; private URI uri; private int expandLevel; /** Creates a new instance of RestfulordersConverter */ public RestfulordersConverter() { } /** * Creates a new instance of RestfulordersConverter. * * @param entities associated entities * @param uri associated uri * @param expandLevel indicates the number of levels the entity graph should be expanded */ public RestfulordersConverter(Collection<Restfulorder> entities, URI uri, int expandLevel) { this.entities = entities; this.uri = uri; this.expandLevel = expandLevel; getRestfulorder(); } ......
where,
@XmlRootElement(name = "restfulorders")
is an annotation that maps the class to the root of the XML document. In this application, when the user creates a new order with a unique id, for example 01, the XML document containing the corresponding data is located at http://localhost:8080/restfulorder-javaee6/resources/restfulorders/01.
This application uses the Asynchronous JavaScript and XML (Ajax) technique to combine Java technologies, XML, and JavaScript for the web service applications to uses client-side scripting to exchange data with the server. The JavaScript for performing Ajax interactions is support.js.
var rjsSupport = { proxy : "", getHttpProxy: function() { return this.proxy; }, setHttpProxy: function(proxy_) { this.proxy = proxy_; }, isSetHttpProxy: function() { return this.getHttpProxy().length > 0; }, getHttpRequest: function() { var xmlHttpReq; try { // Firefox, Opera 8.0+, Safari, IE7.0+ xmlHttpReq=new XMLHttpRequest(); } catch (e) { // Internet Explorer 6.0+, 5.0+ try { xmlHttpReq=new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { try { xmlHttpReq=new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) { this.debug("Your browser does not support AJAX!"); } } } return xmlHttpReq; }, findUrl : function(url, method) { var url2 = url; if(this.isSetHttpProxy()) url2 = this.getHttpProxy()+"?method="+method+"&url="+url2; return url2; }, findMethod : function(method) { var method2 = method; if(method != "GET" && this.isSetHttpProxy()) method2 = "POST"; return method2; }, open : function(method2, url2, mimeType, paramLen, async) { //Change url and method if using http proxy var url = this.findUrl(url2, method2); var method = this.findMethod(method2); //add timestamp to make url unique in case of IE7 var timestamp = new Date().getTime(); if(url.indexOf("?") != -1) url = url+"×tamp="+timestamp; else url = url+"?timestamp="+timestamp; var xmlHttpReq = this.getHttpRequest(); if(xmlHttpReq == null) { this.debug('Error: Cannot create XMLHttpRequest'); return null; } try { netscape.security.PrivilegeManager.enablePrivilege ("UniversalBrowserRead"); } catch (e) { //this.debug("Permission UniversalBrowserRead denied."); } try { xmlHttpReq.open(method, url, async); } catch( e ) { this.debug('Error: XMLHttpRequest.open failed for: '+url+' Error name: '+e.name+' Error message: '+e.message); return null; } if (mimeType != null) { if(method == 'GET') { //this.debug("setting GET accept: "+mimeType); xmlHttpReq.setRequestHeader('Accept', mimeType); } else if(method == 'POST' || method == 'PUT'){ //this.debug("setting content-type: "+mimeType); //Send the proper header information along with the request xmlHttpReq.setRequestHeader("Content-Type", mimeType); xmlHttpReq.setRequestHeader("Content-Length", paramLen); xmlHttpReq.setRequestHeader("Connection", "close"); } } //For cache control on IE7 xmlHttpReq.setRequestHeader("Cache-Control", "no-cache"); xmlHttpReq.setRequestHeader("Pragma", "no-cache"); xmlHttpReq.setRequestHeader("Expires", "-1"); return xmlHttpReq; }, loadXml : function(xmlStr) { var doc2; // code for IE if (window.ActiveXObject) { doc2=new ActiveXObject("Microsoft.XMLDOM"); doc2.async="false"; doc2.loadXML(xmlStr); } // code for Mozilla, Firefox, Opera, etc. else { var parser=new DOMParser(); doc2=parser.parseFromString(xmlStr,getDefaultMime()); } return doc2; }, findIdFromUrl : function(u) { var li = u.lastIndexOf('/'); if(li != -1) { var u2 = u.substring(0, li); var li2 = u2.lastIndexOf('/'); u2 = u.substring(0, li2); return u.substring(li2+1, li); } return -1; }, get : function(url, mime) { var xmlHttpReq = this.open('GET', url, mime, 0, false); try { xmlHttpReq.send(null); if (xmlHttpReq.readyState == 4) { var rtext = xmlHttpReq.responseText; if(rtext == undefined || rtext == '' || rtext.indexOf('HTTP Status') != -1) { if(rtext != undefined) this.debug('Failed XHR(GET, '+url+'): Server returned --> ' + rtext); return '-1'; } return rtext; } } catch( e ) { this.debug('Caught Exception; name: [' + e.name + '] message: [' + e.message+']'); } return '-1'; }, post : function(url, mime, content) { var xmlHttpReq = this.open('POST', url, mime, content.length, false); try { xmlHttpReq.send(content); if (xmlHttpReq.readyState == 4) { var status = xmlHttpReq.status; if(status == 201) { return true; } else { this.debug('Failed XHR(POST, '+url+'): Server returned --> ' + status); } } } catch( e ) { this.debug('Caught Exception; name: [' + e.name + '] message: [' + e.message+']'); } return false; }, put : function(url, mime, content) { var xmlHttpReq = this.open('PUT', url, mime, content.length, false); try { xmlHttpReq.send(content); if (xmlHttpReq.readyState == 4) { var status = xmlHttpReq.status; if(status == 204) { return true; } else { this.debug('Failed XHR(PUT, '+url+'): Server returned --> ' + status); } } } catch( e ) { this.debug('Caught Exception; name: [' + e.name + '] message: [' + e.message+']'); } return false; }, delete_ : function(url) { var xmlHttpReq = this.open('DELETE', url, 'application/xml', 0, false); try { xmlHttpReq.send(null); if (xmlHttpReq.readyState == 4) { var status = xmlHttpReq.status; if(status == 204) { return true; } else { this.debug('Failed XHR(DELETE, '+url+'): Server returned --> ' + status); } } } catch( e ) { this.debug('Caught Exception; name: [' + e.name + '] message: [' + e.message+']'); } return false; }, debug : function(message) { var dbgComp = document.getElementById("dbgComp"); if(dbgComp == null) { dbgComp = document.createElement("div"); dbgComp.setAttribute("id", "dbgComp"); dbgComp.style.border = "#2574B7 1px solid"; dbgComp.style.font = "12pt/14pt sans-serif"; var br = document.createElement("div"); document.getElementsByTagName("body")[0].appendChild(br); br.innerHTML = '<br/><br/><br/>'; document.getElementsByTagName("body")[0].appendChild(dbgComp); if((typeof rjsConfig!="undefined") && rjsConfig.isDebug) { dbgComp.style.display = ""; } else { dbgComp.style.display = "none"; } var tab = 'width: 20px; border-right: #2574B7 1px solid; border-top: #2574B7 1px solid; border-left: #2574B7 1px solid; border-bottom: #2574B7 1px solid; color: #000000; text-align: center;'; var addActionStr = '<div style="'+tab+'"><a style="text-decoration: none" href="javascript:rjsSupport.closeDebug()"><span style="color: red">X</span></a></div>'; dbgComp.innerHTML = '<table><tr><td><span style="color: blue">Rest Debug Window</span></td><td>'+addActionStr + '</td></tr></table><br/>'; } var s = dbgComp.innerHTML; var now = new Date(); var dateStr = now.getHours()+':'+now.getMinutes()+':'+now.getSeconds(); dbgComp.innerHTML = s + '<span style="color: red">rest debug('+dateStr+'): </span>' + message + "<br/>"; }, closeDebug : function() { var dbgComp = document.getElementById("dbgComp"); if(dbgComp != null) { dbgComp.style.display = "none"; dbgComp.innerHTML = ''; } } }
where,
XMLHttpRequest
is a JavaScript object used to send HTTP or HTTPS requests directly to a web server and load the server response data directly back into the script. In this application, anXMLHttpRequest
instancexmlHttpReq
is created to help a client-side script perform HTTP requests. The HTTP method to use for the request, the destination URL, and the request content type are set on theXMLHttpRequest
object with theopen
function when initializing the request.open
is the function that opens a connection to the server resource at the specified URL (URL2
) for the specified method (method2
). This function initializes the HTTP and HTTPS requests of theXMLHttpRequest
object and must be invoked prior to the actual sending of a request.setRequestHeader
is a method of theXMLHttpRequest
object that is used to set HTTP headers to be send with the request.get
,post
,put
anddelete
are functions responsible for dispatching requests. Each function first obtains an instance ofXMLHttpRequest
and initialize it withopen
. Then the requests are sent to the web server and will arrive on the server just like any other HttpServletRequest.send
is the function used to send requests to the web server. It accepts a single parameter that can contain the content to be sent with the request. The requests perform like HttpServletRequests on the web server and after parsing the request parameters, the servlet will invoke the necessary application logic. For this application, theget
,post
,put
anddelete
methods from RestfulordersResource and RestfulorderResource classes are called according to the request type.
Testing of the Sample
Launch the application after deploying it on your server. Your browser shall display the showAll.jsp page. Add a new order with the link at the bottom of the page and the data applied for the order is contained at http://localhost:8080/restfulorder-javaee6/resources/restfulorders/id in XML format, where id is the unique id value you defined for your order. This order resource is also displayed on the showAll.jsp page after saving the data.