Mainframe Modernization

Modernizing IMS applications sometimes means rewriting entire COBOL or PL/I programs in Java™. But another popular approach is to use Java when adding or modifying subroutines in existing PL/I applications. We can do this because IMS supports PL/I to Java interoperability for IMS applications running in IMS dependent regions. By using an incremental approach, you can introduce new features and modernize IMS applications at a pace that fits your business and technical environment.

We’ll use two scenarios to show how easy it can be to modernize IMS applications by integrating Java and PL/I:

  • Scenario 1 shows how a PL/I application that runs in a Message Processing Region (MPR) can integrate calls to Java. The concepts in this scenario also apply to IFP and BMP applications. 
  • Scenario 2 shows how a Java application that runs in a JMP region can integrate calls to PL/I. The concepts in this scenario also apply to JBP applications. 

 The sample applications in each scenario make calls to IMS and Db2 databases in both PL/I and Java. All calls are part of the same unit of work and recovery. Sample execution of each scenario is described at the end of this post. 

Scenario 1 

This sample PL/I application simulates banking transactions that handle deposit and withdrawal requests for a given account. We use Java java.nio.ByteBuffers to share storage between Java and PL/I. ByteBuffers are like byte arrays but have the added benefit of allowing both PL/I and Java to share memory without needing to copy data. Notice that we’ll use the input message address placed in a PL/I structure to obtain a NewDirectByteBuffer in Java, which gives Java access to the input message address. 

An input message is received that includes an account number. The transaction begins by entering into PL/I and issuing calls to query the ACCOUNT ID field in both IMS (named ACCNUM) and Db2 (named ACCOUNT_ID). The account exists if the account number in IMS and Db2 match. If so, PL/I calls a Java application to process the deposit or withdrawal amount in the Db2 ‘ACCOUNT_BALANCE’ table and record a transaction log into the IMS ‘ACTIVITY’ segment.

Here’s how we do it. We have an MPR with a JVM enabled, so we first set up PL/I to Java interoperability:

  1. Unlike COBOL, PL/I applications do not inherit Java-related pointers, so to get a handle to the JVM, we use the following function to look it up:
bufLen = 8;
rc = JNI_GetCreatedJavaVMs(jvm_ptr, 1, nVMs);
display('pli - JNI_GetJavaVM rc = '|| rc );
display('JNI_GetCreatedJavaVMs ended.');
display('pli - Hex value of JVMArr -> ' || heximage(addr(jvm_ptr), stg(jvm_ptr) ) );
display('No of VMs: ' || nVMs);
  1. We attach the JVM to the current thread, which gives access to the JNIEnv pointer:
VM_Attach_Args.version = JNI_VERSION_1_8;
rc = AttachCurrentThread(jvm_ptr, JNIEnv, addr(VM_Attach_Args));
display('pli - AttachCurrentThread rc= '|| rc );
display('pli - Hex value of JavaVM -> '|| heximage(addr(jvm_ptr), stg(jvm_ptr) ) );
display('pli - Hex value of JNIEnv -> ' || heximage(addr(JNIEnv), stg(JNIEnv) ) );
cachedjvm_ptr = jvm_ptr;
cachedJNIEnv = JNIEnv;
  1. Because various data types can be passed between PL/I and Java, in this example we pass direct ByteBuffers. We make two JNI calls to obtain new ByteBuffer addresses that will be used to share data between PL/I and Java. The application obtains two NewDirectByteBuffer addresses based on the PL/I storage addresses where the input and output messages are located, which is how PL/I and Java share and manipulate the same memory:
IN_MESSAGE_BB_PTR  = NewDirectByteBuffer(JNIEnv,
          XIN_MESSAGE_PTR, SIZE(XIN_MESSAGE));
OUT_MESSAGE_BB_PTR = NewDirectByteBuffer(JNIEnv,  
          XOUT_MESSAGE_PTR, SIZE(XOUT_MESSAGE));

Our PL/I to Java interoperability has been established, so we next focus on the existing PL/I code:

  1. Using PLITDLI, we issue a Get Unique off the IMS Message queue, which marks the beginning of this transaction: 
START: CALL PLITDLI  (THREE,GU_FUNC,IOPCB,IN_MESSAGE);
       IF IOPCB.STC_CODE  ^=  ' ' THEN GO TO PGMEND;
  1. Using the passed account number key, we query the Db2 table to obtain the account number stored there. If no ACCOUNT_ID is returned, we assume that the account does not exist: 
EXEC SQL SELECT ACCOUNT_ID
     INTO :ACCOUNT_ID 
     FROM ACCOUNT_BALANCE 
     WHERE ACCOUNT_ID = :IN_ACCOUNT_NUMBER;
  1. We query IMS to obtain the account number (ACCNUM). As with Db2, if no ACCNUM exists, we assume that the account does not exist. This example uses the AIBTDLI interface: 
AIB_PTR = ADDR(AIB);
AIB.EYECATCHER = 'DFSAIB  ';
AIB.AIB_LENGTH = 128;
AIB.RSRC_NAME1 = 'PCB01   ';
IOAREA_PTR = ADDR(IOAREA);
IOAREA = '';
AIB.OUT_LENGTH_TOTAL = 40;
QUAL_SSA.SEG_ACCNUM = IN_ACCOUNT_NUMBER;
CALL AIBTDLI(FOUR, GHU_FUNC, AIB, IOAREA, QUAL_SSA);
DISPLAY('PLI>>IMS>> GHU DONE');
IF DBPCBA_STAT ^= '  ' THEN DO;
    DISPLAY('ACCOUNT NUMBER NOT FOUND');
    GO TO PGMEND;
END;

If the account numbers match, we continue the transaction. To leverage Java, our PL/I application must get the necessary class and method:

  1. From PL/I, we call the Java method doJavaWork in the class PliToJavaFunctions. To do so, we use the JNI functions FindClass, GetStaticMethodID, and CallStaticObjectMethod. The doJavaWork method takes two ByteBuffer objects, which are the addresses of the ByteBuffers we obtained earlier. These objects are based on the PL/I data structures and allow Java’s ByteBuffers to access the same storage used by PL/I:
/**************************************************/
/* Get the Java Class for the PliToJavaFunctions  */
/**************************************************/

if classReference = sysnull() then do;
    Display('FindClass');
    className = "t2/pli2java/PliToJavaFunctions";
    rc = memconvert(addrdata(classNameUTF),
        size(classNameUTF), 1208,
        addrdata(className),
        length(className), 1141
        );
    classReference = FindClass(JNIEnv, classNameUTF);
    exObj = ExceptionOccurred (JNIEnv);
    if exObj <>sysnull() then do;
        signal condition(java_exception);
    end;
end;
if classReference = sysnull() then do;
    Display('Class Reference is null');
end;

/*************************************************************/ 
/* Get the IDMethod (defined by the method's signature       */ 
/*************************************************************/ 

If StaticMethodID = sysnull() then do;
    Display('GetStaticMethodID');
    methodParmDescription =  "(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;)V";
    rc = memconvert(addrdata(methodParmDescriptionUTF),
         size(methodParmDescriptionUTF), 1208,
         addrdata(methodParmDescription),
         length(methodParmDescription), 1141
         );
    methodName = "doJavaWork";
    rc = memconvert(addrdata(methodNameUTF),
         size(methodNameUTF), 1208,
         addrdata(methodName),
         length(methodName), 1141
         );
    StaticMethodID = GetStaticMethodID(JNIEnv, classReference, methodNameUTF, methodParmDescriptionUTF);
    exObj = ExceptionOccurred (JNIEnv);
    if exObj <>sysnull() then do;
        signal condition(java_exception);
    end;
end;
if StaticMethodID = sysnull() then do;
    Display('Class Reference is null');
end;

/***********************************************************/ 
/* Call the method                                         */ 
/***********************************************************/ 

Display
    ('From PL/I - Calling Java method in existing JVM from PL/I.');
myNull = CallStaticObjectMethod (JNIEnv, classReference, StaticMethodID, IN_MESSAGE_BB_PTR, OUT_MESSAGE_BB_PTR);
exObj = ExceptionOccurred (JNIEnv);
if exObj <>sysnull() then do;
    signal condition(java_exception);
end;

Now we can introduce a new Java application to handle a portion of our transaction. Because our Java code uses the same storage as PL/I, changing values of inBuffer reflect in PL/I as well.

Here are the four key steps:

  1. Read the input message from the transaction and parse the transaction type, account number, and transaction amount:  
private static void readAccountNumberFromInputMessage() throws UnsupportedEncodingException { 
byte[] accountNumberBytes = new byte[10];
inBuffer.position(16);
inBuffer.get(accountNumberBytes);
accountNumber = new String(accountNumberBytes, CHARSET).trim();
} 

private static void readTransactionTypeFromInputMessage() throws UnsupportedEncodingException { 
byte[] transactionTypeBytes = new byte[1];
inBuffer.position(15);
inBuffer.get(transactionTypeBytes);
transactionType = new String(transactionTypeBytes, CHARSET).trim();
}

private static void readTransactionAmountFromInputMessage() throws UnsupportedEncodingException { 
byte[] transactionAmountBytes = new byte[13];
inBuffer.position(26);
inBuffer.get(transactionAmountBytes);
transactionAmount = new String(transactionAmountBytes, CHARSET).trim();
}
  1. Use the DB2 JCC driver to update the account balance in Db2: 
// Prepare statement for updating account balance
ps = conn.prepareStatement(DB2_UPDATE_ACCOUNT_SQL);
BigDecimal amount = new BigDecimal(transactionAmount);

// Calculate new balance 
if(transactionType.equalsIgnoreCase("W")) { 
amount = amount.multiply(new BigDecimal(-1));
}

BigDecimal newBalance = oldBalance.add(amount);
ps.setBigDecimal(1, newBalance);
ps.setString(2, accountNumber);
// Update balance 
ps.executeUpdate();
  1. Use the IMS Universal Drivers to insert the transaction record into the IMS Activity segment:
// Establish connection to IMS 
IMSConnectionSpec connSpec = IMSConnectionSpecFactory.createIMSConnectionSpec();
connSpec.setDriverType(IMSConnectionSpec.DRIVER_TYPE_2);
connSpec.setDatabaseName("PHIDPHO5");
PSB psb = PSBFactory.createPSB(connSpec);
PCB pcb = psb.getPCB("PCB01");

// Build SSAList qualified on the Activity child segment. 
SSAList ssaList = pcb.getSSAList(IMS_ACCOUNT_SEGMENT,IMS_ACTIVITY_SEGMENT);
ssaList.addInitialQualification(IMS_ACCOUNT_SEGMENT,IMS_ACCOUNT_KEY, SSAList.EQUALSaccountNumber);

// Fill Path with data to insert 
Path path = ssaList.getPathForInsert(IMS_ACTIVITY_SEGMENT);
path.setString(IMS_ACTIVITY_TYPE_FIELD,transactionType);
path.setBigDecimal(IMS_ACTIVITY_AMOUNT_FIELD, new BigDecimal(transactionAmount));

// Insert transaction 
pcb.insert(path, ssaList);
  1. Insert the updated balance into the output message:
private static void setNewBalanceIntoOutputMessage(String result) throws Exception { 
outBuffer.position(6);
outBuffer.put(result.getBytes(CHARSET));
}

Our Java method returns to the PL/I caller. Because we used shared storage, our PL/I application already has the updated account balance placed by our Java application.

As a final step, PL/I uses PLITDLI to insert back to the IMS Message Queue.

CALL PLITDLI (THREE, ISRT_FUNC,IOPCB, OUT_MESSAGE);

Compiling the application

To compile our sample PL/I application, we start the PL/I compiler under z/OS UNIX using the executable pli command. We then use this sample bash shell script:

# Defining the STEPLIB, pointing do the PL/I compiler and the DB2 SDFSLOAD dataset
export STEPLIB="ENPLI530.SIBMZCMP:DSNC10.SDSNLOAD" 

# Defining the DBRMLIB which points to the DB2 DBRMLIB data set
export DBRMLIB="IMSTESTL.DB2A.DBRMLIB.DATA"

# Defining the SYSLIB which points to the PL/I JNI copybook/include file and the DB2 RUNLIB data set
export SYSLIB='ENPLI530.SIBMZSAM:IMSTESTL.DB2A.RUNLIB.LOAD'

# Defining the PATH pointing to the binary folder of the PL/I installation
export PATH=$PATH:/usr/lpp/pli/pli530/IBM/pli/v5r3/bin

# Defining the _C89_LSYSLIB which points to the Language Environment SCEELKED library and the DB2 SDSNLOAD data set
export _C89_LSYSLIB=CEE.SCEELKED:DSNC10.SDSNLOAD

# Define the Java home installation and the LIBPATH
export JAVA_HOME=/usr/lpp/java/java180/J8.0
LIBPATH=/lib:/usr/lib:"${JAVA_HOME}"/bin
export LIBPATH="$LIBPATH"

# Set up the classpath, here we use the Java binary folder, the IMS Universal Drivers Jar, and the DB2 JCC jar
export CLASSPATH=$CLASSPATH: "${JAVA_HOME}"/bin:/tmp/imsudb.jar:/tmp/db2jcc4.jar

# We copy the IMS the DFSLI000 module (Needed for PL/I IMS work) as DFSLI000.x from the SDFSRESL data set
cp -X "//'IMSBLD.I15RTSMM.SDFSRESL(DFSLI000)'" DFSLI000.x

# Start the compiler
# IMPORTANT NOTES:
# - Must include the PL/I JNI copybook (-qincpds='ENPLI530.SIBMZSAM')
# - Options (-qpp=macro:sql) allows the DB2 precompiler to ignore the the JNI include
# - Must include the DB2 DBRMLIB if DB2 is involved
# - Include the SYSLIB for DB2 (-qddsql='SYSLIB')
pli -c PLITJAVA.pli -qincpds='ENPLI530.SIBMZSAM' -qpp=macro:sql -qdbrmlib='IMSTESTL.DB2A.DBRMLIB.DATA' -qddsql='SYSLIB'

# Link edit the PL/I application into the IMS PGMLIB, making sure the include libjvm.x (for JNI) and the DFSLI000 module
c89 -o "//'IMSTESTL.IMS1.PGMLIB(PHIDPHO5)'" PLITJAVA.o ${JAVA_HOME}/bin/classic/libjvm.x DFSLI000.x

The link edit output is the program PHIDPHO5, which is executable by the IMS Message Processing Region (MPR).

To learn how we configure an MPR region, see Sample MPR region setup and configuration.

To use Db2 from the IMS dependent region, it will be necessary to bind the IMS application name to the Db2 plan name.

Scenario 2 

Our second scenario handles the same transaction as the first, but this time we use Java to query IMS and Db2 to verify the account, and then call to our PL/I application to make IMS and Db2 updates. We then use our Java application to handle Get Unique and Insert from the IMS Message Queue.

Here’s how it works: 

  1. We define the native function that loads from the PL/I.so file included in Java’s libpath:
public native void doPliImsAndDb2(ByteBuffer inBuffer, ByteBuffer outBuffer);
static {
System.loadLibrary("PliFunctions");
}
  1. We set the Application class to interact with the IMS Message Queue. We then parse the input message with Transaction Type, Transaction Amount, and Account ID, and replace the data into a separate ByteBuffer to be shared with our PL/I application:
application = ApplicationFactory.createApplication();
MessageQueue mq = application.getMessageQueue();
while (mq.getUnique(inBuffer)) { 
readTransactionTypeFromInputMessage();
readTransactionAmountFromInputMessage();
readAccountNumberFromInputMessage();
… 
private static void readAccountNumberFromInputMessage() throws UnsupportedEncodingException { 
byte[] accountNumberBytes = new byte[10];
inBuffer.position(14);
inBuffer.get(accountNumberBytes);
accountNumber = new String(accountNumberBytes, CHARSET).trim();
pliInputBuffer.position(1);
pliInputBuffer.put(accountNumberBytes);
}

private static void readTransactionTypeFromInputMessage() throws UnsupportedEncodingException { 
    byte[] transactionTypeBytes = new byte[1];
    inBuffer.position(13);
    inBuffer.get(transactionTypeBytes);
pliInputBuffer.position(0);
pliInputBuffer.put(transactionTypeBytes);
} 

private static void readTransactionAmountFromInputMessage() throws UnsupportedEncodingException { 
    byte[] transactionAmountBytes = new byte[13];
    inBuffer.position(24);
    inBuffer.get(transactionAmountBytes);
    pliInputBuffer.position(1);
BigDecimal bigDecimal = new BigDecimal(new BigInteger(new String(transactionAmountBytes,CHARSET).trim()), 3);
    byte[] packedDecimalInBytes = new byte[5];
DecimalData.convertBigDecimalToPackedDecimal(bigDecimal, packedDecimalInBytes, 0, 9, false);
System.out.println("packedDecimalInBytes " + DatatypeConverter.printHexBinary(packedDecimalInBytes));
    pliInputBuffer.position(11);
    pliInputBuffer.put(packedDecimalInBytes);
  1. Using Account ID from the input message, we query IMS and Db2 to obtain the Account ID from both databases:  
public static void doJavaImsWork() throws Exception { 
IMSDataSource ds = new IMSDataSource();
ds.setDriverType(2);
ds.setDatastoreName("IMS1");
ds.setDatabaseName("PHIDPHO4");
Connection         conn = ds.getConnection();
PreparedStatement ps = conn.prepareStatement("SELECT ACCOUNT_NUMBER FROM PCB01.ACCOUNTS WHERE ACCOUNT_NUMBER= ? ");
ps.setString(1, accountNumber);

ResultSet results = ps.executeQuery();
if (results.next()) { 
accountNumberFromIMS = results.getString("ACCOUNT_NUMBER");
} else { 
throw new Exception("Account does not exist in the IMS table");
}  
if (results != null) { 
results.close();
} 
if (ps != null) { 
ps.close();
} 
if (conn != null) { 
conn.close();
} 
}

public static void doJavaDb2Work() throws Exception { 
DB2SimpleDataSource ds = new DB2SimpleDataSource();
ds.setDatabaseName("STLEC3");
ds.setDriverType(2);
Connection conn = ds.getConnection();
conn.setAutoCommit(false);
PreparedStatement ps = conn 
.prepareStatement("SELECT ACCOUNT_ID FROM SAMPLE.ACCOUNT_BALANCE WHERE ACCOUNT_ID= ? ");
ps.setString(1, accountNumber);
ResultSet results = ps.executeQuery();
if (results.next()) { 
accountNumberFromDB2 = results.getString("ACCOUNT_ID");
} else { 
throw new Exception("Account does not exist in the DB2 table");
} 
if (results != null) { 
results.close();
} 
if (ps != null) { 
ps.close();
} 
if (conn != null) { 
conn.close();
} 
}
  1. We check to see if the account numbers from IMS and Db2 match. If they do, we call the native PL/I function to pass control from our Java application to our PL/I application. We pass the input and output buffer as inputs:  
if (accountNumberFromDB2.equals(accountNumberFromIMS)) {
JavaToPliSql javaToPli = new JavaToPliSql();
javaToPli.doPliImsAndDb2(pliInputBuffer, outBuffer);
else {
throw new Exception("Account does not exist!");
}
  1. To be Language Environment compliant, the PL/I application must be as a subroutine and be reentrant:  
*Process Limits( Extname( 100 ) ) Margins( 1, 100 );
*Process Display(Std) Dllinit Extrn(Short);
*Process Rent DEFAULT(HEXADEC) OR('|');
PliJava_Demo: Package Exports(*);
    Java_t2_pli2java_JavaToPliSql_doPliImsAndDb2:
     Proc( JNIEnv , MyJObject, inBuffer, outBuffer )
        External( "Java_t2_pli2java_JavaToPliSql_doPliImsAndDb2" )
        Options( FromAlien NoDescriptor ByValue );
  1. The data structures for the input and the output are based on pointers we obtained using JNI:
DCL 1 IN_MESSAGE   BASED (IN_MESSAGE_POINTER), 
        05 IN_TRAN_TYPE      CHAR(1), 
        05 IN_ACCOUNT_NUMBER CHAR(10), 
        05 IN_AMOUNT         FIXED DECIMAL(9,3);
DCL 1 OUT_MESSAGE      BASED (OUT_MESSAGE_POINTER), 
        05 OUT_LL              FIXED BIN(15), 
        05 OUT_ZZ              FIXED BIN(15), 
        05 OUT_UPDATED_BALANCE FIXED DECIMAL(9,3);
…
IN_MESSAGE_POINTER = GetDirectBufferAddress(JNIEnv, inBuffer);
OUT_MESSAGE_POINTER = GetDirectBufferAddress(JNIEnv, outBuffer);
  1. Db2 calculates the amount to be withdrawn or deposited and updates the account table. The new account balance is placed in the outputBuffer so that the updated balance can be inserted back to the IMS Message Queue:  
EXEC SQL SELECT ACCOUNT_ID, BALANCE 
   INTO :ACCOUNT_ID, :BALANCE 
   FROM ACCOUNT_BALANCE 
   WHERE ACCOUNT_ID = :IN_ACCOUNT_NUMBER;
   DISPLAY('DB2 ACCOUNT: '      || ACCOUNT_ID);
   DISPLAY('DB2 BALANCE: '      || BALANCE);
    IF IN_TRAN_TYPE = 'W' THEN DO;
        TEMP_TRAN_AMOUNT = TEMP_TRAN_AMOUNT * -1;
    END;
    BALANCE = TEMP_TRAN_AMOUNT + BALANCE;
    DISPLAY('DB2 BALANCE: ' || BALANCE);
EXEC SQL UPDATE ACCOUNT_BALANCE
    SET    BALANCE = :BALANCE
    WHERE ACCOUNT_ID = :IN_ACCOUNT_NUMBER;
OUT_MESSAGE.OUT_UPDATED_BALANCE = BALANCE;
  1. IMS makes an Insert to the Activity record, which keeps track of all transactions that were made by the parent Account segment:
AIB_PTR = ADDR(AIB);
AIB.EYECATCHER = 'DFSAIB  ';
AIB.AIB_LENGTH = 128;
AIB.RSRC_NAME1 = 'PCB01   ';
IOAREA_PTR = ADDR(ACTIVITY_IOAREA);
ACTIVITY_IOAREA = '';
ACTIVITY_IOAREA.TRANSACTION_TYPE = IN_TRAN_TYPE;
ACTIVITY_IOAREA.TRANSACTION_AMOUNT = TEMP_TRAN_AMOUNT;
AIB.OUT_LENGTH_TOTAL = 40;
QUAL_SSA.SEG_ACCNUM = IN_ACCOUNT_NUMBER;
CALL AIBTDLI(FIVE, ISRT_FUNC, AIB,  
   ACTIVITY_IOAREA, QUAL_SSA, UNQUAL_SSA);
DISPLAY('AIB RETURN: '  || AIB.RETURN_CODE);
DISPLAY('AIB REASON: '  || AIB.REASON_CODE);
  1. Our PL/I application returns control to our Java application. As a final step, our Java application issues an Insert back to the IMS Message Queue with the newly updated balance as part of the outputBuffer.
imsMessageQueue.insert(outBuffer);

Compiling the application

As we did in our first scenario, we start the PL/I compiler under z/OS UNIX using the executable pli command to compile our sample PL/I application. We use this sample bash shell script, which creates a DLL during the link-edit step:

# Defining the STEPLIB, pointing do the PL/I compiler and the DB2 SDFSLOAD data set
export STEPLIB="ENPLI530.SIBMZCMP:DSNC10.SDSNLOAD" 

# Defining the DBRMLIB which points to the DB2 DBRMLIB data set
export DBRMLIB="IMSTESTL.DB2A.DBRMLIB.DATA" 

# Defining the SYSLIB which points to the PL/I JNI copybook/include file and the DB2 RUNLIB data set
export SYSLIB='ENPLI530.SIBMZSAM:IMSTESTL.DB2A.RUNLIB.LOAD' 

# Defining the PATH pointing to the binary folder of the PL/I installation
export PATH=$PATH:/usr/lpp/pli/pli530/IBM/pli/v5r3/bin 

# Defining the _C89_LSYSLIB which points to the Language Environment SCEELKED library and the DB2 SDSNLOAD data set
export _C89_LSYSLIB=CEE.SCEELKED:DSNC10.SDSNLOAD 

# Define the Java home installation and the LIBPATH 
export JAVA_HOME=/usr/lpp/java/java180/J8.0 
LIBPATH=/lib:/usr/lib:"${JAVA_HOME}"/bin 
export LIBPATH="$LIBPATH" 

# Set up the classpath, here we use the Java binary folder, the IMS Universal Drivers Jar, and the DB2 JCC jar
export CLASSPATH=$CLASSPATH: "${JAVA_HOME}"/bin:/tmp/imsudb.jar:/tmp/db2jcc4.jar 

# We copy the IMS the DFSLI000 module (Needed for PL/I IMS work) as DFSLI000.x from the SDFSRESL data set
cp -X "//'IMSBLD.I15RTSMM.SDFSRESL(DFSLI000)'" DFSLI000.x 

# Invoke the compiler
# IMPORTANT NOTES: 
# - Must include the PL/I JNI copybook (-qincpds='ENPLI530.SIBMZSAM')
# - Options (-qpp=macro:sql) allows the DB2 precompiler to ignore the the JNI include
# - Must include the DB2 DBRMLIB if DB2 is involved
# - Include the SYSLIB for DB2 (-qddsql='SYSLIB')
pli -c PLITJAVA.pli -qincpds='ENPLI530.SIBMZSAM' -qpp=macro:sql -qdbrmlib='IMSTESTL.DB2A.DBRMLIB.DATA' -qddsql='SYSLIB' 

# For Java going to PL/I, we need to create the DLL (.so) files which will be loaded by the Java application 
c89 -o libPliFunctions.so PliFunctions.o ${JAVA_HOME}/bin/classic/libjvm.x DFSLI000.x

Next we describe how to set up and configure a JMP region.

Sample JMP region setup and configuration

The following is an example of a JMP region procedure that is configured to run with DB2 and IMS. To learn more about the ENVIRON and JVMOPMAS parameters, see the IBM Docs topics ENVIRON= parameter for procedures and JVMOPMAS=name parameter for procedures.

//JMP00001 JOB MSGLEVEL=1,MSGCLASS=E,CLASS=K,
//   LINES=999999,TIME=1440,REGION=0M,
//   MEMLIMIT=NOLIMIT
/*JOBPARM  SYSAFF=*
//         JCLLIB ORDER=(USER.PRIVATE.AUTOSRVR.PROCLIB)
//*
//JMP00001 PROC CL1=001,CL2=000,CL3=000,CL4=000,OPT=W,OVLA=0,
//   SPIE=0,TLIM=00,VALCK=0,PCB=000,SOD=,STIMER=,NBA=350,OBA=50,
//   IMSID=IMS1,AGN=,SSM=DB2A,PREINIT=,ALTID=,PWFI=Y,APARM=,LOCKMAX=,
//   ENVIRON=DFSJVMEV,JVMOPMAS=DFSJVMMS,PARDLI=,PRLD=,MINTHRD=,
//   MAXTHRD=,JVM=31
//REGION   EXEC PGM=DFSRRC00,
//             PARM=(JMP,&CL1&CL2&CL3&CL4,
//         &OPT&OVLA&SPIE&VALCK&TLIM&PCB,&STIMER,&SOD,&NBA,
//         &OBA,&IMSID,&AGN,&PREINIT,&ALTID,&PWFI,'&APARM',
//         &LOCKMAX,&ENVIRON,,&JVMOPMAS,&PRLD,&SSM,&PARDLI,
//         &MINTHRD,&MAXTHRD,&JVM)
//STEPLIB  DD DISP=SHR,
//      DSN=IMSTESTL.IMS1.PGMLIB
//         DD DISP=SHR,
//      DSN=IMSBLD.I15RTSMM.CRESLIB
//         DD DISP=SHR,
//      DSN=DSNC10.SDSNLOAD
//         DD DISP=SHR,
//      DSN=DSNC10.SDSNEXIT
//PROCLIB  DD DISP=SHR,
//      DSN=USER.PRIVATE.AUTOSRVR.PROCLIB
//DFSESL   DD DISP=SHR,
//      DSN=IMSBLD.I15RTSMM.CRESLIB
//         DD DISP=SHR,
//      DSN=DSNC10.SDSNLOAD
//         DD DISP=SHR,
//      DSN=DSNC10.SDSNEXIT
//PRINTDD  DD SYSOUT=*
//SYSPRINT DD SYSOUT=*
//STDOUT   DD SYSOUT=*
//STDERR   DD SYSOUT=*
//*
//         PEND
//JMP00001 EXEC JMP00001

The DFSJVMEV file is used to establish the lib path of the JVM.

LIBPATH=>
/usr/lpp/java/java180/J8.0/bin/j9vm:>
/usr/lpp/java/java180/J8.0/bin:>
/tmp

The DFSJVMMS file is used to set up the class path. Since we are accessing IMS and Db2 with Java, we will specify the paths of the required drivers, including the location of the Java applications we want to run. The DFSJVMMS file is also used to set up JVM options.

-Djava.class.path=>
/usr/lpp/ims/dev/i15ajav/usr/lpp/ims/ims15/imsjava/imsudb.jar:>
/tmp/db2jcc4.jar:>
/tmp/db2jcc_license_cisuz.jar:>
/tmp/udbtest-0.0.1.jar
-Xmx128M                         

The DFSJVMAP member is a link between the IMS program and the Java application. In the case of a JMP Region, when transaction gets executed, IMS will load the Java application linked to the IMS program. In the case of a JBP region, IMS will load the Java application upon the scheduling of the IMS program.

PHIDPHO4=t2/pli2java/JavaToPliSql

Sample MPR region setup and configuration

The following example shows an MPR region procedure configured to run with Db2 and IMS. The configuration is similar to our JMP example, but because the application starts with PL/I, there is no need to include the DFSJVMAP member:

//MPP00001 JOB MSGLEVEL=1,MSGCLASS=E,CLASS=K,
//   LINES=999999,TIME=1440,REGION=0M,
//   MEMLIMIT=NOLIMIT                                                
/*JOBPARM  SYSAFF=*                                                   
//        JCLLIB ORDER=(USER.PRIVATE.AUTOSRVR.PROCLIB)
//*
//MPP00001 PROC AGN=,ALTID=,APARM=,APPLFE=,CL1=005,CL2=005,
//   CL3=005,CL4=005,DBLDL=,ENVIRON=DFSJVMEV,IMSID=IMS1,
//   JVMOPMAS=DFSJVMMS,LOCKMAX=,NBA=5,OBA=5,OPT=W,OVLA=0,PARDLI=,
//   PCB=040,PREINIT=,PRLD=,PWFI=N,SOD=,SPIE=0,SSM=,STIMER=0,TLIM=01,
//   VALCK=0,VFREE=,VSFX=
//REGION   EXEC PGM=DFSRRC00,
//            PARM=(MSG,&CL1&CL2&CL3&CL4,
//        &OPT&OVLA&SPIE&VALCK&TLIM&PCB,&PRLD,&STIMER,&SOD,
//        &DBLDL,&NBA,&OBA,&IMSID,&AGN,&VSFX,&VFREE,&SSM,
//        &PREINIT,&ALTID,&PWFI,&APARM,&LOCKMAX,&APPLFE,
//        &ENVIRON,&JVMOPMAS,&PARDLI)
//STEPLIB  DD DISP=SHR,
//      DSN=IMSTESTL.IMS1.PGMLIB
//         DD DISP=SHR,
//      DSN=IMSBLD.I15RTSMM.CRESLIB
//         DD DISP=SHR,
//      DSN=DSNC10.SDSNLOAD
//         DD DISP=SHR,
//      DSN=DSNC10.SDSNEXIT
//PROCLIB  DD DISP=SHR,
//      DSN=USER.PRIVATE.AUTOSRVR.PROCLIB
//DFSESL   DD DISP=SHR,
//      DSN=IMSBLD.I15RTSMM.CRESLIB
//         DD DISP=SHR,
//      DSN=DSNC10.SDSNLOAD
//         DD DISP=SHR,
//      DSN=DSNC10.SDSNEXIT
//PRINTDD  DD SYSOUT=*
//SYSPRINT DD SYSOUT=*
//STDOUT   DD SYSOUT=*
//STDERR   DD SYSOUT=*
//*
//         PEND
//MPP00001 EXEC MPP00001

The DFSJVMEV file is used to establish the lib path of the JVM.

LIBPATH=>
/usr/lpp/java/java180/J8.0/bin/j9vm:>
/usr/lpp/java/java180/J8.0/bin:>
/tmp

The DFSJVMMS file is used to set up the class path. Since we are accessing IMS and Db2 with Java, we will specify the paths of the required drivers, including the location of the Java applications we want to run. The DFSJVMMS file is also used to set up JVM options.

-Djava.class.path=>
/usr/lpp/ims/dev/i15ajav/usr/lpp/ims/ims15/imsjava/imsudb.jar:>
/tmp/db2jcc4.jar:>
/tmp/db2jcc_license_cisuz.jar:>
/tmp/udbtest-0.0.1.jar
-Xmx128M

Sample execution

The input message structure for this transaction is similar in both scenarios. A key difference is that PL/I uses a 4 byte LL field, whereas Java uses a 2 byte LL field:

  • PL/I: LLLLZZTRANCODEWFBA0011111342.23
  • Java: LLZZTRANCODEWFBA0011111342.23

In both scenarios, the application logic behaves the same way during execution: 

  • W indicates a withdrawal (D would indicate a deposit). 
  • FBA0011111 is the 10-byte account number. 
  • 342.23 is the amount to be debited or credited to the current account balance.

The transaction output returns the updated balance. So if the starting balance is 1000.00 and 342.23 is withdrawn, the output message returns 757.77 as the new balance.

And that’s it. We’ve successfully handled a transaction by integrating modern Java code with existing PL/I applications.

These two examples showed how to modernize IMS applications incrementally by using PL/I to Java interoperability. To learn more about modernizing your IMS applications, visit IBM IMS Central.

Originally published on the IBM Z and LinuxONE Community Blog.

One thought on “Writing IMS Applications Using PL/I and Java Interoperability”
  1. Thank you very much for a lot of usefull information concerning PL/I, JAVA JNI and especially IMS configs!
    It’s not so easy to get PL/I related samples in a COBOL world 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *