GitHubContribute in GitHub: Edit online

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 data sets or invoking z/OS programs and commands. DBB provides the following set of Java classes to be used for issuing z/OS commands:

  • CreatePDS
  • CopyToPDS
  • CopyToHFS
  • MVSExec
  • ISPFExec
  • TSOExec
  • JCLExec

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 partitioned data set (PDS). The source of the command can be one of the following types:

  • A single zFS file
  • A zFS directory
  • A list of physical dependencies generated by the DBB DependencyResolver

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 if the member name of a copied file is needed 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(DBBConstants.CopyMode copyMode) and copyMode(DBBConstants.CopyMode copyMode) in Class CopyToPDS in the DBB Javadoc.

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 code pages

By default, the copied PDS member has the same code page 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 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 code page is used.

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

// 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 code page 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 characters.
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(DBBConstants.CopyMode copyMode) and copyMode(DBBConstants.CopyMode copyMode) in Class CopyToHFS in the DBB Javadoc.

Handling code pages

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

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

Additionally, both the source data set and the target zFS code page 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 so that they can be correctly displayed in Jenkins 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 as compilers, link editors, pre-compilers. Its characteristics are similar to those of a JCL EXEC statement.
Note: The MVSExec function is a Java based API that cannot run APF (Authorized Program Facility) authorized programs. Use JCLExec instead.

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 that is 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 run.

// 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()

A DD statement is constituted of the following four main parts:

  • DD Name (name) - The data definition name. Can be omitted (see DD Concatenation below).
  • Data Set Name (dsn) - The data set to be allocated for the program to read or write. Can be omitted (see Temporary Data Sets below).
  • Path (path) - The path name of a file to allocate. Mutually exclusive to DSN. Can be omitted.
  • BPXWDYN Options (options) - The DDStatement uses the BPXWDYN text interface to dynamically allocate data sets during the build. The options attribute must consist of valid BPXWDYN dynamic allocation options.

DD Concatenation

Many z/OS compilers, linkers require one or more data sets to be concatenated together to form a library to be searched for referenced modules that are 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

A DD concatenation can be defined in two ways in DBB:

  • Create a parent DDStatement and manually concatenate additional DDStatements to it.

    def linker = new MVSExec().pgm("IEWBLINK")
    def syslib = new DDStatement().name("SYSLIB").dsn("USR1.OBJ").options("shr")
    syslib.concatenate(new DDStatement().dsn("CEE.SCEELKED").options("shr"))
    syslib.concatenate(new DDStatement().dsn("DFH.CICS.SDFHLOAD").options("shr"))
    linker.dd(syslib)
    
  • Adding a DDStatement without a DD name to a MVSExec command will automatically concatenate it to the last DDStatement added with a DD name.

    def linker = new MVSExec().pgm("IEWBLINK")
    linker.dd(new DDStatement().name("SYSLIB").dsn("USR1.OBJ").options("shr"))
    linker.dd(new DDStatement().dsn("CEE.SCEELKED").options("shr"))
    linker.dd(new DDStatement().dsn("DFH.CICS.SDFHLOAD").options("shr"))
    

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 by using the ATTACHX assembler macros. The ATTACHX macro uses the TASKLIB as its search path 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. To do so, use 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 you use 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 compiler 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 a 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 the 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 the DD statements are allocated.
  2. The z/OS program is executed.
  3. All the CopyToHFS commands are executed.
  4. All the DD statements are freed except the 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 to function correctly. For more information, see “Environment variables“ in Installing and configuring the DBB 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")

Legacy 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();

Note: The ISPF.conf allocations are only made when the ISPFExec method is used, and not TSOExec. This also applies to the allocjob that can be run to provide additional user allocations. The provided allocjob will not be invoked if the method is TSOExec.

Using the Interactive Gateway

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

  • getOutput() - returns a String with the output from <tso/> or <ispf/> tags contained in the service response from the last execution of the command.
    • Note that getOutput() is applicable for both legacy and interactive gateway calls. For legacy calls, the last line containing the ISPF_RETURN_CODE = 0 is removed from the output.
    • Since the returned output text from the gateway is embedded in a formatted XML document, additional white space such as beginning and ending newline characters as well as indentation characters will be included in the output text.
  • isWaitingForResponse() - flag that indicates that the command is waiting for a response from the user
  • setResponse(String response) or response(String response) - the value for the RESPONSE message to send to the gateway
  • execute() method is used to execute both the initial command as well as the follow-up responses.
  • cancel() - used to cleanly terminate a command while it is still waiting for a response.

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 composed of hundreds of programs, it causes a full build to take hours to run. You can speed up the build process by setting up a multi-threaded build; therefore, 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 the 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:

  • STEPLIB

    By default, it is set to STEPLIB = 'ISP.SISPLPA:ISP.SISPLOAD'

    Note: You can add more system load libraries to this STEPLIB allocation. For example, DB2.SDSNLOAD, if you have programs or executable files that start Db2® functions such as BIND.

  • CGI_ISPCONF

    By default, it is set to CGI_ISPCONF = '/etc/ispf'

  • CGI_ISPWORK

    By default, it is set to CGI_ISPWORK = '/var/ispf'

  • CGI_ISPPREF

    By default, it is set to CGI_ISPPREF = '&SYSPREF..ISPF.VCMISPF'

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, some changes 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 so, 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 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 value:

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 can 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 might 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 can 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")