Table of Contents
- Introduction
- What is EXEC CICS INVOKE SERVICE?
- How It Works
- Prerequisites
- Step-by-Step Integration
- Configuration Guide
- Creating Your Service Builder
- Calling INVOKE SERVICE from PLI
- Container Data Exchange Patterns
- Error Handling
- Testing and Debugging
- 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:
- Your PLI program puts data into a CICS container
- INVOKE SERVICE reads that data, builds a SOAP request, and calls the web service
- The web service responds
- INVOKE SERVICE converts the response back and puts it in a container
- 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-DATAcontainer
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
-
Heirloom PLI Runtime (
pli_runtime2)- Contains
CICSInvokeServiceclass - Contains
SoapServiceBuilderinterface - Contains
WebserviceUtilhelper
- Contains
-
WSDL File for your web service
- Use
wsimportorxjcto generate JAXB classes
- Use
-
PLI Data Structures
- Transpiled Java classes from your PLI copybooks
- These represent your request/response data
-
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.javaOrderResponse.javaOrderDetails.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-urlsuffix) - 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:
-
System Properties (
-Dproperty=valueJVM arguments) - RuntimeEnvironment (global parameters)
- 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:
- Check
webservice.client.packageproperty matches your package - Verify class is compiled:
ls build/classes/java/main/com/heirloomcomputing/cics/webservice/ - Check class name matches exactly (case-sensitive)
- 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:
- Check property key returned by
getUrlKey()matches deploy.properties - Verify deploy.properties is in classpath (
WEB-INF/classes/) - Try absolute path:
-Dmy.deploy.props=/full/path/to/deploy.properties
Problem: Connection timeout
Error:
java.net.SocketTimeoutException: connect timed out
Solutions:
- Verify endpoint URL is correct and accessible
- Check firewall/network configuration
- Test with curl:
curl -v http://your-endpoint:8080/service - Increase timeout:
webservice.connect.timeout=60000
Problem: Read timeout
Error:
java.net.SocketTimeoutException: Read timed out
Solutions:
- Service is slow - increase timeout:
webservice.read.timeout=120000 - Service might be hung - check service logs
- Request too large - optimize data size
Problem: XML parsing error in buildResponse
Error:
org.xml.sax.SAXParseException: Content is not allowed in prolog
Solutions:
- Response might contain BOM or whitespace - trim it
- Check response is valid XML
- Service might be returning HTML error page - check HTTP status
- Print raw response to inspect:
System.out.println(response)
Problem: JAXB unmarshalling error
Error:
jakarta.xml.bind.UnmarshalException: unexpected element
Solutions:
- Namespace mismatch - check JAXB annotations match WSDL
- Element name mismatch - verify case sensitivity
- Missing elements - check if service changed
- Regenerate JAXB classes from latest WSDL
Problem: Data corruption in containers
Symptom: Garbage characters in response
Solutions:
- Check byte order (endianness)
- Verify structure alignment in PLI
- Use
CHARfor strings, notBIT - Ensure matching structure definitions between PLI and Java
Problem: Authentication fails
Error:
HTTP 401 Unauthorized
Solutions:
- Verify username/password in deploy.properties
- Check
getBasicAuthentication()returns correct format:"Basic <base64>" -
Test credentials with curl:
curl -u username:password http://endpoint/service - Check if service requires different auth (OAuth, API key, etc.)
Problem: SOAP Fault returned
Error:
RESP=16, RESP2=6
Solutions:
- Read DFHWS-BODY container for fault details
- Common faults:
-
Server.userException: Business logic error -
Client.validationError: Invalid input data -
Server.generalException: Server error
-
- Fix data issues and retry
Summary
You've learned:
- What INVOKE SERVICE does - Bridges PLI programs and SOAP services
- How it works - Containers → Service Builder → HTTP → Response
- How to configure - deploy.properties with endpoints and timeouts
- How to implement - SoapServiceBuilder interface with 4 methods
- How to call - PUT → INVOKE → GET pattern in PLI
- How to handle errors - RESP/RESP2 codes and SOAP faults
- How to debug - Logging, testing, and troubleshooting
Next Steps:
- Generate JAXB classes from your WSDL
- Create your service builder class
- Configure deploy.properties
- Update your PLI program
- Test and debug
- Deploy to production
Remember:
- Always trim PLI strings before mapping to JAXB
- Use separate containers for array data
- Enable
sql.log=trueduring development - Handle SOAP faults gracefully
Good luck with your integration!
0 Comments