CICS over IBM® MQ

« Back to documentation home

What Traffic Parrot stands in for

The IBM® MQ CICS bridge (the CICS DPL bridge) lets an application call a CICS program by putting a message on a queue. The request carries an MQCIH header, the name of the program and the program's COMMAREA, in the EBCDIC text and packed-decimal layout the program's COBOL copybook describes. The bridge runs the program and puts the reply, an MQCIH and the program's output COMMAREA, on the request's reply-to queue, correlated to the request's message id.

Traffic Parrot stands in for the bridge and for the programs behind it. Replaying the bridge request queue, it reads each request, decodes the COMMAREA with the program's copybook, matches a mapping on the program name, the header fields and the decoded fields, and sends the reply the way the bridge sends it, with a COMMAREA encoded from JSON by the reply copybook. The application under test keeps its MQ client, its queues and its copybooks. Nothing runs on the mainframe, and no CICS region is needed.

You need a native IBM® MQ connection to the queue manager the application uses, the IBM® MQ client JAR in lib/external, a licence that includes native IBM® MQ, and the copybooks of the programs you virtualize. The walkthrough on the IBM® MQ developer container below runs the whole flow on a laptop in a few minutes.

What is not covered
  • The IMS bridge. A request in the IMS format (MQFMT_IMS, with an MQIIH header) is not answered; the log reports it as Unsupported type: 'MQIMS'.
  • Recording. A bridge conversation is not recorded. Write the mapping from the program's copybook fields, starting from the request view the log prints. Traffic Parrot installs nothing in the CICS region in any case.
  • CICS Transaction Gateway (ECI calls), IMS Connect and DRDA. These protocols are not virtualized.
  • z/OS Connect and CICS Web Services are HTTP and SOAP, so they are virtualized like any other HTTP or SOAP service, with no bridge involved.

How it works

  1. The application puts a request on the bridge request queue: an MQMD with the format MQCICS, then an MQCIH header, the program name padded to eight characters, and the COMMAREA, in EBCDIC with big-endian numbers, as a z/OS client sends them.
  2. Traffic Parrot, replaying that queue with the bridge enabled, reads the header, the program name and the COMMAREA, looks the program up in copybooks/programs.json, and decodes the COMMAREA with the program's request copybook into JSON fields.
  3. It matches the mapping on a JSON view of the request: the MQMD user, the MQCIH fields, the program name and the decoded COMMAREA fields.
  4. It builds the reply from the mapping's response: the MQCIH fields a bridge reply can set, and the output COMMAREA encoded from JSON with the program's reply copybook, or raw bytes you give as hex. The reply goes on the request's reply-to queue with its correlation id set to the request's message id, as the bridge does.

Everything else a native IBM® MQ virtual service does applies: the connection to the queue manager, starting and stopping replay, mapping files under ibm-mq-mappings, the mapping editor, delays and the requests page are the ones the native IBM® MQ page describes.

Enable the bridge

The bridge is off by default. Set this property in trafficparrot.properties and restart Traffic Parrot:

trafficparrot.ibmmq.cicsBridge.enabled=true

With it off, a request with the format MQCICS arriving on a replayed queue is not answered, and the log says why:

Unsupported type: 'MQCICS'. To answer CICS DPL bridge requests, set trafficparrot.ibmmq.cicsBridge.enabled=true.

Copybooks

Traffic Parrot decodes and encodes a COMMAREA with the program's COBOL copybooks. Put them in a copybooks directory next to the ibm-mq-mappings directory of the scenario (the main Traffic Parrot directory for the default scenario), together with an index file, programs.json, that names the copybooks of each program:

trafficparrot-x.y.z/
|-- ibm-mq-mappings/
|   `-- acctbal.json
`-- copybooks/
    |-- programs.json
    |-- ACCTBAL-REQ.cpy
    `-- ACCTBAL-RPY.cpy
{
  "ACCTBAL": {
    "requestCopybook": "ACCTBAL-REQ.cpy",
    "replyCopybook": "ACCTBAL-RPY.cpy"
  }
}
  • Each key is a program name as the request carries it, up to eight characters, in the same case.
  • requestCopybook and replyCopybook are both required and name files in the copybooks directory; give the same file for both when the program uses one layout for its input and its output.
  • codePage is optional and names the EBCDIC code page the COMMAREA text is in, IBM037 unless you set it, for example "codePage": "IBM1047". It applies to both copybooks of the program.
  • Any other key is refused, and so is a copybook outside the directory. Traffic Parrot creates neither the directory nor the index.
  • The index and the copybooks are read again whenever they change on disk, so an edit applies to the next request without a restart.
What a copybook may contain

Group levels, PIC X and PIC A text, zoned decimal PIC 9 and PIC S9 (including SIGN SEPARATE), COMP-3 packed decimal, COMP, COMP-4 and BINARY big-endian binary, COMP-5, OCCURS, OCCURS DEPENDING ON, REDEFINES, FILLER and level 88 conditions. A copybook that uses something else is refused when the program is first requested, with a message in the log naming the copybook and the line.

How fields appear as JSON
  • A group is a JSON object and an OCCURS item is an array. FILLER is left out.
  • Text decodes to a string with its trailing spaces removed.
  • A number decodes to a JSON number at the picture's scale, written in plain form: PIC S9(9)V99 COMP-3 holding 1500.25 reads 1500.25, and PIC SV9(7) COMP-3 holding 1 reads 0.0000001. Leading zeros of a PIC 9(4) are not in the number: 0042 reads 42.
  • Every REDEFINES alternative appears under its own name; an alternative whose bytes are not valid for its picture is null.
  • When Traffic Parrot encodes a reply, a field the JSON leaves out takes the value INITIALIZE would give it (spaces for text, zero for a number), FILLER is written as spaces, and a value that does not fit its picture, such as too many digits or a negative number in an unsigned field, is refused.

The request as Traffic Parrot matches it

A bridge request is matched as a JSON document: the user id from the MQMD, the MQCIH fields, the program name and the decoded COMMAREA. For a request to the ACCTBAL program of the walkthrough, with the copybook above, the view is:

{
  "MQMD" : {
    "UserIdentifier" : "app"
  },
  "MQCIH" : {
    "Version" : 2,
    "Encoding" : 785,
    "CodedCharSetId" : 37,
    "Format" : "",
    "Flags" : 0,
    "ReturnCode" : 0,
    "CompCode" : 0,
    "Reason" : 0,
    "UOWControl" : 273,
    "GetWaitInterval" : 0,
    "LinkType" : 1,
    "OutputDataLength" : -1,
    "FacilityKeepTime" : 0,
    "ADSDescriptor" : 0,
    "ConversationalTask" : 0,
    "TaskEndStatus" : 0,
    "Facility" : "0000000000000000",
    "Function" : "",
    "AbendCode" : "",
    "ReplyToFormat" : "",
    "RemoteSysId" : "",
    "RemoteTransId" : "",
    "TransactionId" : "",
    "FacilityLike" : "",
    "AttentionId" : "",
    "StartCode" : "",
    "CancelCode" : "",
    "NextTransactionId" : "",
    "CursorPosition" : 0,
    "ErrorOffset" : 0,
    "InputItem" : 0
  },
  "ProgramName" : "ACCTBAL",
  "COMMAREA" : {
    "ACCOUNT-NO" : "1234567890",
    "BRANCH-ID" : 42,
    "MIN-RATE" : 0.0000001
  }
}
  • MQMD has one field, the UserIdentifier the queue manager set on the request.
  • MQCIH has every header field except StrucId, StrucLength, the reserved fields and the Authenticator, which is a credential and never appears in the view or the log. Numbers are JSON numbers, text is unpadded, and Facility is hex.
  • ProgramName is the eight-character name with its padding removed, read as EBCDIC.
  • COMMAREA holds the fields the request copybook decodes. When the program has no entry in programs.json, or the bytes do not fit its copybook, the view has CommareaHex, the bytes as hex, and CommareaNotDecoded, the reason, in place of COMMAREA, and the request can still be matched on the hex. An empty COMMAREA is "CommareaHex" : "".

With trafficparrot.ibmmq.logger.logMessageBody at its default of true, every request is written to the log under CICS bridge request as Traffic Parrot matches it:, followed by this view, which is the quickest way to see what a mapping has to match. An unmatched request is listed on the IBM® MQ Requests page with the same view and its near misses when trafficparrot.messaging.diagnostics.enabled is true; the page shows the first part of the view, so the decoded fields are easiest to read in the log.

Matching a bridge request

Every request body matcher runs against the JSON view, so equalToJson, matchesJsonPath, contains and the others work on a bridge request as they do on any JSON message, and and, or and not combine them. For example, with matchesJsonPath:

"bodyMatcher": {
  "and": [
    { "matchesJsonPath": { "expression": "$.ProgramName", "equalTo": "ACCTBAL" } },
    { "matchesJsonPath": { "expression": "$.COMMAREA.ACCOUNT-NO", "equalTo": "1234567890" } }
  ]
}
The matchesCicsField matcher

matchesCicsField compares one field of the view by its path, with the comparison a copybook field needs: a number compares as a number, so 0042 in a PIC 9(4) equals 42, and a PIC X holding digits can be compared as a number too. The matcher value is a JSON object with a field path and exactly one comparison:

Key Meaning
field The path of the field from the root of the view, names separated by dots, exactly as the copybook spells them: COMMAREA.ACCOUNT-NO, ProgramName, MQCIH.TransactionId, MQMD.UserIdentifier. An OCCURS element is picked by a zero-based index in brackets: COMMAREA.TXN-ENTRY[1].TXN-AMOUNT is the second entry's amount.
equalTo Equal to the value: numbers by value, text exactly.
matches A regular expression the whole value must match, numbers in their plain form.
greaterThan, greaterThanOrEqualTo, lessThan, lessThanOrEqualTo Compare the field as a decimal; text that does not read as a number does not match.

A path that reaches nothing, a null, a group or a table is no match. A mapping file combining several field matchers:

"bodyMatcher": {
  "and": [
    { "matchesCicsField": { "field": "ProgramName", "equalTo": "ACCTBAL" } },
    { "matchesCicsField": { "field": "MQCIH.TransactionId", "equalTo": "CKBP" } },
    { "matchesCicsField": { "field": "COMMAREA.BRANCH-ID", "greaterThan": 40 } },
    { "matchesCicsField": { "field": "COMMAREA.ACCOUNT-NO", "matches": "123.*" } }
  ]
}

In the IBM® MQ mapping editor, choose matches CICS field in the Request body dropdown and type the object, for example {"field": "COMMAREA.BRANCH-ID", "greaterThan": 40}, as the value. To combine several field matchers with and, or or not, write the mapping file, as the walkthrough does: the editor's sub-matcher lists do not offer the field matchers, so a mapping that combines them opens in the editor with those lists blank and is refused on Save with Must be constructed with at least two matchers, leaving the file as it is. The matcher's expected value can be kept in a file under __files like any other request body matcher value from a file.

When a request does not match
  • The program has no copybook. The log warns No copybook for CICS program 'NOCOPYBK': copybooks/programs.json has no entry for it (or names what is wrong with the entry or the copybook), and the request is matched on its MQCIH fields and the raw CommareaHex. The same happens when the COMMAREA does not fit the copybook, with the field and offset that failed in the log when message bodies are logged.
  • The message is text, not a bridge request. A text message (MQSTR, or a JMS text message, which is MQHRF2 carrying MQSTR data) on a queue whose mapping uses matchesCicsField or the cicsField helper is logged once as a warning naming the message id, the queue, the format and the mapping: IBM® MQ and its clients convert character data between code pages as text, so the EBCDIC text and packed decimals a copybook describes may already be corrupted, and Traffic Parrot does not decode such a message. A bridge request carries an MQCIH and has the format MQCICS.
  • Nothing matched. Compare the mapping with the view the log prints under CICS bridge request as Traffic Parrot matches it, or open the IBM® MQ Requests page, which lists the request with the mappings closest to it and the field each one missed on.

Replying as the bridge

The mapping's response text is a JSON object with up to two keys:

{
  "MQCIH": { "ReturnCode": 0, "CompCode": 0, "Reason": 0, "AbendCode": "" },
  "COMMAREA": {
    "ACCOUNT-NO": "{{cicsField request.body 'COMMAREA.ACCOUNT-NO'}}",
    "BALANCE": 1500.25,
    "STATUS-CODE": "OK",
    "RESERVED": 0
  }
}
  • MQCIH, optional: the fields a bridge reply sets, ReturnCode, CompCode and Reason as integers and AbendCode as text of up to four characters. Every other header field is as the bridge would set it: Version, Encoding and CodedCharSetId copied from the request, Format set to the request's ReplyToFormat, the rest at their initial values. Naming any other field is refused.
  • COMMAREA: the output fields, encoded with the program's reply copybook. The record is cut to the request's OutputDataLength when that is set and smaller, and trailing nulls are removed when the request asked for it with MQCIH_REPLY_WITHOUT_NULLS, as the bridge does.
  • CommareaHex, in place of COMMAREA: the output bytes as hex, sent as they are, for a program with no copybook or a record you hand-craft. A reply that gives both COMMAREA and CommareaHex is refused.

An empty response text sends an MQCIH with no COMMAREA. The reply has the format MQCICS, the request's encoding and character set (the editor's Response Format and Character Set fields do not apply to a bridge reply, whatever they show), the message type reply, and a correlation id equal to the request's message id, and it goes to the request's reply-to queue, the bridge's own rule and the default routing of every IBM® MQ reply; the mapping's response destination is used when the request names no reply-to queue. To answer with an abend, as the real program would on its error path:

{ "MQCIH": { "ReturnCode": 5, "CompCode": 2, "AbendCode": "AEI0" } }

which is MQCRC_APPLICATION_ABEND with MQCC_FAILED; the IBM® MQ documentation of the MQCIH structure lists the return codes.

The cicsField helper

{{cicsField request.body 'COMMAREA.ACCOUNT-NO'}} renders one field of the request view by the same path syntax matchesCicsField uses, so a reply can echo and compute from the request, beside the other response templating helpers, for example {{math (cicsField request.body 'COMMAREA.BRANCH-ID') '*' 10.5}}.

  • Text renders as it is, a number in plain form, and a group or a table as compact JSON.
  • A null (an invalid REDEFINES alternative) renders as nothing.
  • A path that reaches nothing renders an inline error, {{ cicsField ERROR: no field at 'COMMAREA.ACCOUNT-NO' in the CICS bridge request }}, in place of the value. Inside a quoted JSON string, the encoder refuses it as a number, or as text longer than its field, and the client gets a bridge error quoting the start of it, path included unless the path is long; in a PIC X field long enough to hold it, the text is sent as the field's value. Unquoted, as in the RESERVED example below, it makes the response invalid JSON, and the bridge error quotes the JSON parser instead. Check each path against the request view before relying on a reply.
  • A helper placed last in a JSON object needs a space before the closing brace, "RESERVED": {{cicsField request.body 'MQCIH.Version'}} }, so that the object's } is not read as part of the helper's }}.
When a reply cannot be built

A response text that is not a JSON object, an unknown key, an MQCIH field that cannot be set, a COMMAREA for a program with no reply copybook, a value that does not fit its picture, or CommareaHex that is not hex, is logged as an error and answered the way the bridge reports its own failures: ReturnCode MQCRC_BRIDGE_ERROR, CompCode MQCC_FAILED, and the reason as text in place of the COMMAREA. The client's error handling runs, and the log names what to fix.

Try it on the IBM® MQ developer container

This walkthrough runs the whole flow on one machine with the free IBM® MQ for Developers container, a two-copybook sample program and a small Java client that puts a bridge request the way a CICS client does. You need Docker, a Traffic Parrot installation with the IBM® MQ client JAR in lib/external, and a JDK (version 17 or later; the client is run from its source file, which a JRE alone cannot do).

1. Start the queue manager
docker run --env LICENSE=accept --env MQ_QMGR_NAME=QM1 --env MQ_APP_PASSWORD=MQ_APP_PASSWORD --publish 1414:1414 --publish 9443:9443 --detach --name ibm-mq icr.io/ibm-messaging/mq:latest

The developer configuration creates queue manager QM1 with the queues DEV.QUEUE.1 and DEV.QUEUE.2, used here as the bridge request queue and the reply queue, and the channel DEV.APP.SVRCONN for the user app with the password you gave. The connection to it is the Local Docker MQ 9 entry of the example ibm-mq-connections.json; add it to yours if it is not there.

2. Add the copybooks

Create the copybooks directory in the main Traffic Parrot directory with these three files. The program, ACCTBAL, is a synthetic balance inquiry: the request carries an account number, a branch id and a packed rate, and the reply the account number, a packed balance, a status and a binary count.

copybooks/ACCTBAL-REQ.cpy

      * Balance inquiry request COMMAREA.
       01  BALANCE-REQUEST.
           05  ACCOUNT-NO             PIC X(10).
           05  BRANCH-ID              PIC 9(4).
           05  MIN-RATE               PIC SV9(7) COMP-3.

copybooks/ACCTBAL-RPY.cpy

      * Balance inquiry reply COMMAREA.
       01  BALANCE-REPLY.
           05  ACCOUNT-NO             PIC X(10).
           05  BALANCE                PIC S9(9)V99 COMP-3.
           05  STATUS-CODE            PIC X(2).
           05  RESERVED               PIC S9(4) COMP.

copybooks/programs.json

{
  "ACCTBAL": {
    "requestCopybook": "ACCTBAL-REQ.cpy",
    "replyCopybook": "ACCTBAL-RPY.cpy"
  }
}
3. Add the mapping

Save this as ibm-mq-mappings/acctbal.json. It matches any ACCTBAL request whose account number is ten digits, echoes the account number into the reply and answers a balance of 1500.25:

{
  "request": {
    "destination": { "name": "DEV.QUEUE.1", "type": "QUEUE" },
    "bodyMatcher": {
      "and": [
        { "matchesCicsField": { "field": "ProgramName", "equalTo": "ACCTBAL" } },
        { "matchesCicsField": { "field": "COMMAREA.ACCOUNT-NO", "matches": "[0-9]{10}" } }
      ]
    }
  },
  "response": {
    "destination": { "name": "DEV.QUEUE.2", "type": "QUEUE" },
    "text": "{\"MQCIH\": {\"ReturnCode\": 0, \"CompCode\": 0, \"Reason\": 0, \"AbendCode\": \"\"}, \"COMMAREA\": {\"ACCOUNT-NO\": \"{{cicsField request.body 'COMMAREA.ACCOUNT-NO'}}\", \"BALANCE\": 1500.25, \"STATUS-CODE\": \"OK\", \"RESERVED\": 0}}"
  }
}
4. Enable the bridge and start replaying

Set trafficparrot.ibmmq.cicsBridge.enabled=true in trafficparrot.properties, start Traffic Parrot, open IBM MQ, Replay, pick the Local Docker MQ 9 connection for the requests and the responses and click Start, as described under replaying IBM® MQ queues. The mapping appears on the IBM MQ mappings page.

5. Put a bridge request

Save this client as PutCicsRequest.java. It connects as the application would, puts one request for ACCTBAL on DEV.QUEUE.1 with DEV.QUEUE.2 as the reply-to queue, waits for the reply correlated to its message id and prints it. The COMMAREA bytes are what the COBOL program would send for account 1234567890, branch 42 and rate 0.0000001. The connection details can be overridden with the environment variables the code reads, so the same file runs against another queue manager.

import com.ibm.mq.MQGetMessageOptions;
import com.ibm.mq.MQMessage;
import com.ibm.mq.MQPutMessageOptions;
import com.ibm.mq.MQQueue;
import com.ibm.mq.MQQueueManager;
import com.ibm.mq.constants.CMQC;
import com.ibm.mq.headers.MQCIH;

import java.nio.charset.Charset;
import java.util.Hashtable;
import java.util.HexFormat;

/** Puts one CICS DPL bridge request for program ACCTBAL and prints the reply, as a client of the bridge would. */
public class PutCicsRequest {
    /** Big-endian integers and packed decimals: the z/OS encoding the bridge expects. */
    private static final int S390_ENCODING = CMQC.MQENC_INTEGER_NORMAL | CMQC.MQENC_DECIMAL_NORMAL | CMQC.MQENC_FLOAT_S390;
    private static final int EBCDIC_US = 37;
    private static final Charset IBM037 = Charset.forName("IBM037");
    /** ACCTBAL-REQ.cpy: ACCOUNT-NO "1234567890", BRANCH-ID 0042 and MIN-RATE 0.0000001 (packed), as COBOL lays them out. */
    private static final String REQUEST_COMMAREA_HEX = "F1F2F3F4F5F6F7F8F9F0" + "F0F0F4F2" + "0000001C";

    public static void main(String[] args) throws Exception {
        String requestQueue = env("MQ_REQUEST_QUEUE", "DEV.QUEUE.1");
        String replyQueue = env("MQ_REPLY_QUEUE", "DEV.QUEUE.2");
        Hashtable<String, Object> connection = new Hashtable<>();
        connection.put(CMQC.HOST_NAME_PROPERTY, env("MQ_HOST", "localhost"));
        connection.put(CMQC.PORT_PROPERTY, Integer.parseInt(env("MQ_PORT", "1414")));
        connection.put(CMQC.CHANNEL_PROPERTY, env("MQ_CHANNEL", "DEV.APP.SVRCONN"));
        connection.put(CMQC.USER_ID_PROPERTY, env("MQ_USER", "app"));
        connection.put(CMQC.PASSWORD_PROPERTY, env("MQ_PASSWORD", "MQ_APP_PASSWORD"));
        connection.put(CMQC.USE_MQCSP_AUTHENTICATION_PROPERTY, true);
        MQQueueManager queueManager = new MQQueueManager(env("MQ_QUEUE_MANAGER", "QM1"), connection);
        try {
            MQMessage request = bridgeRequest("ACCTBAL", HexFormat.of().parseHex(REQUEST_COMMAREA_HEX), replyQueue);
            MQQueue queue = queueManager.accessQueue(requestQueue, CMQC.MQOO_OUTPUT);
            MQPutMessageOptions putOptions = new MQPutMessageOptions();
            putOptions.options = CMQC.MQPMO_NEW_MSG_ID;
            queue.put(request, putOptions);
            queue.close();
            System.out.println("Put request " + HexFormat.of().withUpperCase().formatHex(request.messageId) + " on " + requestQueue);

            MQMessage reply = new MQMessage();
            reply.correlationId = request.messageId;
            MQGetMessageOptions getOptions = new MQGetMessageOptions();
            getOptions.options = CMQC.MQGMO_WAIT;
            getOptions.matchOptions = CMQC.MQMO_MATCH_CORREL_ID;
            getOptions.waitInterval = 10_000;
            queue = queueManager.accessQueue(replyQueue, CMQC.MQOO_INPUT_SHARED);
            queue.get(reply, getOptions);
            queue.close();

            MQCIH replyHeader = new MQCIH(reply, reply.encoding, reply.characterSet);
            byte[] commarea = new byte[reply.getDataLength()];
            reply.readFully(commarea);
            System.out.println("Reply format " + reply.format.trim() + ": return code " + replyHeader.getReturnCode()
                    + ", completion code " + replyHeader.getCompCode() + ", abend code '" + replyHeader.getAbendCode().trim() + "'");
            System.out.println("Reply COMMAREA as hex: " + HexFormat.of().withUpperCase().formatHex(commarea));
            System.out.println("Reply COMMAREA as EBCDIC text: " + new String(commarea, IBM037));
        } finally {
            queueManager.disconnect();
        }
    }

    /** MQMD format MQCICS, then an MQCIH, the program name padded to 8 characters and the COMMAREA, all EBCDIC. */
    private static MQMessage bridgeRequest(String program, byte[] commarea, String replyQueue) throws Exception {
        MQMessage message = new MQMessage();
        message.format = CMQC.MQFMT_CICS;
        message.encoding = S390_ENCODING;
        message.characterSet = EBCDIC_US;
        message.messageType = CMQC.MQMT_REQUEST;
        message.replyToQueueName = replyQueue;
        message.correlationId = CMQC.MQCI_NEW_SESSION.clone();
        MQCIH header = new MQCIH();
        header.setEncoding(S390_ENCODING);
        header.setCodedCharSetId(EBCDIC_US);
        header.setFormat(CMQC.MQFMT_NONE);
        header.setUOWControl(CMQC.MQCUOWC_ONLY);
        header.setLinkType(CMQC.MQCLT_PROGRAM);
        header.write(message, message.encoding, message.characterSet);
        message.write(String.format("%-8s", program).getBytes(IBM037));
        message.write(commarea);
        return message;
    }

    private static String env(String name, String defaultValue) {
        String value = System.getenv(name);
        return value == null || value.isEmpty() ? defaultValue : value;
    }
}

Run it from the main Traffic Parrot directory with two directories on the class path: lib/external, which holds the client JAR, and lib/trafficparrot/dependencies, which holds the org.json library the client JAR needs and Traffic Parrot ships. On Windows, separate the two with ; instead of :.

java -cp "lib/external/*:lib/trafficparrot/dependencies/*" PutCicsRequest.java

Java first prints a few SLF4J(W) lines, because that class path holds more than one logging provider; they mean nothing here. Then the client prints the reply Traffic Parrot sent:

Put request 414D5120514D31... on DEV.QUEUE.1
Reply format MQCICS: return code 0, completion code 0, abend code ''
Reply COMMAREA as hex: F1F2F3F4F5F6F7F8F9F000000150025CD6D20000
Reply COMMAREA as EBCDIC text: 1234567890  &*OK  

The hex is the reply copybook's layout: the echoed account number, the balance 1500.25 packed with a positive sign, OK, and a two-byte zero. The packed balance and the binary count are not text, so the last line shows their bytes as control characters, which the terminal does not print, and the two printable characters the packed digits happen to decode to.

6. See what Traffic Parrot saw

logs/trafficparrot.log holds the request as it was matched, under CICS bridge request as Traffic Parrot matches it:, the view shown above. Change the account number in the client to one that is not ten digits and run it again: no mapping matches, the client times out, and with trafficparrot.messaging.diagnostics.enabled=true the IBM MQ Requests page lists the request with the field the mapping missed on.

Migrating from CA LISA / DevTest

If your mainframe virtual services were built with DevTest's IBM MQ transport and copybook data protocol, Traffic Parrot for CA LISA / DevTest users maps each of its concepts onto what this page describes and says where the two tools' scope differs.