There are seven steps that are involved in developing this part of the application:
To provide the connection to an MQSeries queue manager, we have created a Java class MQCommunicator. It looks like this:
public class MQCommunicator {.......}
The full code is on the accompanying CD in the directory called \mqseries-java client\MQCommunicator.class.
MQSeries provides a number of Java classes, which we can access on Windows NT by importing the package com.ibm.mq.*. In order to make a connection to the relevant queue manager we need to supply the following information:
The IP address of the host machine on which the OS/390 queue manager that we are going to use is located
The name of the Windows NT queue manager
The name of the MQI channel that makes the connection from the MQSeries client to the Windows NT queue manager (named in the previous bullet)
The request queue where we put the MQSeries message containing details of the customer account number
The reply-to queue onto which we get the response message containing the customer details supplied by the NACT02 application
The code looks like this:
// Setup MQ object MQComms=newMQCommunictaor(hostname,qManager,channel,requestQueue,replyQueue);
Using the MQSeries supplied environment class MQEnvironment, we set the variables for the host name, queue manager, and the communications port to use:
MQEnvironment.hostname =hostname
; // ARG#1 MQEnvironment.channel =channel
; // ARG#2 MQEnvironment.port = 1414; // Hard code port
The communications port in this case has the system default value of 1414. When no value is set for the port, this value will normally be assumed. This is important if you are using more than one port.
Connection to the selected queue manager is then made by creating an instance of the MQSeries supplied object MQQueueManager:
qMgr = new MQQueueManager(qManager
);
Once we have established the connection, we can open the MQSeries queues for use. requestQueue and replyQueue are instances of the MQSeries supplied object MQQueue.
To enable the request queue so that we can place messages on it, we set the relevant open option:
openOptions = MQC.MQOO_OUTPUT ; // Open queue to perform MQPUTs
This information is then supplied with the open request:
requestQueue = qMgr.accessQueue(aRequestQueue, openOptions, null, // queue manager on which the queue is defined null, // no dynamic queue name null); // no alternate user id
The use of null for the queue manager name denotes that the queue is defined to the queue manager to which this MQQueueManager object is connected.
Similarly, we enable the reply-to queue so that we can get the response message:
openOptions = MQC.MQOO_INPUT_AS_Q_DEF ; replyQueue = qMgr.accessQueue(aReplyQueue, openOptions, null, // queue manager on which the queue is defined null, // no dynamic queue name null); // no alternate user id
We have used some MQSeries constants in the previous examples such as MQC.MQOO_OUTPUT…. There are a number of such constants defined by MQSeries which are similar across the different platforms supported by MQSeries. All Java versions of these classes are prefixed by MQC. The MQOO indicates that this is an open option. MQMT would indicate a message type option.
To create an instance of the MQSeries object MQMessage:
sendMessage = new MQMessage();
Default values for the message can be used for most of the message options. The following changes need to be made:
Add the COMMAREA fields to the message buffer:
MQMessage.WriteString . Add COMMAREA structure
Under certain circumstances, the message format required for the MQSeries CICS DPL bridge contains an additional structure compared with standard MQSeries messages.
For our simple example (which only starts one program), this MQCIH structure is not required. The MQCIH is required when you want to do one of the following:
Run a 3270 transaction
Run the bridge with AUTH=VERIFY_*
Include more than one program in a unit of work
The MQCIH header is placed at the start of the message buffer.
The contents of the request COMMAREA for this application always contain the same supplied values apart from the customer account number keyfield. We have chosen to store the COMMAREA fields before the customer account number key-field in the string bufferFront. The fields following the customer account number keyfield are stored in the string bufferEnd.
The bufferFront string is composed as follows:
bufferFront = "NACT02" + // Program name "V1A"+ // Version "E"+ // Request type " "+ // " "+ " "
We need to supply the remainder of the COMMAREA after the customer account number. It consists of only spaces. As a result, bufferEnd is a string of 378 spaces.
These values are now used to create the message buffer string:
String buffer = new String(bufferFront + customerNumber + bufferEnd);
The customerNumber field is a 5-character string holding the customer account number value.
This is then written to the MQSeries message instance sendMessage:
sendMessage.writeString(buffer);
MQSeries provides an object to specify options for the message being put onto a queue. In most cases the default options are acceptable. We create an instance of this MQPutMessageOptions object pmo:
// Specify the message options...(default) MQPutMessageOptions pmo = new MQPutMessageOptions();
The message can now be placed on the appropriate queue:
requestQueue.put(sendMessage, pmo);
The response message is recognized by its correlation ID (CorrelId), which matches the original message ID (msgId
) passed with the request message. The application copies the global: message ID of the request message into the correlation ID of a new MQMessage object, in this case, the response message.
In a similar way to which we built the request message, we first create an instance of the MQSeries object MQGetMessageOptions, which is used to set the options for getting the response message:
MQGetMessageOptions gmo = new MQGetMessageOptions();
You can modify the default values of this new instance gmo as follows:
Specify that you will wait for the message if it is not already on the reply-to queue. Also that the incoming message should be converted to the correct CCSID for your machine if required:
gmo.options = MQC.MQGMO_WAIT MQC.MQGMO_CONVERT;
Specify the length of wait as 300 seconds:
gmo.waitInterval = 300000;
We already have the required MQSeries message object in the instance StoredMessage which is supplied as the parameter replyMessage. The code to get the message is:
ReplyQueue.get(replyMessage,gmo);
The COMMAREA returned by NACT02 within the response message contains a response code and reason code. These need to be checked in order to determine whether the request to retrieve the customer details was successful or whether some error was encountered. For a non-zero return code, the following need to be checked:
No record exists for the customer account number supplied.
The record is currently not accessible. This occurs if a lock has been put on the record where updating is in progress.
The data supplied to CICS contains an error.
The CICS system has abended for some reason.
Some other CICS-related error has occurred. This is a catchall and details of the EXEC Interface Block (EIB) values EIBRESP and EIBRESP2 are provided in the CICSresponseCode and CICSreasonCode fields, as shown in Example 15-1, which is an example of the coding involved in checking whether the record exists or is locked.
Example 15-1. Sample Code for CICS-Related Errors
//informUser("Message received!"); //print("Message is: "+messageReturned); //Check that inquire has been successful CICSresponseCode = messageReturned.substring(12,16); print(">CICS response code" +CICSresponseCode+"#"); CICSreasonCode=messageReturned.substring(16,20); print(">CICS reason code" + CICSreasonCode+"#"); if (CICSresponseCOde.equals("0000")){ //Strip off the fields preceding the customer record customerDetails = messageReturned.substring(25,408); AccountRecord retrieved=new AccountRecord(customerDetails); displayRecord(retreived); return; } //No valid record to display so clear fields clearDisplay(); //Check out error response returned by CICS if(CICSresponseCode.equals("0013")){ informUser("No record can be found for the custimer number given.") return; } if(CICSresponseCode.equals("LOCK")){ informUser("The requested record is currently unavailable, possibly being updated." + "\nPlease try again later."); return; } if(CICSresponseCode.equals("ABND")){ informUser("Please inform the systems administrator the the CICS system has" +"\nabnormally ended with code" + CICSreasonCode + "Severe Error"); return; } if((CICSresponseCode.equals("FRMT")) & (CICSreasonCode.equals("LENE")){ informUser("Please inform the systems administrator that the call to the CICS program" + "\nhas failed due to an error in the length of the data supplied."); } if((CICSresponseCode.equals("FRMT")) & (CICSreasonCode.equals("REQE"))){ informUser("Please inform the systems administrator that the call to the CICS program + "\nhas failed due to an error in the type of request made."); } informUser("Please inform the systems administrator that the call to the CICS program + "\nhas failed with the following error" + "\ nEIBRESP="CICSresponseCode+"EIBRESP2=" CICSreasonCode); }finally{ enableDisableListitems(true); }
The next step is to create retrieved, a new instance of AccountRecord and pass the appropriate portion of the response message string as a parameter. The constructor class for this instance of AccountRecord uses the supplied string held in customerDetails to construct the various fields that make up the customer information returned within the COMMAREA from the NACT02 program. The basic method is to assign substrings based on position to the various declared fields of the AccountRecord structure. A getfieldname method is provided for each record field so that the fields can be easily retrieved by other Java classes. The code looks like:
AccountRecord retrieved = new AccountRecord(customerDetails);
The MQClient method displayRecord can then be used to populate the various fields displayed on our main panel:
displayRecord(retrieved);