# CICS over IBM® MQ

## 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](https://trafficparrot.com/documentation/5.65.x/ibmmq.html#define-connections) to the queue manager the application uses, the [IBM® MQ client JAR](https://trafficparrot.com/documentation/5.65.x/ibmmq.html#ibm-mq-libs) 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](https://trafficparrot.com/documentation/5.65.x/cics.html#try-it) 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 or recorded; the log reports it as `Unsupported type: 'MQIMS'`.
- **Recording inside CICS with an agent.** Traffic Parrot records at the queue, between the application and the bridge (see [Recording](https://trafficparrot.com/documentation/5.65.x/cics.html#recording)), and installs nothing in the region.
- **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](https://trafficparrot.com/documentation/5.65.x/http.html) or [SOAP](https://trafficparrot.com/documentation/5.65.x/http.html#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](https://trafficparrot.com/documentation/5.65.x/cics.html#enable), 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](https://trafficparrot.com/documentation/5.65.x/cics.html#request-view): the program name, the decoded COMMAREA fields, the MQMD user and the `MQCIH` 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](https://trafficparrot.com/documentation/5.65.x/ibmmq.html) page describes.

## Enable the bridge

The bridge is off by default. Set this property in [`trafficparrot.properties`](https://trafficparrot.com/documentation/5.65.x/properties.html#ibmmq-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 or record 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
```

```json
{
  "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 program name, the decoded COMMAREA, the user id from the MQMD and the `MQCIH` fields. For a request to the `ACCTBAL` program of the walkthrough, with the copybook above, the view is:

```json
{
  "ProgramName" : "ACCTBAL",
  "COMMAREA" : {
    "ACCOUNT-NO" : "1234567890",
    "BRANCH-ID" : 42,
    "MIN-RATE" : 0.0000001
  },
  "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` 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 `CommareaNotDecoded`, the reason, and `CommareaHex`, the bytes as hex, in place of `COMMAREA`, and the request can still be matched on the hex. An empty COMMAREA is `"CommareaHex" : ""`.
- `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, the log or a recording. Numbers are JSON numbers, text is unpadded, and `Facility` is hex.

With [`trafficparrot.ibmmq.logger.logMessageBody`](https://trafficparrot.com/documentation/5.65.x/properties.html#ibmmq-properties) 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`](https://trafficparrot.com/documentation/5.65.x/properties.html#jms-properties) is `true`.

## Matching a bridge request

Every [request body matcher](https://trafficparrot.com/documentation/5.65.x/ibmmq.html#request-body-matchers) 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`:

```json
"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:

```json
"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 field matchers with `and`, `or` or `not`, choose `AND`, `OR` or `NOT` in the *Request body* dropdown, pick `matches CICS field` for a sub-matcher and type its object as the value; a mapping file that combines them, like the walkthrough's, opens in the editor with each sub-matcher shown. The matcher's expected value can be kept in a file under `__files` like any other [request body matcher value from a file](https://trafficparrot.com/documentation/5.65.x/ibmmq.html#edit-mq-request-body-file).

![IBM MQ Edit mapping dialog with a CICS bridge mapping: the request queue, matches CICS field chosen as the request body matcher with its JSON object as the value, and a response body holding the MQCIH and COMMAREA JSON](https://trafficparrot.com/documentation/5.65.x/img/mq/cics-mapping-editor.png)

##### 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 three keys:

```json
{
  "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`: 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`, as a [recorded](https://trafficparrot.com/documentation/5.65.x/cics.html#recording) reply does, sends the bytes only while the copybook decodes them to exactly the `COMMAREA` fields; once you edit a field, the fields are encoded instead.

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](https://trafficparrot.com/documentation/5.65.x/ibmmq.html#edit-mq-response-headers) 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:

```json
{ "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](https://trafficparrot.com/documentation/5.65.x/dynamic.html), 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.

## Recording a bridge conversation

With the bridge enabled, [IBM® MQ queue recording](https://trafficparrot.com/documentation/5.65.x/ibmmq.html#recording-mq-queues-external) between the application and the real bridge records each call as a mapping: record requests from the queue the application puts to, forward them to the bridge's request queue, and record the bridge's replies from its reply queue. Requests and replies are paired on the request's message id, with either message matcher. The forwarded request is the client's message byte for byte, its `Authenticator` included, so a bridge that checks it still can.

A recorded mapping sits on the bridge's request queue, matches the program name and the decoded COMMAREA with `equalToJson`, ignoring the other header fields and the user id, and replies with what the bridge sent:

```json
{
  "request": {
    "destination": { "name": "CICS.BRIDGE.REQUEST", "type": "QUEUE" },
    "bodyMatcher": {
      "equalToJson": "{\n  \"ProgramName\" : \"ACCTBAL\",\n  \"COMMAREA\" : {\n    \"ACCOUNT-NO\" : \"1234567890\",\n    \"BRANCH-ID\" : 42,\n    \"MIN-RATE\" : 0.0000001\n  }\n}",
      "ignoreArrayOrder": false,
      "ignoreExtraElements": true
    }
  },
  "response": {
    "destination": { "name": "CICS.BRIDGE.REPLY", "type": "QUEUE" },
    "text": "{\n  \"MQCIH\" : {\n    \"ReturnCode\" : 0,\n    \"CompCode\" : 0,\n    \"Reason\" : 0,\n    \"AbendCode\" : \"\"\n  },\n  \"COMMAREA\" : {\n    \"ACCOUNT-NO\" : \"1234567890\",\n    \"BALANCE\" : 1500.25,\n    \"STATUS-CODE\" : \"OK\",\n    \"RESERVED\" : 0\n  },\n  \"CommareaHex\" : \"F1F2F3F4F5F6F7F8F9F000000150025FD6D20000\"\n}"
  }
}
```

- `MQCIH` holds the four fields a reply sets. `COMMAREA` is the reply decoded by the program's reply copybook, when it fits, and `CommareaHex` is the bridge's bytes. A program with no copybook is recorded with `CommareaHex` on both sides.
- Replay sends `CommareaHex` unchanged, byte for byte, so a reply the bridge wrote with an `F` sign nibble or with data in its `FILLER` is played back exactly. Once you edit a `COMMAREA` field, the whole record is encoded from the fields instead: a positive sign becomes `C` and `FILLER` becomes spaces. To hand-write bytes, delete `COMMAREA`. With `REDEFINES`, editing one alternative gives a bridge error until the others are deleted, since they no longer agree.
- The `Authenticator` is never recorded or logged.

##### What to know

- The forwarded request is put under Traffic Parrot's own identity, so a bridge running with `AUTH=IDENTIFY` or `AUTH=VERIFY_*` sees that user, not the application's.
- A reply shorter than its copybook, because the request set `OutputDataLength` or `MQCIH_REPLY_WITHOUT_NULLS`, is recorded as `CommareaHex` only, with a warning in the log.
- A request whose `MQCIH` is chained behind an `MQRFH2` header is not recorded as a bridge exchange.
- Recording was tested against a stand-in bridge on IBM® MQ, not against a CICS region.

## Try it on the IBM® MQ developer container

This walkthrough runs the whole flow on one machine with the free [IBM® MQ for Developers](https://github.com/ibm-messaging/mq-container) 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](https://trafficparrot.com/documentation/5.65.x/ibmmq.html#ibm-mq-libs) 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`](https://trafficparrot.com/documentation/5.65.x/ibmmq.html#define-connections); 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`

```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:

```json
{
  "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](https://trafficparrot.com/documentation/5.65.x/ibmmq.html#replaying-mq-queues-external). 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.

```java
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](https://trafficparrot.com/documentation/5.65.x/cics.html#request-view). 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](https://trafficparrot.com/documentation/5.65.x/devtest_users.html) maps each of its concepts onto what this page describes and says where the two tools' scope differs.
