Follow

CICS INVOKE SERVICE Integration Guide

Table of Contents

  1. Introduction
  2. What is EXEC CICS INVOKE SERVICE?
  3. How It Works
  4. Prerequisites
  5. Step-by-Step Integration
  6. Configuration Guide
  7. Creating Your Service Builder
  8. Calling INVOKE SERVICE from PLI
  9. Container Data Exchange Patterns
  10. Error Handling
  11. Testing and Debugging
  12. Troubleshooting

Introduction

This guide explains how to integrate external SOAP web services into your PLI/CICS application using the EXEC CICS INVOKE SERVICE command.

Who is this for?

  • Developers migrating mainframe CICS applications to Java
  • Teams integrating PLI programs with modern web services
  • Anyone working with Heirloom's PLI-to-Java transpiler

What you'll learn:

  • How INVOKE SERVICE works under the hood
  • How to configure web service endpoints
  • How to write a custom service builder class
  • How to handle request/response data using CICS containers
  • How to debug and troubleshoot issues

Note: All service names, URLs, credentials, container names, operation names, data structures, and numeric values in this document are examples only. Customers must replace them with values that match their own WSDLs, security requirements, CICS naming standards, and PLI copybooks.


What is EXEC CICS INVOKE SERVICE?

EXEC CICS INVOKE SERVICE is a CICS command that allows your PLI application to call external SOAP web services. Think of it as a bridge between your legacy PLI code and modern REST/SOAP APIs.

In plain English:

  1. Your PLI program puts data into a CICS container
  2. INVOKE SERVICE reads that data, builds a SOAP request, and calls the web service
  3. The web service responds
  4. INVOKE SERVICE converts the response back and puts it in a container
  5. Your PLI program reads the response from the container

How It Works

The Big Picture

┌─────────────────────────────────────────────────────────────────┐
│  PLI Program (Transpiled to Java)                               │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │ 1. PUT request data → "DFHWS-DATA" container              │  │
│  │ 2. EXEC CICS INVOKE SERVICE                               │  │
│  │ 3. GET response data ← "DFHWS-DATA" container             │  │
│  └───────────────────────────────────────────────────────────┘  │
└────────────────────────────┬────────────────────────────────────┘
                             │
                             ▼
         ┌───────────────────────────────────────┐
         │   CICSInvokeService (Runtime)         │
         │  ┌─────────────────────────────────┐  │
         │  │ a. Load your service builder    │  │
         │  │    (via reflection)             │  │
         │  │ b. builder.buildRequest()       │  │
         │  │ c. Wrap in SOAP envelope        │  │
         │  │ d. HTTP POST to endpoint        │  │
         │  │ e. builder.buildResponse()      │  │
         │  │ f. PUT response to container    │  │
         │  └─────────────────────────────────┘  │
         └────────────┬──────────────────────────┘
                      │
                      ▼
         ┌────────────────────────────┐
         │   Your Service Builder     │
         │   (implements              │
         │    SoapServiceBuilder)     │
         │                            │
         │ - Reads PLI data           │
         │ - Maps to JAXB objects     │
         │ - Converts to/from XML     │
         │ - Handles authentication   │
         └────────────────────────────┘
                      │
                      ▼
         ┌────────────────────────────┐
         │   External Web Service     │
         │   (SOAP API)               │
         └────────────────────────────┘

The Flow in Detail

Step 1: PLI Program Prepares Data

DCL CHANNEL_NAME CHAR(16) INIT('MY-CHANNEL');
DCL CONTAINER_NAME CHAR(16) INIT('DFHWS-DATA');
DCL MY_REQUEST STRUCTURE;  /* Your request data */

/* Put data into container */
EXEC CICS PUT CONTAINER(CONTAINER_NAME)
          CHANNEL(CHANNEL_NAME)
          FROM(MY_REQUEST);

Step 2: PLI Program Invokes Service

DCL SERVICE_NAME CHAR(32) INIT('MyWebService');

EXEC CICS INVOKE SERVICE(SERVICE_NAME)
          CHANNEL(CHANNEL_NAME)
          RESP(WS_RESP)
          RESP2(WS_RESP2);

Step 3: Runtime Processes Request

  • CICSInvokeService loads class com.heirloomcomputing.cics.webservice.MyWebService
  • Calls buildRequest() to convert container data → XML
  • Wraps XML in SOAP envelope
  • POSTs to configured endpoint URL
  • Receives SOAP response
  • Calls buildResponse() to convert XML → byte[]
  • Writes byte[] to DFHWS-DATA container

Step 4: PLI Program Reads Response

DCL MY_RESPONSE STRUCTURE;  /* Your response data */

EXEC CICS GET CONTAINER(CONTAINER_NAME)
          CHANNEL(CHANNEL_NAME)
          INTO(MY_RESPONSE);

Prerequisites

What You Need

  1. Heirloom PLI Runtime (pli_runtime2)
    • Contains CICSInvokeService class
    • Contains SoapServiceBuilder interface
    • Contains WebserviceUtil helper
  2. WSDL File for your web service
    • Use wsimport or xjc to generate JAXB classes
  3. PLI Data Structures
    • Transpiled Java classes from your PLI copybooks
    • These represent your request/response data
  4. Basic Java Knowledge
    • Understanding of classes, interfaces, and reflection
    • Familiarity with JAXB (Java XML binding)

Dependencies

Add these to your build.gradle or pom.xml:

dependencies {
    implementation 'jakarta.xml.bind:jakarta.xml.bind-api:4.0.0'
    implementation 'com.sun.xml.bind:jaxb-impl:4.0.0'
    implementation 'javax.xml.parsers:jaxb-api:2.3.1'
}

Step-by-Step Integration

Step 1: Generate JAXB Classes from WSDL

Use wsimport or xjc to generate Java classes from your WSDL.

Example:

wsimport -keep -s src/main/java -p com.heirloomcomputing.webservice.account \
  http://myserver:8080/account-demo/account?wsdl

This generates classes like:

  • CreateOrderRequest.java
  • OrderResponse.java
  • OrderDetails.java

Step 2: Create deploy.properties Configuration File

Create file: src/main/webapp/WEB-INF/classes/deploy.properties

Minimum required properties:

# Package containing your service builder classes
webservice.client.package=com.heirloomcomputing.cics.webservice

# Endpoint URL for your service
# Key format: <lowercase-service-name>.endpoint.url
orderservice-ws-endpoint-url=http://localhost:8080/order-service/api

# Optional: Connection timeouts (defaults shown)
webservice.connect.timeout=30000
webservice.read.timeout=60000

# Optional: Enable debug logging
sql.log=true

Key naming convention:

  • If your service class is OrderService
  • The property key should be orderservice-ws-endpoint-url (lowercase, with -ws-endpoint-url suffix)
  • Or use custom key (explained later)

Step 3: Create Your Service Builder Class

Create a class that implements SoapServiceBuilder interface.

File location: src/main/java/com/heirloomcomputing/cics/webservice/MyWebService.java

Basic template:

package com.heirloomcomputing.cics.webservice;

import com.heirloomcomputing.cics.CICSInvokeService;
import com.heirloomcomputing.ecs.exec.Variable;
import jakarta.xml.bind.JAXBException;

public class MyWebService implements SoapServiceBuilder {

    // Keep reference to CICSInvokeService
    private CICSInvokeService _service;

    // REQUIRED: Constructor with CICSInvokeService parameter
    public MyWebService(CICSInvokeService service) {
        this._service = service;
    }

    // REQUIRED: Return property key for endpoint URL
    @Override
    public String getUrlKey() {
        return "mywebservice-ws-endpoint-url";
    }

    // REQUIRED: Build SOAP request XML from container data
    @Override
    public String buildRequest(String operationName) {
        // 1. Read data from DFHWS-DATA container
        // 2. Convert to JAXB objects
        // 3. Marshal to XML string
        // 4. Return XML (without SOAP envelope - runtime adds it)
    }

    // REQUIRED: Parse SOAP response and write to container
    @Override
    public byte[] buildResponse(String operationName, String response)
            throws JAXBException {
        // 1. Parse SOAP response XML
        // 2. Unmarshal to JAXB objects
        // 3. Map to PLI structure
        // 4. Return byte[] (will be written to DFHWS-DATA)
    }

    // REQUIRED: Return authentication header or null
    @Override
    public String getBasicAuthentication() {
        // Return "Basic <base64-encoded-credentials>" or null
        return null;
    }
}

Step 4: Implement buildRequest() Method

This method reads PLI data from containers and builds the SOAP request body.

Example:

@Override
public String buildRequest(String operationName) {
    // Step 1: Read main request data from DFHWS-DATA container
    Variable request_dataarea = this._service.getContainerContent(
        CICSInvokeService.DFHWS_DATA_CONTAINER_NAME,
        1000  // estimated size
    );

    // Step 2: Convert byte[] to your PLI structure
    Order order = new Order();
    order.fromBytes(request_dataarea.getBytes());

    // Step 3: Check if there are additional containers to read
    String order_container = order.detail.container;
    if (order_container != null && !order_container.trim().isEmpty()) {
        Variable order_container_dataarea = this._service.getContainerContent(
            order_container,
            19635
        );

        OrderDetail order_detail = new OrderDetail();
        order_detail.fromBytes(order_container_dataarea.getBytes());

        // Step 4: Map PLI structures to JAXB objects
        RetrieveNextStatesRequest request = mapToRequestObject(order_detail);

        // Step 5: Marshal JAXB to XML string
        return convertRequestToXML(request);
    }

    return "";
}

// Helper: Map PLI structure to JAXB object
private OrderStateRequest mapToRequestObject(OrderDetail pli) {
    OrderStateDetail orderStateDetail = new OrderStateDetail();
    
    // Map fields 
    meldung.setID(WebserviceUtil.trim(pli.id));
    meldung.setName(WebserviceUtil.trim(pli.name));
    // ... map all fields

    OrderStateRequest request = new OrderStateRequest();
    request.setOrderDetail(orderStateDetail);

    return request;
}

// Helper: Convert JAXB object to XML string
private String convertRequestToXML(OrderStateRequest request) {
    try {
        JAXBContext context = JAXBContext.newInstance(request.getClass());
        Marshaller marshaller = context.createMarshaller();
        StringWriter sw = new StringWriter();
        marshaller.marshal(request, sw);

        String xml = sw.toString();

        // Remove XML declaration (runtime adds it)
        xml = xml.replace("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>", "");

        return xml;
    } catch (JAXBException e) {
        throw new RuntimeException("Failed to marshal request", e);
    }
}

Step 5: Implement buildResponse() Method

This method parses the SOAP response and converts it to PLI structures.

Example:

@Override
public byte[] buildResponse(String operationName, String response)
        throws JAXBException {

    // Step 1: Parse SOAP envelope to get Body content
    Node soapBodyObject = null;
    try {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        DocumentBuilder db = dbf.newDocumentBuilder();
        InputSource is = new InputSource(new StringReader(response));

        Document responseSoapEnvelope = db.parse(is);
        NodeList bodyElement = responseSoapEnvelope.getElementsByTagNameNS("*", "Body");
        soapBodyObject = bodyElement.item(0).getFirstChild();
    } catch (Exception e) {
        throw new RuntimeException("Failed to parse SOAP response", e);
    }

    // Step 2: Unmarshal XML to JAXB object
    String soapBodyContent = WebserviceUtil.nodeToString(soapBodyObject);
    StatusResponse statusResponse = parseToResponseObject(soapBodyObject);

    // Step 3: Map JAXB object to PLI structure
    OrderState orderState = new OrderState();
    orderState.correlationid = statusResponse.getCorrelationId();
    orderState.timestamp = statusResponse.getTimestamp();
    orderState.state = statusResponse.getState();

    // Step 4: Handle array data - put in separate container
    String state_cont = "STATE_CONT";
    orderState.container = state_cont;

    Array<OrderStatusContainer> orderStatusContainer =
        new Array<OrderStatusContainer>(
            orderState.getState().size(),
            OrderStatusContainer.class,
            Object.class,
            new OrderStatusContainer(),
            "orderStatusContainer"
        );

    for (String state : statusResponse.getStates().getState()) {
        StatusContainer statusContainer = new StatusContainer();
        statusContainer.item = new OrderState();
        statusContainer.item.state = state;
        orderStatusContainer.fill(statusContainer);
    }

    // Put array data in separate container
    this._service.putDataToContainer(
        orderStatusContainer.toByteArray(),
        state_cont
    );

    // Step 5: Return main response as byte[] (goes to DFHWS-DATA)
    return orderState.toByteArray();
}

// Helper: Unmarshal XML to JAXB object
private StatusResponse parseToResponseObject(Node soapBodyObject)
        throws JAXBException {
    JAXBContext context = JAXBContext.newInstance(StatusResponse.class);
    Unmarshaller unmarshaller = context.createUnmarshaller();
    String xml = WebserviceUtil.nodeToString(soapBodyObject);
    return (StatusResponse) unmarshaller.unmarshal(new StringReader(xml));
}

Step 6: Add Authentication (Optional)

If your web service requires Basic Authentication:

In deploy.properties:

mywebservice-ws-username=<ws-username>
mywebservice-ws-password=<ws-password>

In your service builder:

public static final String USERNAME_KEY = "mywebservice-ws-username";
public static final String PASSWORD_KEY = "mywebservice-ws-password";

@Override
public String getBasicAuthentication() {
    String user = WebserviceUtil.findProperty(USERNAME_KEY);
    String password = WebserviceUtil.findProperty(PASSWORD_KEY);

    if (user == null || password == null || user.isEmpty() || password.isEmpty()) {
        return null;  // No authentication
    }

    String auth = user + ":" + password;
    byte[] encodedAuth = Base64.getEncoder().encode(
        auth.getBytes(StandardCharsets.UTF_8)
    );

    return "Basic " + new String(encodedAuth);
}

Step 7: Update Your PLI Program

In your PLI code:

/* Declare variables */
DCL CHANNEL_NAME CHAR(16) INIT('MY-CHANNEL');
DCL SERVICE_NAME CHAR(32) INIT('MyWebService');
DCL CONTAINER_NAME CHAR(16) INIT('DFHWS-DATA');
DCL WS_RESP FIXED BIN(31);
DCL WS_RESP2 FIXED BIN(31);

DCL 01 MY_REQUEST,
    05 FIELD1 CHAR(10),
    05 FIELD2 CHAR(20);

DCL 01 MY_RESPONSE,
    05 STATUS CHAR(10),
    05 MESSAGE CHAR(100);

/* Step 1: Prepare request data */
MY_REQUEST.FIELD1 = 'VALUE1';
MY_REQUEST.FIELD2 = 'VALUE2';

/* Step 2: Put request in container */
EXEC CICS PUT CONTAINER(CONTAINER_NAME)
          CHANNEL(CHANNEL_NAME)
          FROM(MY_REQUEST)
          RESP(WS_RESP)
          RESP2(WS_RESP2);

IF WS_RESP ^= 0 THEN DO;
    /* Handle error */
END;

/* Step 3: Invoke web service */
EXEC CICS INVOKE SERVICE(SERVICE_NAME)
          CHANNEL(CHANNEL_NAME)
          RESP(WS_RESP)
          RESP2(WS_RESP2);

IF WS_RESP ^= 0 THEN DO;
    /* Handle error - see error handling section */
END;

/* Step 4: Get response from container */
EXEC CICS GET CONTAINER(CONTAINER_NAME)
          CHANNEL(CHANNEL_NAME)
          INTO(MY_RESPONSE)
          RESP(WS_RESP)
          RESP2(WS_RESP2);

IF WS_RESP ^= 0 THEN DO;
    /* Handle error */
END;

/* Step 5: Process response */
PUT SKIP LIST('Status: ' || MY_RESPONSE.STATUS);
PUT SKIP LIST('Message: ' || MY_RESPONSE.MESSAGE);

Configuration Guide

deploy.properties Reference

Location: src/main/webapp/WEB-INF/classes/deploy.properties

Standard Properties:

Property Required Default Description
webservice.client.package Yes com.heirloomcomputing.cics.webservice Package containing service builder classes
webservice.connect.timeout No 30000 HTTP connection timeout (milliseconds)
webservice.read.timeout No 60000 HTTP read timeout (milliseconds)
sql.log No false Enable debug logging (true/false)

Service-Specific Properties:

Each service needs an endpoint URL. The property key is returned by getUrlKey().

Pattern 1: Auto-derived key (not recommended)

# For class: MyWebService
# Key: mywebservice-ws-endpoint-url (lowercase class name + suffix)
mywebservice-ws-endpoint-url=http://localhost:8080/api/myservice

Pattern 2: Custom key (recommended)

# In deploy.properties
orderservice-ws-endpoint-url=http://localhost:8080/order-service/api

inventory.ws.endpoint.url=http://localhost:8080/inventory-service/api
inventory.ws.username=<ws-username>
inventory.ws.password=<ws-password>
// In your service builder
public String getUrlKey() {
    return "orderservice-ws-endpoint-url";  // Matches property name
}

Property Resolution Order

WebserviceUtil.findProperty() searches in this order:

  1. System Properties (-Dproperty=value JVM arguments)
  2. RuntimeEnvironment (global parameters)
  3. deploy.properties (classpath resource)

Example:

# Override endpoint via JVM property
java -Dorderservice-ws-endpoint-url=http://prod-server:8080/order-service/api \
     -jar myapp.jar

Creating Your Service Builder

SoapServiceBuilder Interface

All service builders must implement this interface:

package com.heirloomcomputing.cics.webservice;

import jakarta.xml.bind.JAXBException;

public interface SoapServiceBuilder {
    /**
     * Returns the property key for the endpoint URL.
     * This key is used to look up the URL in deploy.properties.
     *
     * @return Property key (e.g., "myservice-ws-endpoint-url")
     */
    String getUrlKey();

    /**
     * Builds the SOAP request body XML from container data.
     * The runtime will wrap this in a SOAP envelope.
     *
     * @param operationName Operation name (if applicable)
     * @return XML string (without SOAP envelope)
     */
    String buildRequest(String operationName);

    /**
     * Parses the SOAP response and returns byte[] to write to container.
     * The byte[] will be written to the DFHWS-DATA container.
     *
     * @param operationName Operation name (if applicable)
     * @param response Complete SOAP response XML
     * @return byte[] to write to DFHWS-DATA container
     * @throws JAXBException if XML parsing fails
     */
    byte[] buildResponse(String operationName, String response)
        throws JAXBException;

    /**
     * Returns Basic Authentication header value or null.
     *
     * @return "Basic <base64>" or null for no authentication
     */
    String getBasicAuthentication();
}

Design Patterns

Pattern 1: Simple Service (Single Operation)

Use when you have a simple service with one operation.

Example:

public class OrderService implements SoapServiceBuilder {
    private CICSInvokeService _service;

    public OrderService(CICSInvokeService service) {
        this._service = service;
    }

    @Override
    public String getUrlKey() {
        return "orderservice-ws-endpoint-url";
    }

    @Override
    public String getBasicAuthentication() {
        return null;  // No authentication
    }

    @Override
    public String buildRequest(String operationName) {
        // Single operation - ignore operationName
        Variable data = this._service.getContainerContent(
            CICSInvokeService.DFHWS_DATA_CONTAINER_NAME, 1000
        );

        // Build and return XML...
    }

    @Override
    public byte[] buildResponse(String operationName, String response) {
        // Single operation - ignore operationName
        // Parse response and return byte[]...
    }
}

Pattern 2: Multi-Operation Service

Use when your service has multiple operations.

Example:

public class InventoryService implements SoapServiceBuilder {
    private CICSInvokeService _service;

    public InventoryService(CICSInvokeService service) {
        this._service = service;
    }

    @Override
    public String buildRequest(String operationName) {
        Variable data = this._service.getContainerContent(
            CICSInvokeService.DFHWS_DATA_CONTAINER_NAME, 1000
        );

        // Route based on operation
        if ("orderDetail".equals(operationName)) {
            return buildForOrderDetailRequest(data);
        } else if ("orderAddress".equals(operationName)) {
            return buildForOrderAddressRequest(data);
        } else {
            throw new IllegalArgumentException("Unknown operation: " + operationName);
        }
    }

    @Override
    public byte[] buildResponse(String operationName, String response) {
        // Route based on operation
        if ("orderDetail".equals(operationName)) {
            return parseForOrderDetailResponse(response);
        } else if ("orderAddress".equals(operationName)) {
            return parseForOrderAddressResponse(response);
        } else {
            throw new IllegalArgumentException("Unknown operation: " + operationName);
        }
    }

    private String buildForOrderDetailRequest(Variable data) {
        // Build specific request...
    }

    private byte[] parseForOrderDetailResponse(String response) {
        // Parse specific response...
    }
}

In PLI:

DCL OPERATION_NAME CHAR(32) INIT('orderDetail');

EXEC CICS INVOKE SERVICE(SERVICE_NAME)
          OPERATION(OPERATION_NAME)
          CHANNEL(CHANNEL_NAME)
          RESP(WS_RESP)
          RESP2(WS_RESP2);

Pattern 3: Service with Authentication

Example:

public class SecureService implements SoapServiceBuilder {
    public static final String USERNAME_KEY = "myservice-ws-username";
    public static final String PASSWORD_KEY = "myservice-ws-password";

    private CICSInvokeService _service;

    public SecureService(CICSInvokeService service) {
        this._service = service;
    }

    @Override
    public String getBasicAuthentication() {
        String user = WebserviceUtil.findProperty(USERNAME_KEY);
        String password = WebserviceUtil.findProperty(PASSWORD_KEY);

        if (user == null || password == null) {
            System.err.println("WARNING: No credentials configured for " + getUrlKey());
            return null;
        }

        String auth = user + ":" + password;
        byte[] encodedAuth = Base64.getEncoder().encode(
            auth.getBytes(StandardCharsets.UTF_8)
        );

        return "Basic " + new String(encodedAuth);
    }

    // ... implement other methods
}

In deploy.properties:

myservice-ws-endpoint-url=https://secure-api.example.com/service
myservice-ws-username=<ws-username>
myservice-ws-password=<ws-password>

Calling INVOKE SERVICE from PLI

Basic Call Pattern

Transpiled Java equivalent:

// Variable declarations
private PLIString channel_name = new PLIString("MY-CHANNEL", 16);
private PLIString webservice_name = new PLIString("MyWebService", 32);
private PLIString container_name = new PLIString("DFHWS-DATA", 16);

private MyRequest myRequest = new MyRequest();
private MyResponse myResponse = new MyResponse();

// Method implementation
public void invokeWebService() {
    CICSPutContainer cicsPutContainer;
    CICSInvokeService cicsInvokeService;
    CICSGetContainer cicsGetContainer;
    Integer lresp = 0;
    Integer lresp2 = 0;

    // Step 1: PUT request data to container
    cicsPutContainer = CICSBuilder.getInstance()
        .putContainer(_transenv)
        .container(container_name)
        .channel(channel_name)
        .from(myRequest)
        .resp(lresp)
        .resp2(lresp2);
    cics = cicsPutContainer.execute(this);
    lresp = (Integer) cicsPutContainer.resp();
    lresp2 = (Integer) cicsPutContainer.resp2();

    if (lresp != 0) {
        // Handle error
        throw new RuntimeException("PUT CONTAINER failed: " + lresp);
    }

    // Step 2: Invoke web service
    cicsInvokeService = CICSBuilder.getInstance()
        .invokeService(_transenv)
        .webservice(webservice_name)
        .service(webservice_name)
        .channel(channel_name)
        .resp(lresp)
        .resp2(lresp2);
    cics = cicsInvokeService.execute(this);
    lresp = (Integer) cicsInvokeService.resp();
    lresp2 = (Integer) cicsInvokeService.resp2();

    if (lresp != 0) {
        // Handle error - see error handling section
        handleInvokeServiceError(lresp, lresp2);
    }

    // Step 3: GET response from container
    myResponse.assign("");  // Clear response structure
    cicsGetContainer = CICSBuilder.getInstance()
        .getContainer(_transenv)
        .container(container_name)
        .channel(channel_name)
        .into(myResponse)
        .resp(lresp)
        .resp2(lresp2);
    cics = cicsGetContainer.execute(this);
    lresp = (Integer) cicsGetContainer.resp();
    lresp2 = (Integer) cicsGetContainer.resp2();

    if (lresp != 0) {
        throw new RuntimeException("GET CONTAINER failed: " + lresp);
    }

    // Step 4: Process response
    System.out.println("Response: " + myResponse.toString());
}

With Operation Name

Transpiled Java:

private PLIString operation_name = new PLIString("orderDetail", 255);

cicsInvokeService = CICSBuilder.getInstance()
    .invokeService(_transenv)
    .webservice(webservice_name)
    .service(webservice_name)
    .channel(channel_name)
    .operation(operation_name)  // Add operation
    .resp(lresp)
    .resp2(lresp2);

Container Data Exchange Patterns

Pattern 1: Simple Request/Response (Single Container)

Use when all data fits in one container.

PLI Side:

DCL 01 MY_REQUEST,
    05 FIELD1 CHAR(50),
    05 FIELD2 FIXED BIN(31);

DCL 01 MY_RESPONSE,
    05 STATUS CHAR(10),
    05 MESSAGE CHAR(100);

/* Put request */
EXEC CICS PUT CONTAINER('DFHWS-DATA')
          CHANNEL('MY-CHANNEL')
          FROM(MY_REQUEST);

/* Invoke */
EXEC CICS INVOKE SERVICE('MyService')
          CHANNEL('MY-CHANNEL');

/* Get response */
EXEC CICS GET CONTAINER('DFHWS-DATA')
          CHANNEL('MY-CHANNEL')
          INTO(MY_RESPONSE);

Service Builder:

@Override
public String buildRequest(String operationName) {
    // Read single container
    Variable data = this._service.getContainerContent(
        CICSInvokeService.DFHWS_DATA_CONTAINER_NAME,
        1000
    );

    MyRequestPLI pliRequest = new MyRequestPLI();
    pliRequest.fromBytes(data.getBytes());

    // Map to JAXB and return XML...
}

@Override
public byte[] buildResponse(String operationName, String response) {
    // Parse response
    MyResponseJAXB jaxbResponse = parseResponse(response);

    // Map to PLI structure
    MyResponsePLI pliResponse = new MyResponsePLI();
    pliResponse.status = jaxbResponse.getStatus();
    pliResponse.message = jaxbResponse.getMessage();

    // Return as byte[]
    return pliResponse.toByteArray();
}

Pattern 2: Request with Additional Containers

Use when request has variable-length or array data.

PLI Side:

DCL 01 MY_REQUEST,
    05 ITEM_COUNT FIXED BIN(31),
    05 ITEM_CONTAINER CHAR(16);  /* Name of container with array data */

DCL 01 MY_ITEMS(100),  /* Array */
    05 ITEM_ID CHAR(10),
    05 ITEM_NAME CHAR(50);

/* Put array data in separate container */
EXEC CICS PUT CONTAINER('ITEMS_CONT')
          CHANNEL('MY-CHANNEL')
          FROM(MY_ITEMS);

/* Put main request with reference to array container */
MY_REQUEST.ITEM_COUNT = 100;
MY_REQUEST.ITEM_CONTAINER = 'ITEMS_CONT';

EXEC CICS PUT CONTAINER('DFHWS-DATA')
          CHANNEL('MY-CHANNEL')
          FROM(MY_REQUEST);

/* Invoke service */
EXEC CICS INVOKE SERVICE('MyService')
          CHANNEL('MY-CHANNEL');

Service Builder:

@Override
public String buildRequest(String operationName) {
    // Step 1: Read main request
    Variable mainData = this._service.getContainerContent(
        CICSInvokeService.DFHWS_DATA_CONTAINER_NAME,
        1000
    );

    MyRequestPLI pliRequest = new MyRequestPLI();
    pliRequest.fromBytes(mainData.getBytes());

    // Step 2: Read additional container if specified
    String itemContainerName = pliRequest.item_container.trim();
    if (!itemContainerName.isEmpty()) {
        int itemCount = pliRequest.item_count;

        Variable itemData = this._service.getContainerContent(
            itemContainerName,
            itemCount * 60  // 60 bytes per item
        );

        // Parse array
        Array<MyItemPLI> items = new Array<MyItemPLI>(
            itemCount,
            MyItemPLI.class,
            Object.class,
            new MyItemPLI(),
            "items"
        );
        items.fromBytes(itemData.getBytes());

        // Map to JAXB with array data...
    }

    // Build and return XML...
}

Pattern 3: Response with Array Data

Use when response contains variable-length arrays.

Service Builder:

@Override
public byte[] buildResponse(String operationName, String response) {
    // Parse SOAP response
    MyResponseJAXB jaxbResponse = parseResponse(response);

    // Main response structure
    MyResponsePLI pliResponse = new MyResponsePLI();
    pliResponse.status = jaxbResponse.getStatus();

    // Handle array data
    List<ItemJAXB> items = jaxbResponse.getItems().getItem();
    pliResponse.item_count = items.size();

    String itemContainerName = "RESULT_ITEMS";
    pliResponse.item_container = itemContainerName;

    // Create PLI array
    Array<MyItemPLI> pliItems = new Array<MyItemPLI>(
        items.size(),
        MyItemPLI.class,
        Object.class,
        new MyItemPLI(),
        "items"
    );

    // Map items
    for (ItemJAXB jaxbItem : items) {
        MyItemPLI pliItem = new MyItemPLI();
        pliItem.item_id = jaxbItem.getId();
        pliItem.item_name = jaxbItem.getName();
        pliItems.fill(pliItem);
    }

    // Write array to separate container
    this._service.putDataToContainer(
        pliItems.toByteArray(),
        itemContainerName
    );

    // Return main response
    return pliResponse.toByteArray();
}

PLI Side:

DCL 01 MY_RESPONSE,
    05 STATUS CHAR(10),
    05 ITEM_COUNT FIXED BIN(31),
    05 ITEM_CONTAINER CHAR(16);

DCL 01 MY_ITEMS(100),
    05 ITEM_ID CHAR(10),
    05 ITEM_NAME CHAR(50);

/* Get main response */
EXEC CICS GET CONTAINER('DFHWS-DATA')
          CHANNEL('MY-CHANNEL')
          INTO(MY_RESPONSE);

/* Get array data from additional container */
IF MY_RESPONSE.ITEM_COUNT > 0 THEN DO;
    EXEC CICS GET CONTAINER(MY_RESPONSE.ITEM_CONTAINER)
              CHANNEL('MY-CHANNEL')
              INTO(MY_ITEMS);

    /* Process items */
    DO I = 1 TO MY_RESPONSE.ITEM_COUNT;
        PUT SKIP LIST('Item: ' || MY_ITEMS(I).ITEM_NAME);
    END;
END;

Error Handling

CICS INVOKE SERVICE Response Codes

RESP RESP2 Meaning Action
0 0 Success Continue
13 - Service not found Check service name and class exists
16 6 SOAP fault Parse DFHWS-BODY for fault details
16 13-14 Invalid request XML Check XML generation in buildRequest()
16 17 No response from service Service returned empty response
16 20 General error Check logs for exception details
22 - Invalid container Check container/channel names
115 - Timeout Increase timeout or check service availability

Error Handling in PLI

Transpiled Java:

private void checkIfWebServiceCommandOk(Integer resp, Integer resp2) {
    CICSGetContainer cicsGetContainer;
    Integer lresp = 0;
    Integer lresp2 = 0;
    PLIString detailedErrorMessage = new PLIString(4000);

    if (resp == 0) {
        return;  // Success
    }

    // Handle different error scenarios
    switch (resp) {
        case 115:  // Timeout
            System.err.println("Web service timeout: resp=" + resp +
                ", resp2=" + resp2);
            // Signal WSERR condition
            PLIRuntime.getInstance().signal("WSERR", this);
            break;

        case 16:  // Invalid
            switch (resp2) {
                case 6:  // SOAP Fault
                    // Read SOAP fault details from DFHWS-BODY
                    PLIString bodyStr = new PLIString(4000);
                    cicsGetContainer = CICSBuilder.getInstance()
                        .getContainer(_transenv)
                        .container(new PLIString("DFHWS-BODY", 16))
                        .channel(channel_name)
                        .into(bodyStr)
                        .resp(lresp)
                        .resp2(lresp2);
                    cics = cicsGetContainer.execute(this);
                    bodyStr = (PLIString) cicsGetContainer.into();

                    System.err.println("SOAP Fault: " + bodyStr);

                    // Read namespace info
                    PLIString nsStr = new PLIString(4000);
                    cicsGetContainer = CICSBuilder.getInstance()
                        .getContainer(_transenv)
                        .container(new PLIString("DFHWS-XMLNS", 16))
                        .channel(channel_name)
                        .into(nsStr)
                        .resp(lresp)
                        .resp2(lresp2);
                    cics = cicsGetContainer.execute(this);
                    nsStr = (PLIString) cicsGetContainer.into();

                    System.err.println("Namespace: " + nsStr);

                    // Signal RESERR condition
                    PLIRuntime.getInstance().signal("RESERR", this);
                    break;

                case 13:
                case 14:  // Invalid request
                    // Read error message
                    cicsGetContainer = CICSBuilder.getInstance()
                        .getContainer(_transenv)
                        .container(new PLIString("DFH-XML-ERRORMSG", 16))
                        .channel(channel_name)
                        .into(detailedErrorMessage)
                        .resp(lresp)
                        .resp2(lresp2);
                    cics = cicsGetContainer.execute(this);
                    detailedErrorMessage = (PLIString) cicsGetContainer.into();

                    System.err.println("Invalid request: " + detailedErrorMessage);
                    PLIRuntime.getInstance().signal("WSERR", this);
                    break;

                case 17:  // No response
                    System.err.println("Web service did not return a response");
                    PLIRuntime.getInstance().signal("WSERR", this);
                    break;

                default:
                    System.err.println("Web service error: resp=" + resp +
                        ", resp2=" + resp2);
                    PLIRuntime.getInstance().signal("WSERR", this);
                    break;
            }
            break;

        case 13:  // Service not found
            System.err.println("Service not found: " + webservice_name);
            PLIRuntime.getInstance().signal("WSERR", this);
            break;

        default:
            System.err.println("Unexpected error: resp=" + resp +
                ", resp2=" + resp2);
            PLIRuntime.getInstance().signal("WSERR", this);
            break;
    }
}

Error Handling in Service Builder

Handle SOAP Faults:

@Override
public byte[] buildResponse(String operationName, String response)
        throws JAXBException {

    // Check for SOAP fault first
    Node faultNode = WebserviceUtil.parseFaultNode(response);
    if (faultNode != null) {
        // Parse fault details
        String faultString = extractFaultString(faultNode);
        System.err.println("SOAP Fault: " + faultString);

        // Create error response
        MyErrorResponse errorResponse = new MyErrorResponse();
        errorResponse.error_code = "SOAP_FAULT";
        errorResponse.error_message = faultString;

        return errorResponse.toByteArray();
    }

    // Parse normal response
    Node bodyNode = WebserviceUtil.parseBodyNode(response);
    // ... normal processing
}

private String extractFaultString(Node faultNode) {
    // Parse <faultstring> element
    NodeList children = faultNode.getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
        Node child = children.item(i);
        if ("faultstring".equals(child.getLocalName())) {
            return child.getTextContent();
        }
    }
    return "Unknown SOAP fault";
}

Common Errors and Solutions

Error: Service not found (RESP=13)

Problem: Class com.heirloomcomputing.cics.webservice.MyService not found
Solution:
  1. Check webservice.client.package in deploy.properties
  2. Verify class name matches SERVICE name in INVOKE command
  3. Ensure class is compiled and in classpath

Error: Timeout (RESP=115)

Problem: Web service took too long to respond
Solution:
  1. Check service is running and accessible
  2. Increase webservice.read.timeout in deploy.properties
  3. Check network connectivity

Error: SOAP Fault (RESP=16, RESP2=6)

Problem: Web service returned SOAP fault
Solution:
  1. Read DFHWS-BODY container for fault details
  2. Check request data is valid
  3. Verify service is functioning correctly

Error: Invalid request (RESP=16, RESP2=13)

Problem: XML generated by buildRequest() is invalid
Solution:
  1. Enable sql.log=true to see generated XML(This flag enables verbose web service logging and may expose sensitive data.)
  2. Validate XML against WSDL schema
  3. Check namespace declarations
  4. Verify all required fields are populated

Testing and Debugging

Enable Debug Logging

In deploy.properties:

sql.log=true

In your code:

if (WebserviceUtil.isSqlLogEnabled()) {
    System.out.println("Request XML: " + requestXml);
    System.out.println("Response XML: " + responseXml);
}

Note : This flag enables verbose web service logging and may expose sensitive data.

Test SOAP Request/Response with Real Service

Use SoapUI or curl to test your actual web service.

Using curl:

# Save request XML from logs
cat > request.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Header/>
  <soapenv:Body>
    <myRequest>
      <orderDetail>
        <order>
          <status>MOVE</status>
          <id>12345</id>
          <!-- ... -->
        </order>
      </orderDetail>
    </myRequest>
  </soapenv:Body>
</soapenv:Envelope>
EOF

# Test web service
curl -X POST \
  -H "Content-Type: text/xml; charset=utf-8" \
  -H "SOAPAction: " \
  --data @request.xml \
  http://localhost:8080/order-service/api

# With authentication
curl -X POST \
  -H "Content-Type: text/xml; charset=utf-8" \
  -H "SOAPAction: " \
  -u username:password \
  --data @request.xml \
  http://localhost:8080/order-service/api

Debugging Tips

1. Print all container contents:

if (WebserviceUtil.isSqlLogEnabled()) {
    Variable data = this._service.getContainerContent(
        CICSInvokeService.DFHWS_DATA_CONTAINER_NAME, 5000
    );

    System.out.println("Container data (hex): " +
        bytesToHex(data.getBytes()));
    System.out.println("Container data (string): " +
        new String(data.getBytes(), StandardCharsets.UTF_8));
}

2. Validate your JAXB mapping:

// Marshal to XML and unmarshal back to verify roundtrip
MyRequest original = new MyRequest();
original.setField1("test");

String xml = marshalToXml(original);
MyRequest deserialized = unmarshalFromXml(xml);

assertEquals(original.getField1(), deserialized.getField1());

Troubleshooting

Problem: ClassNotFoundException for service builder

Error:

java.lang.ClassNotFoundException: com.heirloomcomputing.cics.webservice.MyService

Solutions:

  1. Check webservice.client.package property matches your package
  2. Verify class is compiled: ls build/classes/java/main/com/heirloomcomputing/cics/webservice/
  3. Check class name matches exactly (case-sensitive)
  4. Ensure class is in classpath/WAR file

Problem: NoSuchMethodException for constructor

Error:

java.lang.NoSuchMethodException: MyService.<init>(CICSInvokeService)

Solution: Add required constructor:

public class MyService implements SoapServiceBuilder {
    private CICSInvokeService _service;

    // REQUIRED CONSTRUCTOR
    public MyService(CICSInvokeService service) {
        this._service = service;
    }
}

Problem: Endpoint URL not found

Error:

WARNING:Unable to find the property for key (myservice-endpoint-url)

Solutions:

  1. Check property key returned by getUrlKey() matches deploy.properties
  2. Verify deploy.properties is in classpath (WEB-INF/classes/)
  3. Try absolute path: -Dmy.deploy.props=/full/path/to/deploy.properties

Problem: Connection timeout

Error:

java.net.SocketTimeoutException: connect timed out

Solutions:

  1. Verify endpoint URL is correct and accessible
  2. Check firewall/network configuration
  3. Test with curl: curl -v http://your-endpoint:8080/service
  4. Increase timeout: webservice.connect.timeout=60000

Problem: Read timeout

Error:

java.net.SocketTimeoutException: Read timed out

Solutions:

  1. Service is slow - increase timeout: webservice.read.timeout=120000
  2. Service might be hung - check service logs
  3. Request too large - optimize data size

Problem: XML parsing error in buildResponse

Error:

org.xml.sax.SAXParseException: Content is not allowed in prolog

Solutions:

  1. Response might contain BOM or whitespace - trim it
  2. Check response is valid XML
  3. Service might be returning HTML error page - check HTTP status
  4. Print raw response to inspect: System.out.println(response)

Problem: JAXB unmarshalling error

Error:

jakarta.xml.bind.UnmarshalException: unexpected element

Solutions:

  1. Namespace mismatch - check JAXB annotations match WSDL
  2. Element name mismatch - verify case sensitivity
  3. Missing elements - check if service changed
  4. Regenerate JAXB classes from latest WSDL

Problem: Data corruption in containers

Symptom: Garbage characters in response

Solutions:

  1. Check byte order (endianness)
  2. Verify structure alignment in PLI
  3. Use CHAR for strings, not BIT
  4. Ensure matching structure definitions between PLI and Java

Problem: Authentication fails

Error:

HTTP 401 Unauthorized

Solutions:

  1. Verify username/password in deploy.properties
  2. Check getBasicAuthentication() returns correct format: "Basic <base64>"
  3. Test credentials with curl:

    curl -u username:password http://endpoint/service
    
  4. Check if service requires different auth (OAuth, API key, etc.)

Problem: SOAP Fault returned

Error:

RESP=16, RESP2=6

Solutions:

  1. Read DFHWS-BODY container for fault details
  2. Common faults:
    • Server.userException: Business logic error
    • Client.validationError: Invalid input data
    • Server.generalException: Server error
  3. Fix data issues and retry

Summary

You've learned:

  1. What INVOKE SERVICE does - Bridges PLI programs and SOAP services
  2. How it works - Containers → Service Builder → HTTP → Response
  3. How to configure - deploy.properties with endpoints and timeouts
  4. How to implement - SoapServiceBuilder interface with 4 methods
  5. How to call - PUT → INVOKE → GET pattern in PLI
  6. How to handle errors - RESP/RESP2 codes and SOAP faults
  7. How to debug - Logging, testing, and troubleshooting

Next Steps:

  1. Generate JAXB classes from your WSDL
  2. Create your service builder class
  3. Configure deploy.properties
  4. Update your PLI program
  5. Test and debug
  6. Deploy to production

Remember:

  • Always trim PLI strings before mapping to JAXB
  • Use separate containers for array data
  • Enable sql.log=true during development
  • Handle SOAP faults gracefully

Good luck with your integration!

Was this article helpful?
0 out of 0 found this helpful
Have more questions? Submit a request

0 Comments

Article is closed for comments.
Powered by Zendesk