See Road Map for proposed future development
Contents
Creating a Client Interface
for a MANASYS Jazz Web Service.
Revision:
Creating a JSON Web Service
Step
1. Create an Interface Project
Step
2 (Optional). Create Projects for Test Objects
Step
3. Configure Jazz – Client Tab
Step
4. Configure Jazz – Workbench Tab
Step
1. Locating JSON and Creating the
Message Descriptions.
Step2. Creating the Client Interface
Server-side
and Client-Side Validation
When
do you Need to Re-create a Client Interface?
To
Re-create a Client Interface
Getting
Started – Locate or Create a Suitable Project
Client
Projects with Multiple Forms
Introduction:
Test Data for JSPG3
Creating
and testing JSPG3Client
Messages
– COBOL, JSON, C#, and Binding.
Understanding
the Client Interface
In minutes MANASYS Jazz can generate a CICS Web Service that can perform enquiry and/or update from one or several VSAM files or DB2 database tables, generating hundreds of lines of COBOL logic and also the necessary supporting objects (WSDL or JSON, and the binding objects that convert between JSON or WSDL and COBOL). The COBOL logic will validate incoming data and implement the rules of the web service such as “Update must follow an enquiry”.
Both REST (JSON) and SOAP (WSDL) services can immediately be tested with utilities like PostMan and ReadyAPI, but even though a ReadyAPI test can be created in minutes it is far from easy to develop a client-side program to interact with a CICS Web Service, especially if you want client-side validation to avoid unnecessary web service requests. However MANASYS Jazz can now generate an interface object for JSON web services that makes client development very easy. Click here to see a short video introducing this process. The interface exposes properties and methods encapsulating the rules of the service: for example Employee.Sex must be M or F, Employee.Empno must be numeric, Update must follow an Enquiry and must return the CheckSum value from the Enquiry, and so on. Clients need only refer to the properties and methods provided by the interface object to obey the rules. Agile development is now easy: should the web service change, just re-generate the interface object to reflect new rules and data. The Client Program (which you write) will need changes only to reflect new functions and data provided by the new version of the service.
In the diagram above, Jazz will have generated the service MyJSv-JSPG2 as a COBOL program on the “mainframe”, and the interface JSPG2Client as a C# object in your MyJSv interfaces project. Why C#? By targeting C# and .NET Core the interface can be used not only in Windows, but also with Linux, Android, IOS, and perhaps other operating systems. You will write the actual client-side logic in the language of your choice – VB.NET, C#, Java, … for whatever client environment you are using (Windows form, Web page, mobile app…).
As later programs are generated their interfaces are added into the interface project for the service: -
This Users’ Guide chapter shows you how to create these client-side interfaces.
Note: this chapter uses various programs within service MyJSv for its examples. Programs have names starting with “JS”, so when you see “JSPG2” or “MyJSv” written, including as parts of longer words, mentally substitute your own names. For a full list of the programs that might have been used in demonstrations, see Programs used as Examples.
Web Service Clients like Interface JSPG2Client in the diagram above are delivered as C# projects. You need to have Visual Studio 2019 16.4 or later, and .Net Core 3.1 SDK or later.
You need to be using MANASYS Jazz version 3.16.1.239 or later.
This facility is only available for JSON (=REST) web services. It is not available for WSDL (=SOAP) services. REST now seems dominant, and so equivalent SOAP facilities are unlikely to be developed unless requested and funded.
Jazz web service programs like JSPG1 and JSPG2 can be quickly generated from a Jazz-format definition data definition. In this example we’re going to create program JSPG2 to update the Employee table from IBM’s DB2 Sample database. This definition was created from the New/Data/Import from SQL (select table EMPLOYEE) and then editing the definition to add extra information such as validity rules. Here is the definition Employee.jzc, with highlighting to show my editing changes: -
*#
Last Updated by JAZZUSR at 17/07/2023 11:33:11 AM
*#
Created from Table:EMPLOYEE, Schema:Robertbw10, Database:sample by JAZZUSR at
6/05/2023 12:05:36 PM
COPY Department;
DEFINE EMPLOYEE SQL DATA(
EMPNO CHAR(6) PIC '999999'
REQUIRED KEY,
FIRSTNME VARCHAR(12) REQUIRED,
MIDINIT CHAR(1),
LASTNAME VARCHAR(15) REQUIRED,
WORKDEPT CHAR(3) CAPS DKEY 'XEMP2' EXISTS DEPARTMENT.DEPTNO,
PHONENO CHAR(4) PIC '9999',
HIREDATE DATE,
JOB CHAR(8),
EDLEVEL SMALLINT REQUIRED,
SEX CHAR(1) CAPS CODES(M:Male,F:Female),
BIRTHDATE DATE,
SALARY MONEY(9,2) MIN 0,
BONUS MONEY(9,2) MIN 0,
COMM MONEY(9,2) MIN 0,
CURRENCY CHAR(3),
DEPTMGR CHAR(1),
STARTTIME TIME);
Editing changes were: -
· To SQL EMPNO and PHONENO were simply CHAR fields, but they were supposed to be character-format positive integers. SQL doesn’t support a PICTURE data type, but Jazz allows PIC to be given as a property. Now any attempt to assign a value other than 000000 to 999999 to EMPNO will be detected and reported by ACCEPT.
·
WORKDEPT is a foreign key.
EXISTS ensures that non-null values
must be current values of the primary key of DEPARTMENT. So that the reference DEPARTMENT.DEPTNO is recognized correctly COPY Department; is added before DEFINE
Employee …
By also adding CAPS MANASYS ensures that values
received as (for example) “d11” will be converted to “D11”. We strongly recommend that any KEY, DKEY, or UKEY field use CAPS to avoid
errors and ambiguity.
· SEX can have any value as far as SQL is concerned, but the actual data rules are that it must be M meaning Male, or F meaning Female. Adding CODES(M:Male, F:Female) specifies this. As with a key value, CAPS is recommended.
· Import from SQL rendered SALARY, BONUS, and COMM as DECIMAL. Changing DECIMAL to MONEY will display them with $, and MIN 0 ensures that they can’t be -ve.
· I added CURRENCY, DEPTMGR and STARTTIME to IBM’s definition of the EMPLOYEE table.
o CURRENCY was added to test that MANASYS Jazz could handle COBOL reserved words as SQL column names
o DEPTMGR was added to test that a typical “COBOL Boolean value” – a CHAR(1) field with possible values ‘Y’ or ‘N’ – would be handled correctly as a C# Boolean field with values True or False.
o STARTTIME was added (later, build #303) to test that TIME data is handled correctly in clients.
With Employee.jzc properly defined, the dialog New/Logic/Web service, with values like this, creates program JSPG2A to update the Employee table: -
This dialog creates a Jazz definition of the request and response messages, and of the logic of the program (in this case a single-table update program with CICS-style pseudo-locking). Here is the message definition for JSPG2A. Here is a copy of program JSPG2A as Jazz, and as COBOL.
A web service request/response is a relatively slow process, in my test environment taking about 3 seconds whatever the size of the web service message, so it makes sense to return as much as possible each time. Employee is defined with a duplicate key (WorkDept), and with my test data there were 14 records with WorkDept = D11, so JSPG2A was generated like JSPG2 except that I set Max >1. If this were a production situation, I’d set max to 14 or greater. For my testing I set Max=6 to test that the generated logic handled scrolling forward and back, automatically getting the next 6 (and the next) as you scrolled forward. The takeaway: set Max as large as necessary, but it doesn’t have to be the maximum possible. See the appendix Returning Multiple Records for more information.
The process is completed by compiling the COBOL, and generating the binding objects: the JSON description of the input and output messages, and WSBIND to convert these from and to COBOL. With Jazz configured for zOS this happens automatically as JCL is created and submitted to compile the COBOL and then perform further steps to create the binding objects. With Jazz configured for Micro Focus Enterprise Developer (MFED) there are a series of dialog steps to achieve the same effect. This is covered elsewhere: see here for zOS, and here for MFED
Statistics so far: Employee.jzc is 19 lines of code, 10 of which we edited. From this we generated 58 lines of Jazz message definition (editable, but we didn’t in this case), and 28 lines of Jazz program logic (which again, we didn’t edit). This became 2580 lines of COBOL, and the ZOS or MFED process created 355 lines of JSON and the binding objects to convert between COBOL and JSON. All of this compiles cleanly first time, and we have a running web service that can be tested with utilities such as ReadyAPI.
Now to get back to the real point of this Users’ Guide chapter, which is creating a client interface to make using such web services easy. First we must configure MANASYS Jazz to create Client Interfaces.
We must first create an empty ASP.NET Core Web Application. Then we will configure Jazz so it knows where to find this, so that it can create and update objects within it.
1. Open Visual Studio, create a new project. =>
2. Select template ASP.NET Core Web Application, click [Next] =>
3.
Name the project MyJSv (or your web
service name, see above).
Note the location (e.g. copy it to the clipboard). For me, this was
C:\Users\Robertbw10\source\repos
Check “Place solution and project in the same directory”. Click [Create] =>
4. Choose a template. I’ve chosen Empty. Click [Create] =>
Visual Studio creates the project and opens on an initial screen. Solution explorer shows
5.
The generated interface is going to include the line
using Newtonsoft.Json;
To make this valid in project MyJSv, click menu Tools > Nuget Package Manager > Manage NuGet Packages for Solution.
Select the Browse Tab. If Newtonsoft.Json is not already shown in this tab, enter Newtonsoft.json and click search
Click this. It appears to the right.
Click [ ] Project and then Install. Click [OK] when asked.
This
is a planned feature, it is not implemented yet.
As well as the interface itself (the
middle object in the diagram above) it is intended
that Jazz will create template objects that will use the interface objects if
you check the relevant Web or Windows checkbox (the left object in the
diagram). These template objects are
basic Windows or Web programs that invoke the interface to use the web service:
they are unlikely to appear or do exactly what you want, but they will have
some code that you can adapt. Template
objects are usually created into separate projects, with default names MyJSvCS,
MyJSvVB, MyJSvJV, etc. C# objects can
be created directly into the interface project.
If you are going to use this feature,
then you need to identify the target projects before Jazz generates objects
into them. If necessary, create new VB
and Java projects with appropriate templates.
Add a reference to the MyJSv project to these projects.
Click
[Configure], click the Client tab. Identify the full path to the project file
created above where the MyJSv (or your name) project has been created. For example,
C:\Users\Robertbw10\source\repos\MyJSv.
If you want template objects check the
relevant checkboxes, and set or locate the relevant project (optional for C#). Close the Configure form.
Interface generation uses a number of C# templates, with
names @xxxx.cs. The current list is
below. They should have been installed
with Jazz, and be in your Jazz Programs folder.
For example, for me they’re in C:\Tutorials\TstSQL\Jazz_Programs
@BoolValue.cs
@Child.cs
@DateValue.cs
@Delete.cs
@Enquiry.cs
@InList.cs
@IsBool.cs
@IsDate.cs
@IsFloat.cs
@IsInt.cs
@IsNoLongerThan.cs
@IsTime.cs
@JazzClient.cs
@Method.cs
@PChild.cs
@ReadNoScroll.cs
@ReadScroll.cs
@ReadScrollChild.cs
@RequestResponse.cs
@TimeValue.cs
@Update.cs
If necessary, install these objects
from the Jazz workbench with [Configure]/[Initialise Project] from the
Workbench Options tab.
With
the objects selected, click [Open]. This
will copy them to your programs folder, so that they can be copied to the
interface project created in Step 1.
To create an interface, open the web service program for
which you want to create an interface in the Jazz Workbench. If it is a JSON web service, the button after
[Configure] will be enabled and be labelled [Client]. You will need to have completed the
development of the web service, not just generating COBOL but also compiling
the COBOL and creating the Binding File and JSON message descriptions. Click [Process] to create the latest version
of your program, then depending on your test environment: -
·
zOS: [Process] submits the job for remote
compilation. For a web service the job
includes post-compilation steps to generate the JSON definitions and WSBind (a
program which converts between COBOL ó JSON Layouts). Copy the JSON layouts back to your local
(Windows) environment, into the root folder of your COBOL project. For example, I’m using a folder structure
C:Tutorials\TSTSQL that contains subfolders \Jazz Programs etc. For a program
named JSPG2 the post-compilation steps will have created path/JSPG2I.JSON
and path/JSPG2O.JSON. Find path
from your JCL, and copy these to objects to your equivalent of
C:Tutorials\TSTSQL. Hint: right-click
[Process]: you’ll see that the bottom section of this form provides Windows ó z/OS upload and download.
·
Micro
Focus: [Process] creates COBOL, and then
passes control to the Micro Focus COBOL project. Compile the COBOL, then create the web
service interface and binding objects as described here,
particularly the section headed Prepare
Web Services for Testing. If you are
re-creating a client interface after modifying a program, make sure that you’re
using the most recent version of the binding objects by deleting and recreating
the service interface (.svi) and associated objects (.wsbind, and .json). See When do you Need
to Re-create a Client Interface? for more information.
You
will probably have tested that your service works to at least a rudimentary
level with a Postman or ReadyAPI test.
Click
[Client] and the Web Service Client Interface dialog appears: -
WebAPI
was set with Configure/Client (in Step 3. Configure Jazz), and is the location of
your C# project. Service and program
name come from the PROGRAM statement.
The Service name must be the same as the last level of WebApi.
If
you have already created this interface check [
] Replace to re-create it. Local
JSON will always be checked: the checkbox is provided to indicate future
development, which may provide a browse dialog allowing you to search for the
relevant JSON.
Click [Create Interface].
Jazz will create a C# interface object called xxxxClient.cs, e.g.
JSPG2Client.cs, and message #577 will appear just below the PROGRAM statement: -
PROGRAM JSPG2B
WEBSERVICE MyJSv
CONTAINER DFHWS-DATA
DATABASE sample
DB2 JSON;
#577
I JSPG2BClient has been generated in C:\Users\Robertbw10\source\repos\MyJSv
If
errors are detected then messages are reported as Jazz Error messages,
following the Program statement: -
If
you have configured MANASYS Jazz to work with Micro Focus Enterprise Developer,
all the required .json files will already be in your project folder (in my case
C:\tutorials\TSTSQL\.
If
you have configured MANASYS Jazz to work directly with z/OS, the .wsbind and
.json files are created on the mainframe, and you will need to copy the Input
and Output definitions, xxxxI.json and xxxxO.json, back to your local (Windows)
environment. The top level Swagger
definition, xxxx.json is created directly into your project folder when you
created the Web Service program, so you won’t need to copy this. Hint: right-click [Process], and use the
lower part of the [Process] form to download the two .json forms that you need.
Possible future
development: if you have checked [ ] Web
or [ ] Windows MANASYS will create
further objects as “templates” for you to adapt that will use the interface. Here we’ve checked [ü] Windows, so a Windows form named
TestJSPG2.cs will be created into your C# project. Further options (VB.Net, Java, etc) will be
developed. Currently these checkboxes
will be ignored.
When [Create Interface] is clicked, Jazz first locates the
JSON descriptions of the web service.
After you’ve turned your Jazz program into COBOL, compiled this, and
created the support objects (“Binding file” or “WSBIND”), three .json files
should have been written into your project file. For example, my project file (which I set
with [Configure])
is C:\tutorials\TstSQL. For program
JSPG2 C:\tutorials\TstSQL\ contains these three .json files: -
·
JSPG2.json This is created directly by Jazz when
you generated program JSPG2: it is a Swagger definition that defines the
service and has references to the message formats (schemas) JSPG2I and
JSPG2O. These don’t exist when it is
first created.
·
JSPG2I.json This describes the Input (Request)
message. It is created later, by either
ZOS or Micro Focus. If created by ZOS it
will have originally been created on a mainframe, and should have been copied
back to your project file.
·
JSPG2O.json This describes the Output (Response)
message. Like JSPG2I it is created
later.
From
the Swagger definition Jazz extracts path information (request type – post,
put, etc, and path, e.g. http://localhost:9003/cics/services/JSPG2), and
references to the schemas for the input and output messages. The input and output schemas are vital as
they describe the format of the messages sent to and from the web service.
If
these files can’t be located then message #559 will be produced. To correct the problem, first check that all
the steps after producing COBOL – compile the COBOL, produce binding file and
JSON – have been completed (e.g. by
testing the service with a utility like Postman
or ReadyAPI). If you can run such a test then JSON has
been produced, although the JSON may still be only on the mainframe. Locate this JSON manually, and copy it to
your project folder.
If the JSON exists then the message definitions are created:
-
RequestJSPG2.cs Created
from the input message, JSPG2I, this is the actual structure of the request
message.
ResponseJSPG2.cs Created from the output message, JSPG2O, this
is the actual structure of the response message.
If
you were to examine your C# interface project, it would now look like this
except for the client object (JSPG2Client.cs) which will be created in the next
step.
Step
1 has created the Request and Response objects, like the examples below. Next Jazz creates the interface object,
JSPG2Client.
An
object like JSPG2Client provides one or more methods
to communicate with the web service.
Program JSPG2 requires at least four methods, Enquiry, Update, Add, and
Delete, corresponding to the possible values of Function, and there are also
ReadNext, ReadLast, ReadPrev and ReadFirst methods because the Enquiry offers a
scrolling option if you read records by the duplicate key, WorkDept. Other clients might include Logon, Logoff,
New Order, Process Order etc.
The
process starts by reading a template, @JazzClient.cs,
into memory, substituting values for @Service (=MyJSv) and @Program(=JSPG2),
and removing markers like @Private after pointers are set to the preceding
lines. The Client object is then
generated based on all the information that is available to program JSPG2. You can learn more about it at Understanding the Client Interface.
The
purpose of a client interface like JSPG2Client.cs is to make it much easier for
client programs to interface with the web service, and to provide local
validation so that errors are detected instantly rather than with the delay
inherent in a Request/Response cycle.
However local and remote validation rules may differ:
1. Validation requiring further
I/O, such as an EXISTS property,
is not checked locally. For example, Employee.Workdept is defined
WORKDEPT CHAR(3) CAPS DKEY 'XEMP2' EXISTS DEPARTMENT.DEPTNO,
To be valid any value entered for WorkDept must exist as a
value of DEPARTMENT.DEPTNO,
the primary key of the DEPARTMENT table.
This is not checked within JSPG2Client.
There would be no point in doing this: it can only be checked by sending
a request to a web service, incurring the response delay that local validation
is supposed to prevent.
2. CHECKR properties are defined in
the record definition to specify Check Routines, which are routines (paragraphs
or subprograms) that check particular fields, for example a bank account,
social security number, etc. The web
service knows their names and interface rules, but not their internal logic,
and they may be written in COBOL or any language that can be called from a
COBOL program. A Check Routine might even require server-side I/O, for example
looking up reference tables. Like EXISTS, these are not checked
locally.
Future development:
it should be possible to write your own local routines, for example to check
that a social security number has the correct format, and use these in an
interface.
3. Some local validation may
follow different rules because formats are different, or COBOL and C# follow
different rules. Particular examples:
a.
A
Jazz BOOLEAN field is usually CHAR(1) with ‘Y’ meaning ‘true’
and not ‘Y’ meaning ‘false’. The
interface will render these as ‘true’ and ‘false’, and expect input as “t’ or
‘f’ (which will be displayed by the client interface as “true” or “false”, but
be sent to the service as “Y” or “N”).
b.
DATE fields
are transmitted in a standard form (Integer, format yyyymmdd), whatever the
culture settings at either end, but are displayed and managed with your local
culture settings. For example, BirthDate
is transmitted as 19560527, but with New Zealand culture settings will be
displayed and edited as 27/05/1956. With
U.S. settings it would be displayed and edited as 5/27/1956.
c.
TIME fields
are also transmitted as Integers, with format hhmmssmmm. In the client they will be displayed as
hh:mm:ss.mmm, e.g 25.678 seconds after 3:45 PM would be displayed as
15:45:25.678. SQL TIME fields can’t hold
milliseconds, so on the server the milliseconds will be rounded up or down to
the nearest second.
For
these reasons, and also because the service cannot guarantee that the message
it receives has come from the web service client interface, request messages
are fully validated by the service before they are accepted for processing. With good client-side validation most of the
possible errors will have been prevented, but the cost of repeated validation
is a small price to pay for the assurance it provides. Should an error be detected in the service it
will be reported in the returned Error value.
If
you have modified your program in a way that doesn’t change the message formats
or operations supported by the service, then you can generate and compile the
COBOL program but you do not need to re-create the client interface. For example, minor changes to logic like a
different message, or a changed calculation, will have no effect on the message
formats, and you can test your new program with its client without re-creating
the interface. However, if you change
the message format: for example, you change the list of fields to be sent in
either the input or output message, or you add or remove a method (Update, Add,
Delete, etc) compared to the previous message, then you’ll need to regenerate
the interface.
You’ll
also need to regenerate the interface if you change between returning 1 record
or several. I generated program JSPG2 to return 1 EMPLOYEE
record, and program JSPG2A to return 6.
The difference between 1 and 6 means that for JSPG2A the EMPLOYEE data
is returned in an array, and so the interfaces are different. Because EMPLOYEE data is returned as an array
for JSPG2A, but not for JSPG2 (even though the COBOL record structure is
defined with OCCURS 1), JSPG2 and JSPG2A require different interfaces. The interface need not be re-created if
JSPG2A is changed to return a different number, provided that it is > 1.
Note
that, “Array” means something different in the world of COBOL, and
object-oriented languages like Java and C#.
To a COBOL programmer an array is a repeating set of identical
structures mapped to a region of memory, and COBOL programmers can take
advantage of this by redefining the array.
In the client-side world of C# amd Java, arrays are lists. They are not necessarily in a contiguous
region of memory, so Array(2) might not follow Array(1) in memory. Fields of
the array may be directly within the array element, more often they are
pointers to data stored elsewhere.
Elements don’t have to be the same size.
They may even differ in format.
They cannot be redefined: there is no equivalent of a COBOL REDEFINES
clause.
With Micro Focus Enterprise Developer: -
In
Solution Explorer, right-click your COBOL project and click Refresh
Annotations.
Select
your program or click [Auto Select], and then click [Refresh]
Delete
the old .svi, .wsbind, and .json objects, e.g. JSPG2A.svi, JSPG2A.wsbind*,
JSPG2AI.json, and JSPG2AO.json
* The .wsbind may have been
moved
Now
recreate the .svi, and wsbind etc. as described
here.
Refresh the the service
interface. To ensure that everything is consistent: -
·
In
Visual Studio, with the COBOL project (in my case TstSQL) open
·
In
the Solution Explorer window right-click the project and select Refresh
Annotations for Services.
·
A
form like this appears
·
Here
no programs have red exclamation marks, so no further action is necessary. If one or more programs have an exclamation
mark, either select them and click [Refresh], or click [Auto Select] and then
[Refresh] to update them all.
With z/OS: -
[Process]
your program to submit a job that will compile your program and [re-]create the
binding objects including the .json
Copy the two .json files back to your project folder, e.g.
your equivalent of C:\Tutorials\TSTSQL
The section above, Creating a Client Interface,
dealt with creating the middle section of this diagram. Creating this is simply a matter of clicking
[Client] and then [Create Interface] from the Jazz Workbench with the program
that created the web service (right section, “Mainframe”). If changes are made to the service, you can
re-generate the interface.
You’ll develop a client – a
Windows Form, a Web page, or a mobile App – that communicates with the web
service through the interface to use the interface. This client might be developed in C#, Java,
VB.NET, or other languages. This is the left part of this diagram.
Clients
will communicate with the interface through properties and methods, which can
be discovered in the usual way. This
section describes how you can write such a client: JSPG2Test is a Windows form
that uses JSPG2Client to communicate with the web service. This section shows how you would write such a
client, using Visual Studio.
If there is user demand for this,
MANASYS could generate prototype clients, like the Windows form used in this
example. These are unlikely to be
anything more than a code template, from which you can extract useful code
snippets, after all, we don’t know what you want to do. A real client may invoke other web services,
access local data, or services like the client’s current GPS location.
Client
objects are created within a suitable project – for example, if you want to
create a C# Windows client, then you must have a C# Windows project. For a VB Windows Client you’ll need a VB
Windows project, and so on. If such a
project doesn’t exist, you can create a new project.
To
create a suitable C# Windows Project: -
1. Open Visual Studio, select
New Project, and choose a suitable template.
I chose Windows Forms App (.NET Core).
Click [Next]
2. On the next form
a. give the project a name (I
used “MyJSvClientTests”),
b. optionally check [ ] Place solution and Project in the same
directory (recommended), and
c. click [Create]. A project is created with these objects: -
3. Right-click Solution
‘MyJSvClientTests’, and select Add/Existing Project. A Windows Explorer opens showing you your
projects. Click on folder MyJSv, and
then when that folder opens, click on MyJSv.csproj and then click [Open] : -
The project now looks like this: -
4. Rename Form1 to something
with more meaning. I renamed it to
JSPG2Test: -
a.
Right-click
Form1.cs, click Rename
b.
Change
“Form1” to “JSPG2Test”.
c.
Reply
[Yes] to the message “Would you also like to perform a rename in this project
to all references to the code element ‘Form1’?
d.
Open
the form JSPG2Test and change its Text property from Form1 to JSPG2Test to be
consistent with its object name
5. Right-click the Project (NOT the
Solution) MyJSvClientTests and select Project Reference. Because we have already included this
project within our Solution, it is the first option: -
Click this checkbox.
I have followed this naming convention as I developed more
tests: my project MyJSvClientTests currently contains JSPG1Test.cs,
JSPG1VTest.cs, JSPG2ATest.cs, JZPG2Test.cs, and JSTIMETest.cs. Each program uses the corresponding
xxxxClient.cs in project MyJSv.
To add another form, in Solution Explorer right-click your
test project (MyJSvClientTests), and click Add, and then Form(Windows Form): -
Give the form the name that you want (e.g.) JSPG3DTest,
click [Add]. An empty form appears, it
already has the correct name so you don’t need to change this.
Before we can add buttons to
invoke interface methods, and labels and textboxes to display data, we need to
add some code to the form. We have
already added a reference to project MyJSv in step #5. Now, with each new test form: -
6.
We
need to instantiate an interface object within
our form. Edit the code for form JSPG2Test (if it is not already open in Visual
Studio, right-click JSPG2Test.cs in Solution Explorer, and click View
Code). Add
using MyJSv;
towards the
top.
7.
Add
static JSPG2Client Jspg2Client = new JSPG2Client();
where shown in
the code below. Note that we’ve used the
same name, but with different case:
Jspg2Client is an instance of the class JSPG2Client.
8. We also add
bool EditMode = true;
The reason for this will become clear
shortly.
The
C# code for our form now looks like this.
I’ve highlighted the statements that I wrote, the rest was inserted by
Visual Studio when I created JSPG2Test: -
using MyJSv;
using System.Windows.Forms;
namespace MyJSvClientTests
{
public partial class JSPG2Test : Form
{
static
JSPG2Client Jspg2Client = new
JSPG2Client();
bool
EditMode = true;
public JSPG2Test()
{
InitializeComponent();
}
}
}
The following notes detail how you create your first form.
Go to Creating Similar Tests to find
some shortcuts to reuse your earlier tests.
Now
that we’ve created a suitable form object, JSPG2Test, and instantiated the
interface object JSPG2Client as Jspg2Client
(same name, different capitalization), we’re ready to design the form
(or web page or app or …) by adding controls (labels, textboxes, buttons) to
communicate with Jspg2Client properties and methods. As you develop the form, you’ll develop the
form’s logic, described in the next section. In reality you’ll flip-flop between these two
sections, but it’s easier to describe appearance and Form
Logic separately.
Here is a form that I created to test and demonstrate the
interface: -
I’ve created
·
label
fields lblError, lblBrowseCount and lblReturnCode to display various output
fields returned by the service,
·
textboxes
for fields that can be entered by users and passed to the web service and also
returned from the service. I arranged
the fields used for access (EMPNO, WORKDEPT, and SKIP) at the top, and
highlighted the two key fields.
·
Access
by WORKDEPT might return several values, for example WORKDEPT=D11 corresponds
to ~ 14 records in my test data. The
form needs scrolling commands as shown to manage this, arranged in a Groupbox
so that the control group can be displayed or not depending on whether
scrolling is relevant.
·
lblSex
to display the value of the Sex code,
·
lblHttpResponse
to display response header information, and lblRawResponse to display the
response if the request fails.
·
a
series of buttons for the methods that the interface supports, plus [Clear] and
[Close].
This
form uses almost all of the data and methods available, but in a real situation
you might use only some of the interface’s fields and methods. As well as the
buttons like btnEnquire that invoke interface methods, [Clear] empties the form
controls, and [Close] closes it.
How
do you know what data (properties) and methods (operations) are available? You can find out from Intellisense, or from
the source code of the interface if you developed the interface or have access
to it. If you didn’t develop the interface, you might have been given
documentation of its properties and methods.
Within the logic of a control write the name of the service
interface (Jspg2Client) followed by a period.
This name comes from
static JSPG2Client Jspg2Client = new JSPG2Client();
that
we wrote above. Thus we double-click a control, and write
“Jspg2client.” in its event logic:-
private void txtEmpno_TextChanged(object sender, EventArgs e)
{
{
Jspg2Client.
}
}
As
we type the “.” Intellisense responds by showing you a list of properties and
methods in the interface: -
The source code of the service interface provides
useful data.
1. A few lines from the
beginning you’ll find a definition of struct SetByUser. This provides a list of all
the fields that can be changed by the client: these should become
textboxes. I have named my textboxes
txtXxxx, e.g. txtEmpno, and placed a label ahead of them with their name
(EMPNO).
2. A little further down there
is a comment
// Key Properties - fields used by
methods to access records
The following properties are particularly important: they must all be present
in your client program, as they are used to tell the methods which records you
want to work with. For JSPG2Client there
is a field called Skip used to control scrolling, and two fields EMPNO and
WORKDEPT. EMPNO is the primary key of
EMPLOYEE, and WORKDEPT is a duplicate key.
Skip is used when you access the table by WORKDEPT, providing an ability
to handle multiple records with scrolling.
3. Next is a comment
//
I/O Data - non-key fields that may be set for update and add, will be
set in response
These properties are general non-key fields that the client might use, and
might change. Like the key properties,
they should become textboxes if you want them.
4. Coded fields and _Value
properties. Some fields in the web
service have been defined as codes: for example SEX is defined: -
SEX CHAR(1) CAPS CODES(M:Male, F:Female),
The value
which you can enter in the text box is the code – “M” or “F”. Because SEX is defined with CODES, a
readonly property SEX_Value is created which will contain “Male” or “Female”.
Next there are
output fields
//
Output data - readonly properties that are set from response, e.g.
JZ_Error
You should put these into labels, not textboxes, because the client can’t
change their value. Of these, Error is
particularly important. If any errors
are detected then text will be returned in this field, and from a normal
response a message like SQL UPDATE/INSERT SUCCESSFUL
will be displayed. The fields
JZ_Employee_BrowseCount and JZ_Employee_Return code are useful to control
scrolling. Return Code is another example of a coded field: it is defined
ReturnCode
CHAR(1) CAPS CODES(' ':' ',W:Warning,E:Error,S:'Serious Error',T:'Terminal Error',
A:Absent,C:Changed,D:Deleted,F:First,I:Inserted, L:Last, N:Nth,U:Unique));
and there
is a ReturnCode_Value property in case you want to display “Absent”, etc.
5. Following the output fields
are the methods
//
Methods. Usually related to
Function, e.g. Function CHAR(1) CAPS CODES(E:Enquiry, U:Update, A:Add,
D:Delete) will have
// Enquiry, Update, Add, and Delete methods
This section contains code for each method, like
public bool Enquiry(string PSkip = null,string PEMPNO = null,string PWORKDEPT = null)
and the code to implement this method.
You should not need to be concerned with this implementation, but you
will need to put a Button in your client for each method that you want to use,
and write appropriate code to invoke the method. We’ll see how to do this later.
Double-click
a TextBox and a TextChanged handler is added.
Using Intellisence we can select a Key or I/O property of the interface,
and complete the statement by assigning the textbox’s text value to this
property
private void
txtEmpno_TextChanged(object sender, System.EventArgs e)
{
Jspg2Client.EMPNO = txtEmpno.Text;
}
As
values are typed into the textbox they are validated according to the rules
that have been built into the set option for the Jspg2Client property. You will remember that EMPNO is numeric, and
has range 0 to 999999.
txtEmpno_TextChanged will be invoked whenever we key a value into
txtEmpno.Text, so if we attempt to enter a non-numeric character we’ll
immediately get the error message and return code displayed.
However,
txtEmpno_TextChanged is also triggered when a value is returned from an Enquiry
(or other) method. We don’t want this,
so an if condition is added so that the code is only executed when
EditMode is true: -
private void txtEmpno_TextChanged(object sender, EventArgs e)
{
if (EditMode)
{
Jspg2Client.EMPNO = txtEmpno.Text;
}
}
Provided that the value entered obeys this rule the
assignment will accept the value, but if the value is invalid it will be
rejected and the interface sets output fields Error and a return code, so we
write code like this. The two lines setting lblError.text and
lblReturnCode.text will occur many times in the client, so they’re put into a
routine: -
private void txtEmpno_TextChanged(object sender, EventArgs e)
{
if (EditMode)
{
Jspg2Client.EMPNO = txtEmpno.Text;
ShowResponse();
}
}
private void ShowResponse()
{
lblError.Text =
Jspg2Client.Error;
lblReturnCode.Text = Jspg2Client.JZ_Employee_ReturnCode + ":" +
Jspg2Client.JZ_Employee_ReturnCode_Value;
}
We
should write similar TextChanged handlers for all of the textboxes. They will reuse ShowReponse, we don’t need
to write this again.
Every interface will have an Enquiry method, so it’s logical
to start with this. Double-click the [Enquiry] button and a Click handler is
added:
private void btnEnquiry_Click(object sender, EventArgs e)
{
}
We
write code here to invoke the interface’s Enquiry method, and handle its
response. We start by writing a
reference to the interface, and selecting the Enquiry method. Intellisense shows us the parameters that the
method requires.
Parameters
are PSkip, PEMPNO, and PWORKDEPT, corresponding to textboxes txtSkip, txtEmpno,
and txtWorkDept. Note also that
interface methods are functions returning True or False, as they’re designed to
be written as conditions of an IF statement.
We
add this logic to invoke Jspg2Client.Enquiry: -
private void btnEnquiry_Click(object sender, EventArgs e)
{
if (Jspg2Client.Enquiry(txtSkip.Text, txtEmpno.Text,
txtWorkdept.Text))
{
HandleValidResponse();
}
else
{
ShowResponse();
}
}
If
there is a problem then the method returns False and only the Error and Return
Code properties. However, if the
interface request succeeds a response is received with data in all the
interface’s properties, so we execute HandleValidResponse to copy the data from
these to our client’s controls like txtEMPNO.
All of the methods will have the same action on return, so we change
this to
private void btnEnquiry_Click(object sender, EventArgs e)
{
HandleResponse(Jspg2Client.Enquiry(txtSkip.Text, txtEmpno.Text,
txtWorkdept.Text));
}
private void HandleResponse(bool Method)
{
if (Method)
{
HandleValidResponse();
}
else
{
ShowResponse();
}
}
If
there is no need to handle scrolling, HandleValidResponse is just this: -
private void HandleValidResponse()
{
SetFormValues();
}
SetFormValues
simply assigns all the interface properties to corresponding form
controls.
private void SetFormValues()
{
EditMode = false;
lblError.Text =
Jspg2Client.Error;
lblBrowseCount.Text = Jspg2Client.JZ_Employee_BrowseCount;
lblReturnCode.Text
= Jspg2Client.JZ_Employee_ReturnCode + ":" + Jspg2Client.JZ_Employee_ReturnCode_Value;
txtEmpno.Text =
Jspg2Client.EMPNO;
txtWorkdept.Text
= Jspg2Client.WORKDEPT;
txtSkip.Text =
Jspg2Client.Skip;
txtFirstNme.Text
= Jspg2Client.FIRSTNME;
txtMidInit.Text
= Jspg2Client.MIDINIT;
txtLastName.Text
= Jspg2Client.LASTNAME;
txtPhoneNo.Text
= Jspg2Client.PHONENO;
txtHireDate.Text
= Jspg2Client.HIREDATE;
txtJob.Text =
Jspg2Client.JOB;
txtEdlevel.Text
= Jspg2Client.EDLEVEL;
txtSex.Text =
Jspg2Client.SEX;
lblSex.Text =
Jspg2Client.SEX_Value;
txtBirthDate.Text = Jspg2Client.BIRTHDATE;
txtSalary.Text =
Jspg2Client.SALARY;
txtBonus.Text =
Jspg2Client.BONUS;
txtComm.Text =
Jspg2Client.COMM;
txtCurrency.Text
= Jspg2Client.CURRENCY;
txtDeptMgr.Text
= Jspg2Client.DEPTMGR;
EditMode = true;
}
It
is written as a subroutine as it will be used for other methods. Note that it starts by setting EditMode = false; and
concludes with EditMode = true; so
that the assignments don’t execute the code in the TextChanged handlers. This would not only be pointless but could
have some undesirable side effects such as recording the field as Changed (=
set by user) causing it to be included in a following Update.
Program
JSPG2 retrieves Employee records using either Employee’s primary key, EMPNO, or
WORKDEPT. EMPNO may retrieve zero or 1
record, WORKDEPT may retrieve zero or many records. To handle scrolling I’ve placed buttons and
the Skip textbox on the form within a groupbox: -
When the enquiry uses the duplicate key, WORKDEPT, these
scrolling controls will appear if there is more than one Employee for this
WORKDEPT value, and the scrolling buttons will be active if there are more
records in the Next or Previous direction.
Two output fields are important for controlling scrolling: -
·
JZ-Employee-BrowseCount
is set to 0 for access by unique key (EMPNO), and to the number of records that
could be retrieved for non-unique access (WORKDEPT). We do not want the scrolling controls if
BrowseCount is 0 or 1.
·
If
scrolling applies, then the return code, defined as
JZ-Employee-ReturnCode
CHAR(1) CAPS CODES(' ':' ',W:Warning,E:Error,S:'Serious Error',T:'Terminal Error',
A:Absent,C:Changed,D:Deleted,F:First,I:Inserted, L:Last, N:Nth,U:Unique));
can tell us whether where at the beginning or end of the
scrolling list. For ReturnCode F (First) we don’t want the Previous
[ < ] and First [ << ] buttons active, for Return code L (Last) we don’t want Next [ > ]
or Last [ >> ].
Note 1.
Field names in Jazz or COBOL that have hyphens will have underscores in
C# and other client languages. Thus,
these fields are called JZ_Employee_BrowseCount
and JZ_Employee_ReturnCode in C#.
Note
2. BrowseCount
is calculated differently for SQL (=DB/2 etc.) and VSAM. See Browsecount.
Note 3.
It is a bad idea to use a
scroll bar with a web service. Dragging
a scroll bar causes a flurry of request/responses to be sent to/from the web
service.
We’ll need code to be executed with
Enquiry to control whether we see the scrolling commands, and whether the Next
or Previous buttons are active. But the
scrolling buttons, also need this code, so this logic is put into
HandleValidResponse: -
private void HandleValidResponse()
{
int BrowseCount;
int.TryParse(Jspg2Client.JZ_Employee_BrowseCount,
out BrowseCount);
if (BrowseCount < 2)
{
gbxScroll.Visible
= false;
}
else
{
gbxScroll.Visible = true;
if (Jspg2Client.JZ_Employee_ReturnCode == "F")
{
btnPrev.Enabled = false;
btnFirst.Enabled = false;
}
else
{
btnPrev.Enabled = true;
btnFirst.Enabled = true;
}
if (Jspg2Client.JZ_Employee_ReturnCode == "L")
{
btnNext.Enabled = false;
btnLast.Enabled = false;
}
else
{
btnNext.Enabled = true;
btnLast.Enabled = true;
}
}
SetFormValues();
}
Because this web service may require scrolling, the
interface has been generated with ReadNext, ReadLast, ReadFirst and ReadPrev
methods. IntelliSense discovers these,
and we add methods like this: -
private void btnNext_Click(object sender, EventArgs e)
{
HandleResponse(Jspg2Client.ReadNext(txtSkip.Text));
}
private void btnPrev_Click(object sender, EventArgs e)
{
HandleResponse(Jspg2Client.ReadPrev(txtSkip.Text));
}
private void btnLast_Click(object sender, EventArgs e)
{
HandleResponse(Jspg2Client.ReadLast());
}
private void btnFirst_Click(object sender, EventArgs e)
{
HandleResponse(Jspg2Client.ReadFirst());
}
In
the same way we discover that Update, Add, and Delete require a single
argument, EMPNO, which is Employee’s primary key, so except for the method name
we write mostly the same code for each.
Here is the Update logic: -
private void btnUpdate_Click(object sender, EventArgs e)
{
HandleResponse(Jspg2Client.Update(txtEmpno.Text));
}
Update
must follow an enquiry, either a single-record enquiry using the primary key
(EMPNO) and the [Enquiry] button, or a scrolling enquiry with ReadNext or
ReadPrev. The enquiry will have returned
a checksum which is used to prevent invalid updates. If, between your enquiry and update another
user changes the record then a different checksum will be calculated: this
difference will be detected within the interface, and your update
rejected. NB: since this snap was
recorded the check message has changed: it would now be
GET check failure: EMPLOYEE changed or not read
To
update a record we’ll enter new data in various textboxes, then click the
[Update] button. Here I’ve removed
MIDINIT by entering “null” (must be lower case), set JOB to “Manager”, and
DEPTMGR to T (for True).
Click
[Update] and the record is updated, and new values displayed: -
Delete is identical except that it
invokes Jspg2Client.Delete. Like
Update, it must follow an enquiry: -
private void btnDelete_Click(object sender, EventArgs e)
{
HandleResponse(Jspg2Client.Delete(txtEmpno.Text));
}
Add requires some extra
logic. If it were the same as Update
then it would be misleading: Update sends the record key (EMPNO) and the changed
data to the service to the service. With
the Add method you want all the data that you see displayed in the
form’s controls to be included in the new record, whether you’ve edited it or
whether it has come from a preceding read.
Before the Add the btnAdd_Click event therefore sets the Jspg2Client
property corresponding to each of the textboxes except the primary key (EMPNO)
and Skip. Unlike
HandleValidResponse/SetFormValues these assignments are NOT surrounded by EditMode = false; and
EditMode = true;
because the point of these assignments is to set the relevant Changed flag.
private void btnAdd_Click(object sender, EventArgs e)
{
// Input all textboxes (except 1ry
Key and Skip), and set their Changed flag
Jspg2Client.WORKDEPT = txtWorkdept.Text;
Jspg2Client.FIRSTNME = txtFirstNme.Text;
Jspg2Client.MIDINIT = txtMidInit.Text;
Jspg2Client.LASTNAME = txtLastName.Text;
Jspg2Client.PHONENO = txtPhoneNo.Text;
Jspg2Client.HIREDATE = txtHireDate.Text;
Jspg2Client.JOB
= txtJob.Text;
Jspg2Client.EDLEVEL = txtEdlevel.Text;
Jspg2Client.SEX
= txtSex.Text;
Jspg2Client.BIRTHDATE = txtBirthDate.Text;
Jspg2Client.SALARY = txtSalary.Text;
Jspg2Client.BONUS = txtBonus.Text;
Jspg2Client.COMM
= txtComm.Text;
Jspg2Client.CURRENCY = txtCurrency.Text;
// Now add the new record
HandleResponse(Jspg2Client.Add(""));
}
These are basic methods that do not involve the web
service.
Clear simply sets all the textboxes to blank. This must be done with EditMode = false;
to prevent error messages from blank required fields
private void btnClear_Click(object sender, EventArgs e)
{
EditMode = false;
lblError.Text = "";
lblBrowseCount.Text
= "";
lblReturnCode.Text = "";
txtEmpno.Text = "";
txtWorkdept.Text
= "";
txtSkip.Text = "0";
txtFirstNme.Text
= "";
txtMidInit.Text
= "";
txtLastName.Text
= "";
txtPhoneNo.Text
= "";
txtHireDate.Text
= "";
txtJob.Text = "";
txtEdlevel.Text
= "";
txtSex.Text = "";
txtBirthDate.Text = "";
txtSalary.Text =
"";
txtBonus.Text = "";
txtComm.Text = "";
txtCurrency.Text
= "";
EditMode = true;
}
Close is
private void btnClose_Click(object sender, EventArgs e)
{
this.Close();
}
As
your system evolves there can be a need to add extra controls to your client
form. For example, field
Employee.StartTime was added to the Employee record: -
DEFINE EMPLOYEE
SQL DATA(
…
DEPTMGR BOOLEAN,
STARTTIME TIME);
With
the web service JSPG2.jzz (=>JSPG2.cbl) regenerated, and the interface
JSPG2Client.js regenerated, our client program JSPG2Test continues to work
exactly as before, ignoring this new field. To use it, we need to add a control
to the JSPG2Test form. Here I’ve added a
label with the field’s name, and a textbox called “txtStartTime”: -
Now I wire it up so that values can
be displayed and updated. Double-click
the textbox, opening a _TextChanged handler.
Write similar logic within this as for other textboxes: -
private
void txtStartTime_TextChanged(object
sender, EventArgs e)
{
if
(EditMode)
{
Jspg2Client.STARTTIME =
txtStartTime.Text;
ShowResponse();
}
}
Next,
find the routine SetFormValues. Add an
assignment for StartTime: -
private void SetFormValues()
{
EditMode = false;
…
txtStartTime.Text =
Jspg2Client.STARTTIME;
lblHttpResponse.Text = Jspg2Client.StatusCode.ToString() + "(" + Jspg2Client.StatusCode_Value + "), Success:" +
Jspg2Client.IsSuccessStatusCode + ", Reason:" + Jspg2Client.ReasonPhrase;
lblRawResponse.Text = Jspg2Client.RawResponse;
EditMode = true;
}
For coded fields like SEX then there would also have been an
assignment to the relevant label field: -
txtSex.Text =
Jspg2Client.SEX;
lblSex.Text =
Jspg2Client.SEX_Value;
but
this is not relevant to StartTime.
An
assignment from the textbox to the interface property is added to the
btnAdd_Click method, to ensure that an Add will include this value: -
private void btnAdd_Click(object sender, EventArgs e)
{
// Validate all textboxes (except
1ry Key and Skip), ensuring that they will be included in following Add because
Changed will have been set
…
Jspg2Client.CURRENCY = txtCurrency.Text;
Jspg2Client.STARTTIME =
txtStartTime.Text;
// Now add the new record
HandleResponse(Jspg2Client.Add(""));
}
Add
StartTime to btnClear_Click: -
private void btnClear_Click(object sender, EventArgs e)
{
EditMode = false;
…
txtCurrency.Text = "";
txtStartTime.Text = "";
EditMode = true;
}
Having
developed one test, you can save time on your later tests. Here I am developing a test for web service
JSPG3D, which is a single-table enquiry reading JSPG3D to read the Department
table. At this point I have created an
empty form called JSPG3DTest, and I’ve added the code described in initializing the form. Now I want to add controls
to this form, which are similar to the controls on a previous test, JSPG1Test
which read the Employee table.
1.
I
opened form JSPG1Test.cs (design view), and selected the part of the form
containing the controls I wanted.
2.
I
copy/pasted these controls into form JSPG3DTest
3.
I
then edited the form to
a. Change text to the
appropriate field names, e.g. “EMPNO” becomes “DEPTNO”
b. Change the names of controls
to appropriate names where necessary, e.g. “txtEmpno” becomes “txtDeptno”
c. Add extra controls as
necessary: where Employee had one alternate index, Department has two
Now
the form looks like this, with the controls that I wanted, but no logic beyond
that from initializing the form: -
Now
the controls are wired up as described above.
Click on the relevant control, and add similar code. Sometimes you can simply Copy/Paste code from
the copied form (e.g. [Close]), sometimes it is worthwhile to copy/paste and
then edit the code, but often it is just as easy to create new code following
the patterns above.
In Using a Client Interface project MyJSvTests was
created with a single form, JSPG2Test. Starting this project with Visual Studio
automatically opens form JSPG2Test. Now
you want to test another interface? You
could of course create another project, but if your new client is another C#
form then you may prefer to add this form to MyJScTests.
When
you create a C# forms project, one of the objects created in it is Program.cs,
containing this code: -
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace MyJSvClientTests
{
static class Program
{
/// <summary>
///
The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.SetHighDpiMode(HighDpiMode.SystemAware);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new JSPG2Test());
}
}
}
My
next task was to test JSPG2A, a version of JSPG2 that returns several Employee
records. If I add
Application.Run(new JSPG2ATest());
to this, then
the project will first open JSPG2test, then when I close this it will open
JSPG2ATest. This worked well when I was
testing for differences in behaviour between JSPG2 and JSPG2A. When I was working with just one of the
programs I’d comment out the Application.Run that I didn’t want.
I didn’t bother creating a menu. Currently the logic in
MyJSvClientTests.Program is like this: -
static void Main()
{
Application.SetHighDpiMode(HighDpiMode.SystemAware);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
//Application.Run(new JSPG1Test());
//Application.Run(new JSPG1VTest());
//Application.Run(new JSPG2Test());
//Application.Run(new JSPG2ATest());
Application.Run(new JSTimeTest());
}
For
complete details of the JSPG2A test, see Programs
used as Examples
So far
we have dealt with services that handle one type of record, either for Enquiry
only, or with Update, Add, and Delete functions. We can also create services that handle a
parent record and a set of child records.
We’ll illustrate this with program JSPG3 which, like JSPG2, is a web
service capable of updating DB2 data. It
works with two records from the IBM Sample database, Department, and Employee,
defined to MANASYS like this: -
*#
Created from Table:DEPARTMENT, Schema:ROBERTBW10, Database:Sample by JazzUser
at 23/03/2019 7:52:20 AM
DEFINE DEPARTMENT SQL DATA(
DEPTNO CHAR(3) CAPS REQUIRED KEY,
DEPTNAME VARCHAR(36) REQUIRED,
MGRNO CHAR(6) PIC '999999' DKEY 'XDEPT2',
ADMRDEPT CHAR(3) CAPS REQUIRED DKEY 'XDEPT3',
LOCATION CHAR(16));
*#
Created from Table:EMPLOYEE, Schema:ROBERTBW10, Database:Sample by JazzUser at
26/03/2019 4:25:46 PM
COPY Department;
DEFINE EMPLOYEE SQL DATA(
EMPNO CHAR(6) PIC '999999' REQUIRED KEY,
FIRSTNME VARCHAR(12) REQUIRED,
MIDINIT CHAR (1),
LASTNAME VARCHAR(15) REQUIRED,
WORKDEPT CHAR(3) CAPS DKEY 'XEMP2' EXISTS DEPARTMENT.DEPTNO,
PHONENO CHAR(4) PIC '9999',
HIREDATE DATE,
JOB CHAR(8),
EDLEVEL SMALLINT REQUIRED,
SEX CHAR(1) CAPS CODES(M:Male, F:Female),
BIRTHDATE DATE,
SALARY MONEY(9,2) MIN 0,
BONUS MONEY(9,2) MIN 0,
COMM MONEY(9,2) MIN 0,
CURRENCY CHAR(3),
DEPTMGR BOOLEAN);
As the
initial comments indicate, these MANASYS record definitions were initially
created using Import from SQL, but then the definitions were enhanced with Jazz
properties like PIC '999999', CAPS, CODES(M:Male, F:Female),
and others to provide better editing and make programming easier.
Department
has primary key DEPTNO, and two duplicate keys, MGRNO and ADMRDEPT. We can access the table by any of these keys,
for example: -
db2 => Select * from
Department where admrdept = 'A00'
DEPTNO DEPTNAME MGRNO ADMRDEPT LOCATION
------
------------------------------------ ------ -------- ----------------
A00 SPIFFY COMPUTER SERVICE DIVISION 000010 A00 Head Office
B01 Planning. 000020 A00 Building G
C01 INFORMATION CENTER 000030 A00 -
D01 DEVELOPMENT CENTER - A00
-
E01 SUPPORT SERVICES 000050 A00 -
5 record(s) selected.
Employee records are related to Department through WorkDept:
-
WORKDEPT CHAR(3) CAPS DKEY 'XEMP2' EXISTS DEPARTMENT.DEPTNO,
Of the 5 departments in the list above, some have employees:
-
db2 => Select count(*), workdept
from Employee where workdept in (Select DEPTNO from department where admrdept =
'A00') Group by workdept
1 WORKDEPT
----------- --------
30 A00
1 B01
4 C01
1 E01
4 record(s) selected.
Here
we’re creating a service that will return a Department record, and 0 to 10
employees (at a time) who work in that department: -
|
|
[Finish] creates a Jazz web service program, JSPG3, that
that can read and update Department much as program JSPG2 could read and update
Employee. But unlike JSPG2, with each
Department the related Employee records are also read: the [Finish] process
deduces the relationship between the two tables from the EXISTS DEPARTMENT.DEPTNO
property
of EMPLOYEE.WORKDEPT. The web service message definition, and then
the program JSPG3, is displayed in the Jazz Workbench.
Compared to a single-record service like JSPG2
1.
The
Enquiry and Update logic includes code to read the related Employee records: -
…
WHEN
(Enquiry);
…
GET
DEPARTMENT KEY(DEPARTMENT.DEPTNO
OR DEPARTMENT.MGRNO
OR DEPARTMENT.ADMRDEPT) SAVESUM OJSPG3.JZ-DEPARTMENT.Checksum;
PROCESS
EMPLOYEE WHERE(EMPLOYEE.WORKDEPT
= DEPARTMENT.DEPTNO) SAVESUM OJSPG3.JZ-EMPLOYEE.Checksum;
END
PROCESS EMPLOYEE
RESPOND OJSPG3;
END
GET DEPARTMENT
RESPOND OJSPG3;
…
2.
New
functions Child and PChild are included.
WHEN
(Child);
* Create a new EMPLOYEE record
GET
EMPLOYEE FREEKEY
CREATE;
ACCEPT
(IJSPG3.JZ-DEPARTMENT.DEPTNO,IJSPG3.JZ-EMPLOYEE.*) EXCEPT(EMPLOYEE.EMPNO) MESSAGE OJSPG3.Error;
EMPLOYEE.WORKDEPT
= DEPARTMENT.DEPTNO;
GET
DEPARTMENT KEY(DEPARTMENT.DEPTNO);
END
GET DEPARTMENT
RESPOND OJSPG3;
END
GET EMPLOYEE CREATE RESPOND OJSPG3;
WHEN
(PChild);
* Process a selected child record.
* Generated logic is only a basic update:
change this for your situation, e.g. "Process Order".
ACCEPT
(EMPLOYEE.EMPNO=IJSPG3.EMPNO) MESSAGE OJSPG3.Error;
GET
EMPLOYEE
KEY(EMPLOYEE.EMPNO) UPDATE CHECKSUM IJSPG3.JZ-EMPLOYEE.Checksum;
ACCEPT
(IJSPG3.JZ-DEPARTMENT.DEPTNO,IJSPG3.JZ-EMPLOYEE.*) EXCEPT(EMPLOYEE.EMPNO) MESSAGE OJSPG3.Error;
EMPLOYEE.WORKDEPT
= DEPARTMENT.DEPTNO;
GET
DEPARTMENT KEY(DEPARTMENT.DEPTNO);
END
GET DEPARTMENT
RESPOND OJSPG3;
END
GET EMPLOYEE UPDATE RESPOND OJSPG3;
If either of these functions are used the logic will
start by creating or reading an Employee record, and then it will directly read
the corresponding Department record. The
initial Enquiry might have read one of several records and you could scroll
forward and back through these, but after a Child or PChild function parent
scrolling will be lost until the next Enquiry.
We click [Process] to convert this to COBOL.
·
If
MANASYS is configured to work directly with ZOS, a job will be submitted to ZOS
that will compile the COBOL, and generate the JSON and Binding files.
·
If
MANASYS is configured to work with Micro Focus, the COBOL is written directly
into the relevant folder of the COBOL project.
We click the button to the right of [Process] and control is passed to
the MF COBOL project where we compile it, and create the JSON and binding
object as previously described.
Once
the JSON has been created (and for ZOS users, copied back to the Jazz Workbench
environment) the service can be tested with general test software such as
Postman or ReadyAPI. Here is a ReadyAPI
test of program JSPG3: -
This
test shows that the service is working, and when invoked with ADMRDEPT = “A00”
it will return the first of 5 qualifying DEPARTMENT record, plus the first 10
of 30 records of employees working in this department.
Creating
a client interface, JSPG3Client.cs, is as described previously for program
JSPG2. With program JSPG3 displayed in
the Jazz workbench, click [Client].
To
create a client to invoke this, I’ve created JSPG3Test.cs starting by creating
this C# Form, copying and renaming controls from previous forms (see Creating Similar Tests) and hooking up the
initialization: -
The
top part of this form is very like JSPG2Test except that it reads and updates
Department instead of Employee. The
bottom part of this form is also like JSPG2Test, showing Employee fields and
controls. The controls are a little bit
different, and note that it has its own scrolling controls. At this stage we have a form, but without any
code-behind logic to make the buttons do anything, or the textboxes and labels
to display any data.
Next
I wired up the textboxes to provide get and set methods, and wired up all the
buttons. In the top (Department) part of
the form, the scrolling buttons are wired up to the Scroll methods and Enquiry,
Update, Add, Delete, Clear, and Close are wired up as in JSPG2, except for the
obvious differences in object names.
In
the bottom (Employee) section the scrolling buttons are wired up to a set of
ChildScroll commands (ReadNextChild, ReadLastChild, ReadPrevChild, and
ReadFirstChild), [Add Employee] is wired up to Child, [Process Employee] to
PChild, and [Clear Employee] to logic that clears the Employee fields except
for WorkDept. WorkDept is a special
field: it defines the joining relationship, and the Jazz code
PROCESS EMPLOYEE
WHERE(EMPLOYEE.WORKDEPT =
DEPARTMENT.DEPTNO)
…
ensures that
any Employee handled by JSPG3 MUST have a WORKDEPT value = DEPNO. When we created
the service the option “Xcpt Join” omitted the joining fields from
Employee, so Workdept is not in the response message. In test JSPG3Test I created a textbox for
Workdept, but I wired it up to DEPTNO, and made it read only.
Here
is a test. I have just clicked [Clear]
to clear the whole form, then set ADMRDEPT to a00 and clicked [Enquiry]
Scrolling
through the 5 Department records is, like JSPG2, relatively slow, as each Next
or Previous requires a request/response.
The response returns the first 10 employee records, and JSPG3Test
displays the first. Scrolling through
the Employee records behaves like program JSPG2A: there is a slow response when
we first ask for the 11th record, or the 21st, but when
Next or Previous requires a record that has already been read, the response is
instantaneous. Whenever we scroll the
Department Records, the employee list is cleared and reset.
Clicking
various action buttons will Update, Delete, or Add Department records, while
clicking [Add Employee] or [Process Employee] invokes the Child or PChild
methods to add or process the Employee records.
As noted above, using either of these two buttons will fix the
Department record so you need to click [Enquiry] to resume Department
scrolling.
For
complete details of the JSPG3 test, see Programs
used as Examples
Program |
Function |
Comment |
JSPG1 |
DB2
Enquiry |
Generated
web service, JSON, Uses Database Sample, Table Employee, max = 6. |
JSPG1V |
VSAM
Enquiry |
Like
JSPG1, but uses VSAM file Custf |
JSPG2 |
DB2
Update |
Generated
like JSPG1 except Update, max = 1 |
DB2
Update |
Like
JSPG2 except max = 6 |
|
JSTIME |
Returns
Server’s Time |
A
very basic program, with no file or database I/O |
DB2
Update |
Parent/Child
Update (Department/Employee) |
You can click the links to
see the MANASYS test programs used. They
will open in Notepad, copy them to a .jzz file if you have MANASYS Jazz
available.
Files will open with Notepad
JSPG2A |
|||
JSPG3 |
Programs JSPG2 and JSPG2A have been generated to perform the same task, but JSPG2 was defined with Max=1 and JSPG2A with Max=6. The process of defining the interface JSPG2AClient and the test form JSPG2ATest is exactly as described above for JSPG2, and both tests behaved identically except for scrolling: for JSPG2A, ReadNext ([>] button) was instant (< 1 millisecond) except for every 6th record when a Request/Response was required. Each Request/Response took approximately 3 seconds with my test environment, and I couldn’t find any statistically-significant difference between request/responses from JSPG2 and JSPG2A even though JSPG2A’s messages were larger. While returning large output messages than necessary seems wasteful, there is also hidden waste like having to read-and-skip unwanted records to get to the one that you want, so my conclusion is that you should set Max to as large a value as you might conceivably need.
My use of Max=6 is NOT a balance between excessively large messages and other overheads. 6 was chosen because my priority was developing code that would silently handle the situation where Max was not large enough to get all the records. For a real situation with data like mine I’d have set Max=14. Similarly, with program JSPG3 I set the number of child records to 10, even though I had ensured that for DEPTNO=A00 there were 30 EMPLOYEE records. In a real situation I would have set the number to 30.
When Max > 1 then the interface saves the first n records in a list. Thus the first WorkDept=D11 Enquiry saved the first 6 records. On reading the 7th record the next 6 records are added to this list, and so on until the last record (known from BrowseCount) is read. Backward scrolling ([<] and [<<]) is always instant, while forward scrolling ([>]) is instant except for every 6th record. Scroll to end ([>>]) might use several request/responses.
The client’s record list is maintained until there is another [Enquiry]. If a record is updated then the updated record replaces the original record in the list. Added records are added to the list and BrowseCount incremented. Deleted records are deleted from the list and Browsecount reduced, but the deleted record remains displayed until the next scrolling command or [Enquiry].
In the following appendices you’ll see significant differences in JSPG2Client/JSPG2AClient logic, but none in JSPG2Test/JSPG2ATest.
I measured response times by adding code like this to the interface. If relevant, you should do the same to check out your situation: -
JSPG2Client and JSPG2AClient.Scroll.
private bool Scroll(int Increment, string PSkip = null,string PWORKDEPT = null)
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
…
stopwatch.Stop();
Debug.Print("Scroll Stopwatch: ms=" + stopwatch.ElapsedMilliseconds.ToString());
return DoRead(PSkip, "", PWORKDEPT);
}
JSPG2Client
and JSPG2AClient.DoRead. It was
necessary to change the generated statement
return = RequestResponse();
to bool Result = RequestResponse();
so that the Stop time could be reported.
public bool DoRead(string PSkip = null,string PEMPNO = null,string PWORKDEPT = null)
{
…
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
bool Result = RequestResponse();
stopwatch.Stop();
Debug.Print("RequestResponse Stopwatch: ms=" + stopwatch.ElapsedMilliseconds.ToString());
return Result;
}
Browsecount is calculated differently for SQL and for VSAM. For SQL it is always accurate, because the
SQL logic starts with SELECT COUNT(*), e.g.
005440 SELECT COUNT(*) INTO :OJSPG1.JZ-Employee-BrowseCount FROMJSPG1
005450 EMPLOYEE WHERE WORKDEPT LIKE :JZLIKE JSPG1
There
is no equivalent of COUNT(*) with VSAM. Since the point of
BrowseCount is to control scrolling, and for this purpose it is sufficient if
it is greater than the number read unless all the browsed records have been
read, the value of BrowseCount for VSAM is : -
If
the browse list has been read to the end, BrowseCount is accurate.
If
the browse list has not been read completely, BrowseCount is 1 greater than the
number of records that have been read.
Example: Program JSPG1V is like program JSPG1, but
instead of reading data from DB2 table EMPLOYEE it reads a VSAM file
CustF. JSPG1V was generated with Max=6,
so: -
Enter
Name = Barnes and click [Read]. The
first 6 records are read, and BrowseCount = 7: -
Scrolling forward [>] is
instant until the 7th record is reached when the next 6 records are
read, and BrowseCount becomes 13.
Last record, [>>], read
another 6 records, and so might need to be clicked several times to really get
to the end. With my test data this
browse returns 23 records, so three [>>] clicks are needed to get to the
end.
This logic was designed for VSAM to avoid the need to read all the VSAM records with every browse enquiry just to calculate BrowseCount accurately.
This is the message definition for program JSPG2A. Note that it contains three main parts, but also refers to other definitions through COPY statements. To deduce the full data rules, you need to follow the Assign relationships, for example to understand that IJSPG2A.EMPNO must be numeric in the range 1-999999 you need to look up EMPLOYEE.EMPNO. This definition gives Jazz all the information that it needs to create the interface.
*#
Last Updated by JAZZUSR at 14/11/2021 2:43:27 PM
*#
You may edit this definition: right-click the 'WEBSERVICE' keyword of the
PROGRAM statement.
COPY EMPLOYEE;
COPY TYPES;
DEFINE MyJSv-JSPG2A
SYSTEM DATA([Not in COBOL
INPUT VARCHAR(7) VALUE 'IJSPG2A',
OUTPUT VARCHAR(7) VALUE 'OJSPG2A',
MType CHAR(4) VALUE 'JSON',
Template CHAR(8) VALUE '@JSF1Upd',
URL VARCHAR(42) VALUE 'http://localhost:9003/cics/services/JSPG2A');
DEFINE IJSPG2A
SERVICE INPUT DATA(
Function CHAR(1) CAPS CODES(E:Enquiry,U:Update,A:Add,D:Delete) VALUE Enquiry,
JZ-EMPLOYEE-Skip
SMALLINT VALUE 0 RANGE(0:999),
JZ-EMPLOYEE
GROUP,
EMPNO CHAR(6) ASSIGN EMPLOYEE.EMPNO,
FIRSTNME
CHAR(12) ASSIGN EMPLOYEE.FIRSTNME,
MIDINIT
CHAR(1) ASSIGN EMPLOYEE.MIDINIT,
LASTNAME
CHAR(15) ASSIGN EMPLOYEE.LASTNAME,
WORKDEPT
CHAR(3) ASSIGN EMPLOYEE.WORKDEPT,
PHONENO
CHAR(4) ASSIGN EMPLOYEE.PHONENO,
HIREDATE
CHAR(9) ASSIGN EMPLOYEE.HIREDATE,
JOB CHAR(8) ASSIGN EMPLOYEE.JOB,
EDLEVEL
CHAR(7) ASSIGN EMPLOYEE.EDLEVEL,
SEX CHAR(1) ASSIGN EMPLOYEE.SEX,
BIRTHDATE
CHAR(9) ASSIGN EMPLOYEE.BIRTHDATE,
SALARY
CHAR(15) ASSIGN EMPLOYEE.SALARY,
BONUS CHAR(15) ASSIGN EMPLOYEE.BONUS,
COMM CHAR(15) ASSIGN EMPLOYEE.COMM,
CURRENCY
CHAR(3) ASSIGN EMPLOYEE.CURRENCY,
DEPTMGR
CHAR(5) ASSIGN EMPLOYEE.DEPTMGR,
Checksum
CHAR(40),
END GROUP);
DEFINE OJSPG2A
SERVICE OUTPUT DATA(
Error VARCHAR(80),
JZ-EMPLOYEE-ReadTo
SMALLINT VALUE 0,
JZ-EMPLOYEE-NbrReturned
SMALLINT VALUE 0,
JZ-EMPLOYEE-BrowseCount
SMALLINT VALUE 0,
JZ-EMPLOYEE
(6) GROUP,
JZ-EMPLOYEE-ReturnCode
LIKE Types.ReturnCode,
EMPNO LIKE EMPLOYEE.EMPNO
ASSIGN EMPLOYEE.EMPNO,
FIRSTNME
LIKE EMPLOYEE.FIRSTNME
ASSIGN EMPLOYEE.FIRSTNME,
MIDINIT
LIKE EMPLOYEE.MIDINIT
ASSIGN EMPLOYEE.MIDINIT,
LASTNAME
LIKE EMPLOYEE.LASTNAME
ASSIGN EMPLOYEE.LASTNAME,
WORKDEPT
LIKE EMPLOYEE.WORKDEPT
ASSIGN EMPLOYEE.WORKDEPT,
PHONENO
LIKE EMPLOYEE.PHONENO
ASSIGN EMPLOYEE.PHONENO,
HIREDATE
LIKE EMPLOYEE.HIREDATE
ASSIGN EMPLOYEE.HIREDATE,
JOB LIKE EMPLOYEE.JOB
ASSIGN EMPLOYEE.JOB,
EDLEVEL
LIKE EMPLOYEE.EDLEVEL
ASSIGN EMPLOYEE.EDLEVEL,
SEX LIKE EMPLOYEE.SEX
ASSIGN EMPLOYEE.SEX,
BIRTHDATE
LIKE EMPLOYEE.BIRTHDATE
ASSIGN EMPLOYEE.BIRTHDATE,
SALARY
LIKE EMPLOYEE.SALARY
ASSIGN EMPLOYEE.SALARY,
BONUS LIKE EMPLOYEE.BONUS
ASSIGN EMPLOYEE.BONUS,
COMM LIKE EMPLOYEE.COMM
ASSIGN EMPLOYEE.COMM,
CURRENCY
LIKE EMPLOYEE.CURRENCY
ASSIGN EMPLOYEE.CURRENCY,
DEPTMGR
LIKE EMPLOYEE.DEPTMGR
ASSIGN EMPLOYEE.DEPTMGR,
Checksum
CHAR(40),
END GROUP);
Note that the output record is
defined with a dimension, even for program JSPG1. This value corresponds to the Max value given
for Employee when the service was generated,
so for JSPG2A it is 6. For JSPG2 this
would be 1.
Here is the definition of the
EMPLOYEE record, referenced in LIKE and ASSIGN
properties: -
*#
Last Updated by JAZZUSR at 31/08/2020 1:33:28 PM
*#
Created from Table:EMPLOYEE, Schema:ROBERTBW10, Database:Sample by JazzUser at
26/03/2019 4:25:46 PM
COPY Department;
DEFINE EMPLOYEE SQL DATA(
EMPNO CHAR(6) PIC '999999' REQUIRED KEY,
FIRSTNME VARCHAR(12) REQUIRED,
MIDINIT CHAR (1),
LASTNAME VARCHAR(15) REQUIRED,
WORKDEPT CHAR(3) CAPS DKEY 'XEMP2' EXISTS DEPARTMENT.DEPTNO,
PHONENO CHAR(4) PIC '9999',
HIREDATE DATE,
JOB CHAR(8),
EDLEVEL SMALLINT REQUIRED,
SEX CHAR(1) CAPS CODES(M:Male, F:Female),
BIRTHDATE DATE,
SALARY MONEY(9,2) MIN 0,
BONUS MONEY(9,2) MIN 0,
COMM MONEY(9,2) MIN 0,
CURRENCY CHAR(3),
DEPTMGR BOOLEAN);
A Jazz container definition like
that above has a standardized appearance.
It contains three DEFINE statements, and it may reference other definitions directly or
indirectly through COPY statements. The first
definition,
DEFINE MyJSv-JSPG2 SYSTEM DATA([Not in COBOL
is not generated into the COBOL (because it has type SYSTEM) but it gives vital information to the [Client] generation, including
the names of the input and output messages.
The name of this definition is always Service-Program. The input message will be named by prefixing
the program name with “I”, and the output message will use prefix “O”.
Most of the field names in the input
and output messages are user-defined. In
the messages above, most of the fields have come from the EMPLOYEE record, and
use the same names as there. However
there are a few other fields with particular names. Most have the form JZ-xxxx-Field, e.g. JZ-Employee-Skip
which is a field used to control browsing. Jazz will not permit you to name your own
fields starting with JZ. There are a few
fields that do not follow this form: do not use these as your own field names in
web services: -
FUNCTION This is an input field defined by Jazz
logic.
ERROR An output field that may
contain an error message
CheckSum Both Input and Output: this
is used to manage pseudo-locking ensuring update integrity.
If the message doesn’t contain ERROR and ReturnCode
(here with the form JZ-xxxx-ReturnCode) , they will be generated into the
client anyway as they are used by the client to report local validation
issues.
We wrote a web service
program in MANASYS Jazz, and when we clicked [Process] this was turned into
COBOL. The COBOL program will contain
data definitions based on the Jazz-format definitions above. For example, this is the request message,
IJSPG2: -
003360 01 IJSPG2. JSPG2
003370 03 JZ-Function PIC X VALUE 'E'. JSPG2
003380 03 JZ-Employee-Skip PIC S9(4) COMP-5 VALUE 0. JSPG2
003390 03 JZ-Employee. JSPG2
003400 05 EMPNO PIC X(6) VALUE SPACES. JSPG2
003410 05 FIRSTNME PIC X(12) VALUE SPACES. JSPG2
003420 05 MIDINIT PIC X VALUE SPACES. JSPG2
003430 05 LASTNAME PIC X(15) VALUE SPACES. JSPG2
003440 05 WORKDEPT PIC XXX VALUE SPACES. JSPG2
003450 05 PHONENO PIC XXXX VALUE SPACES. JSPG2
003460 05 HIREDATE PIC X(9) VALUE SPACES. JSPG2
003470 05 JOB PIC X(8) VALUE SPACES. JSPG2
003480 05 EDLEVEL PIC X(7) VALUE SPACES. JSPG2
003490 05 SEX PIC X VALUE SPACES. JSPG2
003500 05 BIRTHDATE PIC X(9) VALUE SPACES. JSPG2
003510 05 SALARY PIC X(15) VALUE SPACES. JSPG2
003520 05 BONUS PIC X(15) VALUE SPACES. JSPG2
003530 05 COMM PIC X(15) VALUE SPACES. JSPG2
003540 05 JZ-CURRENCY PIC XXX VALUE SPACES. JSPG2
003550 05 DEPTMGR PIC XXXXX VALUE SPACES. JSPG2
003560 05 Checksum PIC X(40) VALUE SPACES. JSPG2
To get the request message
the COBOL program contains
005700 EXEC CICS
JSPG2
005710 GET CONTAINER(JZInputContainer) INTO(IJSPG2) JSPG2
005720 RESP(JZ-RESPONSE) JSPG2
005730 END-EXEC.
JSPG2
Similarly, there will be a
COBOL definition of the response, which will be sent back to the client with
021250 EXEC CICS
JSPG2
021260 PUT CONTAINER(JZContainerName) FROM(OJSPG2) JSPG2
021270 RESP(JZ-RESPONSE) JSPG2
021280 END-EXEC.
JSPG2
But the client is not
COBOL! The messages must be converted to
a standard form that can be understood, whatever the language the client
uses. For this to happen, after program JSPG2
was compiled a “binding object” is produced to convert the messages between
COBOL-format and JSON, the most popular standard form, and JSON descriptions of
the request and response messages were produced.
With Jazz configured for
z/OS, this all happens on the mainframe as a result of JCL steps that are
automatically inserted into the job that compiles the COBOL. With Jazz configured to work with Micro Focus
Enterprise Developer, this is done through the
dialog described here.
This
is the JSON generated from the COBOL for the request message
{
"$schema" : "http:\/\/json-schema.org\/draft-04\/schema#",
"title" : "JSPG2",
"description" : "Micro Focus Enterprise Developer
- This generated JSON schema document is provided 'as is' and without any
warranties of any kind, and may be used by licensee solely for the purposes of
describing a REST API. Micro Focus shall not be responsible for and hereby
disclaims any liabilities that may result from licensee's use of or
modifications to this generated JSON schema document.",
"type" : "object",
"properties" :
{
"JSPG2" :
{
"type" : "object",
"properties" :
{
"IJSPG2" :
{
"type" : "object",
"properties" :
{
"JZ_Function" :
{
"type" : "string",
"maxLength" : 1
},
"JZ_Employee_Skip" :
{
"type" : "integer"
},
"JZ_Employee" :
{
"type" : "object",
"properties" :
{
"EMPNO" :
{
"type" : "string",
"maxLength" : 6
},
"FIRSTNME" :
{
"type" : "string",
"maxLength" : 12
},
"MIDINIT" :
{
"type" : "string",
"maxLength" : 1
},
"LASTNAME" :
{
"type" : "string",
"maxLength" : 15
},
"WORKDEPT" :
{
"type" : "string",
"maxLength" : 3
},
"PHONENO" :
{
"type" : "string",
"maxLength" : 4
},
"HIREDATE" :
{
"type" : "string",
"maxLength" : 9
},
"JOB" :
{
"type" : "string",
"maxLength" : 8
},
"EDLEVEL" :
{
"type" : "string",
"maxLength" : 7
},
"SEX" :
{
"type" : "string",
"maxLength" : 1
},
"BIRTHDATE" :
{
"type" : "string",
"maxLength" : 9
},
"SALARY" :
{
"type" : "string",
"maxLength" : 15
},
"BONUS" :
{
"type" : "string",
"maxLength" : 15
},
"COMM" :
{
"type" : "string",
"maxLength" : 15
},
"JZ_CURRENCY" :
{
"type" : "string",
"maxLength" : 3
},
"DEPTMGR" :
{
"type" : "string",
"maxLength" : 5
},
"Checksum" :
{
"type" : "string",
"maxLength" : 40
}
}
}
}
}
}
}
}
}
Just
as we use COBOL definitions within the web server program JSPG2, within the C#
program JSPG2Client we convert the JSON into a hierarchy of C# classes. Here is the request definition, as it will be
used within JSPG2Client. Except for
naming, there is no difference between this and RequestJSPG2A.cs
namespace MyJSv
{
public class RequestJSPG2
{
public class JSPG2_
{
public class IJSPG2_
{
public string JZ_Function { get; set; }
public int JZ_Employee_Skip { get; set; }
public class JZ_Employee_
{
public string EMPNO { get; set; }
public string FIRSTNME { get; set; }
public string MIDINIT { get; set; }
public string LASTNAME { get; set; }
public string WORKDEPT { get; set; }
public string PHONENO { get; set; }
public string HIREDATE { get; set; }
public string JOB { get; set; }
public string EDLEVEL { get; set; }
public string SEX { get; set; }
public string BIRTHDATE { get; set; }
public string SALARY { get; set; }
public string BONUS { get; set; }
public string COMM { get; set; }
public string JZ_CURRENCY { get; set; }
public string Checksum { get; set; }
}
public JZ_Employee_ JZ_Employee { get; } = new JZ_Employee_();
}
public IJSPG2_ IJSPG2 { get; } = new IJSPG2_();
}
public JSPG2_ JSPG2 { get; } = new JSPG2_();
}
}
Note the way in which the lower-level classes are made
addressable by naming the original class with a _, e.g. JSPG2_ and then following this class
with
public JSPG2_ JSPG2 { get; } = new JSPG2_();
From
public class IJSPG2_ this corresponds directly to the JSON schema. The outer two layers are added to ensure that
the structure confirms to the JSON that is received, otherwise the JSON would
needs to be text-edited on input and output before being
serialised/de-serialised. Statements
can now be generated into JSPG2Client reflecting this structure, for example
Request.JSPG2.IJSPG2.JZ_Employee.EMPNO
= _EMPNO;
Like
RequestJSPG2.CS, this maps the hierarchical structure of the response, and has
a couple of levels at the top to reflect the JSON structure: -
namespace MyJSv
{
public class ResponseJSPG2
{
public class JSPG2Response_
{
public class OJSPG2_
{
public class JZ_Error_
{
public int JZL_Error { get; set; }
public string JZD_Error { get; set; }
}
public JZ_Error_ JZ_Error { get; } = new JZ_Error_();
public int JZ_Employee_ReadTo { get; set; }
public int JZ_Employee_NbrReturned { get; set; }
public int JZ_Employee_BrowseCount { get; set; }
public class JZ_Employee_
{
public string JZ_Employee_ReturnCode { get; set; }
public string EMPNO { get; set; }
public class FIRSTNME_
{
public int JZL_FIRSTNME { get; set; }
public string JZD_FIRSTNME { get; set; }
}
public FIRSTNME_ FIRSTNME { get; } = new FIRSTNME_();
public string MIDINIT { get; set; }
public class LASTNAME_
{
public int JZL_LASTNAME { get; set; }
public string JZD_LASTNAME { get; set; }
}
public LASTNAME_ LASTNAME { get; } = new LASTNAME_();
public string WORKDEPT { get; set; }
public string PHONENO { get; set; }
public int HIREDATE { get; set; }
public string JOB { get; set; }
public int EDLEVEL { get; set; }
public string SEX { get; set; }
public int BIRTHDATE { get; set; }
public float SALARY { get; set; }
public float BONUS { get; set; }
public float COMM { get; set; }
public string JZ_CURRENCY { get; set; }
public string DEPTMGR { get; set; }
public string Checksum { get; set; }
}
public JZ_Employee_ JZ_Employee { get; } = new JZ_Employee_();
}
public OJSPG2_ OJSPG2 { get; } = new OJSPG2_();
}
public JSPG2Response_ JSPG2Response { get; } = new JSPG2Response_();
}
}
Wheras
the requests for JSPG2 and JSPG2A are the same, there is a significant
difference between their requests. There
is nothing in the JSPG2 structure allowing JZ_Employee to repeat, and
references like this in JSPG2Client don’t have a subscript.
_EMPNO =
Response.JSPG2Response.OJSPG2.JZ_Employee.EMPNO;
The
equivalent in JSPG2AClient is generated as
_EMPNO =
Response.JSPG2AResponse.OJSPG2A.JZ_Employee[IXT1Sub].EMPNO;
The
definition of JZ_Employee is generated as below to support this: -
public
List<JZ_Employee_>JZ_Employee { get; set;} = new List<JZ_Employee_>();
We
started with a web service, written in MANASYS Jazz and converted to COBOL and
JSON objects which were compiled on a “Mainframe”, represented in the right
section of this diagram, repeated from above.
Clicking [Client] and then [Create Interface] created the middle
section, the interface.
The
interface is generated as C#, using .NET Core, and so should run in a wide
variety of environments: Windows, UNIX, Linux, IOS, Android, … The actual
client apps may be written in Java, C#, VB, or other technologies. This section explains the structure and
function of the interface, which is based on Template
@JazzClient.cs. You can see
examples of generated interfaces at https://www.jazzsoftware.co.nz/Docs/JSPG2Client.cs.txt
and https://www.jazzsoftware.co.nz/Docs/JSPG2AClient.cs.txt.
It
contains a number of sections
//
Private - objects that are not known outside the Client object
Here you find things that are NOT communicated to the Client
Program.
·
The
actual Request and Response messages,
private RequestJSPG2 Request = new RequestJSPG2(); // Actual request message
private ResponseJSPG2 Response = new ResponseJSPG2(); // Actual response message
·
Changed. A struct, SetByUser, contains a list of all
the properties that can be set by the client.
It is instantiated with
private SetByUser Changed =
new SetByUser();
The
interface keeps track of which fields have been changed since a response, so
that an Update can send only changed fields.
·
Private
fields like Function and Checksum which are managed within the interface.
// Key Properties - fields used by
methods to access records
Here you find properties that are used by methods to specify
which records are to be handled, in this case EMPNO, WORKDEPT, and Skip.
·
Program
JSPG2 updates the Employee table, which has been defined with primary key EMPNO
·
WORKDEPT
is a duplicate key
·
Because
WORKDEPT may specify several Employee records, Skip also appears here to
control scrolling.
Property EMPNO shows the typical structure of a
property. There is a private variable
_xxx, e.g. “_EMPNO” that the client can’t see, and a public property “EMPNO”
that it can. The value of the private
variable is available with get, and its value is set through code like that below
private string _EMPNO = null;
public string EMPNO // => Request.EMPNO
{
get => _EMPNO;
set
{
Changed.EMPNO = true;
_EMPNO = null;
if (value == "")
{
_ERROR =
"Value
required";
_JZ_Employee_ReturnCode = "E";
return;
}
_EMPNO =
IsInt(value, 0, 999999, "EMPNO");
Request.JSPG2.IJSPG2.JZ_Employee.EMPNO = _EMPNO;
}
}
The set code starts by setting the appropriate Changed flag, and
then checks that the value entered is valid.
For required fields like EMPNO validation starts by checking that a
value has been given. See PHONENO below to see how optional fields are handled.
This is followed by validation that depends on the field’s definition – EMPNO
must be an integer, in the range 0 to 999999.
Finally, the value is assigned to the request message.
//
I/O Data - non-key fields, will be set in Add, Update requests
Like Key fields, these communicate with the Client through get and set methods like that for
EMPNO. PHONENO is an example of an
optional field: unlike EMPNO which was required, a PHONENO value can be absent,
or “null”. For SQL optional fields
“null” (lower case) will cause the database field to be set to null.
Note that “null” cannot be assigned as the value of a string
field, although “Null” and other variations with at least one upper case
character are treated as normal string values.
Thus
_PHONE = "null";
will pass "null" back in the input message, but this value is recognised by
the web service as a special value, and instead of assigning 'null' to EMPLOYEE.PHONENO, the field will be set to
NULL, meaning that it has no value (not even “NULL”).
private string _PHONENO = null;
public string PHONENO // => Request.PHONENO
{
get => _PHONENO;
set
{
Changed.PHONENO = true;
_PHONENO = null;
if (value == "" || value == "n" || value == "nu" || value == "nul")
return;
if (value == "null")
_PHONENO
= "null";
else
_PHONENO
= IsInt(value, 0, 9999, "PHONENO");
Request.JSPG2.IJSPG2.JZ_Employee.PHONENO = _PHONENO;
}
}
From
the Jazz Message Definitions you can
see that there are several fields like ERROR in the output message that have no
equivalent in the input message. These provide only a get method, so the Client can’t change them.
private string _ERROR = null;
public string _ERROR // Readonly
{
get => _ERROR;
}
If
displayed label controls will usually be used.
Within the properties you’ll see a few output properties
named xxxx_Value immediately following the xxxx property. _Value properties
exist when
1. A property relates to a field
defined to MANASYS with CODES.
2. A property relates to a field
that has a meaning defined by the system.
Example 1. The SEX
property is followed by the SEX_Value property: -
public string SEX_Value // => Request.SEX_Value
{
get
{
if (_SEX == "M")
return "Male";
if (_SEX == "F")
return "Female";
return "";
}
}
This
is because SEX is defined with CODES: -
SEX CHAR(1) CAPS CODES(M:Male, F:Female),
Example 2. The output
field JZ_Employee_ReturnCode is followed by JZ_Employee_ReturnCode_Value. There is also an example, StatusCode_Value in
the Standard Response Properties, which returns HttpCode as a string, “OK” instead of 200.
This
section contains the routines required by set
, such as IsInt which is used to check that an input value is an integer,
InList to check that a value is in a list (like SEX), and so on. IsInt is typical, it starts by initializing
ERROR and return code properties, then it checks Value to see if it is
valid. If not, the ERROR will be set to
an appropriate message and the return code set to “E”.
private string IsInt(string Value, int MinVal, int maxval, string FieldName)
{
_ERROR = "";
_JZ_Employee_ReturnCode = "";
if (Value == "") return "0";
int IX = 0;
if (!int.TryParse(Value, out IX))
{
_ERROR =
FieldName + " Value is Not Numeric";
_JZ_Employee_ReturnCode
= "E";
}
else if (IX < MinVal || IX > maxval)
{
_ERROR =
FieldName + " Value out of range";
_JZ_Employee_ReturnCode = "E";
}
return Value;
}
All
html messages have some information in their headers, and so the interface
provides several output-only properties returning this: -
public int StatusCode // returns HttpStatusCode as a number
public string StatusCode_Value // returns HttpStatusCode as a string, e.g. 200 => “OK”
public string IsSuccessStatusCode// True if the request is successful
public string ReasonPhrase // returns the reason for failure
(or not).
public string RawResponse // blank except when unsuccessful
request
A
standard method that is used by all methods except [Clear] and [Close]. The request message is converted to JSON and
sent, the response received and converted back, and then the response values
are assigned to the output properties.
Because JSPG2 is controlled by Function, which is defined
Function CHAR(1) CAPS CODES(E:Enquiry,U:Update,A:Add,D:Delete) VALUE Enquiry,
there
will be Enquiry, Update, Add, and Delete methods.
Because
scrolling is possible, logic will be based on the @ReadScroll.cs template, and
so scrolling methods ReadNext, ReadLast, ReadPrev, and ReadFirst will be
generated into JSPG2Client.
Methods
get their input values from parameters, not properties, allowing them to check
that the values that they are given are valid (for example, EMPNO hasn’t
changed for an Update). Typically they
check their arguments and then invoke RequestResponse, either directly or
through another method (e.g. Scroll and DoRead).
Update
will put changed values into the request, but ignore any that haven’t been
changed. JSPG2Client will have saved
CHECKSUM from the previous Enquiry, this is returned in the request and will be
checked by the service, and the update rejected if it differs from the
recalculated value.
So
will Add, but JSPG2Test ensures that all textboxes except the primary key
(EMPNO) and Skip are marked as changed.
A blank EMPNO is passed in the message, so the service will find the
next free key. Browsecount will be
incremented.
Delete
only needs the primary key to delete a record, and like Update will return the
Enquiry’s Checksum to be checked.
Result =
RequestResponse(false);
is used so that
the deleted record remains displayed in the response. Browsecount will be reduced.
For detail, see https://www.jazzsoftware.co.nz/Docs/JSPG2Client.cs.txt
JSPG2AClient differs from JSPG2Client in only one way: it returns several EMPLOYEE records at a time. This has had minor effects on the Jazz and C# data definitions, but it has a major effect on the code generated for JSPG2AClient, particularly the Enquiry, Update, Add, and Delete methods.
For an enquiry by WorkDept 6 records are read and the first returned to the client (JSPG2ATest), but all 6 are saved in a list. Assignment statements need to specify a subscript, initially 0, to assign data from the first record to the properties returned to the client. Scrolling within the 6 records is very quick, because it’s just a subscript change and doesn’t require a request/response.
If scrolling goes beyond the first 6, the next 6 are read and added to the list.
The list is updated to reflect Update, Delete, and Add, so that it reflects the new database. For Delete and Add Browsecount will change.
For detail, see https://www.jazzsoftware.co.nz/Docs/JSPG2AClient.cs.txt.
The
last section of the interface contains routines that are used by methods.
ResetChanged
resets all the SetByUser flags, marking all input/output fields as
unchanged.
AssignResponseToProperties
assigns data from the response message to the interface properties, whether
private (like CheckSum), Output (like Error), or Input/Output.
ClearRequest
sets all request fields to null, preventing data carry-over
AddPrivateFieldsToRequest
adds the fields to the request that are not shared with the client. Currently this is only CheckSum.
MANASYS
Jazz Version 16.1.230 was the first build to incorporate client interface
generation, although only as a proof-of-concept. The feature was tested with a web service
program JSPG2 which updates table EMPLOYEE, from IBM’s Sample DB2
database. I added two extra fields,
Currency to prove that column names that are COBOL reserved words can be
handled, and DeptMgr to prove that Boolean fields can be handled across the
COBOL and Client worlds.
Build 16.2.239 added: -
·
Enquiry-only
web services.
·
Web
services that return multiple records – e.g. several EMPLOYEE records – in a
message. Timing experiments showed that
there was no measurable time difference in returning a single-record or
multi-record web service, and scrolling through a list of records was instant
compared to ~3 seconds per record for each request/response.
·
Add
and Delete methods as well as Read and Update.
Add and Delete functions are optional.
·
Update
methods may have Max > 1, for update as well as enquiry only
·
VSAM
records as well as DB2.
Build 16.2.240: -
·
Related
Fields. When a web service is generated
from a definition including fields using EXISTS, like
WORKDEPT CHAR(3) CAPS DKEY 'XEMP2' EXISTS DEPARTMENT.DEPTNO,
the
generation dialog offers the option of selecting fields from DEPARTMENT. If (e.g.) DEPARTMENT.DEPTNAME
is selected then this is included in the output message as an output-only
field.
Build 16.3.244: -
·
Client
Interfaces and web services handling parent/child record hierarchies
Build 17.1.296: -
·
TIME data was implemented in Jazz,
with appropriate display formats, validation, and TIME arithmetic. This applies for COBOL web services, but to
clients TIME data is
simply an INTEGER, and there is no special
client-side validation or display format.
Build 17.1.306: -
·
TIME data is displayed with an
appropriate format, and correctly validated (hh is 0-23, mm is 00-59, ss is
00-59, and mmm is 000-999)
We
are looking for early adopters to join us on this exciting journey: not only
will they get some immediate benefits from the features that MANASYS Jazz
already offers, they will be very influential in setting our development
priorities.
Possible
development (in no particular order, will be selected based on user interest
and support): -
·
Support
conversational web services. An
implementation working like ASP.NET “Viewstate” has been defined that would
allow data to be handed from one request/response to the next. Like ASP’s Viewstate the data will be
encrypted so that even if seen by the client it won’t be able to be understood
or changed.
·
Variable
Function Codes. Currently update
programs must use E(Enquiry), U(Update), A(Add), and D(Delete). A configuration option could be provided
allowing users to use R(Read) or I(Inquiry) instead of E(Enquiry), and so
on. Or letters/words in languages other
than English.
·
Check
that all the data types in IBM’s Sample Database that are supported by Jazz*
can be handled
* Jazz doesn’t support BLOB, CLOB, and
National data.
MANASYS
Jazz doesn’t yet handle interface definition for SQL records with multi-part
keys (e.g. PROJACT).
·
Support
VSAM groups
and arrays (dimensions), and redefinition
·
Handle
browsing by multiple fields: Compound key, also two or more alternate indexes.
·
Support
Max=*, where the number is unknown until run time.
·
More
flexible record sequences. Messages could pass records with a record-format
indicator, allowing much more flexible record sequences than at present.
·
Allow
local validation logic to be defined, for example checking that a credit card
number has the correct format including check digit, allowing users to
duplicate the function of CHECKR properties in the service.
·
Client-side
reporting. MANASYS Jazz can generate
report programs that run on the mainframe to produce a report, but even with
the Report Designer these output reports in a single font. It should be possible to have a web service
produce a file that is sent to the client from which a report could be produced
with variable fonts and colours, as when software like Crystal Reports is used. The ideal would be to provide dialogs that
with data-aware interfaces allowing the input data to be selected, and dragged
to the report, much like the Report Designer but with variable fonts.
·
Sample
Clients. The design and this document
anticipate that MANASYS could generate a client in C#, VB, or Java, for Windows
or a Web page. It is not expected that
this client will be any more than an initial prototype, like the sample
JSPG2Test Windows Form. It will provide
useful code snippets that can be used in the client that the user actually
develops, but it is not currently possible for MANASYS Jazz to know what the
user actually wants. For example, the client may need to access other web
services, local databases, or API’s like GPS location data. We have considered providing options that,
when you generate an interface, you can check one or more of the Client
options: -
This
could generate a sample client with code like that described above, in the
option(s) that you choose. This will
need to be validated with each client type, e.g. C# Windows Form, C# Web
Program, Visual Basic, Java … and so on.
We
could provide sample clients intended to provide initial code for users to
start with, but we expect that they will need significant editing to meet
actual requirements, and we’re not sure if this is worth doing. We have no way of knowing what you actually
want the client to do, which might access local data and other API’s (for
example, GPS location), interact with other web services, and be part of a
larger conversation involving authentication and data passing from step to
step.