Tutorial

Build Java records from COBOL with IBM Record Generator

Automate the generation of Java helper classes from a COBOL copybook, and use the generated Java classes in a CICS Java application

When developing a Java program to link to a CICS COBOL program with a COMMAREA or to access a VSAM file, you will typically need to map the data fields from a record structure to specific Java datatypes. This article describes a batch process to automate the generation of Java helper classes from a COBOL copybook using the IBM Record Generator for Java V3.0 and then shows how to use the generated Java classes in a CICS Java application.

Prerequisites

  • IBM Record Generator for Java V3
  • IBM JZOS Toolkit API package com.ibm.jzos.fields at V2.4.8 or later
  • IBM Enterprise COBOL for z/OS V4.2 or later
  • Eclipse development environment with the CICS Explorer SDK plugin
  • A CICS JVM server if you want to deploy the example into CICS

Estimated time

It should take you about 1 hour to complete this tutorial.

IBM Record Generator for Java

The Record Generator is a scriptable Java-based utility that takes as input the ADATA from the COBOL compiler, and uses it to generate Java helper classes to access the fields in a COBOL record structure. The ADATA provides additional program data that can be used to extract information about the program for symbolic debugging or cross-reference tools. The Record Generator is based on a function in the IBM JZOS Toolkit that provides field converter classes for mapping byte array fields into Java data types.

IBM Record Generator for Java

Product details

IBM Record Generator for Java V3.0 is an IBM product that was generally available as of 29th Sept 2017. It superseded the IBM alphaWorks version of the JZOS Record Generator V2.4.6, which is now withdrawn. The Record Generator for Java, V3.0, provides the following new and enhanced functions:

  • Support for ADATA files with or without an IBM Record Descriptor Word (RDW) prefix, making it easier to transfer the ADATA files from z/OS to distributed platforms
  • Support for a user-supplied, Java name generator class to control the schema for generating accessor methods from field names
  • Support to ignore OCCURS 1 on COBOL single element arrays, so that fields are generated as an array of size 1
  • Ability to set the string encoding during record generation
  • A set of new generation options to optimize the generated code
  • A new IBM Knowledge Center
  • Support for Java 11

The Record Generator for Java V3.0 is freely available to download for all users of z/OS V2.1, or later. License redistribution rights are provided for deployment to non-z/OS platforms to support distributed development and build.

Steps

The following steps will be covered in this article:

  1. Set up the environment
  2. Use the Record Generator to create a Java record wrapper class from a COBOL copybook
  3. Develop a CICS Java program to use the Java record wrapper
  4. Deploy and test the Java components in a CICS OSGi JVM server

1. Set up the environment

First, you need to perform the following tasks.

  1. Download the Record Generator zip archive from the download site.
  2. Extract ibm-recgen.jar and transfer this to a directory on your zFS as this will be required by the batch job to generate records from the COBOL source.
  3. Download the EDUPGM CICS COBOL sample if you want to deploy the example from this tutorial.

2. Use the Record Generator to create the Java record wrapper class

The next step is to create the Java record wrapper class from the COBOL copybook using the IBM Record Generator for Java. This creates a helper class than can then be used in your Java application to marshal data to and from the COBOL record-based structures.

2.1. Create the COBOL build job

The first step in creating a record is to modify the compile process to produce ADATA output. To produce the ADATA you simply need to add the ADATA keyword to the COBOL PARM statement and then direct the output to a pre-allocated SYSADATA dataset. This ADATA dataset will then be used as input for the Java record generation in the next step.

Next, if required, link edit the load module for deployment into CICS. The following JCL provides a sample COBOL V5.1 compile job with the ADATA PARM option and SYSADATA DD, based on the sample in the CICS translate and compile job DFHZITCL:

    //ASMCOBJ JOB (H251620-TSOO),CLASS=A,MSGCLASS=T,NOTIFY=&SYSUID,
    // MSGLEVEL=(1,1),REGION=500M
    //  SET PROG='EDUPGM'                                               
    //COBOL  EXEC PGM=IGYCRCTL,REGION=200M,                               
    //            PARM=(NODYNAM,RENT,LIST,MAP,XREF,ADATA,                 
    //            'CICS(''SP,COBOL3'')')                                  
    //STEPLIB  DD DSN=PP.COBOL390.V510.SIGYCOMP,DISP=SHR                  
    //         DD DSN=CICSTS.610.SDFHLOAD,DISP=SHR         
    //SYSLIB   DD DSN=USER.CICS.DSECT,DISP=SHR                         
    //         DD DSN=USER.CICS.COPY,DISP=SHR                          
    //         DD DSN=CICSTS.610.SDFHCOB,DISP=SHR          
    //         DD DSN=CICSTS.610.SDFHMAC,DISP=SHR          
    //         DD DSN=CICSTS.610.SDFHSAMP,DISP=SHR         
    //SYSADATA DD DSN=USER.CICS.COBADATA(&PROG),DISP=SHR               
    //SYSPRINT DD SYSOUT=A                                                
    //SYSLIN   DD DSN=&&LOADSET,DISP=(MOD,PASS),                          
    //            UNIT=SYSDA,SPACE=(TRK,(3,3))                            
    //SYSUT1   DD UNIT=SYSDA,SPACE=(CYL,(1,1))                            
    //SYSUT2   DD UNIT=SYSDA,SPACE=(CYL,(1,1))                            
    //SYSUT3   DD UNIT=SYSDA,SPACE=(CYL,(1,1))                            
    //SYSUT4   DD UNIT=SYSDA,SPACE=(CYL,(1,1))                            
    //SYSUT5   DD UNIT=SYSDA,SPACE=(CYL,(1,1))                            
    //SYSUT6   DD UNIT=SYSDA,SPACE=(CYL,(1,1))                            
    //SYSUT7   DD UNIT=SYSDA,SPACE=(CYL,(1,1))                            
    //SYSUT8   DD UNIT=SYSDA,SPACE=(CYL,(1,1))                            
    //SYSUT9   DD UNIT=SYSDA,SPACE=(CYL,(1,1))                            
    //SYSUT10  DD UNIT=SYSDA,SPACE=(CYL,(1,1))                            
    //SYSUT11  DD UNIT=SYSDA,SPACE=(CYL,(1,1))                            
    //SYSUT12  DD UNIT=SYSDA,SPACE=(CYL,(1,1))                            
    //SYSUT13  DD UNIT=SYSDA,SPACE=(CYL,(1,1))                            
    //SYSUT14  DD UNIT=SYSDA,SPACE=(CYL,(1,1))                            
    //SYSUT15  DD UNIT=SYSDA,SPACE=(CYL,(1,1))                            
    //SYSMDECK DD UNIT=SYSDA,SPACE=(CYL,(1,1))                            
    //SYSIN    DD DSN=USER.CICS.SOURCE(&PROG),DISP=SHR           
    /*                                                                     
    //COPYLINK EXEC PGM=IEBGENER,COND=(7,LT,COBOL)                         
    //SYSUT1   DD DSN=CICSTS.610.SDFHSAMP(DFHEILID),DISP=SHR
    //SYSUT2   DD DSN=&&COPYLINK,DISP=(NEW,PASS),                          
    //            DCB=(LRECL=80,BLKSIZE=400,RECFM=FB),                     
    //            UNIT=SYSDA,SPACE=(400,(20,20))                           
    //SYSPRINT DD SYSOUT=A                                                 
    //SYSIN    DD DUMMY                                                    
    //*                                                                    
    //LKED   EXEC PGM=IEWL,REGION=200M,                                    
    //            PARM='LIST,XREF,RENT,MAP',COND=(7,LT,COBOL)              
    //SYSLIB   DD DSN=CICSTS.610.SDFHLOAD,DISP=SHR          
    //         DD DSN=PP.ADLE370.ZOS201.SCEELKED,DISP=SHR                  
    //SYSUT1   DD UNIT=SYSDA,DCB=BLKSIZE=1024,                             
    //            SPACE=(1024,(200,20))                                    
    //SYSPRINT DD SYSOUT=A                                                 
    //SYSLIN   DD DSN=&&COPYLINK,DISP=(OLD,DELETE)                         
    //         DD DSN=&&LOADSET,DISP=(OLD,DELETE)                          
    //         DD DDNAME=SYSIN                                             
    //SYSLMOD  DD DSN=USER.CICS70.LOADLIB,DISP=SHR                      
    //SYSIN    DD *                                                        
        NAME &PROG(R)                                                   
    /*

2.2 Build the Java record

Now create a job step using BPXBATCH to invoke the Java RecordClassGenerator utility class from the ibm-recgen.jar using the ADATA as the input and naming the desired COBOL structure. In the example below, we used the ADATA in the input PDS USER.CICS.COBADATA from step 2.1 and generated a record com.ibm.cicsdev.bean.JZOSCommareaWrapper.java. This wrappers the DFHCOMMAREA structure from our EDUPGM COBOL program. The inputs to the job are parameterized to allow this to be scripted later.

    //RECGEN EXEC PGM=BPXBATCH      
    //STDENV   DD *               
    JAVAHOME=/java/java80_64/8.0_64     
    PKG=com.ibm.cicsdev.bean               
    DIR=com/ibm/cicsdev/bean
    PROG=EDUPGM
    //STDOUT   DD   SYSOUT=*    
    //STDERR   DD   SYSOUT=*
    //STDPARM DD *                    
    sh cd /u/cics1/recgen;                                              
    $JAVAHOME/bin/java                                                    
      -cp ./ibm-recgen.jar                                              
      com.ibm.recordgen.cobol.RecordClassGenerator                   
      adataFile="//'USER.CICS.COBADATA(${PROG})'"                      
      symbol=DFHCOMMAREA                                                  
      outputDir=.                                                         
      package=${PKG}                                                      
      class=JZOSCommareaWrapper;
    /*

2.3 Compile the Java record class

Lastly, you need to build the Java record JZOSCommareaWrapper.java created in step 2.2. This requires compiling using the javac compiler and then packaging as a JAR for deployment using the Java-supplied jar utility. You can simply add the following statements to the end of the BPXBATCH job step:

    JAVAHOME/bin/javacJAVAHOME/bin/javac JAVAHOME/bin/javac{DIR}/JZOSCommareaWrapper.java;                      
    JAVAHOME/bin/jar−cvfJAVAHOME/bin/jar -cvf JAVAHOME/bin/jar−cvf{PROG}.jar ${DIR}/JZOSCommareaWrapper.class;

The job is now ready and can be used as part of a build system to easily rebuild the record when any changes are made to the copybooks. The record can be transferred to your source code management system and then downloaded for use in an Eclipse development environment to assist with the marshalling of data when interacting with COBOL structures in a COMMAREA or other sequential data records such as VSAM files.

3. Develop the CICS Java program

CICS Java development is centered around the use of the CICS Explorer SDK, which helps you develop with OSGi. In an OSGi development environment, Target Platform definitions are used to define the plug-ins that applications in the workspace will be built against.

3.1 Create the Eclipse project

The first step is to create an Eclipse project. You will be using an OSGi development environment, so before you generate any code you first need to create a new OSGi Plug-in project in Eclipse using the wizard: File -> New -> Plug-in Development -> Plug-in Project. As you go through the New Plug-in Project wizard, follow these steps:

  1. On the first panel of the New Plug-in Project wizard, select the standard OSGi framework option as you do not need any extra Eclipse or Equinox extensions.
  2. On the second panel, make sure that the Version is set to 1.0.0 without a qualifier as the OSGi JVM server does not support the use of qualifiers. Also ensure that the selected Java execution environment is supported in your JVM server, and that the option to generate an activator is not selected as this is not required.
  3. On the third panel, uncheck the box to "Create a plug-in using a template" as you will add your own application code to the project.

3.2 Creating the Target Platform

The CICS Explorer SDK provides a set of sample Target Platforms that provide the JCICS APIs for different CICS TS versions. The Target Platform is set using the Eclipse menu: Windows -> Preferences -> Plug-in Development -> Target Platform. Click Add, and then from the supplied templates select the CICS TS release for your runtime environment, and click Next and then Finish. You can then select this and apply it to your workspace, which will allow you to use the JCICS APIs in your application. For further details on the options for defining OSGi package imports, refer to the article Configuring OSGi package imports.

Target Platform

3.3 Adding OSGI package imports

In addition to JCICS, you also need to consider the Java packages used by the generated records. These records rely on classes in the the com.ibm.jzos.fields package, which is provided with the IBM SDK for Java V8 on z/OS in ibmjzos.jar available in the lib/ext directory, or by IBM Semeru Runtime Certified Edition, Version 11 from this download site.

If you just use the pre-built generated records in your application, then OSGI package imports are not required when compiling the application; however, they will be required at runtime in the OSGI JVM server as all the dependencies need to be correctly wired when the OSGI bundles are installed. To resolve this import, there are 3 options:

  1. Download and wrapper the ibmjzos.jar as an OSGI bundle, export the required packages, add this OSGI bundle to your Eclipse workspace or target platform, and then add an Import-Package statement for com.ibm.jzos.fields to the bundle manifest.
  2. Use a resolution:=optional Import-Package statement in the OSGi bundle manifest. This will allow the bundle to activate if the packages can't be located. This allows the code to be compiled and exported without the JZOS packages being available in the workspace, but will still allow the JZOS package dependency to be resolved in the JVM server when the bundle is installed. This is because the CICS OSGi JVM server adds the JZOS packages to the default list exported by the OSGI system bundle.
  3. If you are using CICS TS V5.3 or later, you can exploit the OSGi last resort boot delegation behaviour which will load classes from the JVM boot classpath if they fail to load from the bundle class loaders. This means you would not need to add an Import-Package statement unless you modify the default boot delegation behavior of the JVM server. For further details, refer to Configuring OSGi package imports.

You will use the optional import method (#2) as you don't need to use any other functions of JZOS other than the com.ibm.jzos.fields package, and it is good practice to ensure that all dependencies are explicitly declared in the bundle manifest.

Add the following Import-Package statements to the bundle manifest for JCICS and JZOS. The JCICS import should be versioned to define the OSGi semantic version referring to the supported JCICS API -- for instance, com.ibm.cics.server;version="[1.300.0,3.0.0)". However, the JZOS package com.ibm.jzos.fields can be listed without a referenced version.

Your resulting manifest should now look something like this:

    Manifest-Version: 1.0
    Bundle-ManifestVersion: 2
    Bundle-Name: com.ibm.cicsdev.jzostest
    Bundle-SymbolicName: com.ibm.cicsdev.jzostest
    Bundle-Version: 1.0.0
    Import-Package: com.ibm.cics.server;version="[1.300.0,3.0.0)",
         com.ibm.jzos.fields;resolution:=optional
    Automatic-Module-Name: com.ibm.cicsdev.jzostest
    Bundle-RequiredExecutionEnvironment: JavaSE-1.8
    .

3.4 Create a CICS Java application

Now you are ready to create the Java application. As you will need to use OSGi, you first need to ensure that the packages required are exported from their corresponding OSGi bundles. We already discussed access to the JZOS packages in step 3.2, but the CommareaWrapper class was built as an ordinary JAR. You can either create a wrapper project for this to convert it into an OSGi bundle, or you can just add this JAR to the OSGi bundle classpath of your new application. You will use the latter method and add the JAR to the bundle classpath. This method will limit usage of the CommareaWapper to components in the OSGi bundle you are about to create, which is fine for our example.

Create a new OSGi bundle project in Eclipse using the wizard: File -> New -> Other -> Plug-in Project, as described in section 3.1. Then create a new Java package com.ibm.cicsdev.jzostest in the src folder and add a new class JZOSprog to that package. Copy in the code shown below, which makes a JCICS Program.link() call to your EDUPGM CICS COBOL program. Alternatively, you can download the sample code.

package com.ibm.cicsdev.jzostest;

import java.text.MessageFormat;
import com.ibm.cics.server.InvalidProgramIdException;
import com.ibm.cics.server.InvalidRequestException;
import com.ibm.cics.server.Program;
import com.ibm.cics.server.Task;
import com.ibm.cicsdev.bean.JZOSCommareaWrapper;

public class JZOSprog {

    public static String proglink = "EDUPGM";  // Linked to COBOL program

    public static void main(String[] args)          
    {

        // Get details about our current CICS task
        Task task = Task.getTask();
        task.getOut().println(" - Starting JOZSprog");

        // Wrapper objects for input and output commareas
        JZOSCommareaWrapper cwIn = null;
        JZOSCommareaWrapper cwOut = null;

        // Set the input data fields
        Short binarydigit = 1;
        String charstring = "hello";
        Short numericstring = 1234;
        Integer packeddigit = 123456789;
        Integer signedpackeddigit = -100;
        String bool = "1";

        cwIn = new JZOSCommareaWrapper();
        cwIn.setBinaryDigit(binarydigit );
        cwIn.setCharacterString(charstring );
        cwIn.setNumericString(numericstring );
        cwIn.setPackedDigit(packeddigit );
        cwIn.setSignedPacked(signedpackeddigit );
        cwIn.setBool(bool );

        // Create a reference to the CICS program
        Program prog = new Program();
        prog.setName(proglink);

        // Create byte array for input commarea from wrapper
        byte[] commarea = cwIn.getByteBuffer();

        try {
            // Link to target CICS program
            prog.link(commarea);

            //Create output record from updated commarea
            cwOut = new JZOSCommareaWrapper(commarea);

            // Catch specific JCICS exception
        } catch (InvalidProgramIdException e) { 
         String msg = "ERROR: Invalid CICS Program {0} with msg({1})";
         task.getOut().println(MessageFormat.format(msg, proglink, e.getMessage()));

            // Catch any other exception and force a rollback of CICS UOW
        } catch (Exception e) {
            String msg = "ERROR: Exception on link to {0} with msg({1})";
            task.getOut().println(MessageFormat.format(msg, proglink, e.getMessage()));
            // Rollback the CICS Task
            try
            {
                task.rollback();
            } catch (InvalidRequestException e1) {      
                // If this fails, then throw Runtime Exception
                throw new RuntimeException(e1);
            }
        }

        String msg = "Returned from {0} with rc({1}) {2}";
        task.getOut().println(MessageFormat.format(msg, proglink,cwOut.getResultCode(), cwOut.getResultText()));

    }

}

This sample uses the setter methods from the generated CommareaWrapper to access the individual fields in the record. Each field in the COBOL record can be written to using methods such as setBinaryDigit() and setCharacterString(), and also read via corresponding getters. The entire byte array representing the records can be passed into a call using a Java byte[] array that's returned via the getByteBuffer().

COBOL source

When generating Java code from COBOL records, it may be helpful if you rename the COBOL data fields in a mixed-case Java fashion so that the generated code has a naming convention that is more familiar to a Java programming style. The figure above shows an example of the renaming used in our COBOL data structure: The image on the left uses the original uppercase-based format, and the image on the right shows the mixed-case format used as input to the Record Generator utility.

The screen shot below shows the outline view of the generated CommareaWrapper class in Eclipse with all the generated setters and getters for the fields in the record.

Java wrapper

Next, transfer the EDUPGM.jar containing the CommareaWrapper record onto the workstation. Create a new folder in the project called lib, and then drag the JAR into this folder in your Eclipse project. Then, from the OSGi bundle manifest editor add this JAR to the OSGI bundle classpath using the menu Runtime -> Classpath -> Add. This will add the following statement to the manifest:

    Bundle-ClassPath: lib/EDUPGM.jar,
     .

Also update the Build tab in the manifest editor to add the lib directory to the Binary Build to ensure that the EDUPGM.jar gets added to the build output.

Finally, add a CICS-MainClass: definition to the bundle manifest to register a MainClass service for your com.ibm.cicsdev.jzostest.JZOSprog class. This allows the Java class to be LINKed to using a CICS program definition. Your manifest should now look something like this:

    Manifest-Version: 1.0
    Bundle-ManifestVersion: 2
    Bundle-Name: com.ibm.cicsdev.jzostest
    Bundle-SymbolicName: com.ibm.cicsdev.jzostest
    Bundle-Version: 1.0.0
    Import-Package: com.ibm.cics.server;version="[1.300.0,2.0.0]",
         com.ibm.jzos.fields;resolution:=optional
    Automatic-Module-Name: com.ibm.cicsdev.jzostest
    Bundle-RequiredExecutionEnvironment: JavaSE-1.8
    Bundle-ClassPath: lib/EDUPGM.jar,
     .
    CICS-MainClass: com.ibm.cicsdev.jzostest.JZOSprog

4. Test the application

The application is now ready to be tested, and can simply be deployed into a CICS OSGi JVM server using a CICS Bundle project as follows:

  1. In Eclipse: Create a CICS bundle project using the menu New -> CICS Bundle Project.
  2. In Eclipse: Add the OSGi Plugin project created in 3.1 to the CICS Bundle project.
    • Right-click on the CICS Bundle project you just created.
    • Select New -> OSGi Bundle Project Include.
    • Then click on the OSGI bundle from the Select an OSGI bundle box.
    • Type the name of the JVM Server to be used in your CICS region.
    • Click Finish.
  3. In Eclipse: Deploy to zFS
    • Right-click on the CICS Bundle project and select Export Bundle Project to z/OS UNIX File System.
    • Select Export to a specific location in the file system.
    • Deploy to a location of your choice on z/FS by updating the Parent Directory field.
    • Check Clear existing contents of the bundle directory.
    • Click Finish.
  4. In CICS: Create a CICS BUNDLE definition that references this zFS location and install it.
  5. In CICS: Create a CICS PROGRAM definition for the Java application called JZOSPROG. This should name the CICS-MainClass: com.ibm.cicsdev.jzostest.JZOSprog in the JVMClass attribute. Also set the JVMServer attribute to the name of the JVMSERVER to be used.
  6. In CICS: Optionally, create a PROGRAM definition for the COBOL EDUPGM if you are not using program autoinstall.
  7. In CICS: Create a transaction definition that references the JZOSPROG program definition if this is to be run from a 3270 terminal.
  8. In CICS: Install the program and transaction definitions.

When invoked, your Java class JZOSprog will link to the COBOL EDUPGM using the CommareaWrapper classes to marshall the input to the required COBOL record formats. It should return the following messages, if successful, indicating the input and output CommareaWrapper records have been successfully used to marshall the data in the COMMAREA:

Starting JZOSprog
Returned from EDUPGM with rc(0) PARAMETERS ARE ALL OK

Summary

This tutorial has demonstrated how to automate the building of Java helper classes from COBOL copybooks using the IBM Record Generator for Java. These helper classes greatly simplify the mapping of record-based data to Java data types within Java applications that need to access data on IBM Z systems. After completing this tutorial, you should be able to customise this process for your own COBOL copybooks or assembler DSECTs, and integrate the build within your own development pipeline.

For more details on the technologies covered in this tutorial, see the Resources links in the right-hand column.