Web Services: Dealing with Multiple
Records
Parent/Child Record Sets - Enquiry
Joining records: the WHERE condition
Parent/Child Record Sets - Update
Prerequisite
reading: JazzUGSOA4.htm
Refer to the Users’ Guide chapter “Web Service Clients” for
detail about client-side programming.
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.
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.
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.
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.
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.
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.
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.
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).
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.
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.
*# 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.
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 Orders … UPDATE 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.