Creating
a Web Service that Provides Data
[Process]
– Preparing the Program for Testing
Pseudo
Locking in Web Services
Record
Lists (e.g. for Combos)
Low-level
Debugging: Using CEDX with Jazz-generated Web Service Providers
A
Useful Routine for CEDF/CEDX Testing
Prerequisite reading: JazzUGSOA2.htm
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
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.
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: -
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,
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.
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.
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
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
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: -
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,
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.
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.
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;
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.
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.
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.
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.
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.
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 “
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.
Situations such as handling parent/child relationships are more complex. They are covered in the next chapter.
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
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
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.
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.
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