The Java CICS API
In this chapter, we give you a gentle introduction to Java programming in a CICS environment.
First, we discuss the difference between traditional CICS programming and using CICS services from Java. Then, we briefly introduce the main functional areas of the API with an in-depth discussion of each. Also, we show and explain some example code to help you get started.
6.1 Introduction to JCICS
There is one fundamental difference between using the CICS API from traditional languages, such as COBOL, PL/I, and C, and using it from Java applications. In languages other than Java, CICS commands are implemented as a language extension, that is, they are embedded into the source program bracketed by EXEC CICS…END-EXEC clauses. Therefore, the source program cannot be compiled as it is; instead, you must first run it through the CICS translator, which extracts all CICS calls and replaces them by calls into the CICS language interface module, which produces a program that can be compiled by the respective language compiler. If you are familiar with database access using embedded SQL, you can see that this is pretty much the same concept.
Figure 6-1 shows the program preparation process for CICS applications by example of a COBOL program.
Figure 6-1 Preparing CICS programs in COBOL
In Java, however, things are much simpler. CICS commands are not implemented as a language extension but rather as a regular Java API (the JCICS API); therefore, no additional translation steps are necessary. In that sense, CICS programming in Java is no more difficult than using any other Java API.
6.2 A short overview of the JCICS API
In this section, we provide a high-level overview of the CICS API, briefly describing its various categories. Later in this chapter, we describe most of the API in more detail.
Figure 6-2 shows a class diagram of the JCICS API. Due to limited space, we only show some particularly important classes and show more detail in later sections.
Figure 6-2 Overview of the JCICS API
In the following sections, we briefly describe several functional areas of the API.
6.2.1 Program control
Program control services allow one program to call another program in the same unit of work. Data can be passed to and received back from the called program. Until recently this was achieved by means of a shared area of storage known as the COMMAREA, but in CICS TS Version 3.1, an additional mechanism known as channels and containers is available as an alternative to a COMMAREA.
6.2.2 File control
Using file control services you can access VSAM files, which come in three different flavors:
Key Sequenced Data Sets (KSDS)
Entry Sequenced Data Sets (ESDS)
Relative Record Data Sets (RRDS)
6.2.3 Synchronization
Synchronization services provide a mechanism to ensure mutual exclusion when dealing with shared resources that must not be modified (or even accessed) by more than one transaction at a time.
6.2.4 Scheduling services
Scheduling services allow a program to start other transactions asynchronously, either immediately or at some specified later time.
6.2.5 Unit of work
This is one of the easiest and probably most important JCICS services to use. You use these services to commit or roll back the current unit of work, which either makes the results of the current transaction permanent or backs them out.
There is no API to explicitly start a new unit of work. A new unit of work is considered active when the previous one ended.
To commit the current unit of work, use the Task.commit() method:
Task.getTask().commit();
To back out the current unit of work, use the Task.rollback() method:
Task.getTask().rollback();
6.2.6 Document services
Document services are most often used in conjunction with CICS Web support services (6.2.7, “Web and TCP/IP services” on page 93) to dynamically build HTML pages that are to be sent back to a Web browser.
You can work with document templates, which are portions of a document that are created offline (or by another program) and might contain symbols that represent dynamic content. You can then replace those symbols to produce the actual document to be sent back.
We show an example of using the Document API in Chapter 7, “Evolving a heritage application using Java” on page 135.
6.2.7 Web and TCP/IP services
Web services allow CICS programs to produce dynamic Web content, probably in response to a client request from a Web browser. In other words, they can act much like CGI programs that are supported by most Web servers or like Java servlets.
The JCICS API provides the following Web-related and TCP/IP-related services:
Examining an HTTP request
Sending a response back to the client
Getting the client’s host name
Security-related services, such as getting authorization method and certificates
We show how to use Web services in conjunction with Document services in Chapter 7, “Evolving a heritage application using Java” on page 135.
6.2.8 Transient storage queues
Temporary storage is the primary CICS facility for storing data that must be available to multiple transactions. Data items in temporary storage are kept in queues whose names are assigned dynamically by the program that stores the data. Think of a temporary storage queue that contains multiple items as a small data set whose records are addressed either sequentially or directly, by item number. If a queue contains only a single item, think of it as a named scratch-pad area.
6.2.9 Transient data queues
Transient data queues (TD queues) are similar in some respects to transient storage queues, but there are some major differences:
They cannot be created on-the-fly but must be predefined.
Items can only be read sequentially and cannot be changed.
Reading from TD queues is destructive, that is, each item can be read only once. After a transaction reads an item, that item is removed from the queue and is not available to any other transaction.
Today, TD queues are pretty much obsolete, because the preferred alternative is to use WebSphere MQ message queues. Therefore, we do not further discuss TD queues in this book. However, they are fully supported by the JCICS API.
6.2.10 Terminal control
Terminal control services allow an application program to interact with the user’s terminal by displaying data on and retrieving user input from the terminal.
However, terminal control in Java is not widely used because there is no JCICS support for an important feature called Basic Mapping Support (BMS). To put it simply, BMS is a set of CICS services and tools to create, display, and interact with panel forms from an application program. As you can imagine, BMS panels are much easier to use (and more portable) than direct interaction with the raw terminal.
During the course of this residency, we developed a small package called JBMS (for Java Basic Mapping Support), which allows you to create and interact with panel forms, just like the real BMS does. Read all about it in 6.13, “Terminal services” on page 125.
6.2.11 Miscellaneous services
In this section, we cover several services that do not fall into one of the categories above.
Specifically, you can:
Inquire about the system name and VTAM® application ID of the CICS region using Region.getSysId() and Region.getAPPLID(), respectively.
Enable and disable tracing in the CICS region: Region.enableTrace(), Region.disableTrace().
Examine and modify the contents of the Common Work Area (CWA), using Region.getCWA() and Region.setCWA().
Retrieve the transaction name and program name under which the current program is executing, which is useful for logging purposes.
Get the name of the user who started the transaction.
Examine and modify the Transaction Work Area (TWA). The TWA is a small (up to 32 K) area of storage that is allocated when a transaction is initiated and is initialized to binary zeros. It lasts for the entire duration of the transaction and is accessible to all local programs in the transaction. Using the TWA is no longer recommended for new applications.
Find out how the current program was started using Task.getTask().getSTARTCODE(). Table 6-1 lists the different possible startcodes and their meanings.
Table 6-1 Startcodes as returned by Task.getTask().getSTARTCODE()
Startcode
Meaning
D
The task was initiated to process a distributed programming link (DPL) command that did not specify the SYNCONRETURN option. (The task is not allowed to issue syncpoints.)
DS
The task was initiated to process a distributed programming link (DPL) command containing the SYNCONRETURN option. (The task is allowed to issue syncpoints.)
QD
CICS initiated the task to process a transient data queue that reached trigger level.
S
Another task initiated this one using a START command that did not pass data in the FROM option.
SD
Another task initiated this one using a START command that passed data in the FROM option.
SZ
The task was initiated with a FEPI START command (see CICS Front End Programming Interface User’s Guide, SC34-6234, for further information).
TO
The task was initiated to process unsolicited input from a terminal (or another system), and the transaction to be executed was determined from the input.
TP
The task was initiated to process unsolicited input or in response to a RETURN IMMEDIATE command in another task. In either case, the transaction to be executed was preset (in the RETURN command or in the associated TERMINAL definition) without reference to input.
U
CICS created the task internally.
6.2.12 Services that the JCICS API does not support
JCICS does not support these CICS services:
APPC unmapped conversations
CICS Business Transaction Services
DUMP services
Journal services
Storage services
Timer services
BMS services SEND MAP and RECEIVE MAP
6.3 JCICS basics
Using JCICS application programming classes, you can invoke CICS services from Java. To invoke a CICS service, simply call a Java method. The methods that JCICS provides use Java exception classes to return error messages to the caller. The exception handling mechanism is internally based on the CICS API response codes, which are set after execution of the CICS commands.
CICS resources, such as files, programs, temporary storage queues, and transient data queues, are represented by instances of the appropriate Java class. Therefore, to work with, for example, a temporary storage queue (TSQ), you first create an instance of a TSQ object. Then assign it a name that corresponds to the name of the TSQ that you want to use, and use its methods to manipulate the queue:
TSQ tsq = new TSQ();
tsq.setName(“MYTSQ”);
tsq.writeItem("Hello World!".getBytes());
Often, you might want to initialize the JCICS objects that your program works within a constructor, and use the initialized objects from regular methods. In Example 6-1 (an excerpt from 6.8, “Using transient storage queues” on page 109), we declare a TSQ to be a blank final instance variable. The final keyword indicates that the variable must be initialized in the constructor and cannot be reassigned after it is initialized.
Example 6-1 Declaring a TSQ to be a blank final instance variable
public class Scratchpad {
 
  public final TSQ tsq;
 
  public Scratchpad(String tsqName) {
this.tsq = new TSQ();
tsq.setName(tsqName);
}
 
  public void writeBytes(byte[] bytes) throws ItemErrorException, ... {
    try {
  tsq.rewriteItem(1, bytes);
  } catch (InvalidQueueIdException e) {
      tsq.writeItem(bytes);
    }
  }
  ...
}
 
The JCICS library structure and naming conventions are modelled to be fairly close to the traditional EXEC CICS programming interface, so translating the CICS calls in a traditional CICS program to JCICS calls in a Java application is fairly easy. The downside is that the JCICS way of doing things often does not quite “feel right” to a seasoned Java programmer, for example, to iterate over a set of related resources, CICS has the concept of browsing, which is similar to the Java idiom of using an Iterator; however, the relevant JCICS classes do not implement the Iterator interface; instead, they stand entirely on their own.
 
Note: All JCICS classes that represent CICS resources are designed to comply with the JavaBeans standard, so you can use them with a visual composition editor.
6.4 Input and output streams
Similar to any standalone Java application, JCICS applications can use the predefined streams: System.in to read input and System.out and System.err to print output.
Additionally, the Task class has two fields, out and err, which are directed at the user’s terminal if the transaction is started from a terminal (in CICS parlance, if the principal facility associated with the transaction is a terminal). Otherwise, they are the same as System.out and System.err, respectively.
However, the standard input stream (System.in) is never connected to the terminal. Rather, it is associated with an HFS or zFS file whose name is set up in the JVM profile. In other words, it is not interactive in the sense that you can read user input from it. So, there is probably little use for the System.in stream, except maybe for reading configuration information at program startup.
System.out and System.err are connected to HFS or zFS files as well. Those files are created (or scratched) when the JVM starts up. For more information, see 8.3.4, “JVM stdout and stderr”.
The terminal might change each time that the JVM is reused, although System.in, System.out, and System.err remain connected to their respective HFS or zFS files.
6.5 Exception handling
Because anything that can go wrong eventually does go wrong, you must handle error conditions in your code.
As usual, error reporting and handling in JCICS is integrated into the standard Java exception handling mechanism.
In traditional languages, CICS indicates the success or failure of a CICS command by returning a condition code to your application program (sometimes called the RESP value because you use the RESP keyword to retrieve it). If everything went fine, the RESP value is NORMAL. If the RESP value is some value other than NORMAL, you can test the value and obtain what happened. Many RESP values also have an associated RESP2 value, which gives further detail about the error.
In Java, RESP codes are mapped to Java exception classes. For each RESP value that can occur in CICS, there is one corresponding Java exception class.
Figure 6-3 on page 97 shows part of the JCICS exception hierarchy.
Figure 6-3 Part of the JCICS exception hierarchy
In Java, exceptions fall into two major categories: Checked exceptions and unchecked exceptions. When you call a method that might throw a checked exception, you are required to either handle the exception in your method or to declare your own method as throwing that exception. Unchecked exceptions, on the other hand, need not necessarily be handled or declared because they usually represent conditions that an application program has difficulty recovering from, and so it is impractical to be forced to handle them.
Example 6-2 on page 98 shows a first example of exception handling in JCICS. The sample program tries to count the number of items in a transient storage queue (we discuss transient storage queues in 6.8, “Using transient storage queues” on page 109). Because there is no straightforward method in the CICS API, we must do this by reading the items in order until CICS responds with an error after trying to read past the last item.
Example 6-2 Exception handling example, first version
package com.ibm.itso.sg245275;
 
import java.io.PrintWriter;
 
import com.ibm.cics.server.*;
 
public class ExceptionExample {
 
  /** Count the number of items in a TS queue. */
private static int countItems(TSQ tsq) throws InvalidRequestException,
IOErrorException, LengthErrorException, InvalidSystemIdException,
ISCInvalidRequestException, NotAuthorisedException,
InvalidQueueIdException // (1)
{
int count = 0;
ItemHolder item = new ItemHolder();
try {
while (true) { // (2)
tsq.readNextItem(item);
count++; // (3)
}
} catch (ItemErrorException e) { // (4)
return count;
}
}
 
public static void main(String[] args) {
TSQ tsq = new TSQ(); // (5)
tsq.setName("MYTSQ");
    PrintWriter out = Task.getTask().out;
try {
out.println("Number of items in TSQ " + tsq.getName() // (6)
                + ": " + countItems(tsq));
} catch (InvalidRequestException e) { // (7)
out.println("Invalid request");
} catch (IOErrorException e) {
out.println("I/O error");
} catch (LengthErrorException e) {
out.println("Length error");
} catch (InvalidSystemIdException e) {
out.println("Invalid system id");
} catch (ISCInvalidRequestException e) {
out.println("Inter-system invalid request");
} catch (NotAuthorisedException e) {
out.println("Not authorized to access queue");
} catch (InvalidQueueIdException e) {
out.println("Invalid queue ID");
}
}
}
Notes on Example 6-2:
The countItems() method expects a parameter of type TSQ, and declares that it might throw several different exceptions — namely, all exceptions that can be thrown by invoking the TSQ.readNextItem() method except for the one we handle ourselves.
In a semi-infinite loop, we try to read the items in the transient storage queue. The loop is not really infinite because it is eventually terminated when no more items are left in the queue.
At this point, the call to TSQ.readNextItem() succeeded, and we increment the number of items read.
When there are no more items left in the queue, JCICS raises an ItemErrorException. The loop is terminated and control flow reaches the catch block. We return the number of items read so far.
If any other exception occurred, however, we do not catch it in the countItems() method; rather, the calling method must handle it, which is why we listed all possible exception classes in the method declaration, except ItemErrorException, which is handled in the countItems() method itself.
The main method creates a TSQ object and sets the queue name.
… and calls countItems() to print the number of items in the queue.
Because the countItems() method declares several checked exceptions, we either must handle them or declare them to be thrown from the main() method. In Example 6-2 on page 98, we ‘handle’ them by listing each possible exception in a catch block and printing a short error message corresponding to the exception type. Obviously, in production code we want to do much more to report the error properly and take action to deal with the problem.
Note that we do not have to catch an ItemErrorException because it is already handled in the countItems() method. In fact, if we try to handle it, we get a compilation error.
Now, if we have a closer look at the long list of catch clauses in the main method of Example 6-2 on page 98, we see that several exceptions cannot actually be thrown., for example, an InvalidSystemIdException or ISCInvalidRequestException can only occur when dealing with remote TS queues (that is, they live in a different CICS region). The queue we use in this example, however, is local (we never invoked the setSysId() method).
Therefore, in a second version of the example, Example 6-3, we chose to specifically handle only the exceptions that are somewhat likely to occur and handle all others in a generic way, rendering them unexpected.
Example 6-3 Exception handling example, second version
public static void main(String[] args) {
TSQ tsq = new TSQ();
tsq.setName("MYTSQ");
createItems(tsq);
PrintWriter out = Task.getTask().out;
try {
out.println("Number of items in TSQ " + tsq.getName() + ": " + countItems(tsq));
} catch (NotAuthorisedException e) {
out.println("Not authorized to access queue"); // (1)
} catch (InvalidQueueIdException e) {
out.println("Invalid queue ID"); // (2)
} catch (CicsException e) {
out.println("Unexpected CICS exception: " + e); // (3)
}
}
In this version of the example, we chose to only handle NotAuthorisedException.
… and InvalidQueueIdException.
This catch clause handles all other exceptions. Note that order is important: A compilation error is displayed if a catch clause for a more generic exception textually appears before a catch clause for a more specific one (that is, for one of its subclasses).
All checked exceptions that the JCICS API throws are subclasses of CicsException, so this clause handles all of them except the two that we chose to handle specifically.
Exception handling in CICS (or generally in Java, for that matter) is seemingly easy but still often done wrong. It is important that your code does “the right thing,” not only if everything works smoothly, but also in case of failure. In Example 6-4, our intention is to protect a shared resource against concurrent updates using the NameResource mechanism (more about NameResource in 6.2.3, “Synchronization” on page 92). Basically, a NameResource is a global flag that indicates whether some resource is in use.
The code in Example 6-4 looks simple enough: Acquire the lock on the shared resource, perform the update, and release the lock. If an error occurs (either when trying to acquire or release the lock, or when updating), an error message is logged.
Example 6-4 Incorrect exception handling  - Resources held
// INCORRECT EXCEPTION HANDLING
 
private void doUpdate() throws CicsException {
// ... code omitted
}
 
private void updateSharedData() throws ResourceUnavailableException, LengthErrorException {
NameResource lock = new NameResource();
lock.setName("SG245275.LOCK");
try {
lock.enqueue(); // Lock the shared resource.
doUpdate(); // Perform the update.
lock.dequeue(); // Release the lock.
} catch (CicsException e) {
logError("Update failed: " + e);
}
}
However, there is one serious flaw in the code: What happens when the lock is acquired successfully, but the update failed? Control is passed to the catch block, the lock.dequeue() call is never executed, and the program still holds the lock. Obviously, that is a bad thing because other instances of the application might want to access the protected resource as well. Of course, this problem is easy enough to fix, you say. Just release the lock in the catch block as well, as shown in Example 6-5.
Example 6-5 Still incorrect exception handling
try {
lock.enqueue(); // Lock the shared resource.
doUpdate(); // Perform the update.
lock.dequeue(); // Release the lock.
} catch (CicsException e) {
logError("Update failed: " + e);
    lock.dequeue(); // Release the lock.
}
But that is not much better. First, we have duplicate code, and second, what if the call to logError() fails? We still have the same problem. Swapping the two lines in the catch block is not much better either because the dequeue() call might fail, and the error is never logged.
The proper way to do it is to use the try-catch-finally mechanism. Code in a finally block always gets control, no matter if the corresponding try block completed successfully or raised an exception. Example 6-6 shows how this is done.
Example 6-6 Correct exception handling using try/catch/finally
private void doUpdate() throws CicsException {
// ... code omitted
}
 
private void updateSharedData() throws ResourceUnavailableException, LengthErrorException {
NameResource lock = new NameResource();
lock.setName("SG245275.LOCK");
lock.enqueue(); // Lock the shared resource.
try {
doUpdate(); // Perform the update.
} catch (CicsException e) {
logError("Update failed: " + e);
} finally {
lock.dequeue(); // Release the lock.
}
}
In Example 6-6, the call to lock.dequeue() is in a finally block, and is therefore executed regardless if the doUpdate() call or the logError() call succeeded or failed, which ensures that the lock is released in any event. Also, we no longer have duplicate code.
6.6 Calling other programs and passing data
In traditional CICS programming, it is common to split an application into several programs. Each program handles a specific part of the application, for example, you can have one front-end program that handles user interaction and one or more subprograms that handle the business logic, such as retrieving data from and inserting data into a database or VSAM file. The front-end program displays menus and data entry forms (maps in CICS parlance), possibly also for input verification, and calls one of the business logic subprograms, depending on the user’s input.
To call another program, you use the CICS LINK mechanism. A LINK causes the invoking program to be temporarily suspended and the linked-to program to be invoked. When the linked-to program returns, control is given back to the original program. The invoked program is still part of the same unit of work. Therefore, any work that is done to recoverable resources by both programs is backed out if the task does not complete successfully. In other words, a CICS LINK works just like a subroutine call except that the calling and called programs are not statically linked together.
To pass control to another program, use the XCTL (transfer control) command. The program issuing an XCTL does not receive control back when the target program terminates; rather, the program that is one level up in the call hierarchy does. See Figure 6-4 on page 102 for an illustration of the program control flow with LINK and XCTL.
Figure 6-4 Program control flow with CICS LINK and XCTL
Of course, in most cases, the linking program must communicate with the linked-to program, for example, to pass in a function code that tells it what specific function to perform and to get results back from it. In CICS, this is done either using the COMMAREA mechanism or using the newer Container mechanism. A COMMAREA is simply a storage area that is owned by the linking program and is made available to the linked-to program. Containers are similar, but they offer advantages over COMMAREAs in certain circumstances, particularly if larger quantities of data must be passed.
In CICS Java applications, separation of logic into several distinct subprograms is not that common, primarily because the Java language lends itself well enough to separate different aspects of the application into different parts of the code, namely, into different classes. Calling separate programs using LINK or XCTL is also more expensive than internal calls between methods in Java programs.
In practice, however, you probably must call existing programs from your Java code, for example, if you want to access heritage modules that are too expensive to be reimplemented using Java.
6.6.1 Calling other programs using LINK and XCTL
The Java equivalents to EXEC CICS LINK and EXEC CICS XCTL are the methods Program.link() and Program.xctl(), respectively. There are several versions of each method, taking different parameters, depending on whether and how you want to pass a COMMAREA to the called program. So, to set up a LINK (or XCTL) to another program, you create an instance of class Program, call its setName() method to supply the name of the program you want to transfer control to, and call link() or xctl() as appropriate.
The usual pattern is to create and initialize the Program object in a constructor so that it can be used from your individual methods, as shown in Example 6-7.
Example 6-7 Typical usage of Program.link()
public class LinkDemo {
 
private final Program okk850;
public LinkDemo() {
okk850 = new Program();
okk850.setName("OKK850");
}
public void callOKK850() throws InvalidRequestException, LengthErrorException,     InvalidSystemIdException, NotAuthorisedException, InvalidProgramIdException,     RolledBackException, TerminalException
  {
okk850.link();
}
  ...
}
6.6.2 Passing data between programs
To pass data between a calling program and a called program, CICS programs typically use the COMMAREA mechanism. Since CICS Version 3.1, they have the alternative of using the Container mechanism. These two mechanisms have much in common, so we provide an overview of both first and then a more detailed discussion of each follows.
A COMMAREA is nothing more than an area of storage that is owned by the calling program and is made available to the callee. A COMMAREA is limited to a maximum of 32 kB of storage, while Containers do not have this restriction.
Containers are named blocks of data that are designed to pass information between programs. You can think of them as “named COMMAREAs”. Programs can pass any number of containers between each other and can pass a theoretically unlimited amount of data. Containers are grouped together in sets called channels. A channel is analogous to a parameter list.
CICS does not care about the format or layout of the COMMAREA  or Container, which is entirely up to the two programs that are involved. Typically, the format is defined in a host language data structure, such as a COBOL Level-01 record or a C struct.
In JCICS, there are six different varieties of the Program.link() method:
Program.link()
Performs a link without a COMMAREA.
Program.link(byte[] commarea)
Performs a link with a COMMAREA. The linked-to program can modify the COMMAREA.
Program.link(byte[] commarea, int datalength)
Performs a link with a COMMAREA, where only the first datalength bytes are passed to the linked-to program. The linked-to program can modify the entire COMMAREA, even if it is longer than datalength bytes.
Program.link(Channel chan)
Performs a link to the program that is passing a CHANNEL.
Program.link(IByteBuffer commarea)
Program.link(IByteBuffer in, IByteBuffer out)
These methods were retained for compatibility only. They were supplied for integration with the IBM Record Framework library, which came with VisualAge® for Java. Because the Record Framework was not carried over to Rational Application Developer, they can be considered deprecated.
 
Tip: If you encapsulate the mapping of data fields to a byte array into a single Java class, with getters and setters for all of the fields, you can use this class in both the sending and receiving programs to reduce the risk of the two programs getting out of sync with each other. This is similar to using a shared copybook in communicating COBOL programs.
6.6.3 Communicating using the COMMAREA
We demonstrate the COMMAREA mechanism with a simple program that just reverts the COMMAREA being passed, as shown in Example 6-8.
Example 6-8 Program to revert the passed COMMAREA
package com.ibm.itso.sg245275;
 
import com.ibm.cics.server.CommAreaHolder;
 
public class Revert {
 
public static void main(CommAreaHolder cah) {
byte[] data = cah.value;
int n = data.length - 1;
for (int i = (n - 1) / 2; i >= 0; --i) {
byte temp = data[i];
data[i] = data[n - i];
data[n - i] = temp;
}
}
}
To test the program, we write another little program, Example 6-9 on page 105, which invokes it using Program.link().
Example 6-9 Linking to the Revert program
public class RevertTest {
private static String revert(String s) throws CicsException {
Program revert = new Program();
revert.setName("REVERT");
byte[] bytes = s.getBytes();
revert.link(bytes);
return new String(bytes);
}
 
public static void main(CommAreaHolder cah) {
PrintWriter out = Task.getTask().out;
try {
String original = "Hello World!";
String reverted = revert(original);
out.println();
out.println("Original: " + original);
out.println("Reverted: " + reverted);
} catch (CicsException ex) {
out.println("Oops: " + ex);
}
}
}
Alternatively, you can use the CECI transaction to test it, as shown in Example 6-10.
Example 6-10 Testing the Revert program using CECI
link program(REVERT) commarea('The quick brown fox')
STATUS: COMMAND EXECUTION COMPLETE NAME=
EXEC CICS LInk Program( 'REVERT ' )
< Commarea( 'xof nworb kciuq ehT' ) < Length( +00019 ) > < Datalength() > >
< SYSid() >
< SYNconreturn >
< Transid() >
< INPUTMSG() < INPUTMSGLen() > >
 
RESPONSE: NORMAL EIBRESP=+0000000000 EIBRESP2=+0000000000
PF 1 HELP 2 HEX 3 END 4 EIB 5 VAR 6 USER 7 SBH 8 SFH 9 MSG 10 SB 11 SF
 
6.6.4 Communicating through Channels and Containers
We demonstrate the use of Channels and containers by changing the COMMAREA example program to use channels and containers instead of a COMMAREA, as shown in Example 6-11.
Example 6-11 Program to revert the data passed in a container
public class Revert2 {
 
public static void main(CommAreaHolder cah) {
Task task = Task.getTask();
Channel channel = task.getCurrentChannel(); // (1)
if (channel != null) {
try {
Container record = channel.getContainer("Revert_Record"); // (2)
byte[] data = record.get(); // (3)
 
int n = data.length - 1;
for (int i = (n - 1) / 2; i >= 0; --i) {
byte temp = data[i];
data[i] = data[n - i];
data[n - i] = temp;
}
record.put(data); // (4)
}
catch (ContainerErrorException e) { // (5)
System.out.println("Container Error Exception "+
"- probably container not found");
e.printStackTrace();
}
catch (ChannelErrorException e) {
System.out.println("Channel Error Exception "+
"- probably invalid channel name or write to a read-only channel");
e.printStackTrace();
}
catch (InvalidRequestException e) {
System.out.println("CICS INVREQ condition "+
" - Something has gone badly wrong!");
e.printStackTrace();
}
catch (CCSIDErrorException e) {
System.out.println("CICS CCSIDERR condition "+
" - Code page conversion problem");
e.printStackTrace();
}
}
else {
System.out.println("There is no Current Channel");
}
}
}
There are several differences between this program and the program that uses a COMMAREA:
1. We first must get the channel from the task, which allows us to get to the data that is passed to this program.
2. After we have the channel, we can get the container that has the data that was passed to the program. In a more complex program, we can retrieve multiple containers from the channel and process each accordingly.
3. As with a COMMAREA, we can get the data as a byte array and process it in the same manner.
4. For simplicity, we re-used the container to return the data to the calling program, but it is advisable to use a new container to return the data.
5. There is a selection of exceptions that can be thrown when you work with containers. These exceptions map closely to the CICS return conditions that can occur when you use channels and containers in other languages. This program attempts to provide some indication of what the problem was that caused the exception, but it does not attempt to recover from the problem. In a more sophisticated program, you want to do more to recover from these exceptions. It is also possible to catch most or all of the exceptions with a more generic catch block, such as catch ‘CICSException’, but this reduces the opportunity to deal with different types of exceptions appropriately.
Once again, we can test the program with a small program, shown in Example 6-12, which is similar to the previous one and also uses Program.link() but this time passing a container.
Example 6-12  
public class Revert2Test {
private static String revert(String s) throws CicsException {
Program revert2 = new Program();
revert2.setName("REVERT2");
Task task = Task.getTask();
Channel data = task.createChannel("Revert_Data"); // (1)
Container record = data.createContainer("Revert_Record"); // (2)
record.put(s); // (3)
revert2.link(data); // (4)
byte[] bytes = record.get();
return new String(bytes);
}
 
public static void main(CommAreaHolder cah) {
PrintWriter out = Task.getTask().out;
try {
String original = "Hello World!";
String reverted = revert(original);
out.println();
out.println("Original: " + original);
out.println("Reverted: " + reverted);
} catch (CicsException ex) {
out.println("Oops: " + ex);
}
}
}
Again, there are differences between this program and the one using a COMMAREA:
1. We create a channel, and give it a name.
2. After we have a channel, we can create a container to hold the data that we want to pass to the program that is being called. A more complex program might create multiple containers on a single channel to be passed to a target program.
3. The data can be placed in the container either as a byte array or as a String. If a String is passed to the container, it is automatically converted to a byte array using the default encoding in the JVM.
4. The call to the other program is passed the reference to the channel that owns the container.
You can run this program in exactly the same way that you ran the previous test program, and the output is exactly the same.
6.6.5 COMMAREAs versus channels and containers
Channels and containers offer many benefits over COMMAREAs, especially where large (greater than 32kB) quantities of data must be passed. A full discussion of these advantages, and how best to make use of them would fill an entire book, and fortunately, that book already exists in the form of the existing IBM Redbooks publication, CICS Transaction Server V3R1 Channels and Containers Revealed, SG24-7227-00.
As a simple rule-of-thumb, the following rules can help:
Where the application must pass large quantities of data, channels and containers are easily the best choice.
For existing applications that use COMMAREAs, and pass small quantities of data, the effort of converting the program might not be justified.
For new applications, channels and containers are typically the default choice.
Obviously, for the full benefit of channels and containers to be realized, effort must be applied in their correct usage in the application and in appropriate system configuration. CICS Transaction Server V3R1 Channels and Containers Revealed, SG24-7227-00 offers considerable guidance in both these areas.
6.7 Remoteable resources
Most CICS resources, such as files, TS queues, and programs, need not actually reside in the CICS region from which they are accessed, but can live in another region connected to it. Resources can either be set up in a CICS region to be remote, in which case the program using the resource does not even know that the resource is owned by a different region, or a program can explicitly access a resource in another region.
In JCICS, remoteable resources are subclasses of the abstract class RemoteableResource (see Figure 6-5 on page 109). This class has two methods, setSysId() and getSysId(), to respectively set and retrieve the name of the region that owns the resource. All of the JCICS resource classes, except WebService, extend RemoteableResource and so can be considered to be Remoteable.
The region accessing the resource and the region owning the resource must be set up to “know each other” in the network. We do not explain the details of setting up a connection between CICS regions in this publication; however, CICS Intercommunication Guide, SC34-6243 has the details, or ask your CICS system programmer.
Example 6-13 demonstrates how to start a new transaction in a different CICS region, using the StartRequest class, which we briefly discussed in 6.12, “Interval control” on page 124.
Example 6-13 Starting a transaction in another CICS region
StartRequest startRequest = new StartRequest();
startRequest.setName("TRN2"); // Name of transaction to be started
try {
  startRequest.setSysId("PJA6"); // System ID of remote CICS region
  startRequest.issue(); // Issue request
} catch (CicsException e) {
  log("Error starting remote transaction: " + e);
}
Figure 6-5 The Resource class hierarchy
6.8 Using transient storage queues
Transient storage queues (TS queues for short) are a simple mechanism for interprocess communication in CICS. Basically, a TS queue is simply an array of numbered slots or items. You can either add a new item to a queue or replace an existing item that is identified by its slot number. The maximum number of items is 32767, and each item can hold up to 32763 bytes of data.
One additional benefit of TS queues, other than being easy to use, is that they can be created dynamically. In other words, no additional set up is necessary in CICS to create a TS queue — if it did not exist before, it is automatically created when a new item is added to it. A TS queue might also be predefined if the queue is to have special characteristics, such as security, recoverability, or is remote.
A common use of a TS queue is a scratchpad functionality, which is  a shared storage area that is available to all instances of an application running in the CICS region. In this section, we develop a simple Scratchpad class to demonstrate usage of TS queues with JCICS.
Example 6-14 shows a skeleton version of our Scratchpad class, just having a reference to the TS queue to use, and three constructors.
Example 6-14 Skeleton Scratchpad class
package com.ibm.itso.sg245275;
 
// Import declarations omitted...
 
/**
*
* Demonstrates how to use TS queues as a scratchpad.
*
* @author Ulrich Gehlert
*/
public class Scratchpad {
 
/** Default TS queue name. */
public static final String DEFAULT_SCRATCHPAD_TSQ_NAME = "SCRATCH"; // (1)
 
/** The TS queue we are writing to. */
private final TSQ tsq; // (2)
 
/** Constructor using the default TS queue name. */
public Scratchpad() { // (3)
this(DEFAULT_SCRATCHPAD_TSQ_NAME);
}
 
  /** Constructor using an explicit TS queue name. */
public Scratchpad(String tsqName) { // (4)
this(new TSQ());
tsq.setName(tsqName);
}
 
/** Constructor using an explicit TS queue (which may be remote). */
public Scratchpad(TSQ tsq) { // (5)
this.tsq = tsq;
}
}
Notes on Example 6-14:
This is the default queue name if the user of the class chose to use the no-argument constructor.
The TS queue where the data goes to. The variable is declared final, so it must be initialized, either directly or indirectly, by each constructor.
The no-argument constructor sets up a TS queue with the default name.
Constructor taking an explicit TS queue name.
Constructor taking an initialized TS queue object. Callers use this constructor if they want to write to a remote TS queue, for example, because they want the scratchpad to be available across CICS regions.
Next, we implement methods for writing data into and getting data from the scratchpad, as shown in Example 6-15 and Example 6-16, respectively.
Example 6-15 Writing data to the scratchpad
/**
* Write a byte array into the scratchpad queue.
*
* @param bytes The byte array to be written.
*/
public void writeBytes(byte[] bytes) throws ItemErrorException, InvalidRequestException,     IOErrorException, LengthErrorException,
InvalidSystemIdException, ISCInvalidRequestException, NotAuthorisedException,     InvalidQueueIdException
{
try {
tsq.rewriteItem(1, bytes); // (1)
} catch (InvalidQueueIdException e) {
// The queue didn't exist -- add item to queue, thus creating the queue
tsq.writeItem(bytes); // (2)
}
}
 
/**
* Write a string to the scratchpad.
*
* @param s The string to be written to the scratchpad.
*/
public void writeString(String s) throws InvalidRequestException, IOErrorException,     LengthErrorException,
InvalidSystemIdException, ItemErrorException, ISCInvalidRequestException,     NotAuthorisedException, InvalidQueueIdException
{
writeBytes(s.getBytes()); // (3)
}
Notes on Example 6-15:
In Example 6-15, we never write into any TS queue slots other than the first one. Note that the slot numbers count from 1, not from 0.
If we got an InvalidQueueIdException when trying to write to the TS queue, it did not exist. So, we use the writeItem() method, which causes the queue to be created.
There is a possible race condition: Another task can create the queue right between our failed call to rewriteItem() and our call to writeItem(). We can solve the problem by protecting this section of code with a NameResource. For the sake of brevity, we ignored that possibility.
This convenience method allows us to write strings into the queue by converting a String to a byte[] and writing that byte array to the queue. We implement a corresponding readString() method to read a String back. Of course, users of the Scratchpad class must agree upon what kind of data to put on the scratchpad.
Example 6-16 shows an example of reading data from the scratchpad.
Example 6-16 Reading data from the scratchpad
/**
* Read a byte array from the scratchpad.
*
* @return The byte array on the scratchpad, or <code>null</code> if the
* scratchpad is empty.
*/
public byte[] readBytes() throws InvalidRequestException, IOErrorException,     LengthErrorException, InvalidSystemIdException,
ISCInvalidRequestException, NotAuthorisedException
Writing data {
try {
ItemHolder item = new ItemHolder();
tsq.readItem(1, item);
return item.value;
} catch (InvalidQueueIdException e) {
return null;
} catch (ItemErrorException e) {
return null;
}
}
 
/**
* Read a string from the scratchpad.
*
* @return The string currently on the scratchpad.
*/
public String readString() throws InvalidRequestException, IOErrorException,     LengthErrorException, InvalidSystemIdException,
ISCInvalidRequestException, NotAuthorisedException, InvalidQueueIdException
{
byte[] bytes = readBytes();
if (bytes == null)
return null;
else
return new String(bytes);
}
6.9 Performing serialization
When you want to make sure that a given resource cannot be accessed by more than one task at a time because you need some form of serialization mechanism.
A resource, in this context, can be a physical resource, such as a TS queue or a file, or it might be a virtual resource, such as a credit card number.
A well-known solution to ensure mutual exclusion is the semaphore mechanism, which in z/OS and CICS is referred to as ENQ/DEQ. A semaphore is simply an integer variable (a counter) with an associated queue. When a task issues an ENQ for a given resource, other tasks that try to ENQ on the resource are suspended until the first task releases the resource using a DEQ operation.
A resource in the context of this command is any string of one through 255 bytes, established by in-house standards, to protect against conflicting actions between tasks or to cause single threading within a program. Therefore if you enqueue on the name of a file, another task attempting the enqueue() call on the same file name is unsuccessful, which does not stop some other task from using the file. It means that the enqueue request is unsuccessful if another task attempts an enqueue on the same character string, which in this case happens to be the same as the string of characters that make up the name of the file. If more than one enqueue() call is issued for the same resource by a given task, the resource remains owned by that task until the task issues a matching number of dequeue() calls, finishes its current unit of work, or terminates.
You can enqueue by string or by address. The JCICS classes provide an AddressResource class and a NameResource class, as shown in see Figure 6-6.
Figure 6-6 JCICS synchronization support classes
The AddressResource class is supplied for compatibility with other languages that use an enqueue on address (or, in general, on any resource name that is binary). The address that is used must be passed to your Java program through a COMMAREA, TSQ, or other shared area. What you actually supply to the AddressResource class is a byte array.
The preferable enqueue method is the enqueue by (human-readable) name, that is, by string. From your Java program, you create a NameResource object and use the setName() method to supply a 1–255 character string. Then issue the enqueue() method.
An enqueue is held only until the current unit of work finishes, that is, it is automatically released when you call Task.getTask().commit() or Task.getTask().rollback(). Also, it is released if a task ends abnormally.
 
Restriction: The EXEC CICS ENQ command has an option that allows a task to hold an enqueue across unit-of-work boundaries (ENQ RESOURCE(…) TASK). Unfortunately, there is no corresponding option, or parameter, in the JCICS API.
In z/OS, each ENQ has a given scope that determines the visibility of the ENQ. An ENQ might be visible:
At local scope, that is, only in the address space issuing it (for CICS applications, the CICS region).
At system scope, that is, only in the z/OS image where the program is executing.
At sysplex scope, that is, in the entire sysplex.
In CICS, an application program cannot by itself determine the scope of an ENQ. By default, each ENQ has local scope only. If you want to ensure serialization across multiple CICS regions, you must set up a special CICS resource called an ENQMODEL. For details, see CICS System Definition Guide, SC34-6226.
 
Set up an ENQMODEL definition: This is so important that we say it again. An ENQ is local to the CICS region by default. To achieve serialization across multiple regions, possibly running on different z/OS images in the sysplex, you must set up an ENQMODEL definition.
6.10 Web, TCP/IP, and document services
CICS Web support is a collection of CICS services that support direct access to CICS application programs from Web browsers. You can use CICS Web support with:
Web-aware application programs that use the EXEC CICS WEB and EXEC CICS DOCUMENT application programming interfaces. See the CICS Application Programming Guide for more information.
Programs that are designed to communicate with 3270 terminals using BMS.
Programs that are designed to be linked to from another program using a COMMAREA interface.
Applications can use the facilities of the CICS Web support to:
Map a URL to a request for CICS services.
Interpret HTTP requests.
Construct HTTP responses.
Build HTML output for display by a Web browser.
Although CICS Web support is designed primarily to provide communication between a Web browser and CICS using HTTP, it also supports clients that send non-HTTP requests. The same components of CICS Web support are used to process HTTP and non-HTTP requests.
CICS has a facility that allows you to build up formatted data areasthat are known as documents. Some examples of how these formatted areas, or documents, can be used, are:
Constructing a COMMAREA
Creating standard formats for printing (for example, using your own letterhead, address, and so on)
Sending HTML data to be displayed by a Web browser
We use CICS document support in Chapter 7, “Evolving a heritage application using Java” on page 135, for the latter purpose, namely, to populate predefined HTML templates.
6.11 File control
Using the JCICS file control methods you can query and manipulate key-sequenced data set (KSDS), entry-sequenced data set (ESDS), and relative record data set (RRDS) VSAM files:
Key-sequenced data set (KSDS)
A key-sequenced data set has each of its records identified by a key. The key of each record is simply a field in a predefined position within the record. Each key must be unique in the data set.
Entry-sequenced data set (ESDS)
An entry-sequenced data set is one in which each record is identified by its relative byte address (RBA), that is, by the byte offset of its starting position within the file. Records are held in an ESDS in the order in which they were first loaded into the data set.
New records added to an ESDS always go after the last record in the data set. You might not delete records or alter their lengths. After a record is stored in an ESDS, its RBA remains constant. When browsing, records are retrieved in the order in which they were added to the data set.
Relative record data set (RRDS)
A relative record data set has records that are identified by their relative record number (RRN). The first record in the data set is RRN 1, the second is RRN 2, and so on.
Records in an RRDS can be fixed or variable length records, and the way in which VSAM handles the data depends on whether the data set is a fixed or variable RRDS. A fixed RRDS has fixed-length slots predefined to VSAM into which records are stored. The length of a record on a fixed RRDS is always equal to the size of the slot. VSAM locates records in a fixed RRDS by multiplying the slot size by the RRN, which you supply on the file control request, to calculate the byte offset from the start of the data set.
A variable RRDS, on the other hand, can accept records of any length up to the maximum for the data set. In a variable, RRDS VSAM locates the records by means of an index.
A fixed RRDS generally offers better performance. A variable RRDS offers greater function.
The operations that are available on files fall into the following categories, all of which are supported by the JCICS API:
Adding new records
Reading an existing record
Deleting an existing record
Changing an existing record
Browsing the file
Figure 6-7 on page 116 shows the JCICS class hierarchy for file control classes.
Figure 6-7 JCICS classes for file control
Here we only discuss KSDS file access. Using the other two types of file with JCICS is fairly similar.
In the rest of this section, we develop an implementation of the Java Map interface that is backed by a KSDS data set. Essentially, it is a kind of wrapper around the KSDS that allows manipulation of the data set in a way that looks more natural to Java programmers than raw JCICS API calls. This example is rather long, and many of the implementation details have nothing to do with JCICS per se, but we feel that it illustrates file access in JCICS quite nicely and can prove to be a useful starting point for your own code.
If you are familiar with Java collection classes, you know that a map is a collection of name/value pairs. You can associate a value with a key (put), delete an association (remove), retrieve the value associated with a key (get), and iterate over the map in three different ways (keys only, values only, or key / value pairs).
The implementation does not always fulfill the Map contract, for example, the documentation for Map says that the remove() method returns the value previously associated with the key or null if no value was previously associated with the key.
However, we chose not to return the previous value but just a Boolean that indicates there actually was a record in the file with the given key (or null if there was none)because of performance: If we want to return the previously associated value, we must perform an extra read operation. Because the value returned from remove() is not typically used anyway, we chose to consciously break the contract.
Class declaration and constructors
We start with the class declaration, which includes fields and constructors, as shown in Example 6-17.
Example 6-17 VsamMap class skeleton
package com.ibm.itso.sg245275.vsam;
 
import java.util.AbstractMap;
... more import statements ...
 
/**
* An implementation of a Map that is backed by a
* KSDS VSAM file.
*
* Note that, for simplicity and performance, this
* implementation violates some of the Map interface's contracts.
* For example, the {@link #remove(Object) remove()} method
* does not return the value previously associated with the key.
*
* @author Ulrich Gehlert
*/
public class VsamMap extends AbstractMap { // (1)
private class VsamIterator implements Iterator {
... see below ... // (2)
}
static class SimpleEntry implements Entry {
... not shown here (copied from java.util.AbstractMap)
}
/** Length of key. */
private final int keylength;
 
/** Underlying KSDS. */
private final KSDS ksds; // (3)
 
  private final RecordHolder recordHolder = new RecordHolder();
 
/**
* Constructor.
*/
public VsamMap(KSDS ksds, int keylength) { // (4)
this.ksds = ksds;
this.keylength = keylength;
}
 
/**
* Convenience constructor that accepts a KSDS name.
*/
public VsamMap(String ksdsName, int keylength) { // (5)
this(new KSDS(), keylength);
ksds.setName(ksdsName);
}
}
Rather than implement all methods of the Map interface ourselves, we extend the AbstractMap class, which already provides a lot of the implementation.
In fact, the only abstract method in AbstractMap is entrySet(), so in theory we only need to implement that. However, the default implementations of several other operations are not terribly efficient, so we override them (for example, AbstractMap.get() iterates over all associations until it finds an entry with the given key).
Eventually, this is an implementation of Iterator, which iterates over the entries in the KSDS (see Example 6-23 on page 121).
This is the object that represents the VSAM KSDS by which this map is backed.
This constructor takes a KSDS and a key length. We need the key length later for iterating through the file.
For convenience, this is a constructor that takes a String argument (the KSDS name) rather than a pre-initialized KSDS object.
Getting a record
Next, we implement the get() method, Example 6-18, which accepts a key and returns the associated value or null when no value is associated with the key (that is, if there is no record in the file having that key). Also, it is easy to implement containsKey() using get().
To get the value that is associated with the key, we try to read a record with the given key from the underlying VSAM KSDS.
Example 6-18 VsamMap.get()
public Object get(Object key) {
try {
ksds.read(keyToBytes(key), recordHolder); // (1)
return valueFromBytes(recordHolder.value); // (2)
} catch (RecordNotFoundException notfound) {
return null; // (3)
} catch (CicsException e) {
throw new RuntimeException(e); // (4)
}
}
 
public boolean containsKey(Object key) {
return get(key) != null; // (5)
}
Notes on Example 6-18:
Read a record with the given key from the KSDS. The first argument to KSDS.read() is the key, as a byte array. The second argument is a RecordHolder object, which contains the data after a successful read.
Because, in general, our keys and records do not have the form of byte arrays, we call helper methods to convert keys and records to byte arrays, and vice versa. The default implementations (see Example 6-19 on page 119) assume that both keys and records are String objects. Subclasses can override these methods to provide their own conversions.
The KSDS read was successful. We return the record after converting it.
When the KSDS did not contain a record with the given key, JCICS raises a RecordNotFoundException. Because the Map interface states that get() return null when no value is associated with the key, we catch the exception and do so.
All other exceptions are wrapped into a RuntimeException. We cannot directly throw instances of CicsException because these are checked exceptions.
Implementing containsKey() is straightforward. Check if get() returns non-null.
The next piece of code, in Example 6-19 provides default implementations of the conversion methods that we talked about in the notes on Example 6-18 on page 118.
Example 6-19 VsamMap - Conversion methods
protected byte[] keyToBytes(Object key) {
return ((String) key).getBytes(); // (1)
}
protected byte[] valueToBytes(Object value) {
return ((String) value).getBytes();
}
protected Object keyFromBytes(byte[] bytes) {
return new String(bytes); // (2)
}
 
protected Object valueFromBytes(byte[] bytes) {
return new String(bytes);
}
Notes on Example 6-19:
This default implementation assumes that both keys and records are strings. So, to convert the key to a byte array, we cast it to a String and convert that string to a byte array.
To convert back, we construct a new String object from the byte array.
As mentioned before, these methods are default implementations only and are meant to be overridden by subclasses as appropriate (that is why they were declared protected).
Removing a record
Next, we implement remove(), which deletes a record from the underlying VSAM data set, as shown in Example 6-20.
Example 6-20 VsamMap.remove()
public Object remove(Object key) {
try {
ksds.delete(convertToBytes(key)); // (1)
return Boolean.TRUE; // (2)
} catch (RecordNotFoundException e) {
return null; // (3)
} catch (CicsException e) {
throw new RuntimeException(e);
}
}
Again, we convert the key to a byte array, and then we call KSDS.delete().
We return Boolean.TRUE if the record was actually deleted, which is a deliberate violation of the Map contract, which says that delete() returns the value previously associated with the key. Although fairly easy to implement, this causes another CICS call, which degrades performance.
If the record was not found, return null.
Adding a record
To implement Map.put(), we try to write a record to the data set, as shown in Example 6-21.
Example 6-21 VsamMap.put()
public Object put(Object key, Object value) {
byte[] keyBytes = convertToBytes(key);
byte[] valueBytes = convertToBytes(value);
try {
ksds.write(keyBytes, valueBytes); // (1)
  } catch (DuplicateRecordException e) {
    throw new IllegalArgumentException("Duplicate key"); // (2)
  } catch (CicsException e) {
throw new RuntimeException(e);
}
return null;
}
Notes on Example 6-21:
Try to write the record to the KSDS.
If we receive a DuplicateRecordException, we throw an IllegalArgumentException, which is explicitly permitted by the documentation of java.util.Map (“some aspect of this key or value prevents it from being stored in this map”).
So, effectively, this implementation only allows you to add new records but not to modify existing ones.
Iteration
Finally, we implement Map.entrySet(), which is the only abstract method in AbstractMap, as shown in Example 6-22, which adds support for iterating over the keys or values in the Map, in other words, to browse the file.
Example 6-22 VsamMap.entrySet()
public Set entrySet() {
return new AbstractSet() { // (1)
public Iterator iterator() {
return new VsamIterator(); // (2)
}
public int size() {
throw new UnsupportedOperationException(); // (3)
}
};
}
Again, we use an abstract implementation provided by the collections framework. The methods that we must implement are iterator() and size().
See Example 6-23 for the implementation of VsamIterator.
Because there is no easy way to obtain the number of records in a VSAM data set (other than counting), we chose not to support this operation.
The hardest part is implementing an iterator class, as shown in Example 6-23. It is implemented as an inner class of VsamMap and so has access to VsamMap’s attributes.
Example 6-23 VsamMap.VsamIterator
public class VsamIterator implements Iterator {
private final KeyedFileBrowse fb;
private final KeyHolder kh = new KeyHolder();
private byte[] nextKey;
private byte[] nextValue;
private final RecordHolder rh = new RecordHolder();
 
public VsamIterator() {
try {
fb = ksds.startBrowse(new byte[keylength]); // (1)
} catch (CicsException e) {
throw new RuntimeException(e);
}
}
 
public boolean hasNext() {
if (nextKey == null) { // (2)
try {
fb.next(rh, kh); // (3)
nextKey = kh.value;
nextValue = rh.value;
} catch (EndOfFileException e) {
close(); // (4)
} catch (CicsException e) {
close(); // (5)
throw new RuntimeException(e);
}
}
return nextKey != null; // (6)
}
 
public Object next() {
if (hasNext()) { // (7)
Entry entry = new SimpleEntry(keyFromBytes(nextKey), valueFromBytes(nextValue));
nextKey = nextValue = null;
return entry;
} else {
throw new NoSuchElementException();
}
}
 
public void remove() {
throw new UnsupportedOperationException(); // (8)
}
 
public void close() {
try {
fb.end(); // (9)
} catch (CicsException ignored) {
}
}
}
In CICS terms, to move through a data set sequentially, you start a BROWSE operation. In JCICS, this is implemented by the startBrowse() method, which returns an instance of class KeyedFileBrowse. This class, in turn, has methods to return the next record from the file.
To protect against multiple invocations of hasNext() without calling next() in between, we first check if the next record was already read. If not, we read it, using the next() method on the KeyedFileBrowse object we received in the constructor call. We remember both key and value.
If the end of file was reached, end the browse.
If any other exception occurred, end the browse to free any resources held by it, and throw a RuntimeException.
At this point, either the next record was successfully read, and nextKey (and nextValue) are non-null, or the end of file was reached, in which case nextKey remained null.
The implementation of next() simply calls hasNext(). If there was another record, construct a key/value pair from it, and reset nextKey and nextValue to null so that hasNext() tries a physical read at the next invocation.
There is no KeyedFileBrowse method to delete the current record, so we do not support this operation. It would be fairly easy to implement, however, by remembering the last key retrieved and calling KSDS.delete() with that key.
5. This method ends the browse, cleaning up any resources held by it.
Testing the implementation
To test our VSAM-backed map, we run a small program, shown in Example 6-24.
Example 6-24 Testing the VsamMap implementation
package com.ibm.itso.sg245275.vsam;
 
import java.util.Iterator;
 
public class VsamMapTest {
private final VsamMap map;
 
public static void main(String[] args) {
try {
new VsamMapTest();
} catch (Exception e) {
e.printStackTrace();
}
}
 
private VsamMapTest() {
String key = "66666";
String record = createRecord(key);
this.map = new VsamMap("ACCTFILE", key.length());
System.out.println("Contains key " + key + ": " + map.containsKey(key));
testPutNew(key, record);
System.out.println("Contains key " + key + ": " + map.containsKey(key));
testPutExisting(key, record);
testRemove(key);
printAllValues();
}
 
private void testPutNew(String key, String record) {
System.out.println("Adding record " + key);
map.put(key, record);
}
 
private void testPutExisting(String key, String record) {
try { // (1)
System.out.print("Trying to modify record with key " + key + "...");
map.put(key, record.toString());
System.out.println("Oops, should have caused an exception");
} catch (IllegalArgumentException expected) {
System.out.println("Caught an IllegalArgumentException, as expected");
}
}
 
private void testRemove(String key) {
System.out.println("Removing record " + key);
map.remove(key);
}
private String createRecord(String key) { // (2)
StringBuffer buf = new StringBuffer(key + "DOE JOHN ");
buf.setLength(383); // Pad to record length
return buf.toString();
}
 
private void printAllValues() {
Iterator iter = map.values().iterator();
while (iter.hasNext()) {
System.out.println(iter.next().toString().substring(0, 35)); // (3)
}
}
}
Modifying an existing record using VsamMap.put() is not allowed. Check that an IllegalArgumentException is thrown, as expected, which creates a record of the correct length for our example VSAM file, whose record length is 383 bytes. (The rest is padded with zeros.)
In each iteration, we print the first 35 bytes of the record.
 
Note: There is one potential flaw in our implementation: After you start iterating over the map, there is no way to end the iteration prematurely (to end the BROWSE operation, in CICS parlance), which can potentially tie up resources.
In particular, the number of concurrent operations that can be processed against a VSAM file is limited by the value of the STRINGS attribute on the file definition (see CICS Resource Definition Guide, SC34-6228, for more information). So, when you open one iterator, and then at a later point open another one before the first one was at end-of-file, your program might block forever.
6.12 Interval control
An important facility in CICS is the ability to have one transaction start another transaction. You can have CICS start the transaction immediately (the default), at a specific time, or after a period of time. You can start the transaction on behalf of a specified terminal, and you can pass the started transaction some data. Additionally, the transaction can be started in another CICS region. The started transaction is asynchronous to the transaction that starts it. Thus, the started transaction is in its own unit of work.
Optionally, you can pass data to the started transaction. Other than being able to retrieve the data passed to it, the started transaction is independent of the originating transaction.
We used this facility when we started the HelloWorld sample program using the START command from CECI. To do it from a JCICS program, you set up an instance of class StartRequest and call the issue() method.
Optionally, you can pass data to the transaction being started, similar to the COMMAREA mechanism explained in 6.2.1, “Program control” on page 91. The difference is that because the starting transaction and the new transaction being started are independent from each other, the new transaction cannot pass data back to the originating transaction. Also, the mechanism to access the passed data is different: You use the Task.retrieve() method. Example 6-25 shows how it is done.
Example 6-25 Retrieving data passed from a START request
package com.ibm.itso.sg245275;
 
import java.util.BitSet;
import com.ibm.cics.server.*;
 
public class RetrieveDemo {
 
public static void main(CommAreaHolder cah) {
RetrievedDataHolder rdh = new RetrievedDataHolder();
BitSet whatToRetrieve = new BitSet(); // (1)
whatToRetrieve.set(RetrieveBits.DATA);
whatToRetrieve.set(RetrieveBits.QUEUE);
    try {
Task.getTask().retrieve(whatToRetrieve, rdh);
System.out.println("Retrieved DATA: " + new String(rdh.value.data)); // (2)
System.out.println("Retrieved QUEUE: " + new String(rdh.value.queue)); // (3)
    } catch (EndOfDataException e) { // (4)
System.err.println("No data to retrieve. Did you start me from a terminal?");
} catch (InvalidRetrieveOptionException e) { // (5)
System.err.println("Expected option not set by START command");
} catch (CicsException e) {
e.printStackTrace();
}
}
}
Notes on Example 6-25:
We must indicate what to retrieve: Data, an RTRANSID, an RTERMID, a queue name, or some combination of these. (See CICS Application Programming Reference, SC34-6232, for more information about the meaning of the various options.)
To indicate what combination to retrieve, we must instantiate a BitSet and set the corresponding bits (one of the constants in the RetrieveBits interface).
Print the data and the queue name we received.
No data was passed.
We tried to retrieve a piece of information that was not present. That is, either no data or no queue name was passed from the transaction that started ours.
You can test the sample using CEMT, as we show in Example 6-26.
Example 6-26 CEDA dialog to test Example 6-25 on page 124
START TR(SMP1) From('Your data goes here') Queue(MYQUEUE)
STATUS: ABOUT TO EXECUTE COMMAND NAME=
EXEC CICS START
TRansid( 'SMP1' )
< Interval( +0000000 ) | TIme() | ( AFter | AT ) < Hours() > < Minutes() >
< SEconds() > >
< FRom( 'Your data goes here' ) < Length( +00019 ) < FMh > > >
< TErmid() | Userid() >
< SYsid() >
< RTRansid() >
< RTErmid() >
< Queue( 'MYQUEUE ' ) >
< Nocheck >
< Protect >
< REqid() >
< ATTach >
< BRExit() < BRDATA() < BRDATALength() > > >
PF 1 HELP 2 HEX 3 END 4 EIB 5 VAR 6 USER 7 SBH 8 SFH 9 MSG 10 SB 11 SF
You enter the data in the From clause of the START command (you must use quotes if the data contains embedded blanks) and the queue name in the Queue clause. Try and omit one or both of them, and inspect the program’s output.
6.13 Terminal services
JCICS terminal services allow interaction with the user terminal. You can send data to and receive data from the terminal, and send cursor and device control instructions to the terminal.
Alas, using these services is not for the faint of heart because they require intimate knowledge of the 3270 datastream format (see 3270 Data Stream Programmer’s Reference, GA23-0059).
There is one terminal service, however, that is relatively straightforward to use. If a JCICS program is started from a terminal, the Task.out variable (an instance of PrintWriter) allows you to write to that terminal, which is much like the standard Java System.out stream:
Task.getTask().out.println("Hello World!");
That said, there is good news. During the residency, we developed a little package boldly called JBMS (Java Basic Mapping Support), which provides an easy-to-use interface to the 3270 terminal, which is much like the corresponding BMS service in traditional CICS programming. Be aware, however, that the usual IBM Redbooks publication disclaimer applies: It is experimental code, not thoroughly tested, and certainly not ready for production purposes. It can come in handy, however, if you want to develop a simple front-end for an application without going through all of the effort to create a full-fledged Web interface.
We only show some particularly interesting parts of the JBMS package in the remainder of this section.
Figure 6-8 JBMS class hierarchy
A brief explanation of all classes follows.
Component
This is the abstract superclass for all classes that represent elements on the window. All components except Screen have a parent Component that must be passed to the constructor. A Component has a row / column position that is relative to its enclosing Component (if any) and a flag that indicates whether the component is visible, that is, must be displayed on the window at all.
Container
This is an abstract superclass for all components that contain other components. The idea is that you add related items to a container using a relative position. So, if you later decide that you must move a container to another position on the window, you simply change the container’s position rather than the position of each individual item in the container.
Panel
Right now, a panel does not have additional functionality over a container. The idea is that it can add visual elements, such as a border as an indication of grouping.
For convenience, we provide a subclass, PfKeyPanel, which you can use to show PF-key assignments.
Screen
This container represents an entire 3270 screen. It is responsible for generating and sending the outbound 3270 data stream to the terminal, and for receiving and parsing the inbound data stream.
Again, there is a convenience subclass, MenuScreen, which can be used to easily create a menu screen showing several items and an input field for the user’s choice.
Field
This is the abstract superclass of all basic components of a screen, that is, the actual fields that make up the screen.
Label
Represents fields that are intended for display only, that is, that are not for data entry by the user. However, the text displayed need not be static but can also be changed during the dialogue with the user, for example, to show an error message.
Text
This is a field for data entry. Optionally, you can set a formatter, that is, an instance of java.text.Format, to validate and format data entered by the user.
Next, we show you a sample screen created using the JBMS package (Figure 6-9 on page 128) and the program that created it (Example 6-27 on page 128).
Figure 6-9 Sample screen produced by a JBMS program
Example 6-27 shows the program to generate this screen.
Example 6-27 Sample JBMS program
package com.ibm.itso.sg245275;
 
import java.text.DecimalFormat;
 
import com.ibm.cics.server.*;
 
import com.ibm.itso.sg245275.jbms.*;
import com.ibm.itso.sg245275.jbms.Field.Color;
import com.ibm.itso.sg245275.vsam.VsamMap;
 
public class BmsDemo {
private Text acctno;
private Text firstname, lastname, midinit;
private Text phone;
private Text[] addr = new Text[3];
private PfKeyPanel pfkeys;
private VsamMap accounts = new VsamMap("ACCTFILE", 5); // (1)
 
public BmsDemo(TerminalPrincipalFacility term) throws InvalidRequestException,
LengthErrorException, NotAllocatedException,
TerminalException
{
Screen screen = createScreen();
byte aid;
String record = null;
boolean end = false;
term.clear();
while (!end) {
do {
fillFields(record); // (2)
aid = screen.displayAndReceive(term); // (3)
} while (aid != AIDValue.PF3 && screen.validate() != null); // (4)
switch (aid) {
// ... omitted
}
}
term.clear();
}
 
private String makeRecord() {
// ... omitted
}
private void fillFields(String record) {
// ... omitted
}
 
private Screen createScreen() { // (5)
Screen screen = new Screen();
Label label;
label = new Label(screen, 1, 1, "Account management");
label.setForegroundColor(Color.PINK);
label = new Label(screen, 4, 1, "Acct no:");
label.setForegroundColor(Color.TURQUOISE);
acctno = new Text(screen, 4, 10, 5); // (6)
acctno.setFormat(new DecimalFormat("00000"));
// Name panel.
Panel namePanel = new Panel(screen, 6, 0); // (7)
label = new Label(namePanel, 0, 1, "Name: ");
label.setForegroundColor(Color.TURQUOISE);
lastname = new Text(namePanel, 0, 10, 18);
label = new Label(namePanel, 0, 29, ",");
label.setForegroundColor(Color.TURQUOISE);
firstname = new Text(namePanel, 0, 31, 12);
midinit = new Text(namePanel, 0, 44, 1);
label = new Label(screen, 8, 1, "Phone: ");
label.setForegroundColor(Color.TURQUOISE);
phone = new Text(screen, 8, 10, 10);
// Address panel.
Panel addressPanel = new Panel(screen, 10, 0);
label = new Label(addressPanel, 0, 1, "Address: ");
label.setForegroundColor(Color.TURQUOISE);
addr[0] = new Text(addressPanel, 0, 10, 24);
addr[1] = new Text(addressPanel, 1, 10, 24);
addr[2] = new Text(addressPanel, 2, 10, 24);
// Create PF key bar.
pfkeys = new PfKeyPanel(screen);
pfkeys.setText(1, "HELP");
// ... (omitted)
 
// Initial cursor position.
screen.setInitialField(acctno); // (8)
return screen;
}
 
public static void main(CommAreaHolder ca) {
// ... omitted
}
}
The data displayed on this screen comes from a VSAM file. We use the VsamMap class developed in 6.11, “File control” on page 115, to access the data.
Populate the fields with values from the record.
Display the screen, and receive the user response. The fields that make up the screen are updated with the data that the user enters, and we get back an AID value that tells us what key the user pressed to send the data (for example, the ENTER key, or a PF key).
If the user entered invalid data (in this example, non-numeric data in the acctname field), start all over again, unless he pressed PF3 for exit.
This method creates the screen, adding labels for informative text, and fields for data entry.
This is the account number field. We set up a formatter that causes the data to be displayed with leading zeros and validates the user input.
For easier maintenance, we group related fields, such as the name and address fields together, with offsets relative to the enclosing container. To move a group of related fields, only change the position of the container rather than of each contained element.
Set the initial field, that is, the one that the cursor is positioned on when the screen is displayed.
In the remainder of this section, we show you some implementation details. The most interesting parts are constructing the data to be sent to the 3270 terminal (in 3270 parlance, the outbound data stream), and parsing the data being sent back by the terminal (the inbound data stream).
Example 6-28 shows the method that creates the 3270 data stream and sends it to the terminal.
Example 6-28 Screen.display()
public void display(TerminalPrincipalFacility term) throws InvalidRequestException,
LengthErrorException, NotAllocatedException, TerminalException
{
ByteArrayOutputStream bos = new ByteArrayOutputStream(); // (1)
bos.write(0);
bos.write(0);
to3270(bos); // (2)
if (getInitialField() != null) { // (3)
// Position cursor to initial field.
initialField.insertBufferAddress(bos, true);
bos.write(Order.INSERT_CURSOR);
}
term.send(bos.toByteArray()); // (4)
}
 
Create a ByteOutputStream to hold the outbound 3270 data stream.
Create the 3270 data stream, as shown in Example 6-29.
If an initial field was specified, write a 3270 instruction to set the current buffer position and another one to move the cursor to that position.
This is the JCICS call to send the data stream to the terminal.
To generate the data stream, we recursively iterate over all components, as shown in Example 6-29.
Example 6-29 Container.to3270()
protected void to3270(ByteArrayOutputStream bos) {
if (isVisible()) { // (1)
for (Iterator iter = components.iterator(); iter.hasNext();) { // (2)
Component component = (Component) iter.next();
component.to3270(bos); // (3)
}
}
}
Notes on Example 6-29:
If this Container is visible at all:
 – Iterate over all contained components.
 – Tell each in turn to generate its part of the data stream, which is a recursive call if the current component is itself a container.
Finally, we must generate the 3270 stream for a single field, which is the most complicated part because here we finally must know about the 3270 data stream format.
Basically, the outbound 3270 data stream consists of command bytes (orders) followed by parameters. To define a field, use the Start Field (SF) or Start Field Extended (SFE) order. In our code, we use SFE.
A field begins at the current buffer position, which you can change with the Set Buffer Address (SBA) order and ends right before the next field begins. SBA is followed by a two-byte buffer address.
Therefore, to start a new field, we first set the current buffer position, and then define the field using SFE and finally create an empty marker field.
A 3270 field can have several attributes, such as color, intensity, and whether the field is protected (no data can be entered into the field). Each attribute is recognized by an attribute type byte followed by an attribute value byte (see Table 6-2 on page 132). One of those attributes, the 3270 attribute, is always present, and is stored in the 3270 display buffer at the first position of the field (it does not display on the screen). The other attributes are optional.
The SFE order is followed by a single byte that indicates how many type/value byte pairs follow.
Table 6-2 Some 3270 attributes and their possible values
Type code
Attribute type
Values
0xC0
3270 Field attribute

Value is a combination of bits. Some combinations have special meanings (autoskip, nondisplay).
Value must be EBCDIC encoded.
0x20
Field is protected
0x10
Field is numeric
0x30
Field is autoskip (no entry)
0x08
Intensified
0x0C
Nondisplay (password)
0x41
Extended highlighting
0x00
Default
0xF0
Normal (as determined by 3270 field attribute)
0xF1
Blink
0xF2
Reverse video
0xF4
Underscore
0x42
Foreground color
0xF0
Neutral
0xF1
Blue
...
 
0xFF
White
Example 6-30 shows how the data stream for a single field is generated.
Example 6-30 Field.to3270()
protected void to3270(ByteArrayOutputStream bos) {
if (isVisible()) {
insertBufferAddress(bos, false); // (1)
bos.write(Order.START_FIELD_EXT); // Start field extended // (2)
bos.write(countExtendedAttributes()); // Number of extended attributes // (3)
// Send extended attributes, as type/value byte pairs. // (4)
writeAttribute(bos, AttributeType.STD3270, (byte) Util.encode(attributes));
writeAttributeConditional(bos, AttributeType.EXT_HILITE, extHilite);
writeAttributeConditional(bos, AttributeType.FOREGROUND, foregroundColor);
try {
bos.write(text.getBytes());
} catch (IOException e) {
// Can’t happen with a ByteArrayOutputStream
}
// Send a dummy marker field to indicate end of current field // (5)
bos.write(Order.START_FIELD);
bos.write(Util.encode(Attribute.AUTOSKIP));
}
}
Notes on Example 6-30:
First we must tell the terminal the position of the field. The 3270 expects the position as a buffer address that is encoded in a rather peculiar format (see the full source code for details).
Next, we indicate the start of a field using the Start Field Extended (SFE) order.
SFE expects the number of attribute / value pairs.
Write attribute / value pairs to the data stream.
The 3270 assumes that a field ends where the next field starts, so we write a dummy marker field to indicate the end of the current one.
To get user input, we must receive and parse the inbound 3270 data stream, as shown in Example 6-31.
Example 6-31 Screen.receive()  - Receive input from terminal, parse response, and update fields
/**
* Receive input from terminal, and parse data stream.
   *
* @return AID (Action Identifier) of the key pressed {one of the
* {@link com.ibm.cics.server.AIDValue AIDValue}constants).
*/
public byte receive(TerminalPrincipalFacility term) throws
InvalidRequestException, LengthErrorException, NotAllocatedException, TerminalException
{
DataHolder dah = new DataHolder();
try {
    term.receive(dah); // (1)
} catch (EndOfChainIndicatorException expected) {
// Always thrown from TerminalPrincipalFacility.receive(). // (2)
// We can safely ignore it.
}
parseResponse(dah.value); // (3)
return term.getAIDbyte(); // (4)
}
Notes on Example 6-31:
JCICS call to receive the response from the terminal.
Term.receive() always throws an EndOfChainIndicatorException (because the underlying EXEC CICS API always sets the corresponding RESP condition), and we can safely ignore it.
Parse the response and return the AID that caused the screen to be sent.
The inbound 3270 consists of Set Buffer Address (SBA) orders that indicate which field is being sent next, followed by the actual data (that is, whatever the user entered into the field). So, we inspect the data stream, looking for SBA orders and finding the corresponding JBMS Field object for each order, as shown in Example 6-32. Then we extract the field data and update the Field object accordingly.
Example 6-32 Parsing the response received from the 3270 terminal
private void parseResponse(byte[] resp) {
// Start at index 1 (index 0 has a SET_BUFFER_ADDRESS for first field).
for (int pos = 1; pos < resp.length; pos++) { // (1)
// Get buffer address.
int bufaddr = ((resp[pos++] << 8) | (resp[pos++] & 0xFF)) & 0xFFFF; // (2)
// Find end of current segment (look for next SBA).
int end;
for (end = pos; end < resp.length && resp[end] != Order.SET_BUFFER_ADDRESS; end++)
;
// Find the field this segment corresponds to.
Field field = findFieldByAddress(bufaddr); // (3)
if (field != null) {
// Found field; set its contents to the data received.
String fieldContents = new String(resp, pos, end - pos); // (4)
field.setText(fieldContents);
}
pos = end;
}
}
Scan the data stream, and examine it for SBA orders.
Extract the buffer address (next two bytes after SBA).
Look up the field that this buffer address refers to.
Extract the field data, and set the new field text.
6.14 Using JZOS with CICS
The IBM JZOS Batch Toolkit for z/OS SDKs is a set of tools that address many of the functional and environmental shortcomings in current Java batch capabilities on z/OS. It includes a native launcher for running Java applications directly as batch jobs or started tasks, and a set of Java methods that make access to traditional z/OS data and key system services directly available from Java applications. Additional system services include console communication, multiline WTO (write to operator), and return code passing capability. In addition, JZOS provides facilities for flexible configuration of the run-time environment, and it allows intermediate data to be seen through z/OS System Display and Search Facility (SDSF). Java applications can be fully integrated as job steps to augment existing batch applications.
When working in CICS, use JCICS APIs in preference to JZOS to gain from the benefits offered by CICS, but there are times where JZOS might be useful to CICS Java developers. These include:
When writing Java Batch applications that invoke CICS transactions using the J2C APIs
Using the JZOS APIs to access PDS resources from a CICS region
Converting COBOL and Assembler data type fields to Java objects
For more information about JZOS, see the JZOS Installation and User’s Guide, SA23-2245-00 and the book Java Stand-alone Applications on z/OS Volume II, SG24-7291-00.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset