Appendix A Using Java on the AS/400
Appendix A Using Java on the AS 400
Overview
This appendix is a brief tour of getting, installing, and using Java on OS/400. You might start your Java journey on your personal computer, or by adding a GUI to your existing OS/400 applications. However, OS/400 is committed to being a first-class Java platform based on its own merits, so you can run Java applications on OS/400 itself. Because Java is so portable, there are not too many special instructions for doing this, but the following additional information will give you a leg up.
Using Java on OS/400 involves at least a passing knowledge of the following:
- AS/400 Developer Kit for Java (the JDK)
- Integrated File System (IFS), where Java files live and run on the AS/400
- QShell Interpreter, the UNIX shell required for working with the JDK commands
- AS/400 Toolbox for Java, a set of pure Java classes for accessing OS/400 resources from OS/400 Java or client Java
- VisualAge for Java, or its follow-on; IBM's premier development product for Java
Each of these is covered in the following sections, except for VisualAge for Java, which at the time of this writing was on the brink of a major new generation. Further, if you decide to use Java servlets and JavaServer Pages, you need to read up on WebSphere Application Server and the IBM HTTP Server. As of V4R3, everything you need to run servlets and JSPs is built into OS/400.
You might also wish to explore Enterprise JavaBeans, available with the Advanced edition of WebSphere Application Server. This is not part of the base operating system, but an optional product. Beyond what has been covered in the rest of this book, we leave the topic of servlets, JSPs and Enterprise JavaBeans for your own research. You can start at www.ibm.com/iseries/websphere.
Installing the Components
Table A.1 shows the information for installing the various pieces, as of Version 4. Note that versions after V4 will have a different number than 5769, such as 5722 for Version 5.
Product |
Product Number |
Notes |
---|---|---|
AS/400 Developer Kit for Java |
5769JV1 |
You can choose from multiple JDKs to install and use. Each is a different option. |
Qshell Interpreter |
5769SS1, Option 30 |
|
AS/400 Toolbox for Java |
5769JC1 |
To find out if these products are installed, use GO LICPGM, and select F11 after finding the products in the list. They will show *COMPATIBLE in the Installed Status field. If they are not in this status, use option 1 to install them. If they are on the list, use RSTLICPGM to install them from the OS/400 CD-ROM.
The AS 400 Developer KIT for Java
As mentioned in Chapter 1, the JDK was originally written for Sun Solaris and Microsoft Windows operating systems by Sun Microsystems. IBM licenses the JDK, and the IBM Hursley, England laboratory ports it to all operating systems. The OS/400 team in Rochester, Minnesota then pushes it deep into the operating system to get great performance. Because the JDK is standard, and by the terms of the licensing agreement, IBM and others can only add to it, not take away from it, you will find that all the standard JDK commands are available on OS/400. These include java, javac, javap, javadoc, and jar, among others.
Of course, these are not the standard OS/400-style command names that you are used to. Furthermore, they have to work on Java source files and class files that are ASCII-based, not EBCDIC-based as your RPG source and programs are. This is because the goal is to be able to run any Java class file from anywhere without changing or even recompiling it. Enabling all this-that is, making OS/400 Java support as industry-standard as possible-requires that the JDK be based not in the library/file native file system, but rather on the Integrated File System.
Using the Integrated File System and QShell Interpreter
RPG development uses the native OS/400 file system, which involves libraries inside QSYS, files and other objects inside libraries, and members inside files. Files, and hence their members, are either data files (physical file or logical file) or source files (Source Physical Files). There are other file types, too, like display files and printer files. The underlying collating sequence is EBCDIC, which differs from the ASCII sequence used on most other operating systems (except for OS/390, which is also EBCDIC-based).
The Integrated File System (IFS) is an alternative to the native file system. It is actually multiple file systems in one, but the one you are most interested in is the "root." This is very similar to the file system on Windows. It has directories that can contain other directories and stream files, much like Windows. Stream files are simply flat files that contain a sequence of bytes, with no record partitioning and no relational database constructs-just like files on Windows.
The highest-level directory, the root, is designated by a single forward slash: /. Unlike Windows, the IFS uses a forward slash for path names, not a backward slash. So, if you have a file named
Prior to Java coming along in V4R2, you could only work with the IFS through OS/400 CL commands. For example, to create a directory, you would use the CRTDIR command, like this:
CRTDIR DIR('/Phil') CRTDIR DIR('/Phil/myJava')
You have to create the parent directory before creating the child directory. Each job has a "current directory" that can be determined by the CL command DSPCURDIR, and changed by CHGCURDIR or CHDDIR. To list files and work with them in a given directory, you would use the command WRKLNK. To see all the IFS CL commands, you would type GO FILESYS.
These CL commands are still of value. For example, the first thing you need to do is create a directory for your Java files. First, check to see if the administrator has created one for you in homeUserID, where UserID is your user ID. Use WRKLNK to look for this directory. If one doesn't exist, use CRTDIR to create it. Then, to always make it your default current directory, use CHGPRF (Change Profile) and the HOMEDIR parameter, like this:
CHGPRF HOMEDIR('/Phil/myJava')
Of course, you might need to get the system operator to do this for you if your profile lacks the necessary authority.
Once you have a directory, and have made it your current directory, you can use the new (as of V4R2) QShell Interpreter. To start it, use the CL command STRQSH or QSHELL. This puts you in a command shell where you can enter standard UNIX commands, instead of OS/400 CL-style commands. This will be more comfortable if you are familiar with UNIX. It is also where you enter the familiar JDK commands, as you'll soon see.
Table A.2 lists some of the common commands you might do on Windows, and their equivalent in IFS-both the CL version and the QShell version. This little table should be enough to get you going with IFS. More detailed information is available online at the iSeries information center.
Command |
Windows |
CL |
QShell |
---|---|---|---|
Show current directory name |
cd (no parameters) |
DSPCURDIR |
pwd ("present working directory") |
Change current directory name |
cd path or path |
CHGCURDIR |
cd path or /path |
Make a directory |
md or mkdir |
CRTDIR |
mkdir |
Delete a directory |
rd or rmdir |
RMVDIR |
rmdir |
Rename a directory |
rename |
||
Display or change directory owner |
Windows explorer |
chown |
|
Display or change directory authority |
attrib |
DSPAUT, CHGAUT |
chgrp, chmod |
List files in directory |
dir |
WRKLNK |
ls |
Display file contents |
type |
pr, cat, tail, head |
|
Copy a file |
copy |
COPY |
cp |
Move a file |
move |
MOVE |
mv |
Delete a file |
erase |
WRKLNK |
rm |
Rename a file |
rename |
RENAME |
mv |
Display or change file owner |
Windows explorer |
WRKOBJOWN |
chown |
Display or change file authority |
attrib |
DSPAUT, CHGAUT |
chgrp, chmod |
Create link |
ADDLNK |
ln |
|
Define an alias (shortcut) command |
alias |
||
Exit shell or prompt |
exit |
F3 |
|
Run CL command |
system |
||
Search files for a string |
grep |
||
Set environment variable |
set |
WRKENVVAR |
export |
Display commands previously entered |
history |
||
Compress/uncompress files |
WINZIP or jar |
tar/untar or jar |
|
Get online help for a command |
help |
man |
Working with Java on OS 400
Now that some of the basics are out of the way, it's time to focus on working with Java. To run a Java class on OS/400, you enter the QShell Interpreter, ensure you are in the directory containing the Java class file, and then run it using the java command. It's that simple. All the JDK commands are there and are identical to their Windows counterparts-well, actually their UNIX counterparts, but other than remembering to use a forward slash instead of a backslash for path names, it is the same.
So, the first order of business is to get a Java class to run. You have two choices for doing that: either create the .class file using javac on Windows (say) and copy it to the OS/400 IFS, or copy the .java source file from Windows (say) and use javac on OS/400 to create it. Note that in either case, development of Java is done on a workstation running Windows (say) and Java development tools like VisualAge for Java. You don't use the old workhorses PDM and SEU to create Java applications because these tools work on EBCDIC-based native file system objects, and Java is a new breed of ASCII-based IFS objects. If you really are addicted to minimalist green-screen tools, you could use SEU to edit a source member containing Java, and then use CPYTOSTMF (Copy To Stream File) to copy that member to an IFS file.
So, how do you get your .java or .class files from Windows to OS/400 IFS? A few options are listed in Table A.3.
Option |
Description |
---|---|
Use ftp (File Transfer Protocol) |
If your TCP/IP configuration allows this, using ftp from the Windows command line is an easy way to copy any file to OS/400. |
Use a mapped drive |
Using Windows Explorer, map a network drive to your system and simply use the Windows copy command to copy any file to OS/400 via the mapped drive. |
Use CODE/400 |
If you use CODE/400 anyway for your RPG, CL, and DDS programming (and you should!), it has support under File for saving source to an IFS file and support under Actions for exporting any file to the IFS. |
Use VisualAge for Java |
The Enterprise Toolkit for AS/400 has easy-to-use support for exporting your Java source or class files to OS/400 IFS, directly from the IDE. You only specify the target location once, and it remembers it every time. This is the easiest option! |
Pick whatever you like, or whatever is handy. The only one that you might need help with is ftp, so here is a sample script from a Windows command-prompt window as an example:
c:PhilmyJava>ftp MYSYSTEM User: coulthar Password: 230 COULTHAR logged on. ftp> cd /Phil/myJava 250 "/Phil/myJava" is current directory. ftp> binary 200 Representation type is binary IMAGE. ftp> put HelloWorld.class 467 bytes sent in 0.02 seconds ftp> ascii 200 Representation type is ASCII ftp> put HelloWorld.java 206 bytes sent in 0.03 seconds ftp> quit
The first cd into your IFS directory is important. When using ftp to the AS/400, it decides whether you are working with the IFS or the native file system based on the syntax of the first place you "cd" into. If the location starts with a forward slash, IFS is assumed; otherwise, QSYS is assumed. Also, it is important to switch into binary mode for class files and ASCII mode for source files. By the way, for multiple files, use the mput (multiple put) command, instead of put. There are also get and mget commands for going the other way.
Setting your classpath in QShell
Your Java files are now in the IFS and you know how to find your way to them and run them, using QShell. You're on our way. The last thing you really need to know is how to set your CLASSPATH so that Java can find your Java classes. By default, it only looks in the current directory, which is not very useful, since you'll probably have classes spread over multiple directories.
The first important note about the CLASSPATH environment variable on OS/400 is that it is based on UNIX, so it uses a colon to separate the entries, not a semicolon as in Windows, for example:
/Phil/myJava:/George/myJava
To set your CLASSPATH dynamically-that is, each time you run Java-use the -classpath parameter to the java command, like this:
java -classpath /Phil/myJava:/George/myJava HelloWorld
This is nice, but a pain to remember each time. To make it more permanent, you can set it once for your QShell session using the export command, much like the set command on Windows, for example:
export -s CLASSPATH=.:/Phil/myJava:/George/myJava
If you type this in your QShell session, that classpath remains in effect for the duration of the session. To see all current environment-variable settings, type export with no parameters. The -s option tells QShell to make this not only a variable for use by QShell scripts, but also for use by applications like the Java interpreter.
This, then, is a little better, but only marginally. To get a little more permanence, create a file named .profile ("dot profile") in your directory in the IFS and put the export command in it:
export -s CLASSPATH=.:/Phil/myJava:/George/myJava
Use the same options discussed for .java files to create your .profile file, and copy it to the appropriate IFS directory. This is an example where SEU and CPYTOSTMF might be just fine. The "dot" (.) included in the above example represents your current directory. We recommend always putting this on your CLASSPATH, even in Windows. For this .pro-file to work correctly, it must be in your home directory, as specified on the HOMEDIR of your user profile. By convention, your home directory should be /home/userID, where userID is your user profile name.
You will most often use the .profile option for your own personal Java exploration. However, eventually, you or your system operator might install a Java application for general use on your system, and the time will come to set the CLASSPATH globally for all users. To this, just place that .profile file in a special directory, specifically /etc/pro-file, where it will be applied to all users on the system.
Actually, this file probably already exists, so you will want to append to it, not create it. To do this, use CPYFRMSTMF to copy into a source member for editing by SEU. Alternatively, just use CODE/400, which has support for directly editing IFS source files. When running a Java program, the CLASSPATH specified in /etc/profile/.profile is checked first for Java classes, and the CLASSPATH specified in your home directory .profile is searched if the class is still not found. Thus, these two files are cumulative, not mutually exclusive.
Using CL commands for Java and the Java Transformer
If you could only run Java classes from QShell, you might be in trouble if you wanted to mix RPG and Java in the same application. For example, how could RPG call Java if there is no CL command to invoke via the command analyzer API? Well, an alternative to the java command in QShell is the RUNJVA (Run Java) CL command, or its identical twin the JAVA CL command. These commands allow you to run a Java class directly from the OS/400 command line, a CL program, or any of the traditional ways of running CL commands. Note the Java class files still exist in the IFS. These CL commands will run the QShell java command behind the scenes.
To use them, specify the class file on the CLSF parameter, using the usual fully qualified syntax for IFS files, for example:
RUNJVA CLSF('/Phil/HelloWorld')
The only tricky part is specifying the CLASSPATH in this situation. The answer is to use the CLASSPATH parameter to specify it dynamically, similar to the -classpath option on the java QShell command. If specifying dynamically doesn't cut it, specify it in the CLASSPATH environment variable using the ADDENVVAR or CHGENVVAR commands. The syntax for the path itself is the same as discussed for QShell.
Note that the CL CLASSPATH environment variable only pertains to you, and only lives as long as your job (much like the QTEMP library). This means you have to add the ADDENVVAR command to a CL program that is run when you sign on, if you want permanence. Also note that this CLASSPATH is only searched after the CLASSPATH in the export-s classpath statement in /etc/profile/.profile, as was the case for .profile in your home directory. And by the way, that profile will be run too, since RUNJVA will invoke QShell and hence this .profile file will be run if it is in your home directory. Really, then, the .profile file in your home directory is the best option for setting up your own persistent and unique classpaths.
When you use RUNJVA, it actually does a little bit more than the java command in QShell. It actually transforms your Java class into OS/400 machine code (PowerPC instructions) the first time you run the class. This is the work of the Java Transformer. On subsequent runs, that machine code is executed, instead of the bytecode. This truly compiled Java is much faster than interpreted Java. Behind the scenes, a new object is created-a *JVAPGM (Java Program) object, which is referred to as a direct execution (DE) object. The .class file remains untouched, so it can still be copied to another operating system and run there, which means portability is not affected. So, you get the best of both worlds: the performance benefits of compiled Java and the portability of interpreted Java. Note that if you subsequently change the .class file by replacing it with a new version, the *JVAPGM is invalidated and so is re-created when you next use RUNJVA.
You can explicitly compile a single class or an entire .jar file of classes in a separate step, if you prefer, using the CRTJVAPGM command. In both commands, you can specify the optimization level, anywhere from 10 to 40. The lower number is best for debugging, while the higher number offers the most optimization.
Two other commands to use with Java Program are DLTJVAPGM and CHGJVAPGM (for example, to change the optimization). Note that DLTJVAPGM only deletes the hidden *JVAPGM object, not the original class file. Finally, there is a DSPJVAPGM command that displays attributes about Java Program, such as its optimization level.
By the way, you can use the green-screen system debugger to debug a Java application. You might want to use interpret mode versus compile mode for this, in which case you can specify INTERPRET(*YES) on the RUNJVA command. To actually debug your Java program, use the RUNJVA command and specify OPTION(*DEBUG), which puts you in a source-level debug session. For this to work, your class must have been compiled with the -g option (javac -g MyJava.java), and the source must exist in the same directory as the class file.
Another option for debugging Java running on the AS/400 is the IBM Distributed Debugger that comes with CODE/400 and VisualAge for Java. It runs on Windows while debugging your interpreted or transformed Java program on the AS/400.
One last point on the Transformer: If you use the java command in QShell, it will use the direct execution object if it exists. Otherwise, it will interpret the class. Thus, many people first explicitly transform their class files using CRTJVAPGM, and then subsequently run them in QShell using java.
AS 400 Toolbox for Java
The Java Program that the Java Transformer creates is not an ILE service program because Java is not an ILE language. You cannot bind or link a Java Program into a non-Java ILE program or service program. Thus, to "get out" of Java, and access you data, other programs, data queues, message files, and so on, you have to rely on some unique-to-OS/400 Java code written by the IBM Rochester team. These classes are called the AS/400 Toolbox for Java.
You are going to love these! They are well-designed, well-written, and come free with the system (and with CODE/400, VisualAge for Java, and Inprise J/Builder). Furthermore, because they are written entirely in Java using only industry-standard TCP/IP sockets for their communication layer, they run everywhere. By extension, so does your code that uses them. This means you can write Java code to access your DB2/400 files or your RPG programs, and run that code anywhere. Run it on the same AS/400, a different AS/400, on Windows, on Linux, on UNIX, in a Web Browser, in a network station, in a personal device. Anywhere. This is the ultimate OS/400 middleware!
The downside (there is always one, isn't there?) is that while your code will run anywhere, it will always be trying to talk to an AS/400 somewhere in the world. So, if you are interested or worried about server portability, you will have to forgo the Toolbox, or carefully isolate its usage so that your move to OS/390, for example, is easy. The one exception to this rule is the JDBC driver inside the Toolbox, as described in Chapter 13. It is the one portable set of classes in the Toolbox, as JDBC is industry standard, not unique to OS/400. To switch databases, you need only change the code to register the driver and connect to the database (assuming your SQL statements are standard).
At the time of this writing, there were more than 500 classes in this product, and there are more to come. A brief description of the functionality that these classes provide follows. Refer to the IBM redbook Accessing the AS/400 System with JAVA (SG24-2152) for a well-done and more detailed introduction to these classes. Also see the documentation for the AS/400 Toolbox for Java, available at publib.boulder.ibm.com/html/as400/infocenter.html.
Table A.4 lists the functions offered by the AS/400 Toolbox for Java, in their likely order of importance for most Java applications.
Function |
Description |
---|---|
Data access via JDBC |
SQL access, as described in Chapter 13. |
Data access via record-level access |
Much like RPG's chain, setll, read, write, update, and delete statements. |
Command call |
Runs an OS/400 command in batch. Any messages sent by the command are returned to your Java application. |
Program call |
Runs any program object in batch. Parameters can be passed and updated. Any messages sent by the program are returned to Java. |
Data queues |
Your Java application can create, read, write, and delete data queues, both sequential and queued. It can also work with the attributes of a data queue. |
Data areas |
Your Java application can create, read, write, and delete data areas. It can also work with the attributes of a data area. |
Function |
Description |
User space |
Your Java application can create, read, write, and delete user spaces. It can also read and write the attributes of a user space. |
IFS file system |
Your Java application can access directories and files in the IFS. This is a superset (and super classes) of java.io in the JDK, tweaked specifically for local or remote IFS access. |
Network print |
Allows your Java application to work with spooled files, output queues, printers, printer files, writer jobs, and AFP resources. Yes, you can create reports, but it's pretty scary stuff, as you have to create SCS data streams. Reading a spool file is pretty cool, though! |
System values |
Your Java application can read and change system values and network attributes. |
Jobs |
Your Java application can retrieve lists of jobs (all, or by name, number, or user) and job log messages, and read the details about a particular job. |
Users and groups |
Your Java application can retrieve lists of users and groups. |
System status |
Your Java application can retrieve system status information. It can also access system pool information. |
Permissions |
Your Java application can retrieve and change OS/400 object authorities. |
Digital certificates |
Manages digital certificates, which are used for secure transactions over the Internet. For example, they are used by the Secure Socket Layer (SSL). |
ftp |
Allows you to connect, run ftp commands, and get/put files from Java. |
Java application call |
Allows you to remotely run Java classes on the AS/400. |
Service program call |
Allows you to call ILE service program procedures from Java. |
Security |
Support for the SSL and user authentication. |
HTML classes |
Classes can be used and run from a servlet or JSP, and generate common HTML tags that can be tedious to code by hand. |
Servlet classes |
These classes are specifically for use in servlets, for generating HTML output from the other Toolbox classes. |
Proxy classes |
This thin client-side proxy of the full Toolbox allows Toolbox classes to be used locally, yet actually executed remotely. |
System properties |
For configuring properties that affect certain Toolbox functions. |
These major functions are offered through a myriad of classes. To use these classes, numerous other helper classes are supplied that are quite useful in their own right. They are listed in Table A.5. All of these classes are in a package named com.ibm.as400.access. (Notice that the JDBC classes do not require or use these explicitly. They are used under the covers.)
Helper Classes |
Description |
---|---|
AS/400 object |
All service classes except JDBC require one of these as input into their constructor. This class manages OS/400 logins, optionally prompting the user for ID and password (on GUI clients). |
AS/400 data types |
These classes help you easily translate OS/400 values to and from appropriate Java data types. |
AS/400 messages |
This class represents an OS/400 message returned from a program or command call. |
AS/400 record format information |
These classes represent DB2/400 field definitions, record formats, and actual data records. They are used when doing record-level data access, and can be used when working with program calls and data queues. |
AS/400 QSYS object path name |
This represents objects in the IFS. Because the QSYS file system can be accessed through the IFS, you can use IFS objects to get at your library-based objects, for example: /qsys.lib/mylibr.lib/myfile.fil The helper class QSYSObjectPathName makes it easy to convert native file system object names to IFS-style path names, which all the Toolbox classes expect. Specifically, the static method toPath takes library, object, and object type names and returns a string of the form above. |
Exceptions |
The Toolbox has a rich set of its own exception objects. |
Trace |
Using the Trace class, you can enable logging to help with problem determination. There are five levels of tracing you can enable: information, warning, error, diagnostic, and data stream. |
The AS/400 Toolbox for Java also contains a number of visual classes, which are Java Swing GUI components that use these base non-visual classes. The visual classes are found in the package com.ibm.as400.vaccess, and they are designed to allow you to easily embed them inside your own Swing GUI applications, saving you some time you would otherwise spend writing them yourself. They all leverage the concept of reusable panels, as discussed in Chapter 12. The GUI classes have to run on a client, so regardless of your OS/400 level, you can always use the latest level of the Toolbox when running Java code on a client. The only thing to beware of is that some newer functions might not apply to a back level of OS/400.
The following are the GUI functions available as of V5R1:
- Function
- Command call
- Data queues
- Error events
- IFS
- JDBC
- Record-level access
- Jobs
- Messages
- Network print
- Permissions
- Program call
- System status
- System values
- Users and groups
- Java application call
We do not describe these classes here because you will probably not be writing Java GUIs. (Instead, we believe you will probably be writing HTML UIs via servlets and JSPs.) We leave them to your own exploration.
In addition to all this, the Toolbox comes with some other functionality:
- Program Call Markup Language (PCML), an XML language that makes it is easy to code calls to *PGM objects from Java, and even procedures inside *SRVPGM objects from Java. This allows you to define the target program or procedure and its parameters via a simple tag-based language. This is interpreted and turned into the appropriate ProgramCall and ServiceProgramCall classes.
- Panel Definition Markup Language (PDML), an XML language for defining GUIs that is arguably easier to write than Swing classes, although in the end that is how it is rendered.
- GUI Builder, a tool for visually defining PDML windows and panels, generating the PDML tags for you. Also included is a conversion tool to convert Windows resource files into PDML.
Getting the AS 400 Toolbox for Java classes
To use the AS/400 Toolbox for Java, you must first get it. If you have V4R2 or later, it is on your system CD-ROM or tape. To install it, use GO LICPGM, select option 11, and install 5769 JC1 (for a Version 4 operating system). This places the files jt400.jar and jt400.zip in your IFS, in the directory /QIBM/ProdData/HTTP/Public/jt400/lib. You don't need both of these files; either one will do. We prefer the .jar file because it is smaller.
There is also a file jt400doc.zip, which contains the English JavaDoc detailed help for the Toolbox classes. (The jt400mri.zip file has the other languages). To see the JavaDoc help, download this file to your workstation and unzip it. You can use the jar tool in the JDK to unzip a file, as in jar -xvf jt400doc.zip. This gives you many HTML files, so start with the one named Index.htm in the doc_en subdirectory. If you prefer to get the very latest version of the Toolbox, go to www.ibm.com/iseries/toolbox. The Toolbox is an open-source product, meaning you can even get the source code for it! Alternatively, you can simply get the Toolbox as part of WebSphere Development Tools for iSeries, which also includes CODE/400, VisualAge for Java, VisualAge for RPG, WebSphere Studio, and WebFacing.
If you are going to use the Toolbox classes from Java code running on a client, and you don't have it as part of one of the products listed above, simply copy the jt400.jar file from the IFS to your workstation. Use any number of methods for this, such as ftp. Once on your workstation, add it to your CLASSPATH. You do not have to unzip this file.
If you are going to use the Toolbox classes for Java code running on the OS/400 itself, you will have to add the /QIBM/ProdData/HTTP/Public/ jt400.lib/jt400.jar file to your OS/400 CLASSPATH, using one of the methods described earlier in this appendix. As of V4R4, there is also a jt400access.zip file, which is a smaller version containing only the non-visual classes. You probably want to use that on OS/400. This file has already been optimized for you with the CRTJVAPGM command.
As of this writing, the Toolbox had just been transformed into an open-source product. This means that anyone can get its source and contribute to its functionality, as is the case for other popular open-source products like the Apache Web Server and the Linux operating system. It also means that in order to get the very latest version of the Toolbox, you have to register at the Toolbox Web site for the open-source version and download it. This product is known as JTOpen.
Most of the additional functionality described above, such as PCML, is not part of the base jt400.jar file. Therefore, you will need to get JTOpen to get the additional necessary .jar files needed to compile code that uses this functionality. After installing JTOpen, you will find all the .jar files in the lib subdirectory.
Finally, the Toolbox requires the QUSER user ID, so ensure its password has not expired. Also, be sure to start all the host servers (STRHOSTSVR *ALL) and the TCP/IP DDM server (STRTCPSVR SERVER(*DDM)). By the way, ensure you have the TCP/IP Connectivity Utilities for AS/400 installed (5769-TC1).
Using the AS 400 Toolbox for Java classes
The following discussion is independent of the JDBC classes, described in Chapter 13.
To write code that uses the AS/400 Toolbox, import the package com.ibm.as400.access. Your first job is to create an instance of the AS400 class. This class will manage a connection to your local or remote AS/400 and is required as a parameter into the constructor of all the other primary Toolbox classes. The AS400 class can be instantiated with no parameters, or you can specify the AS/400 host name, user ID, and/or password. If you run your Java class on the AS/400 itself, and don't specify any parameters, the object will implicitly use localhost for the system and *CURRENT for the user ID and password. When running on a client, however, the user will be prompted for any of the missing three values:
AS400 systemHome = new AS400(); AS400 systemNY = new AS400("NYC","NYCID,"NYCPWD");
For a Secure Socket Layer (SSL) connection, use the SecureAS400 subclass of AS400 instead. Once you have an AS400 or SecureAS400 object, it can be used with any of the classes. All the primary classes, like CommandCall and ProgramCall, take an AS400 object as a parameter on their constructor. It is at this time, when running on the client, that the user is prompted for any missing information. It is also at this time that the actual connection is established. You can explicitly force the connection at any time by calling the method connectService. This takes one parameter identifying the type of connection you desire, which in the end tells the Toolbox which AS/400 server subsystem to connect with. The parameter value constants are shown in Table A.6.
Constant |
Service |
AS/400 Subsystem |
AS/400 Job |
---|---|---|---|
FILE |
Access Integrated File System |
QSERVER |
QPWFSERVSO |
COMMAND |
Run AS/400 commands or call AS/400 programs |
QSYSWRK |
QZRCSRVS |
|
Access AS/400 spool files |
QSYSWRK |
QNPSERVS |
DATAQUEUE |
Access AS/400 data queues or data areas |
QSYSWRK |
QZHQSSRV |
RECORDACCESS |
Access DB2/400 via record-level access |
QSYSWRK |
QRWRTSRVR |
It's your choice whether to have only a single AS400 object with multiple connections for different services, or a separate AS400 object for each service. When you are done with your connection, you must use the method disconnectAllServices to disconnect from OS/400. To end your application, you must use System.exit(0) to kill the daemon threads created by the Toolbox.
The following sections look briefly at the most popular Toolbox classes.
The CommandCall class
Assuming you have an AS400 object named (say) system400 already, you can use it to call one or more non-interactive commands on that AS/400. Just instantiate a CommandCall object with the AS400 object as a parameter, and then call the run method for each command to be run. This will return true if the command ran successfully. To get any AS/400 messages that resulted from the command, call the getMessageList method. It returns an array (possibly null) of AS400Message objects, which you can simply walk and call the getText method on. Listing A.1 shows an example.
Listing A.1: An AS/400 Command Call via the Toolbox
import com.ibm.as400.access.*; public class TestCommandCall { public static void main(String args[]) { AS400 system400 = new AS400(); CommandCall cmdObj = null; try { System.out.println("Connecting..."); system400.connectService(AS400.COMMAND); System.out.println("Creating CommandCall object..."); cmdObj = new CommandCall(system400); } catch (Exception exc) { System.out.println("Error connecting: " + exc.getMessage()); System.exit(1); } runCmd(cmdObj, "ADDLIBLE CUSTLIB"); runCmd(cmdObj, "ADDLIBLE CUSTLIB"); system400.disconnectAllServices(); System.exit(1); } public static boolean runCmd(CommandCall cmdObj, String cmdString) { boolean cmdOK = false; try { System.out.println("Calling command "+cmdString+"..."); cmdOK = cmdObj.run(cmdString); System.out.println("Command returned. Result = "+cmdOK); } catch (Exception exc) {} AS400Message msgs[] = cmdObj.getMessageList(); if (msgs != null) { for (int idx=0; idx < msgs.length; idx++) System.out.println("Message: " + msgs[idx].getText()); } System.out.printIn(); return cmdOK; } // end runCmd method }
The static method runCmd is created in this example to run any given command string, given a previously instantiated CommandCall object. The main method creates the AS400 object and explicitly does the appropriate connection, then calls the runCmd method twice with the same ADDLIBLE command, so you can see what happens when the command works and when it does not.
The result of running this command from Windows is that you are prompted for your system, user ID, and password values (at the time of the connectService method call), after which you get the following:
ListingA-1>java TestCommandCall Connecting... Creating CommandCall object... Calling command ADDLIBLE CUSTLIB... Command returned. Result = true Message: Library CUSTLIB added to library list. Calling command ADDLIBLE CUSTLIB... Command returned. Result = false Message: Library CUSTLIB already exists in library list.
The ProgramCall class
It's easy to call any *PGM object on OS/400, but only if the program does not take any parameters. We'll cover this scenario first, then show how to handle parameters.
Again, first instantiate an AS400 object and optionally explicitly connect to the COMMAND service. Then, instantiate a ProgramCall object, passing it the AS400 object, and call any AS/400 program using the ProgramCall method run. Specify as parameters to this method the name of the program to run (in IFS style syntax) and an empty array of type ProgramParameter. (You will see later that, to pass parameters, you simply pass a non-empty array for the second parameter.) Again, run returns true if the call went okay, and you can retrieve program-queue messages using the getMessageList method.
Listing A.2 shows an example of calling program PRINTRPT in library CUSTLIB.
Listing A.2: Calling an AS/400 Program without Parameters
import com.ibm.as400.access.*; public class PrintRpt { protected AS400 system400 = null; protected ProgramCall pgmObj = null; protected String pgmLib, pgmName, pgmIFSName; protected ProgramParameter[] parms= null; protected boolean pgmRanOk = false; public PrintRpt(AS400 system400) { this.system400 = system400; pgmName = "PRINTRPT"; pgmLib = "CUSTLIB"; pgmIFSName = QSYSObjectPathName.toPath(pgmLib,pgmName,"PGM"); pgmObj = new ProgramCall(system400); parms = setPgmParms(); // call our helper method } protected ProgramParameter[] setPgmParms() { parms = new ProgramParameter[0]; // empty array return parms; } public boolean callPgm() { try { System.out.println("Calling pgm "+pgmName+"..."); pgmRanOk = pgmObj.run(pgmIFSName, parms); System.out.println("Result = " + pgmRanOk); } catch (Exception exc) System.out.println("Exc: " + exc.getMessage()); } { AS400Message msgs[] = pgmObj.getMessageList(); if (msgs != null) for (int idx=0; idx < msgs.length; idx++) System.out.println("Message: "+msgs[idx].getText()); System.out.println(); return pgmRanOk; } // end callPgm method }
Listing A.2 does not show its main method, which exists only for testing purposes. Here it is:
public static void main(String args[]) { AS400 system400 = new AS400(); try { system400.connectService(AS400.COMMAND); } catch (Exception exc) { System.out.println("Error connecting: "+exc.getMessage()); System.exit(1); } PrintRpt printRpt = new PrintRpt(system400); printRpt.callPgm(); system400.disconnectAllServices(); System.exit(1); }
Notice how we designed this class. It has the same name as the program itself, and it contains a constructor that prepares the ProgramCall object and the parameter list (an array of ProgramParameter that has zero elements for no-parameter calls). Callers simply pass an AS400 object into the constructor. They then simply call the method callPgm, which actually calls the program, returning true or false to indicate its success. The main method shows how easy it is to use the class. Your team can simply use this class anytime they want to call this program, rather than having to write their own Toolbox code every time. It nicely encapsulates the program as a Java class.
The program called in this example does nothing. Its source is on the CD-ROM included with this book (
Running the Java program gives this result:
ListingA-2>java PrintRpt Calling pgm PRINTRPT... Result = true
If you have any trouble running this example or any of the following examples, be sure to use WRKACTJOB on the AS/400, prompting it to specify subsystem QSYSWRK. Look for jobs named QZRCSRVS, in status *MSGW. Use option 7 to look at the message, and you'll probably find the job is waiting on a response to an inquiry message, indicating a library list or authority error.
When you pass parameters, you enter the world of data conversion. Basically, the difference is that the ProgramParameter array will be populated with actual ProgramParame ter objects, one for every parameter the program expects. These objects require you to specify the length of the parameter in bytes, and the actual data for input parameters in the form of a byte array. The tricky part, then, is determining the length and producing the byte array. After you call the program, the next trick is to retrieve the updated parameter values as byte arrays and then convert those arrays to actual Java objects.
The reason the data is passed back and forth as byte arrays is that all data sent across a network must be send as a byte stream, as that is how computers communicate with each other. You need help to determine the byte length given the data type and digit length of a parameter, and you need help to convert the data to a byte stream and back again (and from Unicode to EBCDIC, for text). As it turns out, the Toolbox supplies all this help, in the form of a set of helper classes, one per AS/400 data type.
A Java class named CUSTBAL, which calls an RPG program, will illustrate all this. This program takes a 10-digit (four-byte), unsigned integer value representing a customer ID, and a 7.2 packed decimal parameter that will be updated to hold this customer's current balance. The first parameter is input-only, while the second parameter is output-only. Here is the simple RPG program, just for testing purposes:
H DFTACTGRP(*NO) ACTGRP('QILE') D*-------------------------------------- D* Prototype for main entry D*-------------------------------------- D CUSTBAL PR EXTPGM('CUSTBAL') D Id 10U 0 D Balance 7P 2 D*-------------------------------------- D* Actual main entry D*-------------------------------------- D CUSTBAL PI D Id 10U 0 D Balance 7P 2 C EVAL Balance = 76543.21 C EVAL *INLR = *ON
As you can see, RPG IV prototyping defines the parameters, and the return value is hard-coded, but this is enough to test calling an RPG program with parameters from Java. You are welcome to create and populate a database, and enhance this example to use a CHAIN operation to get the requested record.
The Java class that calls this program will need to have two elements in its ProgramParameter array. To create a ProgramParameter object, you need to pass to its constructor the length for that parameter, in bytes. The helper classes from the Toolbox help with that. For the four-byte unsigned integer (10 digits equals four bytes), use the AS400UnsignedBin4 class, and for the packed decimal, use the AS400PackedDecimal class. The former is instantiated with no parameters, while the latter requires you to specify the total length and decimal digits (seven and two in this example).
Here are two instance variables for this:
protected AS400UnsignedBin4 parm1Converter = new AS400UnsignedBin4(); protected AS400PackedDecimal parm2Converter = new AS400PackedDecimal(7,2);
All the data-type helper classes contain a method named getByteLength that returns the number of bytes a parameter of this type will require in the resulting byte stream. This makes it easy to create the ProgramParameter array entries, as you can see here:
protected ProgramParameter[] setPgmParms() { parms = new ProgramParameter[2]; parms[0] = new ProgramParameter(parm1Converter.getByteLength()); parms[1] = new ProgramParameter(parm2Converter.getByteLength()); return parms; }
The next requirement is to actually set the data for input parameters. The data-type helper objects help here, as well. In the example, only the first parameter (customer ID) requires input, as the other is output-only.
To set data, use the setInputData method of the ProgramParameter object. However, this takes only a byte array as input, so use the AS400UnsignedBin4 helper object parm2Converter to convert the data into a byte array. All these helper classes support a toBytes method, which takes a Java object as input and gives back a byte array version of that object. The type of that Java object input is different for each of the helper classes.
For AS400UnsignedBin4, it requires a Long object. This might surprise you, since long integers are eight bytes in Java, but this is because unsigned four-byte numbers won't fit in signed four-byte fields, so the next size up is required.
To allow callers of the Java class to set the customer ID, you'll need to supply a method that takes an int value, converts it to a Long object, and finally uses the toBytes method of the helper object to turn that into a byte array. That byte array is subsequently passed as input into the setInputData method on the ProgramParameter array entry for the first parameter. Here is the method:
public void setCustId(int custID) { custIdObj = new Long(custID); try { parms[0].setInputData(parm1Converter.toBytes(custIdObj)); } catch (Exception exc) {} }
The setInputData method can throw exceptions, so you have to use a try/catch block.
Now you are ready to call the program, with a parms array. You can do this by simply using the same callPgm method from Listing A.2. After callers call the program, they will want to retrieve the customer balance, which is now sitting in the second ProgramParameter array element. To get this value out, you only have a getOutputData method, which gives the result in a byte array-that's not very useful!
What you want instead is a BigDecimal object, Java's equivalent to RPG's packed decimal. Again, use a helper object, this time the AS400PackedDecimal object named parm2Converter. You simply use its toObject method, which converts a byte array into (in this case) a 7.2 BigDecimal object. Once again, a method hides the complexity from callers:
public BigDecimal getCustBalance() { BigDecimal custBalance = null; if (pgmRanOk) custBalance = (BigDecimal) parm2Converter.toObject(parms[1].getOutputData()); return custBalance; }
The assumption is that callers will call this method right after callPgm, in which case you need to be sure that was successful. That's the reason for the code to check the pgmRanOk instance variable. Also, the toObject method requires you to cast the result.
Listing A.3 is the full class, named consistently with the target program. The idea is to instantiate the class, call the setXXX methods for each input parameter, call the program via the callPgm method, and then extract the results by calling the getXXX methods for each output parameter.
Listing A.3: A Java Class Encapsulating the CUSTBAL Program, which Takes Parameters
import com.ibm.as400.access.*; import java.math.*; public class CustBal { *** SAME INSTANCE VARIABLES AS IN LISTING A.2 *** protected AS400UnsignedBin4 parm1Converter = new AS400UnsignedBin4(); protected AS400PackedDecimal parm2Converter = new AS400PackedDecimal(7,2); public CustBal(AS400 system400) { this.system400 = system400; pgmName = "CUSTBAL"; pgmLib = "CUSTLIB"; pgmIFSName = QSYSObjectPathName.toPath(pgmLib,pgmName,"PGM"); pgmObj = new ProgramCall(system400); parms = setPgmParms(); // call our helper method { protected ProgramParameter[] setPgmParms() } parms = new ProgramParameter[2]; parms[0] = new ProgramParameter(parm1Converter.getByteLength()); parms[1] = new ProgramParameter(parm2Converter.getByteLength()); return parms; } public void setCustId(int custID) { Long custIdObj = new Long(custID); try { parms[0].setInputData(parm1Converter.toBytes(custIdObj)); } catch (Exception exc) {} } public BigDecimal getCustBalance() { { BigDecimal custBalance = null; if (pgmRanOk) custBalance = (BigDecimal) parm2Converter.toObject(parms[1].getOutputData()); return custBalance; } public boolean callPgm() { *** SAME AS LISTING A.2 *** } }
As usual, a main method is supplied to test this:
public static void main(String args[]) { AS400 system400 = new AS400(); try { system400.connectService(AS400.COMMAND); } catch (Exception exc) { System.out.println("Error connecting: " + exc.getMessage()); System.exit(1); } CustBal custBal = new CustBal(system400); custBal.setCustId(123456); custBal.callPgm(); BigDecimal balance = custBal.getCustBalance(); System.out.println("Customer Balance: " + balance); system400.disconnectAllServices(); System.exit(1); }
When you run the class, you get the following:
ListingA-3>java CustBal Calling pgm CUSTBAL... Result = true Customer balance: 76543.21
It worked!
Did you notice a lot of redundant code between the first program-call class in Listing A.2 and the second in Listing A.3? As you write more of these, you'll get more redundancy. What is the answer to redundancy in Java? Inheritance! We recommend you create an abstract base class that all your program-call classes extend. It can supply all the common instance variables, the common constructor code, and even the common callPgm method. Your unique child classes per program will override the setPgmParms method and add the appropriate setXXX and getXXX method for the input and output parameters. Also, each child class would supply its own main method for testing. This isn't shown here, but you can see it on the CD-ROM in the UsingCommonParent subdirectory of the ListingA-3 directory. The parent class is named AS400Program.
Also included in the parent class is a method named addLibraryListEntry that takes a library name as input and adds it to the library list, using the CommandCall class. Most programs you call require the library list to be set up properly. You might want to supply a similar method for doing file overrides. Each child class can call these inherited methods in their constructors. You might also want to supply a default constructor so the class can be used as a bean, for example in the Visual Composition Editor of VisualAge for Java. To support a default constructor, put the code currently in the constructor into its own init method that takes an AS400 object as a parameter. Callers will then have to be instructed to call the init method immediately after instantiating the object.
By the way, the other data-type helper classes (besides AS400UnsignedBin4 and AS400PackedDecimal) are listed in Table A.7.
Toolbox Class |
OS/400 Data Type |
Java Data Type |
---|---|---|
AS400Bin2 |
Signed two-byte numeric |
short |
AS400Bin4 |
Signed four-byte numeric |
int |
AS400ByteArray |
Hexadecimal, or any type |
byte[] |
AS400Float4 |
Signed four-byte floating-point |
float |
AS400Float8 |
Signed eight-byte floating-point |
double |
AS400PackedDecimal |
Packed-decimal numeric |
BigDecimal in java.math |
AS400UnsignedBin2 |
Unsigned two-byte numeric |
short |
AS400UnsignedBin4 |
Unsigned four-byte numeric |
long |
AS400ZonedDecimal |
Zoned-decimal numeric |
BigDecimal in java.math |
AS400Text |
Character |
String (Unicode) |
AS400Array |
Array |
An array of other data types |
AS400Structure |
Structure |
A structure of other data types |
PCML for easy calling of programs and service programs
As you have seen, manually coding to the ProgramCall class is tedious. The Toolbox designers have recognized this and added an option that makes it significantly easier. They have invented an XML-based language named PCML, or Program Call Markup Language. It is a simple, tag-based language that can be typed into a source file, where tags are used to define the program to call and the parameter attributes for that call. Having typed this in, you can then simply use the ProgramCallDocument class in the Toolbox to read and parse that PCML file and automatically convert it into the necessary ProgramCall and related objects. You can do this for every call, or do it once and serialize the results for better performance.
Let's look again at the example program CUSTBAL in library CUSTLIB, which takes one input parameter and one output parameter. The input parameter is a 10-digit, unsigned integer value holding the customer ID. The output parameter will be updated to hold the packed-decimal 7.2 customer balance value. Listing A.4 shows what the PCML file
Listing A.4: A PCML File CustBal.pcml for Calling an AS/400 Program