Part 12 — Real-World Examples¶
12.1 Overview¶
This section provides real-world examples of PLI Platform usage in enterprise applications. All examples use anonymized code with generic business logic and dummy variable names.
Example Applications¶
- Enterprise CICS Web Application — Large-scale online transaction processing system (150+ programs)
- Multi-Wave Batch Processing System — Complex batch workload with multi-wave delivery structure
- Common Integration Patterns — SQL, CICS, and file I/O patterns
12.2 Enterprise CICS Web Application¶
12.2.1 Application Overview¶
Profile: - Type: CICS online transaction processing - Size: 153 programs - Architecture: 3-tier (H1/H8/H9 pattern) - Deployment: Payara 6 application server - Access: Web UI via servlet interface
Program Distribution: - H1 Programs (5 programs): Transaction handlers and coordinators - H8 Programs (68 programs): Service and utility modules - H9 Programs (80 programs): Database I/O operations - Copybooks: 29,000+ shared definitions
12.2.2 Program Organization Pattern¶
The application follows a strict layered architecture:
┌─────────────────────────────────────────────────────────┐
│ H1 Layer - Transaction Handlers (Entry Points) │
│ • H1MAIN01 - Main transaction router │
│ • H1CUST01 - Customer inquiry │
│ • H1ORD01 - Order processing │
└───────────────────┬─────────────────────────────────────┘
│ calls
┌───────────────────▼─────────────────────────────────────┐
│ H8 Layer - Business Services │
│ • H8VALID1 - Customer validation │
│ • H8CALC01 - Balance calculation │
│ • H8FMT01 - Data formatting utilities │
└───────────────────┬─────────────────────────────────────┘
│ calls
┌───────────────────▼─────────────────────────────────────┐
│ H9 Layer - Data Access │
│ • H9CUSTR1 - Customer read │
│ • H9CUSTU1 - Customer update │
│ • H9ORDCR1 - Order create │
└─────────────────────────────────────────────────────────┘
12.2.3 Example: Customer Inquiry Transaction¶
H1 Transaction Handler (H1CUST01.pli):
H1CUST01: PROCEDURE;
/* ============================================================
* Transaction: CUST - Customer Inquiry
* Purpose: Display customer information
* Author: Heirloom Platform Example
* ============================================================ */
/* Include copybooks */
%INCLUDE DFHAID; /* CICS AID keys */
%INCLUDE CUSTMAP; /* Customer map */
%INCLUDE CUSTCOPY; /* Customer copybook */
/* Working storage */
DCL WS_FUNCTION CHAR(4);
DCL WS_CUSTOMER_ID CHAR(10);
DCL WS_RESP FIXED BIN(31);
DCL WS_RESP2 FIXED BIN(31);
/* Communication area */
DCL 1 DFHCOMMAREA,
2 CA_FUNCTION CHAR(4),
2 CA_CUSTID CHAR(10),
2 CA_MESSAGE CHAR(80);
/* Initialize */
IF EIBCALEN = 0 THEN DO;
/* First time - send initial map */
WS_FUNCTION = 'INIT';
CALL SEND_MAP;
EXEC CICS RETURN TRANSID('CUST');
END;
ELSE DO;
/* Retrieve commarea */
WS_FUNCTION = CA_FUNCTION;
WS_CUSTOMER_ID = CA_CUSTID;
END;
/* Process based on function */
SELECT (WS_FUNCTION);
WHEN ('INIT')
CALL SEND_MAP;
WHEN ('INQR')
CALL PROCESS_INQUIRY;
OTHERWISE
CALL ERROR_INVALID_FUNCTION;
END;
EXEC CICS RETURN TRANSID('CUST') COMMAREA(DFHCOMMAREA);
/* ============================================================
* Process customer inquiry
* ============================================================ */
PROCESS_INQUIRY: PROCEDURE;
DCL H8_RC FIXED BIN(31);
/* Receive map */
EXEC CICS RECEIVE
MAP('CUSTMAP')
MAPSET('CUSTSET')
INTO(CUSTMAPI)
RESP(WS_RESP);
IF WS_RESP ^= DFHRESP(NORMAL) THEN DO;
CA_MESSAGE = 'Error receiving map';
CALL SEND_ERROR;
RETURN;
END;
/* Get customer ID from map */
WS_CUSTOMER_ID = CUSTIDI;
/* Validate customer ID */
CALL H8VALID1(WS_CUSTOMER_ID, H8_RC);
IF H8_RC ^= 0 THEN DO;
CA_MESSAGE = 'Invalid customer ID';
CALL SEND_ERROR;
RETURN;
END;
/* Retrieve customer data */
CALL H8CUSTG1(WS_CUSTOMER_ID, H8_RC);
IF H8_RC ^= 0 THEN DO;
CA_MESSAGE = 'Customer not found';
CALL SEND_ERROR;
RETURN;
END;
/* Send response map */
CALL SEND_MAP;
END PROCESS_INQUIRY;
/* ============================================================
* Send map to terminal
* ============================================================ */
SEND_MAP: PROCEDURE;
EXEC CICS SEND
MAP('CUSTMAP')
MAPSET('CUSTSET')
FROM(CUSTMAPO)
ERASE
RESP(WS_RESP);
END SEND_MAP;
/* ============================================================
* Send error message
* ============================================================ */
SEND_ERROR: PROCEDURE;
MSGO = CA_MESSAGE;
EXEC CICS SEND
MAP('CUSTMAP')
MAPSET('CUSTSET')
FROM(CUSTMAPO)
ALARM
RESP(WS_RESP);
END SEND_ERROR;
/* ============================================================
* Handle invalid function
* ============================================================ */
ERROR_INVALID_FUNCTION: PROCEDURE;
CA_MESSAGE = 'Invalid function code';
CALL SEND_ERROR;
END ERROR_INVALID_FUNCTION;
END H1CUST01;
H8 Service Module (H8VALID1.pli):
H8VALID1: PROCEDURE(P_CUSTID, P_RC);
/* ============================================================
* Service: Customer ID Validation
* Purpose: Validate customer ID format and existence
* Parameters:
* P_CUSTID - Customer ID to validate (input)
* P_RC - Return code (output): 0=OK, 4=Invalid format, 8=Not found
* ============================================================ */
DCL P_CUSTID CHAR(10);
DCL P_RC FIXED BIN(31);
DCL WS_LENGTH FIXED BIN(31);
DCL WS_I FIXED BIN(31);
DCL WS_CHAR CHAR(1);
/* Initialize */
P_RC = 0;
/* Check for blanks */
IF P_CUSTID = '' | P_CUSTID = ' ' THEN DO;
P_RC = 4;
RETURN;
END;
/* Check format (must be alphanumeric) */
DO WS_I = 1 TO LENGTH(P_CUSTID);
WS_CHAR = SUBSTR(P_CUSTID, WS_I, 1);
IF ^((WS_CHAR >= 'A' & WS_CHAR <= 'Z') |
(WS_CHAR >= '0' & WS_CHAR <= '9')) THEN DO;
P_RC = 4;
RETURN;
END;
END;
/* Check if customer exists (call H9 data access) */
CALL H9CUSTE1(P_CUSTID, P_RC);
RETURN;
END H8VALID1;
H9 Data Access Module (H9CUSTR1.pli):
H9CUSTR1: PROCEDURE(P_CUSTID, P_CUSTOMER_REC, P_RC);
/* ============================================================
* Data Access: Customer Read
* Purpose: Read customer record from database
* Parameters:
* P_CUSTID - Customer ID (input)
* P_CUSTOMER_REC - Customer record (output)
* P_RC - Return code (output): 0=OK, 8=Not found, 12=SQL error
* ============================================================ */
DCL P_CUSTID CHAR(10);
/* Customer record structure */
DCL 1 P_CUSTOMER_REC,
2 CUST_ID CHAR(10),
2 CUST_NAME CHAR(30),
2 CUST_ADDRESS,
3 STREET CHAR(30),
3 CITY CHAR(20),
3 STATE CHAR(2),
3 ZIP CHAR(10),
2 CUST_BALANCE DEC FIXED(15,2),
2 CUST_STATUS CHAR(1);
DCL P_RC FIXED BIN(31);
/* SQL communication area */
EXEC SQL INCLUDE SQLCA;
/* Initialize */
P_RC = 0;
/* Execute SQL query */
EXEC SQL
SELECT
CUSTOMER_ID,
CUSTOMER_NAME,
STREET,
CITY,
STATE,
ZIP_CODE,
ACCOUNT_BALANCE,
STATUS
INTO
:CUST_ID,
:CUST_NAME,
:STREET,
:CITY,
:STATE,
:ZIP,
:CUST_BALANCE,
:CUST_STATUS
FROM CUSTOMERS
WHERE CUSTOMER_ID = :P_CUSTID;
/* Check SQL return code */
SELECT (SQLCODE);
WHEN (0)
P_RC = 0; /* Success */
WHEN (100)
P_RC = 8; /* Not found */
OTHERWISE
P_RC = 12; /* SQL error */
END;
RETURN;
END H9CUSTR1;
12.2.4 Helper System Usage¶
The application uses the Helper system to override compiler-generated code for specific scenarios:
helper.json:
{
"helpers": [
{
"program": "H1CUST01",
"variable": "WS_CURRENT_DATE",
"replaceWith": "LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE)"
},
{
"program": "H8CALC01",
"method": "calculateInterest",
"replaceWith": "com.company.custom.InterestCalculator.compute(principal, rate, term)"
}
]
}
12.2.5 Build Configuration¶
build.gradle:
plugins {
id 'java'
id 'war'
}
group = 'com.company.cics'
version = '1.0.0'
dependencies {
implementation 'com.heirloom:pli_runtime2:26.2.27.RC1'
annotationProcessor 'com.heirloom:heirloom-manifold:2.0.0'
compileOnly 'jakarta.platform:jakarta.jakartaee-api:10.0.0'
runtimeOnly 'org.postgresql:postgresql:42.7.1'
}
// PLI compilation
task compilePli(type: Exec) {
commandLine 'java', '-jar', 'lib/pli_compiler2-26.2.27.RC1.jar',
'--source', 'src/main/pli/programs',
'--target', 'generated',
'--package', 'com.company.cics',
'--strategy', 'instance', // Instance strategy for CICS
'--cics',
'--sql',
'--helper', 'src/main/resources/helper.json'
}
compileJava.dependsOn compilePli
12.2.6 Deployment¶
Deploy to Payara:
# Build WAR
./gradlew war
# Deploy
asadmin deploy build/libs/enterprise-cics-1.0.0.war
# Access transaction
curl "http://localhost:8080/cics-app/servlet?transid=CUST&userid=USER01"
12.3 Multi-Wave Batch Processing System¶
12.3.1 Application Overview¶
Profile: - Type: Batch processing - Waves: 11 delivery waves (wave1-wave10, plus wave5_1, wave5_2) - Programs per wave: 15-30 programs - Execution: Via EBP (Elastic Batch Platform) - Schedule: Daily batch jobs
12.3.2 Project Structure¶
batch-processing-system/
├── wave1/
│ ├── src/main/pli/
│ │ ├── H4LOAD01.pli ← Data load program
│ │ ├── H4PROC01.pli ← Data processor
│ │ ├── H9DBRD01.pli ← Database read
│ │ └── H9DBWR01.pli ← Database write
│ ├── src/main/jcl/
│ │ ├── LOAD001.jcl ← Load job
│ │ └── PROC001.jcl ← Process job
│ └── build.gradle
├── wave2/
├── wave3/
├── ...
└── settings.gradle
12.3.3 Example: Data Processing Batch Job¶
Batch Program (H4PROC01.pli):
H4PROC01: PROCEDURE OPTIONS(MAIN);
/* ============================================================
* Batch: Customer Data Processor
* Purpose: Process daily customer transactions
* Input: CUSTOMER.TRANS.FILE (transaction file)
* Output: CUSTOMER.SUMMARY.FILE (summary report)
* ============================================================ */
/* File declarations */
DCL INPUT_FILE FILE RECORD INPUT ENV(FB RECSIZE(200));
DCL OUTPUT_FILE FILE RECORD OUTPUT ENV(FB RECSIZE(120));
DCL REPORT_FILE FILE STREAM PRINT;
/* Transaction record */
DCL 1 TRANS_REC,
2 TRANS_ID CHAR(10),
2 CUSTOMER_ID CHAR(10),
2 TRANS_DATE CHAR(8),
2 TRANS_AMOUNT DEC FIXED(15,2),
2 TRANS_TYPE CHAR(2);
/* Summary record */
DCL 1 SUMMARY_REC,
2 CUSTOMER_ID CHAR(10),
2 TRANS_COUNT FIXED BIN(31),
2 TOTAL_AMOUNT DEC FIXED(15,2);
/* Work variables */
DCL WS_RECORD_COUNT FIXED BIN(31) INIT(0);
DCL WS_ERROR_COUNT FIXED BIN(31) INIT(0);
DCL WS_TOTAL_AMOUNT DEC FIXED(15,2) INIT(0);
/* SQL declarations */
EXEC SQL INCLUDE SQLCA;
/* Initialize */
PUT FILE(REPORT_FILE) SKIP LIST('Customer Data Processor');
PUT FILE(REPORT_FILE) SKIP LIST('Run Date:', DATE());
PUT FILE(REPORT_FILE) SKIP;
/* Open files */
OPEN FILE(INPUT_FILE);
OPEN FILE(OUTPUT_FILE);
/* Set up end-of-file condition */
ON ENDFILE(INPUT_FILE) GO TO END_PROCESSING;
/* Process records */
DO WHILE('1'B);
READ FILE(INPUT_FILE) INTO(TRANS_REC);
WS_RECORD_COUNT = WS_RECORD_COUNT + 1;
/* Validate transaction */
IF TRANS_AMOUNT <= 0 THEN DO;
WS_ERROR_COUNT = WS_ERROR_COUNT + 1;
PUT FILE(REPORT_FILE) SKIP
LIST('ERROR: Invalid amount for transaction', TRANS_ID);
ITERATE; /* Skip to next record */
END;
/* Process transaction */
CALL PROCESS_TRANSACTION;
/* Update summary */
WS_TOTAL_AMOUNT = WS_TOTAL_AMOUNT + TRANS_AMOUNT;
/* Write to output file every 100 records */
IF MOD(WS_RECORD_COUNT, 100) = 0 THEN
WRITE FILE(OUTPUT_FILE) FROM(SUMMARY_REC);
END;
END_PROCESSING:
/* Close files */
CLOSE FILE(INPUT_FILE);
CLOSE FILE(OUTPUT_FILE);
/* Print summary */
PUT FILE(REPORT_FILE) SKIP;
PUT FILE(REPORT_FILE) SKIP LIST('Processing Summary');
PUT FILE(REPORT_FILE) SKIP LIST('Records processed:', WS_RECORD_COUNT);
PUT FILE(REPORT_FILE) SKIP LIST('Errors:', WS_ERROR_COUNT);
PUT FILE(REPORT_FILE) SKIP LIST('Total amount:', WS_TOTAL_AMOUNT);
/* Set return code */
IF WS_ERROR_COUNT > 0 THEN
CALL PLIRETC(4); /* Warning return code */
ELSE
CALL PLIRETC(0); /* Success */
/* ============================================================
* Process individual transaction
* ============================================================ */
PROCESS_TRANSACTION: PROCEDURE;
DCL H9_RC FIXED BIN(31);
/* Update customer balance in database */
EXEC SQL
UPDATE CUSTOMERS
SET ACCOUNT_BALANCE = ACCOUNT_BALANCE + :TRANS_AMOUNT,
LAST_TRANS_DATE = :TRANS_DATE
WHERE CUSTOMER_ID = :CUSTOMER_ID;
IF SQLCODE ^= 0 THEN DO;
PUT FILE(REPORT_FILE) SKIP
LIST('ERROR: SQL update failed for customer', CUSTOMER_ID);
WS_ERROR_COUNT = WS_ERROR_COUNT + 1;
RETURN;
END;
/* Populate summary record */
SUMMARY_REC.CUSTOMER_ID = CUSTOMER_ID;
SUMMARY_REC.TRANS_COUNT = 1;
SUMMARY_REC.TOTAL_AMOUNT = TRANS_AMOUNT;
END PROCESS_TRANSACTION;
END H4PROC01;
12.3.4 JCL Job Definition¶
PROC001.jcl:
//PROC001 JOB (BATCH),'Customer Processing',CLASS=A,
// MSGCLASS=X,NOTIFY=&SYSUID
//*
//* Step 1: Process transactions
//*
//PROCESS EXEC PGM=com.company.batch.wave1.H4PROC01,
// PARM='DATE=20260302'
//STEPLIB DD DSN=BATCH.LOAD.LIB,DISP=SHR
//INPUT DD DSN=CUSTOMER.TRANS.FILE,DISP=SHR
//OUTPUT DD DSN=CUSTOMER.SUMMARY.FILE,
// DISP=(NEW,CATLG,DELETE),
// SPACE=(CYL,(10,5)),UNIT=SYSDA
//REPORT DD SYSOUT=*
//SYSOUT DD SYSOUT=*
//SYSPRINT DD SYSOUT=*
//*
//* Step 2: Generate reports (runs only if Step 1 succeeds)
//*
//REPORT EXEC PGM=com.company.batch.wave1.H4RPT01,
// COND=(0,NE,PROCESS)
//INPUT DD DSN=CUSTOMER.SUMMARY.FILE,DISP=SHR
//OUTPUT DD SYSOUT=*
12.3.5 Wave Build Configuration¶
wave1/build.gradle:
// Wave-specific configuration
ext {
pliSourceDir = file('src/main/pli')
javaTargetDir = file('generated')
}
task compilePli(type: Exec) {
commandLine 'java', '-jar', rootProject.file('lib/pli_compiler2-26.2.27.RC1.jar'),
'--source', pliSourceDir,
'--target', javaTargetDir,
'--package', 'com.company.batch.wave1',
'--strategy', 'static', // Static strategy for batch
'--sql'
}
compileJava.dependsOn compilePli
// Create executable JAR
jar {
archiveBaseName = 'wave1'
manifest {
attributes 'Main-Class': 'com.company.batch.wave1.H4PROC01'
}
// Include all dependencies
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
}
12.3.6 EBP Job Submission¶
Submit job to EBP:
# Via REST API
curl -X POST http://localhost:8080/ebp/api/jobs \
-H "Content-Type: text/plain" \
-d @wave1/src/main/jcl/PROC001.jcl
# Response
{
"jobId": "JOB00456",
"jobName": "PROC001",
"status": "SUBMITTED"
}
# Monitor job
curl http://localhost:8080/ebp/api/jobs/JOB00456
# View output
curl http://localhost:8080/ebp/api/jobs/JOB00456/output/SYSOUT
12.4 Common Integration Patterns¶
12.4.1 SQL Integration Pattern¶
Pattern: Cursor-based processing for large result sets
/* Declare cursor */
EXEC SQL DECLARE CUST_CURSOR CURSOR FOR
SELECT CUSTOMER_ID, CUSTOMER_NAME, ACCOUNT_BALANCE
FROM CUSTOMERS
WHERE STATUS = :WS_STATUS
ORDER BY CUSTOMER_ID;
/* Open cursor */
EXEC SQL OPEN CUST_CURSOR;
/* Fetch loop */
DO WHILE(SQLCODE = 0);
EXEC SQL FETCH CUST_CURSOR
INTO :CUSTOMER_ID, :CUSTOMER_NAME, :ACCOUNT_BALANCE;
IF SQLCODE = 0 THEN
CALL PROCESS_CUSTOMER;
END;
/* Close cursor */
EXEC SQL CLOSE CUST_CURSOR;
12.4.2 File I/O Pattern¶
Pattern: Sequential file processing with error handling
DCL INPUT_FILE FILE RECORD INPUT;
DCL WS_EOF_FLAG BIT(1) INIT('0'B);
/* Error handling */
ON ENDFILE(INPUT_FILE) WS_EOF_FLAG = '1'B;
ON ERROR CALL FILE_ERROR_HANDLER;
OPEN FILE(INPUT_FILE);
DO WHILE(^WS_EOF_FLAG);
READ FILE(INPUT_FILE) INTO(INPUT_REC);
IF ^WS_EOF_FLAG THEN
CALL PROCESS_RECORD;
END;
CLOSE FILE(INPUT_FILE);
12.4.3 CICS Pseudo-Conversational Pattern¶
Pattern: Maintaining conversation state
/* First transaction - send map */
IF EIBCALEN = 0 THEN DO;
CALL INITIALIZE_MAP;
EXEC CICS SEND MAP('MAINMAP') MAPSET('MAPSET');
EXEC CICS RETURN TRANSID('MAIN') COMMAREA(CA);
END;
/* Subsequent transaction - process input */
ELSE DO;
EXEC CICS RECEIVE MAP('MAINMAP') INTO(MAP_IN);
CALL PROCESS_INPUT;
EXEC CICS SEND MAP('MAINMAP') FROM(MAP_OUT);
EXEC CICS RETURN TRANSID('MAIN') COMMAREA(CA);
END;
12.5 Testing Strategies¶
12.5.1 Unit Testing Generated Java¶
@Test
void testH9CUSTR1_CustomerFound() {
// Setup test database
TestDB.insertCustomer("CUST001", "Test Customer", 100.00);
// Execute H9 program
H9CUSTR1 program = new H9CUSTR1();
program.setP_CUSTID("CUST001");
CUSTOMER_REC customerRec = new CUSTOMER_REC();
int rc = program.execute("CUST001", customerRec);
// Verify
assertEquals(0, rc); // Success
assertEquals("Test Customer", customerRec.getCUST_NAME());
assertEquals(new BigDecimal("100.00"), customerRec.getCUST_BALANCE());
}
12.5.2 Integration Testing with Database¶
@Test
void testBatchProcessing() throws Exception {
// Setup test data
loadTestTransactions();
// Execute batch program
H4PROC01 batchProgram = new H4PROC01();
int returnCode = batchProgram.main(new String[]{});
// Verify results
assertEquals(0, returnCode);
// Check database was updated
int processedCount = TestDB.countProcessedTransactions();
assertTrue(processedCount > 0);
}
12.6 Key Takeaways¶
From CICS Application Example:¶
- Layered architecture (H1/H8/H9) promotes code reuse and maintainability
- Helper system allows customization without modifying generated code
- Instance strategy required for thread-safe CICS applications
From Batch Application Example:¶
- Multi-wave delivery enables incremental migration and testing
- Static strategy optimized for batch performance
- EBP integration provides mainframe-like job management
General Best Practices:¶
- Copybooks reduce code duplication
- Error handling at every layer (H1, H8, H9)
- Testing both PL/I logic (via generated Java) and integration points
- Configuration externalization for environment-specific settings