|
Calling a Program Using the iSeries Toolbox for Java
by Kevin Vandever
More and more, those who develop outside the iSeries are required to access business rules written
on the iSeries. They've always understood that data exists on that weird black box but
like to dismiss the business rules. You see, accessing legacy applications doesn't excite non-
iSeries programmers. It's not sexy. But it makes good business sense, and makes for good code
reuse, too. The good news is IBM's iSeries Toolbox for Java provides tools to access a variety
of iSeries services, like calling programs. And using the Program Call Markup Language, an XML
extension, simplifies and standardizes the process, and may even make it a little sexy.
What Is the Toolbox?
The iSeries Toolbox for Java is a set of JAR files that contain
the necessary Java classes used to access iSeries resources. You
can think of JAR files much like you do Zip files, because that's
what they really are. JAR files are used to store the classes,
images, and whatever else is needed in a compressed format for
downloading and installation ease. You can download the Java toolbox,
JTOPEN, containing iSeries- specific classes, from IBM's
toolbox site. Or, if you have the Java Toolkit (5722-JV1)
installed on your iSeries, you can run everything from your iSeries.
Other options you have are to install the WebSphere Development
Studio (5722-WDS) tools on your PC (which comes with the AS/400
Java toolkit as well as IBM's version of the Java SDK), or you
can copy the iSeries' 5722-JV1 JAR files to your PC from the iSeries
Integrated File System. Regardless of how you get the Java SDK
and the iSeries-specific Java classes there, once installed, you
point your CLASSPATH to where the toolbox is installed so that
the Java compiler and the Java runtime commands can find them.
More about that later. Then you can use the classes contained
within the toolbox as you would any other Java class, as they
are 100% Pure Java.
Calling iSeries Programs
In this article, I am going to show you how to call a simple
RPG program, but everything you learn here can be used to call
any iSeries program--even COBOL. There are two ways to directly
call an iSeries program using the Java toolbox. The first involves
creating an array of parameter objects inside your Java application
and properly converting the data from Java format to the correct
iSeries format. It works just fine and was the only method used
to call iSeries programs up until V4R2. Then there came a new
sheriff to town, Program Call Markup Language. PCML is an extension
of XML. PCML handles the program call and parameter definitions
for you. Although each technique is still used, PCML is becoming
the standard method for calling iSeries programs, and it is the
method I am going to explain in this article. Who knows, there
may be a future Midrange Programmer article written to
explain the original method used to call iSeries programs (that's
called a tease, in the business world).
PCML Basics
As I mentioned, PCML is an extension of XML, which has become
the standard for transporting data across disparate as well as
similar platforms. PCML allows you to remove the definition of
the program and its parameters from the Java code and place them
inside an easily readable, tag-based document that is then generated
into PCML class objects. These PCML class objects handle the calling
of the program, the retrieval of the data from the program, and
the conversion from iSeries data to Java objects--all of which
are tasks you had to code inside your Java program before the
introduction of PCML. For my example, I am going to call an RPG
program with four parameters. I am going to send the program two
numbers to add, and it will return the sum of those numbers and
a comment, just to show that you that you can use different data
types. The following PCML will be used to call the RPG program:
<pcml version="1.0">
<program name="program" path="/QSYS.lib/YOUR_LIBRARY.lib/PROGRAM.pgm">
<data name="parameter1" type="zoned"
length="6" precision="0"
usage="input"/>
<data name="parameter2" type="packed"
length="9" precision="2"
usage="input"/>
<data name="sum" type="packed" length="9"
precision="2"
usage="output"/>
<data name="comment" type="char" length="20"
usage="output"/>
</program>
</pcml>
The first line defines the version of PCML that is used. That
line is required. As for the rest, PCML is made up of three tags:
the program tag, the struct tag, and the data tag. Notice the
PCML has a program tag and data tags but no struct tags. That
is because the struct tag is used to pass or retrieve array typed
data. We don't have that in this program. Each of these tags can
be further expanded by attributes. For instance, the program tag
can be expanded using the name, path, parseorder, and threadsafe
attributes. There are two other attributes, but they can be used
only when calling service programs, which I will discuss in a
future article (another tease).
PCML Tags
In my example, I am only interested in the name attribute, which
defines the program name--in this case PROGRAM--and the path attribute,
which tells the PCML where the program resides on the iSeries.
The parseorder attribute allows you to tell which output parameter
to process first. The default is to process in the order they
are entered in the PCML document. This might come in handy when
one parameter returns information about a previous parameter.
You may then want to change the processing order. The threadsafe
attribute is used when you are calling an iSeries program from
a Java application on the same server. In that case, you may want
to call the iSeries program in the same job and on the same thread
as the Java program. This may result in better performance
but will only work if the iSeries program is threadsafe. Again,
you don't need to use the parseorder or threadsafe attributes
in this example, but I thought I would explain them anyway.
Now it's time to define the parameters. This is done using the
data tag and its attributes. The data tag can be further expanded
by the following attributes:
| Type |
Type of data |
| Bidistringtype |
Bidirectional string type for
character variables (the default is blank, which will use
the CCSID |
| CCSID |
The host's coded character set
ID |
| Count |
Specifies that the element is
an array, and this specifies the number of entries |
| Init |
Specifies an initial value for
the data element |
| Length |
Specifies the length of the
data element |
| Maxvrm |
Specifies the highest version
of OS/400 where this element exists |
| Minvrm |
Specifies the lowest version
of OS/400 where this element exists |
| Name |
Specifies the name of the data
element |
| Offset |
Specifies the offset to the
data element within an output parameter |
| Offsetfrom |
Specifies a base location from
which the offset value is derived |
| Outputsize |
Specifies the number of bytes
to reserve for output data for this element |
| Passby |
Passed by reference or by value
(valid only with service program calls) |
| Precision |
Specifies the number of bytes
for numeric data |
| Struct |
Different from the struct tag,
this attribute is used to name a structure element for this
data element |
| Usage |
Specifies whether the parameter
is input, output, inputoutput, or inherit |
As you can see, there are a number of attributes that can be
used with the data tag. In my example, I use name, type, precision,
length, and usage. The good news--for iSeries programmers, anyway--is
that you define these parameters using iSeries data types, such
as packed and zoned. Notice after each data tag is defined, the
line is ended with an end tag (/>). This signifies the end of
a tag. I didn't code the end tag symbol on the program tag line,
because all the data tags are for this particular program and
are therefore included with the program tag; so, I won't code
the end program tag until after I define all the parameters.
OK, so most of the data attribute stuff is straightforward,
right? I've defined a couple of packed fields, a zoned field,
and a character field just to show that I could. However, be cautious
when using the usage attribute. Usage really matters when employing
PCML. We iSeries programmers are accustomed to our parameters
being input/output, and you can certainly define them that way
in PCML, but you don't have to.
Notice that I defined the first two parameters as input. These
parameters are used only for input into the iSeries program, so
I only want input values created. I defined the last two parameters
as output because they are results passed back to my Java application
from the iSeries program, and therefore I only want output values
created for them. I could have defined them all as inputoutput,
but if you do that, both input and output values will be created
for these data elements even if you don't really need both. This
might impact performance, but it definitely creates objects that
aren't needed.
Now that all the data elements have been defined, it's time
to code the end tag (/>) for the program tag and then the PCML
tag. Once this document is created, save it with an extension
of .pcml and place it in the same directory where your java class
will exist. Let me repeat that: Place the PCML document in the
same directory as your Java class! One final note: If you save
it with the name ExamplePCML.pcml, you won't have to modify the
Java code included with this example.
Java and PCML
The complete Java source code for this example, as well as the
RPG and PCML source, can be downloaded from www.itjungle.com/downloads/mpo011702-story04.zip.
What I specifically want to show you, however, is the Java source that relates
to the toolbox and PCML. If you'd like to download the code and
have it next to you as you read on, I'll wait.... Now that you
have the code in front of you, take a look first at the first
four lines:
import com.ibm.as400.data.ProgramCallDocument;
import com.ibm.as400.data.PcmlException;
import com.ibm.as400.access.AS400;
import com.ibm.as400.access.AS400Message;
These lines allow you to import the necessary classes required
to connect to the iSeries, to use PCML, and to retrieve iSeries
messages and PCML exceptions. Once you have the necessary imports
defined, you'll next want to create a new AS/400 object, instantiate
that object, set your input parameters, and call the program.
The following code will accomplish those tasks:
AS400 sys = new AS400();
pcml = new ProgramCallDocument(sys, "ExamplePcml");
pcml.setValue("program.parameter1", new Integer("5"));
pcml.setValue("program.parameter2", new BigDecimal("5.25"));
rc = pcml.callProgram("program");
What I've done here is to create an AS/400 object called sys
and instantiated that object with new AS400(). By placing
nothing between the parentheses, I will be prompted for a system,
user ID, and password when the program is called. You can optionally
add parameters between the parentheses to fill in details about
the iSeries connection and therefore save the users from keying
it in every time they call the program. For example:
AS400 sys = new AS400("url of iSeries", "user id", "password");
Instantiating the AS/400 object in this manner would directly
connect without prompting. You can also leave any parameter out,
from right to left, to fit your needs. If you left the third parameter
blank, you would be prompted, but the system name or IP address
and user ID would be filled in, leaving only the password to be
entered.
Next you create a new PCML object and give it the AS/400 object
name, sys, and the PCML document name, ExamplePCML. Next you set
the PCML object input parameters using the setValue method from
the PCML object. Notice that the parameter names and program match
those in the PCML document. The last thing you do is call the
program, with the callProgram method, from the PCML object. Done,
right? Well, not exactly. When you get the results you are looking
for, you will have to know how to retrieve the values from the
program call. First, however, there are a couple of things you
can do just in case you get errors on the call.
The following code can be used to retrieve messages from the
iSeries if something went wrong with the call:
if(rc == false)
{
// Retrieve list of AS/400 messages
AS400Message[] msgs = pcml.getMessageList("program");
// Loop through all messages and write them to standard output
for (int m = 0; m < msgs.length; m++)
{
msgId = msgs[m].getID();
msgText = msgs[m].getText();
System.out.println(" " + msgId + " - " + msgText);
}
System.out.println("Call to PROGRAM failed. See messages listed above");
System.exit(0);
}
If you look back at the actual call, I used a return code of
rc to capture whether or not the program ran successfully. If
it didn't, rc will equal false and I will want to retrieve my
messages. I created an AS400Message array object, called msgs,
and instantiated it with the getMessageList method from the PCML
object. Notice my program is the only parameter for the method
call. Once the message list is retrieved, you can determine how
many messages there are with the length method in the msgs object
and loop through the messages, sending them to standard output.
Then I print a message to let the user know that the program failed
and to see the message just printed. In most cases, standard out
might be a log file, but for the purpose of my example, I just
spit them out to the screen.
If everything ran smoothly, the following code will retrieve
and present your results:
else
{
// Process the returned Data
sum = (BigDecimal) pcml.getValue("program.sum");
comment = (String) pcml.getValue("program.comment");
System.out.print("5" + " + " + "5.25" + " = " + sum);
System.out.print("\n");
System.out.print(comment);
System.exit(0);
}
Pretty simple stuff, huh? The two output parameters, sum and
comment, are defined as Java data types and instantiated with
the getValue method from the PCML object. The getValue method
is used for each output variable defined in the PCML document.
Another way to define the Java variables sum and comment would
have been to define them as objects with the other object definitions
above, and use them here without the Java types placed in parentheses.
It's up to you.
Compiling and Running
There are a couple of things to keep in mind before compiling
and running your application. If you are planning to run your
app in a Windows environment, you must first tell the operating
system, and possibly your development environment, how to find
your Java environment. I coded and compiled my Java application
using CODE/400 on Windows 2000. Before I could successfully do
this, I had to set my PATH environment variable to tell CODE/400
where to find my Java environment. If you use WebSphere Development
Tools, you can add the following to your PATH environment variable:
SET PATH=%PATH%;C:/WDT400/Studion35/bin
Your setup might be different, or you might not use CODE/400
as your development environment, so make sure to find where your
javac command is stored before modifying your PATH environment
variable. The next thing you'll want to do is set your CLASSPATH
environment variable to tell it where your toolbox JAR files reside.
In my case, jt400 and data400, which are the JAR files needed
to use PCML in the iSeries toolbox, reside in C:\WDT400\java.
So the entry in my CLASSPATH environment variable would be the
following:
SET CLASSPATH=%CLASSPATH%;C:\WDT400\java\jt400.jar;C:\WDT400\java\data400.jar
On the toolbox Web site mentioned earlier, there is a choice
to download toolbox documentation. I suggest that you do this.
It will not only tell you how to work with the toolbox but also
explain how to manually install the JAR files and tell you which
JAR files contain which classes for which iSeries services.
On the iSeries, the CLASSPATH is configured automatically when
you install the Java Development Kit (JDK) and the iSeries Toolbox
licensed programs. If you have other Java classes that you want
to access, or you've copied the toolbox or JDK to your iSeries,
you can modify the CLASSPATH either in the Qshell environment
or from a command line using the Work with Environment Variables
(WRKENVVAR) command, or in a CL program using the ADDENVVAR command.
That's All I Have to Say About That
I've covered a lot of material here, but I hope you will come
away with a basic understanding of how to call iSeries programs
from Java using PCML. Once you get your hands on the toolbox,
download my code and play around with it to see how it works.
In addition to the actual code, I've also tried to give you a
little information on how to install and implement the toolbox.
Getting the CLASSPATH and PATH environment variables correct may
seem a little confusing at first, but think of it as your library
list. You can't very well run an iSeries program unless you qualify
it or it exists in a library in your library list. The same holds
true for Java. The classes must either be qualified when you run
them with the Java command or be resident in the path-related
environment variables. Tune into future articles on how to access
the other iSeries services using the iSeries Toolbox for Java.
I know; I'm such a tease.
Kevin Vandever is a lead IS engineer for Boise Cascade Office
Products in Itasca, Illinois, and co- editor of Midrange Programmer,
OS/400 Edition. He can be reached at kvandever@itjungle.com.
|