Skip to content

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

  1. Enterprise CICS Web Application — Large-scale online transaction processing system (150+ programs)
  2. Multi-Wave Batch Processing System — Complex batch workload with multi-wave delivery structure
  3. 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