Build your own API using the List Jobs System API.
I was recently asked by Mark H. if there's an easy way to determine whether a given job, using only a simple job name such as DAYEND rather than a qualified job name such as 123456/ABC/DAYEND, was currently active on the system. From my point of view, it's easy enough to call some system APIs to determine this, but he would prefer for application developers to just make a simple call to a function that, at a minimum, returns a yes or no type of response with no additional fuss. So today we'll be looking at how to package our use of system APIs within a *SRVPGM exported function that meets this requirement. In other words, we'll build our own API, over the system APIs, that will return an indicator value of *on if the specified job is active, otherwise *off to indicate the job is not currently active.
To accomplish this task, we'll define the function JobActive. JobActive requires one input parameter—a Char(10) job name—and returns an indicator. This function will enable the application developer to condition the running of the application, based on whether or not DAYEND is running, with code such as this:
/free
if JobActive('DAYEND');
// Do what is appropriate
// (ENDJOB, retry in X minutes, etc)
else;
// Do what is appropriate
// (SBMJOB, continue running, etc)
endif;
/end-free
To simplify life for our application developers, we'll also provide a source member in QRPGLESRC named JobChksPR. This member will provide the prototype for JobActive and can be copied into the application source using a directive such as /copy qrpglesrc,jobchkspr. This is the source for JobChksPR:
/if not defined(JobChks_Prototypes)
d JobActive pr n
d JobName 10a const
/define JobChks_Prototypes
/endif
To implement JobActive, we will use the List Job (QUSLJOB) API. As we will in the future be adding additional exports and capabilities we will use the rather generic name of JOBCHKS (Job Checks) for our *SRVPGM and module source. The initial source for JOBCHKS is this:
h nomain
d GetJobList pr
d JobName 10a const
d Status 10a const
/copy qrpglesrc,JobChksPR
*************************************************************
d CrtUsrSpc pr extpgm('QUSCRTUS')
d QualUsrSpcN 20a const
d XAttr 10a const
d IntSize 10i 0 const
d IntValue 1a const
d PubAut 10a const
d TxtDesc 50a const
d ReplaceOpt 10a const options(*nopass)
d ErrCde likeds(QUSEC) options(*nopass)
d Domain 10a const options(*nopass)
d TfrSize 10i 0 const options(*nopass)
d OptSpcAlgn 1a const options(*nopass)
d LstJobs pr extpgm('QUSLJOB')
d QualUsrSpcN 20a const
d Format 8a const
d QualJobName 26a const
d Status 10a const
d ErrCde likeds(QUSEC) options(*nopass)
d JobType 1a const options(*nopass)
d NbrFldsRtn 10i 0 const options(*nopass)
d FldKeys 10i 0 const options(*nopass)
d CntHandle 48a const options(*nopass)
d RtvUsrSpcPtr pr extpgm('QUSPTRUS')
d QualUsrSpcN 20a const
d UsrSpcPtr *
d ErrCde likeds(QUSEC) options(*nopass)
***********************************************************
d JobHdrPtr s *
d LJobHdr ds likeds(QUSH0100)
d based(JobHdrPtr)
d ErrCde ds qualified
d Hdr likeds(QUSEC)
d MsgDta 256a
/copy qsysinc/qrpglesrc,qusec
/copy qsysinc/qrpglesrc,qusgen
***********************************************************
p JobActive b export
d JobActive pi n
d JobName 10a const
/free
QUSBPrv = 0;
ErrCde.Hdr.QUSBPrv = %size(ErrCde);
if %subst(JobName :1 :1) = '*';
return *on;
endif;
GetJobList(JobName :'*ACTIVE');
return (LJobHdr.QUSNbrLE <> 0);
/end-free
p JobActive e
***********************************************************
p GetJobList b
d GetJobList pi
d JobName 10a const
d Status 10a const
/free
// Get addressability to Job List *USRSPC
RtvUsrSpcPtr('JOBLIST QTEMP' :JobHdrPtr :ErrCde);
if ErrCde.Hdr.QUSBAvl = 0;
// All is OK
else;
// Assume UsrSpc not found, so create it
CrtUsrSpc('JOBLIST QTEMP' :'JobList' :4096
:x'00' :'*ALL' :'Used by JobChks'
:'*YES' :QUSEC :'*DEFAULT' :0 :'1');
RtvUsrSpcPtr('JOBLIST QTEMP' :JobHdrPtr :QUSEC);
endif;
LstJobs('JOBLIST QTEMP' :'JOBL0100'
:(JobName + '*ALL ' + '*ALL ')
:Status :QUSEC);
/end-free
p GetJobList e
Before reviewing the JobActive procedure, let's look at the GetJobList procedure, where the real work within JOBCHKS is done and which is called by JobActive. The GetJobList procedure defines two input parameters. The first is the job name we're interested in; the second is the status of the job that we want to work with. The value of status can be *ACTIVE, *JOBQ, *OUTQ, or *ALL and is set by the caller of GetJobList (JobActive in our case). GetJobList utilizes the List Job API which, like other list APIs, uses a user space to return the result list of the API call. So the first piece of work in GetJobList is to attempt to retrieve a pointer to the user space QTEMP/JOBLIST, using the Retrieve Pointer to User Space (QUSPTRUS) API, and assign the pointer value to the variable JobHdrPtr.
If an error is encountered accessing the pointer value (that is ErrCde.Hdr.QUSBAvl, Bytes available of the API error code structure, is not 0), GetJobList attempts to create the QTEMP/JOBLIST user space using the Create User Space (QUSCRTUS) API and then again attempts to access the pointer using the QUSPTRUS API. Note that on the initial call to QUSPTRUS, the error code parameter used is ErrCde, while the call to QUSCRTUS, and the second call to QUSPTRUS, uses QUSEC as the error code parameter. When using ErrCde as the error code parameter, we are telling the called API that any errors encountered (like the user space not being found) will be handled by the caller (us). When using QUSEC as the error code parameter, we are telling the called API that any errors encountered (like being unable to create the user space) should be returned as escape messages—essentially ending our program with a highly visible error message to the user. So on the initial call to GetJobList, within the current job, the user space will not exist and GetJobList will create it. If for some reason the user space cannot be created, the system will send an appropriate CPF escape message.
If no error is encountered when initially accessing the pointer (ErrCde.Hdr.QUSBAvl is 0)—implying that this is not the initial call to GetJobList within the job—GetJobList will just re-use the existing user space.
At this point, the user space QTEMP/JOBLIST exists and the variable JobHdrPtr is set to the initial byte of the user space. GetJobList now calls the List Job API. While the QUSLJOB API supports up to nine parameters, GetJobList needs only the first five. The first parameter identifies the user space to return the results of the API call to and is set to the user space JOBLIST in QTEMP. The second parameter identifies the type of job information to be returned in the user space and is set to the format name JOBL0100. JOBL0100 returns the least amount of information about the jobs found and since, as you'll see shortly, we don't really care about the job information at all, is used to minimize the work being done by the API. The third parameter identifies the job name we're interested in and is set to the JobName passed to GetJobList concatenated with a job user value of *ALL and a job number of *ALL. This indicates we want all jobs with the specified job name, regardless of the user the job is running under and the job number. The fourth parameter is the status of the jobs that we're interested in. This value comes from the caller of GetJobList and, in the case of JobActive, will be set to '*ACTIVE', indicating we only want active jobs, not jobs on a job queue waiting to run or jobs that have completed and have spooled output. The fifth parameter is the error code parameter and is set to QUSEC, indicating that any errors encountered by the API should be returned as escape messages.
If no error in running the QUSLJOB API is encountered, GetJobList then returns to its caller—in the current case, JobActive.
Now let's take a look at the JobActive procedure. After setting the two instances of the API error code structure QUSEC and ErrCde to appropriate values (send escapes and not send escapes, respectively), JobActive checks to see if a special value (a job name starting with an asterisk) was specified by the application developer. Reviewing the QUSLJOB API documentation you will find that the job name parameter of the API supports three special values. These values ('*', '*CURRENT', and '*ALL') make no sense within the context of JobActive. The special values '*' and '*CURRENT' essentially indicate to test the current job, which will always be active, while the special value '*ALL' is to test for any job being active, which will always be the case. So rather than wasting our time getting the list, we'll just return *on (yes, you're active) and be done with it.
Note that this handling of special values is not quite "right." As written, JobActive is assuming that the special value being passed in by the application developer is one of the special values defined by the QUSLJOB API. That is, if the application developer codes JobActive('*INVALID'), they will also get *on returned by the procedure. But as I have no desire today to get into message handling (though you'll find plenty of previous articles that I've written on how to send an error message) and this article is on QUSLJOB rather than how to properly validate input parameter values, I'm leaving it in the current "not quite right" condition.
If the job name is not a special value, JobActive then calls GetJobList to request a list of all *ACTIVE jobs with the specified job name. As mentioned previously, we really don't care about the jobs—just whether or not there are any. So upon return from GetJobList, JobActive simply returns an indicator based on the number of entries returned (LJobHdr.QUSNbrLE) not being equal to 0. If not 0, then *on is returned, otherwise *off.
Assuming that the above source is stored in member JOBCHKS of source file QRPGLESRC, you can create the JOBCHKS module using this command:
CRTRPGMOD MODULE(JOBCHKS)
As my intention is to add more exports to JOBCHKS as time goes on, and I don't care for the hassle of working with system-generated signatures as these enhancements are made, let's explicitly define the exports and signature to be associated with the *SRVPGM. The following export specifications should be stored in member JOBCHKS for source file QSRVSRC.
StrPgmExp PgmLvl(*Current) Signature('JOBCHKS')
Export Symbol("JOBACTIVE")
EndPgmExp
This source indicates that we will be exporting the function JOBACTIVE and that the signature for the *SRVPGM is to be hard-coded as JOBCHKS. We can now create the JOBCHKS *SRVPGM with the following command.
CRTSRVPGM SRVPGM(JOBCHKS)
Having created the *SRVPGM, now let's create a *BNDDIR, named SOMETOOLS, that application developers can reference when using functions exported by JOBCHKS. To do this, run the following commands.
CRTBNDDIR BNDDIR(SOMETOOLS)
ADDBNDDIRE BNDDIR(SOMETOOLS) OBJ((JOBCHKS))
We're now ready to start using the JobActive function within our application code. The following program, named UseJobChks, demonstrates what is needed.
h dftactgrp(*no) bnddir('SOMETOOLS')
d UseJobChks pr
d JobName 10a const
d UseJobChks pi
d JobName 10a const
/copy qrpglesrc,JobChksPR
/free
if JobActive(JobName);
dsply 'It is active';
else;
dsply 'Not active right now';
endif;
*inlr = *on;
return;
/end-free
The sample program source and flow is as follows:
- An H-spec referencing the new *BNDDIR SOMETOOLS
- The definition of one input parameter—the job name to be tested
- A /copy of the prototypes for JOBCHKS
- Running of the JobActive function, passing it the job name to be tested
- DSPLYing a message based on the setting of the returned indicator
- Returning to the caller
To compile our sample program, you can use this command:
CRTBNDRPG PGM(USEJOBCHKS)
To test the UseJobChks program with a job name of NotHere, you can do this:
CALL PGM(USEJOBCHKS) PARM(NOTHERE)
Assuming there are no jobs on the system named NotHere, this will result in the following message:
DSPLY Not active right now
Testing using the program with a job name of SCPF using
CALL PGM(USEJOBCHKS) PARM(SCPF)
is likely to return the message
DSPLY It is active
And testing with CALL PGM(USEJOBCHKS) PARM(DAYEND) will return the message appropriate for whether or not DAYEND is currently running on the system.
Taking it a step further, let's say you have a set of related jobs (named DAYEND1, DAYEND2, DAYENDY, and DAYENDZ) and you want to know if one or more of these jobs is currently in an active status. What would be needed in order to check all four of these jobs? Assuming that the only job names that start with DAYEND are those that you're interested in, then you could simply use the following call:
CALL PGM(USEJOBCHKS) PARM(DAYEND*)
The QUSLJOB API supports generic job names, so to check if any jobs starting with the name DAYEND' are active, you just append the trailing asterisk to the job name and let UseJobChks, JobActive, and GetJobList pass that name, as is, to the QUSLJOB API.
Hopefully, you will agree that, from an application developer point of view, determining if a job is active or not is rather straightforward. And even building the *SRVPGM wasn't all that difficult.
Next month, we'll be providing additional exports and capabilities in JOBCHKS, so keep your source around for awhile.
As usual, if you have any API questions, send them to me at
LATEST COMMENTS
MC Press Online