Visual COBOL 2.2 - REST Web Services Tutorial

0 Likes
over 7 years ago

Document Support Material

This document depends upon many different technologies. While it tries to cover the material as completely as possible it is not viable to present an exhaustive view of the topic. The material listed below references any extra material that is related to the topic. It should not be necessary to complete the tutorial but will make it easier to understand the material.

  • Java - Java is used to implement our web service and call out to our COBOL code that implements the core functionality of the service. At least a rudimentary understanding of Java is required for this tutorial.
  • Java Servlets - Java Servlets are the core technology in the Java Enterprise Edition stack for handling web services.
  • REST - REST is a particular design philosophy for web services. RESTful services present resources rather than actions. They use the standard HTTP methods to implement their API. They use standard HTTP error codes to report errors. They are designed to be scalable and transparent.
  • JAX-RS - JAX-RS is a standard Java API for implementing RESTful services in the Java environment. It builds upon the standard servlet technology.
  • JavaScript - JavaScript is the central client side programming language used in web application development. We will use JavaScript in our tutorial to implement our web client.
  • AJAX - AJAX means asynchronous JavaScript and XML. It has evolved into a common design pattern in web application where an asynchronous call is made to a web service that returns some kind of resource (which isn't necessarily encoded in XML) and then processes the data when the call is completed. Our tutorial will use an AJAX based model.
  • JQuery - JQuery is a library that allows you to search the DOM tree of an HTML document and pick out particular nodes. Then it provides standard ways to manipulate these nodes. It is commonly used to create HTML rich web applications.

In recent years the web service market has seen the introduction of RESTful services. These services are designed around the concept that your service can be designed such that you use the traditional HTTP methods to create, read, update and delete records within your service back end.

A further advancement has been the use of JSON as the payload of these services. JSON is a simple notation for objects that can be natively understood by the Javascript engines in modern web browsers. It also has wide spread tool support in other programming language environments.

A third development in this space has been the creation of the JQuery library. This is used to make queries in a web pages domain object model (DOM) to allow easy access to web page elements from Javascript. This allows us to easily find and modify any aspect of a web page in order to manipulate a UI. JQuery also includes tools to perform asynchronous calls to an HTTP service.

The combination of these three tools gives us a powerful framework for the creation and consumption of web services. This tutorial details the creation of a REST based web service using a procedural COBOL program. It will wrap the COBOL program in a Java Enterprise Edition web application deployed on Tomcat. The web application will use the JAXB standard to handle the conversion of data types to JSON and back. It will use the Jersey library that implements the JAX-RS standard to handle the mapping of URLs onto service resources. Then it will cover the consumption of that service from an ordinary web page that uses HTML and Javascript with the JQuery library.

What is REST and why should you use it?

The general principle of REST as mentioned is that it presents your service as a set of resources as opposed to operations. These resources might be records in a file or database. It might be something more abstract like a database view. Whatever it is the fundamental principle remains that REST operations are about manipulating resources via HTTP GET, PUT, DELETE and POST methods.

URLs in this scheme represent different resources that can be manipulated. For instance a customer record with ID 1576 might be at the URL "customers/1576". To read this record you'd make a GET request to that URL. To update it you'd do a PUT. To create a new customer record you'd perform a POST to "customers". This would return the customer ID of the new record in the response.

Key to all REST services is that GET, PUT and DELETE should be idempotent. This will allow caching to work correctly and thus allow your service to scale more readily than a SOAP style web service.

There are more details to the principles, design and implementation of REST services but they are beyond the scope of this document. For more information see the RESTful Best Practices document.

First Time Setup

If this is your first time doing this tutorial you will need to set up your environment to run JVM COBOL JAX-RS applications. You will need to install and set up Micro Focus Visual COBOL for Eclipse 2.2. You need to install your Tomcat server and setup the Micro Focus JVM COBOL runtime to be accessible in that environment. Links to those steps are below:

REST Service Using JVM COBOL

Let's look at creating a REST service from a traditional COBOL program. As mentioned previously this will involve the Jersey implementation of the JAX-RS standard. JAX-RS is a standard that is used to link URLs to methods that produce the appropriate response to a request. The payload for both request and response will be encoded in JSON specified via the JAXB standard. JAXB is normally a Java library for marking up classes for serialization to XML. JAX-RS libraries have serializers that can convert application data between Java classes and JSON format.

First of all create a fresh Eclipse workspace for our project (the tutorial will refer to this location as WORKSPACE or just as 'the workspace'). You should use Micro Focus Visual COBOL for Eclipse to enable you to create JVM COBOL projects. In this workspace we will be adding a JVM COBOL project containing our traditional COBOL program. We will also be adding an Eclipse Web Tools Platform project to hold our service and manage the passing of calls to our traditional COBOL. We will detail the creation of these projects in the sections to follow.

There is an attached source code archive for this tutorial. You can acquire it here. You should download this and store it on your machine. The location it is stored at will be referred to as SRC_DIR in this tutorial.

CobolBook Project

First we will create a project to contain our procedural COBOL program and make it available for execution from a Java program. The steps to do this are as follows:

  1. Click File->New->Other.
  2. Expand the Micro Focus COBOL node and select COBOL JVM Project. Click Next.
  3. Set the Project name field to "CobolBook".
  4. Ensure Use default location is checked.
  5. Ensure that the JRE is set up to use a Java 6 VM.
  6. Click Finish.

This will create a blank JVM COBOL project which can contain our procedural COBOL program and be imported into our REST service later. If it asks you to switch to the COBOL perspective then do so. To change perspectives later you can click on the appropriate option in the top right of the Eclipse main window (see the image below). Unless otherwise specified, all actions in the following COBOL sections must be executed in the COBOL perspective.

The Eclipse Perspective Bar

One aspect of Java it is necessary to adapt our program to is the Java packaging system. By default COBOL programs will be placed in the default root Java package. JVM servlet containers do not always allow root package classes to be loaded so we will need to set up a package for our program. For reference the Java package system is equivalent to the JVM COBOL namespace system. These two terms are interchangeable.

To set up the package right click on src in the CobolBook project of the COBOL Explorer in the top left of Eclipse. Now select New->COBOL JVM Package. In the dialog insert the text "com.microfocus.book" into the Name text field. Click Finish to create the package. This will create a directory structure in our workspace in which we can store our COBOL program. If you have the source code attached to this project you can copy SRC_DIR/CobolBook/src/com/microfocus/book/book.cbl to the equivalent directory in WORKSPACE. You should also copy SRC_DIR/src/book-rec.cpy to the equivalent location in the workspace.

If you do not have the source archive you can create these files as follows. Right click on src in the CobolBook project in the COBOL Explorer tab and select New->COBOL Copybook. Ensure the Containing project text box contains "CobolBook/src". If it does not, alter it so it does. Then in the New file name text box enter "book-rec.cpy". Click finish to create the copybook. Now replace the entire content of the opened file with the text in book-rec.cpy.

Similarly to create book.cbl right click on src/com/microfocus/book in the CobolBook project. Select New->COBOL Program. Ensure the Containing project text box contains "CobolBook/src" again. The Package text box should contain "com.microfocus.book". Enter "book.cbl" in the Name text box. Click Finish to create the COBOL program. Now replace the entire content of the opened program with the text in book.cbl.

Putting the program in the right directory structure is only half the requirement to put it in the right package. You need to alter the program to specify the correct namespace as a qualified program name. Alternatively you can set a compiler directive either globally to the project or in the program using the $set syntax. For this tutorial we will set a global compiler directive. We have only one program, so there is no need for program specific directives. This also allows us to avoid altering our COBOL program. In other situations it may make more sense to make your programs use different namespaces. In that case you should use program local directives or a qualified program name.

To set compiler directives complete the following:

  1. Open the project properties for CobolBook by right-clicking on the CobolBook node and clicking Properties.
  2. Expand Micro Focus COBOL and choose Build Configuration.
  3. At the bottom there is a box which is labelled as Additional directives. Here you can enter your project global directives. Add the line "ilnamespace(com.microfocus.book)" to that text box.
  4. Click OK to save the new directives.

This will now generate a COBOL program that is in the specified namespace. When you import this into Java the namespace will interact appropriately with Java packages. Now look in the directory bin/com/microfocus/book in the COBOL Explorer. In there you will see a BookLegacy.class and a BookLegacy.cbldat file. These together form your COBOL program specified by book.cbl.

There is one other facility we will use to make life easier when calling the book demo program from Java. That is Micro Focus Smartlinkage. This is a tool that converts between traditional COBOL group item types and Java types. So a pic x(99) will be exposed as a Java String. A comp-5 item will become a Java int. This makes calling COBOL programs from Java much easier. There are three compiler directives we will be adding to support this.

Add the compiler directive "ilsmartlinkage" on a new line in the Build Configuration dialog as before. If you save and close this configuration you will notice additional files in bin/com/microfocus/book. These are LnkBDetails.class, LnkFilename.class and LnkFileStatus.class. These are classes generated to represent the linkage items in our book.cbl program. The BookLegacy class will now have an entry point that takes these wrapper classes rather than just taking a generic reference class that gives direct byte access. This simplifies access.

If you open book.cbl and press F4 you will inline any copy books that are copied into this program. If you navigate down to the linkage section you will note the existence of the group items lnk-filename, lnk-file-status and lnk-b-details. These map directly onto the generated classes mentioned above. However we'd prefer to remove the prefixes commonly used in COBOL programs when working in Java. There is a directive to do this. Add the directives "ilcutprefix(lnk-b-)" and "ilcutprefix(lnk-)" to the Build Configuration. When you save and close the dialog your COBOL will be rebuilt. If you look in bin/com/microfocus/book once more you'll note that the prefixes have been removed from the generated class files.

This finalises the JVM COBOL project and we will now move onto incorporating this into a REST service.

RestBook Dynamic Web Project

Now we will create the JSP component of the application. This project will import the previous project as a dependency. It will be composed of in total:

  1. The CobolBook project.
  2. A set of JAXB annotated classes that match our JSON input and output.
  3. A wrapper interface that will call out to our COBOL and map between our JAXB classes and the smartlinkage types.
  4. A set of classes that will map JAX-RS paths to the functions that produce the relevant resources.
  5. A web.xml deployment descriptor.

Creating the Project

The steps to create the dynamic web project are as follows:

  1. Click File->New->Other.
  2. Expand the Web node and select Dynamic Web Project. Click Next.
  3. Set the Project name field to "RestBook".
  4. Ensure Use default location is checked.
  5. In the Target runtime field either select an already existing Tomcat runtime or set one up by clicking New Runtime. To set up a Tomcat runtime see here.
  6. Set the Dynamic web module version to 2.5.
  7. Click Finish.

This may ask you to open the Java EE perspective. Do so. As before all commands involving the RestBook project must be executed in the Java EE perspective.

Now we need to add appropriate references to the previous project. There are two places this must be done: in the build options, so the Java compiler can find the appropriate classes to build against; and in the deployment assembly so that the JVM COBOL program will be exported into the created web archive.

To set up the Java Build Path complete the following:

  1. Open the project Properties for the RestBook project as with the CobolBook project.
  2. Select Java Build Path.
  3. Select the Projects tab.
  4. Click the Add... button, check the CobolBook box and click OK.
  5. Select the Libraries tab.
  6. Click the Add Library... button, select COBOL JVM Runtime System, click Next and finally Finish.

Now we need to set up the Deployment Assembly complete the following:

  1. In the Properties dialog select Deployment Assembly. Click Apply if asked to save any build path changes.
  2. Click Add, select Project and click Next.
  3. Select CobolBook and click Finish.
  4. Click Add, select Java Build Path Entries and click Next.
  5. Select COBOL JVM Runtime System and click Finish.
  6. Click OK to exit the Properties dialog.

Now we need to add the Jersey runtime. First download it from here. Get the Jersey 1.x zip bundle and copy all the jar files within to WORKSPACE/RestBook/WebContent/WEB-INF/lib.

JAXB Data Classes

JAXB is a standard JVM library that allows you to annotate a class such that an XML serializer can easily convert it to and from XML. For the purposes of REST we can use a JSON serializer with the same annotations to create JSON output.

There are two annotations that are of prime importance to our tutorial. They are:

  1. @XmlRootElement
  2. @XmlElement

As the linked docs specify, the XmlRootElement is used both to mark the class as a JAXB class and to create the root data tag name in XML. When a JAXB class is a component of another class this tag is replaced with the appropriate XmlElement name of that component. In JSON the XmlRootElement doesn't do anything since only fields are labelled in a JSON object.

The XmlElement annotation is used to specify the names of sub-elements in XML. These translate into fields in JSON. These can be attached either to a Java field or to a Java property in the standard get/setProperty format. When the property is an array or collection the XML output would be multiple elements of the same name. With JSON the output will be converted into a JSON array. However there is a slight problem with this. The JSON parser cannot normally distinguish between an array of one element and simply a single element in this format. So you need to create a context resolver so that JAXB knows this element is always an array. Otherwise you will get strange behaviour when your collections are of size 1. We will come back to this problem later.

We will be creating two different resource types in our service. The collection of all books and of individual books. There will also be a Link class which will be used to specify the links between various records in the book file. For an individual book we will contain links to the first, last, previous, next and current book. For the collection we will have similar links but on a page by page basis.

First we clearly need a book class. Lets create it. Make a normal Java class in the package com.microfocus.book.rest. Give it the name JaxbBookBean. Enter the code from the link here. As you can see this contains the fields and properties that represent our book data. We have annotations identifying what JSON field will be generated for every property in the object.

However returning an object like this without any reference information is considered bad REST style. We need the links we defined previously. Create a new class in the same package and call it Link. Add the code from the link here. The links are straight-forward objects containing two fields. These are rel and href. Predictably rel specifies the relation this link has to the record it is embedded in, while href contains the actual link. These will allow us to navigate around our database. Good REST style will put the link to the next record in a database into the current record. This will allow us to step forwards and backwards through our data.

We have the structure of our object and of our links. What we need now is a payload object that combines the two to form our response. Create a class in the same package called JsonBookIdGetResponse. Copy the code from the link here. As you can see the response contains two properties. The data and the links. This is a standard structure for REST object.

We have a similar structure for our second payload object. Create JsonBookGetResponse and copy the code from here. The only difference here is that the second type contains a property which is a collection of the first payload type. This is a normal structure for a collection type. You have links for each entry (though it is normal to limit the per entry links to only the self reference) and then a set of links that specify how to reach particular pages in the collection. The first payload would look like this:

{
  "bookdata": {
    "author": "J. R. R. Tolkien",
    "isbn": "7123825",
    "onhand": "23",
    "price": "4.95",
    "sold": "100",
    "stockno": "1123",
    "stockval": "113.85",
    "title": "The Fellowship of the Ring",
    "type": "Fantasy"
  },
  "links":[
    {
      "rel": "self",
      "href": "localhost/RestBook/rest/book/1111"
    },
    {
      "rel": "next",
      "href": "localhost/RestBook/rest/book/1112"
    },
    {
      "rel": "prev",
      "href": "localhost/RestBook/rest/book/1110"
    },
    ....
  ]
}
    

This encodes the details of the book record in our database in JSON. It also gives links to other relevant records in the database such as the next and previous records. The second payload that specifies collections of books would look like this:

{
  "data": [
    {
      "bookdata": {    
        "author": "J. R. R. Tolkien",
        "isbn": "7123825",
        "onhand": "23",
        "price": "4.95",
        "sold": "100",
        "stockno": "1123",
        "stockval": "113.85",
        "title": "The Fellowship of the Ring",
        "type": "Fantasy"
      },
      "links":[
        {
          "rel": "self",
          "href": "localhost/RestBook/rest/book/1111"
        }
      ]
    },
    {
      "bookdata": {
        "title": "Harry Potter and the Philosophers Stone",
        ...
      },
      "links":[
        {
          "rel": "self",
          "href": "localhost/RestBook/rest/book/1112"
        }
      ]
    },
    ...
  ],
  "links":[
    {
      "rel": "self",
      "href": "localhost/RestBook/rest/book?id=1111&limit=5"
    },
    {
      "rel": "next",
      "href": "localhost/RestBook/rest/book?id=1116&limit=5"
    }
  ]
}
    

Note in the second case the use of the GET parameters to specify position and page size within our collection URL. Also note the URLs are different. The first specifies a particular ID within the book database. The second specifies book in general and gets the collection. This is a common shape for REST. With this structure it is easy to see how you can navigate between pages in the collection. Merely perform a GET at the URL in question. It is recommended that the limit parameter is given a maximum size and a default.

We mentioned that the serializers sometime struggle to distinguish between a collection of 1 item and a single item. For this you need to give the parser some aid. Create a class JAXBContextResolver in the same package as before. Add the code from the link here. This class is used to tell the framework always to serialize certain elements as collection types, even if there is only one entry. This completes the code we need for serialisation.

Interfacing to COBOL

We now need to interface into our COBOL program from Java. This section will be kept short. If you need a more detailed description of what is going on see this JSP tutorial. The JSP tutorial uses a similar program and interfaces to it in the same way. The only real difference is the JSP tutorial maintains session information between calls while this tutorial only keeps the RunUnits around long enough to complete a single call. This tutorial uses a modified BookBean that has been marked up with JAXB annotations as well. Finally there are some added operations to help construct the extra information needed by the REST service but these should be transparent.

Since the details are covered elsewhere we'll briefly point out which code to create for this section. We need three new classes. All in the package we created previously. These are BookId, JavaBookException and JaxbBookInterface. These three combined forms a functional interface to our COBOL that presents the data via our JAXB object. All we need to add is the code to turn a particular URL into a resource in our book file. Open the JaxbBookInterface file and change the BOOK_FILE constant to point to the location of the bookfile.dat file delivered with the source archive.

URLs and HTTP methods

As mentioned previously REST works by exposing resources as URLs and operations as HTTP methods. For instance you read a book record by performing a GET at the URL domain/RestBook/rest/book/id where id is replaced with your book's id and domain is naturally the domain you are running your service from. To update the book you would PUT a book record back to this URL. To delete it you'd call the HTTP DELETE method. If the book in question doesn't exist the call should return a HTTP 404 response. All the methods and signals work as if we were just storing documents on a web server.

JAX-RS is another set of annotations that allows us to mark which URL a service class is bound to and how it handles each method. It even allows you to do different things based upon whether the payload is encoded or requested as XML as opposed to JSON though we won't cover the details of that here. We will consider the collection URL first. This one will only have a GET method because it makes no sense to delete or update an entire collection.

Create the class BookUrl in the same package as the rest of our code. Add the code from the link here. The annotations are the interesting part here. We have four of them on the class, fields and methods:

  1. @Path
  2. @Context
  3. @GET
  4. @Produces

The Path annotation is given class scope. It specifies the path of this resource relative to the base path that will be specified later in our web.xml file. When the Jersey engine sees a call it will use these annotations to decide where to direct them. We pass in a base path of "book" but also a JSON extension which is specified as a named parameter which is optionally ".json" or blank via the regex provided. This means our user could enter http://domain/rest/book/1111.json and get the same result as for http://domain/rest/book/1111. It exists to enable us to possibly allow XML output. If the extension was .xml you would redirect to an appropriate method with that output type.

The Context annotation denotes a UriInfo field. The call will put the appropriate information from the REST call here. The routine can use this field to get hold of the relevant information to construct the links.

The GET annotation simply specifies that this field responds to the GET HTTP method. By not having a corresponding entry for the other methods we are specifying that they are not supported.

The Produces annotation states what type of output is being produced. This correlates with the relevant information in the HTTP header returned by the GET call. In this case we are telling it that we want to return JSON.

There are also two annotations on method parameters. These are:

  1. @PathParam
  2. @QueryParam

These are relatively straight-forward. They enable the framework to decide where it is going to put the parameters to the call. The PathParam picks up the extension we talked about previously and binds it to the string ext. The QueryParam annotations take the GET parameters and pass them into the specified variables. Note the strings specified in the annotations are the same parameters as detailed in our JSON example earlier.

This method returns the type Response. This is a JAX-RS type that is comprised of a HTTP status code and an optional payload. As you can see, when an error is found we are returning a standard 404 Not Found status. When everything executes correctly we are returning 200 OK along with our generated payload. The rest of the code is logic that calls our previously created interface class and puts together our response payload. It uses the uri field to get the base URL of our service and from there constructs the links for each related record in the table.

There is another class to handle the individual records. Create a class called BookIdUrl in the same package. Add the code from here. Note that there are some extra additions. There are multiple path parameters in this URL. First we have the ID and secondly we have the extension. Each has its own regular expression that defines its format. The extension works as before. The ID limits our IDs to a four digit numeric string.

We have some new annotations here. They are:

  1. @PUT
  2. @Consumes
  3. @DELETE

PUT and DELETE are pretty straight forward. These bind the relevant HTTP methods to the routines in question. Consumes dictates what is in our HTTP headers again. In this case we are specifying that our PUT consumes a JSON object. The first parameter of this method takes a type JAXBElement<JaxbBookBean>. This specifies the type we are consuming. The JAXBElement contains the unparsed data and the generic parameter tells us this represents a JaxbBookBean.

Note we are passing in the raw type here. It doesn't make sense to set links in an update or create operation. The links aren't something that are normally editable. We call getValue() to parse our JSON into a JaxbBookBean that Java can work with. That record has the URL ID put into it and is then written to our table. It is worth noting that on an exception we return the 500 Internal Server Error, keeping with our principle of using HTTP server codes to specify server state.

One aspect of final interest is the DELETE method. If our exception has the return code "23" it means that the record in question was not found in the table. In this instance we return the 204 No Content status code to the client.

Deployment Descriptor (web.xml)

The last part we need for a working service is the web.xml file. This file is used to tell any web application how to direct its URLs to particular servlets. In this case we want to direct calls to a certain subset of URLs to the Jersey framework so it can process the calls. We also want to set up appropriate details so that Jersey can find our classes. If you haven't already done so create a web.xml file in WebContent/WEB-INF. Then add the code from below:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>com.microfocus.rest</display-name>
  <servlet>
    <servlet-name>REST-Service</servlet-name>
    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
    <init-param>
      <param-name>com.sun.jersey.config.property.packages</param-name>
      <param-value>com.microfocus.book.rest</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>REST-Service</servlet-name>
    <url-pattern>/rest/*</url-pattern>
  </servlet-mapping>
  <welcome-file-list>
    <welcome-file>client.html</welcome-file> 
  </welcome-file-list>  
</web-app>
    

The key here is the servlet and servlet-mapping nodes. The first loads the Jersey framework with our package in the initial parameters. This is used by Jersey to find where it should direct its calls to. Any class in the specified package with a Path annotation will be loaded.

The second maps any URLs starting with /rest/ onto that servlet. All our URLs will then be extensions of this base URL. The welcome file list does not matter for now. It will eventually point to our client.

This is enough to get a working service. Run the service by right-clicking on the RestBook project and clicking Run As->Run on Server. Navigate to http://localhost:8080/RestBook/rest/book/1111. The file that is downloaded at that URL should be our record 1111 from the book file.

Web Client using JQuery

As mentioned previously our client will be developed using HTML and Javascript using the JQuery library. JQuery is a framework for searching a web pages domain object model for particular elements and selecting them. You can then call all the normal methods on that element to hide, show, format and modify the element among other options.

We will create an HTML page as a user interface view. Then we will create a set of Javascript routines that attach event handlers to the buttons in our view. These will be used to switch between UI panels by calling hide and show. They will trigger appropriate AJAX calls to our REST service, the response of which will be put into our web form.

Project Setup

We will use the existing RestBook project for convenience. This is not strictly necessary but using a separate project can introduce cross-site scripting. Some web browsers block such applications from working unless the appropriate permissions are set up to allow cross-site access. This setup is beyond the scope of this tutorial.

First in the WebContent folder create a folder named js. This will hold all our client Javascript code. Now download JQuery: the tutorial was developed with JQuery version 1.8.3. Place the Javascript file into the js directory. For this tutorial the file was renamed simply as jquery.js. If you prefer to keep the full versioned name then you can. However you must change the appropriate name in the HTML script tag later.

Now add our view code. Create an HTML file in the WebContent directory called client.html. This will hold our user interface for the book demo client. Add the code below:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
  <title>REST Book client</title>
  </head>
  <body>
    <div id="viewSwitcher">
      <button id="btnSingleView">Single Book View</button>
      <button id="btnCollectionView">Collection View</button>
      <br/><br/>
    </div>

    <div id="collectionView" style="border-width:1px;border-style:solid;">
      <h3 style="margin-top:0px;">Book Demo Collection View</h3>
      <div id="collectionLinks">
      </div>
      <button id="btnNextCol">Next</button>
      <button id="btnPrevCol">Previous</button>
    </div>
     
    <div id="singleBookView" style="border-width:1px;border-style:solid;">
      <h3 style="margin-top:0px;">Book Demo Single Book View</h3>
      <form id="bookForm" onSubmit="return false;">
        <table>
          <tr><td>Stock Number:</td><td colspan="7"><input type="text" id="stockno" value=""/></td></tr>
          <tr><td>ISBN:</td><td colspan="7"><input type="text" id="isbn" value=""/></td></tr>
          <tr><td>Title:</td><td colspan="7"><input type="text" size="120" id="title" value=""/></td></tr>
          <tr><td>Author:</td><td colspan="7"><input type="text" size="120" id="author" value=""/></td></tr>
          <tr><td>Type:</td><td colspan="7"><input type="text" size="120" id="type" value=""/></td></tr>
          <tr>
            <td>Price:</td> <td><input type="text" id="price" value=""/></td>
            <td>On Hand:</td> <td><input type="text" id="onhand" value=""/></td>
            <td>Sold:</td> <td><input type="text" id="sold" value=""/></td>
            <td>Stock Value:</td> <td><input type="text" readonly="readonly" id="stockval" value=""/></td>
          </tr>
          <tr>
            <td colspan="8">
              <button id="btnRead">Read</button>
              <button id="btnAdd">Add</button>
              <button id="btnDelete">Delete</button>
              <button id="btnPrev">Previous</button>
              <button id="btnNext">Next</button>
            </td>
          </tr>
        </table>
      </form>
    </div>
    <script type="text/javascript" src="js/jquery.js"></script>
    <script type="text/javascript" src="js/client.js"></script>
    <script type="text/javascript" src="js/single.js"></script>
    <script type="text/javascript" src="js/collections.js"></script>
  </body>
</html>
    

You will note that there are three div elements in the body section of this HTML file. These correspond to three sections of our UI. The first is a switch between the two views of our application. The second two are the views themselves. When the appropriate button in the view switcher is pressed one view will be hidden and the other displayed.

The collectionView div contains two relevant components. The collection links will be populated with links to various books in the file on load or upon update. There are then a pair of buttons which allows us to page forwards and backwards into the collection. When it is complete a click on any of the links will bring up the single book view at that book.

The singleBookView is used to view all the details of a specific book. It will allow us to read, update and delete books from the file. It will also allow us to scan through the file on a single book basis.

The last detail of note is the four script links. The first one is to load the JQuery library. The last three contain our Javascript code. The first of these is for code common to the entire client. The latter two are specific to either of the two views. Create these files in the js directory now.

Client Javascript

Open client.js and add the code from the link here. This code contains some of the more basic details for the use of JQuery. We shall consider the code below:

$(document).ready(function(){
  $('#singleBookView').hide();
  initialiseCollections();
 });
    

This code binds a function to the document's ready event handler. This code in particular hides the singleBookView and initialises the collection view. The use of the form $(selector) appears twice here. This is in fact a function call that is at the heart of JQuery. The $ function takes a object, usually a string, that we call a selector. The selector is a query that will match zero, one or many elements in the page's DOM. In the first case we are using the document selector. This selects the root of the document so you can perform document-wide actions: in this case attaching an event handler for the document ready event.

Then we are using a selector '#singleBookView': the use of the # is to pick out a DOM object id. If you look at our HTML you will see that one div has an id attribute with this value. When this selector is called that item in particular will be selected. We then call the hide method to make that item invisible.

Another event is set up straight after the document ready handler. These attach click handlers to our view switcher buttons. They work very similarly to the ready event so we will not go into them in any further detail.

Single View Javascript

Open the single.js file and add the code from the link here. This code handles the events from the single book view. It also manages the execution of REST requests for the single book resources. Most of the details should be transparent given understanding of the previous Javascript file. However the AJAX calls themselves are new. Look at the function below:

function readByUrl(url, postfunc) {
  $.ajax({
    type: 'GET',
    url: url,
    dataType: "json",
    success: function(book, status, jqXHR){
      unpackBook(book);
      renderBook(currentBook);
      if(postfunc != null)
      {
        postfunc();
      }
    },
      error: function (e) {
        //console.log('readById error');
      }
  });
}
    

This code uses the JQuery ajax method to put together an asynchronous call to our REST service. We specify the type of the call, the target URL, the expected output datatype and the functions to call on success and on error.

Look at the success function. It takes as its first argument our JSON payload from the GET call. We do not need to do anything to parse this: JQuery manages this for us and JSON is a native data format for Javascript. We can access the fields directly. The unpackBook function does that by pulling out the links and book data from our payload object and storing them in fields. The renderBook method then takes that information and puts it into the form fields using appropriate JQuery selectors. Note that the entire mechanism of the call is handled in the event handler. This is necessary given the asynchronous nature of the call.

The delete and add calls work similarly to the read call.

Collection View Javascript

Our last Javascript file is collections.js. Open it and add the code from the link here. This code should be relatively easy to understand given understanding of the previous two files. There are two new elements here: we have a new selector type; and we are adding and deleting elements from the page's DOM.

Let's look at the removal and our new selector in one go. Look at the code below:

var COL_LINK_CLASS = 'collectionLink';
var COL_LINK_SELECTOR = '.'   COL_LINK_CLASS;

...

function clearCollection() {
	$(COL_LINK_SELECTOR).remove();
}
    

This code creates a new selector string. Once you combine the two constant strings you get '.collectionLink'. The dot is used to specify that the query is for a class rather than an id. In this case we are removing any DOM element that has the class collectionLink. It is as simple as calling the JQuery function with the appropriate selector and picking the remove method.

If you look at the HTML you will not find any elements that contain this class. That is because we will create them dynamically. To create a DOM element you must perform two steps: first create the DOM object; second insert it into the correct place in the DOM.

We are creating two DOM objects in this client: The links; and the line breaks to separate them out. Look at the code below:

function createCollectionLink(book) {
  var href = getLink("self", book.links);
  var content = createCollectionLinkContent(book.bookdata);
  var elementString = linkOpenTag(href)   content   LINK_CLOSE_TAG;
  
  return $(elementString);
}

function linkOpenTag(href) {
  return "<a href=\""   href  "\" class=\""   COL_LINK_CLASS  "\">";
}

function createCollectionLinkContent(book) {
  return book.stockno   ": "   book.title   " - "   book.author;
}
    

Here we are piecing together a string which contains an HTML anchor tag. We then pass that string into the JQuery function which is enough to generate a DOM object. It is generated with the appropriate self link from our book variables link field. We are putting the stock number, title and author of the book into the link. The actual reference is pointing to the location of the book on our REST service. This seems wrong but we will be overriding the behaviour of our links. See the code below:

function registerLinkEventHandlers() {
  $(COL_LINK_SELECTOR).click(function() {
    readByUrl(this, function() {
      singleBookView();
    });
    return false;
  });
}
    

As you can see this overrides the click event for those links. When the links are clicked on the previously seen readByUrl function is called: this time with a post function that calls singleBookView. The readByUrl function executes asynchronously so we need to handle post call rendering via a post function such as this. It is good style to put relevant link information into the tag rather than relying upon internal variables.

One last element remains: adding our DOM objects. See the code below:

var COL_SELECTOR = '#collectionLinks';

...

function renderCollection(collection) {
  clearCollection();
  var i = 0;
  
  for(i = 0; i < collection.length;   i)
  {
    var link = createCollectionLink(collection);
    var lineBreak = $(LINE_BREAK);
    link.appendTo(COL_SELECTOR);
    lineBreak.appendTo(COL_SELECTOR);
  }
  
  for(; i < 5; i  )
  {
    var lineBreak = $(LINE_BREAK);
    lineBreak.appendTo(COL_SELECTOR);
  }
  
  registerLinkEventHandlers();
}
    

This function is called to update our collection. The collection of books is passed in. First it clears out the old details by calling the function we saw earlier. Then it looks through the collection and creates our link and line break objects. Then it calls the appendTo method on these objects with the COL_SELECTOR selector string. This selector points at the empty div we set aside in our collection view. The function then pads out with line breaks in case our page has less than 5 items in it. Then it registers the link event handlers. This needs to be done every time new links are created.

Finishing the Client

We will make one more observation before running our client. Open the WebContent/WEB-INF/web.xml file and find the welcome-file-list node. This should contain an entry to client.html. That is what points our web app's default landing page to our client. When we execute this web app it should now display our JQuery client by default.

Right-click on the RestBook project and select Run As->Run on Server. This should load our application up in collection view as expected. The mere existence of the links on this page is evidence enough that the JQuery code is working. Clicking on one should take you to the single book view. Clicking on the view selection buttons should switch between the views. You should be able to add and delete records from the file.

Conclusion

This completes a full end-to-end tutorial on the creation of REST services that use COBOL code and data files, followed by the creation of an appropriate client to consume that service. You should now be able to create appropriate JAXB data types, copy data from COBOL programs into these types and convert resource URLs into operations that return these data types in a RESTful manner.

You should also have an overview of the use of HTML, Javascript and JQuery in the creation of clients that can understand the JSON coming back from our service.

Appendix 1 - REST Service Code

book.cbl

      $set retrylock
       program-id. BookLegacy.

       environment division.
       input-output section.
       file-control.
           select bookfile assign to filename
               file status is ls-file-status
               organization is indexed
               access mode is dynamic
               record key is b-stockno
               alternate record key is b-title with duplicates
               alternate record key is b-author with duplicates
               .

       data division.
       file section.
       FD bookfile.
       copy "book-rec.cpy" replacing ==(prefix)== by ==b==.

       working-storage section.
       01 ls-file-status   pic xx.
       01 ls-call-status   pic x(2) comp-5.

       linkage section.
       01 lnk-filename          pic x(256).
       01 lnk-function          pic x.
           88 read-record       value "1".
           88 add-record        value "2".
           88 delete-record     value "3".
           88 next-record       value "4".
           88 prev-record       value "5".
           88 first-record      value "6".
           88 last-record       value "7".
           88 update-record     value "8".
       01 lnk-file-status       pic xx.
       78 no-key-error          value "B1".
       copy "book-rec.cpy" replacing ==(prefix)== by ==lnk-b==.

       procedure division using by value lnk-function
                                by reference lnk-b-details
                                by reference lnk-file-status.
       main section.

      *     call "CBL_TOUPPER" using lnk-b-text-details
      *                        by value length lnk-b-text-details
      *                        returning ls-call-status

           evaluate true
               when read-record
                   perform do-read-record

               when add-record
                   perform do-add-record

               when delete-record
                   perform do-delete-record

               when next-record
                   perform do-next-record

               when prev-record
                   perform do-prev-record

               when first-record
                   perform do-first-record

               when last-record
                   perform do-last-record

               when update-record
                   perform do-update-record

           end-evaluate
           exit program
           .

       do-read-record section.
           open input bookfile
           if ls-file-status <> "00"
               initialize lnk-b-details
               move all '*' to lnk-b-text-details

               move ls-file-status to lnk-file-status 
               exit section
           end-if
           evaluate true
                when lnk-b-stockno <> spaces
                    move lnk-b-stockno to b-stockno
                    read bookfile

                when lnk-b-title <> spaces
                    move lnk-b-title to b-title
                    read bookfile key is b-title

                when lnk-b-author <> spaces
                    move lnk-b-author to b-author
                    read bookfile key is b-author

                when other
      *>------------No key specified - return unsuccessful read
                     move no-key-error to  ls-file-status

           end-evaluate
           move ls-file-status to lnk-file-status
           if ls-file-status = "00"
               move b-title to lnk-b-title
               move b-type to lnk-b-type
               move b-author to lnk-b-author
               move b-stockno to lnk-b-stockno
               move b-isbn to lnk-b-isbn
               move b-retail to lnk-b-retail
               move b-onhand to lnk-b-onhand
               move b-sold to lnk-b-sold
           else
               initialize lnk-b-details
               move all '*' to lnk-b-text-details
           end-if
           close bookfile
           .

       do-next-record section.
           open input bookfile
           if ls-file-status <> "00"
               initialize lnk-b-details
               move all '*' to lnk-b-text-details

               move ls-file-status to lnk-file-status
               exit section
           end-if

           move lnk-b-stockno to b-stockno
           start bookfile key > b-stockno
           read bookfile next

           move ls-file-status to lnk-file-status
           if ls-file-status = "00"
               move b-title to lnk-b-title
               move b-type to lnk-b-type
               move b-author to lnk-b-author
               move b-stockno to lnk-b-stockno
               move b-isbn to lnk-b-isbn
               move b-retail to lnk-b-retail
               move b-onhand to lnk-b-onhand
               move b-sold to lnk-b-sold
           else
               initialize lnk-b-details
               move all '*' to lnk-b-text-details
           end-if
           close bookfile
           .

       do-prev-record section.
           open input bookfile
           if ls-file-status <> "00"
               initialize lnk-b-details
               move all '*' to lnk-b-text-details

               move ls-file-status to lnk-file-status
               exit section
           end-if

           move lnk-b-stockno to b-stockno
           start bookfile key < b-stockno
           read bookfile previous

           move ls-file-status to lnk-file-status
           if ls-file-status = "00"
               move b-title to lnk-b-title
               move b-type to lnk-b-type
               move b-author to lnk-b-author
               move b-stockno to lnk-b-stockno
               move b-isbn to lnk-b-isbn
               move b-retail to lnk-b-retail
               move b-onhand to lnk-b-onhand
               move b-sold to lnk-b-sold
           else
               initialize lnk-b-details
               move all '*' to lnk-b-text-details
           end-if
           close bookfile
           .

       do-add-record section.
           open i-o bookfile
           evaluate ls-file-status
               when "05"
      *>-------File not created yet
               when "00"
                   continue

               when other
                   move ls-file-status to lnk-file-status
                   exit section
           end-evaluate

           move lnk-b-stockno to b-stockno
           read bookfile
           if ls-file-status = "00"
      * Record already exists - so error
               move "99" to ls-file-status
           else
               move lnk-b-isbn to b-isbn
               move lnk-b-title to b-title
               move lnk-b-type to b-type
               move lnk-b-author to b-author
               move lnk-b-retail to b-retail
               move lnk-b-onhand to b-onhand
               move lnk-b-sold to b-sold
               write b-details
           end-if

           move ls-file-status to lnk-file-status
           close bookfile
           .

       do-delete-record section.
           open i-o bookfile
           if ls-file-status <> "00"
               move ls-file-status to lnk-file-status
               exit section
           end-if

           evaluate true
               when lnk-b-stockno <> spaces
                   move lnk-b-stockno to b-stockno
                   read bookfile
                   delete bookfile record

               when lnk-b-title <> spaces
                   move lnk-b-title to b-title
                   read bookfile key is b-title
                   delete bookfile record

               when lnk-b-author <> spaces
                   move lnk-b-author to b-author
                   read bookfile key is b-author
                   delete bookfile record

               when other
      *>------------No key specified - return unsuccessful read
                   move no-key-error to ls-file-status

           end-evaluate

           move ls-file-status to lnk-file-status
           close bookfile
           .

       do-first-record section.
           open input bookfile
           if ls-file-status <> "00"
               initialize lnk-b-details
               move all '*' to lnk-b-text-details

               move ls-file-status to lnk-file-status
               exit section
           end-if

           set b-stockno to "0000"
           start bookfile key >= b-stockno
           read bookfile next

           move ls-file-status to lnk-file-status
           if ls-file-status = "00"
               move b-title to lnk-b-title
               move b-type to lnk-b-type
               move b-author to lnk-b-author
               move b-stockno to lnk-b-stockno
               move b-isbn to lnk-b-isbn
               move b-retail to lnk-b-retail
               move b-onhand to lnk-b-onhand
               move b-sold to lnk-b-sold
           else
               initialize lnk-b-details
               move all '*' to lnk-b-text-details
           end-if
           close bookfile
           .

       do-last-record section.
           open input bookfile
           if ls-file-status <> "00"
               initialize lnk-b-details
               move all '*' to lnk-b-text-details

               move ls-file-status to lnk-file-status
               exit section
           end-if

           set b-stockno to "9999"
           start bookfile key <= b-stockno
           read bookfile previous

           move ls-file-status to lnk-file-status
           if ls-file-status = "00"
               move b-title to lnk-b-title
               move b-type to lnk-b-type
               move b-author to lnk-b-author
               move b-stockno to lnk-b-stockno
               move b-isbn to lnk-b-isbn
               move b-retail to lnk-b-retail
               move b-onhand to lnk-b-onhand
               move b-sold to lnk-b-sold
           else
               initialize lnk-b-details
               move all '*' to lnk-b-text-details
           end-if
           close bookfile
           .

       do-update-record section.
           open i-o bookfile
           evaluate ls-file-status
               when "05"
      *>-------File not created yet
               when "00"
                   continue

               when other
                   move ls-file-status to lnk-file-status
                   exit section
           end-evaluate

           move lnk-b-stockno to b-stockno
           read bookfile
           move lnk-b-isbn to b-isbn
           move lnk-b-title to b-title
           move lnk-b-type to b-type
           move lnk-b-author to b-author
           move lnk-b-retail to b-retail
           move lnk-b-onhand to b-onhand
           move lnk-b-sold to b-sold
           if ls-file-status = "00"
               rewrite b-details
           else
               write b-details
           end-if

           move ls-file-status to lnk-file-status
           close bookfile
           .
       
       set-filename section.
       entry "SET_FILENAME" using lnk-filename.
           move lnk-filename to filename
           
           goback.
    

book-rec.cpy

       01 (prefix)-details.
           03 (prefix)-text-details.
             05 (prefix)-title     pic x(50).
             05 (prefix)-type      pic x(20).
             05 (prefix)-author    pic x(50).

           03 (prefix)-stockno     pic x(4).
           03 (prefix)-isbn          pic 9(13).
           03 (prefix)-retail      pic 99v99.
           03 (prefix)-onhand      pic 9(5).
           03 (prefix)-sold          pic 9(5) comp-3.

    

JaxbBookBean.java

package com.microfocus.book.rest;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "book")
public class JaxbBookBean
{
  private String stockno;
  private String isbn;
  private String title;
  private String author;
  private String type;
  private String price;
  private String onhand;
  private String sold;
  private String stockval;

  JaxbBookBean()
  {
    this("0000", "0", "", "", "", "0.0", "0", "0", "0.0");
  }

  JaxbBookBean(String stockno, String isbn, String title, String author, String type, String price, String onhand, String sold, String stockval)
  {
    this.stockno = stockno;
    this.isbn = isbn;
    this.title = title;
    this.author = author;
    this.type = type;
    this.price = price;
    this.onhand = onhand;
    this.sold = sold;
    this.stockval = stockval;
  }

  @XmlElement(name = "stockno")
  public String getStockno()
  {
    return stockno;
  }

  public void setStockno(String stockno)
  {
    this.stockno = stockno;
  }

  @XmlElement(name = "isbn")
  public String getIsbn()
  {
    return isbn;
  }

  public void setIsbn(String isbn)
  {
    this.isbn = isbn;
  }

  @XmlElement(name = "title")
  public String getTitle()
  {
    return title;
  }

  public void setTitle(String title)
  {
    this.title = title;
  }

  @XmlElement(name = "author")
  public String getAuthor()
  {
    return author;
  }

  public void setAuthor(String author)
  {
    this.author = author;
  }

  @XmlElement(name = "type")
  public String getType()
  {
    return type;
  }

  public void setType(String type)
  {
    this.type = type;
  }

  @XmlElement(name = "price")
  public String getPrice()
  {
    return price;
  }

  public void setPrice(String price)
  {
    this.price = price;
  }

  @XmlElement(name = "onhand")
  public String getOnhand()
  {
    return onhand;
  }

  public void setOnhand(String onhand)
  {
    this.onhand = onhand;
  }

  @XmlElement(name = "sold")
  public String getSold()
  {
    return sold;
  }

  public void setSold(String sold)
  {
    this.sold = sold;
  }

  @XmlElement(name = "stockval")
  public String getStockval()
  {
    return stockval;
  }

  public void setStockval(String stockval)
  {
    this.stockval = stockval;
  }

  public static JaxbBookBean blankBook()
  {
    return msgBook("*************************************");
  }

  public static JaxbBookBean msgBook(String msg)
  {
    String stockno = "****";
    String isbn = "*************";
    String title = msg;
    String author = "*************************************";
    String type = "****";
    String price = "****";
    String onhand = "****";
    String sold = "****";
    String stockval = "****";

    return new JaxbBookBean(stockno, isbn, title, author, type, price, onhand, sold, stockval);
  }
}
    

JsonBookIdGetResponse.java

package com.microfocus.book.rest;

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name="book")
public class JsonBookIdGetResponse
{
  private JaxbBookBean data;
  private List<Link> links;
  
  public JsonBookIdGetResponse()
  {
    data = new JaxbBookBean();
    links = new ArrayList<Link>();
  }
  
  public JsonBookIdGetResponse(JaxbBookBean data, List<Link> links)
  {
    this.data = data;
    this.links = links;
  }
  
  @XmlElement(name = "bookdata")
  public JaxbBookBean getData()
  {
    return data;
  }

  public void setData(JaxbBookBean data)
  {
    this.data = data;
  }

  @XmlElement(name = "links")
  public List<Link> getLinks()
  {
    return links;
  }

  public void setLinks(List<Link> links)
  {
    this.links = links;
  }
}

    

JsonBookGetResponse.java

package com.microfocus.book.rest;

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name="books")
public class JsonBookGetResponse
{
  private List<JsonBookIdGetResponse> data;
  private List<Link> links;
  
  public JsonBookGetResponse()
  {
    data = new ArrayList<JsonBookIdGetResponse>();
    links = new ArrayList<Link>();
  }
  
  public JsonBookGetResponse(List<JsonBookIdGetResponse> data, List<Link> links)
  {
    this.data = data;
    this.links = links;
  }
  
  @XmlElement(name = "data")
  public List<JsonBookIdGetResponse> getData()
  {
    return data;
  }

  public void setData(List<JsonBookIdGetResponse> data)
  {
    this.data = data;
  }

  @XmlElement(name = "links")
  public List<Link> getLinks()
  {
    return links;
  }

  public void setLinks(List<Link> links)
  {
    this.links = links;
  }
}

    

BookId.java

package com.microfocus.book.rest;

import java.util.regex.Pattern;

public class BookId
{
  public final String id;
  public static final String idRegExStr = "\\d\\d\\d\\d";
  public static final Pattern idRegEx = Pattern.compile(idRegExStr);
  
  public BookId(String id)
  {
    if(!idRegEx.matcher(id).matches())
    {
      throw new RuntimeException("Input string is not an ID!");
    }
    
    this.id = id;
  }
  
  public String toString()
  {
    return this.id;
  }
  
  public boolean equals(Object obj)
  {
    return obj instanceof BookId ? equals((BookId) obj) : false;
  }
  
  public boolean equals(BookId rhs)
  {
    return id.equals(rhs.id);
  }
}
    

JavaBookException.java

package com.microfocus.book.rest;

import java.util.*;

public class JavaBookException extends Exception
{

  /**
   * 
   */
  private static final long serialVersionUID = -3882735817601888938L;

  private static final Map<String, String> messages;
  private static final String unknownErrorMessage = "Unknown Error: ";

  public final String statusCode;

  public JavaBookException(String statusCode)
  {
    super(messages.containsKey(statusCode) ? messages.get(statusCode)
        : unknownErrorMessage   statusCode);
    this.statusCode = statusCode;
  }

  static
  {
    messages = new HashMap<String, String>();
    messages.put("35", "Error: Data file not found");
    messages.put("23", "Error: Stock item not found");
    messages.put("46", "No more items left");
    messages.put("99", "Error: Item already exists");
    messages.put("01", "Error: File error");
    messages.put("B1", "Error: No key entered");
  }
}
    

JaxbBookInterface.java

package com.microfocus.book.rest;

import com.microfocus.book.BookLegacy;
import com.microfocus.book.Details;
import com.microfocus.book.FileStatus;
import com.microfocus.book.Filename;
import com.microfocus.cobol.program.IObjectControl;
import com.microfocus.cobol.program.ScaledInteger;
import com.microfocus.cobol.runtimeservices.IRunUnit;
import com.microfocus.cobol.runtimeservices.RunUnit;

public class JaxbBookInterface
{
  private static final String READ_RECORD = "1";
  private static final String ADD_RECORD = "2";
  private static final String DELETE_RECORD = "3";
  private static final String NEXT_RECORD = "4";
  private static final String PREV_RECORD = "5";
  private static final String FIRST_RECORD = "6";
  private static final String LAST_RECORD = "7";
  private static final String UPDATE_RECORD = "8";

  private static final String BOOK_FILE = "C:/work/garethm/JavaWebServicesDemos/COBOLJSPDemo/CobolBook/bookfile.dat";

  public JaxbBookBean readBook(BookId stockNo) throws JavaBookException
  {
    return doBookOperation(READ_RECORD, stockNo);
  }

  public JaxbBookBean addBook(JaxbBookBean book) throws JavaBookException
  {
    doBookOperation(ADD_RECORD, book);

    return book;
  }

  public JaxbBookBean updateBook(JaxbBookBean book) throws JavaBookException
  {
    doBookOperation(UPDATE_RECORD, book);

    return book;
  }

  public JaxbBookBean deleteBook(BookId stockNo) throws JavaBookException
  {
    return doBookOperation(DELETE_RECORD, stockNo);
  }

  public JaxbBookBean nextBook(BookId stockNo) throws JavaBookException
  {
    return doBookOperation(NEXT_RECORD, stockNo);
  }
  
  public BookId nextId(BookId stockNo) throws JavaBookException
  {
    if(lastId().equals(stockNo))
    {
      return stockNo;
    }
    else
    {
      return new BookId(nextBook(stockNo).getStockno());
    }
  }

  public JaxbBookBean prevBook(BookId stockNo) throws JavaBookException
  {
    return doBookOperation(PREV_RECORD, stockNo);
  }
  
  public BookId prevId(BookId stockNo) throws JavaBookException
  {
    if(firstId().equals(stockNo))
    {
      return stockNo;
    }
    else
    {
      return new BookId(prevBook(stockNo).getStockno());
    }
  }

  private static final BookId FIRST_BOOK_ID = new BookId("1111");
  public JaxbBookBean firstBook() throws JavaBookException
  {
    return doBookOperation(FIRST_RECORD, FIRST_BOOK_ID);
  }
  
  public BookId firstId() throws JavaBookException
  {
    return new BookId(firstBook().getStockno());
  }


  private static final BookId LAST_BOOK_ID = new BookId("9999");
  public JaxbBookBean lastBook() throws JavaBookException
  {
    return doBookOperation(LAST_RECORD, LAST_BOOK_ID);
  }
  
  public BookId lastId() throws JavaBookException
  {
    return new BookId(lastBook().getStockno());
  }

  private JaxbBookBean doBookOperation(String function, BookId stockId) throws JavaBookException
  {
    IRunUnit runUnit = new RunUnit();
    BookLegacy bookLegacy = new BookLegacy();
    JaxbBookBean output = new JaxbBookBean();
    runUnit.Add(bookLegacy);
    Filename filename = getFilenameObject(runUnit, BOOK_FILE);

    bookLegacy.SET_FILENAME(filename);

    Details details = getObject(runUnit, Details.class);
    FileStatus status = getObject(runUnit, FileStatus.class);
    details.setStockno(stockId.id);

    bookLegacy.BookLegacy(function, details, status);

    throwExceptionIfError(status);

    jaxbBookBeanFromDetails(details, output);

    runUnit.StopRun();
    
    return output;
  }

  private void doBookOperation(String function, JaxbBookBean book) throws JavaBookException
  {
    IRunUnit runUnit = new RunUnit();
    BookLegacy bookLegacy = new BookLegacy();
    runUnit.Add(bookLegacy);
    Filename filename = getFilenameObject(runUnit, BOOK_FILE);

    bookLegacy.SET_FILENAME(filename);

    Details details = getObject(runUnit, Details.class);
    FileStatus status = getObject(runUnit, FileStatus.class);
    jaxbBookBeanToDetails(details, book);

    bookLegacy.BookLegacy(function, details, status);

    throwExceptionIfError(status);

    jaxbBookBeanFromDetails(details, book);

    runUnit.StopRun();
  }
  
  private static void jaxbBookBeanFromDetails(Details details, JaxbBookBean book)
  {
    book.setStockno(details.getStockno().trim());
    book.setIsbn(""   details.getIsbn());
    book.setTitle(details.getTitle().trim());
    book.setAuthor(details.getAuthor().trim());
    book.setType(details.getType().trim());
    book.setPrice(details.getRetail().toString());
    book.setOnhand(""   details.getOnhand());
    book.setSold(""   details.getSold());
    ScaledInteger stockvalInt = details.getRetail().multiply(new ScaledInteger(details.getOnhand(), 0));
    book.setStockval(stockvalInt.toString());
  }

  private static void jaxbBookBeanToDetails(Details details, JaxbBookBean book)
  {
    details.setStockno(book.getStockno());
    details.setIsbn(Long.parseLong(book.getIsbn()));
    details.setTitle(book.getTitle());
    details.setAuthor(book.getAuthor());
    details.setType(book.getType());
    details.setRetail(ScaledInteger.parseScaledInteger(book.getPrice()));

    int onHandInt = Integer.parseInt(book.getOnhand());
    if(onHandInt < 0)
      throw new RuntimeException("The number of books on hand must be 0 or positive");
    details.setOnhand(onHandInt);

    int soldInt = Integer.parseInt(book.getSold());
    if(soldInt < 0)
      throw new RuntimeException("The number of books sold must be 0 or positive");
    details.setSold(soldInt);
  }

  private static void throwExceptionIfError(FileStatus statusCode) throws JavaBookException
  {
    throwExceptionIfError(statusCode.getFileStatus().trim());
  }

  private static void throwExceptionIfError(String statusCode) throws JavaBookException
  {
    if(!"00".equals(statusCode) && !"02".equals(statusCode))
    {
      throw new JavaBookException(statusCode);
    }
  }

  private static Filename getFilenameObject(IRunUnit runUnit, String filename)
  {
    Filename output = getObject(runUnit, Filename.class);

    output.setFilename(filename);

    return output;
  }

  private static <T extends IObjectControl> T getObject(IRunUnit runUnit, Class<T> cls)
  {
    try
    {
      T output = cls.newInstance();
      runUnit.Add(output);

      return output;
    }
    catch(Throwable t)
    {
      throw new RuntimeException(t);
    }
  }
}
    

package com.microfocus.book.rest;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name="link")
public class Link
{
  public static final String FIRST  = "first";
  public static final String LAST   = "last";
  public static final String NEXT   = "next";
  public static final String PREV   = "previous";
  public static final String SELF   = "self";
  
  private String rel;
  private String href;
  
  public Link()
  {
    this("","");
  }

  public Link(String rel, String href)
  {
    this.rel = rel;
    this.href = href;
  }
  
  @XmlElement(name = "rel")
  public String getRel()
  {
    return rel;
  }

  public void setRel(String rel)
  {
    this.rel = rel;
  }

  @XmlElement(name = "href")
  public String getHref()
  {
    return href;
  }

  public void setHref(String href)
  {
    this.href = href;
  }
  
  public static Link first(String href)
  {
    return new Link(FIRST, href);
  }
  
  public static Link last(String href)
  {
    return new Link(LAST, href);
  }
  
  public static Link next(String href)
  {
    return new Link(NEXT, href);
  }
  
  public static Link prev(String href)
  {
    return new Link(PREV, href);
  }
  
  public static Link self(String href)
  {
    return new Link(SELF, href);
  }
}
    

JAXBContextResolver.java

package com.microfocus.book.rest;

import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import javax.xml.bind.JAXBContext;

import com.sun.jersey.api.json.JSONConfiguration;
import com.sun.jersey.api.json.JSONJAXBContext;

@Provider
public class JAXBContextResolver implements ContextResolver<JAXBContext>
{
  private JAXBContext context;
  private Class<?>[] types = {JsonBookGetResponse.class};

  public JAXBContextResolver() throws Exception
  {
    this.context = new JSONJAXBContext(JSONConfiguration.mapped().arrays("data").build(), types);
  }

  public JAXBContext getContext(Class<?> objectType)
  {
    return (types[0].equals(objectType)) ? context : null;
  }
}
    

BookUrl.java

package com.microfocus.book.rest;

import java.util.ArrayList;
import java.util.List;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
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 com.sun.jersey.api.client.ClientResponse.Status;

@Path(BookUrl.book   BookUrl.jsonExt)
public class BookUrl
{
  public static final String idName = "id";
  public static final String limitName = "limit";
  public static final String book = "book";
  public static final String extName = "ext";
  public static final String jsonExt ="{"   extName   ":(\\.json)?}";
  public static final int MAX_LIMIT = 25;
  public static final int DEFAULT_LIMIT = 5;
  
  @Context
  UriInfo uri;

  @GET
  @Produces(MediaType.APPLICATION_JSON)
  public Response jsonGetBooks(@PathParam(extName) String ext, @QueryParam(idName) BookId id, @QueryParam(limitName) String limitStr)
  {
    JsonBookGetResponse output;
    int limit = DEFAULT_LIMIT;
    
    try
    {
      if(limitStr != null)
      {
        limit = Integer.parseInt(limitStr);
        
        if(limit < 0)
        {
          limit = DEFAULT_LIMIT;
        }
        
        if(limit > MAX_LIMIT)
        {
          limit = MAX_LIMIT;
        }
      }
    }
    catch(NumberFormatException nfe)
    {
      return Response.status(Status.NOT_FOUND).build();
    }
    
    try
    {
      if(id == null)
      {
        JaxbBookInterface instance = new JaxbBookInterface();
        id = instance.firstId();
      }
      output = getBooks(id, ext, limit);
    }
    catch(JavaBookException e)
    {
      return Response.status(Status.NOT_FOUND).build();
    }

    return Response.ok(output).build();
  }
  
  private JsonBookGetResponse getBooks(BookId id, String ext, int limit) throws JavaBookException
  {
    List<JsonBookIdGetResponse> data = new ArrayList<JsonBookIdGetResponse>();
    JaxbBookInterface instance = new JaxbBookInterface();

    BookId firstId = id;
    try
    {
      instance.readBook(id);
    }
    catch(JavaBookException jbe)
    {
      if(jbe.statusCode.equals("23"))
      {
        firstId = instance.nextId(id);
      }
    }
    
    BookId currentId = firstId;
    for(int i = 0; i < limit; i  )
    {
      JsonBookIdGetResponse book = getBook(currentId, ext);
      data.add(book);
      
      BookId nextId = instance.nextId(currentId);
      
      if(nextId.equals(currentId))
      {
        break;
      }
      else
      {
        currentId = nextId;
      }
    }
    
    BookId nextId = currentId;
    BookId prevId = firstId;
    
    for(int i = 0; i < limit; i  )
    {
      prevId = instance.prevId(prevId);
    }
    
    List<Link> links = new ArrayList<Link>();
    String baseUri = uri.getBaseUri()   BookUrl.book   ext;
    links.add(Link.first(baseUri   queryString(instance.firstId(), limit)));
    links.add(Link.last(baseUri   queryString(instance.lastId(), limit)));
    links.add(Link.self(baseUri   queryString(id, limit)));
    links.add(Link.prev(baseUri   queryString(prevId, limit)));
    links.add(Link.next(baseUri   queryString(nextId, limit)));

    return new JsonBookGetResponse(data, links);
  }
  
  private JsonBookIdGetResponse getBook(BookId id, String ext) throws JavaBookException
  {
    JaxbBookInterface instance = new JaxbBookInterface();
    JaxbBookBean book = instance.readBook(id);;
    List<Link> links = new ArrayList<Link>();
    String baseUri = uri.getBaseUri()   BookUrl.book   "/";
    links.add(Link.self(baseUri   id   ext));

    return new JsonBookIdGetResponse(book, links);
  }
  
  private String queryString(BookId id, int limit)
  {
    return "?"   idName   "="   id   "&"   limitName   "="   limit;
  }
}

    

BookIdUrl.java

package com.microfocus.book.rest;

import java.util.ArrayList;
import java.util.List;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
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 javax.xml.bind.JAXBElement;

import com.sun.jersey.api.client.ClientResponse.Status;

@Path(BookUrl.book   "/"   BookIdUrl.id   BookUrl.jsonExt)
public class BookIdUrl
{
  @Context
  UriInfo uri;
  
  
  public static final String id = "{"   BookUrl.idName   ":"  BookId.idRegExStr   "}";

  @GET
  @Produces(MediaType.APPLICATION_JSON)
  public Response jsonGetBook(@PathParam(BookUrl.idName) BookId id, @PathParam(BookUrl.extName) String ext)
  {
    JaxbBookInterface instance = new JaxbBookInterface();

    JaxbBookBean book;
    List<Link> links = new ArrayList<Link>();
    try
    {
      book = instance.readBook(id);
      String baseUri = uri.getBaseUri()   BookUrl.book   "/";
      links.add(Link.first(baseUri   instance.firstId()   ext));
      links.add(Link.last(baseUri   instance.lastId()   ext));
      links.add(Link.self(baseUri   id   ext));
      links.add(Link.prev(baseUri   instance.prevId(id)   ext));
      links.add(Link.next(baseUri   instance.nextId(id)   ext));
    }
    catch(JavaBookException e)
    {
      return Response.status(Status.NOT_FOUND).build();
    }
    
    JsonBookIdGetResponse output = new JsonBookIdGetResponse(book, links);

    return Response.ok(output).build();
  }
  
  @PUT
  @Consumes(MediaType.APPLICATION_JSON)
  public Response jsonPutBook(JAXBElement<JaxbBookBean> parseBook, @PathParam(BookUrl.idName) BookId id)
  {
    JaxbBookBean book = parseBook.getValue();
    book.setStockno(id.id);
    JaxbBookInterface instance = new JaxbBookInterface();

    try
    {
      instance.updateBook(book);
    }
    catch(JavaBookException e)
    {
      return Response.status(Status.INTERNAL_SERVER_ERROR).build();
    }


    return Response.ok().build();
  }
  
  @DELETE
  public Response jsonDeleteBook(@PathParam(BookUrl.idName) BookId id)
  {
    JaxbBookInterface instance = new JaxbBookInterface();

    try
    {
      instance.deleteBook(id);
    }
    catch(JavaBookException e)
    {
      if(e.statusCode.equals("23"))
      {
        return Response.noContent().build();
      }
      else
      {
        return Response.status(Status.INTERNAL_SERVER_ERROR).build();
      }
    }

    return Response.ok().build();
  }
}

    

Appendix 2 - Web Client Code

client.js

var baseURL = "http://localhost:8080/RestBookServer/rest/book";

$(document).ready(function(){
  $('#singleBookView').hide();
  initialiseCollections();
 });

$('#btnSingleView').click(function() {
  singleBookView();
  
  return false;
});

$('#btnCollectionView').click(function() {
  collectionView();
  
  return false;
});

function singleBookView() {
  $('#collectionView').hide();
  $('#singleBookView').show();
}

function collectionView() {
  $('#singleBookView').hide();
  $('#collectionView').show();
}

function parseLink(link, links) {
  switch(link.rel)
  {
    case "self":
      links.self = link.href;
      break;
    case "first":
      links.first = link.href;
      break;
    case "last":
      links.last = link.href;
      break;
    case "previous":
      links.prev = link.href;
      break;
    case "next":
      links.next = link.href;
      break;
  }
}

function getLink(relType, links) {
  
  if(links.length == undefined)
  {
    if(links.rel == relType)
    {
      return links.href;
    }
  }
  else
  {
    for(var i = 0; i < links.length; i  )
    {
      var rel = links.rel;
      if(rel == relType)
      {
        return links.href;
      }
    }
  }
  
  return null;
}
    

single.js

var currentBook;
var bookLinks = {};

$('#btnRead').click(function() {
  //console.log('btnRead.click');
  var id = $('#stockno').val();
  readById(id);
  return false;
});

$('#btnAdd').click(function() {
  //console.log('btnAdd.click');
  addBook();
  return false;
});

$('#btnDelete').click(function() {
  //console.log('btnDelete.click');
  var id = $('#stockno').val();
  deleteBookById(id);
  return false;
});

$('#btnNext').click(function() {
  //console.log('btnNext.click');
  
  if(bookLinks.next != null)
  {
    readByUrl(bookLinks.next);
  }
  else
  {
    outputMessage("No next book!");
  }
  return false;
});

$('#btnPrev').click(function() {
  //console.log('btnPrev.click');
  
  if(bookLinks.prev != null)
  {
    readByUrl(bookLinks.prev);
  }
  else
  {
    outputMessage("No previous book!");
  }
  return false;
});

function readById(id) {
  readByUrl(baseURL   '/'   id);
}


function readByUrl(url, postfunc) {
  $.ajax({
    type: 'GET',
    url: url,
    dataType: "json",
    success: function(book, a, b){
      //console.log('readById success');
      unpackBook(book);
      renderBook(currentBook);
      if(postfunc != null)
      {
        postfunc();
      }
    },
      error: function (e) {
        //console.log('readById error');
      }
  });
}

function deleteBookById(id) {
  //console.log('deleteBookById');
  $.ajax({
    type: 'DELETE',
    url: baseURL   '/'   id,
    success: function(data, status, jqXHR){
      alert('Book deleted successfully');
    },
    error: function(e){
      //console.log('deleteBookById error');
    }
  });
}

function addBook() {
  //console.log('addBook');
  var book = bookFromForm();
  var url = baseURL   '/'   $('#stockno').val();
  $.ajax({
    type: 'PUT',
    contentType: 'application/json',
    url: url,
    dataType: "json",
    data: book,
    success: function(data, status, jqXHR){
      alert('Book added successfully');
    },
    error: function(jqXHR, status, error){
      outputMessage('addBook error:'   status);
    }
  });
  
  readByUrl(url);
}

function unpackBook(book) {
  currentBook = book.bookdata;
  var links = book.links;
  for(var i = 0; i < links.length; i  )
  {
    parseLink(links, bookLinks);
  }
}

function renderBook(book) {
  $('#stockno').val(book.stockno);
  $('#isbn').val(book.isbn);
  $('#title').val(book.title);
  $('#author').val(book.author);
  $('#type').val(book.type);
  $('#price').val(book.price);
  $('#onhand').val(book.onhand);
  $('#sold').val(book.sold);
  $('#stockval').val(book.stockval);
}

function bookFromForm() {
  return JSON.stringify({
    "stockno": $('#stockno').val(),
    "isbn": $('#isbn').val(),
    "title": $('#title').val(),
    "author": $('#author').val(),
    "type": $('#type').val(),
    "price": $('#price').val(),
    "onhand": $('#onhand').val(),
    "sold": $('#sold').val()
    });
}

function outputMessage(msg) {
  //console.log("Message output:"   msg);
  $('#title').val(msg);
}
    

collections.js

var curCollectionURL = baseURL;

var collectionData;
var collectionLinks = {};
var COL_SELECTOR = '#collectionLinks';
var COL_LINK_CLASS = 'collectionLink';
var COL_LINK_SELECTOR = '.'   COL_LINK_CLASS;
var LINK_CLOSE_TAG = "</a>";
var LINE_BREAK="<br class=\""   COL_LINK_CLASS   "\"/>";

$('#btnNextCol').click(function() {
  curCollectionURL = collectionLinks.next;
  readCollection(curCollectionURL);
  
  return false;
});

$('#btnPrevCol').click(function() {
  curCollectionURL = collectionLinks.prev;
  readCollection(curCollectionURL);
  
  return false;
});

function initialiseCollections() {
  readCollection(curCollectionURL);
}

function readCollection(url) {
  $.ajax({
    type: 'GET',
    url: url,
    dataType: "json",
    success: function(collection, a, b){
      unpackCollection(collection);
      renderCollection(collectionData);
      readById(collectionData[0].bookdata.stockno);
    },
      error: function (e) {
        //console.log('readById error');
      }
  });
}

function unpackCollection(collection) {
  collectionData = collection.data;
  var links = collection.links;
  for(var i = 0; i < links.length; i  )
  {
    parseLink(links, collectionLinks);
  }
}

function renderCollection(collection) {
  clearCollection();
  var i = 0;
  
  for(i = 0; i < collection.length; i  )
  {
    var link = createCollectionLink(collection);
    var lineBreak = $(LINE_BREAK);
    link.appendTo(COL_SELECTOR);
    lineBreak.appendTo(COL_SELECTOR);
  }
  
  for(; i < 5; i  )
  {
    var lineBreak = $(LINE_BREAK);
    lineBreak.appendTo(COL_SELECTOR);
  }
  
  registerLinkEventHandlers();
}

function clearCollection() {
  $(COL_LINK_SELECTOR).remove();
}

function createCollectionLink(book) {
  var href = getLink("self", book.links);
  var content = createCollectionLinkContent(book.bookdata);
  var elementString = linkOpenTag(href)   content   LINK_CLOSE_TAG;
  
  return $(elementString);
}

function linkOpenTag(href) {
  return "<a href=\""   href  "\" class=\""   COL_LINK_CLASS  "\">";
}

function createCollectionLinkContent(book) {
  return book.stockno   ": "   book.title   " - "   book.author;
}

function registerLinkEventHandlers() {
  $(COL_LINK_SELECTOR).click(function() {
    readByUrl(this, function() {
      singleBookView();
    });
    return false;
  });
}
    
Comment List
Anonymous
  • Hi, Trying to get this example to work on my machine. I'm getting 2 errors:

    1)   Description Resource Path Location Type

    The public type BookId must be defined in its own file BookID.java /RestBook/src/com/microfocus/book/rest line 21 Java Problem

    2)Description Resource Path Location Type

    Content is not allowed in prolog. web.xml /RestBook/WebContent/WEB-INF line 2 XML Problem

    Any suggestions?

Related Discussions
Recommended