The CICS Dynamic Scripting Feature Pack (hereafter called the Feature Pack) provides a platform for the agile development and deployment of modern Web applications. The runtime environment supports Java, PHP, and Groovy, enabling you to access existing CICS assets using the JCICS API.
Dynamic scripting uses the REST architectural style to access collections. Certain CICS resources that contain data may be used in this manner, including TSQs, TDQs, and files. This article shows you how to implement a resource handler to access the data contained in a KSDS file. The approach used here can be easily modified so that the underlying collection could be another file or a queue.
This article assumes that you have previously installed the Feature Pack and are familiar with dynamic scripting applications. If this is not the case, you can Download the Feature Pack and install it, and visit the Project Zero Web site to become more familiar with dynamic scripting.
Before doing the exercises in this article, do the following steps:
- Download employeedemo.zip into the applications directory you created when installing the Feature Pack.
- Use the command
jar -xf employeedemo.zipto unzip the file to create an application folder. - Configure the application to run in your environment, changing the port and applid of the region where appropriate.
- Download and unzip the DEFKSDS.zip JCL file to define and populate the VSAM file that will be used by the application. You will need to customise this file in order to create the data set in the correct location.
- Define the file in the CICS region in which the application is to run. Ensure that it is given the name KSDS; and that the access attributes ADD, BROWSE, DELETE, READ, and UPDATE are all set to YES.
A resource handler is a script found in the app/resources folder of an application that is executed when the application receives an HTTP request. The four HTTP methods GET, PUT, POST, and DELETE map to one of five methods in the script. Each HTTP method can be used against a collection or an element of a collection. The URI specifies whether the method acts on an entire collection or on a member of that collection. If the URI ends with a key for an element of a collection, then the HTTP method acts on that element. If it ends with just the collection name, then the method acts on the collection. Table 1 below shows how the HTTP methods can be used on a collection and how they relate to the methods in the resource handler. This list is not an exhaustive, and is provided to illustrate the relationships for the methods implemented in the sample resource handler.
The example in this article has been written in a scripting language called Groovy, which was chosen because Java syntax is also valid Groovy syntax. Therefore the JCICS API can be called as if this were just another Java class. However, you can write a resource handler in PHP and access the JCICS API via the PHP-Java bridge.
Table 1. Mapping of HTTP methods and URIs to Groovy methods in the resource handler
| HTTP Method | URI | Groovy Method | Description |
|---|---|---|---|
| GET | /resources/messages | onList() | Lists all members of the collection |
| POST | /resources/messages | onCreate() | Creates a new member |
| GET | /resources/messages/1 | onRetrieve() | Retrieves one member |
| PUT | /resources/messages/1 | onUpdate() | Updates a member |
| DELETE | /resources/messages/1 | onDelete() | Deletes a member |
The example used in this article is the zero.employee.demo application, which you can download from the Project Zero sample applications page. The original application used a Derby database to store a list of employees, whereas the modified version will use a KSDS file. The file will simulate an existing CICS asset to illustrate how easy it is to use them in new dynamic scripting applications. The file used contains 80-byte records, with the username as a 19 byte key. The remainder of the record contains first name, last name, and phone number.
The first section of the resource handler is a class definition that defines the structure of the records in the file, and it makes it easier to work with the data retrieved from the file.
Listing 1. Definition of the record structure in the file
class employee {
private String username
private String firstname
private String lastname
private String phonenumber
public employee(String username, String firstname, String lastname,
String phonenumber) {
this.username = username
this.firstname = firstname
this.lastname = lastname
this.phonenumber = phonenumber
}
public String getUsername(){
return username
}
public String getFirstname(){
return firstname
}
public String getLastname(){
return lastname
}
public String getPhonenumber(){
return phonenumber
}
}
|
This definition consists of the fields that constitute a record, and a constructor that creates an object representation of each record. Additionally, there are some accessor methods that let you retrieve the attributes of the object.
The next step is to import the classes you will need to browse the file, plus a few other classes for convenience. Add the following import statements to the top of the script:
Listing 2. Imported Java classes in the resource handler
import com.ibm.cics.server.KSDS; import com.ibm.cics.server.RecordHolder; import com.ibm.cics.server.KeyHolder; import com.ibm.cics.server.KeyedFileBrowse; import com.ibm.cics.server.Task; import java.nio.ByteBuffer; import java.nio.charset.Charset; import com.ibm.cics.server.CicsConditionException; |
The first method to be implemented is onList(), which retrieves all records in the file, using the JCICS API to browse the file and read each record in turn. First, create an empty array called records to hold all of the records read from the file. Next, initiate a generic browse of the file, which returns a KeyedFileBrowse object
that you can use to read records from the file. Once the browse object is obtained, you execute a loop to read each record in turn.
As each record is read, a ByteBuffer is used to extract the individual fields from the record. In order for this to work the method requires the offset and length of each field. As each field is extracted, it is decoded to Unicode and stored in a string. Once all of the fields are retrieved, an employee object is created and stored in the records array.
Upon reaching end of the file, an end of file exception is thrown, which ends the execution of the loop.
The final three lines of the method use a view renderer to return the data to the output stream in the JavaScript Object Notation (JSON) format. The first of these lines sets the view renderer to the JSON type. The next line sets the object to be serialised to the output stream and the final line invokes the renderer:
Listing 3. The onList() method of the resource handler
def onList()
{
def records = []
KSDS KSDSFile = new KSDS();
KSDSFile.setName("KSDS");
String startKey = new String("");
KeyedFileBrowse browse = KSDSFile.startGenericBrowse(startKey.getBytes(),19);
RecordHolder record = new RecordHolder();
KeyHolder keyHolder = new KeyHolder();
Exception eof = null;
while(eof == null){
try {
browse.next(record, keyHolder);
Charset charset = Charset.forName("IBM1047");
// obtain each of the fields from within the file
ByteBuffer bb = ByteBuffer.wrap(record.value,0,19)
String username = charset.decode(bb).toString()
bb = ByteBuffer.wrap(record.value,19,20)
String firstname = charset.decode(bb).toString()
bb = ByteBuffer.wrap(record.value,39,20)
String lastname = charset.decode(bb).toString()
bb = ByteBuffer.wrap(record.value,59,20)
String phonenumber = charset.decode(bb).toString()
def employee = new employee(username, firstname, lastname,
phonenumber)
records.add(employee)
} catch(Exception e){
eof = e;
}
}
request.view="JSON"
request.json.output=records
render()
}
|
Retrieving just one member of the file is done by the onRetrieve() method. The first line of code obtains the key for the desired record from the URI. Next, a file object is created and the name given is the name used in the CICS resource definition. The format() method of the String class is used to pad the key with spaces to the required length.
Using the same method used previously a ByteBuffer is used to obtain the individual components of a record and store them in strings, ready for the
employee object to be created.
Once a record has been successfully read and an employee object created, a view renderer is used as with the onList() method, to return the single employee object in JSON format.
Should the JCICS call to read a record fail for any reason, a CicsConditionException is thrown and passed, along with an error message, to a method to log the error. This method is discussed in more detail below. The error handling in this resource handler is a rudimentary example that illustrates the information available via the JCICS API to make debugging applications easier.
After the error message has been processed an HTTP 500 Internal Server error response is returned to the caller to indicate that a problem occurred:
Listing 4. The onRetrieve() method of the resource handler
def onRetrieve()
{ // obtain the key passed in via the URI
String rec = request.params.myKSDSId[]
// create the file object
KSDS KSDSFile = new KSDS();
KSDSFile.setName("KSDS");
String paddedKey = String.format("%-" + 19 + "s", rec)
Charset charset = Charset.forName("IBM285");
ByteBuffer bb = ByteBuffer.wrap(paddedKey.getBytes(),0,19)
rec = charset.decode(bb).toString()
// read the record from the file
RecordHolder record = new RecordHolder();
try {
KSDSFile.read(paddedKey.toString().getBytes(),record);
// obtain each of the fields from within the file
bb = ByteBuffer.wrap(record.value,0,19)
String username = charset.decode(bb).toString()
bb = ByteBuffer.wrap(record.value,19,20)
String firstname = charset.decode(bb).toString()
bb = ByteBuffer.wrap(record.value,39,20)
String lastname = charset.decode(bb).toString()
bb = ByteBuffer.wrap(record.value,59,20)
String phonenumber = charset.decode(bb).toString()
// create and return the employee object
if(record != null) {
// Use ViewEngine JSON rendering
def employee = new employee(username.trim(), firstname.trim(),
lastname.trim(), phonenumber.trim())
request.view = 'JSON'
request.json.output = employee
render()
}
} catch(CicsConditionException e){
logError("ERROR in onRetrieve().", e)
request.status = 500;
request.view = 'JSON'
render()
}
}
|
To add a new member to a collection, use the HTTP POST method on the collection URI, which causes the execution of the onCreate() method in the resource handler.
This method uses the format() method in the String class to pad each of the fields to the required length with spaces.
Then a StringBuilder is used to build up the record, before writing it to the file:
Listing 5. The onCreate() method of the resource handler
def onCreate()
{
// Convert entity to JSON object
def record = zero.json.Json.decode(request.input[])
String username = record.username;
String firstname = record.firstname;
String lastname = record.lastname;
String phonenumber = record.phonenumber;
username = String.format("%-" + 19 + "s", username)
firstname = String.format("%-" + 20 + "s", firstname)
lastname = String.format("%-" + 20 + "s", lastname)
phonenumber = String.format("%-" + 20 + "s", phonenumber)
def emp = new employee(username, firstname, lastname, phonenumber)
// Use a string builder to build up the record
StringBuilder recordBuilder = new StringBuilder(79);
recordBuilder.insert(0, username);
recordBuilder.insert(19, firstname);
recordBuilder.insert(39, lastname);
recordBuilder.insert(59, phonenumber);
KSDS KSDSFile = new KSDS();
KSDSFile.setName("KSDS");
try{
KSDSFile.write(recordBuilder.substring(0,19).getBytes(),
recordBuilder.toString().getBytes());
request.status = 201;
request.view = 'JSON'
request.json.output = emp
render()
} catch(CicsConditionException e){
logError("ERROR in onCreate().", e)
request.status = 500;
request.view = 'JSON'
render()
}
}
|
To change an existing member, the key of the existing record is specified in the URI of the HTTP request using the PUT method. This causes the onUpdate() method of the resource handler to be executed. The key of the record is obtained in the same way as above in the onRetrieve() method. In the same way as a new record is created,
a StringBuilder is used to build up the record that will be written to the file.
Whilst this method is largely similar to the onCreate() method there are some differences. The rewrite() method is used to write the record to the file, instead of the write() method used previously. This takes an extra parameter that is the key of the record being written as well as a byte array of the data itself.
Additionally, before the rewrite() method can be used, the readForUpdate() method must be called to lock the record to allow it to be written to:
Listing 6. The onUpdate() method of the resource handler
def onUpdate()
{
// Convert entity to JSON object
def record = zero.json.Json.decode(request.input[])
String id = request.params.myKSDSId[]
String username = record.username;
String firstname = record.firstname;
String lastname = record.lastname;
String phonenumber = record.phonenumber;
username = String.format("%-" + 19 + "s", username)
firstname = String.format("%-" + 20 + "s", firstname)
lastname = String.format("%-" + 20 + "s", lastname)
phonenumber = String.format("%-" + 20 + "s", phonenumber)
def emp = new employee(username, firstname, lastname, phonenumber)
// Use a string builder to build up the record
StringBuilder recordBuilder = new StringBuilder(79);
recordBuilder.insert(0, username);
recordBuilder.insert(19, firstname);
recordBuilder.insert(39, lastname);
recordBuilder.insert(59, phonenumber);
KSDS KSDSFile = new KSDS();
KSDSFile.setName("KSDS");
try{
// Must perform a read for update before doing a rewrite
RecordHolder recordHolder = new RecordHolder();
KSDSFile.readForUpdate(recordBuilder.substring(0,19).getBytes(),
recordHolder);
// update the record in the file
KSDSFile.rewrite(recordBuilder.toString().getBytes());
request.status = 201;
request.view = 'JSON'
request.json.output = emp
render()
} catch(CicsConditionException e){
logError("ERROR in onUpdate().", e)
request.status = 500;
request.view = 'JSON'
render()
}
}
|
To delete a member from a collection, the HTTP DELETE method is used specifying the key of the record in the URI. This causes the onDelete() method in the resource handler to be executed. As in previous methods the key is obtained from the URI and then used in the delete() method on the KSDS file:
Listing 7. The onDelete() method of the resource handler
def onDelete()
{
String rec = request.params.myKSDSId[]
KSDS KSDSFile = new KSDS();
KSDSFile.setName("KSDS");
KSDSFile.delete(rec.getBytes());
request.status = HttpURLConnection.HTTP_NO_CONTENT
}
|
In this article all five methods have been implemented to illustrate the logic required, however only the methods required for an application need to be implemented. For example, a dynamic scripting application may be developed simply as a way to view the contents of files and to deny users of the application a way of modifying them. Another reason for omitting implementations of certain methods is where they would be illogical or invalid. For example, if the collection being manipulated is a TSQ, the onDelete() method would not be able to delete individual members of the collection.
If the application receives an HTTP request that would cause an unimplemented method to be executed, the request receives an HTTP 405 Method not allowed response.
The methods discussed previously used a method called logError() to report any errors that occurred during the JCICS calls. This method itself uses JCICS to obtain more information regarding the nature of the error.
Listing 8. The logError() method
def logError(String msg, CicsConditionException e)
{
String tranID = Task.getTask().getTransactionName();
int taskNo = Task.getTask().getTaskNumber();
System.err.println(msg + " " + e.getMessage() +
". Resp 2 value: " + e.getRESP2() + " TranID: " + tranID + " Task No: " + taskNo);
}
|
This method forms an error message that is printed to the error log for the JVM, located in the application directory. The message reported contains some useful details about the error.
Listing 9. Example of an error message from a failed onRetrieve()
ERROR in onRetrieve(). CICS NOTFND Condition. Resp 2 value: 80 TranID: CPIH Task No: 4598 |
The CICS condition that was thrown as a result of the error reported is included along with the resp 2 value. Additional information includes the transaction ID and the task number from which the error originated.
The application can be tested by opening it in a Web browser. As it is based on the employee demo, the user interface remains largely unchanged. Interaction with the application takes place via the four buttons below the data grid. Attempting to add or update the data causes a form to be displayed to allow new data to be input. To verify that the file is being modified it must first be closed in CICS. Once it is closed it is possible to view the contents via ISPF:
Figure 1. The user interface to the application
In this article you have written a resource handler that will provide RESTful access to a CICS resource, in this case a KSDS file. Using a similar approach, you can easily modify this resource handler to use other CICS resources, such as TSQs, TDQs, ESDS files, and RRDS files. As you investigate the various applications that can be created with the Dynamic Scripting Feature Pack, you will learn how powerful this functionality is and how quickly you can create a a Web interface to interact with existing CICS assets.
| Description | Name | Size | Download method |
|---|---|---|---|
| Code sample | employeedemo.zip | 72 KB | HTTP |
| Code sample | DEFKSDS.zip | 1 KB | HTTP |
Information about download methods
- CICS Dynamic Scripting Feature Pack: Download
Download the Feature Pack and get basic information about it. - CICS Dynamic Scripting Feature Pack: Information centre
Complete documentation from IBM. - Project Zero
The development community for WebSphere sMash -- Agile Web 2.0 with PHP scripting, REST, and Dojo. - Project Zero: Sample applications
Includes original Employee data sample. - Project Zero: Resource (REST) programming model and conventions
How to use WebSphere sMash to simplify the task of creating applications using the Representational State Transfer (REST) architectural style. - Javadoc documentation for Package com.ibm.cics.server
A useful reference when using the JCICS API. - JavaScript Object Notation (JSON)
Complete documentation from json.org. - Groovy scripting language
Complete documentation from groovy.codehaus.org/. - IBM CICS product page
Product descriptions, product news, training information, support information, and more. - IBM CICS Transaction Server for z/OS information center
A single Web portal to all WebSphere Adapters documentation, with conceptual, task, and reference information on installing, configuring, and using CICS Transaction Server for z/OS. - developerWorks WebSphere developer resources
Technical information and resources for developers who use WebSphere products. developerWorks WebSphere provides product downloads, how-to information, support resources, and a free technical library of more than 2000 technical articles, tutorials, best practices, IBM Redbooks, and online product manuals. - developerWorks WebSphere application connectivity developer resources
How-to articles, downloads, tutorials, education, product info, and other resources to help you build WebSphere application connectivity and business integration solutions. - Most popular WebSphere trial downloads
No-charge trial downloads for key WebSphere products. - WebSphere forums
Product-specific forums where you can get answers to your technical questions and share your expertise with other WebSphere users. - developerWorks WebSphere weekly newsletter
The developerWorks newsletter gives you the latest articles and information only on those topics that interest you. In addition to WebSphere, you can select from Java, Linux, Open source, Rational, SOA, Web services, and other topics. Subscribe now and design your custom mailing. - WebSphere-related books from IBM Press
Convenient online ordering through Barnes & Noble. - WebSphere-related events
Conferences, trade shows, Webcasts, and other events around the world of interest to WebSphere developers. - developerWorks blogs
Join a conversation with developerWorks users and authors, and IBM editors and developers. - developerWorks Webcasts
Free technical sessions by IBM experts that can accelerate your learning curve and help you succeed in your most difficult software projects. Sessions range from one-hour Webcasts to half-day and full-day live sessions in cities worldwide. - developerWorks podcasts
Listen to interesting and offbeat interviews and discussions with software innovators. - developerWorks on Twitter
Check out recent Twitter messages and URLs.






