Web Services: Dealing with Multiple Records

Web Services: Dealing with Multiple Records. 1

Introduction. 1

Parent/Child Record Sets - Enquiry. 1

Generating the Program.. 1

The Container definition. 2

The Program.. 2

Joining records: the WHERE condition. 3

END PROCESS and Max. 4

Testing WSPG3 with SOAPUI 4

Return Codes. 5

Parent/Child Record Sets - Update. 6

The container 6

The program.. 8

 

Prerequisite reading: JazzUGSOA4.htm

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

Introduction

The previous Users Guide chapter dealt with accessing data, with program WSPG1 providing Enquiry, and WSPG2 providing Update (and Add and Delete) functions.  We were only dealing with a single file, although it would have been easy to enhance these programs with a reference look-up.   In this chapter we move into more complex situations, dealing with record sets such as Customers and their Orders, with operations including not only the Enquiry/Update/Add/Delete as before but also adding new orders, and processing existing ones.

Parent/Child Record Sets - Enquiry

How do we handle Parent/Child record relationships such as Customers/Orders?  We’ll illustrate this by creating a program WSPG3 that performs similar functions to classical CICS program CICS3 illustrated in this video.  In this example the parent file is CustF, and the child file is Orders, and they have a 1:M relationship like this. 

Generating the Program

1                     We start by creating a new program as we did for WSPG1 and WSPG2.  This time we enter the name of Table 2, forcing Max for the first file (Custf) to be set to 1 as if we’d clicked [ ] Update. 

The Container definition

2                     Click [Finish] and the Container definition is created: -

*# Last Updated by IBMUSER at 25/03/2016 4:30:18 p.m.

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

COPY custf;

COPY orders;

DEFINE MySvce-WSPG3 SERVICE DATA(

    INPUT VARCHAR(30) VALUE 'IWSPG3',

    OUTPUT VARCHAR(30) VALUE 'OWSPG3');

DEFINE IWSPG3 SERVICE DATA([Input message

    JZ-custf-Skip SMALLINT VALUE 0,

    custf GROUP,

        Account LIKE custf.Account, [KEY

        Name LIKE custf.Name, [DKEY

        END GROUP,

    JZ-orders-Skip SMALLINT VALUE 0,

    orders GROUP,

        OrdNbr LIKE orders.OrdNbr, [KEY

        OrdCustId LIKE orders.OrdCustId, [DKEY

        OrdPart LIKE orders.OrdPart, [DKEY

        END GROUP);

DEFINE OWSPG3 SERVICE DATA([Output message

    ERROR VARCHAR(80),

    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,

    JZ-orders-ReadTo SMALLINT VALUE 0,

    JZ-orders-NbrReturned SMALLINT VALUE 0,

    JZ-orders-ReturnCode LIKE Types.ReturnCode,

    orders (30) GROUP,

        OrdNbr LIKE orders.OrdNbr,

        ordDate LIKE orders.ordDate,

        OrdPart LIKE orders.OrdPart,

        OrdQty LIKE orders.OrdQty,

        OrdDiscount LIKE orders.OrdDiscount,

        OrdField1 LIKE orders.OrdField1,

        OrdField2 LIKE orders.OrdField2,

        OrdStatus LIKE orders.OrdStatus,

        END GROUP);

 

Note that there are control fields (On input Skip, on Output ReadTo, NbrReturned, and ReturnCode) for both files, giving the client program the information that it needs to control paging on either file.  Even though we may have set Max for Orders to a value that we never expect to be exceeded, Jazz generates programs allowing for that possibility.

The Program

3                     Click [Exit] and Jazz displays the program that it has created: -

 

Except for PROCESS/END PROCESS within the GET block this is similar to program WSPG1, discussed in the previous chapter.

Joining records: the WHERE condition

Jazz determines the WHERE condition joining the parent (Custf) and child(Orders) files by looking in the definition of the child file for a field that is a key of some kind (KEY, DKEY, or UKEY) that is defined with EXISTS or LIKE the primary key of the parent file (Custf).   In this example: -

The primary key of CustF is Account

DEFINE CustF VSAM DATA( 

    Account PIC '999999' HEADING 'Account Number' KEY,

       

The definition of Orders begins

COPY Custf;

COPY Parts ;

DEFINE Orders VSAM DATA(

    OrdNbr DECIMAL(7) DPIC '9999999' KEY HEADING 'Order Number',

    OrdCustId LIKE custf.account  DKEY 'ibmuser.vsam.Orders1',

   

The 2nd field, OrdCustID has property DKEY so

1.       it is a key field and

2.       it is defined LIKE the primary key of Custf. 

Jazz therefore creates the WHERE condition WHERE(Orders.OrdCustId = CustF.Account).

 

If Jazz can’t determine such a condition then it will create an invalid WHERE condition, leaving to you to change the WHERE condition to something correct.  For example, if OrdCustID had been defined: -

                OrdCustId PIC '999999'  DKEY 'ibmuser.vsam.Orders1'

then the program would have been generated like this: -

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

PROGRAM WSPG3 WEBSERVICE MySvce CONTAINER JZCWSPG3;

*  Parent/Child Table Enquiry

ACCEPT (Custf.Account = IWSPG3.Account OR Custf.Name = IWSPG3.Name) MESSAGE OWSPG3.ERROR;

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

    PROCESS orders WHERE([Relationship joining Orders to CustF can't be inferred] = CustF.Account);

        #029 E '=' is invalid here

        #199 S Reference to a field expected

        #425 S CustF.Account is not a valid comparison operator

        #208 S No valid WHERE expressions

    END PROCESS orders RESPOND OWSPG3;

END GET Custf RESPOND OWSPG3;

REPLY;

 

You can change the WHERE to include the valid relationship.  You may want to change the definition of the container as well: it will include OrdCustId in the 30 Orders records in spite of the choice of “Xcpt Join” because Jazz hadn’t inferred the join relationship when the container definition was created.

END PROCESS and Max

Because we set Max to 30 when the program was created PROCESS reads up to 30 records from Orders, putting these into OWSPG3.Orders(1), (2), (3)… until there are no more Orders records satisfying the WHERE criteria.  We’re not limited by the restrictions of 3270 screen formats, so Max should be the maximum number of Orders that could ever be related to one customer.  No matter what value we set there could be more, so the Skip, ReadTo, and NbrReturned fields will still allow us handle overflows but it will be simpler if we don’t have to.

 

Hint: when you are developing a client program to use a service provided by a program like WSPG3, initially generate your web service program with a much lower Max value, say 3, so that you can test that the client program correctly handles overflow situations.  Then re-generate the service program with a larger Max value.

Testing WSPG3 with SOAPUI

Two records in the test data are particularly interesting: Account = 24 is related to 5 Orders records, while Account = 21 is related to 32.  Like program WSPG1 we can request a record by name and then use Skip to move forward or backward to the record we want: we’ll get the same results as here, where we request records by Account.  First, Account=24: -

 

The returned results include: -

 

Five rows of <Orders> are shown.  The client program will be able to tell that this is all there are because JZ-orders-ReturnCode is “D”, meaning “EndFile”.  See ReturnCodes below for more information.

 

With Account 21 there are 32 orders, but our message has only allowed for 30.  30 orders are returned, and the Orders control fields are returned like this: -

 

The return code is nothing, and ReadTo is greater than NbrReturned + Skip.  (Skip is in the input message, it currently has value nothing, = zero).

Return Codes

Jazz will include one or more return codes in an output message if the message contains a suitable field.  ReturnCode fields are: -

1.                   Named JZ[-Filename]-ReturnCode, i.e. a ReturnCode named JZ-Orders-ReturnCode contains return codes relating to the Orders file.   JZ-ReturnCode relates to unspecific messages, such as: an error has been found by ACCEPT validation.

2.                   ReturnCode values may be one of the following

a.       ‘D’  A browse operation – either GET with an ambiguous operation (example: Name) or PROCESS – has reached the end of the qualifying records – “Endfile”

b.      ‘E’  A CHECKSUM or CHECKSAVE error has been detected on I/O

c.       ‘N’  GET or PROCESS has found no record(s) – Not Found.

d.      ‘V’  ACCEPT validation has found one or more errors.         

Parent/Child Record Sets - Update

It is even less likely that we’ll simply be able to generate a program to handle orders without having to modify what Jazz provides, but at least it’s a start.  We’ll generate program WSPG4 just like WSPG3 except that this time we check [  ] Update. 

 

The container

*# Last Updated by IBMUSER at 9/04/2016 3:53:12 p.m.

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

COPY custf;

COPY orders;

DEFINE MySvce-WSPG4 SERVICE DATA(

    INPUT VARCHAR(30) VALUE 'IWSPG4',

    OUTPUT VARCHAR(30) VALUE 'OWSPG4');

DEFINE IWSPG4 SERVICE DATA([Input message

    Function CHAR(1) CODES(E:Enquiry,U:Update,A:Add,D:Delete,C:Child,S:Select) 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,

    JZ-orders-Skip SMALLINT VALUE 0,

    orders GROUP,

        OrdNbr LIKE orders.OrdNbr,

        ordDate LIKE orders.ordDate,

        OrdPart LIKE orders.OrdPart,

        OrdQty LIKE orders.OrdQty,

        OrdDiscount LIKE orders.OrdDiscount,

        OrdField1 LIKE orders.OrdField1,

        OrdField2 LIKE orders.OrdField2,

        OrdStatus LIKE orders.OrdStatus,

        END GROUP,

    ViewState GROUP, [Must not be changed

        CheckSum-custf CHAR(40),

        CheckSum-orders CHAR(40),

        END GROUP);

DEFINE OWSPG4 SERVICE DATA([Output message

    ERROR VARCHAR(80),

    ViewState LIKE IWSPG4.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,

    JZ-orders-ReadTo SMALLINT VALUE 0,

    JZ-orders-NbrReturned SMALLINT VALUE 0,

    JZ-orders-ReturnCode LIKE Types.ReturnCode,

    orders (30) GROUP,

        Checksum CHAR(40),

        OrdNbr LIKE orders.OrdNbr,

        ordDate LIKE orders.ordDate,

        OrdPart LIKE orders.OrdPart,

        OrdQty LIKE orders.OrdQty,

        OrdDiscount LIKE orders.OrdDiscount,

        OrdField1 LIKE orders.OrdField1,

        OrdField2 LIKE orders.OrdField2,

        OrdStatus LIKE orders.OrdStatus,

        END GROUP);

 

This is pretty much what you’d expect from combining programs WSPG2 and WSPG3.   The input message contains a function code, this time augmented by two extra functions, C:Child to create a new child record, and S:Select to select an existing one.  These will form the basis for the logic to create a new Orders record, or to process an existing order.

 

The input message also contains record definitions for one CustF and one Orders record, allowing the client program to update either of these records. 

 

As with program WSPG2 there is a ViewState field, this time with two CheckSum fields in it, one for each file.

 

The Output Message is the same as the output message for the enquiry program WSPG3 except for the ViewState field, and for an extra CheckSum field in each of the Orders records.  When there are several records which might be updated the use of CheckSum fields is naturally a little more complicated than it was with program WSPG2: this is explained in the next section.

The program

Like the container, this is pretty much as you’d expect from programs WSPG2 and WSPG3. 

 

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

PROGRAM WSPG4 WEBSERVICE MySvce CONTAINER JZCWSPG4;

*  Parent/Child Update.

*  You will need to develop the generated logic further

ACCEPT (IWSPG4.Function) MESSAGE OWSPG4.ERROR;

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

CASE (IWSPG4.Function);

    WHEN (Enquiry);

        ACCEPT (custf.Account = IWSPG4.Account OR custf.Name = IWSPG4.Name) MESSAGE OWSPG4.ERROR;

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

            PROCESS orders WHERE(Orders.OrdCustId = CustF.Account) SAVESUM OWSPG4.CheckSum-orders;

            END PROCESS orders RESPOND OWSPG4;

        END GET custf RESPOND OWSPG4;

    WHEN (Update);

        ACCEPT (CustF.Account=IWSPG4.Account) MESSAGE OWSPG4.ERROR;

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

            ACCEPT (IWSPG4.custf.*) EXCEPT(IWSPG4.Account) TO custf MESSAGE OWSPG4.ERROR;

            #447 I Numeric Data will already be converted to numbers

        END GET custf UPDATE RESPOND OWSPG4;

    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 (IWSPG4.custf.*) EXCEPT(IWSPG4.Account) TO custf MESSAGE OWSPG4.ERROR;

            #447 I Numeric Data will already be converted to numbers

        END GET custf CREATE RESPOND OWSPG4;

    WHEN (Delete);

        ACCEPT (CustF.Account=IWSPG4.Account) MESSAGE OWSPG4.ERROR;

        DELETE custf KEY(CustF.Account) CHECKSUM IWSPG4.CheckSum-custf NOCHILDREN(Orders);

    WHEN (Child);

*   Create a new orders record

        Orders.OrdNbr = orders.$LastKey + 1; [Will need to be changed if key is not a number

        #361 E Assignment to a key field

        GET orders KEY(Orders.OrdNbr) CREATE;

            Orders.OrdCustId = CustF.Account;

            ACCEPT (IWSPG4.orders.*) EXCEPT(IWSPG4.OrdNbr) TO orders MESSAGE OWSPG4.ERROR;

            #447 I Numeric Data will already be converted to numbers

        END GET orders CREATE RESPOND OWSPG4;

    WHEN (Select);

*   Process a selected child record. 

*   Generated logic is only a basic update: change this for your situation, e.g. "Process Order".

        ACCEPT (Orders.OrdNbr=IWSPG4.OrdNbr) MESSAGE OWSPG4.ERROR;

        #447 I IWSPG4.OrdNbr is already a number

        GET orders  KEY(Orders.OrdNbr) UPDATE CHECKSUM IWSPG4.CheckSum-orders;

            ACCEPT (IWSPG4.orders.*) EXCEPT(IWSPG4.OrdNbr) TO  orders MESSAGE OWSPG4.ERROR;

            #447 I Numeric Data will already be converted to numbers

        END GET orders UPDATE RESPOND OWSPG4;

END CASE;

REPLY;

 

Let’s review each case, starting with the case for Enquiry.  This is essentially the same as in program WSPG3, except for the addition of SAVESUM options on the GET and PROCESS statements.  As we know from program WSPG2 of the previous chapter SAVESUM calculates and saves a checksum.

 

In the case of the PROCESS loop,

PROCESS orders … SAVESUM Owspg4.CheckSum-orders;

saves the checksum of the last record read in Owspg4.CheckSum-orders, but also as each of the 1-30 records that we’ve allowed for in the output message is saved the current checksum is saved in the current Orders group: -

    orders (30) GROUP,

        Checksum CHAR(40),    ç  Checksum saved here

        OrdNbr LIKE orders.OrdNbr,

        ordDate LIKE orders.ordDate,

        OrdPart LIKE orders.OrdPart,

        OrdQty LIKE orders.OrdQty,

        OrdDiscount LIKE orders.OrdDiscount,

        OrdField1 LIKE orders.OrdField1,

        OrdField2 LIKE orders.OrdField2,

        OrdStatus LIKE orders.OrdStatus,

        END GROUP);

 

Logic for Update, Add, and Delete is the same as the logic in program WSPG2, discussed in the previous chapter. 

 

The program then continues with cases to handle Child (adding a new order) and Select (processing an existing order).   Jazz cannot know what logic you need for these processes, so this is outline logic only which you are expected to modify.  You may need to change the container as well as the program: just right-click WEBSERVICE in the PROGRAM statement to open a second workbench session to edit the container definition.  You will probably start by changing the function codes from “C:Child,S;Select” to something like “R:’New Order’,P:’Process Order’” that is more relevant to your situation.

 

For Select processing Jazz creates logic that is basically a normal update so, like updating Custf, we get the Orders record key from the input message and then read the record we want, using CHECKSUM to ensure that it hasn’t changed in the meantime. This rudimentary Select logic is written expecting that the client program will behave as follows when one of the orders (for example, #3) is selected: -

1.                   The client program copies the selected record’s primary key (Orders.OrdNbr) from the output record to the input record: i.e. assigning IWSPG4.Orders.Ordnbr = OWSPG4.Orders.OrdNbr(3).

2.                   The client program assigns IWSPG4.CheckSum-Orders = OWSPG4.Orders.Checksum(3).

3.                   Other new values are moved to the fields in IWSPG4.Orders.

 

Here is the logic that Jazz has generated: -

    WHEN (Select);

*   Process a selected child record. 

*   Generated logic is only a basic update: change this for your situation, e.g. "Process Order".

        ACCEPT (Orders.OrdNbr=IWSPG4.OrdNbr) MESSAGE OWSPG4.ERROR;

        #447 I IWSPG4.OrdNbr is already a number

        GET orders  KEY(Orders.OrdNbr) UPDATE CHECKSUM IWSPG4.CheckSum-orders;

            ACCEPT (IWSPG4.orders.*) EXCEPT(IWSPG4.OrdNbr) TO  orders MESSAGE OWSPG4.ERROR;

            #447 I Numeric Data will already be converted to numbers

        END GET orders UPDATE RESPOND OWSPG4;

 

This logic will need more development, but at least it provides the basic structure that we need.  GET OrdersUPDATE CHECKSUM will ensure that we’re returning an update for one of the orders that was sent to us, that nobody else has changed it since it was sent, and because OrdCustid is not in the data list we can’t invalidly transfer this order to another customer.   Using ACCEPT (IWSPG4.orders.*) does basic validation: ranges will be checked, and so on. 

 

However more logic will undoubtedly be needed. What are the business rules about changing an order’s status?   What happens when an order is filled?  What if it is partly filled?  And so on.  Jazz can’t anticipate your business rules, but it has provided a basic framework so that you can concentrate on these rules without worrying about managing the common features of all CICS transactions.

 

The Child case that Jazz generates for you is: -

    WHEN (Child);

*   Create a new orders record

        Orders.OrdNbr = orders.$LastKey + 1; [Will need to be changed if key is not a number

        #361 E Assignment to a key field

        GET orders KEY(Orders.OrdNbr) CREATE;

            Orders.OrdCustId = CustF.Account;

            ACCEPT (IWSPG4.orders.*) EXCEPT(IWSPG4.OrdNbr) TO orders MESSAGE OWSPG4.ERROR;

            #447 I Numeric Data will already be converted to numbers

        END GET orders CREATE RESPOND OWSPG4;

 

This is essentially the same as Add, which created a new Custf record.  Logic starts by determining the next free key, then using GET CREATE; to lock this key and create an initialised record.  

 

Orders.OrdCustId = CustF.Account; ensures that the joining relationship is set: we don’t want the Orders record to be for another customer!  ACCEPT to put the values in IWSPG4.orders into Orders is as we’d expect.   We use EXCEPT(IWSPG4.OrdNbr) because the program has set this value and we don’t want any user-entered value.  We also don’t want to change the joining field, but we don’t need to include OrdCustId in the EXCEPT list because by generating the program with option “Xcpt Join” we omitted the join key from the input message IWSPG4.

 

In a real situation there will be more logic to be written in this case.  Some fields will be set by program logic, for example “OrdCreated”, and there is likely to be logic to check the customer’s status before you allow a new order to be created.