Three ways I called an RPGLE program from Java
A recent project had me comparing different ways to call an RPGLE program using Java. I am a newbie / novice Java programmer, so this gave a lot of insight into the Java language and some of the JT400 / JTOpen tools.
To start, I had this RPGLE program. It retrieves, increments, sets, and returns a general ledger (GL) journal entry sequence number.
//////// remove this line
// Program: incgljseq
// Retrieve last used journal entry sequence; increment and store;
// return new sequence.
Fdbaxrel0 UF E K DISK
d incgljseq pr extpgm('INCGLJSEQ')
d inlocation 5a const
d outuseseq 10i 0
d incgljseq pi
d inlocation 5a const
d outuseseq 10i 0
d sequence s 10i 0
/free
sequence = 0;
chain inlocation @axrecd;
if %found;
axlgnb += 1;
sequence = axlgnb;
update @axrecd;
endif;
outuseseq = sequence;
*inlr = *on;
return;
/end-free
Originally, I intended to call the program using a stored procedure. In the course of the project I decided to pursue calling the RPGLE program directly. In the end, I called the program using PCML, but would consider JDBC due to its brevity.
Here is the stored procedure definition.
create procedure rtvgljseq (in location char(5), out newseq integer) external name incgljseq language rpgle parameter style general ;
The integration tool I was using, Extol Business Integrator, has special parameter requirements when using Java programs a particular way. Because of this, I used the main() method as a testbed, enabling me to call the program from the command line, and later in Eclipse when developing the other calls.
I investigated three different ways to call the RPGLE program: using ProgramCall (call the program directly), ProgramCallDocument (call the program using PCML, which is like a prototyped call), and JDBC. The main method is the same for all three classes:
public static void main(String[] args) {
// Exerciser program.
Object[] parms = new Object[5];
// parms[0] receives the output, nextSequence
parms[1] = "192.168.0.1"; // IP address of IBM i system
parms[2] = "USERID"; // user id to sign on
parms[3] = "PASSWORD"; // password for user id
parms[4] = "LOC"; // location from which to retrieve the next sequence
try {
System.out.println("Input : '" + parms[4] + "'");
execute(parms, null, null);
System.out.println("Output: '" + parms[0] + "'");
}
catch (Exception e) {
System.out.println(e.toString());
e.printStackTrace();
}
}
For all three classes, the execute() method calls the RPGLE program and returns the next sequence. First, here is the call using ProgramCall:
public static synchronized void execute(Object[] parms, StateTableInstructionState stis, MethodMessages mm) {
int nextSequence = 0;
String sysname = (String) parms[1];
String userid = (String) parms[2];
String password = (String) parms[3];
String vanguardLocation = (String) parms[4];
AS400 sys = new AS400(sysname, userid, password);
try {
String msgId, msgText;
// Set up library list for program call.
CommandCall command = new CommandCall(sys);
if (command.run("CHGLIBL LIBL(PROD_MOD VNGDBDTA)") != true) {
AS400Message[] messageList = command.getMessageList();
for (int i = 0; i < messageList.length; i++) {
msgId = messageList[i].getID();
msgText = messageList[i].getText();
System.err.println(msgId + " - " + msgText);
}
}
// Create field types for parameters.
AS400Text txt5 = new AS400Text(5);
// Create parameter array and populate.
ProgramParameter[] parmList = new ProgramParameter[2];
parmList[0] = new ProgramParameter(txt5.toBytes(vanguardLocation));
parmList[1] = new ProgramParameter(4);
// Set up program call and run.
ProgramCall pgm = new ProgramCall(sys, "/QSYS.LIB/PROD_MOD.LIB/INCGLJSEQ.PGM", parmList);
if (pgm.run() != true) {
AS400Message[] messageList = pgm.getMessageList();
for (int i = 0; i < messageList.length; i++) {
msgId = messageList[i].getID();
msgText = messageList[i].getText();
System.err.println(msgId + " - " + msgText);
}
} else {
AS400Bin4 bin4Converter = new AS400Bin4();
byte[] data = parmList[1].getOutputData();
nextSequence = bin4Converter.toInt(data);
}
} catch (Exception e) {
System.err.println(e.getMessage());
e.printStackTrace();
}
sys.disconnectAllServices();
parms[0] = Integer.toString(nextSequence);
}
Don’t worry about the execute() method declaration; that is special for Extol Business Integrator (EBI). Also disregard the conversion of nextSequence from integer to string; again, this is to work around an issue I had with EBI.
The ProgramCall code has lots of parts: conversions from Java to IBM i (AS/400) data types and back, setting up the library list, and handling fro retrieving IBM i error messages if necessary.
A little shorter way is to call using PCML. PCML is a tagged document similar to XML which is a “prototype” for calling an RPGLE program from Java. For more information about compiling a program to generate the PCML file see the post http://code.blackrobes.net/calling-rpgle-programs-with-java-and-pcml/.
The PCML file defines the interface to the called program, including the program’s location, and number and type of parameters. The PCML’s file name is incgljseq.pcml.
<pcml version="4.0">
<!-- RPG program: INCGLJSEQ -->
<!-- created: 2009-10-30-13.26.10 -->
<!-- source: LOYD/SOURCE(INCGLJSEQ) -->
<!-- 18 -->
<program name="INCGLJSEQ" path="/QSYS.LIB/LOYD.LIB/INCGLJSEQ.PGM">
<data name="INLOCATION" type="char" length="5" usage="input" />
<data name="OUTUSESEQ" type="int" length="4" precision="31" usage="inputoutput" />
</program>
</pcml>
And here is the Java code to call using PCML. Note on line 20, we do not have to give the PCML’s extension when passing the file name into ProgramCallDocument. Under the covers, the method looks for a serialized version with extension .pcml.ser, or the regular file with extension .pcml.
public static synchronized void execute(Object[] parms, StateTableInstructionState stis, MethodMessages mm) {
int nextSequence = 0;
String sysname = (String) parms[1];
String userid = (String) parms[2];
String password = (String) parms[3];
String vanguardLocation = (String) parms[4];
AS400 sys = new AS400(sysname, userid, password);
try {
String msgId, msgText;
// Set up library list for program call.
CommandCall command = new CommandCall(sys);
if (command.run("CHGLIBL LIBL(PROD_MOD VNGDBDTA)") != true) {
AS400Message[] messageList = command.getMessageList();
for (int i = 0; i < messageList.length; i++) {
msgId = messageList[i].getID();
msgText = messageList[i].getText();
System.err.println(msgId + " - " + msgText);
}
}
ProgramCallDocument pcml = new ProgramCallDocument(sys, "incgljseq");
pcml.setValue("INCGLJSEQ.INLOCATION", vanguardLocation);
pcml.setValue("INCGLJSEQ.OUTUSESEQ", nextSequence);
if (pcml.callProgram("INCGLJSEQ") != true) {
AS400Message[] messageList = pcml.getMessageList("INCGLJSEQ");
for (int i = 0; i < messageList.length; i++) {
msgId = messageList[i].getID();
msgText = messageList[i].getText();
System.err.println(msgId + " - " + msgText);
}
} else {
nextSequence = (Integer) pcml.getValue("INCGLJSEQ.OUTUSESEQ");
}
}
catch (Exception e) {
System.err.println(e.getMessage());
e.printStackTrace();
}
sys.disconnectAllServices();
parms[0] = Integer.toString(nextSequence);
}
Finally, here is the call using JDBC, using the stored procedure definition listed above. It has by far the fewest lines of code, and eliminates the need for multiple conversions from Java types to IBM i types. It also elminiates the need of a separate call for setting the library list. (This can be done by calling a CL program wrapper, but this exercise is calling the RPGLE program.) One disadvantage of JDBC is being limited to using “simple” parameter types. Contrast with the PCML call, which can use complex parameters, such as RPG data structures.
public static synchronized void execute(Object[] parms, StateTableInstructionState stis, MethodMessages mm) {
int nextSequence = 0;
String sysname = (String) parms[1];
String userid = (String) parms[2];
String password = (String) parms[3];
String vanguardLocation = (String) parms[4];
try {
DriverManager.registerDriver(new com.ibm.as400.access.AS400JDBCDriver());
Connection conn = DriverManager.getConnection("jdbc:as400://" + sysname + ";naming=system; libraries=,prod_dta,prod_mod,vngdbdta", userid, password);
CallableStatement cs = conn.prepareCall("call rtvgljseq(?,?)");
cs.registerOutParameter(2, java.sql.Types.INTEGER);
cs.setString(1, vanguardLocation);
cs.execute();
nextSequence = cs.getInt(2);
cs.close();
}
catch (Exception e) {
System.err.println(e.getMessage());
e.printStackTrace();
}
parms[0] = Integer.toString(nextSequence);
}
My preference is to use either PCML or JDBC. The JDBC code is very brief and hides or elminiates variable casting. However, it is really suitable for simple parameter types, and requires an extra IBM i object (the stored procedure definition). PCML is nice because like JDBC, it describes the number and kind of parameters. However, there is a separate system connection and call to set the library list if needed. Like JDBC an extra object is needed (the PCML file) but rather than server-side, it is bundled with the Java code.
This post pulled together and briefly compared various ways to call an ILE RPG program from Java.
Links:
- IBM Toolbox for Java / JT400.jar:
http://publib.boulder.ibm.com/infocenter/iseries/v5r4/index.jsp?topic=/rzahh/page1.htm - PCML (Program Call Markup Language) overview:
http://publib.boulder.ibm.com/infocenter/iseries/v5r4/index.jsp?topic=/rzahh/pcml.htm - Extol Business Integrator:
http://www.extol.com/index.php/products/355-extol-ebi-1