This
feature was released in build 16.1.238 for initial testing.
Build 16.2 introduced
client support for
Add and Delete functions (Update was
supported with 16.1)
Messages with multiple records (Max
> 1)
Services returning data from VSAM
(previously only DB2)
Related Fields: .
This is
a work-in-progress
See Road
Map for its current status, and 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?
Getting
Started – Locate or Create a Suitable Project
Client
Projects with Multiple Forms
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 was 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.
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 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);
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. 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 had added CURRENCY and DEPTMGR to IBM’s definition of the EMPLOYEE table. CURRENCY was added to test that MANASYS Jazz could handle COBOL reserved words as SQL column names, and 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.
With Employee.jzc properly defined, the dialog New/Logic/Web service, with values like this, creates program JSPG2 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 JSPG2. Here is a copy of program JSPG2 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 this, 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 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 18 lines of code, 9 of which we edited. From this we generated 54 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 2500 lines of COBOL, 320 lines of JSON conforming to the Swagger standard 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 for 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 (not necessary for
C#). Close the Configure form.
Interface generation uses a number of C# templates, with
names @xxxx.cs. If you have installed
MANASYS Jazz before the Client Interface feature was developed then these
objects will not have been included in your previous downloads. If so, or you have been advised by your
MANASYS support that there is an updated version, you should ensure that you
have the latest version.
With WebAPI defined, return to the Workbench tab, and click
[Initialize Project]. Select all the
@xxxx.cs objects. The current list is:
-
@BoolValue.cs
@DateValue.cs
@Delete.cs
@InList.cs
@IsBool.cs
@IsDate.cs
@IsFloat.cs
@IsInt.cs
@IsNoLongerThan.cs
@JazzClient.cs
@Method.cs
@ReadNoScroll.cs
@ReadScroll.cs
@RequestResponse.cs
@Update.cs
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.
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 the Binding file (a program which converts between COBOL ó JSON Layouts). The JSON layouts should have been copied back
to your local (Windows) environment.
·
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
is 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 for future development, which will allow interfaces to be
created for externally-developed web services.
Click
[Create Interface]. Jazz will create a
C# interface object called xxxxClient.cs, e.g. JSPG2Client.cs, and if you have checked [ ] Web or [ ] Windows it 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.
Planned, not yet available.
If
errors are detected then messages are reported as Jazz Error messages,
following the Program statement: -
When [Create Interface] is clicked, Jazz first locates the
JSON descriptions of the web service.
When you created program JSPG2 (including the steps following Jazz, when
the COBOL program was compiled and the support objects (“Binding file”) were
created), 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
the three .json files are: -
·
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) IJSPG2 and OJSPG2,
which may not exist yet.
·
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
automatically copied back to your project file.
·
JSPG2O.json This describes the Output (Response)
message. Like JSPG2 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.
Jazz then creates the message definitions: -
RequestJSPG2.cs Created
from the input message, IJSPG2, this is the actual structure of the request
message.
ResponseJSPG2.cs Created from the output message, OJSPG2, 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 a 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 become “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.
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 when they are processed.
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’ve selected a list of fields to be sent in the response and you change
this list, or if you add or remove a method (Update, Add, Delete, etc) compared
to the previous message, then you’ll need to regenerate the interface.
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. However client-side
arrays are more like lists than a repeating set of identical structures that a
COBOL programmer understands as an array. 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.
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.
To
use 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. This client might
be developed in C#, Java, VB, 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: TestJSPG2 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.
It is planned that MANASYS will
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. 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. (Optional Step). 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
Now we want to add controls to the form: buttons to invoke
interface methods, labels and textboxes to display data. Before we can do any of this, we have to add
a reference so that we can refer to the interface JSPG2Client in project MyJSv.
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.
6.
Now
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
static JSPG2Client Jspg2Client = new JSPG2Client();
where shown in
the code below. Visual Studio adds
using MyJSv;
at the top,
because it is now necessary.
7. We also add
bool EditMode = true;
The reason for this will be clear
shortly.
The
C# code for our form now looks like this: -
using MyJSv;
using System.Windows.Forms;
namespace MyJSvClientTests
{
public partial class JSPG2Test : Form
{
static JSPG2Client Jspg2Client = new JSPG2Client();
bool EditMode = true;
public JSPG2Test()
{
InitializeComponent();
}
}
}
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’ve 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.
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. 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;
}
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;
ShowResponse();
}
}
We
should write similar code for all of the textboxes.
Double-click a button like [Enquiry] 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.
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) meaning that it will 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 box 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: -
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 only sends the changed data to the
service except for the record key (EMPNO).
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)
{
// Validate all textboxes (except
1ry Key and Skip), ensuring that they will be included in following Add because
Changed will have been set
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();
}
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.
It is planned that when you generate an
interface you can check one or more of the Client options: -
This
will generate a sample client with code like that described above, in the
option(s) that you choose. Initial
validation of this will be with a C# Windows Form, then C# Web Program, then
Visual Basic, then Java. Further sample
client options will be provided if required.
These
sample clients are intended to provide initial code for users to start with,
but we expect that they will need significant editing to meet actual
requirements. As well as dealing with
the web service, the client program may access local data, interact with other
web services, and be part of a larger conversation involving authentication and
data passing from step to step.
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 |
JSPG2A |
DB2
Update |
Like
JSPG2 except max = 6 |
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 production situation with data like mine I’d have set Max=14.
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 require 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. Logic in … and the last statement are different.
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, the BrowseCount is accurate.
If
the browse list has not been read completely, the 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 JSPG2. 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 IJSPG2.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 6/12/2020 1:01:08 PM
*#
You may edit this definition: right-click the 'WEBSERVICE' keyword of the
PROGRAM statement.
COPY Employee;
COPY TYPES;
DEFINE MyJSv-JSPG2 SYSTEM DATA([Not in COBOL
INPUT VARCHAR(6) VALUE 'IJSPG2',
OUTPUT VARCHAR(6) VALUE 'OJSPG2',
MType CHAR(4) VALUE 'JSON',
Template CHAR(8) VALUE '@JSF1Upd',
URL VARCHAR(41) VALUE 'http://localhost:9003/cics/services/JSPG2');
DEFINE IJSPG2 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 OJSPG2 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
(1) 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 though it has extent = 1. This value corresponds to the Max value given
for Employee when the service was generated.
For JSPG2A this would be 6.
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);
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_();
}
}
Unlike
Request, there is a significant difference for JSPG2A. 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;
To
support this the definition of JZ_Employee is generated as
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, and cannot be assigned as the
value of a string field.
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 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
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, for an Update EMPNO
hasn’t changed). 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 the 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 3.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
updating a single DB2 table: program JSPG2 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 app 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 added: -
·
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.DEPTNO
is selected then this is included in the output message as an output-only field.
Not handled yet: Parent/child record
sets, VSAM records containing repeating fields, repeating groups, or
redefinitions.
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.
Planned
development (in no particular order): -
·
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.
·
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.
·
Support
multiple-record services (Parent/Child, and more complex record relationships)
·
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.
·
Generate
prototype Clients. The design and this
document anticipates that MANASYS will 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.
·
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.