Providing Useful Web Services

Providing Useful Web Services. 1

Introduction. 1

Getting Record(s) 1

Creating a Web Service that Provides Data. 1

Generating a Web Service. 1

Communication – the Container 1

Variable-length Messages. 1

Program WSPG1. 1

[Process] – Preparing the Program for Testing. 1

Test Results. 1

Updating Data. 1

Communication – the Container 1

Pseudo Locking in Web Services. 1

Program WSPG2. 1

[Process] Program WSPG2. 1

Test WSPG2. 1

Record Lists (e.g. for Combos) 1

Notes. 1

Reference Look-up. 1

More Advanced Situations. 1

Low-level Debugging: Using CEDX with Jazz-generated Web Service Providers. 1

A Useful Routine for CEDF/CEDX Testing. 1

An Example. 1

 

Prerequisite reading: JazzUGSOA2.htm

Introduction

In earlier chapters we wrote a simple web service provider program GetTime that returned the current time from a zOS system in Dallas, and then we wrote a web service requester that accessed a test service, SOATest1, returning the current time in Auckland from the Jazz Software web site.  While you should start with similar programs to verify that your web service set-up is correct, there are much easier ways of finding out the time in Dallas or Auckland.  Now it’s time to get real, finding out how to do useful things like returning data from VSAM or DB2.   We also need to learn how to handle errors.

Getting Record(s)

We know from previous chapters on batch programming and classical CICS programming that we use the GET statement to look up a database record: -

            GET Custf KEY(CustF.Account);

The GET statement will retrieve a record from CustF with the key value given.  The GET statement may specify several alternative keys, and the logic will use the first non-default-value for lookup: -

          GET Custf KEY(CustF.Account OR Custf.Name);

If no record is found then an initialised record is created within the program.  The program logic may choose to treat this exactly like an actual record, or it may need to know whether this is a real record: if the logic needs to know then it can simply test $Found.  For example: -

IF Custf.$Found = 'N' THEN;

    Some.Field = 'No Record Found';

END IF;

 

Whether CustF is from VSAM file or DB2, the Jazz statement is the same. This is no different in a web service program.  For our next exercise, we’ll write a program that is given a record key, and return one or several records. 

Creating a Web Service that Provides Data

Generating a Web Service

As earlier, we create a new web service from the Jazz New/Logic/New Web Service dialog. 

 

We entered the program name, WSPG1, and checked Replace because an earlier version of this program exists.  The file name, Custf, and 3 is set as the maximum number of records to be returned at a time.  Since every field from the record is wanted the default of All Fields is left, and then [Finish] is clicked.  Leave Table 2 blank.  A selection dialog appears: -

We click FR to select FR.* (meaning “all fields of FR”) and [Finish].   Next we see the container definition: -

Communication – the Container

This is the container definition that Jazz created for us: -

*# Last Updated by JAZZUSR at 14/01/2024 3:09:37 PM

*# You may edit this definition: right-click the 'WEBSERVICE' keyword of the PROGRAM statement.

COPY CustF;

#388 E FR.Region and CustF.Region have different types

COPY TYPES;

DEFINE MyWSv-WSPG1 SYSTEM DATA([Not in COBOL

    INPUT VARCHAR(6) VALUE 'IWSPG1',

    OUTPUT VARCHAR(6) VALUE 'OWSPG1',

    MType CHAR(4) VALUE 'WSDL',

    Template CHAR(8) VALUE '@WSF1Enq',

    URL VARCHAR(41) VALUE 'http://localhost:5482/cics/services/WSPG1');

DEFINE IWSPG1 SERVICE INPUT DATA(

    JZ-CustF-Skip SMALLINT VALUE 0 RANGE(0:999),

    CustF GROUP,

        Account CHAR(6) ASSIGN CustF.Account, [KEY

        Name CHAR(30) ASSIGN CustF.Name, [DKEY

        END GROUP);

DEFINE OWSPG1 SERVICE OUTPUT DATA(

    Error VARCHAR(80),

    JZ-CustF-ReadTo SMALLINT VALUE 0,

    JZ-CustF-NbrReturned SMALLINT VALUE 0,

    JZ-CustF-BrowseCount SMALLINT VALUE 0,

    CustF (3) GROUP,

        JZ-CustF-ReturnCode LIKE Types.ReturnCode,

        Account LIKE CustF.Account ASSIGN CustF.Account,

        Region LIKE CustF.Region ASSIGN CustF.Region,

        District LIKE CustF.District ASSIGN CustF.District,

        Name LIKE CustF.Name ASSIGN CustF.Name,

        SalesThisMonth LIKE CustF.SalesThisMonth ASSIGN CustF.SalesThisMonth,

        SalesYTD LIKE CustF.SalesYTD ASSIGN CustF.SalesYTD,

        Billingcycle SMALLINT ASSIGN CustF.Billingcycle,

        DateCommenced LIKE CustF.DateCommenced ASSIGN CustF.DateCommenced,

        END GROUP);

Note:

1.             The definition starts with COPY Custf; so that the program will be able to define fields using LIKE. This will save us the necessity of writing COPY Custf; in our program, although if we do it’s harmless.  

2.             Message #388 is harmless.  It is caused by Custf having this EXISTS relationship,
            Region DECIMAL(3) EXISTS FR.Region
but with FR.Region being defined PIC '999', not DECIMAL(3).   MANASYS has no problem generating COBOL to check EXISTS FR.Region that works correctly.

3.            The copy book will be named “MySvce-WSPG1”, using data from the New Web Service form.  The first definition defines a record with this name, satisfying the Jazz rule that a definition must include a DEFINE with the same name as the copy book. This definition provides some information that might be useful in our program. 

4.            Input and output records are named by prefixing the program name with “I” and “O” respectively.

5.            DEFINE IWSPG1 defines the input message, passed from the client to your program.  From the Custf definition Jazz knows that the keys are Custf.Account and Custf.Name, and it knows that Custf.Name is a generic key and so might correspond to several records.  Three fields are generated in the input message: -

a.    JZ-Custf-Skip       Skip will be needed for paging logic: however many records we’ve specified (in this case 3), there may be more. 

b.    Account                 This is the record’s 1ry Key

c.     Name                    Name was defined as a Duplicate Key.

6.            In the input record ASSIGN records the relationship between the message field and its source: -

        Account CHAR(6) ASSIGN CustF.Account, [KEY

        Name CHAR(30) ASSIGN CustF.Name, [DKEY

This not only defines that data in Account will be assigned to CustF.Account, it also specifies that the data will be validated according to the rules of CustF.Account.

7.            The output record, OWSPG1, contains

a.      A message field called Error, for ACCEPT and other program messages.

b.      A number of fields relating to the input file.  Like the input record’s Skip field, these are named “JZ-file-xxxx.   The fields are: -

·         ReadTo  (JZ-Custf-ReadTo).  This is not used when a file is accessed by its primary key (e.g. Custf.Account), and so if we look up Custf by Account this will have no value.  However if we use a generic read, such as access by Custf.Name then there may be more  records returned that we’ve allowed space for.  This is the case if we ask for records with Name = “APTHORPE”: we’ve allowed space for 3 records, but there are actually 5 such records.  The client program can use ReadTo and ReturnCode to set Skip values for paging.

·         NbrReturned.   For access by primary key this will have value 1  (a dummy, initialised record will be returned if there is no real record), but for generic reading this will have value 0 to the maximum (3 in this example).

·         Return code.  This will be returned with one of these character values: -
            ReturnCode CHAR(1) CODES(' ':'    ',W:Warning,E:Error,
                                                           
S:'Serious Error',T:'Terminal Error')

Normal responses will have ReturnCode values of blank.  It will have value “W”, meaning “Warning” if: -

·         ACCEPT validation has reported errors.

·         GET has found no records.

·         Generic reading has reached the end of the browse: there are no more records

E (Error) is reported when an update can’t proceed because the record has been changed by another program.

c.     Space for up to 3 records.

8.            In the output message the relationship between the message fields and the record data are again recorded with ASSIGN properties: -

Region LIKE CustF.Region ASSIGN CustF.Region,

Variable-length Messages

We have created a message structure with space for 3 records.  This is a fixed structure: if there is only one record (as for direct access) then there are empty record spaces for records 2 and 3.   With early versions of CICS we would have had to send an output message including empty space for the missing records, but now by specifying that we’re using CICS Mapping Level 4 or greater we can generate variable-length messages, returning only the number of records for which there is data: -

 

Click [Configure], and click the Lang(uage) tab: -

 

Change the CICS mapping level to 4 and Jazz will generate output records with

custf (3(JZ-Custf-NbrReturned)) GROUP,

This corresponds to a COBOL definition using DEPENDING ON, and will cause the WSDL to be created using both MaxOccurs and MinOccurs, so that only the number of records actually used are transmitted.

 

If you set the CICS mapping level to 4 but your CICS system is actually using a lower mapping level there will be errors when zOS runs the job submitted to compile the program and generate the binding objects.

Program WSPG1

Clicking [Exit] from the Jazz generates this program for us.  Several features of this will be familiar to those of you who have read the Users’ guide chapter or seen the videos about Classical CICS programming.

*# Last Updated by robertb at 26/11/2013 2:56:56 p.m.

PROGRAM WSPG1 WEBSERVICE MySvce CONTAINER DFHWS-DATA  WSDL;

*  Single Table Enquiry

COPY JZTrim;

ACCEPT (CustF.Account = IWSPG1.Account OR CustF.Name = IWSPG1.Name) MESSAGE OWSPG1.ERROR;

GET Custf KEY(CustF.Account OR CustF.Name);

END GET Custf RESPOND OWSPG1;

REPLY;

 

Compared to an equivalent classical CICS program to display a record: -

1.    The PROGRAM statement uses WEBSERVICE MySvce CONTAINER DFHWS-DATA WSDL instead of INSCREEN WSPG1S

2.    ACCEPT is different.  In a Classical CICS program the relationship between message fields and related fields in your program are defined into the screen format and so you’d have written
      ACCEPT (Account);
Here the relationship is be specified in the ACCEPT statement.  In this case input items have the format of assignment statements.
     ACCEPT (CustF.Account = IWSPG1.Account OR CustF.Name = IWSPG1.Name) MESSAGE OWSPG1.ERROR;

As with classical CICS, if MANASYS can find a field named ERROR in the output message for ACCEPT then this is assumed.

 

3.    The program exits with REPLY rather than SEND.

[Process] – Preparing the Program for Testing

In 2016 when I was using the IBM Dallas zOS Test Centre for testing, I ran the following tests.  

 

Clicking [Process] causes Jazz to submit a job to create the web service objects and the program.  In a few minutes this completed and the results returned: -

 

When this has run we

·         Define program WSPG1 into our CICS group with the CICS command CEDA DEFINE PROGRAM(WSPG1) GROUP(MANAJAZZ)
(if the program already exists, then use CEMT SET PROGRAM(WSPG1) NEWCOPY)

·         Perform a pipeline scan with CEMT PERFORM PIPELINE(MNJZPROV) SCAN

 

Test Results

Our program is now ready to test.  We’ll first use the general test utility SOAPUI (now called ReadyAPI) for this, discovering a service at (in this case) http://192.86.32.59:9014/MNJZPROV/WSPG1?wsdl to create a test project.  Supply the input and click the  icon and the results are returned: -

 

I also developed a page on the Jazz Software web site to invoke this service and display the returned results.   To see how to write client-side programs like this, see JazzUGClient.htm.  It doesn’t work now as it depended on my Dallas connection, but here are its test results

 

Case 1.                        Find a record by primary key

 

 

Case 2.  Record not found

 

 

1.        Get records by Name.  There are more than three

 

2.        [Get From] lets us move up and down the list of records

Updating Data

Creating a program that will update the data is also very simple.  Start with New/Logic/New Web Service as before, but check “Update”.  The program will now only allow you to return one record at a time, but following access by a generic key like “Name = “APTHORPE” we will still have paging options to scroll through the list of returned records: -

Communication – the Container

This is the container definition that Jazz created for us.   There are a few significant changes compared to the definitions created for program WSPG1 which read records but did not update them.

*# Last Updated by IBMUSER at 25/03/2016 3:55:29 p.m.

*# You may edit this definition: right-click the 'WEBSERVICE' keyword of the PROGRAM statement.

COPY custf;

DEFINE MySvce-WSPG2 SERVICE DATA(

    INPUT VARCHAR(30) VALUE 'IWSPG2',

    OUTPUT VARCHAR(30) VALUE 'OWSPG2');

DEFINE IWSPG2 SERVICE DATA([Input message

    Function CHAR(1) CODES(E:Enquiry,U:Update,A:Add,D:Delete) VALUE Enquiry,

    JZ-custf-Skip SMALLINT VALUE 0,

    custf GROUP,

        Account LIKE custf.Account,

        Region LIKE custf.Region,

        District LIKE custf.District,

        Name LIKE custf.Name,

        SalesThisMonth LIKE custf.SalesThisMonth,

        SalesYTD LIKE custf.SalesYTD,

        Billingcycle LIKE custf.Billingcycle,

        DateCommenced LIKE custf.DateCommenced,

        END GROUP,

    ViewState GROUP, [Must not be changed

        CheckSum-custf CHAR(40),

        END GROUP);

DEFINE OWSPG2 SERVICE DATA([Output message

    ERROR VARCHAR(80),

    ViewState LIKE IWSPG2.ViewState,

    JZ-custf-ReadTo SMALLINT VALUE 0,

    JZ-custf-NbrReturned SMALLINT VALUE 0,

    JZ-custf-ReturnCode LIKE Types.ReturnCode,

    custf (1) GROUP,

        Account LIKE custf.Account,

        Region LIKE custf.Region,

        District LIKE custf.District,

        Name LIKE custf.Name,

        SalesThisMonth LIKE custf.SalesThisMonth,

        SalesYTD LIKE custf.SalesYTD,

        Billingcycle LIKE custf.Billingcycle,

        DateCommenced LIKE custf.DateCommenced,

        END GROUP);

 

Firstly, the input record contains a Function code, as well the Skip field that was there for the enquiry program, WSPG1.  Also, the input message does not just contain the record keys, but contains all the fields of the CustF record that were selected.  In this case we chose the selection option “All fields”, so this is a complete list of the CustF fields.

 

Secondly, there is a group field called Viewstate containing a field “CheckSum”.   There is a similar ViewState field in the output record.  These are used to manage “pseudo-locking” with the same concept as in Classical CICS. 

Pseudo Locking in Web Services

In a large-scale CICS system we cannot afford to lock a record while it is displayed at a terminal waiting for an operator to make changes and respond: with many users on-line at once our CICS system could grind to a halt!  With classical CICS the standard technique to deal with this problem was

1              With the initial enquiry a copy of the record is saved in the Commarea, and the screen returned to the record.  The record is not locked.

2              When the Update response is received the program reads the record and locks it, and then compares the record just read with the saved copy in Commarea.  Almost certainly the two records are identical and the update proceeds, with the record being unlocked when the program terminates.  However in the unlikely (but just possible) event that somebody else has updated the record in the meantime, the program aborts the update with a message telling the user to re-apply their update.

 

With the increased complexity and delays of web service it is even more important that we manage possible update conflicts through an efficient mechanism.  We want to use the same concept, but web services are completely stateless and so we can’t save hidden data in a Commarea that we can pick up in the next part of the conversation.  Instead everything needed must be within the input message where it is potentially visible to the user.  We can’t use the CICS concept with a copy of the original record because the user could possibly change it, but we can use the concept with an Message Digest.  This is how it works: -

a.            When the initial enquiry reads the record a Message Digest, or CheckSum, is calculated.  This is a fixed-length encrypted value guaranteed to be different for different messages.  This CheckSum is returned with the other data in the output message.

b.            If the client program wants to proceed with the update then, as well as setting the function to “Update” and putting the new values in the input message, the CheckSum from the output message is copied to the input message. 

c.             When the program reads the record for update it re-calculates the checksum: if any other process has changed the record it will be different, and the update rejected.

 

The client program must not change the CheckSum value it receives: any change will cause the update to be rejected.

Program WSPG2

When we [Exit] from the container definition the program below is displayed.  It is similar in concept to the classical CICS program CICS2 (see this video or web page) apart from the web service changes discussed in the Notes below.

*# Last Updated by robertb at 26/11/2013 2:56:56 p.m.

PROGRAM WSPG2 WEBSERVICE MySvce CONTAINER JZCWSPG2;

*  Single Table Update

ACCEPT (IWSPG2.Function) MESSAGE OWSPG2.ERROR;

#052 W Item Function will be validated, but not moved from the input record

CASE (IWSPG2.Function);

    WHEN (Enquiry);

        ACCEPT (custf.Account = IWSPG2.Account OR custf.Name = IWSPG2.Name) MESSAGE OWSPG2.ERROR;

        GET custf KEY(CustF.Account OR CustF.Name) SAVESUM OWSPG2.CheckSum-custf;

        END GET custf RESPOND OWSPG2;

    WHEN (Update);

        ACCEPT (CustF.Account=IWSPG2.Account) MESSAGE OWSPG2.ERROR;

        GET custf KEY(CustF.Account) UPDATE CHECKSUM IWSPG2.CheckSum-custf;

            ACCEPT (IWSPG2.custf.*) EXCEPT(IWSPG2.Account) TO custf MESSAGE OWSPG2.ERROR;

            #447 I Numeric Data will already be converted to numbers

        END GET custf UPDATE RESPOND OWSPG2;

    WHEN (Add);

        CustF.Account = custf.$LastKey + 1; [Will need to be changed if key is not a number

        #361 E Assignment to a key field

        GET custf KEY(CustF.Account) CREATE;

            ACCEPT (IWSPG2.custf.*) EXCEPT(IWSPG2.Account) TO custf MESSAGE OWSPG2.ERROR;

            #447 I Numeric Data will already be converted to numbers

        END GET custf CREATE RESPOND OWSPG2;

    WHEN (Delete);

        ACCEPT (CustF.Account=IWSPG2.Account) MESSAGE OWSPG2.ERROR;

        DELETE custf KEY(CustF.Account) CHECKSUM IWSPG2.CheckSum-custf;

END CASE;

REPLY;

Notes

1.    The PROGRAM statement uses the options WEBSERVICE and CONTAINER.

2.    ACCEPT (IWSPG2.Function) MESSAGE OWSPG2.ERROR;  has no implied (or explicit) assignment, so it validates Function but doesn’t move it to another field.  This is OK: a warning message is produced, which we will ignore.

3.      In the Enquiry case ACCEPT (Account OR Name) is related to the following GET Custf KEY(Account OR Name) in the now-familiar way.  If an Account value is present then that will be used for lookup, otherwise the Name value will be used.  Since Name can be ambiguous the program allows for scrolling by including a ReadTo value (OWSPG2.JZ-Custf-ReadTo) in the output record, which can be used to set a Skip value (IWSPG2.JZ-Custf-Skip) in the next input message.

4.    The GET statement includes SAVESUM OWSPG2.CheckSum. This causes an encrypted message digest to be created and put into the field OWSPG2.CheckSum in the output message.

5.    The GET statement will also set the output field OWSPG2.JZ-Custf-ReturnCode.  A blank (or null) value is returned if everything is completely normal, such as when a direct access read finds a record, or generic access is successful and there are more records (Name=’APTHORPE’, skip less than 5).  A value of W (Warning) is set if a direct GET found no records (Account=25, which doesn’t exist in my test data), or (browsing) if there are no more records.  Thus if you have asked for records with Name=APTHORPE and Skip 5 or greater, or a name that doesn’t exist (XXXX) ReturnCode will be W.

6.    END GET Custf RESPOND OWSPG2; causes all fields of Custf that are also defined in OWSPG2 (in this case, all of them) to be assigned to the output message.  It will also assign the control fields OWSPG2.JZ-Custf-ReadTo and OWSPG2.JZ-Custf-NbrReturned.  Here JZ-Custf-ReadTo can only be 0 or 1 more than the input JZ-Custf-Skip value.

7.    Logic for Update must follow an enquiry : -
    WHEN (Update);

        ACCEPT (CustF.Account=IWSPG2.Account) MESSAGE OWSPG2.ERROR;

        GET custf KEY(CustF.Account) UPDATE CHECKSUM IWSPG2.CheckSum-custf;

            ACCEPT (IWSPG2.custf.*) EXCEPT(IWSPG2.Account) TO custf MESSAGE OWSPG2.ERROR;

        END GET custf UPDATE RESPOND OWSPG2;

 

The user has previously invoked WSPG2 with function Enquiry to find a record, which has been returned, along with its calculated Checksum.  The client will have read the CustF record in the output message, set new values in the corresponding fields in the input message, and copied the output Checksum into the input record.  The user now invokes program WSPG2 with function Update to update the record. 

 

Update logic starts by using ACCEPT to get the record key and the using GET to read CustF with this key. 

 

8.    GET Custf KEY(CustF.Account) UPDATE CHECKSUM IWSPG2.CheckSum; reads the record for update, i.e. it reads and locks it. The message digest is then recalculated and compared to the value (returned by the client) in IWSPG2.CheckSum. If it is different then the update is abandoned: the new CustF record and CheckSum is returned in the output message as if a new enquiry had been done.  Error contains a message, and ReturnCode will have been set to E(Error).

Any read-for-update must read the record by its primary key, so this would not have been valid with KEY(Custf.Name). 

9.      Unless the CHECKSUM test has failed execution continues with ACCEPT and END GET.

10.  ACCEPT (IWSPG2.custf.*) EXCEPT(IWSPG2.Account) TO custf MESSAGE OWSPG2.ERROR; shows several features of ACCEPT that you may not have seen before. 

Firstly, the data list, (IWSPG2.custf.*), is a generic reference.   This is a shorthand way of referring to all the fields defined in the group IWSPG2.custf, i.e.  IWSPG2.custf.Account, IWSPG2,custf.Region, and so on.   This implied list is modified by EXCEPT: this excludes the named item, so that the implied list excludes IWSPG2.Account.

 

The option TO custf specifies the destination for validated data. Combining the generic reference, EXCEPT, and TO, the statement ACCEPT (IWSPG2.custf.*) EXCEPT(IWSPG2.Account) TO custf is equivalent to a series of assignment expressions: -

ACCEPT (Custf.Region = IWSPG2.Region, Custf.District = IWSPG2.District, …)

Each of these assignment expressions means

If the input field is present then

     Validate it (check that it is valid for its definition)

     If it is valid then

            assign it to the target field

     else

            Put an error message in the MESSAGE field

 

If any errors are detected then the message field OWSPG2.ERROR will be set, OWSPG2.JZ-Custf-ReturnCode will be set to ‘W’ (Warning), and the program will exit.

11.  Note the information message: #447 I Numeric Data will already be converted to numbers. Unlike INSCREEN data (read from a 3270 terminal) where each input field is a group of three fields – a length field, an attribute byte, and the data itself as a character string – when data is received in a web service message it is presented to a program already converted to a numeric format. SMALLINT, INTEGER, and BIGINT will already be 2, 4, or 8 bytes of binary number, DECIMAL and MONEY data will be in the format that COBOL programmers know as COMP-3 and PL/I programmers know as DECIMAL FIXED, and so on.  This conversion has already occurred in the conversion of the SOAP message to a COBOL structure somewhere in the IBM logic where the SOAP message is converted to a COBOL structure using the WSDL and the Binding file. 

What if there is a problem in this conversion – for example input includes
<Region>ABC</Region> which can’t be converted to a number?  This problem will already have been reported as a SOAPFault in the process by which the input is processed into a message for our program, and our program won’t be invoked at all.  We have no opportunity to respond to this error in our program, it’s already handled!  Thus there must be nothing, or a valid number, for our program to have been invoked without a SOAPFault, and whatever we receive for Region will already be in the format required by Custf.Region, which is DECIMAL(3)  (COBOL:  PIC S9(3) COMP-3).

 

Because of this the ACCEPT logic in a Web Service is a little different to the logic in a classical CICS program.  When data is coming from a 3270 screen then ACCEPT logic is: -

      IF something has been entered (i.e. length field > 0) THEN

          Check that is can be converted to the target format (i.e., in this case converted to a number)

          Perform other validity checks (Range, code, etc)

      ENDIF

 

For a web service SOAP/CICS logic prior to our program has already tested “has something has been entered?”, and converted it to a number if true.  Thus our ACCEPT can’t detect that you’ve put “ABC” into the Region field because this error will never reach our program, having already been detected and reported as a SOAPFault.  Also there is no length field, so the logic can’t use “If length field > 0” to test if anything has been entered.  ACCEPT logic therefore skips the formatting checks in a web service program, and uses a test “Does the field have its default value”, which is usually zero or blank, for the test “Has something been entered”.

 

However ACCEPT DOES check that fields are within the correct range, that coded values are correct, that CheckRoutines pass the value, and so on.

12.  On reaching END GET Custf UPDATE RESPOND OWSPG2; CustF is updated, and the output message OWSPG2 returned.

13.  The logic to add a new record is: -
    WHEN (Add);

        CustF.Account = custf.$LastKey + 1; [Will need to be changed if key is not a number

        #361 E Assignment to a key field

        GET custf KEY(CustF.Account) CREATE;

            ACCEPT (IWSPG2.custf.*) EXCEPT(IWSPG2.Account) TO custf MESSAGE OWSPG2.ERROR;

            #447 I Numeric Data will already be converted to numbers

        END GET custf CREATE RESPOND OWSPG2;

 

14.  To add a new record the first thing that our program must do is to determine what record key to use.  In this case it’s easy to do this: we simply use the special value $LastKey and add one to it: -

         CustF.Account = custf.$LastKey + 1;

This works here because CustF.Account has definition PIC '999999' and so it’s a simple number.  This is not always so – the record key might be a structure, or a CHAR field, and so we might need to write our own logic to work out the next key.  When Jazz generates the program it doesn’t know whether a simple assignment such as the above will be valid, so we are warned that we might need to amend the logic with the comment  [Will need to be changed if key is not a number.  As always, the programs that Jazz generates from New/Logic may only be a starting point for our own logic.

 

15.  Now our logic uses

        GET custf KEY(CustF.Account) CREATE;

This is like GET custf KEY(CustF.Account) UPDATE;, reading the Custf record and locking it. Of course the Read will “Fail” as there is no such record, but this will lock the key preventing another user from coincidentally creating another record with the same key.  The GET therefore “Finds” an initialised record, with the key value that we are seeking and other fields set to initial values (usually blanks and zeros).  If coincidentally another user creates a new Custf record at the same time with the same key so that GET custf KEY(CustF.Account) CREATE doesn’t fail but returns an actual record then the error is detected and reported and our program will terminate.  We must resubmit our Add request.

 

16.  The rest of the logic is the same as for Update. 

17.  As in the Update case the logic of WHEN (Delete); starts by re-validating IWSPG2.Account. Like GET, the DELETE statement reads and locks the record and then calculates it checksum to ensure that it hasn’t changed since the initial Enquiry.  As with GET, if there is a change a message will be set and the user asked to re-confirm the Delete. 

[Process] Program WSPG2

Click [Process] and Jazz generates COBOL, then prepares and submits a job to compile this and create all the necessary web service objects.  In a minute or less this job is returned to Jazz, and you can check that it has run without correctly.  We then execute a couple of CICS CEMT commands to make the programs ready to test,

            CEMT SET PROGRAM(WSPG2) NEWCOPY

and

            CEMT PERFORM PIPELINE(MNJZPROV) SCAN

This process has not been illustrated as it is the same as above for program WSPG1.

Test WSPG2

We can now test our new program, either with the general test utility ReadyAPI  (previously SOAPUI) or a client-end test program.   JazzUGClient.htm tells you how to use ReadyAPI, and to write such test programs.  Here is WSPG2 being invoked by ReadyAPI to update a record, following an enquiry: -

 

A client test program can build in rules such as

·         Function codes are valid (Capital “E”, “U”, “A”, or “D”, not lower case, no other characters).

·         Update and Delete must follow an enquiry that found a record, Add must follow an enquiry that didn’t.

·         The CheckSum from the output of an enquiry is copied to the input of the following update

·         Data can be pre-validated so that we don’t attempt to pass non-numeric data to numeric fields.

·         The field ReturnCode indicates the success of the operation.  If it’s not absent or blank then there is at least a warning that should be handled.

While this validation will be repeated in the server-side web service provider, a better user experience results if as much such logic is possible is written into the client.

 

Here is our program repeating the test above with a test web page.  First we do an enquiry: -

Since a record was found, ReturnCode was returned as a blank, so our client-side program creates the message at the bottom and exposes [Update] and [Delete], but not [Add].   We change something – Name to “ARVO, Albert” – and click [Update]: -

 

We can combine ReadyAPI and this test page to demonstrate server-side integrity checking.  Click [Enquiry] and we’ll get the same display except that the message will change and [Update] and [Delete] controls appear: -

 

To simulate the effect of somebody else updating the record we’ll update it with ReadAPI.  We do an enquiry for Account=30: -

 

Now we

·         Copy CheckSum from the output record to the input record.

·         Set Function to “U”

·         Change the name to “ARVO, Fred”

·         Click Submit ()

ReadyAPI submits the request, everything is normal, and the output record is returned: -

 

Our test page has just done an enquiry for this record, and so is prepared to do an update.  In a real situation we wouldn’t know that the record has been updated elsewhere, so we’d proceed with the change that we want to make: -

·         Change one or more fields.  We’ll attempt to change Name to “ARVO, Ardi”

·         Click [Update]

The server-side logic of program WSPG2 detects that there has been a change since the program’s enquiry, and rejects the update: -

In my client logic I have chosen not to simply display the record as updated by the other process, and to require another [Enquiry].  Alternative logic could display this updated record and allow an immediate [Update].

 

For more information on client-side programming – ReadyAPI and writing client programs – refer to the Users’ Guide chapter “Web Service Clients”.   This chapter continues with more information on the development of Web Server Providers with Jazz for the CICS/COBOL environment.

Record Lists (e.g. for Combos)

We can create a list to (for example) populate a combo by: -

1.    Create an enquiry program like WSPG1, but selecting only the primary key and the other fields that we want to display.  The selection option “Keys” may be what we want – for CustF this means Account and Name.  Set a suitably large value for Max so that the client program won’t normally have to go back for more.

 

2.    Jazz creates a container definition like this: -

*# Last Updated by IBMUSER at 29/02/2016 1:25:58 p.m.

*# You may edit this definition: right-click the 'WEBSERVICE' keyword of the PROGRAM statement.

COPY Custf;

DEFINE MySvce-WSPG1L SERVICE DATA(

    INPUT VARCHAR(30) VALUE 'IWSPG1L',

    OUTPUT VARCHAR(30) VALUE 'OWSPG1L');

DEFINE IWSPG1L SERVICE DATA([Input message

    INPUT GROUP,

        JZ-Custf-Skip SMALLINT VALUE 0,

        Account LIKE Custf.Account, [KEY

        Name LIKE Custf.Name, [DKEY

        END GROUP);

DEFINE OWSPG1L SERVICE DATA([Output message

    ReturnCode LIKE Types.ReturnCode,

    ERROR VARCHAR(80),

    JZ-Custf-ReadTo SMALLINT VALUE 0,

    JZ-Custf-NbrReturned SMALLINT VALUE 0,

    Custf (100) GROUP,

        Account LIKE Custf.Account, [KEY

        Name LIKE Custf.Name, [DKEY

        END GROUP);

We will not want to access the file by its primary key, so we should remove Account from IWSPG1L

 

3.    Jazz creates a program like this.  Of course it is invalid because we removed one of the key fields: -

 

Remove Custf.Account = IWSPG1L.Account OR, and also remove Account from the GET statement, so that the program is now

*# Last Updated by robertb at 26/11/2013 2:56:56 p.m.

PROGRAM WSPG1L WEBSERVICE MySvce CONTAINER JZCWSPG1L;

*  Single Table Enquiry

ACCEPT (Custf.Name = IWSPG1L.Name) MESSAGE OWSPG1L.ERROR;

GET Custf KEY(CustF.Name);

END GET Custf RESPOND OWSPG1L;

REPLY;

 

4.    Now [Process] the program.  We now have a program called WSPG1L that, given a name, will return up to 100 pairs of (Account,Name).  It also includes the usual logic using Skip, ReadTo, and NbrReturned to allow the client program to get more if necessary.

 

We could now use this program in the clients for WSPG1 and WSPG2.  We could enhance these client programs so that, if we access CustF by Name, then as well as their present function a combo box is displayed.  This is populated by invoking WSPG1L.

Notes

1              You are not limited to the 32K maximum size of a Commarea, so you can define a container of any size.  However the more rows returned, and the more fields in each row, the larger the message and the slower the response.   If max is too small then the list won’t be very useful, but if there could be several thousand rows users will find the combo unmanageable, even if the system has no problem returning it. If the number of rows could be very large then you should consider using logic – perhaps another level of dialog – that refines the search.  Unless you have used [Configure] to set CICS Mapping Level = 4, indicating that you are using CICS 5.2 or later, then the whole message will be transmitted even if there is only one record.

2              No matter how large you set Max the client programs should be programmed to handle situations where there are more records.  With Skip, Readto, and NbrReturned they have the data they need to do this.  Suggestion: initially set a low Max, say 3, to develop the client and test its overflow logic.  Then reset Max to a higher value, say 100.

3              Refer to the Users’ Guide chapter “Web Service Clients” for detail about client-side programming.

Reference Look-up

Looking up related files to get information is easy.  For example, in the first demonstration video a batch program looked up file FR to convert the Region Number carried in CustF to a Region Name such as “France”, “Italy”, etc.  The logic of this batch program was: -

 

The definition of FR was

*# Last Updated by IBMUSER at 1/11/2014 3:08:23 p.m.

DEFINE FR VSAM DATA(

    Region PIC '999' KEY,

    Name CHAR(30) VALUE('No Record found') HEADING 'Region Name',

    Fill CHAR(47))

    DSNAME 'IBMUSER.VSAM.Region';

 

Doing something similar within a CICS program, whether a Web Service or a classical CICS program, is also easy. For example, to add this lookup to programs WSPG1 or WSPG2 all we have to do is: -

 

1              Make the file FR available to CICS, defining it within our CICS group with appropriate properties.

2              Add COPY FR; to the program

3              Write a GET statement, just like the one in the batch program, between GET and END GET.  In programs like WSPG2 with more than one GET statement, you will need to add this statement in each case.

 

With this easy modification, data from the reference file will be available from the GET to the END GET (including code in a ROUTINE that you PERFORM between GET and END GET).  If you want to return data from the lookup file to the client, such as FR.Name, then you can add a field to the output message and write assignments to set it within the GET block.  As usual the value of fields like FR.Name will be undefined in statements that are executed outside the GET block.

More Advanced Situations

Situations such as handling parent/child relationships are more complex.  They are covered in the next chapter.

Low-level Debugging: Using CEDX with Jazz-generated Web Service Providers

In developing Jazz programs you should normally be able to work entirely at the high level of Jazz statements, debugging problems at this level without descending to the low level of COBOL and CICS commands.  Occasionally however this may be necessary.

 

CICS provides a diagnostic tool, CEDF, that shows the input and output of every command.  When testing a COBOL classical CICS program from your 3270 emulator you’d execute the command

            CEDF  (response: THIS TERMINAL: EDF MODE ON)

then you’d enter the transaction code to start your program, e.g. TRN2 to start program CICS2.   Then, when the program initiates and terminates, and whenever execution reaches an EXEC CICS command, EDF displays diagnostic information.

 

Of course a web service doesn’t send data to/from a 3270 screen and so it doesn’t involve a 3270 emulator.  Instead, it is initiated from your client program and returns results to it.  Instead of using CEDF, we therefore use CEDX, like this: -

 

1              Open an emulator window, as you would for testing a classical CICS program, and enter the command
            CEDX CPIH,ON. 

            Response will be: - 

TRANSACTION CPIH: EDF ON AND ASSIGNED A TEMPORARY TRANCLASS OF DFHEDFTC

 

2              Now invoke your web service from your client – either a test utility like ReadyAPI or your own client logic – and as the service initiates, executes EXEC CICS commands, and terminates, it will display EDF diagnostics as it would for CEDF with a 3270 transaction.

A Useful Routine for CEDF/CEDX Testing

EDF diagnostics only display data on program initiation/termination and when a CICS command is executed.  You may wish to find out the value of COBOL data somewhere else in your program.  Jazz ships with a routine JZBR14 that we developed for this purpose (for our own use actually).  At any point in your CICS COBOL program you can write

            EXEC CICS LINK PROGRAM(‘JZBR14’) COMMAREA(DataName) END-EXEC

and CICS will branch to routine JZBR14, passing it the data named in the COMMAREA operand.  JZBR14 does nothing at all, returning immediately to the calling program, so this is simply a null statement as far as the COBOL logic is concerned.  However when debugging with CEDF or CEDX this creates a point at which EDF diagnostics are displayed, allowing you to see the contents of DataName.

An Example.

A case where CEDX debugging proved necessary was in the development of program WSPG2. As shown above, the update logic is: -

    WHEN (Update, Add);

        ACCEPT (CustF.Account=IWSPG2.Account) MESSAGE OWSPG2.ERROR;

        GET CustF KEY(CustF.Account) UPDATE CHECKSUM IWSPG2.CheckSum;

            ACCEPT (IWSPG2.Region,IWSPG2.District,IWSPG2.Name,IWSPG2.SalesThisMonth,

                          IWSPG2.SalesYTD, IWSPG2.Billingcycle,IWSPG2.DateCommenced)

               TO Custf MESSAGE OWSPG2.ERROR;

        END GET Custf UPDATE RESPOND OWSPG2;

 

The rules of ACCEPT (without an ACCEPTNULLS option) are that fields that are absent from the input message are ignored: the value that is in the target (CustF) field are left unchanged. Testing this with ReadyAPI everything worked correctly.  Then I started testing with the test web page, but when I ran the test shown above, instead of the results shown where the record is updated and the message “OK: Record Updated” is produced, the record was not updated and the message “Warning:Billingcycle Outside Code Range;” is displayed.

 

Why should the READYAPI test be different from my web page?   In the ReadyAPI test an absent value of Billingcycle had been put into the input message with

            <wsp:Billingcycle></wsp:Billingcycle>

 

In the ASP.NET web page code the logic checks to see if anything has changed by comparing its value with the value read from the previous enquiry.  If the value has changed then it is validated locally, so that invalid data is trapped locally.  The relevant section of logic for BillingCycle was: -

        Jz_Input.Billingcycle = Nothing

        If txtBillingCycle.Text <> hdnBillingCycle.Value Then

            If IsNumeric(txtBillingCycle.Text) AndAlso txtBillingCycle.Text >= 1

                     AndAlso txtBillingCycle.Text <= 12 Then

                Jz_Input.Billingcycle = txtBillingCycle.Text

                DataHasChanged = True

            Else

                Message &= "Billing Cycle value is not valid;"

            End If

        End If

 

Local (Visual Studio) debugging easily showed that Message was not being set and that a message was being sent to the web service with Jz_Input.Billingcycle set to Nothing.  I had believed that this would be the same as the value sent from the READYAPI test.

 

I turned CEDX on, changed the values of Name and DateCommenced in the web page (leaving Billing Cycle unchanged) and clicked [Update]: -

 

This should have transmitted a message with Nothing in the value of IWSPG2.BillingCycle.  With CEDX on there were a series of EDF displays: -

 

1              Program Initiation: can be ignored

2              About to execute command EXEC CICS GET CONTAINER ….    This can be ignored

3              Billingcycle is a TINYINT field just before DateCommenced, which I set to “DDDDDDDDDD” to give me an easy marker.  The fact that this is displayed as “.” rather than “ ” suggests that Nothing has been sent as LowValues (Hex ‘00’) rather than blank (Hex ‘40’)[1]. 

 

4              To confirm this I clicked PF2 to display the container in hex. 

 

5              Unfortunately IWSPG2.Billingcycle is now too far to the right to be displayed, but clicking PF5 and then pasting the address 1B00BFE0 and clicking Enter displays the complete container.  The hypothesis that Nothing has been sent as Hex ‘00’ is confirmed by the byte preceding the row of D’s, in position 1B00C004,  

6              Repeating the test with ReadyAPI, I found that

            <wsp:Billingcycle></wsp:Billingcycle>

results in a blank (X‘40’)

 

The results are now explained, as the COBOL code from ACCEPT tests whether any value has been entered with -

006080     IF Billingcycle OF IWSPG2 NOT = SPACES

 

A temporary fix was easy: the web page logic changed from
        Jz_Input.Billingcycle = Nothing

to

        Jz_Input.Billingcycle = ""

Of course this issue has been fixed in Jazz, so that now TINYINT fields are always transmitted in web service messages as integers, with values from 0 to 255.   With this fix the COBOL logic from ACCEPT changes to test

006080     IF Billingcycle OF IWSPG2 NOT = ZERO

as it does for every other kind of number in this situation.  So you won’t see this particular error, but the CEDF techniques demonstrated here might still prove useful.

 



[1] For readers unfamiliar with IBM mainframes:  the zOS system hosting the CICS system is using EBCDIC character encoding instead of ASCII (which has become Unicode) which is the standard in the non-IBM world.  In EBCDIC Blank is X‘40’ and D is X‘C4’.  In Ascii they are X‘20’ and X‘44’ repectively