What DBB APIs issue z/OS commands

Java and Groovy both provide easy mechanisms to create directories, copy files and even invoke external programs. However, these mechanisms are only for use with distributed operating systems such as Unix, Linux and Windows that use hierarchical file systems (HFS). There is usually no native support for working with z/OS datasets or invoking z/OS programs and commands. DBB provides the following set of Java classes to be used for issuing z/OS commands:

This section of documentation is meant to provide an overview of these commands as well as tips on how to use them in your build scripts. For a more detailed look at the Java API specifics for each command, see the Toolkit Javadoc. All of the z/OS command Java classes are located in the com.ibm.dbb.build package.

CreatePDS

This command is used to create a new cataloged partitioned data set (PDS). It does nothing if the data set already exists. The command has two attributes: the data set name and the creation options.

def newPDS = new CreatePDS()
newPDS.setDataset("USR1.SRC.COBOL")
newPDS.setOptions("cyl space(1,1) lrecl(80) dsorg(PO) recfm(F,B) dsntype(library)")
newPDS.execute()

The CreatePDS command also supports setter method chaining which provides a more compact and readable way to define a command for execution. For example:

new CreatePDS().dataset("USR1.SRC.COBOL").options("cyl space(1,1) lrecl(80) dsorg(PO) recfm(F,B) dsntype(library)").execute()

The CreatePDS command uses the BPXWDYN text interface to dynamically allocate a new cataloged data set. The options attribute must consist of valid BPXWDYN dynamic allocation options.

CopyToPDS

This command is used to copy source files, binary files and load modules from a zSeries File System (zFS) to a partition data set (PDS). The source of the command can be:

The target of the command is a PDS. By default the copied PDS member name is derived from the source file name by removing any file extension and uppercasing the result. When copying a single file, an optional PDS member name can be set.

// Copy a single file
def copyFile = new CopyToPDS()
copyFile.setFile(new File("/u/usr1/build/helloworld.cbl"))
copyFile.setDataset("USR1.BUILD.COBOL")
copyFile.setMember("HELLO")
copyFile.execute()

// CopyToPDS supports setter method chaining
new CopyToPDS().file(new File("/u/usr1/build/helloworld.cbl")).dataset("USR1.BUILD.COBOL").member("HELLO").execute()

// Copy a entire directory to PDS
new CopyToPDS().file(new File("/u/usr1/src/cobol")).dataset("USR1.SRC.COBOL").execute()

//Copy resolved dependencies to PDS
def resolver = tools.getDefaultDependencyResolver(file)
def deps = resolver.resolve()
new CopyToPDS().dependencies(deps).dataset("USR1.SRC.COPYBOOK").execute()

The CopyToPDS class also has a createMemberName(name) static method that can be used to create a PDS member name from a zFS file path. It can be useful when needing the member name of a copied file later in a build script.

def fileName = "/u/usr1/build/hello.cbl"
def dsName = "USR1.BUILD.COBOL"
def memberName = CopyToPDS.createMemberName(fileName)  // memberName = HELLO
new CopyToPDS().file(new File(fileName)).dataset(dsName).member(memberName).execute()
. . .
compile.dd(new DDStatement().name("SYSIN").dsn("$dsName($memberName)").options("shr"))

The value of copyMode can be TEXT, BINARY or LOAD. If copyMode is not specified, the default TEXT is used. For more information, see setCopyMode(CopyMode copyMode) and copyMode(CopyMode copyMode).

Archive files support

You can also use CopyToPDS to copy files contained within archives. This is useful for copying binary or text files to a data set from archived or compressed archive files.

// copy individual binary file with standard member name
copy = new CopyToPDS()
copy.setDataset("USR.INDIV") // data set to copy to
copy.setArchive("archive/archive_bin.tar") // acrhive to copy from
copy.setArchivedFile("bin/EPSMLIST").copyMode(CopyMode.BINARY) // file in archive
copy.execute()

// CopyToPDS supports setter method chaining
cmd = new CopyToPDS().dataset("USR.INDIV").archive("archive/archive_bin.tar").archivedFile("bin/EPSMLIST").copyMode(CopyMode.BINARY)
cmd.execute()

Handling codepages

By default, the copied PDS member has the same codepage as the source zFS file. The code page of the file is determined in the following order:

  1. The file encoding tag of the source file is used if present.

    • Rocket's Git client automatically adds file encoding tags when cloning or pulling source files from a distributed Git server.
  2. The ZLANG environment variable is used if set.

  3. The default IBM-1047 codepage is used.

Additionally, you can manually set both the source file codepage and the target data set member codepage.

// Indicates that the file is encoded as IBM-037 and will write the PDS member as IBM-037
new CopyToPDS().file(new File(fileName)).hfsEncoding("IBM-037").dataset(dsName).member(memberName).execute()

If the PDS encoding differs from the HFS encoding, a codepage conversion is automatically performed during the copy process.

// Indicates that the file is encoded as UTF-8 and will write the PDS member as IBM-1047
new CopyToPDS().file(new File(fileName)).hfsEncoding("UTF-8").dataset(dsName).member(memberName).pdsEncoding("IBM-1047").execute()

Note: The PDS and HFS encoding options are applied only when the value of copyMode is TEXT. These values are ignored for BINARY or LOAD values.

CopyToHFS

This command is used to copy PDS members to zFS files. A common use of CopyToHFS command is to copy the SYSPRINT outputted from the COBOL compiler to a zFS directory as a log file so that you can view it without having to change environments.

// Copy a PDS member to zFS
def copyLog = new CopyToHFS()
copyLog.setDataset("USR1.BUILD.SYSPRINT")
copyLog.setMember("HELLO")
copyLog.setFile(new File("/u/usr1/build/helloworld.log"))
copyLog.execute()

// CopyToHFS supports setter method chaining
new CopyToHFS().dataset("USR1.BUILD.SYSPRINT").member("HELLO").file(new File("/u/usr1/build/helloworld.log")).execute()

CopyToPDS can also use as its source the name of an allocated data definition (DD) statement. This is useful to copy when copying temporary data sets to zFS.

new CopyToHFS().ddName("SYSPRINT").file(new File("/u/usr1/build/helloworld.log").execute()

The CopyToHFS command supports four modes of operation:

Copy Mode Usage Description
TEXT .copyMode(CopyMode.TEXT) Default. Copy data set members to zFS files as text.
ASA_TEXT .copyMode(CopyMode.ASA_TEXT) Copy SYSPRINT data set members to zFS files as text preserving ASA carriage control charaters.
BINARY .copyMode(CopyMode.BINARY) Copy data set members to zFS files in binary mode.
LOAD .copyMode(CopyMode.LOAD) This load module copy is similar to a BINARY copy but also preserves the metadata of the load module being copied and including ISPF Stats and ALIAS.

For more information, see setCopyMode(CopyMode copyMode) and copyMode(CopyMode copyMode).

Handling Codepages

By default the copied zFS file will have the same codepage as the source PDS member or data set. The code page of the dataset is determined in the following order:

  1. The ZLANG environment variable is used if set.
  2. The default IBM-1047 codepage is used.

Additionally both the source data set and the target zFS codepage can be manually set by the user. When using DBB in conjunction with a Jenkins CI build server, a common practice is to convert the SYSPRINT files to UTF-8 when they are copied to zFS in order that they can display correctly in Jenkin build result dashboards.

new CopyToHFS().ddName("SYSPRINT").file(new File("/u/usr1/build/helloworld.log").hfsEncoding("UTF-8").execute()

Note: The PDS and HFS encoding options are only applied when the value for copyMode is TEXT. These values are ignored for BINARY or LOAD values.

MVSExec

This command is used to invoke z/OS programs such a compilers, link editors, pre-compilers. It has similar characteristics of a JCL EXEC statement:

def zoscmd = new MVSExec()
zoscmd.setPgm("WKLYRPT")
zoscmd.setParm("COL=3")
def rc = zoscmd.execute()

The MVSExec command execute method returns the standard RC of the command as an int value that can be evaluated to determine the success or failure of the command. When rewritten using setter method chaining, you can begin to see the similarities between a JCL EXEC statement and the MVSExec command:

// JCL
//STEP1  EXEC  PGM=WKLYRPT,PARM='COL=3',COND=(8,LE)

// DBB
def step1 = new MVSExec().pgm("WKLYRPT").parm("COL=3").execute()
if (step1 > 8) System.exit(1)

The above example is simplistic in that most z/OS programs also require data definition (DD) statements to define the data sets that the program uses when it runs.

DDStatement

The DDStatement class is a utility class used by the three Exec commands (MVSExec, ISPFExec, TSOExec) to define data definition (DD) statements that will automatically be allocated before and freed after the program or command is executed.

// Delete a dataset by calling IEFBR14
def delete = new MVSExec().pgm("IEFBR14")

def oldDS = new DDStatement()
oldDS.setName("OLDDS")
oldDS.setDsn("USR1.BUILD.COBOL")
oldDS.setOptions("old delete")
delete.addDDStatment(oldDS)

def rc = delete.execute()

// Using setter method chaining
def delete = new MVSExec().pgm("IEFBR14")
delete.dd(new DDStatement().name("OLDDS").dsn("USR1.BUILD.COBOL").options("old delete"))
def rc = delete.execute()

The four main parts of a DD statement are:

DD Concatenation

Many z/OS compilers, linkers will require one or more data sets to be concatenated together to form a library to be searched for referenced modules needed by the z/OS program. In JCL, this is done by creating a DD concatenation.

// JCL
//SYSLIB   DD   DSNAME=USR1.OBJ,DISP=SHR
//         DD   DSNAME=CEE.SCEELKED,DISP=SHR
//         DD   DSNAME=DFH.CICS.SDFHLOAD,DISP-SHR

In DBB there are two ways to define a DD concatenation:

TASKLIB vs STEPLIB

A major difference between the JCL EXEC statement and the DBB MVSExec command is that the JCL STEPLIB has been replaced with a TASKLIB in DBB. This is because DBB runs as a Java application in a zOS UNIX subsystem where the STEPLIB is defined as an environment variable that cannot be modified during the DBB build process. Additionally DBB invokes z/OS programs using the ATTACHX assembler macros. The ATTACHX macro uses the TASKLIB as its searchpath to locate the program being invoked.

// JCL
//COBOL  EXEC PGM=IGYCRCTL,REGION=0M,PARM='LIB'                                                                                    
//STEPLIB  DD DISP=SHR,DSN=IGY.SIGYCOMP                                                                           

// DBB
def cobol = new MVSExec().pgm("IGYCRCTL").parm("LIB")
cobol.dd(new DDStatement().name("TASKLIB").dsn("IGY.SIGYCOMP").options("shr"))

Temporary data sets

Temporary data sets are data sets that are created when they are allocated and deleted when they are freed. In DBB a temporary data set is created by defining a DDStatement with no data set name (DSN) or providing a data set name that begins with "&&".

// Create temporary datasest that exists only for the length of the MVSExec command
compile.dd(new DDStatement().name("SYSUT1").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSLIN").dsn("&&TEMPOBJ").options("cyl space(5,5) unit(vio) new"))

Passing temporary data sets

In some cases it is useful to have a temporary data set continue to exist after the MVSExec command completes to be used by another MVSExec command. This is done by using the pass option when declaring the DDStatement which will prevent the MVSExec command from freeing the DD statement upon completion.

compile.dd(new DDStatement().name("SYSLIN").dsn("&&TEMPOBJ").options("cyl space(5,5) unit(vio) new").pass(true))

After the compile MVSExec command completes, the SYSLIN DD allocation still exists and can be used by following MVSExec commands such as the linker which requires a SYSLIN allocation as input.

def linkedit = new MVSExec().pgm("IEWBLINK").parm("MAP,RENT,COMPAT(PM5)")

The pass option only applies to DD statements with temporary data sets. When using the pass option, you need to provide a data set name for temporary data sets. For permanent data sets it is ignored if set and the DD allocation will be freed at the end of the MVSExec command as normal.

IMPORTANT Passed DD statements can cause a problem if not used in conjunction with the MVSJob utility class.

Free temporary DDs allocated by REXX and C compilers

Some compilers such as REXX and C often allocate temporary DDs when certain files are being included. These temporary DDs are not freed automatically at completion of the compiler run. Beginning from DBB V1.0.3, MVSExec has a freePgmAllocatedDDs() option that you can use to free these DDs when a complier run completes. The freePgmAllocatedDDs() option triggers a comparison of all the DDs before and after the compiler is called. If some DDs are detected to be not freed by the compiler, specifying the freePgmAllocatedDDs() option frees those DDs.

MVSJob

In JCL a passed DD statement is automatically freed at the end of the JCL job. However in DBB this would not happen until the entire build process finishes. This can cause a problem when the same called build script is invoked repeatedly while building a list of programs of the same type. The second time the script is invoked, it would fail due to a BPXWDYN allocation error. Probably either a 0x410 - Specified ddname unavailable or a 0x420 - Specified ddname or dsname associated with an open data set.

DBB provides MVSJob, which frees DDs until the end of MVSJob, so that you can use MVSJob to define the scope of the passed DD statement. This is useful with temporary data sets. Moreover, you can embed several MVSExec commands in one MVSJob.

// use MVSJob start and stop commands to handle passed DDs
def job = new MVSJob()
job.start()
rc = compile.execute()
if (rc <= 4)
    rc = linkedit.execute()
job.stop()

When the MVSJob start method is invoked all passed DDStatements with temporary data sets are registered in a table when allocated and will remain allocated until the MVSJob stop method is invoked in which case all of the DDStatements in the table are freed. There is no limit or restriction of how many or what type of statements can be coded between the MVSJob start and stop method invocations. All statements are ignored except MVSExec statements.

MVSJob also has an executable mode that uses setter method chaining to add several MVSExec commands to the MVSJob and then execute the entire MVSJob. An explicit start() is invoked at the beginning of the job execution and an implicit stop() is invoked at the end of the job.

// Build a BMS map
def copybookGen = new MVSExec().file(file).pgm("ASMA90").parm("SYSPARM(DSECT),DECK,NOOBJECT")
. . .
def compile = new MVSExec().file(file).pgm("ASMA90").parm("SYSPARM(MAP),DECK,NOOBJECT")
. . .
def linkedit = new MVSExec().file(file).pgm("IEWBLINK").parm("MAP,RENT,COMPAT(PM5)")
. . .
def rc = new MVSJob().executable(copybookGen).executable(compile).executable(linkedit).maxRC(0).execute()

In the above example, the three MVSExec commands are executed in the order they are added to the MVSJob. An optional maxRC can be added that will cause the job to terminate early if any MVSExec command returns an RC greater than the maxRC.

Multi-thread support with MVSJob

Executing two or more MVSExec commands at the same time in a multi-thread environment often requires the ability to concurrently allocate the same DD name. This is generally not possible when running the commands in a single JVM because they use the same address space and will often lead to an error when a second process tries to allocate the same DD that was already allocated by the first process. Beginning with v1.0.2, DBB provides a new Java option DBB_SUBPROCESS_ENABLED that when set to true will cause all MVSExec executions and DD allocations performed as part of a MVSJob to be done in a separate address space. The DBB_PROCESS_ENABLED option can be set using Java -D option:

groovyz -DDBB_SUBPROCESS_ENABLED=true build.groovy

Note: You might have set the USS environment in such a way as to prevent processes from creating separate address spaces. While this is not an issue when launching DBB build scripts from a Jenkins remote agent, it does impact DBB build processes launched from the command line. It is recommented to use the DBB groovyz shell script included in the DBB toolkit at /usr/lpp/IBM/dbb/bin/groovyz to launch DBB build scripts from the command line as it has been modified to allow for the creation of additional address spaces.

Copying temporary data sets

As mentioned in the section covering the CopyToHFS command, it can be useful to copy the SYSPRINT output from compilers and linkers to zFS as log files to retain for later use. Instead of having to write the SYSPRINT file to a permanent data set, the CopyToPDS can also use as its source a DD statement name. Additionally declared CopyToHFS commands can be added to an MVSExec command in the same way the DD statements are added. These CopyToHFS commands are then executed as part of the MVSCommand execution.

def compile = new MVSExec().file(file).pgm("IGYCRCTL").parm("LIB")
compile.dd(new DDStatement().name("SYSPRINT").options("cyl space(5,5) unit(vio) new"))
compile.copy(new CopyToHFS().ddName("SYSPRINT").file(new File("/u/usr1/build/helloworld.log").hfsEncoding("UTF-8"))

When the MVSExec command is executed the following occurs:

  1. All DD statements are allocated
  2. The z/OS program is executed
  3. All CopyToHFS commands are executed
  4. All DD statements are freed except statements with temporary data sets marked as pass

In-stream data sets

An in-stream data set is a way to define input content to a z/OS program without having to read in a file or data set from disk.

// JCL
//SYSIN *
HELLO
/*

// DBB 
new DDStatement().name("SYSIN").instreamData("HELLO")

Use Java newline characters to separate the String into multiple lines of text.

// JCL
//SYSIN *
RECORD1
RECORD2
RECORD3
/*

// DBB
def records = "RECORD1\nRECORD2\nRECORD3"
new DDStatement().name("SYSIN").instreamData(records)

You can also use triple quotes for this purpose:

// JCL
//SYSIN *
RECORD1
RECORD2
RECORD3
/*

// DBB
def records = '''RECORD1
RECORD2
RECORD3'''
new DDStatement().name("SYSIN").instreamData(records)

Note: In-stream data has a maximum line length of 80 characters. It is typical that column 1 for In-stream data would be a white space.

ISPFExec and TSOExec

These commands are used to run TSO or ISPF commands using the TSO/ISPF Client Gateway. They are combined into one section because other than the command names, the API for each are identical.

// copy a member from one dataset to another
def memberCopy = new TSOExec().command("OCOPY INDD(IN) OUTDD(OUT) TEXT CONVERT(YES) TO1047")
memberCopy.dd(new DDStatement().name("IN").dsn("USR1.BUILD.LOAD(EPSCMORT)").options("shr"))
memberCopy.dd(new DDStatement().name("OUT").dsn("USR1.BUILD.LOADBK(EPSCMORT)").options("shr"))
memberCopy.logFile(new File("/u/usr1/build/logs/EPSCMORT_ispf.log"))
int rc = memberCopy.execute();

Using ISPF Gateway

Setting up ConfDir

ISPFExec and TSOExec commands require location of the DBB configuration directory i.e. confDir which contains the location of the shell script used to communicate with the gateway. The easiest way to do this is to set the DBB_CONF environment variable. Beginning in v1.0.2 DBB requires two new environment variables: DBB_HOME and DBB_CONF in order to function correctly. For more information, see “Environment variables” in Installing and configuring toolkit on z/OS.

The confDir can also be set on the command itself. This will be used in place of the value in DBB_CONF.

def memberCopy = new TSOExec().command("OCOPY INDD(IN) OUTDD(OUT) TEXT CONVERT(YES) TO1047")
memberCopy.confDir("/usr/lpp/IBM/dbb/conf")

Legecy vs Interactive gateway

DBB supports using both the Legacy ISPF Gateway and the newer Interactive ISPF Gateway. The type of gateway to be used must be specified by the user so that DBB will generate the correct gateway XML API. The easiest way to specify the gateway type is to set the DBB gateway type configuration property in a property file and read it in to the DBB BuildProperties at the beginning of the build process

# The dbb.gateway.type property determines which gateway type is used for the entire build process
# Possible values are 'legacy' and 'interactive.  Default if not indicated is 'legacy'
dbb.gateway.type=interactive

The gateway type can also be set on the command itself:

def memberCopy = new TSOExec().command("OCOPY INDD(IN) OUTDD(OUT) TEXT CONVERT(YES) TO1047")
memberCopy.gatewayType("interactive")

Using the Legacy Gateway

Support for the Legacy Gateway requires DBB to generate and execute a temporary REXX script.

IMPORTANT! Users must include a DD allocation for CMDSCP with a valid DSN to store the temporary script that will be executed.

This script is automatically generated by DBB.

// legacy gateway commands require DD allocation CMDSCP
def memberCopy = new TSOExec().command("OCOPY INDD(IN) OUTDD(OUT) TEXT CONVERT(YES) TO1047")
memberCopy.dd(new DDStatement().name("CMDSCP").dsn("USR1.ISPFGWY.EXEC").options("shr"))
memberCopy.dd(new DDStatement().name("IN").dsn("USR1.BUILD.LOAD(EPSCMORT)").options("shr"))
memberCopy.dd(new DDStatement().name("OUT").dsn("USR1.BUILD.LOADBK(EPSCMORT)").options("shr"))
memberCopy.logFile(new File("/u/usr1/build/logs/EPSCMORT_ispf.log"))
int rc = memberCopy.execute();

Using the Interactive Gateway

As the name implies, the Interactive Gateway allows for users to execute programs that are interactive, using a conversational pattern. In order to facilitate interacting with conversational programs, DBB has added the following additional APIs to the ISPFExec and TSOExec commands:

Example:

// HELLO Rexx script keeps prompting for a name
def hello = new TSOExec().command("exec 'USR1.REXX(HELLO)'")
hello.execute()
println hello.getOutput()

["John", "Paul", "George", "Ringo"].each { name ->
   if (hello.isWaitingForResponse()) {
       println name
       hello.response(name).execute()
       println hello.getOutput()
   }
}

// if the hello program is still waiting for a response then cancel the session to end
if (hello.isWaitingForResponse()) {
      println "Cancelling conversation"
      hello.cancel()
}

Output:

What is your name?
John
Hello John
What is your name?
Paul
Hello Paul
What is your name?
George
Hello George
What is your name?
Ringo
Hello Ringo
What is your name?
Cancelling conversation

The interactive gateway requires additional configuration values to be set:

# Procedure Name - specified with the procname parameter
dbb.gateway.procedureName=

# Account number - specified with the acctnum parameter
dbb.gateway.accountNumber=

# Group name - specified with a groupid parameter
dbb.gateway.groupId=

# Region size - specified with the regionsz parameter
dbb.gateway.regionSize=

# Gateway logging level.  Add values for multiple types:
# 1 - Log error information
# 2 - Log debug information
# 4 - Log communication information
# 8 - Log time information
# 16 - Log information to the system console
dbb.gateway.logLevel=2

Note: The interactive gateway support does not require the generation of a REXX program, so a CMDSCP DD statement is not required.

Using ISPF gateway in multi-threaded mode

By default, DBB runs its build process in a single thread and therefore builds programs one at a time. This is not an issue for incremental builds or even full builds for small applications. However, when an application is comprised of hundreds of programs, it causes a full build to take hours to perform. You can speed up the build process by setting up a multi-threaded build; therefore, that multiple programs can build at the same time thus speeding up a full build process significantly.

To use TSOExec or ISPFExec to call the ISPF gateway in multi-threaded mode, complete the following steps:

  1. Copy ISPZXENV to a new location (such as /etc/ispf)
  2. Update settings in ISPZXENV for CGI_ISPPREF
  3. Add the location to your runIspf.sh PATH
  4. If necessary, update DBB_CONF or confDir to point to the modified runIspf.sh directory

Additional details can be found in the following sections.

Configuring ISPZXENV

ISPZXENV is the ISPF gateway environment file that contains customizable settings for the gateway.

The file is provided as part of z/OS. By default, this file is installed into /usr/lpp/ispf/bin. This environment file is run by the ISPF gateway to get the value of certain environment variables necessary for the ISPF gateway to run. Default variables include the following:

The default directory that contains ISPZXENV (/usr/lpp/ispf/bin) is read-only after installation. To set variables to non-default values, copy ISPZXENV to a writable configuration directory, such as /etc/ispf, and then set the variables to your required values.

When running in multi-threaded mode, several temporary ISPF data sets need to be created. The unique identifier that the ISPF gateway generates is not unique enough when running in multi-threaded mode. However there are some changes that can be made to create a unique prefix that ISPF can use. In ISPZXENV, instead of setting the CGI_ISPPREF environment variable to &SYSPREF..ISPF.VCMISPF, it is possible to generate a prefix based on the PID (Process Identifier) of the ISPF gateway process that is running. To do this, add the following code to replace the setting of CGI_ISPPREF:

/* Running multithreaded we get issues with ISPF.VCMISPF not being unique */ 
/* Use a hex representation of the PID to uniquely create ISPF.VCMISPF */ 

address syscall 'getpid' ; pid = retval                                      

Say 'Pid:'pid                                                                

If Length(Pid) > 7 Then                                        
Do                                                                           
  CGI_ISPPREF = '&SYSPREF..P$'D2X(Substr(pid,1,5))||,
                         '.P$'D2X(Substr(pid,6))
End                                                                          
Else                                                                         
  CGI_ISPPREF = '&SYSPREF..P'D2X(pid)

Update PATH in runIspf.sh

If you copied the ISPZXENV file to another directory and edited it, you must then change the PATH environment variable in DBB’s runIspf.sh to point to the file. The PATH variable must point to the ISPF gateway home directory.

By default the variable is set to export PATH=$PATH:/usr/lpp/ispf/bin.

You might need to set it to the following:

export PATH=$PATH:/etc/ispf:/usr/lpp/ispf/bin

If necessary, you should also update DBB_CONF or confDir to point to the modified runIspf.sh file.

JCLExec

This command is used to submit a JCL job and retrieve the status and job outputs. The target JCL source could be located in a sequential data set, a PDS or PDSE member, a file on zFS, or dynamically created in the build script and passed in as a Java String.

Note: Similar to TSOExec and JCLExec, this command requires the location of the configure directory to locate the jobStatus REXX script. The command uses this REXX script to check the status of the submitted job.

Security implication: When using JCLExec to save job output to a file, sometimes the content of the file is empty. This could be caused by the fact that the current user does not have permission to look at jobs from another user, or the job name does not start with the user ID. To get more details of the issue, you could enable security tracing by specifying -Dcom.ibm.dbb.SecurityTrace=true.

Using SDSF function from a Java™ program is protected just as using SDSF interactively, or from a REXX exec, is protected, with the same SAF resources and ISFPARMS parameters.

Examples:

//  declare the confDir location
def confDir = "/usr/lpp/IBM/dbb/conf"

// Execute JCL from a data set member
new JCLExec().dataset("USR1.JCL").member("TEST").confDir(confDir).execute()

// Execute JCL from a file on HFS
new JCLExec().file(new File("/u/usr1/jcl/test.jcl")).confDir(confDir).execute()

// Execute JCL from a String value in the script
def jcl = '''\
//HELLO  JOB ,
//       MSGCLASS=H,MSGLEVEL=(1,1),TIME=(,4),REGION=0M
//*
//* PRINT \"HELLO WORLD\" ON JOB OUTPUT
//*
//STEP0001 EXEC PGM=IEBGENER
//SYSIN    DD DUMMY
//SYSPRINT DD SYSOUT=*
//SYSUT1   DD *
HELLO, WORLD
/*
//SYSUT2   DD SYSOUT=*
//
'''

new JCLExec().text(jcl).confDir(confDir).execute()

// Execute JCL and get output information
JCLExec jclExec = new JCLExec()
jclExec.dataset('USR1.JCL').member('TEST').execute()
def maxRC = jclExec.getMaxRC()
def jobID = jclExec.getSubmittedJobId()
def jobName = jclExec.getSubmittedJobName()

// Execute JCL and save the SYSPRINT output to zFS file
JCLExec jclExec = new JCLExec()
jclExec.dataset('USR1.JCL').member('TEST').execute()
jclExec.saveOutput('SYSPRINT',  new File("/u/usr1/output/sysprint.out"), "UTF-8")