Chapter 6. The Java CICS API
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.
Note: All JCICS classes that represent CICS resources are designed to comply with the
Chapter 6. The Java CICS API 97
Figure 6-3 Part of the JCICS exception hierarchy
In Java, exceptions fall into two major categories:
Checked exceptions
andunchecked
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.
Chapter 6. The Java CICS API 99
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) }
Notes on Example 6-3 on page 99:
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);
Chapter 6. The Java CICS API 101
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 correspondingtry
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.